block.cljs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753
  1. (ns frontend.format.block
  2. (:require [clojure.string :as string]
  3. [clojure.walk :as walk]
  4. [cljs.core.match :as match]
  5. [frontend.config :as config]
  6. [frontend.date :as date]
  7. [frontend.db :as db]
  8. [frontend.format :as format]
  9. [frontend.state :as state]
  10. [frontend.text :as text]
  11. [frontend.utf8 :as utf8]
  12. [frontend.util :as util]
  13. [frontend.util.property :as property]
  14. [logseq.graph-parser.util :as gp-util]
  15. [logseq.graph-parser.config :as gp-config]
  16. [logseq.graph-parser.mldoc :as gp-mldoc]
  17. [lambdaisland.glogi :as log]
  18. [medley.core :as medley]))
  19. (defn heading-block?
  20. [block]
  21. (and
  22. (vector? block)
  23. (= "Heading" (first block))))
  24. (defn get-tag
  25. [block]
  26. (when-let [tag-value (and (vector? block)
  27. (= "Tag" (first block))
  28. (second block))]
  29. (->
  30. (map (fn [e]
  31. (match/match e
  32. ["Plain" s]
  33. s
  34. ["Link" t]
  35. (let [{full_text :full_text} t]
  36. full_text)
  37. ["Nested_link" t]
  38. (let [ {content :content} t]
  39. content)
  40. :else
  41. ""
  42. )) tag-value)
  43. (string/join))))
  44. (defn get-page-reference
  45. [block]
  46. (let [page (cond
  47. (and (vector? block) (= "Link" (first block)))
  48. (let [typ (first (:url (second block)))
  49. value (second (:url (second block)))]
  50. ;; {:url ["File" "file:../pages/hello_world.org"], :label [["Plain" "hello world"]], :title nil}
  51. (or
  52. (and
  53. (= typ "Page_ref")
  54. (and (string? value)
  55. (not (or (gp-config/local-asset? value)
  56. (gp-config/draw? value))))
  57. value)
  58. (and
  59. (= typ "Search")
  60. (text/page-ref? value)
  61. (text/page-ref-un-brackets! value))
  62. (and
  63. (= typ "Search")
  64. (not (contains? #{\# \* \/ \[} (first value)))
  65. (let [ext (some-> (util/get-file-ext value) keyword)]
  66. (when (and (not (util/starts-with? value "http:"))
  67. (not (util/starts-with? value "https:"))
  68. (not (util/starts-with? value "file:"))
  69. (not (gp-config/local-asset? value))
  70. (or (= ext :excalidraw)
  71. (not (contains? (config/supported-formats) ext))))
  72. value)))
  73. (and
  74. (= typ "Complex")
  75. (= (:protocol value) "file")
  76. (:link value))
  77. (and
  78. (= typ "File")
  79. (second (first (:label (second block)))))))
  80. (and (vector? block) (= "Nested_link" (first block)))
  81. (let [content (:content (last block))]
  82. (subs content 2 (- (count content) 2)))
  83. (and (vector? block)
  84. (= "Macro" (first block)))
  85. (let [{:keys [name arguments]} (second block)
  86. argument (string/join ", " arguments)]
  87. (when (= name "embed")
  88. (text/page-ref-un-brackets! argument)))
  89. (and (vector? block)
  90. (= "Tag" (first block)))
  91. (let [text (get-tag block)]
  92. (text/page-ref-un-brackets! text))
  93. :else
  94. nil)]
  95. (text/block-ref-un-brackets! page)))
  96. (defn get-block-reference
  97. [block]
  98. (when-let [block-id (cond
  99. (and (vector? block)
  100. (= "Block_reference" (first block)))
  101. (last block)
  102. (and (vector? block)
  103. (= "Link" (first block))
  104. (map? (second block))
  105. (= "Block_ref" (first (:url (second block)))))
  106. (second (:url (second block)))
  107. (and (vector? block)
  108. (= "Macro" (first block)))
  109. (let [{:keys [name arguments]} (second block)]
  110. (when (and (= name "embed")
  111. (string? (first arguments))
  112. (string/starts-with? (first arguments) "((")
  113. (string/ends-with? (first arguments) "))"))
  114. (subs (first arguments) 2 (- (count (first arguments)) 2))))
  115. (and (vector? block)
  116. (= "Link" (first block))
  117. (map? (second block)))
  118. (if (= "id" (:protocol (second (:url (second block)))))
  119. (:link (second (:url (second block))))
  120. (let [id (second (:url (second block)))]
  121. (text/block-ref-un-brackets! id)))
  122. :else
  123. nil)]
  124. (when (and block-id
  125. (gp-util/uuid-string? block-id))
  126. block-id)))
  127. (defn paragraph-block?
  128. [block]
  129. (and
  130. (vector? block)
  131. (= "Paragraph" (first block))))
  132. (defn timestamp-block?
  133. [block]
  134. (and
  135. (vector? block)
  136. (= "Timestamp" (first block))))
  137. ;; TODO: we should move this to mldoc
  138. (defn extract-properties
  139. [format properties]
  140. (when (seq properties)
  141. (let [properties (seq properties)
  142. page-refs (->>
  143. properties
  144. (remove (fn [[k _]]
  145. (contains? #{:background-color :background_color} (keyword k))))
  146. (map last)
  147. (map (fn [v]
  148. (when (and (string? v)
  149. (not (gp-mldoc/link? format v)))
  150. (let [v (string/trim v)
  151. result (text/split-page-refs-without-brackets v {:un-brackets? false})]
  152. (if (coll? result)
  153. (map text/page-ref-un-brackets! result)
  154. [])))))
  155. (apply concat)
  156. (remove string/blank?))
  157. properties (->> properties
  158. (map (fn [[k v]]
  159. (let [k (-> (string/lower-case (name k))
  160. (string/replace " " "-")
  161. (string/replace "_" "-"))
  162. k (if (contains? #{"custom_id" "custom-id"} k)
  163. "id"
  164. k)
  165. v (if (coll? v)
  166. (remove string/blank? v)
  167. (if (string/blank? v)
  168. nil
  169. (text/parse-property format k v)))
  170. k (keyword k)
  171. v (if (and
  172. (string? v)
  173. (contains? #{:alias :aliases :tags} k))
  174. (set [v])
  175. v)
  176. v (if (coll? v) (set v) v)]
  177. [k v])))
  178. (remove #(nil? (second %))))]
  179. {:properties (into {} properties)
  180. :properties-order (map first properties)
  181. :page-refs page-refs})))
  182. (defn- paragraph-timestamp-block?
  183. [block]
  184. (and (paragraph-block? block)
  185. (or (timestamp-block? (first (second block)))
  186. (timestamp-block? (second (second block))))))
  187. (defn extract-timestamps
  188. [block]
  189. (some->>
  190. (second block)
  191. (filter timestamp-block?)
  192. (map last)
  193. (into {})))
  194. ;; {"Deadline" {:date {:year 2020, :month 10, :day 20}, :wday "Tue", :time {:hour 8, :min 0}, :repetition [["DoublePlus"] ["Day"] 1], :active true}}
  195. (defn timestamps->scheduled-and-deadline
  196. [timestamps]
  197. (let [timestamps (medley/map-keys (comp keyword string/lower-case) timestamps)
  198. m (some->> (select-keys timestamps [:scheduled :deadline])
  199. (map (fn [[k v]]
  200. (let [{:keys [date repetition]} v
  201. {:keys [year month day]} date
  202. day (js/parseInt (str year (util/zero-pad month) (util/zero-pad day)))]
  203. (cond->
  204. (case k
  205. :scheduled
  206. {:scheduled day}
  207. :deadline
  208. {:deadline day})
  209. repetition
  210. (assoc :repeated? true))))))]
  211. (apply merge m)))
  212. (defn convert-page-if-journal
  213. "Convert journal file name to user' custom date format"
  214. [original-page-name]
  215. (when original-page-name
  216. (let [page-name (util/page-name-sanity-lc original-page-name)
  217. day (date/journal-title->int page-name)]
  218. (if day
  219. (let [original-page-name (date/int->journal-title day)]
  220. [original-page-name (util/page-name-sanity-lc original-page-name) day])
  221. [original-page-name page-name day]))))
  222. (defn page-name->map
  223. "Create a page's map structure given a original page name (string).
  224. map as input is supported for legacy compatibility.
  225. with-timestamp?: assign timestampes to the map structure.
  226. Useful when creating new pages from references or namespaces,
  227. as there's no chance to introduce timestamps via editing in page"
  228. ([original-page-name with-id?]
  229. (page-name->map original-page-name with-id? true))
  230. ([original-page-name with-id? with-timestamp?]
  231. (cond
  232. (and original-page-name (string? original-page-name))
  233. (let [original-page-name (util/remove-boundary-slashes original-page-name)
  234. [original-page-name page-name journal-day] (convert-page-if-journal original-page-name)
  235. namespace? (and (not (boolean (text/get-nested-page-name original-page-name)))
  236. (text/namespace-page? original-page-name))
  237. page-entity (db/entity [:block/name page-name])
  238. original-page-name (or (:block/original-name page-entity) original-page-name)]
  239. (merge
  240. {:block/name page-name
  241. :block/original-name original-page-name}
  242. (when with-id?
  243. (if page-entity
  244. {:block/uuid (:block/uuid page-entity)}
  245. {:block/uuid (db/new-block-id)}))
  246. (when namespace?
  247. (let [namespace (first (gp-util/split-last "/" original-page-name))]
  248. (when-not (string/blank? namespace)
  249. {:block/namespace {:block/name (util/page-name-sanity-lc namespace)}})))
  250. (when (and with-timestamp? (not page-entity)) ;; Only assign timestamp on creating new entity
  251. (let [current-ms (util/time-ms)]
  252. {:block/created-at current-ms
  253. :block/updated-at current-ms}))
  254. (if journal-day
  255. {:block/journal? true
  256. :block/journal-day journal-day}
  257. {:block/journal? false})))
  258. (and (map? original-page-name) (:block/uuid original-page-name))
  259. original-page-name
  260. (and (map? original-page-name) with-id?)
  261. (assoc original-page-name :block/uuid (db/new-block-id))
  262. :else
  263. nil)))
  264. (defn with-page-refs
  265. [{:keys [title body tags refs marker priority] :as block} with-id?]
  266. (let [refs (->> (concat tags refs [marker priority])
  267. (remove string/blank?)
  268. (distinct))
  269. refs (atom refs)]
  270. (walk/prewalk
  271. (fn [form]
  272. ;; skip custom queries
  273. (when-not (and (vector? form)
  274. (= (first form) "Custom")
  275. (= (second form) "query"))
  276. (when-let [page (get-page-reference form)]
  277. (swap! refs conj page))
  278. (when-let [tag (get-tag form)]
  279. (let [tag (text/page-ref-un-brackets! tag)]
  280. (when (gp-util/tag-valid? tag)
  281. (swap! refs conj tag))))
  282. form))
  283. (concat title body))
  284. (let [refs (remove string/blank? @refs)
  285. children-pages (->> (mapcat (fn [p]
  286. (let [p (if (map? p)
  287. (:block/original-name p)
  288. p)]
  289. (when (string? p)
  290. (let [p (or (text/get-nested-page-name p) p)]
  291. (when (text/namespace-page? p)
  292. (util/split-namespace-pages p))))))
  293. refs)
  294. (remove string/blank?)
  295. (distinct))
  296. refs (->> (distinct (concat refs children-pages))
  297. (remove nil?))
  298. refs (map (fn [ref] (page-name->map ref with-id?)) refs)]
  299. (assoc block :refs refs))))
  300. (defn with-block-refs
  301. [{:keys [title body] :as block}]
  302. (let [ref-blocks (atom nil)]
  303. (walk/postwalk
  304. (fn [form]
  305. (when-let [block (get-block-reference form)]
  306. (swap! ref-blocks conj block))
  307. form)
  308. (concat title body))
  309. (let [ref-blocks (->> @ref-blocks
  310. (filter gp-util/uuid-string?))
  311. ref-blocks (map
  312. (fn [id]
  313. [:block/uuid (medley/uuid id)])
  314. ref-blocks)
  315. refs (distinct (concat (:refs block) ref-blocks))]
  316. (assoc block :refs refs))))
  317. (defn- block-keywordize
  318. [block]
  319. (medley/map-keys
  320. (fn [k]
  321. (if (namespace k)
  322. k
  323. (keyword "block" k)))
  324. block))
  325. (defn- sanity-blocks-data
  326. [blocks]
  327. (map (fn [block]
  328. (if (map? block)
  329. (block-keywordize (gp-util/remove-nils block))
  330. block))
  331. blocks))
  332. (defn with-path-refs
  333. [blocks]
  334. (loop [blocks blocks
  335. acc []
  336. parents []]
  337. (if (empty? blocks)
  338. acc
  339. (let [block (first blocks)
  340. cur-level (:block/level block)
  341. level-diff (- cur-level
  342. (get (last parents) :block/level 0))
  343. [path-refs parents]
  344. (cond
  345. (zero? level-diff) ; sibling
  346. (let [path-refs (mapcat :block/refs (drop-last parents))
  347. parents (conj (vec (butlast parents)) block)]
  348. [path-refs parents])
  349. (> level-diff 0) ; child
  350. (let [path-refs (mapcat :block/refs parents)]
  351. [path-refs (conj parents block)])
  352. (< level-diff 0) ; new parent
  353. (let [parents (vec (take-while (fn [p] (< (:block/level p) cur-level)) parents))
  354. path-refs (mapcat :block/refs parents)]
  355. [path-refs (conj parents block)]))
  356. path-ref-pages (->> path-refs
  357. (concat (:block/refs block))
  358. (map (fn [ref]
  359. (cond
  360. (map? ref)
  361. (:block/name ref)
  362. :else
  363. ref)))
  364. (remove string/blank?)
  365. (map (fn [ref]
  366. (if (string? ref)
  367. {:block/name (util/page-name-sanity-lc ref)}
  368. ref)))
  369. (remove vector?)
  370. (remove nil?)
  371. (distinct))]
  372. (recur (rest blocks)
  373. (conj acc (assoc block :block/path-refs path-ref-pages))
  374. parents)))))
  375. (defn block-tags->pages
  376. [{:keys [tags] :as block}]
  377. (if (seq tags)
  378. (assoc block :tags (map (fn [tag]
  379. (let [tag (text/page-ref-un-brackets! tag)]
  380. [:block/name (util/page-name-sanity-lc tag)])) tags))
  381. block))
  382. (defn- get-block-content
  383. [utf8-content block format meta]
  384. (let [content (if-let [end-pos (:end_pos meta)]
  385. (utf8/substring utf8-content
  386. (:start_pos meta)
  387. end-pos)
  388. (utf8/substring utf8-content
  389. (:start_pos meta)))
  390. content (when content
  391. (let [content (text/remove-level-spaces content format)]
  392. (if (or (:pre-block? block)
  393. (= (:format block) :org))
  394. content
  395. (gp-mldoc/remove-indentation-spaces content (inc (:level block)) false))))]
  396. (if (= format :org)
  397. content
  398. (property/->new-properties content))))
  399. (defn get-custom-id-or-new-id
  400. [properties]
  401. (or (when-let [custom-id (or (get-in properties [:properties :custom-id])
  402. (get-in properties [:properties :custom_id])
  403. (get-in properties [:properties :id]))]
  404. (let [custom-id (and (string? custom-id) (string/trim custom-id))]
  405. (when (and custom-id (gp-util/uuid-string? custom-id))
  406. (uuid custom-id))))
  407. (db/new-block-id)))
  408. (defn get-page-refs-from-properties
  409. [properties]
  410. (let [page-refs (mapcat (fn [v] (cond
  411. (coll? v)
  412. v
  413. (text/page-ref? v)
  414. [(text/page-ref-un-brackets! v)]
  415. :else
  416. nil)) (vals properties))
  417. page-refs (remove string/blank? page-refs)]
  418. (map (fn [page] (page-name->map page true)) page-refs)))
  419. (defn with-page-block-refs
  420. [block with-id?]
  421. (some-> block
  422. (with-page-refs with-id?)
  423. with-block-refs
  424. block-tags->pages
  425. (update :refs (fn [col] (remove nil? col)))))
  426. (defn with-pre-block-if-exists
  427. [blocks body pre-block-properties encoded-content]
  428. (let [first-block (first blocks)
  429. first-block-start-pos (get-in first-block [:block/meta :start_pos])
  430. ;; Add pre-block
  431. blocks (if (or (> first-block-start-pos 0)
  432. (empty? blocks))
  433. (cons
  434. (merge
  435. (let [content (utf8/substring encoded-content 0 first-block-start-pos)
  436. {:keys [properties properties-order]} pre-block-properties
  437. id (get-custom-id-or-new-id {:properties properties})
  438. property-refs (->> (get-page-refs-from-properties properties)
  439. (map :block/original-name))
  440. block {:uuid id
  441. :content content
  442. :level 1
  443. :properties properties
  444. :properties-order properties-order
  445. :refs property-refs
  446. :pre-block? true
  447. :unordered true
  448. :body body}
  449. block (with-page-block-refs block false)]
  450. (block-keywordize block))
  451. (select-keys first-block [:block/format :block/page]))
  452. blocks)
  453. blocks)]
  454. (with-path-refs blocks)))
  455. (defn- construct-block
  456. [block properties timestamps body encoded-content format pos-meta with-id?]
  457. (let [id (get-custom-id-or-new-id properties)
  458. ref-pages-in-properties (->> (:page-refs properties)
  459. (remove string/blank?))
  460. block (second block)
  461. unordered? (:unordered block)
  462. markdown-heading? (and (:size block) (= :markdown format))
  463. block (if markdown-heading?
  464. (assoc block
  465. :type :heading
  466. :level (if unordered? (:level block) 1)
  467. :heading-level (or (:size block) 6))
  468. block)
  469. block (cond->
  470. (assoc block
  471. :uuid id
  472. :refs ref-pages-in-properties
  473. :format format
  474. :meta pos-meta)
  475. (seq (:properties properties))
  476. (assoc :properties (:properties properties))
  477. (seq (:properties-order properties))
  478. (assoc :properties-order (:properties-order properties)))
  479. block (if (get-in block [:properties :collapsed])
  480. (assoc block :collapsed? true)
  481. block)
  482. block (assoc block
  483. :content (get-block-content encoded-content block format pos-meta))
  484. block (if (seq timestamps)
  485. (merge block (timestamps->scheduled-and-deadline timestamps))
  486. block)
  487. block (assoc block :body body)
  488. block (with-page-block-refs block with-id?)
  489. {:keys [created-at updated-at]} (:properties properties)
  490. block (cond-> block
  491. (and created-at (integer? created-at))
  492. (assoc :block/created-at created-at)
  493. (and updated-at (integer? updated-at))
  494. (assoc :block/updated-at updated-at))]
  495. (dissoc block :title :body :anchor)))
  496. (defn extract-blocks
  497. "Extract headings from mldoc ast.
  498. Args:
  499. `blocks`: mldoc ast.
  500. `content`: markdown or org-mode text.
  501. `with-id?`: If `with-id?` equals to true, all the referenced pages will have new db ids.
  502. `format`: content's format, it could be either :markdown or :org-mode."
  503. [blocks content with-id? format]
  504. {:pre [(seq blocks) (string? content) (boolean? with-id?) (contains? #{:markdown :org} format)]}
  505. (try
  506. (let [encoded-content (utf8/encode content)
  507. [blocks body pre-block-properties]
  508. (loop [headings []
  509. blocks (reverse blocks)
  510. timestamps {}
  511. properties {}
  512. body []]
  513. (if (seq blocks)
  514. (let [[block pos-meta] (first blocks)
  515. ;; fix start_pos
  516. pos-meta (assoc pos-meta :end_pos
  517. (if (seq headings)
  518. (get-in (last headings) [:meta :start_pos])
  519. nil))]
  520. (cond
  521. (paragraph-timestamp-block? block)
  522. (let [timestamps (extract-timestamps block)
  523. timestamps' (merge timestamps timestamps)]
  524. (recur headings (rest blocks) timestamps' properties body))
  525. (property/properties-ast? block)
  526. (let [properties (extract-properties format (second block))]
  527. (recur headings (rest blocks) timestamps properties body))
  528. (heading-block? block)
  529. (let [block (construct-block block properties timestamps body encoded-content format pos-meta with-id?)]
  530. (recur (conj headings block) (rest blocks) {} {} []))
  531. :else
  532. (recur headings (rest blocks) timestamps properties (conj body block))))
  533. [(-> (reverse headings)
  534. sanity-blocks-data)
  535. body
  536. properties]))
  537. result (with-pre-block-if-exists blocks body pre-block-properties encoded-content)]
  538. (map #(dissoc % :block/meta) result))
  539. (catch js/Error e
  540. (js/console.error "extract-blocks-failed")
  541. (log/error :exception e))))
  542. (defn with-parent-and-left
  543. [page-id blocks]
  544. (loop [blocks (map (fn [block] (assoc block :block/level-spaces (:block/level block))) blocks)
  545. parents [{:page/id page-id ; db id or a map {:block/name "xxx"}
  546. :block/level 0
  547. :block/level-spaces 0}]
  548. result []]
  549. (if (empty? blocks)
  550. (map #(dissoc % :block/level-spaces) result)
  551. (let [[block & others] blocks
  552. level-spaces (:block/level-spaces block)
  553. {:block/keys [uuid level parent] :as last-parent} (last parents)
  554. parent-spaces (:block/level-spaces last-parent)
  555. [blocks parents result]
  556. (cond
  557. (= level-spaces parent-spaces) ; sibling
  558. (let [block (assoc block
  559. :block/parent parent
  560. :block/left [:block/uuid uuid]
  561. :block/level level)
  562. parents' (conj (vec (butlast parents)) block)
  563. result' (conj result block)]
  564. [others parents' result'])
  565. (> level-spaces parent-spaces) ; child
  566. (let [parent (if uuid [:block/uuid uuid] (:page/id last-parent))
  567. block (cond->
  568. (assoc block
  569. :block/parent parent
  570. :block/left parent)
  571. ;; fix block levels with wrong order
  572. ;; For example:
  573. ;; - a
  574. ;; - b
  575. ;; What if the input indentation is two spaces instead of 4 spaces
  576. (>= (- level-spaces parent-spaces) 1)
  577. (assoc :block/level (inc level)))
  578. parents' (conj parents block)
  579. result' (conj result block)]
  580. [others parents' result'])
  581. (< level-spaces parent-spaces)
  582. (cond
  583. (some #(= (:block/level-spaces %) (:block/level-spaces block)) parents) ; outdent
  584. (let [parents' (vec (filter (fn [p] (<= (:block/level-spaces p) level-spaces)) parents))
  585. left (last parents')
  586. blocks (cons (assoc (first blocks)
  587. :block/level (dec level)
  588. :block/left [:block/uuid (:block/uuid left)])
  589. (rest blocks))]
  590. [blocks parents' result])
  591. :else
  592. (let [[f r] (split-with (fn [p] (<= (:block/level-spaces p) level-spaces)) parents)
  593. left (first r)
  594. parent-id (if-let [block-id (:block/uuid (last f))]
  595. [:block/uuid block-id]
  596. page-id)
  597. block (cond->
  598. (assoc block
  599. :block/parent parent-id
  600. :block/left [:block/uuid (:block/uuid left)]
  601. :block/level (:block/level left)
  602. :block/level-spaces (:block/level-spaces left)))
  603. parents' (->> (concat f [block]) vec)
  604. result' (conj result block)]
  605. [others parents' result'])))]
  606. (recur blocks parents result)))))
  607. (defn parse-block
  608. ([block]
  609. (parse-block block nil))
  610. ([{:block/keys [uuid content page format] :as block} {:keys [with-id?]
  611. :or {with-id? true}}]
  612. (when-not (string/blank? content)
  613. (let [block (dissoc block :block/pre-block?)
  614. ast (format/to-edn content format nil)
  615. blocks (extract-blocks ast content with-id? format)
  616. new-block (first blocks)
  617. parent-refs (->> (db/get-block-parent (state/get-current-repo) uuid)
  618. :block/path-refs
  619. (map :db/id))
  620. {:block/keys [refs]} new-block
  621. ref-pages (filter :block/name refs)
  622. path-ref-pages (->> (concat ref-pages parent-refs [(:db/id page)])
  623. (remove nil?))
  624. block (cond->
  625. (merge
  626. block
  627. new-block
  628. {:block/path-refs path-ref-pages})
  629. (> (count blocks) 1)
  630. (assoc :block/warning :multiple-blocks))
  631. block (dissoc block :block/title :block/body :block/level)]
  632. (if uuid (assoc block :block/uuid uuid) block)))))
  633. (defn parse-title-and-body
  634. ([block]
  635. (when (map? block)
  636. (merge block
  637. (parse-title-and-body (:block/uuid block)
  638. (:block/format block)
  639. (:block/pre-block? block)
  640. (:block/content block)))))
  641. ([block-uuid format pre-block? content]
  642. (when-not (string/blank? content)
  643. (let [content (if pre-block? content
  644. (str (config/get-block-pattern format) " " (string/triml content)))]
  645. (if-let [result (state/get-block-ast block-uuid content)]
  646. result
  647. (let [ast (->> (format/to-edn content format (gp-mldoc/default-config format))
  648. (map first))
  649. title (when (heading-block? (first ast))
  650. (:title (second (first ast))))
  651. body (vec (if title (rest ast) ast))
  652. body (drop-while property/properties-ast? body)
  653. result (cond->
  654. (if (seq body) {:block/body body} {})
  655. title
  656. (assoc :block/title title))]
  657. (state/add-block-ast-cache! block-uuid content result)
  658. result))))))
  659. (defn macro-subs
  660. [macro-content arguments]
  661. (loop [s macro-content
  662. args arguments
  663. n 1]
  664. (if (seq args)
  665. (recur
  666. (string/replace s (str "$" n) (first args))
  667. (rest args)
  668. (inc n))
  669. s)))
  670. (defn break-line-paragraph?
  671. [[typ break-lines]]
  672. (and (= typ "Paragraph")
  673. (every? #(= % ["Break_Line"]) break-lines)))
  674. (defn trim-paragraph-special-break-lines
  675. [ast]
  676. (let [[typ paras] ast]
  677. (if (= typ "Paragraph")
  678. (let [indexed-paras (map-indexed vector paras)]
  679. [typ (->> (filter
  680. #(let [[index value] %]
  681. (not (and (> index 0)
  682. (= value ["Break_Line"])
  683. (contains? #{"Timestamp" "Macro"}
  684. (first (nth paras (dec index)))))))
  685. indexed-paras)
  686. (map #(last %)))])
  687. ast)))
  688. (defn trim-break-lines!
  689. [ast]
  690. (drop-while break-line-paragraph?
  691. (map trim-paragraph-special-break-lines ast)))