block.cljs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  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. [medley.core :as medley]
  13. [frontend.state :as state]
  14. [frontend.db :as db]))
  15. (defn heading-block?
  16. [block]
  17. (and
  18. (vector? block)
  19. (= "Heading" (first block))))
  20. (defn get-tag
  21. [block]
  22. (and (vector? block)
  23. (= "Tag" (first block))
  24. (second block)))
  25. (defn get-page-reference
  26. [block]
  27. (let [page (cond
  28. (and (vector? block) (= "Link" (first block)))
  29. (let [typ (first (:url (second block)))]
  30. ;; {:url ["File" "file:../pages/hello_world.org"], :label [["Plain" "hello world"]], :title nil}
  31. (or
  32. (and
  33. (= typ "Search")
  34. ;; FIXME: alert error
  35. (not (contains? #{\# \* \/ \[} (first (second (:url (second block))))))
  36. (let [page (second (:url (second block)))
  37. ext (some-> (util/get-file-ext page) keyword)]
  38. (when (and (not (util/starts-with? page "http:"))
  39. (not (util/starts-with? page "https:"))
  40. (not (util/starts-with? page "file:"))
  41. (or (= ext :excalidraw) (not (contains? (config/supported-formats) ext))))
  42. page)))
  43. (and
  44. (= typ "Complex")
  45. (= (:protocol (second (:url (second block)))) "file")
  46. (:link (second (:url (second block)))))
  47. (and
  48. (= typ "File")
  49. (second (first (:label (second block)))))))
  50. (and (vector? block) (= "Nested_link" (first block)))
  51. (let [content (:content (last block))]
  52. (subs content 2 (- (count content) 2)))
  53. (and (vector? block)
  54. (= "Macro" (first block)))
  55. (let [{:keys [name arguments]} (second block)]
  56. (let [argument (string/join ", " arguments)]
  57. (when (and (= name "embed")
  58. (string? argument)
  59. (text/page-ref? argument))
  60. (text/page-ref-un-brackets! argument))))
  61. (and (vector? block)
  62. (= "Tag" (first block)))
  63. (let [text (second block)]
  64. (when (and
  65. (string? text)
  66. (text/page-ref? text))
  67. (text/page-ref-un-brackets! text)))
  68. :else
  69. nil)]
  70. (cond
  71. (and
  72. (string? page)
  73. (text/block-ref? page))
  74. (text/block-ref-un-brackets! page)
  75. (and
  76. (string? page)
  77. (not (string/blank? page)))
  78. (string/trim page)
  79. :else
  80. nil)))
  81. (defn get-block-reference
  82. [block]
  83. (when-let [block-id (cond
  84. (and (vector? block)
  85. (= "Block_reference" (first block)))
  86. (last block)
  87. (and (vector? block)
  88. (= "Macro" (first block)))
  89. (let [{:keys [name arguments]} (second block)]
  90. (when (and (= name "embed")
  91. (string? (first arguments))
  92. (string/starts-with? (first arguments) "((")
  93. (string/ends-with? (first arguments) "))"))
  94. (subs (first arguments) 2 (- (count (first arguments)) 2))))
  95. (and (vector? block)
  96. (= "Link" (first block))
  97. (map? (second block))
  98. (= "id" (:protocol (second (:url (second block))))))
  99. (:link (second (:url (second block))))
  100. :else
  101. nil)]
  102. (when (and block-id
  103. (util/uuid-string? block-id))
  104. block-id)))
  105. ;; FIXME:
  106. (defn extract-title
  107. [block]
  108. (-> (:title (second block))
  109. first
  110. second))
  111. (defn paragraph-block?
  112. [block]
  113. (and
  114. (vector? block)
  115. (= "Paragraph" (first block))))
  116. (defn hiccup-block?
  117. [block]
  118. (and
  119. (vector? block)
  120. (= "Hiccup" (first block))))
  121. (defn timestamp-block?
  122. [block]
  123. (and
  124. (vector? block)
  125. (= "Timestamp" (first block))))
  126. (defn properties-block?
  127. [block]
  128. (and
  129. (vector? block)
  130. (= "Property_Drawer" (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. (defn extract-properties
  140. [[_ properties] _start-pos _end-pos]
  141. (let [properties (into {} properties)
  142. page-refs (->>
  143. (map (fn [v]
  144. (when (string? v)
  145. (let [page-refs (->> (re-seq text/page-ref-re v)
  146. (map second))
  147. tags (->> (string/split v #",")
  148. (filter (fn [s] (= \# (first s))))
  149. (map (fn [s] (subs s 1))))]
  150. (concat page-refs tags))))
  151. (vals properties))
  152. (apply concat)
  153. (remove string/blank?))
  154. properties (->> properties
  155. (medley/map-kv (fn [k v]
  156. (let [v (string/trim v)
  157. k (string/replace k " " "-")
  158. k (string/replace k "_" "-")
  159. k (string/lower-case k)
  160. v (cond
  161. (= v "true")
  162. true
  163. (= v "false")
  164. false
  165. (re-find #"^\d+$" v)
  166. (util/safe-parse-int v)
  167. (and (= "\"" (first v) (last v))) ; wrapped in ""
  168. (string/trim (subs v 1 (dec (count v))))
  169. (contains? @non-parsing-properties (string/lower-case k))
  170. v
  171. :else
  172. (let [v' v
  173. ;; built-in collections
  174. comma? (contains? #{"tags" "alias"} k)]
  175. (if (and k v'
  176. (contains? config/markers k)
  177. (util/safe-parse-int v'))
  178. (util/safe-parse-int v')
  179. (text/split-page-refs-without-brackets v' comma?))))]
  180. [(keyword k) v]))))]
  181. {:properties properties
  182. :page-refs page-refs}))
  183. (defn- paragraph-timestamp-block?
  184. [block]
  185. (and (paragraph-block? block)
  186. (timestamp-block? (first (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. :scheduled-ast v}
  208. :deadline
  209. {:deadline day
  210. :deadline-ast v})
  211. repetition
  212. (assoc :repeated? true))))))]
  213. (apply merge m)))
  214. (defn convert-page-if-journal
  215. "Convert journal file name to user' custom date format"
  216. [original-page-name]
  217. (let [page-name (string/lower-case original-page-name)
  218. day (date/journal-title->int page-name)]
  219. (if day
  220. (let [original-page-name (date/int->journal-title day)]
  221. [original-page-name (string/lower-case original-page-name) day])
  222. [original-page-name page-name day])))
  223. (defn page-name->map
  224. [original-page-name with-id?]
  225. (when original-page-name
  226. (let [[original-page-name page-name journal-day] (convert-page-if-journal original-page-name)
  227. m (merge
  228. {:block/name page-name
  229. :block/original-name original-page-name}
  230. (when with-id?
  231. (if-let [block (db/entity [:block/name page-name])]
  232. {}
  233. {:block/uuid (db/new-block-id)})))]
  234. (if journal-day
  235. (merge m
  236. {:block/journal? true
  237. :block/journal-day journal-day})
  238. (assoc m :block/journal? false)))))
  239. (defn with-page-refs
  240. [{:keys [title body tags refs] :as block} with-id?]
  241. (let [refs (->> (concat tags refs)
  242. (remove string/blank?)
  243. (distinct))
  244. refs (atom refs)]
  245. (walk/postwalk
  246. (fn [form]
  247. (when-let [page (get-page-reference form)]
  248. (swap! refs conj page))
  249. (when-let [tag (get-tag form)]
  250. (when (util/tag-valid? tag)
  251. (swap! refs conj tag)))
  252. form)
  253. (concat title body))
  254. (let [refs (remove string/blank? @refs)
  255. children-pages (->> (mapcat (fn [p]
  256. (when (and (string/includes? p "/")
  257. (not (string/starts-with? p "../"))
  258. (not (string/starts-with? p "./"))
  259. (not (string/starts-with? p "http")))
  260. ;; Don't create the last page for now
  261. (butlast (string/split p #"/"))))
  262. refs)
  263. (remove string/blank?))
  264. refs (->> (distinct (concat refs children-pages))
  265. (remove nil?))
  266. refs (map (fn [ref] (page-name->map ref with-id?)) refs)]
  267. (assoc block :refs refs))))
  268. (defn with-block-refs
  269. [{:keys [title body] :as block}]
  270. (let [ref-blocks (atom nil)]
  271. (walk/postwalk
  272. (fn [form]
  273. (when-let [block (get-block-reference form)]
  274. (swap! ref-blocks conj block))
  275. form)
  276. (concat title body))
  277. (let [ref-blocks (->> @ref-blocks
  278. (filter util/uuid-string?))
  279. ref-blocks (map
  280. (fn [id]
  281. [:block/uuid (medley/uuid id)])
  282. ref-blocks)
  283. refs (distinct (concat (:refs block) ref-blocks))]
  284. (assoc block :refs refs))))
  285. (defn update-src-pos-meta!
  286. [{:keys [body] :as block}]
  287. (let [body (walk/postwalk
  288. (fn [form]
  289. (if (and (vector? form)
  290. (= (first form) "Src")
  291. (map? (:pos_meta (second form))))
  292. (let [{:keys [start_pos end_pos]} (:pos_meta (second form))
  293. new_start_pos (- start_pos (get-in block [:meta :start-pos]))]
  294. ["Src" (assoc (second form)
  295. :pos_meta {:start_pos new_start_pos
  296. :end_pos (+ new_start_pos (- end_pos start_pos))})])
  297. form))
  298. body)]
  299. (assoc block :body body)))
  300. (defn block-keywordize
  301. [block]
  302. (medley/map-keys
  303. (fn [k]
  304. (if (namespace k)
  305. k
  306. (keyword "block" k)))
  307. block))
  308. (defn safe-blocks
  309. [blocks]
  310. (map (fn [block]
  311. (if (map? block)
  312. (block-keywordize (util/remove-nils block))
  313. block))
  314. blocks))
  315. (defn with-path-refs
  316. [blocks]
  317. (loop [blocks blocks
  318. acc []
  319. parents []]
  320. (if (empty? blocks)
  321. acc
  322. (let [block (first blocks)
  323. cur-level (:block/level block)
  324. level-diff (- cur-level
  325. (get (last parents) :block/level 0))
  326. [path-refs parents]
  327. (cond
  328. (zero? level-diff) ; sibling
  329. (let [path-refs (mapcat :block/refs (drop-last parents))
  330. parents (conj (vec (butlast parents)) block)]
  331. [path-refs parents])
  332. (> level-diff 0) ; child
  333. (let [path-refs (mapcat :block/refs parents)]
  334. [path-refs (conj parents block)])
  335. (< level-diff 0) ; new parent
  336. (let [parents (vec (take-while (fn [p] (< (:block/level p) cur-level)) parents))
  337. path-refs (mapcat :block/refs parents)]
  338. [path-refs (conj parents block)]))
  339. path-ref-pages (->> path-refs
  340. (concat (:block/refs block))
  341. (map (fn [ref]
  342. (cond
  343. (map? ref)
  344. (:block/name ref)
  345. :else
  346. ref)))
  347. (remove string/blank?)
  348. (map (fn [ref]
  349. (if (string? ref)
  350. {:block/name (string/lower-case ref)}
  351. ref)))
  352. (remove vector?)
  353. (distinct))]
  354. (recur (rest blocks)
  355. (conj acc (assoc block :block/path-refs path-ref-pages))
  356. parents)))))
  357. (defn block-tags->pages
  358. [{:keys [tags] :as block}]
  359. (if (seq tags)
  360. (assoc block :tags (map (fn [tag]
  361. [:block/name (string/lower-case tag)]) tags))
  362. block))
  363. (defn extract-blocks
  364. [blocks content with-id? format]
  365. (let [encoded-content (utf8/encode content)
  366. last-pos (utf8/length encoded-content)
  367. pre-block-body (atom nil)
  368. blocks
  369. (loop [headings []
  370. block-body []
  371. blocks (reverse blocks)
  372. timestamps {}
  373. properties {}
  374. last-pos last-pos
  375. last-level 1000
  376. children []]
  377. (if (seq blocks)
  378. (let [[block {:keys [start_pos end_pos]}] (first blocks)
  379. unordered? (:unordered (second block))
  380. markdown-heading? (and (false? unordered?) (= :markdown format))]
  381. (cond
  382. (paragraph-timestamp-block? block)
  383. (let [timestamps (extract-timestamps block)
  384. timestamps' (merge timestamps timestamps)
  385. other-body (->> (second block)
  386. (drop-while #(= ["Break_Line"] %)))]
  387. (recur headings (conj block-body ["Paragraph" other-body]) (rest blocks) timestamps' properties last-pos last-level children))
  388. (properties-block? block)
  389. (let [properties (extract-properties block start_pos end_pos)]
  390. (recur headings block-body (rest blocks) timestamps properties last-pos last-level children))
  391. (heading-block? block)
  392. (let [id (or (when-let [custom-id (or (get-in properties [:properties :custom-id])
  393. (get-in properties [:properties :custom_id])
  394. (get-in properties [:properties :id]))]
  395. (let [custom-id (string/trim custom-id)]
  396. (when (util/uuid-string? custom-id)
  397. (uuid custom-id))))
  398. (db/new-block-id))
  399. ref-pages-in-properties (->> (:page-refs properties)
  400. (remove string/blank?))
  401. block (second block)
  402. block (if markdown-heading?
  403. (assoc block
  404. :type :heading
  405. :level 1
  406. :heading-level (:level block))
  407. block)
  408. level (:level block)
  409. [children current-block-children]
  410. (cond
  411. (< level last-level)
  412. (let [current-block-children (set (->> (filter #(< level (second %)) children)
  413. (map first)
  414. (map (fn [id]
  415. [:block/uuid id]))))
  416. others (vec (remove #(< level (second %)) children))]
  417. [(conj others [id level])
  418. current-block-children])
  419. (>= level last-level)
  420. [(conj children [id level])
  421. #{}])
  422. block (-> (assoc block
  423. :uuid id
  424. :body (vec (reverse block-body))
  425. :properties (:properties properties)
  426. :refs ref-pages-in-properties
  427. :children (or current-block-children []))
  428. (assoc-in [:meta :start-pos] start_pos)
  429. (assoc-in [:meta :end-pos] last-pos))
  430. block (if (seq timestamps)
  431. (merge block (timestamps->scheduled-and-deadline timestamps))
  432. block)
  433. block (-> block
  434. (with-page-refs with-id?)
  435. with-block-refs
  436. block-tags->pages
  437. update-src-pos-meta!)
  438. last-pos' (get-in block [:meta :start-pos])]
  439. (recur (conj headings block) [] (rest blocks) {} {} last-pos' (:level block) children))
  440. :else
  441. (let [block-body' (conj block-body block)]
  442. (recur headings block-body' (rest blocks) timestamps properties last-pos last-level children))))
  443. (do
  444. (when (seq block-body)
  445. (reset! pre-block-body (reverse block-body)))
  446. (-> (reverse headings)
  447. safe-blocks))))]
  448. (let [first-block (first blocks)
  449. first-block-start-pos (get-in first-block [:block/meta :start-pos])
  450. blocks (if (seq @pre-block-body)
  451. (cons
  452. (merge
  453. (let [content (utf8/substring encoded-content 0 first-block-start-pos)]
  454. (->
  455. {:uuid (db/new-block-id)
  456. :content content
  457. :level 1
  458. :meta {:start-pos 0
  459. :end-pos (or first-block-start-pos
  460. (utf8/length encoded-content))}
  461. :body @pre-block-body
  462. :pre-block? true}
  463. (block-keywordize)))
  464. (select-keys first-block [:block/file :block/format :block/page]))
  465. blocks)
  466. blocks)]
  467. (with-path-refs blocks))))
  468. (defn with-parent-and-left
  469. [page-id blocks]
  470. (loop [blocks blocks
  471. parents [{:page/id page-id ; db id or lookup ref [:block/name "xxx"]
  472. :block/level 0}]
  473. result []]
  474. (if (empty? blocks)
  475. result
  476. (let [[block & others] blocks
  477. cur-level (:block/level block)
  478. {:block/keys [uuid level parent] :as last-parent} (last parents)
  479. [blocks parents result]
  480. (cond
  481. (= cur-level level) ; sibling
  482. (let [block (assoc block
  483. :block/parent parent
  484. :block/left [:block/uuid uuid])
  485. parents' (conj (vec (butlast parents)) block)
  486. result' (conj result block)]
  487. [others parents' result'])
  488. (> cur-level level) ; child
  489. (let [parent (if uuid [:block/uuid uuid] (:page/id last-parent))
  490. block (assoc block
  491. :block/parent parent
  492. :block/left parent)
  493. parents' (conj parents block)
  494. result' (conj result block)]
  495. [others parents' result'])
  496. (< cur-level level) ; outdent
  497. (let [parents' (vec (filter (fn [p] (<= (:block/level p) cur-level)) parents))]
  498. [blocks parents' result]))]
  499. (recur blocks parents result)))))
  500. (defn parse-block
  501. ([block]
  502. (parse-block block nil))
  503. ([{:block/keys [uuid content meta file page parent left format] :as block} {:keys [with-id?]
  504. :or {with-id? true}}]
  505. (when-not (string/blank? content)
  506. (let [block (dissoc block :block/pre-block?)
  507. ast (format/to-edn content format nil)
  508. new-block (first (extract-blocks ast content with-id? format))
  509. parent-refs (->> (db/get-block-parent (state/get-current-repo) uuid)
  510. :block/path-refs
  511. (map :db/id))
  512. {:block/keys [refs]} new-block
  513. ref-pages (filter :block/name refs)
  514. path-ref-pages (concat ref-pages parent-refs)
  515. block (merge
  516. block
  517. new-block
  518. {:block/path-refs path-ref-pages})]
  519. (if uuid (assoc block :block/uuid uuid) block)))))
  520. (defn macro-subs
  521. [macro-content arguments]
  522. (loop [s macro-content
  523. args arguments
  524. n 1]
  525. (if (seq args)
  526. (recur
  527. (string/replace s (str "$" n) (first args))
  528. (rest args)
  529. (inc n))
  530. s)))
  531. (defn break-line-paragraph?
  532. [[typ break-lines]]
  533. (and (= typ "Paragraph")
  534. (every? #(= % ["Break_Line"]) break-lines)))
  535. (defn trim-break-lines!
  536. [ast]
  537. (drop-while break-line-paragraph? ast))