block.cljs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. (ns frontend.format.block
  2. (:require [frontend.util :as util :refer-macros [profile]]
  3. [clojure.walk :as walk]
  4. [clojure.string :as string]
  5. [frontend.format :as format]
  6. [frontend.utf8 :as utf8]
  7. [medley.core :as medley]
  8. [frontend.config :as config]
  9. [datascript.core :as d]
  10. [frontend.date :as date]
  11. [frontend.text :as text]
  12. [frontend.util.property :as property]
  13. [medley.core :as medley]
  14. [frontend.state :as state]
  15. [frontend.db :as db]))
  16. (defn heading-block?
  17. [block]
  18. (and
  19. (vector? block)
  20. (= "Heading" (first block))))
  21. (defn get-tag
  22. [block]
  23. (and (vector? block)
  24. (= "Tag" (first block))
  25. (second block)))
  26. (defn get-page-reference
  27. [block]
  28. (let [page (cond
  29. (and (vector? block) (= "Link" (first block)))
  30. (let [typ (first (:url (second block)))]
  31. ;; {:url ["File" "file:../pages/hello_world.org"], :label [["Plain" "hello world"]], :title nil}
  32. (or
  33. (and
  34. (= typ "Search")
  35. ;; FIXME: alert error
  36. (not (contains? #{\# \* \/ \[} (first (second (:url (second block))))))
  37. (let [page (second (:url (second block)))
  38. ext (some-> (util/get-file-ext page) keyword)]
  39. (when (and (not (util/starts-with? page "http:"))
  40. (not (util/starts-with? page "https:"))
  41. (not (util/starts-with? page "file:"))
  42. (or (= ext :excalidraw)
  43. (not (contains? (config/supported-formats) ext))))
  44. page)))
  45. (and
  46. (= typ "Complex")
  47. (= (:protocol (second (:url (second block)))) "file")
  48. (:link (second (:url (second block)))))
  49. (and
  50. (= typ "File")
  51. (second (first (:label (second block)))))))
  52. (and (vector? block) (= "Nested_link" (first block)))
  53. (let [content (:content (last block))]
  54. (subs content 2 (- (count content) 2)))
  55. (and (vector? block)
  56. (= "Macro" (first block)))
  57. (let [{:keys [name arguments]} (second block)]
  58. (let [argument (string/join ", " arguments)]
  59. (when (and (= name "embed")
  60. (string? argument)
  61. (text/page-ref? argument))
  62. (text/page-ref-un-brackets! argument))))
  63. (and (vector? block)
  64. (= "Tag" (first block)))
  65. (let [text (second block)]
  66. (when (and
  67. (string? text)
  68. (text/page-ref? text))
  69. (text/page-ref-un-brackets! text)))
  70. :else
  71. nil)]
  72. (cond
  73. (and
  74. (string? page)
  75. (text/block-ref? page))
  76. (text/block-ref-un-brackets! page)
  77. (and
  78. (string? page)
  79. (not (string/blank? page)))
  80. (string/trim page)
  81. :else
  82. nil)))
  83. (defn get-block-reference
  84. [block]
  85. (when-let [block-id (cond
  86. (and (vector? block)
  87. (= "Block_reference" (first block)))
  88. (last block)
  89. (and (vector? block)
  90. (= "Macro" (first block)))
  91. (let [{:keys [name arguments]} (second block)]
  92. (when (and (= name "embed")
  93. (string? (first arguments))
  94. (string/starts-with? (first arguments) "((")
  95. (string/ends-with? (first arguments) "))"))
  96. (subs (first arguments) 2 (- (count (first arguments)) 2))))
  97. (and (vector? block)
  98. (= "Link" (first block))
  99. (map? (second block)))
  100. (if (= "id" (:protocol (second (:url (second block)))))
  101. (:link (second (:url (second block))))
  102. (let [id (second (:url (second block)))]
  103. (when (text/block-ref? id)
  104. (text/block-ref-un-brackets! id))))
  105. :else
  106. nil)]
  107. (when (and block-id
  108. (util/uuid-string? block-id))
  109. block-id)))
  110. ;; FIXME:
  111. (defn extract-title
  112. [block]
  113. (-> (:title (second block))
  114. first
  115. second))
  116. (defn paragraph-block?
  117. [block]
  118. (and
  119. (vector? block)
  120. (= "Paragraph" (first block))))
  121. (defn hiccup-block?
  122. [block]
  123. (and
  124. (vector? block)
  125. (= "Hiccup" (first block))))
  126. (defn timestamp-block?
  127. [block]
  128. (and
  129. (vector? block)
  130. (= "Timestamp" (first block))))
  131. (defn definition-list-block?
  132. [block]
  133. (and
  134. (vector? block)
  135. (= "List" (first block))
  136. (:name (first (second block)))))
  137. (defonce non-parsing-properties
  138. (atom #{"background-color" "background_color"}))
  139. ;; TODO: we should move this to mldoc
  140. (defn extract-properties
  141. [properties]
  142. (let [properties (into {} properties)
  143. page-refs (->>
  144. (map (fn [v]
  145. (when (string? v)
  146. (let [result (text/split-page-refs-without-brackets v {:un-brackets? false})]
  147. (if (coll? result)
  148. (map text/page-ref-un-brackets! result)
  149. []))))
  150. (vals properties))
  151. (apply concat)
  152. (remove string/blank?))
  153. properties (->> properties
  154. (medley/map-kv (fn [k v]
  155. (let [k (-> (string/lower-case (name k))
  156. (string/replace " " "-")
  157. (string/replace "_" "-"))
  158. k (if (contains? #{"custom_id" "custom-id"} k)
  159. "id"
  160. k)
  161. v (if (coll? v)
  162. v
  163. (property/parse-property k v))
  164. k (keyword k)
  165. v (if (and
  166. (string? v)
  167. (contains? #{:alias :aliases :tags} k))
  168. (set [v])
  169. v)]
  170. [k v]))))]
  171. {:properties properties
  172. :page-refs page-refs}))
  173. (defn- paragraph-timestamp-block?
  174. [block]
  175. (and (paragraph-block? block)
  176. (or (timestamp-block? (first (second block)))
  177. (timestamp-block? (second (second block))))))
  178. (defn extract-timestamps
  179. [block]
  180. (some->>
  181. (second block)
  182. (filter timestamp-block?)
  183. (map last)
  184. (into {})))
  185. ;; {"Deadline" {:date {:year 2020, :month 10, :day 20}, :wday "Tue", :time {:hour 8, :min 0}, :repetition [["DoublePlus"] ["Day"] 1], :active true}}
  186. (defn timestamps->scheduled-and-deadline
  187. [timestamps]
  188. (let [timestamps (medley/map-keys (comp keyword string/lower-case) timestamps)
  189. m (some->> (select-keys timestamps [:scheduled :deadline])
  190. (map (fn [[k v]]
  191. (let [{:keys [date repetition]} v
  192. {:keys [year month day]} date
  193. day (js/parseInt (str year (util/zero-pad month) (util/zero-pad day)))]
  194. (cond->
  195. (case k
  196. :scheduled
  197. {:scheduled day}
  198. :deadline
  199. {:deadline day})
  200. repetition
  201. (assoc :repeated? true))))))]
  202. (apply merge m)))
  203. (defn convert-page-if-journal
  204. "Convert journal file name to user' custom date format"
  205. [original-page-name]
  206. (let [page-name (string/lower-case original-page-name)
  207. day (date/journal-title->int page-name)]
  208. (if day
  209. (let [original-page-name (date/int->journal-title day)]
  210. [original-page-name (string/lower-case original-page-name) day])
  211. [original-page-name page-name day])))
  212. (defn page-name->map
  213. [original-page-name with-id?]
  214. (when original-page-name
  215. (let [[original-page-name page-name journal-day] (convert-page-if-journal original-page-name)
  216. m (merge
  217. {:block/name page-name
  218. :block/original-name original-page-name}
  219. (when with-id?
  220. (if-let [block (db/entity [:block/name page-name])]
  221. {}
  222. {:block/uuid (db/new-block-id)})))]
  223. (if journal-day
  224. (merge m
  225. {:block/journal? true
  226. :block/journal-day journal-day})
  227. (assoc m :block/journal? false)))))
  228. (defn with-page-refs
  229. [{:keys [title body tags refs marker priority] :as block} with-id?]
  230. (let [refs (->> (concat tags refs [marker priority])
  231. (remove string/blank?)
  232. (distinct))
  233. refs (atom refs)]
  234. (walk/postwalk
  235. (fn [form]
  236. (when-let [page (get-page-reference form)]
  237. (swap! refs conj page))
  238. (when-let [tag (get-tag form)]
  239. (when (util/tag-valid? tag)
  240. (swap! refs conj tag)))
  241. form)
  242. (concat title body))
  243. (let [refs (remove string/blank? @refs)
  244. children-pages (->> (mapcat (fn [p]
  245. (when (and (string/includes? p "/")
  246. (not (string/starts-with? p "../"))
  247. (not (string/starts-with? p "./"))
  248. (not (string/starts-with? p "http")))
  249. ;; Don't create the last page for now
  250. (butlast (string/split p #"/"))))
  251. refs)
  252. (remove string/blank?))
  253. refs (->> (distinct (concat refs children-pages))
  254. (remove nil?))
  255. refs (map (fn [ref] (page-name->map ref with-id?)) refs)]
  256. (assoc block :refs refs))))
  257. (defn with-block-refs
  258. [{:keys [title body] :as block}]
  259. (let [ref-blocks (atom nil)]
  260. (walk/postwalk
  261. (fn [form]
  262. (when-let [block (get-block-reference form)]
  263. (swap! ref-blocks conj block))
  264. form)
  265. (concat title body))
  266. (let [ref-blocks (->> @ref-blocks
  267. (filter util/uuid-string?))
  268. ref-blocks (map
  269. (fn [id]
  270. [:block/uuid (medley/uuid id)])
  271. ref-blocks)
  272. refs (distinct (concat (:refs block) ref-blocks))]
  273. (assoc block :refs refs))))
  274. (defn block-keywordize
  275. [block]
  276. (medley/map-keys
  277. (fn [k]
  278. (if (namespace k)
  279. k
  280. (keyword "block" k)))
  281. block))
  282. (defn safe-blocks
  283. [blocks]
  284. (map (fn [block]
  285. (if (map? block)
  286. (block-keywordize (util/remove-nils block))
  287. block))
  288. blocks))
  289. (defn with-path-refs
  290. [blocks]
  291. (loop [blocks blocks
  292. acc []
  293. parents []]
  294. (if (empty? blocks)
  295. acc
  296. (let [block (first blocks)
  297. cur-level (:block/level block)
  298. level-diff (- cur-level
  299. (get (last parents) :block/level 0))
  300. [path-refs parents]
  301. (cond
  302. (zero? level-diff) ; sibling
  303. (let [path-refs (mapcat :block/refs (drop-last parents))
  304. parents (conj (vec (butlast parents)) block)]
  305. [path-refs parents])
  306. (> level-diff 0) ; child
  307. (let [path-refs (mapcat :block/refs parents)]
  308. [path-refs (conj parents block)])
  309. (< level-diff 0) ; new parent
  310. (let [parents (vec (take-while (fn [p] (< (:block/level p) cur-level)) parents))
  311. path-refs (mapcat :block/refs parents)]
  312. [path-refs (conj parents block)]))
  313. path-ref-pages (->> path-refs
  314. (concat (:block/refs block))
  315. (map (fn [ref]
  316. (cond
  317. (map? ref)
  318. (:block/name ref)
  319. :else
  320. ref)))
  321. (remove string/blank?)
  322. (map (fn [ref]
  323. (if (string? ref)
  324. {:block/name (string/lower-case ref)}
  325. ref)))
  326. (remove vector?)
  327. (distinct))]
  328. (recur (rest blocks)
  329. (conj acc (assoc block :block/path-refs path-ref-pages))
  330. parents)))))
  331. (defn block-tags->pages
  332. [{:keys [tags] :as block}]
  333. (if (seq tags)
  334. (assoc block :tags (map (fn [tag]
  335. [:block/name (string/lower-case tag)]) tags))
  336. block))
  337. (defn- remove-indentation-spaces
  338. [s level]
  339. (let [level (inc level)
  340. lines (string/split-lines s)
  341. [f & r] lines
  342. body (map (fn [line]
  343. (if (string/blank? (util/safe-subs line 0 level))
  344. (util/safe-subs line level)
  345. line))
  346. r)
  347. content (cons f body)]
  348. (string/join "\n" content)))
  349. (defn src-block?
  350. [block]
  351. (some (fn [x] (and (vector? x) (= "Src" (first x)))) (:body block)))
  352. (defn- get-block-content
  353. [utf8-content block format]
  354. (let [meta (:meta block)
  355. content (if-let [end-pos (:end-pos meta)]
  356. (utf8/substring utf8-content
  357. (:start-pos meta)
  358. end-pos)
  359. (utf8/substring utf8-content
  360. (:start-pos meta)))
  361. content-orig content]
  362. (let [content (when content
  363. (let [content (text/remove-level-spaces content format)]
  364. (if (or (:pre-block? block)
  365. (= (:format block) :org))
  366. content
  367. (remove-indentation-spaces content (:level block)))))]
  368. (if (= format :org)
  369. content
  370. (property/->new-properties content)))))
  371. (defn- remove-indentations
  372. [format level element]
  373. (if (or (= level 1) (= format :org))
  374. element
  375. (case (first element)
  376. "Paragraph"
  377. ["Paragraph"
  378. (let [level (if (= (ffirst (second element)) "Plain")
  379. (count (re-find #"^[\s\t]+" (second (first (second element)))))
  380. level)]
  381. (->> (partition-by #(= ["Break_Line"] %) (second element))
  382. (map (fn [c]
  383. (if (and (= (ffirst c) "Plain")
  384. (>= (count (re-find #"^[\s\t]+" (second (first c)))) level))
  385. (cons ["Plain" (subs (second (first c)) level)] (rest c))
  386. c)))
  387. (apply concat)))]
  388. element)))
  389. (defn extract-blocks
  390. [blocks content with-id? format]
  391. (let [encoded-content (utf8/encode content)
  392. last-pos (utf8/length encoded-content)
  393. pre-block-body (atom nil)
  394. pre-block-properties (atom nil)
  395. blocks
  396. (loop [headings []
  397. block-body []
  398. blocks (reverse blocks)
  399. timestamps {}
  400. properties {}
  401. last-pos last-pos
  402. last-level 1000
  403. children []]
  404. (if (seq blocks)
  405. (let [[block {:keys [start_pos end_pos]}] (first blocks)
  406. unordered? (:unordered (second block))
  407. markdown-heading? (and (false? unordered?) (= :markdown format))]
  408. (cond
  409. (paragraph-timestamp-block? block)
  410. (let [timestamps (extract-timestamps block)
  411. timestamps' (merge timestamps timestamps)
  412. other-body (->> (second block)
  413. (drop-while #(= ["Break_Line"] %)))]
  414. (recur headings (conj block-body ["Paragraph" other-body]) (rest blocks) timestamps' properties last-pos last-level children))
  415. (property/properties-ast? block)
  416. (let [properties (extract-properties (second block))]
  417. (recur headings block-body (rest blocks) timestamps properties last-pos last-level children))
  418. (heading-block? block)
  419. (let [id (or (when-let [custom-id (or (get-in properties [:properties :custom-id])
  420. (get-in properties [:properties :custom_id])
  421. (get-in properties [:properties :id]))]
  422. (let [custom-id (string/trim custom-id)]
  423. (when (util/uuid-string? custom-id)
  424. (uuid custom-id))))
  425. (db/new-block-id))
  426. ref-pages-in-properties (->> (:page-refs properties)
  427. (remove string/blank?))
  428. block (second block)
  429. block (if markdown-heading?
  430. (assoc block
  431. :type :heading
  432. :level 1
  433. :heading-level (:level block))
  434. block)
  435. level (:level block)
  436. [children current-block-children]
  437. (cond
  438. (< level last-level)
  439. (let [current-block-children (set (->> (filter #(< level (second %)) children)
  440. (map first)
  441. (map (fn [id]
  442. [:block/uuid id]))))
  443. others (vec (remove #(< level (second %)) children))]
  444. [(conj others [id level])
  445. current-block-children])
  446. (>= level last-level)
  447. [(conj children [id level])
  448. #{}])
  449. block (-> (assoc block
  450. :uuid id
  451. :body (vec
  452. (->> (reverse block-body)
  453. (map #(remove-indentations format (:level block) %))))
  454. :properties (:properties properties)
  455. :refs ref-pages-in-properties
  456. :children (or current-block-children [])
  457. :format format)
  458. (assoc-in [:meta :start-pos] start_pos)
  459. (assoc-in [:meta :end-pos] last-pos)
  460. ((fn [block]
  461. (assoc block
  462. :content (get-block-content encoded-content block format)))))
  463. block (if (seq timestamps)
  464. (merge block (timestamps->scheduled-and-deadline timestamps))
  465. block)
  466. block (-> block
  467. (with-page-refs with-id?)
  468. with-block-refs
  469. block-tags->pages)
  470. last-pos' (get-in block [:meta :start-pos])]
  471. (recur (conj headings block) [] (rest blocks) {} {} last-pos' (:level block) children))
  472. :else
  473. (let [block-body' (conj block-body block)]
  474. (recur headings block-body' (rest blocks) timestamps properties last-pos last-level children))))
  475. (do
  476. (when (seq block-body)
  477. (reset! pre-block-body (reverse block-body)))
  478. (when (seq properties)
  479. (let [properties (:properties properties)]
  480. (reset! pre-block-properties properties)))
  481. (-> (reverse headings)
  482. safe-blocks))))]
  483. (let [first-block (first blocks)
  484. first-block-start-pos (get-in first-block [:block/meta :start-pos])
  485. blocks (if (or (seq @pre-block-body)
  486. (seq @pre-block-properties))
  487. (cons
  488. (merge
  489. (let [content (utf8/substring encoded-content 0 first-block-start-pos)]
  490. (->
  491. {:uuid (db/new-block-id)
  492. :content content
  493. :level 1
  494. :meta {:start-pos 0
  495. :end-pos (or first-block-start-pos
  496. (utf8/length encoded-content))}
  497. :body @pre-block-body
  498. :properties @pre-block-properties
  499. :pre-block? true
  500. :unordered true}
  501. (block-keywordize)))
  502. (select-keys first-block [:block/file :block/format :block/page]))
  503. blocks)
  504. blocks)]
  505. (with-path-refs blocks))))
  506. (defn with-parent-and-left
  507. [page-id blocks]
  508. (loop [blocks (map (fn [block] (assoc block :block/level-spaces (:block/level block))) blocks)
  509. parents [{:page/id page-id ; db id or lookup ref [:block/name "xxx"]
  510. :block/level 0
  511. :block/level-spaces 0}]
  512. sibling nil
  513. result []]
  514. (if (empty? blocks)
  515. (map #(dissoc % :block/level-spaces) result)
  516. (let [[block & others] blocks
  517. level-spaces (:block/level-spaces block)
  518. {:block/keys [uuid level parent unordered] :as last-parent} (last parents)
  519. parent-spaces (:block/level-spaces last-parent)
  520. [blocks parents sibling result]
  521. (cond
  522. (= level-spaces parent-spaces) ; sibling
  523. (let [block (assoc block
  524. :block/parent parent
  525. :block/left [:block/uuid uuid]
  526. :block/level level
  527. )
  528. parents' (conj (vec (butlast parents)) block)
  529. result' (conj result block)]
  530. [others parents' block result'])
  531. (> level-spaces parent-spaces) ; child
  532. (let [parent (if uuid [:block/uuid uuid] (:page/id last-parent))
  533. block (cond->
  534. (assoc block
  535. :block/parent parent
  536. :block/left parent)
  537. ;; fix block levels with wrong order
  538. ;; For example:
  539. ;; - a
  540. ;; - b
  541. ;; What if the input indentation is two spaces instead of 4 spaces
  542. (>= (- level-spaces parent-spaces) 1)
  543. (assoc :block/level (inc level)))
  544. parents' (conj parents block)
  545. result' (conj result block)]
  546. [others parents' block result'])
  547. ;; - a
  548. ;; - b
  549. ;; - c
  550. (and (>= (count parents) 2)
  551. (< level-spaces parent-spaces)
  552. (> level-spaces (:block/level-spaces (nth parents (- (count parents) 2)))))
  553. (let [block (assoc block
  554. :block/parent parent
  555. :block/left [:block/uuid uuid]
  556. :block/level level
  557. :block/level-spaces parent-spaces)
  558. parents' (conj (vec (butlast parents)) block)
  559. result' (conj result block)]
  560. [others parents' block result'])
  561. (< level-spaces parent-spaces) ; outdent
  562. (let [parents' (vec (filter (fn [p] (<= (:block/level-spaces p) level-spaces)) parents))
  563. blocks (cons (assoc (first blocks) :block/level (dec level))
  564. (rest blocks))]
  565. [blocks parents' (last parents') result]))]
  566. (recur blocks parents sibling result)))))
  567. (defn parse-block
  568. ([block]
  569. (parse-block block nil))
  570. ([{:block/keys [uuid content meta file page parent left format] :as block} {:keys [with-id?]
  571. :or {with-id? true}}]
  572. (when-not (string/blank? content)
  573. (let [block (dissoc block :block/pre-block?)
  574. ast (format/to-edn content format nil)
  575. new-block (first (extract-blocks ast content with-id? format))
  576. parent-refs (->> (db/get-block-parent (state/get-current-repo) uuid)
  577. :block/path-refs
  578. (map :db/id))
  579. {:block/keys [refs]} new-block
  580. ref-pages (filter :block/name refs)
  581. path-ref-pages (concat ref-pages parent-refs [(:db/id page)])
  582. block (merge
  583. block
  584. new-block
  585. {:block/path-refs path-ref-pages})]
  586. (if uuid (assoc block :block/uuid uuid) block)))))
  587. (defn macro-subs
  588. [macro-content arguments]
  589. (loop [s macro-content
  590. args arguments
  591. n 1]
  592. (if (seq args)
  593. (recur
  594. (string/replace s (str "$" n) (first args))
  595. (rest args)
  596. (inc n))
  597. s)))
  598. (defn break-line-paragraph?
  599. [[typ break-lines]]
  600. (and (= typ "Paragraph")
  601. (every? #(= % ["Break_Line"]) break-lines)))
  602. (defn trim-break-lines!
  603. [ast]
  604. (drop-while break-line-paragraph? ast))