block.cljs 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924
  1. (ns logseq.graph-parser.block
  2. "Given mldoc ast, prepares block data in preparation for db transaction.
  3. Used by file and DB graphs"
  4. (:require [clojure.set :as set]
  5. [clojure.string :as string]
  6. [clojure.walk :as walk]
  7. [datascript.core :as d]
  8. [datascript.impl.entity :as de]
  9. [logseq.common.config :as common-config]
  10. [logseq.common.date :as common-date]
  11. [logseq.common.util :as common-util]
  12. [logseq.common.util.block-ref :as block-ref]
  13. [logseq.common.util.date-time :as date-time-util]
  14. [logseq.common.util.page-ref :as page-ref]
  15. [logseq.common.uuid :as common-uuid]
  16. [logseq.db :as ldb]
  17. [logseq.db.common.order :as db-order]
  18. [logseq.db.frontend.class :as db-class]
  19. [logseq.graph-parser.mldoc :as gp-mldoc]
  20. [logseq.graph-parser.property :as gp-property]
  21. [logseq.graph-parser.text :as text]
  22. [logseq.graph-parser.utf8 :as utf8]))
  23. (defn heading-block?
  24. [block]
  25. (and
  26. (vector? block)
  27. (= "Heading" (first block))))
  28. (defn get-tag
  29. [block]
  30. (when-let [tag-value (and (vector? block)
  31. (= "Tag" (first block))
  32. (second block))]
  33. (->> tag-value
  34. (map (fn [[elem value]]
  35. (case elem
  36. "Plain" value
  37. "Link" (:full_text value)
  38. "Nested_link" (:content value)
  39. "")))
  40. (string/join))))
  41. (defn get-page-reference
  42. [block format]
  43. (let [page (cond
  44. (and (vector? block) (= "Link" (first block)))
  45. (let [url-type (first (:url (second block)))
  46. value (second (:url (second block)))]
  47. ;; {:url ["File" "file:../pages/hello_world.org"], :label [["Plain" "hello world"]], :title nil}
  48. (or
  49. (and
  50. (= url-type "Page_ref")
  51. (and (string? value)
  52. (not (or (common-config/local-asset? value)
  53. (common-config/draw? value))))
  54. value)
  55. (and
  56. (= url-type "Search")
  57. (page-ref/page-ref? value)
  58. (text/page-ref-un-brackets! value))
  59. (and (= url-type "Search")
  60. (= format :org)
  61. (not (common-config/local-asset? value))
  62. value)
  63. (and
  64. (= url-type "File")
  65. (second (first (:label (second block)))))))
  66. (and (vector? block) (= "Nested_link" (first block)))
  67. (let [content (:content (last block))]
  68. (subs content 2 (- (count content) 2)))
  69. (and (vector? block)
  70. (= "Macro" (first block)))
  71. (let [{:keys [name arguments]} (second block)
  72. argument (string/join ", " arguments)]
  73. (if (= name "embed")
  74. (when (page-ref/page-ref? argument)
  75. (text/page-ref-un-brackets! argument))
  76. {:type "macro"
  77. :name name
  78. :arguments arguments}))
  79. (and (vector? block)
  80. (= "Tag" (first block)))
  81. (let [text (get-tag block)]
  82. (text/page-ref-un-brackets! text))
  83. :else
  84. nil)]
  85. (when page (or (when (string? page)
  86. (block-ref/get-block-ref-id page))
  87. page))))
  88. (defn get-block-reference
  89. [block]
  90. (when-let [block-id (cond
  91. (and (vector? block)
  92. (= "Block_reference" (first block)))
  93. (last block)
  94. (and (vector? block)
  95. (= "Link" (first block))
  96. (map? (second block))
  97. (= "Block_ref" (first (:url (second block)))))
  98. (second (:url (second block)))
  99. (and (vector? block)
  100. (= "Macro" (first block)))
  101. (let [{:keys [name arguments]} (second block)]
  102. (when (and (= name "embed")
  103. (string? (first arguments))
  104. (block-ref/string-block-ref? (first arguments)))
  105. (block-ref/get-string-block-ref-id (first arguments))))
  106. (and (vector? block)
  107. (= "Link" (first block))
  108. (map? (second block)))
  109. (if (= "id" (:protocol (second (:url (second block)))))
  110. (:link (second (:url (second block))))
  111. (let [id (second (:url (second block)))]
  112. ;; these can be maps
  113. (when (string? id)
  114. (or (block-ref/get-block-ref-id id) id))))
  115. :else
  116. nil)]
  117. (when (some-> block-id parse-uuid)
  118. block-id)))
  119. (defn- paragraph-block?
  120. [block]
  121. (and
  122. (vector? block)
  123. (= "Paragraph" (first block))))
  124. (defn timestamp-block?
  125. [block]
  126. (and
  127. (vector? block)
  128. (= "Timestamp" (first block))))
  129. (defn- get-page-refs-from-property-names
  130. [properties {:property-pages/keys [enabled? excludelist]}]
  131. (if (contains? #{true nil} enabled?)
  132. (sequence
  133. (comp (map (comp name first))
  134. (remove string/blank?)
  135. (remove (set (map name excludelist)))
  136. ;; Remove built-in properties as we don't want pages
  137. ;; created for them by default
  138. (remove (into #{}
  139. (map name)
  140. (apply conj
  141. (apply disj
  142. (gp-property/editable-built-in-properties)
  143. gp-property/editable-linkable-built-in-properties)
  144. (gp-property/hidden-built-in-properties))))
  145. (distinct))
  146. properties)
  147. []))
  148. (defn- extract-refs-from-property-value
  149. [value format]
  150. (cond
  151. (coll? value)
  152. (filter (fn [v] (and (string? v) (not (string/blank? v)))) value)
  153. (and (string? value) (= \" (first value) (last value)))
  154. nil
  155. (string? value)
  156. (let [ast (gp-mldoc/inline->edn value (gp-mldoc/default-config format))]
  157. (text/extract-refs-from-mldoc-ast ast))
  158. :else
  159. nil))
  160. (defn- get-page-ref-names-from-properties
  161. [properties user-config]
  162. (let [page-refs (->>
  163. properties
  164. (remove (fn [[k _]]
  165. (contains?
  166. (set/union (apply disj
  167. (gp-property/editable-built-in-properties)
  168. gp-property/editable-linkable-built-in-properties)
  169. (gp-property/hidden-built-in-properties))
  170. (keyword k))))
  171. ;; get links ast
  172. (map last)
  173. (mapcat (fn [value]
  174. (extract-refs-from-property-value value (get user-config :format :markdown))))
  175. ;; comma separated collections
  176. (concat (->> (map second properties)
  177. (filter coll?)
  178. (apply concat))))
  179. page-refs-from-property-names (get-page-refs-from-property-names properties user-config)]
  180. (->> (concat page-refs page-refs-from-property-names)
  181. (remove string/blank?)
  182. distinct)))
  183. (defn- extract-block-refs
  184. [nodes]
  185. (let [ref-blocks (atom nil)]
  186. (walk/postwalk
  187. (fn [form]
  188. (when-let [block (get-block-reference form)]
  189. (swap! ref-blocks conj block))
  190. form)
  191. nodes)
  192. (keep (fn [block]
  193. (when-let [id (parse-uuid block)]
  194. [:block/uuid id]))
  195. @ref-blocks)))
  196. (defn extract-properties
  197. [properties user-config]
  198. (when (seq properties)
  199. (let [properties (seq properties)
  200. *invalid-properties (atom #{})
  201. properties (->> properties
  202. (map (fn [[k v mldoc-ast]]
  203. (let [k (if (or (keyword? k) (symbol? k))
  204. (subs (str k) 1)
  205. k)
  206. k (-> (string/lower-case k)
  207. (string/replace " " "-")
  208. (string/replace "_" "-"))]
  209. (if (gp-property/valid-property-name? (str ":" k))
  210. (let [k' (keyword
  211. (if (contains? #{"custom_id" "custom-id"} k)
  212. "id"
  213. k))
  214. v' (text/parse-property k v mldoc-ast user-config)]
  215. [k' v' mldoc-ast v])
  216. (do (swap! *invalid-properties conj k)
  217. nil)))))
  218. (remove #(nil? (second %))))
  219. page-refs (get-page-ref-names-from-properties properties user-config)
  220. block-refs (extract-block-refs properties)
  221. properties-text-values (->> (map (fn [[k _v _refs original-text]] [k original-text]) properties)
  222. (into {}))
  223. properties (map (fn [[k v _]] [k v]) properties)
  224. properties' (into {} properties)]
  225. {:properties properties'
  226. :properties-order (map first properties)
  227. :properties-text-values properties-text-values
  228. :invalid-properties @*invalid-properties
  229. :page-refs page-refs
  230. :block-refs block-refs})))
  231. (defn- paragraph-timestamp-block?
  232. [block]
  233. (and (paragraph-block? block)
  234. (or (timestamp-block? (first (second block)))
  235. (timestamp-block? (second (second block))))))
  236. (defn- extract-timestamps
  237. [block]
  238. (some->>
  239. (second block)
  240. (filter timestamp-block?)
  241. (map last)
  242. (into {})))
  243. ;; {"Deadline" {:date {:year 2020, :month 10, :day 20}, :wday "Tue", :time {:hour 8, :min 0}, :repetition [["DoublePlus"] ["Day"] 1], :active true}}
  244. (defn timestamps->scheduled-and-deadline
  245. [timestamps]
  246. (let [timestamps (update-keys timestamps (comp keyword string/lower-case))
  247. m (some->> (select-keys timestamps [:scheduled :deadline])
  248. (map (fn [[k v]]
  249. (let [{:keys [date repetition]} v
  250. {:keys [year month day]} date
  251. day (js/parseInt (str year (common-util/zero-pad month) (common-util/zero-pad day)))]
  252. (cond->
  253. (case k
  254. :scheduled
  255. {:scheduled day}
  256. :deadline
  257. {:deadline day})
  258. repetition
  259. (assoc :repeated? true))))))]
  260. (apply merge m)))
  261. (defn- convert-page-if-journal-impl
  262. "Convert journal file name to user' custom date format"
  263. [original-page-name date-formatter & {:keys [export-to-db-graph?]}]
  264. (when original-page-name
  265. (let [page-name (common-util/page-name-sanity-lc original-page-name)
  266. day (when date-formatter
  267. (date-time-util/journal-title->int
  268. page-name
  269. ;; When exporting, only use the configured date-formatter. Allowing for other date formatters allows
  270. ;; for page names to change which breaks looking up journal refs for unconfigured journal pages
  271. (if export-to-db-graph? [date-formatter] (date-time-util/safe-journal-title-formatters date-formatter))))]
  272. (if day
  273. (let [original-page-name' (date-time-util/int->journal-title day date-formatter)]
  274. [original-page-name' (common-util/page-name-sanity-lc original-page-name') day])
  275. [original-page-name page-name day]))))
  276. (def convert-page-if-journal (memoize convert-page-if-journal-impl))
  277. ;; Hack to detect export as some fns are too deeply nested to be refactored to get explicit option
  278. (def *export-to-db-graph? (atom false))
  279. (defn- page-name-string->map
  280. [original-page-name db date-formatter
  281. {:keys [with-timestamp? page-uuid from-page class? skip-existing-page-check?]}]
  282. (let [db-based? (ldb/db-based-graph? db)
  283. original-page-name (common-util/remove-boundary-slashes original-page-name)
  284. [original-page-name' page-name journal-day] (convert-page-if-journal original-page-name date-formatter {:export-to-db-graph? @*export-to-db-graph?})
  285. namespace? (and (or (not db-based?) @*export-to-db-graph?)
  286. (not (boolean (text/get-nested-page-name original-page-name')))
  287. (text/namespace-page? original-page-name'))
  288. page-entity (when (and db (not skip-existing-page-check?))
  289. (if class?
  290. (ldb/get-case-page db original-page-name')
  291. (ldb/get-page db original-page-name')))
  292. original-page-name' (or from-page (:block/title page-entity) original-page-name')
  293. page (merge
  294. {:block/name page-name
  295. :block/title original-page-name'}
  296. (when (and original-page-name
  297. (not= (string/lower-case original-page-name)
  298. (string/lower-case original-page-name'))
  299. (not @*export-to-db-graph?))
  300. {:block.temp/original-page-name original-page-name})
  301. (if (and class? page-entity (:db/ident page-entity))
  302. {:block/uuid (:block/uuid page-entity)
  303. :db/ident (:db/ident page-entity)}
  304. (let [new-uuid* (if (uuid? page-uuid)
  305. page-uuid
  306. (if journal-day
  307. (common-uuid/gen-uuid :journal-page-uuid journal-day)
  308. (common-uuid/gen-uuid)))
  309. new-uuid (if skip-existing-page-check?
  310. new-uuid*
  311. (or
  312. (cond page-entity (:block/uuid page-entity)
  313. (uuid? page-uuid) page-uuid)
  314. new-uuid*))]
  315. {:block/uuid new-uuid}))
  316. (when namespace?
  317. (let [namespace' (first (common-util/split-last "/" original-page-name))]
  318. (when-not (string/blank? namespace')
  319. {:block/namespace {:block/name (string/trim (common-util/page-name-sanity-lc namespace'))}})))
  320. (when (and with-timestamp? (or skip-existing-page-check? (not page-entity))) ;; Only assign timestamp on creating new entity
  321. (let [current-ms (common-util/time-ms)]
  322. {:block/created-at current-ms
  323. :block/updated-at current-ms}))
  324. (if journal-day
  325. (cond-> {:block/journal-day journal-day}
  326. db-based?
  327. (assoc :block/tags [:logseq.class/Journal])
  328. (not db-based?)
  329. (assoc :block/type "journal"))
  330. {}))]
  331. [page page-entity]))
  332. (defn sanitize-hashtag-name
  333. "This must be kept in sync with its reverse operation in logseq.db.frontend.content"
  334. [s]
  335. (string/replace s "#" "HashTag-"))
  336. (defn page-with-parent-and-order
  337. "Apply to namespace pages"
  338. [db page & {:keys [parent]}]
  339. (let [library (ldb/get-built-in-page db "Library")]
  340. (when (nil? library)
  341. (throw (ex-info "Library page doesn't exist" {})))
  342. (assoc page
  343. :block/parent (or parent (:db/id library))
  344. :block/order (db-order/gen-key))))
  345. ;; TODO: refactor
  346. (defn page-name->map
  347. "Create a page's map structure given a original page name (string).
  348. map as input is supported for legacy compatibility.
  349. `with-timestamp?`: assign timestampes to the map structure.
  350. Useful when creating new pages from references or namespaces,
  351. as there's no chance to introduce timestamps via editing in page
  352. `skip-existing-page-check?`: if true, allows pages to have the same name"
  353. [original-page-name db with-timestamp? date-formatter
  354. & {:keys [page-uuid class?] :as options}]
  355. (when-not (and db (common-util/uuid-string? original-page-name)
  356. (not (ldb/page? (d/entity db [:block/uuid (uuid original-page-name)]))))
  357. (let [db-based? (ldb/db-based-graph? db)
  358. original-page-name (cond-> (string/trim original-page-name)
  359. db-based?
  360. sanitize-hashtag-name)
  361. [page _page-entity] (cond
  362. (and original-page-name (string? original-page-name))
  363. (page-name-string->map original-page-name db date-formatter
  364. (assoc options :with-timestamp? with-timestamp?))
  365. :else
  366. (let [page (cond (and (map? original-page-name) (:block/uuid original-page-name))
  367. original-page-name
  368. (map? original-page-name)
  369. (assoc original-page-name :block/uuid (or page-uuid (d/squuid)))
  370. :else
  371. nil)]
  372. [page nil]))]
  373. (when page
  374. (if db-based?
  375. (let [tags (if class? [:logseq.class/Tag]
  376. (or (:block/tags page)
  377. [:logseq.class/Page]))]
  378. (assoc page :block/tags tags))
  379. (assoc page :block/type (or (:block/type page) "page")))))))
  380. (defn- db-namespace-page?
  381. "Namespace page that're not journal pages"
  382. [db-based? page]
  383. (and db-based?
  384. (text/namespace-page? page)
  385. (not (common-date/valid-journal-title-with-slash? page))))
  386. (defn- ref->map
  387. [db *col {:keys [date-formatter db-based? *name->id tag?]}]
  388. (let [db-based? (or (and db (ldb/db-based-graph? db)) db-based?)
  389. col (remove string/blank? @*col)
  390. children-pages (when-not db-based?
  391. (->> (mapcat (fn [p]
  392. (let [p (if (map? p)
  393. (:block/title p)
  394. p)]
  395. (when (string? p)
  396. (let [p (or (text/get-nested-page-name p) p)]
  397. (when (text/namespace-page? p)
  398. (common-util/split-namespace-pages p))))))
  399. col)
  400. (remove string/blank?)
  401. (distinct)))
  402. col (->> (distinct (concat col children-pages))
  403. (remove nil?))]
  404. (map
  405. (fn [item]
  406. (let [macro? (and (map? item)
  407. (= "macro" (:type item)))]
  408. (when-not macro?
  409. (let [m (page-name->map item db true date-formatter {:class? tag?})
  410. result (cond->> m
  411. (and db-based? tag? (not (:db/ident m)))
  412. (db-class/build-new-class db))
  413. page-name (if db-based? (:block/title result) (:block/name result))
  414. id (get @*name->id page-name)]
  415. (when (nil? id)
  416. (swap! *name->id assoc page-name (:block/uuid result)))
  417. ;; Changing a :block/uuid should be done cautiously here as it can break
  418. ;; the identity of built-in concepts in db graphs
  419. (if id
  420. (assoc result :block/uuid id)
  421. result))))) col)))
  422. (defn- with-page-refs-and-tags
  423. [{:keys [title body tags refs marker priority] :as block} db date-formatter parse-block]
  424. (let [db-based? (and (ldb/db-based-graph? db) (not *export-to-db-graph?))
  425. refs (->> (concat tags refs (when-not db-based? [marker priority]))
  426. (remove string/blank?)
  427. (distinct))
  428. *refs (atom refs)
  429. *structured-tags (atom #{})]
  430. (walk/prewalk
  431. (fn [form]
  432. ;; skip custom queries
  433. (when-not (and (vector? form)
  434. (= (first form) "Custom")
  435. (= (second form) "query"))
  436. (when-let [page (get-page-reference form (get block :format :markdown))]
  437. (when-let [page' (when-not (db-namespace-page? db-based? page)
  438. page)]
  439. (swap! *refs conj page')))
  440. (when-let [tag (get-tag form)]
  441. (let [tag (text/page-ref-un-brackets! tag)]
  442. (when-let [tag' (when-not (db-namespace-page? db-based? tag)
  443. tag)]
  444. (when (common-util/tag-valid? tag')
  445. (swap! *refs conj tag')
  446. (swap! *structured-tags conj tag')))))
  447. form))
  448. (concat title body))
  449. (swap! *refs #(remove string/blank? %))
  450. (let [*name->id (atom {})
  451. ref->map-options {:db-based? db-based?
  452. :date-formatter date-formatter
  453. :*name->id *name->id}
  454. refs (->> (ref->map db *refs ref->map-options)
  455. (remove nil?)
  456. (map (fn [ref]
  457. (let [ref' (if-let [entity (ldb/get-case-page db (:block/title ref))]
  458. (if (= (:db/id parse-block) (:db/id entity))
  459. ref
  460. (select-keys entity [:block/uuid :block/title :block/name]))
  461. ref)]
  462. (cond-> ref'
  463. (:block.temp/original-page-name ref)
  464. (assoc :block.temp/original-page-name (:block.temp/original-page-name ref)))))))
  465. tags (ref->map db *structured-tags (assoc ref->map-options :tag? true))]
  466. (assoc block
  467. :refs refs
  468. :tags tags))))
  469. (defn- with-block-refs
  470. [{:keys [title body] :as block}]
  471. (let [ref-blocks (extract-block-refs (concat title body))
  472. refs (distinct (concat (:refs block) ref-blocks))]
  473. (assoc block :refs refs)))
  474. (defn block-keywordize
  475. [block]
  476. (update-keys
  477. block
  478. (fn [k]
  479. (if (namespace k)
  480. k
  481. (keyword "block" k)))))
  482. (defn- sanity-blocks-data
  483. "Clean up blocks data and add `block` ns to all keys"
  484. [blocks]
  485. (map (fn [block]
  486. (if (map? block)
  487. (block-keywordize (common-util/remove-nils-non-nested block))
  488. block))
  489. blocks))
  490. (defn get-block-content
  491. [utf8-content block format meta' block-pattern]
  492. (let [content (if-let [end-pos (:end_pos meta')]
  493. (utf8/substring utf8-content
  494. (:start_pos meta')
  495. end-pos)
  496. (utf8/substring utf8-content
  497. (:start_pos meta')))
  498. content (when content
  499. (let [content (text/remove-level-spaces content format block-pattern)]
  500. (if (or (:pre-block? block)
  501. (= (get block :format :markdown) :org))
  502. content
  503. (gp-mldoc/remove-indentation-spaces content (inc (:level block)) false))))]
  504. (if (= format :org)
  505. content
  506. (gp-property/->new-properties content))))
  507. (defn get-custom-id-or-new-id
  508. [properties]
  509. (or (when-let [custom-id (or (get-in properties [:properties :custom-id])
  510. (get-in properties [:properties :custom_id])
  511. (get-in properties [:properties :id]))]
  512. ;; guard against non-string custom-ids
  513. (when-let [custom-id (and (string? custom-id) (string/trim custom-id))]
  514. (some-> custom-id parse-uuid)))
  515. (d/squuid)))
  516. (defn get-page-refs-from-properties
  517. [properties db date-formatter user-config]
  518. (let [page-refs (get-page-ref-names-from-properties properties user-config)]
  519. (map (fn [page] (page-name->map page db true date-formatter)) page-refs)))
  520. (defn- with-page-block-refs
  521. [block db date-formatter & {:keys [parse-block]}]
  522. (some-> block
  523. (with-page-refs-and-tags db date-formatter parse-block)
  524. with-block-refs
  525. (update :refs (fn [col] (remove nil? col)))))
  526. (defn- macro->block
  527. "macro: {:name \"\" arguments [\"\"]}"
  528. [macro]
  529. {:block/uuid (common-uuid/gen-uuid)
  530. :block/type "macro"
  531. :block/properties {:logseq.macro-name (:name macro)
  532. :logseq.macro-arguments (:arguments macro)}})
  533. (defn extract-macros-from-ast
  534. [ast]
  535. (let [*result (atom #{})]
  536. (walk/postwalk
  537. (fn [f]
  538. (if (and (vector? f) (= (first f) "Macro"))
  539. (do
  540. (swap! *result conj (second f))
  541. nil)
  542. f))
  543. ast)
  544. (mapv macro->block @*result)))
  545. (defn with-pre-block-if-exists
  546. [blocks body pre-block-properties encoded-content {:keys [db date-formatter user-config]}]
  547. (let [first-block (first blocks)
  548. first-block-start-pos (get-in first-block [:block/meta :start_pos])
  549. ;; Add pre-block
  550. blocks (if (or (> first-block-start-pos 0)
  551. (empty? blocks))
  552. (cons
  553. (merge
  554. (let [content (utf8/substring encoded-content 0 first-block-start-pos)
  555. {:keys [properties properties-order properties-text-values invalid-properties]} pre-block-properties
  556. id (get-custom-id-or-new-id {:properties properties})
  557. property-refs (->> (get-page-refs-from-properties
  558. properties db date-formatter
  559. user-config)
  560. (map :block/title))
  561. pre-block? (if (:heading properties) false true)
  562. block {:block/uuid id
  563. :block/title content
  564. :block/level 1
  565. :block/properties properties
  566. :block/properties-order (vec properties-order)
  567. :block/properties-text-values properties-text-values
  568. :block/invalid-properties invalid-properties
  569. :block/pre-block? pre-block?
  570. :block/macros (extract-macros-from-ast body)
  571. :block.temp/ast-body body}
  572. {:keys [tags refs]}
  573. (with-page-block-refs {:body body :refs property-refs} db date-formatter)]
  574. (cond-> block
  575. tags
  576. (assoc :block/tags tags)
  577. true
  578. (assoc :block/refs (concat refs (:block-refs pre-block-properties)))))
  579. (select-keys first-block [:block/format :block/page]))
  580. blocks)
  581. blocks)]
  582. blocks))
  583. (defn- with-heading-property
  584. [properties markdown-heading? size]
  585. (if markdown-heading?
  586. (assoc properties :heading size)
  587. properties))
  588. (defn- construct-block
  589. [block properties timestamps body encoded-content format pos-meta {:keys [block-pattern db date-formatter parse-block remove-properties? db-graph-mode? export-to-db-graph?]}]
  590. (let [id (get-custom-id-or-new-id properties)
  591. ref-pages-in-properties (->> (:page-refs properties)
  592. (remove string/blank?))
  593. block (second block)
  594. unordered? (:unordered block)
  595. markdown-heading? (and (:size block) (= :markdown format))
  596. block (if markdown-heading?
  597. (assoc block
  598. :level (if unordered? (:level block) 1))
  599. block)
  600. block (cond->
  601. (-> (assoc block
  602. :uuid id
  603. :refs ref-pages-in-properties
  604. :format format
  605. :meta pos-meta)
  606. (dissoc :size :unordered))
  607. (or (seq (:properties properties)) markdown-heading?)
  608. (assoc :properties (with-heading-property (:properties properties) markdown-heading? (:size block))
  609. :properties-text-values (:properties-text-values properties)
  610. :properties-order (vec (:properties-order properties)))
  611. (seq (:invalid-properties properties))
  612. (assoc :invalid-properties (:invalid-properties properties)))
  613. block (if (get-in block [:properties :collapsed])
  614. (-> (assoc block :collapsed? true)
  615. (update :properties (fn [m] (dissoc m :collapsed)))
  616. (update :properties-text-values dissoc :collapsed)
  617. (update :properties-order (fn [keys'] (vec (remove #{:collapsed} keys')))))
  618. block)
  619. title (cond->> (get-block-content encoded-content block format pos-meta block-pattern)
  620. remove-properties?
  621. (gp-property/remove-properties (get block :format :markdown)))
  622. block (assoc block :block/title title)
  623. block (if (seq timestamps)
  624. (merge block (timestamps->scheduled-and-deadline timestamps))
  625. block)
  626. db-based? (or db-graph-mode? export-to-db-graph?)
  627. block (-> block
  628. (assoc :body body)
  629. (with-page-block-refs db date-formatter {:parse-block parse-block}))
  630. block (if db-based? block
  631. (-> block
  632. (update :tags (fn [tags] (map #(assoc % :block/format format) tags)))
  633. (update :refs (fn [refs] (map #(if (map? %) (assoc % :block/format format) %) refs)))))
  634. block (update block :refs concat (:block-refs properties))
  635. {:keys [created-at updated-at]} (:properties properties)
  636. block (cond-> block
  637. (and created-at (integer? created-at))
  638. (assoc :block/created-at created-at)
  639. (and updated-at (integer? updated-at))
  640. (assoc :block/updated-at updated-at))]
  641. (dissoc block :title :body :anchor)))
  642. (defn fix-duplicate-id
  643. [block]
  644. (println "Logseq will assign a new id for block with content:" (pr-str (:block/title block)))
  645. (-> block
  646. (assoc :block/uuid (d/squuid))
  647. (update :block/properties dissoc :id)
  648. (update :block/properties-text-values dissoc :id)
  649. (update :block/properties-order #(vec (remove #{:id} %)))
  650. (update :block/title (fn [c]
  651. (let [replace-str (re-pattern
  652. (str
  653. "\n*\\s*"
  654. (if (= :markdown (get block :block/format :markdown))
  655. (str "id" gp-property/colons " " (:block/uuid block))
  656. (str (gp-property/colons-org "id") " " (:block/uuid block)))))]
  657. (string/replace-first c replace-str ""))))))
  658. (defn block-exists-in-another-page?
  659. "For sanity check only.
  660. For renaming file externally, the file is actually deleted and transacted before-hand."
  661. [db block-uuid current-page-name]
  662. (when (and db current-page-name)
  663. (when-let [block-page-name (:block/name (:block/page (d/entity db [:block/uuid block-uuid])))]
  664. (not= current-page-name block-page-name))))
  665. (defn fix-block-id-if-duplicated!
  666. "If the block exists in another page, we need to fix it
  667. If the block exists in the current extraction process, we also need to fix it"
  668. [db page-name *block-exists-in-extraction block]
  669. (let [block (if (or (@*block-exists-in-extraction (:block/uuid block))
  670. (block-exists-in-another-page? db (:block/uuid block) page-name))
  671. (fix-duplicate-id block)
  672. block)]
  673. (swap! *block-exists-in-extraction conj (:block/uuid block))
  674. block))
  675. (defn extract-blocks
  676. "Extract headings from mldoc ast. Args:
  677. *`blocks`: mldoc ast.
  678. * `content`: markdown or org-mode text.
  679. * `format`: content's format, it could be either :markdown or :org-mode.
  680. * `options`: Options are :user-config, :block-pattern, :parse-block, :date-formatter, :db and
  681. * :db-graph-mode? : Set when a db graph in the frontend
  682. * :export-to-db-graph? : Set when exporting to a db graph"
  683. [blocks content format {:keys [user-config db-graph-mode? export-to-db-graph?] :as options}]
  684. {:pre [(seq blocks) (string? content) (contains? #{:markdown :org} format)]}
  685. (let [encoded-content (utf8/encode content)
  686. all-blocks (vec (reverse blocks))
  687. [blocks body pre-block-properties]
  688. (loop [headings []
  689. blocks (reverse blocks)
  690. block-idx 0
  691. timestamps {}
  692. properties {}
  693. body []]
  694. (if (seq blocks)
  695. (let [[block pos-meta] (first blocks)]
  696. (cond
  697. (paragraph-timestamp-block? block)
  698. (let [timestamps (extract-timestamps block)
  699. timestamps' (merge timestamps timestamps)]
  700. (recur headings (rest blocks) (inc block-idx) timestamps' properties body))
  701. (gp-property/properties-ast? block)
  702. (let [properties (extract-properties (second block) (assoc user-config :format format))]
  703. (recur headings (rest blocks) (inc block-idx) timestamps properties body))
  704. (heading-block? block)
  705. ;; for db-graphs cut multi-line when there is property, deadline/scheduled or logbook text in :block/title
  706. (let [cut-multiline? (and export-to-db-graph?
  707. (when-let [prev-block (first (get all-blocks (dec block-idx)))]
  708. (or (and (gp-property/properties-ast? prev-block)
  709. (not= "Custom" (ffirst (get all-blocks (- block-idx 2)))))
  710. (= ["Drawer" "logbook"] (take 2 prev-block))
  711. (and (= "Paragraph" (first prev-block))
  712. (seq (set/intersection (set (flatten prev-block)) #{"Deadline" "Scheduled"}))))))
  713. pos-meta' (if cut-multiline?
  714. pos-meta
  715. ;; fix start_pos
  716. (assoc pos-meta :end_pos
  717. (if (seq headings)
  718. (get-in (last headings) [:meta :start_pos])
  719. nil)))
  720. ;; Remove properties text from custom queries in db graphs
  721. options' (assoc options
  722. :remove-properties?
  723. (and export-to-db-graph?
  724. (and (gp-property/properties-ast? (first (get all-blocks (dec block-idx))))
  725. (= "Custom" (ffirst (get all-blocks (- block-idx 2)))))))
  726. block' (construct-block block properties timestamps body encoded-content format pos-meta' options')
  727. block'' (if (or db-graph-mode? export-to-db-graph?)
  728. block'
  729. (assoc block' :macros (extract-macros-from-ast (cons block body))))]
  730. (recur (conj headings block'') (rest blocks) (inc block-idx) {} {} []))
  731. :else
  732. (recur headings (rest blocks) (inc block-idx) timestamps properties (conj body block))))
  733. [(-> (reverse headings)
  734. sanity-blocks-data)
  735. body
  736. properties]))
  737. result (with-pre-block-if-exists blocks body pre-block-properties encoded-content options)]
  738. (map #(dissoc % :block/meta) result)))
  739. (defn with-parent-and-order
  740. [page-id blocks]
  741. (let [[blocks other-blocks] (split-with
  742. (fn [b]
  743. (not= "macro" (:block/type b)))
  744. blocks)
  745. result (loop [blocks (map (fn [block] (assoc block :block/level-spaces (:block/level block))) blocks)
  746. parents [{:page/id page-id ; db id or a map {:block/name "xxx"}
  747. :block/level 0
  748. :block/level-spaces 0}]
  749. result []]
  750. (if (empty? blocks)
  751. (map #(dissoc % :block/level-spaces) result)
  752. (let [[block & others] blocks
  753. level-spaces (:block/level-spaces block)
  754. {uuid' :block/uuid :block/keys [level parent] :as last-parent} (last parents)
  755. parent-spaces (:block/level-spaces last-parent)
  756. [blocks parents result]
  757. (cond
  758. (= level-spaces parent-spaces) ; sibling
  759. (let [block (assoc block
  760. :block/parent parent
  761. :block/level level)
  762. parents' (conj (vec (butlast parents)) block)
  763. result' (conj result block)]
  764. [others parents' result'])
  765. (> level-spaces parent-spaces) ; child
  766. (let [parent (if uuid' [:block/uuid uuid'] (:page/id last-parent))
  767. block (cond->
  768. (assoc block
  769. :block/parent parent)
  770. ;; fix block levels with wrong order
  771. ;; For example:
  772. ;; - a
  773. ;; - b
  774. ;; What if the input indentation is two spaces instead of 4 spaces
  775. (>= (- level-spaces parent-spaces) 1)
  776. (assoc :block/level (inc level)))
  777. parents' (conj parents block)
  778. result' (conj result block)]
  779. [others parents' result'])
  780. (< level-spaces parent-spaces)
  781. (cond
  782. (some #(= (:block/level-spaces %) (:block/level-spaces block)) parents) ; outdent
  783. (let [parents' (vec (filter (fn [p] (<= (:block/level-spaces p) level-spaces)) parents))
  784. blocks (cons (assoc (first blocks)
  785. :block/level (dec level))
  786. (rest blocks))]
  787. [blocks parents' result])
  788. :else
  789. (let [[f r] (split-with (fn [p] (<= (:block/level-spaces p) level-spaces)) parents)
  790. left (first r)
  791. parent-id (if-let [block-id (:block/uuid (last f))]
  792. [:block/uuid block-id]
  793. page-id)
  794. block (assoc block
  795. :block/parent parent-id
  796. :block/level (:block/level left)
  797. :block/level-spaces (:block/level-spaces left))
  798. parents' (->> (concat f [block]) vec)
  799. result' (conj result block)]
  800. [others parents' result'])))]
  801. (recur blocks parents result))))
  802. result' (map (fn [block] (assoc block :block/order (db-order/gen-key))) result)]
  803. (concat result' other-blocks)))
  804. (defn extract-plain
  805. "Extract plain elements including page refs"
  806. [repo content]
  807. (let [ast (gp-mldoc/->edn repo content :markdown)
  808. *result (atom [])]
  809. (walk/prewalk
  810. (fn [f]
  811. (cond
  812. ;; tag
  813. (and (vector? f)
  814. (= "Tag" (first f)))
  815. nil
  816. ;; nested page ref
  817. (and (vector? f)
  818. (= "Nested_link" (first f)))
  819. (swap! *result conj (:content (second f)))
  820. ;; page ref
  821. (and (vector? f)
  822. (= "Link" (first f))
  823. (map? (second f))
  824. (vector? (:url (second f)))
  825. (= "Page_ref" (first (:url (second f)))))
  826. (swap! *result conj
  827. (:full_text (second f)))
  828. ;; plain
  829. (and (vector? f)
  830. (= "Plain" (first f)))
  831. (swap! *result conj (second f))
  832. :else
  833. f))
  834. ast)
  835. (-> (string/trim (apply str @*result))
  836. text/page-ref-un-brackets!)))
  837. (defn extract-refs-from-text
  838. [repo db text date-formatter]
  839. (when (string? text)
  840. (let [ast-refs (gp-mldoc/get-references text (gp-mldoc/get-default-config repo :markdown))
  841. page-refs (map #(get-page-reference % :markdown) ast-refs)
  842. block-refs (map get-block-reference ast-refs)
  843. refs' (->> (concat page-refs block-refs)
  844. (remove string/blank?)
  845. distinct)]
  846. (-> (map #(cond
  847. (de/entity? %)
  848. {:block/uuid (:block/uuid %)}
  849. (common-util/uuid-string? %)
  850. {:block/uuid (uuid %)}
  851. :else
  852. (page-name->map % db true date-formatter))
  853. refs')
  854. set))))