page.cljs 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. (ns frontend.worker.handler.page.db-based.page
  2. "Page operations for DB graphs"
  3. (:require [clojure.string :as string]
  4. [datascript.core :as d]
  5. [datascript.impl.entity :as de]
  6. [logseq.common.util :as common-util]
  7. [logseq.common.util.namespace :as ns-util]
  8. [logseq.db :as ldb]
  9. [logseq.db.frontend.class :as db-class]
  10. [logseq.db.frontend.order :as db-order]
  11. [logseq.db.frontend.property.build :as db-property-build]
  12. [logseq.db.frontend.property.util :as db-property-util]
  13. [logseq.db.sqlite.util :as sqlite-util]
  14. [logseq.graph-parser.block :as gp-block]
  15. [logseq.graph-parser.text :as text]
  16. [logseq.outliner.validate :as outliner-validate]
  17. [logseq.db.frontend.entity-util :as entity-util]))
  18. (defn- build-page-tx [conn properties page {:keys [whiteboard? class? tags]}]
  19. (when (:block/uuid page)
  20. (let [type-tag (cond class? :logseq.class/Tag
  21. whiteboard? :logseq.class/Whiteboard
  22. :else :logseq.class/Page)
  23. tags' (conj tags type-tag)
  24. page' (update page :block/tags
  25. (fnil into [])
  26. (mapv (fn [tag]
  27. (let [v (if (uuid? tag)
  28. (d/entity @conn [:block/uuid tag])
  29. tag)]
  30. (cond
  31. (de/entity? v)
  32. (:db/id v)
  33. (map? v)
  34. (:db/id v)
  35. :else
  36. v)))
  37. tags'))
  38. property-vals-tx-m
  39. ;; Builds property values for built-in properties like logseq.property.pdf/file
  40. (db-property-build/build-property-values-tx-m
  41. page'
  42. (->> properties
  43. (keep (fn [[k v]]
  44. ;; TODO: Pass in property type in order to support property
  45. ;; types other than :default
  46. (when (db-property-util/built-in-has-ref-value? k)
  47. [k v])))
  48. (into {})))]
  49. (cond-> [(if class? (db-class/build-new-class @conn page') page')]
  50. (seq property-vals-tx-m)
  51. (into (vals property-vals-tx-m))
  52. true
  53. (conj (merge {:block/uuid (:block/uuid page)}
  54. properties
  55. (db-property-build/build-properties-with-ref-values property-vals-tx-m)))))))
  56. ;; TODO: Revisit title cleanup as this was copied from file implementation
  57. (defn sanitize-title
  58. [title]
  59. (let [title (-> (string/trim title)
  60. (text/page-ref-un-brackets!)
  61. ;; remove `#` from tags
  62. (string/replace #"^#+" ""))
  63. title (common-util/remove-boundary-slashes title)]
  64. title))
  65. (defn build-first-block-tx
  66. [page-uuid format]
  67. (let [page-id [:block/uuid page-uuid]]
  68. [(sqlite-util/block-with-timestamps
  69. {:block/uuid (ldb/new-block-id)
  70. :block/page page-id
  71. :block/parent page-id
  72. :block/order (db-order/gen-key nil nil)
  73. :block/title ""
  74. :block/format format})]))
  75. (defn- get-page-by-parent-name
  76. [db parent-title child-title]
  77. (some->>
  78. (d/q
  79. '[:find [?b ...]
  80. :in $ ?parent-name ?child-name
  81. :where
  82. [?b :logseq.property/parent ?p]
  83. [?b :block/name ?child-name]
  84. [?p :block/name ?parent-name]]
  85. db
  86. (common-util/page-name-sanity-lc parent-title)
  87. (common-util/page-name-sanity-lc child-title))
  88. first
  89. (d/entity db)))
  90. (defn- split-namespace-pages
  91. [db page date-formatter]
  92. (let [{:block/keys [title] block-uuid :block/uuid} page]
  93. (->>
  94. (if (and (or (entity-util/class? page)
  95. (entity-util/page? page))
  96. (ns-util/namespace-page? title))
  97. (let [class? (entity-util/class? page)
  98. parts (->> (string/split title ns-util/parent-re)
  99. (map string/trim)
  100. (remove string/blank?))
  101. pages (doall
  102. (map-indexed
  103. (fn [idx part]
  104. (let [last-part? (= idx (dec (count parts)))
  105. page (if (zero? idx)
  106. (ldb/get-page db part)
  107. (get-page-by-parent-name db (nth parts (dec idx)) part))
  108. result (or page
  109. (-> (gp-block/page-name->map part db true date-formatter
  110. {:page-uuid (when last-part? block-uuid)
  111. :skip-existing-page-check? true
  112. :class? class?})
  113. (assoc :block/format :markdown)))]
  114. result))
  115. parts))]
  116. (cond
  117. (and (not class?) (not (every? ldb/internal-page? pages)))
  118. (throw (ex-info "Cannot create this page unless all parents are pages"
  119. {:type :notification
  120. :payload {:message "Cannot create this page unless all parents are pages"
  121. :type :warning}}))
  122. (and class? (not (every? ldb/class? pages)))
  123. (throw (ex-info "Cannot create this tag unless all parents are tags"
  124. {:type :notification
  125. :payload {:message "Cannot create this tag unless all parents are tags"
  126. :type :warning}}))
  127. :else
  128. (map-indexed
  129. (fn [idx page]
  130. (let [parent-eid (when (> idx 0)
  131. (when-let [id (:block/uuid (nth pages (dec idx)))]
  132. [:block/uuid id]))]
  133. (if class?
  134. (cond
  135. (and (de/entity? page) (ldb/class? page))
  136. (assoc page :logseq.property/parent parent-eid)
  137. (de/entity? page) ; page exists but not a class, avoid converting here because this could be troublesome.
  138. nil
  139. (zero? idx)
  140. (db-class/build-new-class db page)
  141. :else
  142. (db-class/build-new-class db (assoc page :logseq.property/parent parent-eid)))
  143. (if (or (de/entity? page) (zero? idx))
  144. page
  145. (assoc page :logseq.property/parent parent-eid)))))
  146. pages)))
  147. [page])
  148. (remove nil?))))
  149. (defn create!
  150. [conn title*
  151. {:keys [create-first-block? properties uuid persist-op? whiteboard? class? today-journal? split-namespace? skip-existing-page-check?]
  152. :or {create-first-block? true
  153. properties nil
  154. uuid nil
  155. persist-op? true
  156. skip-existing-page-check? false}
  157. :as options}]
  158. (let [db @conn
  159. date-formatter (:logseq.property.journal/title-format (d/entity db :logseq.class/Journal))
  160. title (sanitize-title title*)
  161. type (cond class?
  162. :logseq.class/Tag
  163. whiteboard?
  164. :logseq.class/Whiteboard
  165. today-journal?
  166. :logseq.class/Journal
  167. :else
  168. :logseq.class/Page)]
  169. (when-not (ldb/page-exists? db title #{type})
  170. (let [format :markdown
  171. page (-> (gp-block/page-name->map title @conn true date-formatter
  172. {:class? class?
  173. :page-uuid (when (uuid? uuid) uuid)
  174. :skip-existing-page-check? (if (some? skip-existing-page-check?)
  175. skip-existing-page-check?
  176. true)})
  177. (assoc :block/format format))
  178. [page parents] (if (and (text/namespace-page? title) split-namespace?)
  179. (let [pages (split-namespace-pages db page date-formatter)]
  180. [(last pages) (butlast pages)])
  181. [page nil])]
  182. (when page
  183. ;; Don't validate journal names because they can have '/'
  184. (when (not= :logseq.class/Journal type)
  185. (outliner-validate/validate-page-title-characters (str (:block/title page)) {:node page})
  186. (doseq [parent parents]
  187. (outliner-validate/validate-page-title-characters (str (:block/title parent)) {:node parent})))
  188. (let [page-uuid (:block/uuid page)
  189. page-txs (build-page-tx conn properties page (select-keys options [:whiteboard? :class? :tags]))
  190. first-block-tx (when (and
  191. (nil? (d/entity @conn [:block/uuid page-uuid]))
  192. create-first-block?
  193. (not (or whiteboard? class?))
  194. page-txs)
  195. (build-first-block-tx (:block/uuid (first page-txs)) format))
  196. txs (concat
  197. parents
  198. page-txs
  199. first-block-tx)]
  200. (when (seq txs)
  201. (ldb/transact! conn txs (cond-> {:persist-op? persist-op?
  202. :outliner-op :create-page}
  203. today-journal?
  204. (assoc :create-today-journal? true
  205. :today-journal-name title))))
  206. [title page-uuid]))))))