editor.cljs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. (ns frontend.handler.db-based.editor
  2. "DB-based graph implementation"
  3. (:require [clojure.string :as string]
  4. [frontend.config :as config]
  5. [frontend.commands :as commands]
  6. [frontend.db :as db]
  7. [frontend.format.block :as block]
  8. [frontend.format.mldoc :as mldoc]
  9. [frontend.util :as util]
  10. [frontend.state :as state]
  11. [logseq.common.util.page-ref :as page-ref]
  12. [frontend.handler.ui :as ui-handler]
  13. [frontend.handler.common.config-edn :as config-edn-common-handler]
  14. [frontend.handler.property :as property-handler]
  15. [frontend.handler.property.util :as pu]
  16. [frontend.handler.repo-config :as repo-config-handler]
  17. [frontend.modules.outliner.ui :as ui-outliner-tx]
  18. [frontend.modules.outliner.op :as outliner-op]
  19. [frontend.schema.handler.repo-config :as repo-config-schema]
  20. [promesa.core :as p]
  21. [logseq.db.frontend.content :as db-content]))
  22. (defn- remove-non-existed-refs!
  23. [refs]
  24. (remove (fn [x] (or
  25. (and (vector? x)
  26. (= :block/uuid (first x))
  27. (nil? (db/entity x)))
  28. (nil? x))) refs))
  29. (defn- replace-tag-ref
  30. [content page-name id]
  31. (let [id' (str db-content/page-ref-special-chars id)
  32. [page wrapped-id] (if (string/includes? page-name " ")
  33. (map page-ref/->page-ref [page-name id'])
  34. [page-name id'])
  35. page-name (util/format "#%s" page)
  36. r (util/format "#%s" wrapped-id)]
  37. ;; hash tag parsing rules https://github.com/logseq/mldoc/blob/701243eaf9b4157348f235670718f6ad19ebe7f8/test/test_markdown.ml#L631
  38. ;; Safari doesn't support look behind, don't use
  39. ;; TODO: parse via mldoc
  40. (string/replace content
  41. (re-pattern (str "(?i)(^|\\s)(" (util/escape-regex-chars page-name) ")(?=[,\\.]*($|\\s))"))
  42. ;; case_insense^ ^lhs ^_grp2 look_ahead^ ^_grp3
  43. (fn [[_match lhs _grp2 _grp3]]
  44. (str lhs r)))))
  45. (defn- replace-page-ref
  46. [content page-name id]
  47. (let [id' (str db-content/page-ref-special-chars id)
  48. [page wrapped-id] (map page-ref/->page-ref [page-name id'])]
  49. (util/replace-ignore-case content page wrapped-id)))
  50. (defn- replace-page-ref-with-id
  51. [content page-name id]
  52. (-> content
  53. (replace-page-ref page-name id)
  54. (replace-tag-ref page-name id)))
  55. (defn- replace-page-refs-with-ids
  56. [block]
  57. (let [content (:block/content block)
  58. content' (if (some :block/name (:block/refs block))
  59. (reduce
  60. (fn [content {:block/keys [original-name uuid]}]
  61. (replace-page-ref-with-id content original-name uuid))
  62. content
  63. (filter :block/name (:block/refs block)))
  64. content)]
  65. (assoc block :block/content content')))
  66. (defn wrap-parse-block
  67. [{:block/keys [content left level tags] :as block}]
  68. (let [block (or (and (:db/id block) (db/pull (:db/id block))) block)
  69. block (if (nil? content)
  70. block
  71. (let [ast (mldoc/->edn (string/trim content) :markdown)
  72. first-elem-type (first (ffirst ast))
  73. block-with-title? (mldoc/block-with-title? first-elem-type)
  74. content' (str (config/get-block-pattern :markdown) (if block-with-title? " " "\n") content)
  75. block (merge block (block/parse-block (assoc block :block/content content')))]
  76. (update block :block/refs remove-non-existed-refs!)))
  77. block (if (and left (not= (:block/left block) left)) (assoc block :block/left left) block)
  78. result (-> block
  79. (merge (if level {:block/level level} {}))
  80. (replace-page-refs-with-ids))]
  81. (-> result
  82. ;; Remove tags from content
  83. (assoc :block/content
  84. (db-content/content-without-tags
  85. (:block/content result)
  86. (->>
  87. (map
  88. (fn [tag]
  89. (when (:block/uuid tag)
  90. (str db-content/page-ref-special-chars (:block/uuid tag))))
  91. (:block/tags result))
  92. (remove nil?))))
  93. ;; Remove :block/tags built from mldoc
  94. (dissoc :block/tags)
  95. ;; Add tags back
  96. (assoc :block/tags tags))))
  97. (defn save-file!
  98. "This fn is the db version of file-handler/alter-file"
  99. [path content]
  100. (let [file-valid? (if (= path "logseq/config.edn")
  101. (do (config-edn-common-handler/detect-deprecations path content {:db-graph? true})
  102. (config-edn-common-handler/validate-config-edn path content repo-config-schema/Config-edn))
  103. true)]
  104. (when file-valid?
  105. (db/transact! [{:file/path path
  106. :file/content content
  107. :file/last-modified-at (js/Date.)}])
  108. ;; Post save
  109. (cond (= path "logseq/config.edn")
  110. (p/let [_ (repo-config-handler/restore-repo-config! (state/get-current-repo) content)]
  111. (state/pub-event! [:shortcut/refresh]))
  112. (= path "logseq/custom.css")
  113. (ui-handler/add-style-if-exists!)))))
  114. (defn- set-heading-aux!
  115. [block-id heading]
  116. (let [block (db/pull [:block/uuid block-id])
  117. old-heading (pu/lookup (:block/properties block) :heading)]
  118. (cond
  119. ;; nothing changed for first two cases
  120. (or (and (nil? old-heading) (nil? heading))
  121. (and (true? old-heading) (true? heading))
  122. (= old-heading heading))
  123. nil
  124. (or (and (nil? old-heading) (true? heading))
  125. (and (true? old-heading) (nil? heading)))
  126. nil
  127. (and (or (nil? heading) (true? heading))
  128. (number? old-heading))
  129. (let [content (commands/clear-markdown-heading (:block/content block))]
  130. {:block/content content
  131. :block/uuid (:block/uuid block)})
  132. (and (or (nil? old-heading) (true? old-heading))
  133. (number? heading))
  134. (let [content (commands/set-markdown-heading (:block/content block) heading)]
  135. {:block/content content
  136. :block/uuid (:block/uuid block)})
  137. ;; heading-num1 -> heading-num2
  138. :else
  139. (let [content (-> block
  140. :block/content
  141. commands/clear-markdown-heading
  142. (commands/set-markdown-heading heading))]
  143. {:block/uuid (:block/uuid block)
  144. :block/content content}))))
  145. (defn batch-set-heading!
  146. [repo block-ids heading]
  147. (ui-outliner-tx/transact!
  148. {:outliner-op :save-block}
  149. (doseq [block (keep #(set-heading-aux! % heading) block-ids)]
  150. (outliner-op/save-block! block))
  151. (property-handler/batch-set-block-property! repo block-ids :heading heading)))