block.cljs 78 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079
  1. (ns frontend.components.block
  2. (:refer-clojure :exclude [range])
  3. (:require [frontend.config :as config]
  4. [cljs.core.match :refer-macros [match]]
  5. [promesa.core :as p]
  6. [frontend.fs :as fs]
  7. [clojure.string :as string]
  8. [frontend.util :as util]
  9. [rum.core :as rum]
  10. [frontend.state :as state]
  11. [frontend.db :as db]
  12. [frontend.db.model :as model]
  13. [frontend.db.query-dsl :as query-dsl]
  14. [dommy.core :as d]
  15. [datascript.core :as dc]
  16. [goog.dom :as gdom]
  17. [frontend.handler.expand :as expand]
  18. [frontend.components.svg :as svg]
  19. [frontend.components.draw :as draw]
  20. [frontend.components.datetime :as datetime-comp]
  21. [frontend.ui :as ui]
  22. [frontend.handler.editor :as editor-handler]
  23. [frontend.handler.block :as block-handler]
  24. [frontend.handler.route :as route-handler]
  25. [frontend.handler.dnd :as dnd]
  26. [frontend.handler.ui :as ui-handler]
  27. [frontend.handler.repeated :as repeated]
  28. [goog.object :as gobj]
  29. [medley.core :as medley]
  30. [cljs.reader :as reader]
  31. [frontend.util :as util :refer-macros [profile]]
  32. [frontend.db-mixins :as db-mixins]
  33. [frontend.extensions.latex :as latex]
  34. [frontend.components.lazy-editor :as lazy-editor]
  35. [frontend.extensions.highlight :as highlight]
  36. [frontend.extensions.sci :as sci]
  37. ["/frontend/utils" :as utils]
  38. [frontend.format.block :as block]
  39. [clojure.walk :as walk]
  40. [cljs-bean.core :as bean]
  41. [frontend.handler.image :as image-handler]
  42. [frontend.format.mldoc :as mldoc]
  43. [frontend.text :as text]
  44. [frontend.utf8 :as utf8]
  45. [frontend.date :as date]
  46. [frontend.security :as security]
  47. [reitit.frontend.easy :as rfe]
  48. [frontend.commands :as commands]
  49. [lambdaisland.glogi :as log]
  50. [frontend.context.i18n :as i18n]))
  51. ;; TODO: remove rum/with-context because it'll make reactive queries not working
  52. (defn safe-read-string
  53. ([s]
  54. (safe-read-string s true))
  55. ([s warn?]
  56. (try
  57. (reader/read-string s)
  58. (catch js/Error e
  59. (println "read-string error:")
  60. (js/console.error e)
  61. (when warn?
  62. [:div.warning {:title "read-string failed"}
  63. s])))))
  64. ;; local state
  65. (defonce *block-children
  66. (atom {}))
  67. (defonce *dragging?
  68. (atom false))
  69. (defonce *dragging-block
  70. (atom nil))
  71. (defonce *move-to-top?
  72. (atom false))
  73. ;; TODO: Improve blocks grouped by pages
  74. (defonce max-blocks-per-page 500)
  75. (defonce virtual-list-scroll-step 450)
  76. (defonce virtual-list-previous 50)
  77. (defonce container-ids (atom {}))
  78. (defonce container-idx (atom 0))
  79. ;; TODO:
  80. ;; add `key`
  81. (defn- remove-nils
  82. [col]
  83. (remove nil? col))
  84. (defn vec-cat
  85. [& args]
  86. (->> (apply concat args)
  87. remove-nils
  88. vec))
  89. (defn ->elem
  90. ([elem items]
  91. (->elem elem nil items))
  92. ([elem attrs items]
  93. (let [elem (keyword elem)]
  94. (if attrs
  95. (vec
  96. (cons elem
  97. (cons attrs
  98. (seq items))))
  99. (vec
  100. (cons elem
  101. (seq items)))))))
  102. (defn- join-lines
  103. [l]
  104. (string/trim (apply str l)))
  105. (defn- string-of-url
  106. [url]
  107. (match url
  108. (:or ["File" s] ["Search" s])
  109. s
  110. ["Complex" m]
  111. (let [{:keys [link protocol]} m]
  112. (if (= protocol "file")
  113. link
  114. (str protocol ":" link)))))
  115. (defn- get-file-absolute-path
  116. [config path]
  117. (let [path (string/replace path "file:" "")
  118. block-id (:block/uuid config)
  119. current-file (and block-id
  120. (:file/path (:page/file (:block/page (db/entity [:block/uuid block-id])))))]
  121. (when current-file
  122. (let [parts (string/split current-file #"/")
  123. parts-2 (string/split path #"/")
  124. current-dir (string/join "/" (drop-last 1 parts))]
  125. (cond
  126. (util/starts-with? path "/")
  127. path
  128. (and (not (util/starts-with? path ".."))
  129. (not (util/starts-with? path ".")))
  130. (str current-dir "/" path)
  131. :else
  132. (let [parts (loop [acc []
  133. parts (reverse parts)
  134. col (reverse parts-2)]
  135. (if (empty? col)
  136. acc
  137. (let [[part parts] (case (first col)
  138. ".."
  139. [(first parts) (rest parts)]
  140. "."
  141. ["" parts]
  142. [(first col) (rest parts)])]
  143. (recur (conj acc part)
  144. parts
  145. (rest col)))))
  146. parts (remove #(string/blank? %) parts)]
  147. (string/join "/" (reverse parts))))))))
  148. (defonce *resizing-image? (atom false))
  149. (rum/defcs resizable-image <
  150. (rum/local nil ::size)
  151. {:will-unmount (fn [state]
  152. (reset! *resizing-image? false)
  153. state)}
  154. [state config title src metadata full_text local?]
  155. (rum/with-context [[t] i18n/*tongue-context*]
  156. (let [size (get state ::size)]
  157. (ui/resize-provider
  158. (ui/resize-consumer
  159. (cond->
  160. {:className "resize"
  161. :onSizeChanged (fn [value]
  162. (when (and (not @*resizing-image?)
  163. (some? @size)
  164. (not= value @size))
  165. (reset! *resizing-image? true))
  166. (reset! size value))
  167. :onMouseUp (fn []
  168. (when (and @size @*resizing-image?)
  169. (when-let [block-id (:block/uuid config)]
  170. (let [size (bean/->clj @size)]
  171. (editor-handler/resize-image! block-id metadata full_text size))))
  172. (when @*resizing-image?
  173. ;; TODO: need a better way to prevent the clicking to edit current block
  174. (js/setTimeout #(reset! *resizing-image? false) 200)))
  175. :onClick (fn [e]
  176. (when @*resizing-image? (util/stop e)))}
  177. (and (:width metadata) (not (util/mobile?)))
  178. (assoc :style {:width (:width metadata)}))
  179. [:div.asset-container
  180. [:img.rounded-sm.shadow-xl.relative
  181. (merge
  182. {:loading "lazy"
  183. :src src
  184. :title title}
  185. metadata)]
  186. [:span.ctl
  187. [:a.delete
  188. {:title "Delete this image"
  189. :on-click
  190. (fn [e]
  191. (when-let [block-id (:block/uuid config)]
  192. (let [confirm-fn (ui/make-confirm-modal
  193. {:title (t :asset/confirm-delete (.toLocaleLowerCase (t :text/image)))
  194. :sub-title (if local? :asset/physical-delete "")
  195. :sub-checkbox? local?
  196. :on-confirm (fn [e {:keys [close-fn sub-selected]}]
  197. (close-fn)
  198. (editor-handler/delete-asset-of-block!
  199. {:block-id block-id
  200. :local? local?
  201. :repo (state/get-current-repo)
  202. :href src
  203. :title title
  204. :full-text full_text}))})]
  205. (state/set-modal! confirm-fn)
  206. (util/stop e))))}
  207. svg/trash-sm]]])))))
  208. (rum/defcs asset-link < rum/reactive
  209. (rum/local nil ::src)
  210. [state config title href label metadata full_text]
  211. (let [src (::src state)
  212. granted? (state/sub [:nfs/user-granted? (state/get-current-repo)])
  213. href (config/get-local-asset-absolute-path href)]
  214. (when (or granted? (util/electron?))
  215. (p/then (editor-handler/make-asset-url href) #(reset! src %)))
  216. (when @src
  217. (resizable-image config title @src metadata full_text true))))
  218. ;; TODO: safe encoding asciis
  219. ;; TODO: image link to another link
  220. (defn image-link [config url href label metadata full_text]
  221. (let [metadata (if (string/blank? metadata)
  222. nil
  223. (safe-read-string metadata false))
  224. title (second (first label))]
  225. (if (and (config/local-asset? href)
  226. (config/local-db? (state/get-current-repo)))
  227. (asset-link config title href label metadata full_text)
  228. (let [href (if (util/starts-with? href "http")
  229. href
  230. (get-file-absolute-path config href))]
  231. (resizable-image config title href metadata full_text false)))))
  232. (defn repetition-to-string
  233. [[[kind] [duration] n]]
  234. (let [kind (case kind
  235. "Dotted" "."
  236. "Plus" "+"
  237. "DoublePlus" "++")]
  238. (str kind n (string/lower-case (str (first duration))))))
  239. (defn timestamp-to-string
  240. [{:keys [active date time repetition wday active]}]
  241. (let [{:keys [year month day]} date
  242. {:keys [hour min]} time
  243. [open close] (if active ["<" ">"] ["[" "]"])
  244. repetition (if repetition
  245. (str " " (repetition-to-string repetition))
  246. "")
  247. hour (if hour (util/zero-pad hour))
  248. min (if min (util/zero-pad min))
  249. time (cond
  250. (and hour min)
  251. (util/format " %s:%s" hour min)
  252. hour
  253. (util/format " %s" hour)
  254. :else
  255. "")]
  256. (util/format "%s%s-%s-%s %s%s%s%s"
  257. open
  258. (str year)
  259. (util/zero-pad month)
  260. (util/zero-pad day)
  261. wday
  262. time
  263. repetition
  264. close)))
  265. (defn timestamp [{:keys [active date time repetition wday] :as t} kind]
  266. (let [prefix (case kind
  267. "Scheduled"
  268. [:i {:class "fa fa-calendar"
  269. :style {:margin-right 3.5}}]
  270. "Deadline"
  271. [:i {:class "fa fa-calendar-times-o"
  272. :style {:margin-right 3.5}}]
  273. "Date"
  274. nil
  275. "Closed"
  276. nil
  277. "Started"
  278. [:i {:class "fa fa-clock-o"
  279. :style {:margin-right 3.5}}]
  280. "Start"
  281. "From: "
  282. "Stop"
  283. "To: "
  284. nil)]
  285. (let [class (if (= kind "Closed")
  286. "line-through")]
  287. [:span.timestamp (cond-> {:active (str active)}
  288. class
  289. (assoc :class class))
  290. prefix
  291. (timestamp-to-string t)])))
  292. (defn range [{:keys [start stop]} stopped?]
  293. [:div {:class "timestamp-range"
  294. :stopped stopped?}
  295. (timestamp start "Start")
  296. (timestamp stop "Stop")])
  297. (declare map-inline)
  298. (declare markup-element-cp)
  299. (declare markup-elements-cp)
  300. (declare page-reference)
  301. (defn page-cp
  302. [{:keys [html-export? label children contents-page?] :as config} page]
  303. (when-let [page-name (:page/name page)]
  304. (let [source-page (model/get-alias-source-page (state/get-current-repo)
  305. (string/lower-case page-name))
  306. original-page-name (get page :page/original-name page-name)
  307. original-page-name (if (date/valid-journal-title? original-page-name)
  308. (string/capitalize original-page-name)
  309. original-page-name)
  310. page (string/lower-case page-name)
  311. redirect-page-name (cond
  312. (:page/alias? config)
  313. page
  314. (db/page-empty? (state/get-current-repo) page-name)
  315. (or (when source-page (:page/name source-page))
  316. page)
  317. :else
  318. page)
  319. href (if html-export?
  320. (util/encode-str page)
  321. (rfe/href :page {:name redirect-page-name}))]
  322. [:a.page-ref
  323. {:data-ref page-name
  324. :href href
  325. :on-click (fn [e]
  326. (util/stop e)
  327. (if (gobj/get e "shiftKey")
  328. (when-let [page-entity (db/entity [:page/name redirect-page-name])]
  329. (state/sidebar-add-block!
  330. (state/get-current-repo)
  331. (:db/id page-entity)
  332. :page
  333. {:page page-entity}))
  334. (route-handler/redirect! {:to :page
  335. :path-params {:name redirect-page-name}}))
  336. (when (and contents-page?
  337. (state/get-left-sidebar-open?))
  338. (ui-handler/close-left-sidebar!)))}
  339. (if (seq children)
  340. (for [child children]
  341. (if (= (first child) "Label")
  342. [:span (last child)]
  343. (let [{:keys [content children]} (last child)
  344. page-name (subs content 2 (- (count content) 2))]
  345. (page-reference html-export? page-name (assoc config :children children) nil))))
  346. (if (and label
  347. (string? label)
  348. (not (string/blank? label))) ; alias
  349. label
  350. original-page-name))])))
  351. (rum/defc asset-reference
  352. [title path]
  353. (let [repo-path (config/get-repo-dir (state/get-current-repo))
  354. full-path (.. util/node-path (join repo-path (config/get-local-asset-absolute-path path)))]
  355. [:a.asset-ref {:target "_blank" :href full-path} (or title path)]))
  356. (rum/defc page-reference < rum/reactive
  357. [html-export? s config label]
  358. (let [show-brackets? (state/show-brackets?)
  359. nested-link? (:nested-link? config)
  360. contents-page? (= "contents" (string/lower-case (str (:id config))))]
  361. [:span.page-reference
  362. (when (and (or show-brackets? nested-link?)
  363. (not html-export?)
  364. (not contents-page?))
  365. [:span.text-gray-500 "[["])
  366. (if (string/ends-with? s ".excalidraw")
  367. [:a.page-ref
  368. {:on-click (fn [e]
  369. (util/stop e)
  370. (set! (.-href js/window.location)
  371. (rfe/href :draw nil {:file (string/replace s (str config/default-draw-directory "/") "")})))}
  372. [:span
  373. (svg/excalidraw-logo)
  374. (string/capitalize (draw/get-file-title s))]]
  375. (page-cp (assoc config
  376. :label (mldoc/plain->text label)
  377. :contents-page? contents-page?) {:page/name s}))
  378. (when (and (or show-brackets? nested-link?)
  379. (not html-export?)
  380. (not contents-page?))
  381. [:span.text-gray-500 "]]"])]))
  382. (defn- latex-environment-content
  383. [name option content]
  384. (if (= (string/lower-case name) "equation")
  385. content
  386. (util/format "\\begin%s\n%s\\end{%s}"
  387. (str "{" name "}" option)
  388. content
  389. name)))
  390. (declare blocks-container)
  391. (rum/defc block-embed < rum/reactive db-mixins/query
  392. [config id]
  393. (let [blocks (db/get-block-and-children (state/get-current-repo) id)]
  394. [:div.color-level.embed-block.bg-base-2 {:style {:z-index 2}}
  395. [:div.px-3.pt-1.pb-2
  396. (blocks-container blocks (assoc config
  397. :embed? true
  398. :ref? false))]]))
  399. (rum/defc page-embed < rum/reactive db-mixins/query
  400. [config page-name]
  401. (let [page-name (string/lower-case page-name)
  402. current-page (state/get-current-page)]
  403. [:div.color-level.embed.embed-page.bg-base-2
  404. {:class (if (:sidebar? config) "in-sidebar")}
  405. [:section.flex.items-center.p-1.embed-header
  406. [:div.mr-3 svg/page]
  407. (page-cp config {:page/name page-name})]
  408. (when (and
  409. (not= (string/lower-case (or current-page ""))
  410. page-name)
  411. (not= (string/lower-case (get config :id ""))
  412. page-name))
  413. (let [blocks (db/get-page-blocks (state/get-current-repo) page-name)]
  414. (blocks-container blocks (assoc config
  415. :embed? true
  416. :ref? false))))]))
  417. (defn- get-label-text
  418. [label]
  419. (and (= 1 (count label))
  420. (let [label (first label)]
  421. (string? (last label))
  422. (last label))))
  423. (defn- get-page
  424. [label]
  425. (when-let [label-text (get-label-text label)]
  426. (db/entity [:page/name (string/lower-case label-text)])))
  427. (defn- macro->text
  428. [name arguments]
  429. (if (and (seq arguments)
  430. (not= arguments ["null"]))
  431. (util/format "{{{%s %s}}}" name (string/join ", " arguments))
  432. (util/format "{{{%s}}}" name)))
  433. (declare block-content)
  434. (rum/defc block-reference < rum/reactive
  435. [config id]
  436. (when-not (string/blank? id)
  437. (let [block (and (util/uuid-string? id)
  438. (db/pull-block (uuid id)))]
  439. (if block
  440. [:span
  441. [:div.block-ref-wrap
  442. {:on-click (fn [e]
  443. (util/stop e)
  444. (if (gobj/get e "shiftKey")
  445. (state/sidebar-add-block!
  446. (state/get-current-repo)
  447. (:db/id block)
  448. :block-ref
  449. {:block block})
  450. (route-handler/redirect! {:to :page
  451. :path-params {:name id}})))}
  452. (let [title (:block/title block)]
  453. (if (empty? title)
  454. ;; display the content
  455. [:div.block-ref
  456. (block-content config block nil (:block/uuid block) (:slide? config))]
  457. (->elem
  458. :span.block-ref
  459. (map-inline config title))))]]
  460. [:span.warning.mr-1 {:title "Block ref invalid"}
  461. (util/format "((%s))" id)]))))
  462. (defn inline-text
  463. [format v]
  464. (when (string? v)
  465. (let [inline-list (mldoc/inline->edn v (mldoc/default-config format))]
  466. [:div.inline.mr-1 (map-inline {} inline-list)])))
  467. (defn selection-range-in-block? []
  468. (and (= "Range" (. (js/window.getSelection) -type))
  469. (-> (js/window.getSelection)
  470. (.-anchorNode)
  471. (.-parentNode)
  472. (.closest ".block-content"))))
  473. (defn- render-macro
  474. [config name arguments macro-content format]
  475. (if macro-content
  476. (let [ast (->> (mldoc/->edn macro-content (mldoc/default-config format))
  477. (map first))
  478. block? (contains? #{"Paragraph"
  479. "Raw_Html"
  480. "Hiccup"}
  481. (ffirst ast))]
  482. (if block?
  483. [:div
  484. (markup-elements-cp (assoc config :block/format format) ast)]
  485. (inline-text format macro-content)))
  486. [:span.warning {:title (str "Unsupported macro name: " name)}
  487. (macro->text name arguments)]))
  488. (rum/defc nested-link < rum/reactive
  489. [config html-export? link]
  490. (let [show-brackets? (state/show-brackets?)
  491. {:keys [content children]} link]
  492. [:span.page-reference.nested
  493. (when (and show-brackets?
  494. (not html-export?)
  495. (not (= (:id config) "contents")))
  496. [:span.text-gray-500 "[["])
  497. (let [page-name (subs content 2 (- (count content) 2))]
  498. (page-cp (assoc config
  499. :children children
  500. :nested-link? true) {:page/name page-name}))
  501. (when (and show-brackets?
  502. (not html-export?)
  503. (not (= (:id config) "contents")))
  504. [:span.text-gray-500 "]]"])]))
  505. (declare custom-query)
  506. (defn inline
  507. [{:keys [html-export?] :as config} item]
  508. (match item
  509. ["Plain" s]
  510. s
  511. ["Spaces" s]
  512. s
  513. ["Superscript" l]
  514. (->elem :sup (map-inline config l))
  515. ["Subscript" l]
  516. (->elem :sub (map-inline config l))
  517. ["Tag" s]
  518. (if (and s (util/tag-valid? s))
  519. [:a.tag {:data-ref s
  520. :href (rfe/href :page {:name s})
  521. :on-click (fn [e]
  522. (let [repo (state/get-current-repo)
  523. page (db/pull repo '[*] [:page/name (string/lower-case (util/url-decode s))])]
  524. (when (gobj/get e "shiftKey")
  525. (state/sidebar-add-block!
  526. repo
  527. (:db/id page)
  528. :page
  529. {:page page})
  530. (.preventDefault e))))}
  531. (str "#" s)]
  532. [:span.warning.mr-1 {:title "Invalid tag, tags only accept alphanumeric characters, \"-\", \"_\", \"@\" and \"%\"."}
  533. (str "#" s)])
  534. ["Emphasis" [[kind] data]]
  535. (let [elem (case kind
  536. "Bold" :b
  537. "Italic" :i
  538. "Underline" :ins
  539. "Strike_through" :del
  540. "Highlight" :mark)]
  541. (->elem elem (map-inline config data)))
  542. ["Entity" e]
  543. [:span {:dangerouslySetInnerHTML
  544. {:__html (:html e)}}]
  545. ["Latex_Fragment" ["Displayed" s]]
  546. (if html-export?
  547. (latex/html-export s false true)
  548. (latex/latex (str (dc/squuid)) s false true))
  549. ["Latex_Fragment" ["Inline" s]]
  550. (if html-export?
  551. (latex/html-export s false true)
  552. (latex/latex (str (dc/squuid)) s false false))
  553. ["Target" s]
  554. [:a {:id s} s]
  555. ["Radio_Target" s]
  556. [:a {:id s} s]
  557. ["Email" address]
  558. (let [{:keys [local_part domain]} address
  559. address (str local_part "@" domain)]
  560. [:a {:href (str "mainto:" address)}
  561. address])
  562. ["Block_reference" id]
  563. ;; FIXME: alert when self block reference
  564. (block-reference config id)
  565. ["Nested_link" link]
  566. (nested-link config html-export? link)
  567. ["Link" link]
  568. (let [{:keys [url label title metadata full_text]} link
  569. img-formats (set (map name (config/img-formats)))]
  570. (match url
  571. ["Search" s]
  572. (cond
  573. ;; image
  574. (some (fn [fmt] (re-find (re-pattern (str "(?i)\\." fmt)) s)) img-formats)
  575. (image-link config url s label metadata full_text)
  576. (= \# (first s))
  577. (->elem :a {:on-click #(route-handler/jump-to-anchor! (mldoc/anchorLink (subs s 1)))} (subs s 1))
  578. ;; FIXME: same headline, see more https://orgmode.org/manual/Internal-Links.html
  579. (and (= \* (first s))
  580. (not= \* (last s)))
  581. (->elem :a {:on-click #(route-handler/jump-to-anchor! (mldoc/anchorLink (subs s 1)))} (subs s 1))
  582. (re-find #"(?i)^http[s]?://" s)
  583. (->elem :a {:href s
  584. :data-href s
  585. :target "_blank"}
  586. (map-inline config label))
  587. (and (util/electron?) (config/local-asset? s))
  588. (asset-reference (second (first label)) s)
  589. :else
  590. (page-reference html-export? s config label))
  591. :else
  592. (let [href (string-of-url url)
  593. protocol (or
  594. (and (= "Complex" (first url))
  595. (:protocol (second url)))
  596. (and (= "File" (first url))
  597. "file"))]
  598. (cond
  599. (and (= "Complex" (first url))
  600. (= protocol "id")
  601. (string? (:link (second url)))
  602. (util/uuid-string? (:link (second url)))) ; org mode id
  603. (block-reference config (:link (second url)))
  604. (= protocol "file")
  605. (if (some (fn [fmt] (re-find (re-pattern (str "(?i)\\." fmt)) href)) img-formats)
  606. (image-link config url href label metadata full_text)
  607. (let [label-text (get-label-text label)
  608. page (if (string/blank? label-text)
  609. {:page/name (db/get-file-page (string/replace href "file:" ""))}
  610. (get-page label))]
  611. (if (and page
  612. (when-let [ext (util/get-file-ext href)]
  613. (config/mldoc-support? ext)))
  614. [:span.page-reference
  615. [:span.text-gray-500 "[["]
  616. (page-cp config page)
  617. [:span.text-gray-500 "]]"]]
  618. (->elem
  619. :a
  620. (cond->
  621. {:href href
  622. :data-href href
  623. :target "_blank"}
  624. title
  625. (assoc :title title))
  626. (map-inline config label)))))
  627. ;; image
  628. (some (fn [fmt] (re-find (re-pattern (str "(?i)\\." fmt)) href)) img-formats)
  629. (image-link config url href label metadata full_text)
  630. :else
  631. (->elem
  632. :a.external-link
  633. (cond->
  634. {:href href
  635. :target "_blank"}
  636. title
  637. (assoc :title title))
  638. (map-inline config label))))))
  639. ["Verbatim" s]
  640. [:code s]
  641. ["Code" s]
  642. [:code s]
  643. ["Inline_Source_Block" x]
  644. [:code (:code x)]
  645. ["Export_Snippet" "html" s]
  646. (when (not html-export?)
  647. [:span {:dangerouslySetInnerHTML
  648. {:__html s}}])
  649. ;; String to hiccup
  650. ["Inline_Hiccup" s]
  651. (ui/catch-error
  652. [:div.warning {:title "Invalid hiccup"} s]
  653. (-> (safe-read-string s)
  654. (security/remove-javascript-links-in-href)))
  655. ["Break_Line"]
  656. [:br]
  657. ["Hard_Break_Line"]
  658. [:br]
  659. ["Timestamp" ["Scheduled" t]]
  660. (timestamp t "Scheduled")
  661. ["Timestamp" ["Deadline" t]]
  662. (timestamp t "Deadline")
  663. ["Timestamp" ["Date" t]]
  664. (timestamp t "Date")
  665. ["Timestamp" ["Closed" t]]
  666. (timestamp t "Closed")
  667. ["Timestamp" ["Range" t]]
  668. (range t false)
  669. ["Timestamp" ["Clock" ["Stopped" t]]]
  670. (range t true)
  671. ["Timestamp" ["Clock" ["Started" t]]]
  672. (timestamp t "Started")
  673. ["Cookie" ["Percent" n]]
  674. [:span {:class "cookie-percent"}
  675. (util/format "[d%%]" n)]
  676. ["Cookie" ["Absolute" current total]]
  677. [:span {:class "cookie-absolute"}
  678. (util/format "[%d/%d]" current total)]
  679. ["Footnote_Reference" options]
  680. (let [{:keys [id name]} options
  681. encode-name (util/url-encode name)]
  682. [:sup.fn
  683. [:a {:id (str "fnr." encode-name)
  684. :class "footref"
  685. :on-click #(route-handler/jump-to-anchor! (str "fn." encode-name))}
  686. name]])
  687. ["Macro" options]
  688. (let [{:keys [name arguments]} options
  689. arguments (if (and
  690. (>= (count arguments) 2)
  691. (and (string/starts-with? (first arguments) "[[")
  692. (string/ends-with? (last arguments) "]]"))) ; page reference
  693. (let [title (string/join ", " arguments)]
  694. [title])
  695. arguments)]
  696. (cond
  697. (= name "query")
  698. [:div.dsl-query
  699. (let [query (string/join ", " arguments)]
  700. (custom-query (assoc config :dsl-query? true)
  701. {:title [:code.p-1 (str "Query: " query)]
  702. :query query}))]
  703. (= name "youtube")
  704. (let [url (first arguments)]
  705. (let [YouTube-regex #"^((?:https?:)?//)?((?:www|m).)?((?:youtube.com|youtu.be))(/(?:[\w-]+\?v=|embed/|v/)?)([\w-]+)(\S+)?$"]
  706. (when-let [youtube-id (cond
  707. (== 11 (count url)) url
  708. :else
  709. (nth (re-find YouTube-regex url) 5))]
  710. (when-not (string/blank? youtube-id)
  711. (let [width (min (- (util/get-width) 96)
  712. 560)
  713. height (int (* width (/ 315 560)))]
  714. [:iframe
  715. {:allow-full-screen "allowfullscreen"
  716. :allow
  717. "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
  718. :frame-border "0"
  719. :src (str "https://www.youtube.com/embed/" youtube-id)
  720. :height height
  721. :width width}])))))
  722. (= name "embed")
  723. (let [a (first arguments)]
  724. (cond
  725. (nil? a) ; empty embed
  726. nil
  727. (and (string/starts-with? a "[[")
  728. (string/ends-with? a "]]"))
  729. (let [page-name (-> (string/replace a "[[" "")
  730. (string/replace "]]" "")
  731. string/trim)]
  732. (when-not (string/blank? page-name)
  733. (page-embed config page-name)))
  734. (and (string/starts-with? a "((")
  735. (string/ends-with? a "))"))
  736. (when-let [s (-> (string/replace a "((" "")
  737. (string/replace "))" "")
  738. string/trim)]
  739. (when-let [id (and s
  740. (let [s (string/trim s)]
  741. (and (util/uuid-string? s)
  742. (uuid s))))]
  743. (block-embed config id)))
  744. :else ;TODO: maybe collections?
  745. nil))
  746. :else
  747. (if-let [block-uuid (:block/uuid config)]
  748. (let [format (get-in config [:block :block/format] :markdown)
  749. macro-content (or
  750. (-> (db/entity [:block/uuid block-uuid])
  751. (:block/page)
  752. (:db/id)
  753. (db/entity)
  754. :page/properties
  755. :macros
  756. (get name))
  757. (get (state/get-macros) name)
  758. (get (state/get-macros) (keyword name)))
  759. macro-content (if (and (seq arguments) macro-content)
  760. (block/macro-subs macro-content arguments)
  761. macro-content)
  762. macro-content (when macro-content
  763. (editor-handler/resolve-dynamic-template! macro-content))]
  764. (render-macro config name arguments macro-content format))
  765. (when-let [macro-txt (macro->text name arguments)]
  766. (let [macro-txt (when macro-txt
  767. (editor-handler/resolve-dynamic-template! macro-txt))
  768. format (get-in config [:block :block/format] :markdown)]
  769. (render-macro config name arguments macro-txt format))))))
  770. :else
  771. ""))
  772. (declare blocks-cp)
  773. (rum/defc block-child
  774. [block]
  775. block)
  776. (defonce *control-show? (atom {}))
  777. (rum/defcs block-control < rum/reactive
  778. {:will-mount (fn [state]
  779. (let [block (nth (:rum/args state) 1)
  780. collapsed? (:block/collapsed? block)]
  781. (state/set-collapsed-state! (:block/uuid block)
  782. collapsed?))
  783. state)}
  784. [state config block uuid block-id level start-level body children dummy?]
  785. (let [has-child? (and
  786. (not (:pre-block? block))
  787. (or (seq children)
  788. (seq body)))
  789. collapsed? (state/sub [:ui/collapsed-blocks uuid])
  790. collapsed? (and has-child? collapsed?)
  791. control-show (util/react (rum/cursor *control-show? block-id))
  792. dark? (= "dark" (state/sub :ui/theme))
  793. heading? (= (get (:block/properties block) "heading") "true")]
  794. [:div.mr-2.flex.flex-row.items-center
  795. {:style {:height 24
  796. :margin-top (if (and heading? (<= level 6))
  797. (case level
  798. 1
  799. 32
  800. 2
  801. 22
  802. 18)
  803. 0)
  804. :float "left"}}
  805. [:a.block-control.opacity-50.hover:opacity-100
  806. {:id (str "control-" uuid)
  807. :style {:width 14
  808. :height 16
  809. :margin-right 2}
  810. :on-click (fn [e]
  811. (util/stop e)
  812. (if collapsed?
  813. (expand/expand! block)
  814. (expand/collapse! block))
  815. (state/set-collapsed-state! uuid (not collapsed?)))}
  816. (cond
  817. (and control-show collapsed?)
  818. (svg/caret-right)
  819. (and control-show has-child?)
  820. (svg/caret-down)
  821. :else
  822. [:span ""])]
  823. [:a (if (not dummy?)
  824. {:href (rfe/href :page {:name uuid})
  825. :on-click (fn [e]
  826. (when (gobj/get e "shiftKey")
  827. (state/sidebar-add-block!
  828. (state/get-current-repo)
  829. (:db/id block)
  830. :block
  831. block)
  832. (util/stop e)))})
  833. [:span.bullet-container.cursor
  834. {:id (str "dot-" uuid)
  835. :draggable true
  836. :on-drag-start (fn [event]
  837. (editor-handler/highlight-block! uuid)
  838. (.setData (gobj/get event "dataTransfer")
  839. "block-uuid"
  840. uuid)
  841. (.setData (gobj/get event "dataTransfer")
  842. "block-dom-id"
  843. block-id)
  844. (state/clear-selection!)
  845. (reset! *dragging? true)
  846. (reset! *dragging-block block))
  847. :blockid (str uuid)
  848. :class (str (when collapsed? "bullet-closed")
  849. " "
  850. (when (and (:document/mode? config)
  851. (not collapsed?))
  852. "hide-inner-bullet"))}
  853. [:span.bullet {:blockid (str uuid)
  854. :class (if heading? "bullet-heading" "")}]]]]))
  855. (defn- build-id
  856. [config ref? sidebar? embed?]
  857. (let [k (pr-str config)
  858. n (or
  859. (get @container-ids k)
  860. (let [n' (swap! container-idx inc)]
  861. (swap! container-ids assoc k n')
  862. n'))]
  863. (str n "-")))
  864. (rum/defc dnd-separator
  865. [block margin-left bottom top? nested?]
  866. (let [id (str (:block/uuid block)
  867. (cond nested?
  868. "-nested"
  869. top?
  870. "-top"
  871. :else
  872. nil))]
  873. [:div.dnd-separator
  874. {:id id
  875. :style (merge
  876. {:position "absolute"
  877. :left margin-left
  878. :width "100%"
  879. :z-index 3}
  880. (if top?
  881. {:top 0}
  882. {:bottom 0}))}]))
  883. (declare block-container)
  884. (defn block-checkbox
  885. [block class]
  886. (let [marker (:block/marker block)
  887. [class checked?] (cond
  888. (nil? marker)
  889. nil
  890. (contains? #{"NOW" "LATER" "DOING" "IN-PROGRESS" "TODO" "WAIT" "WAITING"} marker)
  891. [class false]
  892. (= "DONE" marker)
  893. [(str class " checked") true])]
  894. (when class
  895. (ui/checkbox {:class class
  896. :style {:margin-top -2
  897. :margin-right 5}
  898. :checked checked?
  899. :on-change (fn [_e]
  900. ;; FIXME: Log timestamp
  901. (if checked?
  902. (editor-handler/uncheck block)
  903. (editor-handler/check block)))}))))
  904. (defn list-checkbox
  905. [checked?]
  906. (ui/checkbox {:style {:margin-right 6}
  907. :checked checked?}))
  908. (defn marker-switch
  909. [{:block/keys [pre-block? marker] :as block}]
  910. (when (contains? #{"NOW" "LATER" "TODO" "DOING"} marker)
  911. (let [set-marker-fn (fn [marker]
  912. (fn [e]
  913. (util/stop e)
  914. (editor-handler/set-marker block marker)))]
  915. (case marker
  916. "NOW"
  917. [:a.marker-switch
  918. {:title "Change from NOW to LATER"
  919. :on-click (set-marker-fn "LATER")}
  920. [:span "N"]]
  921. "LATER"
  922. [:a.marker-switch
  923. {:title "Change from LATER to NOW"
  924. :on-click (set-marker-fn "NOW")}
  925. "L"]
  926. "TODO"
  927. [:a.marker-switch
  928. {:title "Change from TODO to DOING"
  929. :on-click (set-marker-fn "DOING")}
  930. "T"]
  931. "DOING"
  932. [:a.marker-switch
  933. {:title "Change from DOING to TODO"
  934. :on-click (set-marker-fn "TODO")}
  935. "D"]
  936. nil))))
  937. (defn marker-cp
  938. [{:block/keys [pre-block? marker] :as block}]
  939. (when-not pre-block?
  940. (if (contains? #{"IN-PROGRESS" "WAIT" "WAITING"} marker)
  941. [:span {:class (str "task-status " (string/lower-case marker))
  942. :style {:margin-right 3.5}}
  943. (string/upper-case marker)])))
  944. (defn priority-cp
  945. [{:block/keys [pre-block? priority] :as block}]
  946. (when (and (not pre-block?) priority)
  947. (ui/tooltip
  948. [:ul
  949. (for [p (remove #(= priority %) ["A" "B" "C"])]
  950. [:a.mr-2.text-base.tooltip-priority {:priority p
  951. :on-click (fn [] (editor-handler/set-priority block p))}])]
  952. [:a.opacity-50.hover:opacity-100
  953. {:class "priority"
  954. :href (rfe/href :page {:name priority})
  955. :style {:margin-right 3.5}}
  956. (util/format "[#%s]" (str priority))])))
  957. (defn block-tags-cp
  958. [{:block/keys [pre-block? tags] :as block}]
  959. (when (and (not pre-block?)
  960. (seq tags))
  961. (->elem
  962. :span
  963. {:class "block-tags"}
  964. (mapv (fn [tag]
  965. (when-let [page (db/entity (:db/id tag))]
  966. (let [tag (:page/name page)]
  967. [:a.tag.mx-1 {:data-ref tag
  968. :key (str "tag-" (:db/id tag))
  969. :href (rfe/href :page {:name tag})}
  970. (str "#" tag)])))
  971. tags))))
  972. (defn build-block-part
  973. [{:keys [slide?] :as config} {:block/keys [uuid title tags marker level priority anchor meta format content pre-block? dummy? block-refs-count page properties]
  974. :as t}]
  975. (let [config (assoc config :block t)
  976. slide? (boolean (:slide? config))
  977. html-export? (:html-export? config)
  978. checkbox (when (and (not pre-block?)
  979. (not html-export?))
  980. (block-checkbox t (str "mr-1 cursor")))
  981. marker-switch (when (and (not pre-block?)
  982. (not html-export?))
  983. (marker-switch t))
  984. marker-cp (marker-cp t)
  985. priority (priority-cp t)
  986. tags (block-tags-cp t)
  987. contents? (= (:id config) "contents")
  988. heading? (= (get properties "heading") "true")
  989. bg-color (get properties "background_color")]
  990. (when level
  991. (let [element (if (and (<= level 6) heading?)
  992. (keyword (str "h" level))
  993. :div)]
  994. (->elem
  995. element
  996. (merge
  997. {:id anchor}
  998. (when (and marker
  999. (not (string/blank? marker))
  1000. (not= "nil" marker))
  1001. {:class (str (string/lower-case marker))})
  1002. (when bg-color
  1003. {:style {:background-color bg-color
  1004. :padding-left 6
  1005. :padding-right 6
  1006. :color "#FFFFFF"}
  1007. :class "with-bg-color"}))
  1008. (remove-nils
  1009. (concat
  1010. [(when-not slide? checkbox)
  1011. (when-not slide? marker-switch)
  1012. marker-cp
  1013. priority]
  1014. (cond
  1015. dummy?
  1016. [[:span.opacity-50 "Click here to start writing"]]
  1017. ;; empty item
  1018. (and contents? (or
  1019. (empty? title)
  1020. (= title [["Plain" "[[]]"]])))
  1021. [[:span.opacity-50 "Click here to add a page, e.g. [[favorite-page]]"]]
  1022. :else
  1023. (map-inline config title))
  1024. [tags])))))))
  1025. (defn dnd-same-block?
  1026. [uuid]
  1027. (= (:block/uuid @*dragging-block) uuid))
  1028. (defn show-dnd-separator
  1029. [element-id]
  1030. (when-let [element (gdom/getElement element-id)]
  1031. (when (d/has-class? element "dnd-separator")
  1032. (d/remove-class! element "dnd-separator")
  1033. (d/add-class! element "dnd-separator-cur"))))
  1034. (defn hide-dnd-separator
  1035. [element-id]
  1036. (when-let [element (gdom/getElement element-id)]
  1037. (when (d/has-class? element "dnd-separator-cur")
  1038. (d/remove-class! element "dnd-separator-cur")
  1039. (d/add-class! element "dnd-separator"))))
  1040. (defn- get-data-transfer-attr
  1041. [event attr]
  1042. (.getData (gobj/get event "dataTransfer") attr))
  1043. (defn- pre-block-cp
  1044. [config content format]
  1045. (rum/with-context [[t] i18n/*tongue-context*]
  1046. (let [ast (mldoc/->edn content (mldoc/default-config format))
  1047. ast (map first ast)
  1048. slide? (:slide? config)
  1049. only-title? (and (= 1 (count ast))
  1050. (= "Properties" (ffirst ast))
  1051. (let [m (second (first ast))]
  1052. (= (keys m) [:title])))
  1053. block-cp [:div {:class (if only-title?
  1054. (util/hiccup->class "pre-block.opacity-50")
  1055. (util/hiccup->class "pre-block.bg-base-2.p-2.rounded"))}
  1056. (if only-title?
  1057. [:span (t :page/edit-properties-placeholder)]
  1058. (markup-elements-cp (assoc config :block/format format) ast))]]
  1059. (if slide?
  1060. [:div [:h1 (:page-name config)]]
  1061. block-cp))))
  1062. (rum/defc properties-cp
  1063. [config block]
  1064. (let [properties (apply dissoc (:block/properties block) text/hidden-properties)]
  1065. (when (seq properties)
  1066. [:div.blocks-properties.text-sm.opacity-80.my-1.p-2
  1067. (for [[k v] properties]
  1068. ^{:key (str (:block/uuid block) "-" k)}
  1069. [:div.my-1
  1070. [:b k]
  1071. [:span.mr-1 ":"]
  1072. (if (coll? v)
  1073. (let [v (->> (remove string/blank? v)
  1074. (filter string?))
  1075. vals (for [v-item v]
  1076. (page-cp config {:page/name v-item}))]
  1077. (interpose [:span ", "] vals))
  1078. (let [page-name (string/lower-case (str v))]
  1079. (if (db/entity [:page/name page-name])
  1080. (page-cp config {:page/name page-name})
  1081. (inline-text (:block/format block) (str v)))))])])))
  1082. (rum/defcs timestamp-cp < rum/reactive
  1083. (rum/local false ::show?)
  1084. (rum/local {} ::pos)
  1085. {:will-unmount (fn [state]
  1086. (when-let [show? (::show? state)]
  1087. (reset! show? false))
  1088. state)}
  1089. [state block typ ast]
  1090. (let [show? (get state ::show?)]
  1091. [:div.flex.flex-col
  1092. [:div.text-sm.mt-1.flex.flex-row
  1093. [:div.opacity-50.font-medium {:style {:width 95}}
  1094. (str typ ": ")]
  1095. [:a.opacity-80.hover:opacity-100
  1096. {:on-click (fn []
  1097. (if @show?
  1098. (do
  1099. (reset! show? false)
  1100. (reset! commands/*current-command nil)
  1101. (state/set-editor-show-date-picker! false)
  1102. (state/set-timestamp-block! nil))
  1103. (do
  1104. (reset! show? true)
  1105. (reset! commands/*current-command typ)
  1106. (state/set-editor-show-date-picker! true)
  1107. (state/set-timestamp-block! {:block block
  1108. :typ typ
  1109. :show? show?}))))}
  1110. (repeated/timestamp->text ast)]]
  1111. (when (true? @show?)
  1112. (let [ts (repeated/timestamp->map ast)]
  1113. [:div.my-4
  1114. (datetime-comp/date-picker nil nil ts)]))]))
  1115. (rum/defc block-content < rum/reactive
  1116. [config {:block/keys [uuid title level body meta content marker dummy? page format repo children pre-block? properties collapsed? idx container block-refs-count scheduled scheduled-ast deadline deadline-ast repeated?] :as block} edit-input-id block-id slide?]
  1117. (let [dragging? (rum/react *dragging?)
  1118. attrs {:blockid (str uuid)
  1119. ;; FIXME: Click to copy a selection instead of click first and then copy
  1120. ;; It seems that `util/caret-range` can't get the correct range
  1121. :on-click (fn [e]
  1122. (when-not (selection-range-in-block?)
  1123. (let [target (gobj/get e "target")]
  1124. (when-not (or (util/link? target)
  1125. (util/input? target)
  1126. (util/details-or-summary? target)
  1127. (and (util/sup? target)
  1128. (d/has-class? target "fn")))
  1129. (editor-handler/clear-selection! nil)
  1130. (editor-handler/unhighlight-block!)
  1131. (let [cursor-range (util/caret-range (gdom/getElement block-id))
  1132. properties-hidden? (text/properties-hidden? properties)
  1133. content (text/remove-level-spaces content format)
  1134. content (if properties-hidden? (text/remove-properties! content) content)
  1135. block (db/pull [:block/uuid (:block/uuid block)])]
  1136. (state/set-editing!
  1137. edit-input-id
  1138. content
  1139. block
  1140. cursor-range))
  1141. (util/stop e)))))
  1142. :on-drag-over (fn [event]
  1143. (util/stop event)
  1144. (when-not (dnd-same-block? uuid)
  1145. (show-dnd-separator (str uuid "-nested"))))
  1146. :on-drag-leave (fn [event]
  1147. (hide-dnd-separator (str uuid))
  1148. (hide-dnd-separator (str uuid "-nested"))
  1149. (hide-dnd-separator (str uuid "-top")))
  1150. :on-drop (fn [event]
  1151. (util/stop event)
  1152. (when-not (dnd-same-block? uuid)
  1153. (let [from-dom-id (get-data-transfer-attr event "block-dom-id")]
  1154. (dnd/move-block @*dragging-block
  1155. block
  1156. from-dom-id
  1157. false
  1158. true)))
  1159. (reset! *dragging? false)
  1160. (reset! *dragging-block nil)
  1161. (editor-handler/unhighlight-block!))}]
  1162. [:div.flex.relative
  1163. [:div.flex-1.flex-col.relative.block-content
  1164. (cond-> {:id (str "block-content-" uuid)}
  1165. (not slide?)
  1166. (merge attrs))
  1167. (if pre-block?
  1168. (pre-block-cp config (string/trim content) format)
  1169. (build-block-part config block))
  1170. (when (and dragging? (not slide?))
  1171. (dnd-separator block 0 -4 false true))
  1172. (when (and deadline deadline-ast)
  1173. (timestamp-cp block "DEADLINE" deadline-ast))
  1174. (when (and scheduled scheduled-ast)
  1175. (timestamp-cp block "SCHEDULED" scheduled-ast))
  1176. (when (and (seq properties)
  1177. (let [hidden? (text/properties-hidden? properties)]
  1178. (not hidden?))
  1179. (not (:slide? config)))
  1180. (properties-cp config block))
  1181. (when (and (not pre-block?) (seq body))
  1182. [:div.block-body {:style {:display (if collapsed? "none" "")}}
  1183. ;; TODO: consistent id instead of the idx (since it could be changed later)
  1184. (let [body (block/trim-break-lines! (:block/body block))]
  1185. (for [[idx child] (medley/indexed body)]
  1186. (when-let [block (markup-element-cp config child)]
  1187. (rum/with-key (block-child block)
  1188. (str uuid "-" idx)))))])]
  1189. (when (and block-refs-count (> block-refs-count 0))
  1190. [:div
  1191. [:a.open-block-ref-link.bg-base-2
  1192. {:title "Open block references"
  1193. :style {:margin-top -1}
  1194. :on-click (fn []
  1195. (state/sidebar-add-block!
  1196. (state/get-current-repo)
  1197. (:db/id block)
  1198. :block-ref
  1199. {:block block}))}
  1200. block-refs-count]])
  1201. (when (and (= marker "DONE")
  1202. (state/enable-timetracking?))
  1203. (let [start-time (or
  1204. (get properties "now")
  1205. (get properties "doing")
  1206. (get properties "in-progress")
  1207. (get properties "later")
  1208. (get properties "todo"))
  1209. finish-time (get properties "done")]
  1210. (when (and start-time finish-time (> finish-time start-time))
  1211. [:div.text-sm.absolute.time-spent {:style {:top 0
  1212. :right 0
  1213. :padding-left 2}
  1214. :title (str (date/int->local-time start-time) " ~ " (date/int->local-time finish-time))}
  1215. [:span.opacity-70
  1216. (utils/timeConversion (- finish-time start-time))]])))]))
  1217. (rum/defc block-content-or-editor < rum/reactive
  1218. [config {:block/keys [uuid title level body meta content dummy? page format repo children pre-block? collapsed? idx] :as block} edit-input-id block-id slide?]
  1219. (let [edit? (state/sub [:editor/editing? edit-input-id])
  1220. editor-box (get config :editor-box)]
  1221. (if (and edit? editor-box)
  1222. [:div.editor-wrapper {:id (str "editor-" edit-input-id)}
  1223. (editor-box {:block block
  1224. :block-id uuid
  1225. :block-parent-id block-id
  1226. :format format
  1227. :dummy? dummy?
  1228. :on-hide (fn [value event]
  1229. (when (= event :esc)
  1230. (editor-handler/highlight-block! uuid)))}
  1231. edit-input-id
  1232. config)]
  1233. (block-content config block edit-input-id block-id slide?))))
  1234. (rum/defc dnd-separator-wrapper < rum/reactive
  1235. [block slide? top?]
  1236. (let [dragging? (rum/react *dragging?)]
  1237. (cond
  1238. (and dragging? (not slide?))
  1239. (dnd-separator block 20 0 top? false)
  1240. :else
  1241. nil)))
  1242. (defn non-dragging?
  1243. [e]
  1244. (and (= (gobj/get e "buttons") 1)
  1245. (not (d/has-class? (gobj/get e "target") "bullet-container"))
  1246. (not (d/has-class? (gobj/get e "target") "bullet"))
  1247. (not @*dragging?)))
  1248. (defn block-parents
  1249. ([config repo block-id format]
  1250. (block-parents config repo block-id format true))
  1251. ([config repo block-id format show-page?]
  1252. (let [parents (db/get-block-parents repo block-id 3)
  1253. page (db/get-block-page repo block-id)
  1254. page-name (:page/name page)]
  1255. (when (or (seq parents)
  1256. show-page?
  1257. page-name)
  1258. (let [parents-atom (atom parents)
  1259. component [:div.block-parents.flex-row.flex-1
  1260. (when show-page?
  1261. [:a {:href (rfe/href :page {:name page-name})}
  1262. (or (:page/original-name page)
  1263. (:page/name page))])
  1264. (when (and show-page? (seq parents))
  1265. [:span.mx-2.opacity-50 "➤"])
  1266. (when (seq parents)
  1267. (let [parents (doall
  1268. (for [{:block/keys [uuid title]} parents]
  1269. [:a {:href (rfe/href :page {:name uuid})}
  1270. (map-inline config title)]))
  1271. parents (remove nil? parents)]
  1272. (reset! parents-atom parents)
  1273. (when (seq parents)
  1274. (interpose [:span.mx-2.opacity-50 "➤"]
  1275. parents))))]
  1276. component (filterv identity component)]
  1277. (when (or (seq @parents-atom) show-page?)
  1278. component))))))
  1279. (rum/defc block-container < rum/static
  1280. {:did-mount (fn [state]
  1281. (let [block (nth (:rum/args state) 1)
  1282. collapsed? (:block/collapsed? block)]
  1283. (when collapsed?
  1284. (expand/collapse! block))
  1285. state))
  1286. :should-update (fn [old-state new-state]
  1287. (not= (:block/content (second (:rum/args old-state)))
  1288. (:block/content (second (:rum/args new-state)))))}
  1289. [config {:block/keys [uuid title level body meta content dummy? page format repo children collapsed? pre-block? idx properties refs-with-children] :as block}]
  1290. (let [ref? (boolean (:ref? config))
  1291. breadcrumb-show? (:breadcrumb-show? config)
  1292. sidebar? (boolean (:sidebar? config))
  1293. slide? (boolean (:slide? config))
  1294. doc-mode? (:document/mode? config)
  1295. embed? (:embed? config)
  1296. unique-dom-id (build-id (dissoc config :block/uuid) ref? sidebar? embed?)
  1297. edit-input-id (str "edit-block-" unique-dom-id uuid)
  1298. block-id (str "ls-block-" unique-dom-id uuid)
  1299. has-child? (boolean
  1300. (and
  1301. (not pre-block?)
  1302. (or (seq children)
  1303. (seq body))))
  1304. start-level (or (:start-level config) 1)
  1305. attrs {:on-drag-over (fn [event]
  1306. (util/stop event)
  1307. (when-not (dnd-same-block? uuid)
  1308. (if (zero? idx)
  1309. (let [element-top (gobj/get (utils/getOffsetRect (gdom/getElement block-id)) "top")
  1310. cursor-top (gobj/get event "clientY")]
  1311. (if (<= (js/Math.abs (- cursor-top element-top)) 16)
  1312. ;; top
  1313. (do
  1314. (hide-dnd-separator (str uuid))
  1315. (show-dnd-separator (str uuid "-top"))
  1316. (reset! *move-to-top? true))
  1317. (do
  1318. (hide-dnd-separator (str uuid "-top"))
  1319. (show-dnd-separator (str uuid)))))
  1320. (show-dnd-separator (str uuid)))))
  1321. :on-drag-leave (fn [event]
  1322. (hide-dnd-separator (str uuid))
  1323. (hide-dnd-separator (str uuid "-nested"))
  1324. (hide-dnd-separator (str uuid "-top"))
  1325. (reset! *move-to-top? false))
  1326. :on-drop (fn [event]
  1327. (when-not (dnd-same-block? uuid)
  1328. (let [from-dom-id (get-data-transfer-attr event "block-dom-id")]
  1329. (dnd/move-block @*dragging-block
  1330. block
  1331. from-dom-id
  1332. @*move-to-top?
  1333. false)))
  1334. (reset! *dragging? false)
  1335. (reset! *dragging-block nil)
  1336. (editor-handler/unhighlight-block!))
  1337. :on-mouse-move (fn [e]
  1338. (when (and (non-dragging? e)
  1339. (not @*resizing-image?))
  1340. (state/into-selection-mode!)))
  1341. :on-mouse-down (fn [e]
  1342. (when (and
  1343. (not (state/get-selection-start-block))
  1344. (= (gobj/get e "buttons") 1))
  1345. (when block-id (state/set-selection-start-block! block-id))))
  1346. :on-mouse-over (fn [e]
  1347. (util/stop e)
  1348. (when has-child?
  1349. (swap! *control-show? assoc block-id true))
  1350. (when-let [parent (gdom/getElement block-id)]
  1351. (let [node (.querySelector parent ".bullet-container")
  1352. closed? (d/has-class? node "bullet-closed")]
  1353. (if closed?
  1354. (state/collapse-block! uuid)
  1355. (state/expand-block! uuid))
  1356. (when doc-mode?
  1357. (d/remove-class! node "hide-inner-bullet"))))
  1358. (when (and
  1359. (state/in-selection-mode?)
  1360. (non-dragging? e))
  1361. (util/stop e)
  1362. (editor-handler/highlight-selection-area! block-id)))
  1363. :on-mouse-out (fn [e]
  1364. (util/stop e)
  1365. (when has-child?
  1366. (swap! *control-show?
  1367. assoc block-id false))
  1368. (when doc-mode?
  1369. (when-let [parent (gdom/getElement block-id)]
  1370. (when-let [node (.querySelector parent ".bullet-container")]
  1371. (d/add-class! node "hide-inner-bullet")))))}
  1372. data-refs (let [refs (model/get-page-names-by-ids
  1373. (->> (map :db/id refs-with-children)
  1374. (remove nil?)))]
  1375. (text/build-data-value refs))
  1376. data-refs-self (let [refs (model/get-page-names-by-ids
  1377. (->> (map :db/id (:block/ref-pages block))
  1378. (remove nil?)))]
  1379. (text/build-data-value refs))]
  1380. [:div.ls-block.flex.flex-col.rounded-sm
  1381. (cond->
  1382. {:id block-id
  1383. :data-refs data-refs
  1384. :data-refs-self data-refs-self
  1385. :style {:position "relative"}
  1386. :class (str uuid
  1387. (when dummy? " dummy")
  1388. (when (and collapsed? has-child?) " collapsed")
  1389. (when pre-block? " pre-block"))
  1390. :blockid (str uuid)
  1391. :repo repo
  1392. :level level
  1393. :haschild (str has-child?)}
  1394. (not slide?)
  1395. (merge attrs))
  1396. (when (and ref? breadcrumb-show?)
  1397. (when-let [comp (block-parents config repo uuid format false)]
  1398. [:div.my-2.opacity-50.ml-4 comp]))
  1399. (dnd-separator-wrapper block slide? (zero? idx))
  1400. [:div.flex-1.flex-row
  1401. (when (not slide?)
  1402. (block-control config block uuid block-id level start-level body children dummy?))
  1403. (block-content-or-editor config block edit-input-id block-id slide?)]
  1404. (when (seq children)
  1405. [:div.block-children {:style {:margin-left (if doc-mode? 12 22)
  1406. :display (if collapsed? "none" "")}}
  1407. (for [child children]
  1408. (when (map? child)
  1409. (let [child (dissoc child :block/meta)]
  1410. (rum/with-key (block-container (assoc config :block/uuid (:block/uuid child)) child)
  1411. (:block/uuid child)))))])
  1412. (when ref?
  1413. (let [children (-> (db/get-block-immediate-children repo uuid)
  1414. db/sort-by-pos)]
  1415. (when (seq children)
  1416. [:div.ref-children.ml-12
  1417. (blocks-container children (assoc config
  1418. :breadcrumb-show? false
  1419. :ref? true))])))
  1420. (dnd-separator-wrapper block slide? false)]))
  1421. (defn divide-lists
  1422. [[f & l]]
  1423. (loop [l l
  1424. ordered? (:ordered f)
  1425. result [[f]]]
  1426. (if (seq l)
  1427. (let [cur (first l)
  1428. cur-ordered? (:ordered cur)]
  1429. (if (= ordered? cur-ordered?)
  1430. (recur
  1431. (rest l)
  1432. cur-ordered?
  1433. (update result (dec (count result)) conj cur))
  1434. (recur
  1435. (rest l)
  1436. cur-ordered?
  1437. (conj result [cur]))))
  1438. result)))
  1439. (defn list-element
  1440. [l]
  1441. (match l
  1442. [l1 & tl]
  1443. (let [{:keys [ordered name]} l1]
  1444. (cond
  1445. (seq name)
  1446. :dl
  1447. ordered
  1448. :ol
  1449. :else
  1450. :ul))
  1451. :else
  1452. :ul))
  1453. (defn list-item
  1454. [config {:keys [name content checkbox items number] :as l}]
  1455. (let [content (when-not (empty? content)
  1456. (match content
  1457. [["Paragraph" i] & rest]
  1458. (vec-cat
  1459. (map-inline config i)
  1460. (markup-elements-cp config rest))
  1461. :else
  1462. (markup-elements-cp config content)))
  1463. checked? (some? checkbox)
  1464. items (if (seq items)
  1465. (->elem
  1466. (list-element items)
  1467. (for [item items]
  1468. (list-item config item))))]
  1469. (cond
  1470. (seq name)
  1471. [:dl {:checked checked?}
  1472. [:dt (map-inline config name)]
  1473. (->elem :dd
  1474. (vec-cat content [items]))]
  1475. :else
  1476. (if (nil? checkbox)
  1477. (->elem
  1478. :li
  1479. {:checked checked?}
  1480. (vec-cat
  1481. [(->elem
  1482. :p
  1483. content)]
  1484. [items]))
  1485. (->elem
  1486. :li
  1487. {:checked checked?}
  1488. (vec-cat
  1489. [(->elem
  1490. :p
  1491. (list-checkbox checkbox)
  1492. content)]
  1493. [items]))))))
  1494. (defn table
  1495. [config {:keys [header groups col_groups]}]
  1496. (let [tr (fn [elm cols]
  1497. (->elem
  1498. :tr
  1499. (mapv (fn [col]
  1500. (->elem
  1501. elm
  1502. {:scope "col"
  1503. :class "org-left"}
  1504. (map-inline config col)))
  1505. cols)))
  1506. tb-col-groups (try
  1507. (mapv (fn [number]
  1508. (let [col-elem [:col {:class "org-left"}]]
  1509. (->elem
  1510. :colgroup
  1511. (repeat number col-elem))))
  1512. col_groups)
  1513. (catch js/Error e
  1514. []))
  1515. head (if header
  1516. [:thead (tr :th header)])
  1517. groups (mapv (fn [group]
  1518. (->elem
  1519. :tbody
  1520. (mapv #(tr :td %) group)))
  1521. groups)]
  1522. [:div.table-wrapper {:style {:max-width (min 700
  1523. (gobj/get js/window "innerWidth"))}}
  1524. (->elem
  1525. :table
  1526. {:class "table-auto"
  1527. :border 2
  1528. :cell-spacing 0
  1529. :cell-padding 6
  1530. :rules "groups"
  1531. :frame "hsides"}
  1532. (vec-cat
  1533. tb-col-groups
  1534. (cons head groups)))]))
  1535. (defn map-inline
  1536. [config col]
  1537. (map #(inline config %) col))
  1538. (declare ->hiccup)
  1539. (defn built-in-custom-query?
  1540. [title]
  1541. (let [repo (state/get-current-repo)]
  1542. (let [queries (state/sub [:config repo :default-queries :journals])]
  1543. (when (seq queries)
  1544. (boolean (some #(= % title) (map :title queries)))))))
  1545. (defn- trigger-custom-query!
  1546. [state]
  1547. (let [[config query] (:rum/args state)
  1548. query-atom (if (:dsl-query? config)
  1549. (query-dsl/query (state/get-current-repo) (:query query))
  1550. (db/custom-query query))]
  1551. (assoc state :query-atom query-atom)))
  1552. (rum/defcs custom-query < rum/reactive
  1553. {:will-mount trigger-custom-query!
  1554. :did-mount (fn [state]
  1555. (when-let [query (last (:rum/args state))]
  1556. (state/add-custom-query-component! query (:rum/react-component state)))
  1557. state)
  1558. :did-remount (fn [_old_state state]
  1559. (trigger-custom-query! state))
  1560. :will-unmount (fn [state]
  1561. (when-let [query (last (:rum/args state))]
  1562. (state/remove-custom-query-component! query)
  1563. (db/remove-custom-query! (state/get-current-repo) query))
  1564. state)}
  1565. [state config {:keys [title query inputs view collapsed? children?] :as q}]
  1566. (let [dsl-query? (:dsl-query? config)
  1567. query-atom (:query-atom state)]
  1568. (let [current-block-uuid (or (:block/uuid (:block config))
  1569. (:block/uuid config))
  1570. ;; exclude the current one, otherwise it'll loop forever
  1571. remove-blocks (if current-block-uuid [current-block-uuid] nil)
  1572. query-result (and query-atom (rum/react query-atom))
  1573. result (if (and query-result dsl-query?)
  1574. query-result
  1575. (db/custom-query-result-transform query-result remove-blocks q))
  1576. result (if query-result
  1577. (db/custom-query-result-transform query-result remove-blocks q))
  1578. view-f (and view (sci/eval-string (pr-str view)))
  1579. only-blocks? (:block/uuid (first result))
  1580. blocks-grouped-by-page? (and (seq result)
  1581. (coll? (first result))
  1582. (:page/name (ffirst result))
  1583. (:block/uuid (first (second (first result))))
  1584. true)
  1585. built-in? (built-in-custom-query? title)]
  1586. [:div.custom-query.mt-2 (get config :attr {})
  1587. (when-not (and built-in? (empty? result))
  1588. (ui/foldable
  1589. [:div.opacity-70
  1590. title]
  1591. (cond
  1592. (and (seq result) view-f)
  1593. (let [result (try
  1594. (sci/call-fn view-f result)
  1595. (catch js/Error error
  1596. (log/error :custom-view-failed {:error error
  1597. :result result})
  1598. [:div "Custom view failed: "
  1599. (str error)]))]
  1600. (util/hiccup-keywordize result))
  1601. (and (seq result)
  1602. (or only-blocks? blocks-grouped-by-page?))
  1603. (->hiccup result (cond-> (assoc config
  1604. ;; :editor-box editor/box
  1605. :custom-query? true
  1606. :group-by-page? blocks-grouped-by-page?)
  1607. children?
  1608. (assoc :ref? true))
  1609. {:style {:margin-top "0.25rem"
  1610. :margin-left "0.25rem"}})
  1611. ;; page list
  1612. (and (seq result)
  1613. (:page/name (first result)))
  1614. [:ul#query-pages.mt-1
  1615. (for [{:page/keys [name original-name] :as page-entity} result]
  1616. [:li.mt-1
  1617. [:a {:href (rfe/href :page {:name name})
  1618. :on-click (fn [e]
  1619. (util/stop e)
  1620. (if (gobj/get e "shiftKey")
  1621. (state/sidebar-add-block!
  1622. (state/get-current-repo)
  1623. (:db/id page-entity)
  1624. :page
  1625. {:page page-entity})
  1626. (route-handler/redirect! {:to :page
  1627. :path-params {:name name}})))}
  1628. (or original-name name)]])]
  1629. (seq result) ;TODO: table
  1630. (let [result (->>
  1631. (for [record result]
  1632. (if (map? record)
  1633. (str (util/pp-str record) "\n")
  1634. record))
  1635. (remove nil?))]
  1636. [:pre result])
  1637. :else
  1638. [:div.text-sm.mt-2.ml-2.font-medium.opacity-50 "Empty"])
  1639. collapsed?))])))
  1640. (defn admonition
  1641. [config type options result]
  1642. (when-let [icon (case (string/lower-case (name type))
  1643. "note" svg/note
  1644. "tip" svg/tip
  1645. "important" svg/important
  1646. "caution" svg/caution
  1647. "warning" svg/warning
  1648. nil)]
  1649. [:div.flex.flex-row.admonitionblock.align-items {:class type}
  1650. [:div.pr-4.admonition-icon.flex.flex-col.justify-center
  1651. {:title (string/upper-case type)} (icon)]
  1652. [:div.ml-4.text-lg
  1653. (markup-elements-cp config result)]]))
  1654. (defn markup-element-cp
  1655. [{:keys [html-export?] :as config} item]
  1656. (try
  1657. (match item
  1658. ["Properties" m]
  1659. [:div.properties
  1660. (let [format (:block/format config)]
  1661. (for [[k v] (dissoc m :roam_alias :roam_tags)]
  1662. (when (and (not (and (= k :macros) (empty? v))) ; empty macros
  1663. )
  1664. [:div.property
  1665. [:span.font-medium.mr-1 (str (name k) ": ")]
  1666. (if (coll? v)
  1667. (let [vals (for [item v]
  1668. (if (coll? v)
  1669. (let [config (if (= k :alias)
  1670. (assoc config :page/alias? true))]
  1671. (page-cp config {:page/name item}))
  1672. (inline-text format item)))]
  1673. (interpose [:span ", "] vals))
  1674. (inline-text format v))])))]
  1675. ["Paragraph" l]
  1676. ;; TODO: speedup
  1677. (if (re-find #"\"Export_Snippet\" \"embed\"" (str l))
  1678. (->elem :div (map-inline config l))
  1679. (->elem :div.is-paragraph (map-inline config l)))
  1680. ["Horizontal_Rule"]
  1681. (when-not (:slide? config)
  1682. [:hr])
  1683. ["Heading" h]
  1684. (block-container config h)
  1685. ["List" l]
  1686. (let [lists (divide-lists l)]
  1687. (if (= 1 (count lists))
  1688. (let [l (first lists)]
  1689. (->elem
  1690. (list-element l)
  1691. (map #(list-item config %) l)))
  1692. [:div.list-group
  1693. (for [l lists]
  1694. (->elem
  1695. (list-element l)
  1696. (map #(list-item config %) l)))]))
  1697. ["Table" t]
  1698. (table config t)
  1699. ["Math" s]
  1700. (if html-export?
  1701. (latex/html-export s true true)
  1702. (latex/latex (str (dc/squuid)) s true true))
  1703. ["Example" l]
  1704. [:pre.pre-wrap-white-space
  1705. (join-lines l)]
  1706. ["Src" options]
  1707. (let [{:keys [language options lines pos_meta]} options
  1708. attr (if language
  1709. {:data-lang language})
  1710. code (join-lines lines)]
  1711. (cond
  1712. html-export?
  1713. (highlight/html-export attr code)
  1714. :else
  1715. (let [language (if (contains? #{"edn" "clj" "cljc" "cljs" "clojure"} language) "text/x-clojure" language)]
  1716. (if (:slide? config)
  1717. (highlight/highlight (str (medley/random-uuid)) {:data-lang language} code)
  1718. [:div
  1719. (lazy-editor/editor config (str (dc/squuid)) attr code pos_meta)
  1720. (when (and (= language "text/x-clojure") (contains? (set options) ":results"))
  1721. (sci/eval-result code))]))))
  1722. ["Quote" l]
  1723. (->elem
  1724. :blockquote
  1725. (markup-elements-cp config l))
  1726. ["Raw_Html" content]
  1727. (when (not html-export?)
  1728. [:div.raw_html {:dangerouslySetInnerHTML
  1729. {:__html content}}])
  1730. ["Export" "html" options content]
  1731. (when (not html-export?)
  1732. [:div.export_html {:dangerouslySetInnerHTML
  1733. {:__html content}}])
  1734. ["Hiccup" content]
  1735. (ui/catch-error
  1736. [:div.warning {:title "Invalid hiccup"}
  1737. content]
  1738. (-> (safe-read-string content)
  1739. (security/remove-javascript-links-in-href)))
  1740. ["Export" "latex" options content]
  1741. (if html-export?
  1742. (latex/html-export content true false)
  1743. (latex/latex (str (dc/squuid)) content true false))
  1744. ["Custom" "query" _options result content]
  1745. (try
  1746. (let [query (reader/read-string content)]
  1747. (custom-query config query))
  1748. (catch js/Error e
  1749. (println "read-string error:")
  1750. (js/console.error e)
  1751. [:div.warning {:title "Invalid query"}
  1752. content]))
  1753. ["Custom" "note" options result content]
  1754. (admonition config "note" options result)
  1755. ["Custom" "tip" options result content]
  1756. (admonition config "tip" options result)
  1757. ["Custom" "important" options result content]
  1758. (admonition config "important" options result)
  1759. ["Custom" "caution" options result content]
  1760. (admonition config "caution" options result)
  1761. ["Custom" "warning" options result content]
  1762. (admonition config "warning" options result)
  1763. ["Custom" name options l content]
  1764. (->elem
  1765. :div
  1766. {:class name}
  1767. (markup-elements-cp config l))
  1768. ["Latex_Fragment" l]
  1769. [:p.latex-fragment
  1770. (inline config ["Latex_Fragment" l])]
  1771. ["Latex_Environment" name option content]
  1772. (let [content (latex-environment-content name option content)]
  1773. (if html-export?
  1774. (latex/html-export content true true)
  1775. (latex/latex (str (dc/squuid)) content true true)))
  1776. ["Displayed_Math" content]
  1777. (if html-export?
  1778. (latex/html-export content true true)
  1779. (latex/latex (str (dc/squuid)) content true true))
  1780. ["Footnote_Definition" name definition]
  1781. (let [id (util/url-encode name)]
  1782. [:div.footdef
  1783. [:div.footpara
  1784. (conj
  1785. (markup-element-cp config ["Paragraph" definition])
  1786. [:a.ml-1 {:id (str "fn." id)
  1787. :style {:font-size 14}
  1788. :class "footnum"
  1789. :on-click #(route-handler/jump-to-anchor! (str "fnr." id))}
  1790. [:sup.fn (str name "↩︎")]])]])
  1791. :else
  1792. "")
  1793. (catch js/Error e
  1794. (println "Convert to html failed, error: " e)
  1795. "")))
  1796. (defn markup-elements-cp
  1797. [config col]
  1798. (map #(markup-element-cp config %) col))
  1799. (defn build-slide-sections
  1800. ([blocks config]
  1801. (build-slide-sections blocks config nil))
  1802. ([blocks config build-block-fn]
  1803. (when (seq blocks)
  1804. (let [blocks (map #(dissoc % :block/children) blocks)
  1805. first-block-level (:block/level (first blocks))
  1806. sections (reduce
  1807. (fn [acc block]
  1808. (let [block (dissoc block :block/meta)
  1809. level (:block/level block)
  1810. block-cp (if build-block-fn
  1811. (build-block-fn config block)
  1812. (rum/with-key
  1813. (block-container config block)
  1814. (str "slide-" (:block/uuid block))))]
  1815. (if (= first-block-level level)
  1816. ;; new slide
  1817. (conj acc [[block block-cp]])
  1818. (update acc (dec (count acc))
  1819. (fn [sections]
  1820. (conj sections [block block-cp]))))))
  1821. []
  1822. blocks)]
  1823. sections))))
  1824. (rum/defc add-button < rum/reactive
  1825. [config ref? custom-query? blocks]
  1826. (let [editing (state/sub [:editor/editing?])]
  1827. (when-not (or ref? custom-query?
  1828. (:block/dummy? (last blocks))
  1829. (second (first editing)))
  1830. (when-let [last-block (last blocks)]
  1831. [:a.add-button-link {:on-click (fn []
  1832. (editor-handler/insert-new-block-without-save-previous! config last-block))}
  1833. svg/plus-circle]))))
  1834. (defn blocks-container
  1835. [blocks config]
  1836. (let [blocks (map #(dissoc % :block/children) blocks)
  1837. sidebar? (:sidebar? config)
  1838. ref? (:ref? config)
  1839. custom-query? (:custom-query? config)
  1840. blocks->vec-tree #(if (or custom-query? ref?) % (block-handler/blocks->vec-tree %))
  1841. blocks (blocks->vec-tree blocks)]
  1842. (when (seq blocks)
  1843. [:div.blocks-container.flex-1
  1844. {:style {:margin-left (cond
  1845. sidebar?
  1846. 0
  1847. :else
  1848. -10)}}
  1849. (let [first-block (first blocks)
  1850. blocks (if (and (:block/pre-block? first-block)
  1851. (block-handler/pre-block-with-only-title? (:block/repo first-block) (:block/uuid first-block)))
  1852. (rest blocks)
  1853. blocks)
  1854. first-id (:block/uuid (first blocks))]
  1855. (for [[idx item] (medley/indexed blocks)]
  1856. (let [item (-> (if (:block/dummy? item)
  1857. item
  1858. (dissoc item :block/meta)))
  1859. config (assoc config :block/uuid (:block/uuid item))]
  1860. (rum/with-key
  1861. (block-container config item)
  1862. (:block/uuid item)))))
  1863. ;; (add-button config ref? custom-query? blocks)
  1864. ])))
  1865. ;; headers to hiccup
  1866. (defn ->hiccup
  1867. [blocks config option]
  1868. [:div.content
  1869. (cond-> option
  1870. (:document/mode? config)
  1871. (assoc :class "doc-mode"))
  1872. (if (:group-by-page? config)
  1873. [:div.flex.flex-col
  1874. (for [[page blocks] blocks]
  1875. (let [alias? (:page/alias? page)
  1876. page (db/entity (:db/id page))]
  1877. [:div.my-2 (cond-> {:key (str "page-" (:db/id page))}
  1878. (:ref? config)
  1879. (assoc :class "color-level px-7 py-2 rounded"))
  1880. (ui/foldable
  1881. [:div
  1882. (page-cp config page)
  1883. (when alias? [:span.text-sm.font-medium.opacity-50 " Alias"])]
  1884. (blocks-container blocks config))]))]
  1885. (blocks-container blocks config))])
  1886. (comment
  1887. ;; timestamps
  1888. ;; [2020-02-10 Mon 13:22]
  1889. ;; repetition
  1890. (def r1 "<2005-10-01 Sat +1m>")
  1891. ;; TODO: mldoc add supports
  1892. (def r2 "<2005-10-01 Sat +1m -3d>")
  1893. (def l
  1894. "1. First item
  1895. hello world
  1896. 2. Second item
  1897. nice
  1898. 3. Third item")
  1899. (def t
  1900. "| Name | Phone | Age |
  1901. |-------+-------+-----|
  1902. | Peter | 1234 | 17 |
  1903. | Anna | 4321 | 25 |"))