page.cljs 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219
  1. (ns frontend.components.page
  2. (:require ["/frontend/utils" :as utils]
  3. [clojure.string :as string]
  4. [dommy.core :as dom]
  5. [frontend.components.block :as component-block]
  6. [frontend.components.class :as class-component]
  7. [frontend.components.content :as content]
  8. [frontend.components.db-based.page :as db-page]
  9. [frontend.components.editor :as editor]
  10. [frontend.components.file-based.hierarchy :as hierarchy]
  11. [frontend.components.objects :as objects]
  12. [frontend.components.plugins :as plugins]
  13. [frontend.components.query :as query]
  14. [frontend.components.reference :as reference]
  15. [frontend.components.scheduled-deadlines :as scheduled]
  16. [frontend.components.svg :as svg]
  17. [frontend.config :as config]
  18. [frontend.context.i18n :refer [t]]
  19. [frontend.date :as date]
  20. [frontend.db :as db]
  21. [frontend.db-mixins :as db-mixins]
  22. [frontend.db.async :as db-async]
  23. [frontend.db.model :as model]
  24. [frontend.extensions.graph :as graph]
  25. [frontend.extensions.graph.pixi :as pixi]
  26. [frontend.extensions.pdf.utils :as pdf-utils]
  27. [frontend.format.mldoc :as mldoc]
  28. [frontend.handler.common :as common-handler]
  29. [frontend.handler.config :as config-handler]
  30. [frontend.handler.dnd :as dnd]
  31. [frontend.handler.editor :as editor-handler]
  32. [frontend.handler.graph :as graph-handler]
  33. [frontend.handler.notification :as notification]
  34. [frontend.handler.page :as page-handler]
  35. [frontend.handler.route :as route-handler]
  36. [frontend.mixins :as mixins]
  37. [frontend.mobile.util :as mobile-util]
  38. [frontend.rum :as frontend-rum]
  39. [frontend.state :as state]
  40. [frontend.ui :as ui]
  41. [frontend.util :as util]
  42. [frontend.util.text :as text-util]
  43. [goog.object :as gobj]
  44. [logseq.common.util :as common-util]
  45. [logseq.common.util.page-ref :as page-ref]
  46. [logseq.db :as ldb]
  47. [logseq.graph-parser.mldoc :as gp-mldoc]
  48. [logseq.shui.hooks :as hooks]
  49. [logseq.shui.ui :as shui]
  50. [promesa.core :as p]
  51. [reitit.frontend.easy :as rfe]
  52. [rum.core :as rum]))
  53. (defn- get-page-name
  54. [state]
  55. (let [route-match (first (:rum/args state))]
  56. (get-in route-match [:parameters :path :name])))
  57. ;; Named block links only works on web (and publishing)
  58. (if util/web-platform?
  59. (defn- get-block-uuid-by-block-route-name
  60. "Return string block uuid for matching :name and :block-route-name params or
  61. nil if not found"
  62. [state]
  63. ;; Only query if block name is in the route
  64. (when-let [route-name (get-in (first (:rum/args state))
  65. [:parameters :path :block-route-name])]
  66. (->> (model/get-block-by-page-name-and-block-route-name
  67. (state/get-current-repo)
  68. (get-page-name state)
  69. route-name)
  70. :block/uuid
  71. str)))
  72. (def get-block-uuid-by-block-route-name (constantly nil)))
  73. (defn- open-root-block!
  74. [state]
  75. (let [[_ block _ sidebar? preview?] (:rum/args state)]
  76. (when (and
  77. (or preview?
  78. (not (contains? #{:home :all-journals} (state/get-current-route))))
  79. (not sidebar?))
  80. (when (and (string/blank? (:block/title block))
  81. (not preview?))
  82. (editor-handler/edit-block! block :max)))))
  83. (rum/defc page-blocks-inner <
  84. {:did-mount (fn [state]
  85. (open-root-block! state)
  86. state)}
  87. [page-e blocks config sidebar? _preview? _block-uuid]
  88. (when page-e
  89. (let [hiccup (component-block/->hiccup blocks config {})]
  90. [:div.page-blocks-inner {:style {:min-height 29}}
  91. (rum/with-key
  92. (content/content (str (:block/uuid page-e))
  93. {:hiccup hiccup
  94. :sidebar? sidebar?})
  95. (str (:block/uuid page-e) "-hiccup"))])))
  96. (declare page-cp)
  97. (if config/publishing?
  98. (rum/defc dummy-block
  99. [_page]
  100. [:div])
  101. (rum/defc dummy-block
  102. [page]
  103. (let [[hover set-hover!] (rum/use-state false)
  104. click-handler-fn (fn []
  105. (p/let [result (editor-handler/insert-first-page-block-if-not-exists! (:block/uuid page))
  106. result (:tx-data result)
  107. first-child-id (first (map :block/uuid result))
  108. first-child (when first-child-id (db/entity [:block/uuid first-child-id]))]
  109. (when first-child
  110. (editor-handler/edit-block! first-child :max {:container-id :unknown-container}))))
  111. drop-handler-fn (fn [^js event]
  112. (util/stop event)
  113. (p/let [block-uuids (state/get-selection-block-ids)
  114. lookup-refs (map (fn [id] [:block/uuid id]) block-uuids)
  115. selected (db/pull-many (state/get-current-repo) '[*] lookup-refs)
  116. blocks (if (seq selected) selected [@component-block/*dragging-block])
  117. _ (editor-handler/insert-first-page-block-if-not-exists! (:block/uuid page))]
  118. (js/setTimeout #(let [target-block page]
  119. (dnd/move-blocks event blocks target-block nil :sibling))
  120. 0)))
  121. *dummy-block-uuid (rum/use-ref (random-uuid))
  122. *el-ref (rum/use-ref nil)
  123. _ (frontend-rum/use-atom (@state/state :selection/blocks))
  124. selection-ids (state/get-selection-block-ids)
  125. selected? (contains? (set selection-ids) (rum/deref *dummy-block-uuid))
  126. idstr (str (rum/deref *dummy-block-uuid))
  127. focus! (fn [] (js/setTimeout #(some-> (rum/deref *el-ref) (.focus)) 16))]
  128. ;; mounted
  129. ;(hooks/use-effect! #(focus!) [])
  130. (hooks/use-effect! #(if selected? (focus!)
  131. (some-> (rum/deref *el-ref) (.blur))) [selected?])
  132. (shui/trigger-as
  133. :div.ls-dummy-block.ls-block
  134. {:style {:width "100%"
  135. ;; The same as .dnd-separator
  136. :border-top (if hover
  137. "3px solid #ccc"
  138. nil)
  139. :margin-left 20}
  140. :ref *el-ref
  141. :tabIndex 0
  142. :on-click click-handler-fn
  143. :id idstr
  144. :blockid idstr
  145. :class (when selected? "selected")}
  146. [:div.flex.items-center
  147. [:div.flex.items-center.mx-1 {:style {:height 24}}
  148. [:span.bullet-container.cursor
  149. [:span.bullet]]]
  150. [:div.flex.flex-1.cursor-text
  151. {:on-drag-enter #(set-hover! true)
  152. :on-drag-over #(util/stop %)
  153. :on-drop drop-handler-fn
  154. :on-drag-leave #(set-hover! false)}
  155. [:span.opacity-70.text
  156. "Click here to edit..."]]]))))
  157. (rum/defc add-button
  158. [args container-id]
  159. (let [*bullet-ref (rum/use-ref nil)]
  160. [:div.flex-1.flex-col.rounded-sm.add-button-link-wrap
  161. {:on-click (fn [e]
  162. (util/stop e)
  163. (state/set-state! :editor/container-id container-id)
  164. (editor-handler/api-insert-new-block! "" args))
  165. :on-mouse-over #(dom/add-class! (rum/deref *bullet-ref) "opacity-50")
  166. :on-mouse-leave #(dom/remove-class! (rum/deref *bullet-ref) "opacity-50")
  167. :on-key-down (fn [e]
  168. (util/stop e)
  169. (when (= "Enter" (util/ekey e))
  170. (state/set-state! :editor/container-id container-id)
  171. (editor-handler/api-insert-new-block! "" args)))
  172. :tab-index 0}
  173. [:div.flex.flex-row
  174. [:div.flex.items-center {:style {:height 28
  175. :margin-left 22}}
  176. [:span.bullet-container.cursor.opacity-0.transition-opacity.ease-in.duration-100 {:ref *bullet-ref}
  177. [:span.bullet]]]]]))
  178. (rum/defcs page-blocks-cp < rum/reactive db-mixins/query
  179. {:will-mount (fn [state]
  180. (when-not (config/db-based-graph?)
  181. (let [page-e (first (:rum/args state))
  182. page-name (:block/name page-e)]
  183. (when (and page-name
  184. (db/journal-page? page-name)
  185. (>= (date/journal-title->int page-name)
  186. (date/journal-title->int (date/today))))
  187. (state/pub-event! [:journal/insert-template page-name]))))
  188. state)}
  189. [state block* {:keys [sidebar? whiteboard?] :as config}]
  190. (when-let [id (:db/id block*)]
  191. (let [block (db/sub-block id)
  192. block-id (:block/uuid block)
  193. block? (not (db/page? block))
  194. children (:block/_parent block)
  195. children (cond
  196. (ldb/class? block)
  197. (remove (fn [b] (contains? (set (map :db/id (:block/tags b))) (:db/id block))) children)
  198. (ldb/property? block)
  199. (remove (fn [b] (some? (get b (:db/ident block)))) children)
  200. :else
  201. children)]
  202. (cond
  203. (and
  204. (not block?)
  205. (empty? children) block)
  206. (dummy-block block)
  207. :else
  208. (let [document-mode? (state/sub :document/mode?)
  209. hiccup-config (merge
  210. {:id (str (:block/uuid block))
  211. :db/id (:db/id block)
  212. :block? block?
  213. :editor-box editor/box
  214. :document/mode? document-mode?}
  215. config)
  216. config (common-handler/config-with-document-mode hiccup-config)
  217. blocks (if block? [block] (db/sort-by-order children block))]
  218. (let [add-button? (not (or config/publishing?
  219. (let [last-child-id (model/get-block-deep-last-open-child-id (db/get-db) (:db/id (last blocks)))
  220. block' (if last-child-id (db/entity last-child-id) (last blocks))
  221. link (:block/link block')]
  222. (string/blank? (:block/title (or link block'))))))]
  223. [:div.relative
  224. {:class (when add-button? "show-add-button")}
  225. (page-blocks-inner block blocks config sidebar? whiteboard? block-id)
  226. (let [args {:block-uuid block-id}]
  227. (add-button args (:container-id config)))]))))))
  228. (rum/defc today-queries < rum/reactive
  229. [repo today? sidebar?]
  230. (when (and today? (not sidebar?))
  231. (let [queries (get-in (state/sub-config repo) [:default-queries :journals])]
  232. (when (seq queries)
  233. [:div#today-queries
  234. (for [query queries]
  235. (let [query' (if (config/db-based-graph?)
  236. (assoc query :collapsed? true)
  237. query)]
  238. (rum/with-key
  239. (ui/catch-error
  240. (ui/component-error "Failed default query:" {:content (pr-str query')})
  241. (query/custom-query (component-block/wrap-query-components
  242. {:attr {:class "mt-10"}
  243. :editor-box editor/box
  244. :page page-cp})
  245. query'))
  246. (str repo "-custom-query-" (:query query')))))]))))
  247. (rum/defc tagged-pages
  248. [repo tag tag-title]
  249. (let [[pages set-pages!] (rum/use-state nil)]
  250. (hooks/use-effect!
  251. (fn []
  252. (p/let [result (db-async/<get-tag-pages repo (:db/id tag))]
  253. (set-pages! result)))
  254. [tag])
  255. (when (seq pages)
  256. [:div.references.page-tags.flex-1.flex-row
  257. [:div.content
  258. (ui/foldable
  259. [:h2.font-bold.opacity-50 (util/format "Pages tagged with \"%s\"" tag-title)]
  260. [:ul.mt-2
  261. (for [page (sort-by :block/title pages)]
  262. [:li {:key (str "tagged-page-" (:db/id page))}
  263. (component-block/page-cp {} page)])]
  264. {:default-collapsed? false})]])))
  265. (rum/defc page-title-editor < rum/reactive
  266. [page {:keys [*input-value *title-value *edit? untitled? page-name old-name whiteboard-page?]}]
  267. (let [input-ref (rum/create-ref)
  268. tag-idents (map :db/ident (:block/tags page))
  269. collide? #(and (not= (util/page-name-sanity-lc page-name)
  270. (util/page-name-sanity-lc @*title-value))
  271. (db/page-exists? page-name tag-idents)
  272. (db/page-exists? @*title-value tag-idents))
  273. rollback-fn #(let [old-name (if untitled? "" old-name)]
  274. (reset! *title-value old-name)
  275. (gobj/set (rum/deref input-ref) "value" old-name)
  276. (reset! *edit? true)
  277. (.focus (rum/deref input-ref)))
  278. blur-fn (fn [e]
  279. (when (common-util/wrapped-by-quotes? @*title-value)
  280. (swap! *title-value common-util/unquote-string)
  281. (gobj/set (rum/deref input-ref) "value" @*title-value))
  282. (cond
  283. (or (= old-name @*title-value) (and whiteboard-page? (string/blank? @*title-value)))
  284. (reset! *edit? false)
  285. (string/blank? @*title-value)
  286. (do (when-not untitled? (notification/show! (t :page/illegal-page-name) :warning))
  287. (rollback-fn))
  288. (collide?)
  289. (do (notification/show! (t :page/page-already-exists @*title-value) :error)
  290. (rollback-fn))
  291. (and (date/valid-journal-title? @*title-value) whiteboard-page?)
  292. (do (notification/show! (t :page/whiteboard-to-journal-error) :error)
  293. (rollback-fn))
  294. :else
  295. (p/do!
  296. (page-handler/rename! (:block/uuid page) @*title-value)
  297. (js/setTimeout #(reset! *edit? false) 100)))
  298. (util/stop e))]
  299. [:input.edit-input.p-0.outline-none.focus:outline-none.no-ring
  300. {:type "text"
  301. :ref input-ref
  302. :auto-focus true
  303. :style {:width "100%"
  304. :font-weight "inherit"}
  305. :auto-complete (if (util/chrome?) "chrome-off" "off") ; off not working here
  306. :value (rum/react *input-value)
  307. :on-change (fn [^js e]
  308. (let [value (util/evalue e)]
  309. (reset! *title-value (string/trim value))
  310. (reset! *input-value value)))
  311. :on-blur blur-fn
  312. :on-key-down (fn [^js e]
  313. (when (= (gobj/get e "key") "Enter")
  314. (blur-fn e)))
  315. :placeholder (when untitled? (t :untitled))
  316. :on-key-up (fn [^js e]
  317. ;; Esc
  318. (when (= 27 (.-keyCode e))
  319. (reset! *title-value old-name)
  320. (reset! *edit? false)))
  321. :on-focus (fn []
  322. (when untitled? (reset! *title-value "")))}]))
  323. (rum/defcs ^:large-vars/cleanup-todo page-title-cp < rum/reactive db-mixins/query
  324. (rum/local false ::edit?)
  325. (rum/local "" ::input-value)
  326. {:init (fn [state]
  327. (let [page (first (:rum/args state))
  328. title (:block/title page)
  329. *title-value (atom title)]
  330. (assoc state ::title-value *title-value)))}
  331. [state page {:keys [fmt-journal? preview?]}]
  332. (when page
  333. (let [page (db/sub-block (:db/id page))
  334. title (:block/title page)]
  335. (when title
  336. (let [repo (state/get-current-repo)
  337. journal? (ldb/journal? page)
  338. *title-value (get state ::title-value)
  339. *edit? (get state ::edit?)
  340. *input-value (get state ::input-value)
  341. hls-page? (pdf-utils/hls-file? title)
  342. whiteboard-page? (model/whiteboard-page? page)
  343. untitled? (and whiteboard-page? (parse-uuid title)) ;; normal page cannot be untitled right?
  344. title (if hls-page?
  345. [:a.asset-ref (pdf-utils/fix-local-asset-pagename title)]
  346. (if fmt-journal?
  347. (date/journal-title->custom-format title)
  348. title))
  349. old-name title]
  350. [:div.ls-page-title.flex.flex-1.flex-row.flex-wrap.w-full.relative.items-center.gap-2
  351. [:h1.page-title.flex-1.cursor-pointer.gap-1
  352. {:class (when-not whiteboard-page? "title")
  353. :on-pointer-down (fn [e]
  354. (when (util/right-click? e)
  355. (state/set-state! :page-title/context {:page (:block/title page)
  356. :page-entity page})))
  357. :on-click (fn [e]
  358. (when-not (= (.-nodeName (.-target e)) "INPUT")
  359. (.preventDefault e)
  360. (if (gobj/get e "shiftKey")
  361. (state/sidebar-add-block!
  362. repo
  363. (:db/id page)
  364. :page)
  365. (when (and (not hls-page?)
  366. (not journal?)
  367. (not config/publishing?)
  368. (not (ldb/built-in? page)))
  369. (reset! *input-value (if untitled? "" old-name))
  370. (reset! *edit? true)))))}
  371. (if @*edit?
  372. (page-title-editor page {:*title-value *title-value
  373. :*edit? *edit?
  374. :*input-value *input-value
  375. :page-name (:block/title page)
  376. :old-name old-name
  377. :untitled? untitled?
  378. :whiteboard-page? whiteboard-page?
  379. :preview? preview?})
  380. [:span.title.block
  381. {:on-click (fn []
  382. (when (and (not preview?)
  383. (contains? #{:home :all-journals} (get-in (state/get-route-match) [:data :name])))
  384. (route-handler/redirect-to-page! (:block/uuid page))))
  385. :data-value @*input-value
  386. :data-ref (:block/title page)
  387. :style {:opacity (when @*edit? 0)}}
  388. (let [nested? (and (string/includes? title page-ref/left-brackets)
  389. (string/includes? title page-ref/right-brackets))]
  390. (cond untitled? [:span.opacity-50 (t :untitled)]
  391. nested? (component-block/map-inline {} (gp-mldoc/inline->edn title (mldoc/get-default-config
  392. (get page :block/format :markdown))))
  393. :else title))])]])))))
  394. (rum/defc db-page-title-actions
  395. [page]
  396. [:div.absolute.-top-4.left-0.opacity-0.db-page-title-actions
  397. [:div.flex.flex-row.items-center.gap-2
  398. (when-not (:logseq.property/icon (db/entity (:db/id page)))
  399. (shui/button
  400. {:variant :outline
  401. :size :sm
  402. :class "px-2 py-0 h-6 text-xs text-muted-foreground"
  403. :on-click (fn [e]
  404. (state/pub-event! [:editor/new-property {:property-key "Icon"
  405. :block page
  406. :target (.-target e)}]))}
  407. "Add icon"))
  408. (shui/button
  409. {:variant :outline
  410. :size :sm
  411. :class "px-2 py-0 h-6 text-xs text-muted-foreground"
  412. :on-click (fn [e]
  413. (state/pub-event! [:editor/new-property {:block page
  414. :target (.-target e)}]))}
  415. "Set property")]])
  416. (rum/defc db-page-title
  417. [page whiteboard-page? sidebar? container-id]
  418. (let [with-actions? (not config/publishing?)]
  419. [:div.ls-page-title.flex.flex-1.w-full.content.items-start.title
  420. {:class (when-not whiteboard-page? "title")
  421. :data-testid "page title"
  422. :on-pointer-down (fn [e]
  423. (when (util/right-click? e)
  424. (state/set-state! :page-title/context {:page (:block/title page)
  425. :page-entity page})))
  426. :on-click (fn [e]
  427. (when-not (some-> e (.-target) (.closest ".ls-properties-area"))
  428. (when-not (= (.-nodeName (.-target e)) "INPUT")
  429. (.preventDefault e)
  430. (when (gobj/get e "shiftKey")
  431. (state/sidebar-add-block!
  432. (state/get-current-repo)
  433. (:db/id page)
  434. :page)))))}
  435. [:div.w-full.relative
  436. (component-block/block-container
  437. {:page-title? true
  438. :page-title-actions-cp (when (and with-actions? (not= (:db/id (state/get-edit-block)) (:db/id page))) db-page-title-actions)
  439. :hide-title? sidebar?
  440. :sidebar? sidebar?
  441. :hide-children? true
  442. :container-id container-id
  443. :show-tag-and-property-classes? true
  444. :from-journals? (contains? #{:home :all-journals} (get-in (state/get-route-match) [:data :name]))}
  445. page)]]))
  446. (defn- page-mouse-over
  447. [e *control-show? *all-collapsed?]
  448. (util/stop e)
  449. (reset! *control-show? true)
  450. (p/let [blocks (editor-handler/<all-blocks-with-level {:collapse? true})
  451. all-collapsed?
  452. (->> blocks
  453. (filter (fn [b] (editor-handler/collapsable? (:block/uuid b))))
  454. (empty?))]
  455. (reset! *all-collapsed? all-collapsed?)))
  456. (defn- page-mouse-leave
  457. [e *control-show?]
  458. (util/stop e)
  459. (reset! *control-show? false))
  460. (rum/defcs page-blocks-collapse-control <
  461. [state title *control-show? *all-collapsed?]
  462. [:a.page-blocks-collapse-control
  463. {:id (str "control-" title)
  464. :on-click (fn [event]
  465. (util/stop event)
  466. (if @*all-collapsed?
  467. (editor-handler/expand-all!)
  468. (editor-handler/collapse-all!))
  469. (swap! *all-collapsed? not))}
  470. [:span.mt-6 {:class (if @*control-show?
  471. "control-show cursor-pointer" "control-hide")}
  472. (ui/rotating-arrow @*all-collapsed?)]])
  473. (defn- get-path-page-name
  474. [state page-name]
  475. (or page-name
  476. (get-block-uuid-by-block-route-name state)
  477. ;; is page name or uuid
  478. (get-page-name state)
  479. (state/get-current-page)))
  480. (defn get-page-entity
  481. [page-name]
  482. (cond
  483. (uuid? page-name)
  484. (db/entity [:block/uuid page-name])
  485. (common-util/uuid-string? page-name)
  486. (db/entity [:block/uuid (uuid page-name)])
  487. :else
  488. (db/get-page page-name)))
  489. (defn- get-sanity-page-name
  490. [state page-name]
  491. (when-let [path-page-name (get-path-page-name state page-name)]
  492. (util/page-name-sanity-lc path-page-name)))
  493. (rum/defc lsp-pagebar-slot <
  494. rum/static
  495. []
  496. (when (not config/publishing?)
  497. (when config/lsp-enabled?
  498. [:div.flex.flex-row
  499. (plugins/hook-ui-slot :page-head-actions-slotted nil)
  500. (plugins/hook-ui-items :pagebar)])))
  501. (rum/defc tabs < rum/static
  502. {:did-mount (fn [state]
  503. (let [*tabs-rendered? (:*tabs-rendered? (last (:rum/args state)))]
  504. (reset! *tabs-rendered? true)
  505. state))}
  506. [page opts]
  507. (let [class? (ldb/class? page)
  508. property? (ldb/property? page)
  509. both? (and class? property?)
  510. default-tab (cond
  511. both?
  512. "tag"
  513. class?
  514. "tag"
  515. :else
  516. "property")]
  517. [:div.page-tabs
  518. (shui/tabs
  519. {:defaultValue default-tab
  520. :class "w-full"}
  521. (when (or both? property?)
  522. [:div.flex.flex-row.gap-1.items-center
  523. (shui/tabs-list
  524. {:class "h-8"}
  525. (when class?
  526. (shui/tabs-trigger
  527. {:value "tag"
  528. :class "py-1 text-xs"}
  529. "Tagged nodes"))
  530. (when property?
  531. (shui/tabs-trigger
  532. {:value "property"
  533. :class "py-1 text-xs"}
  534. "Nodes with property"))
  535. (when property?
  536. (db-page/configure-property page)))])
  537. (when class?
  538. (shui/tabs-content
  539. {:value "tag"}
  540. (objects/class-objects page opts)))
  541. (when property?
  542. (shui/tabs-content
  543. {:value "property"}
  544. (objects/property-related-objects page (:current-page? opts)))))]))
  545. (rum/defc sidebar-page-properties
  546. [config page]
  547. (let [[collapsed? set-collapsed!] (rum/use-state true)]
  548. [:div.ls-sidebar-page-properties.flex.flex-col.gap-2.mt-2
  549. [:div
  550. (shui/button
  551. {:variant :ghost
  552. :size :sm
  553. :class "px-1 text-muted-foreground"
  554. :on-click #(set-collapsed! (not collapsed?))}
  555. [:span.text-xs (str (if collapsed? "Open" "Hide")) " properties"])]
  556. (when-not collapsed?
  557. [:<>
  558. (component-block/db-properties-cp config page {:sidebar-properties? true})
  559. [:hr.my-4]])]))
  560. ;; A page is just a logical block
  561. (rum/defcs ^:large-vars/cleanup-todo page-inner < rum/reactive db-mixins/query mixins/container-id
  562. (rum/local false ::all-collapsed?)
  563. (rum/local false ::control-show?)
  564. (rum/local nil ::current-page)
  565. (rum/local false ::tabs-rendered?)
  566. [state {:keys [repo page preview? sidebar? linked-refs? unlinked-refs? config] :as option}]
  567. (let [current-repo (state/sub :git/current-repo)
  568. *tabs-rendered? (::tabs-rendered? state)
  569. repo (or repo current-repo)
  570. block-id (:block/uuid page)
  571. block? (some? (:block/page page))
  572. class-page? (ldb/class? page)
  573. property-page? (ldb/property? page)
  574. title (:block/title page)
  575. journal? (db/journal-page? title)
  576. db-based? (config/db-based-graph? repo)
  577. fmt-journal? (boolean (date/journal-title->int title))
  578. whiteboard? (:whiteboard? option) ;; in a whiteboard portal shape?
  579. whiteboard-page? (model/whiteboard-page? page) ;; is this page a whiteboard?
  580. today? (and
  581. journal?
  582. (= title (date/journal-name)))
  583. *control-show? (::control-show? state)
  584. *all-collapsed? (::all-collapsed? state)
  585. block-or-whiteboard? (or block? whiteboard?)
  586. home? (= :home (state/get-current-route))
  587. show-tabs? (and db-based? (or class-page? (ldb/property? page)))
  588. tabs-rendered? (rum/react *tabs-rendered?)]
  589. (if page
  590. (when (or title block-or-whiteboard?)
  591. [:div.flex-1.page.relative.cp__page-inner-wrap
  592. (merge (if (seq (:block/tags page))
  593. (let [page-names (map :block/title (:block/tags page))]
  594. (when (seq page-names)
  595. {:data-page-tags (text-util/build-data-value page-names)}))
  596. {})
  597. {:key title
  598. :class (util/classnames [{:is-journals (or journal? fmt-journal?)
  599. :is-node-page (or class-page? property-page?)}])})
  600. (if (and whiteboard-page? (not sidebar?))
  601. [:div ((state/get-component :whiteboard/tldraw-preview) (:block/uuid page))] ;; FIXME: this is not reactive
  602. [:div.relative.grid.gap-8.page-inner
  603. (when-not (or block? sidebar?)
  604. [:div.flex.flex-row.space-between
  605. (when (and (or (mobile-util/native-platform?) (util/mobile?)) (not db-based?))
  606. [:div.flex.flex-row.pr-2
  607. {:style {:margin-left -15}
  608. :on-mouse-over (fn [e]
  609. (page-mouse-over e *control-show? *all-collapsed?))
  610. :on-mouse-leave (fn [e]
  611. (page-mouse-leave e *control-show?))}
  612. (page-blocks-collapse-control title *control-show? *all-collapsed?)])
  613. (when (and (not whiteboard?) (ldb/page? page))
  614. (if db-based?
  615. (db-page-title page whiteboard-page? sidebar? (:container-id state))
  616. (page-title-cp page {:journal? journal?
  617. :fmt-journal? fmt-journal?
  618. :preview? preview?})))
  619. (lsp-pagebar-slot)])
  620. (when (and db-based? sidebar? (ldb/page? page))
  621. [:div.-mb-8
  622. (sidebar-page-properties config page)])
  623. (when (and block? (not sidebar?) (not whiteboard?))
  624. (let [config (merge config {:id "block-parent"
  625. :block? true})]
  626. [:div.mb-4
  627. (component-block/breadcrumb config repo block-id {:level-limit 3})]))
  628. (when show-tabs?
  629. (tabs page {:current-page? option :sidebar? sidebar? :*tabs-rendered? *tabs-rendered?}))
  630. (when (or (not show-tabs?) tabs-rendered?)
  631. [:div.ls-page-blocks
  632. {:style {:margin-left (if whiteboard? 0 -20)}}
  633. (page-blocks-cp page (merge option {:sidebar? sidebar?
  634. :container-id (:container-id state)
  635. :whiteboard? whiteboard?}))])])
  636. (when (and (not preview?) (or (not show-tabs?) tabs-rendered?))
  637. [:div.ml-1.flex.flex-col.gap-4
  638. (when today?
  639. (today-queries repo today? sidebar?))
  640. (when today?
  641. (scheduled/scheduled-and-deadlines title))
  642. (when (and (not block?) (not db-based?))
  643. (tagged-pages repo page title))
  644. (when (and (ldb/page? page) (:logseq.property/_parent page))
  645. (class-component/class-children page))
  646. ;; referenced blocks
  647. (when-not (or whiteboard? linked-refs? (and block? (not db-based?)))
  648. [:div {:key "page-references"}
  649. (rum/with-key
  650. (reference/references page {:sidebar? sidebar?})
  651. (str title "-refs"))])
  652. (when-not block-or-whiteboard?
  653. (when (and (not journal?) (not db-based?))
  654. (hierarchy/structures (:block/title page))))
  655. (when-not (or whiteboard? unlinked-refs?
  656. sidebar?
  657. home?
  658. (or class-page? property-page?)
  659. (and block? (not db-based?)))
  660. [:div {:key "page-unlinked-references"}
  661. (reference/unlinked-references page {:sidebar? sidebar?})])])])
  662. [:div.opacity-75 "Page not found"])))
  663. (rum/defcs page-aux < rum/reactive
  664. {:init (fn [state]
  665. (let [page* (first (:rum/args state))
  666. page-name (:page-name page*)
  667. page-id-uuid-or-name (or (:db/id page*) (:block/uuid page*)
  668. (get-sanity-page-name state page-name))
  669. option (last (:rum/args state))
  670. preview-or-sidebar? (or (:preview? option) (:sidebar? option))
  671. page-uuid? (when page-name (util/uuid-string? page-name))
  672. *loading? (atom true)
  673. page (db/get-page page-id-uuid-or-name)
  674. *page (atom page)]
  675. (when (:block.temp/fully-loaded? page) (reset! *loading? false))
  676. (p/let [page-block (db-async/<get-block (state/get-current-repo) page-id-uuid-or-name)]
  677. (reset! *loading? false)
  678. (reset! *page (db/entity (:db/id page-block)))
  679. (when page-block
  680. (when-not preview-or-sidebar?
  681. (if-let [page-uuid (and (not (:db/id page*)) (not page-uuid?) (:block/uuid page-block))]
  682. (route-handler/redirect-to-page! (str page-uuid) {:push false})
  683. (route-handler/update-page-title-and-label! (state/get-route-match))))))
  684. (assoc state
  685. ::loading? *loading?
  686. ::*page *page)))
  687. :will-unmount (fn [state]
  688. (state/set-state! :editor/virtualized-scroll-fn nil)
  689. state)}
  690. [state option]
  691. (let [loading? (rum/react (::loading? state))
  692. page (rum/react (::*page state))]
  693. (when (and page (not loading?))
  694. (page-inner (assoc option :page page)))))
  695. (rum/defcs page-cp
  696. [state option]
  697. (let [page-name (or (:page-name option) (get-page-name state))]
  698. (rum/with-key
  699. (page-aux (assoc option :page-name page-name))
  700. (str
  701. (state/get-current-repo)
  702. "-"
  703. (or (:db/id option) page-name)))))
  704. (defonce layout (atom [js/window.innerWidth js/window.innerHeight]))
  705. ;; scrollHeight
  706. (rum/defcs graph-filter-section < (rum/local false ::open?)
  707. [state title content {:keys [search-filters]}]
  708. (let [open? (get state ::open?)]
  709. (when (and (seq search-filters) (not @open?))
  710. (reset! open? true))
  711. [:li.relative
  712. [:div
  713. [:button.w-full.px-4.py-2.text-left.focus:outline-none {:on-click #(swap! open? not)}
  714. [:div.flex.items-center.justify-between
  715. title
  716. (if @open? (svg/caret-down) (svg/caret-right))]]
  717. (content open?)]]))
  718. (rum/defc filter-expand-area
  719. [open? content]
  720. [:div.relative.overflow-hidden.transition-all.max-h-0.duration-700
  721. {:style {:max-height (if @open? 400 0)}}
  722. content])
  723. (defonce *n-hops (atom nil))
  724. (defonce *focus-nodes (atom []))
  725. (defonce *graph-reset? (atom false))
  726. (defonce *graph-forcereset? (atom false))
  727. (defonce *journal? (atom nil))
  728. (defonce *orphan-pages? (atom true))
  729. (defonce *builtin-pages? (atom nil))
  730. (defonce *excluded-pages? (atom true))
  731. (defonce *show-journals-in-page-graph? (atom nil))
  732. (defonce *created-at-filter (atom nil))
  733. (defonce *link-dist (atom 70))
  734. (defonce *charge-strength (atom -600))
  735. (defonce *charge-range (atom 600))
  736. (rum/defcs simulation-switch < rum/reactive
  737. [state]
  738. (let [*simulation-paused? pixi/*simulation-paused?]
  739. [:div.flex.flex-col.mb-2
  740. [:p {:title "Pause simulation"}
  741. "Pause simulation"]
  742. (ui/toggle
  743. (rum/react *simulation-paused?)
  744. (fn []
  745. (let [paused? @*simulation-paused?]
  746. (if paused?
  747. (pixi/resume-simulation!)
  748. (pixi/stop-simulation!))))
  749. true)]))
  750. (rum/defc ^:large-vars/cleanup-todo graph-filters < rum/reactive
  751. [graph settings forcesettings n-hops]
  752. (let [{:keys [journal? orphan-pages? builtin-pages? excluded-pages?]
  753. :or {orphan-pages? true}} settings
  754. {:keys [link-dist charge-strength charge-range]} forcesettings
  755. journal?' (rum/react *journal?)
  756. orphan-pages?' (rum/react *orphan-pages?)
  757. builtin-pages?' (rum/react *builtin-pages?)
  758. excluded-pages?' (rum/react *excluded-pages?)
  759. link-dist' (rum/react *link-dist)
  760. charge-strength' (rum/react *charge-strength)
  761. charge-range' (rum/react *charge-range)
  762. journal? (if (nil? journal?') journal? journal?')
  763. orphan-pages? (if (nil? orphan-pages?') orphan-pages? orphan-pages?')
  764. builtin-pages? (if (nil? builtin-pages?') builtin-pages? builtin-pages?')
  765. excluded-pages? (if (nil? excluded-pages?') excluded-pages? excluded-pages?')
  766. created-at-filter (or (rum/react *created-at-filter) (:created-at-filter settings))
  767. link-dist (if (nil? link-dist') link-dist link-dist')
  768. charge-strength (if (nil? charge-strength') charge-strength charge-strength')
  769. charge-range (if (nil? charge-range') charge-range charge-range')
  770. set-setting! (fn [key value]
  771. (let [new-settings (assoc settings key value)]
  772. (config-handler/set-config! :graph/settings new-settings)))
  773. set-forcesetting! (fn [key value]
  774. (let [new-forcesettings (assoc forcesettings key value)]
  775. (config-handler/set-config! :graph/forcesettings new-forcesettings)))
  776. search-graph-filters (state/sub :search/graph-filters)
  777. focus-nodes (rum/react *focus-nodes)]
  778. [:div.absolute.top-4.right-4.graph-filters
  779. [:div.flex.flex-col
  780. [:div.shadow-xl.rounded-sm
  781. [:ul
  782. (graph-filter-section
  783. [:span.font-medium "Nodes"]
  784. (fn [open?]
  785. (filter-expand-area
  786. open?
  787. [:div
  788. [:p.text-sm.opacity-70.px-4
  789. (let [c1 (count (:nodes graph))
  790. s1 (if (> c1 1) "s" "")
  791. ;; c2 (count (:links graph))
  792. ;; s2 (if (> c2 1) "s" "")
  793. ]
  794. ;; (util/format "%d page%s, %d link%s" c1 s1 c2 s2)
  795. (util/format "%d page%s" c1 s1))]
  796. [:div.p-6
  797. ;; [:div.flex.items-center.justify-between.mb-2
  798. ;; [:span "Layout"]
  799. ;; (ui/select
  800. ;; (mapv
  801. ;; (fn [item]
  802. ;; (if (= (:label item) layout)
  803. ;; (assoc item :selected "selected")
  804. ;; item))
  805. ;; [{:label "gForce"}
  806. ;; {:label "dagre"}])
  807. ;; (fn [_e value]
  808. ;; (set-setting! :layout value))
  809. ;; {:class "graph-layout"})]
  810. [:div.flex.items-center.justify-between.mb-2
  811. [:span (t :settings-page/enable-journals)]
  812. ;; FIXME: why it's not aligned well?
  813. [:div.mt-1
  814. (ui/toggle journal?
  815. (fn []
  816. (let [value (not journal?)]
  817. (reset! *journal? value)
  818. (set-setting! :journal? value)))
  819. true)]]
  820. [:div.flex.items-center.justify-between.mb-2
  821. [:span "Orphan pages"]
  822. [:div.mt-1
  823. (ui/toggle orphan-pages?
  824. (fn []
  825. (let [value (not orphan-pages?)]
  826. (reset! *orphan-pages? value)
  827. (set-setting! :orphan-pages? value)))
  828. true)]]
  829. [:div.flex.items-center.justify-between.mb-2
  830. [:span "Built-in pages"]
  831. [:div.mt-1
  832. (ui/toggle builtin-pages?
  833. (fn []
  834. (let [value (not builtin-pages?)]
  835. (reset! *builtin-pages? value)
  836. (set-setting! :builtin-pages? value)))
  837. true)]]
  838. [:div.flex.items-center.justify-between.mb-2
  839. [:span "Excluded pages"]
  840. [:div.mt-1
  841. (ui/toggle excluded-pages?
  842. (fn []
  843. (let [value (not excluded-pages?)]
  844. (reset! *excluded-pages? value)
  845. (set-setting! :excluded-pages? value)))
  846. true)]]
  847. (when (config/db-based-graph? (state/get-current-repo))
  848. [:div.flex.flex-col.mb-2
  849. [:p "Created before"]
  850. (when created-at-filter
  851. [:div (.toDateString (js/Date. (+ created-at-filter (get-in graph [:all-pages :created-at-min]))))])
  852. (ui/tooltip
  853. ;; Slider keeps track off the range from min created-at to max created-at
  854. ;; because there were bugs with setting min and max directly
  855. (ui/slider created-at-filter
  856. {:min 0
  857. :max (- (get-in graph [:all-pages :created-at-max])
  858. (get-in graph [:all-pages :created-at-min]))
  859. :on-change #(do
  860. (reset! *created-at-filter (int %))
  861. (set-setting! :created-at-filter (int %)))})
  862. [:div.px-1 (str (js/Date. (+ created-at-filter (get-in graph [:all-pages :created-at-min]))))])])
  863. (when (seq focus-nodes)
  864. [:div.flex.flex-col.mb-2
  865. [:p {:title "N hops from selected nodes"}
  866. "N hops from selected nodes"]
  867. (ui/tooltip
  868. (ui/slider (or n-hops 10)
  869. {:min 1
  870. :max 10
  871. :on-change #(reset! *n-hops (int %))})
  872. [:div n-hops])])
  873. [:a.opacity-70.opacity-100 {:on-click (fn []
  874. (swap! *graph-reset? not)
  875. (reset! *focus-nodes [])
  876. (reset! *n-hops nil)
  877. (reset! *created-at-filter nil)
  878. (set-setting! :created-at-filter nil)
  879. (state/clear-search-filters!))}
  880. "Reset Graph"]]]))
  881. {})
  882. (graph-filter-section
  883. [:span.font-medium "Search"]
  884. (fn [open?]
  885. (filter-expand-area
  886. open?
  887. [:div.p-6
  888. (if (seq search-graph-filters)
  889. [:div
  890. (for [q search-graph-filters]
  891. [:div.flex.flex-row.justify-between.items-center.mb-2
  892. [:span.font-medium q]
  893. [:a.search-filter-close.opacity-70.opacity-100 {:on-click #(state/remove-search-filter! q)}
  894. svg/close]])
  895. [:a.opacity-70.opacity-100 {:on-click state/clear-search-filters!}
  896. "Clear All"]]
  897. [:a.opacity-70.opacity-100 {:on-click #(route-handler/go-to-search! :graph)}
  898. "Click to search"])]))
  899. {:search-filters search-graph-filters})
  900. (graph-filter-section
  901. [:span.font-medium "Forces"]
  902. (fn [open?]
  903. (filter-expand-area
  904. open?
  905. [:div
  906. [:p.text-sm.opacity-70.px-4
  907. (let [c2 (count (:links graph))
  908. s2 (if (> c2 1) "s" "")]
  909. (util/format "%d link%s" c2 s2))]
  910. [:div.p-6
  911. (simulation-switch)
  912. [:div.flex.flex-col.mb-2
  913. [:p {:title "Link Distance"}
  914. "Link Distance"]
  915. (ui/tooltip
  916. (ui/slider (/ link-dist 10)
  917. {:min 1 ;; 10
  918. :max 18 ;; 180
  919. :on-change #(let [value (int %)]
  920. (reset! *link-dist (* value 10))
  921. (set-forcesetting! :link-dist (* value 10)))})
  922. [:div link-dist])]
  923. [:div.flex.flex-col.mb-2
  924. [:p {:title "Charge Strength"}
  925. "Charge Strength"]
  926. (ui/tooltip
  927. (ui/slider (/ charge-strength 100)
  928. {:min -10 ;;-1000
  929. :max 10 ;;1000
  930. :on-change #(let [value (int %)]
  931. (reset! *charge-strength (* value 100))
  932. (set-forcesetting! :charge-strength (* value 100)))})
  933. [:div charge-strength])]
  934. [:div.flex.flex-col.mb-2
  935. [:p {:title "Charge Range"}
  936. "Charge Range"]
  937. (ui/tooltip
  938. (ui/slider (/ charge-range 100)
  939. {:min 5 ;;500
  940. :max 40 ;;4000
  941. :on-change #(let [value (int %)]
  942. (reset! *charge-range (* value 100))
  943. (set-forcesetting! :charge-range (* value 100)))})
  944. [:div charge-range])]
  945. [:a
  946. {:on-click (fn []
  947. (swap! *graph-forcereset? not)
  948. (reset! *link-dist 70)
  949. (reset! *charge-strength -600)
  950. (reset! *charge-range 600))}
  951. "Reset Forces"]]]))
  952. {})
  953. (graph-filter-section
  954. [:span.font-medium "Export"]
  955. (fn [open?]
  956. (filter-expand-area
  957. open?
  958. (when-let [canvas (js/document.querySelector "#global-graph canvas")]
  959. [:div.p-6
  960. ;; We'll get an empty image if we don't wrap this in a requestAnimationFrame
  961. [:div [:a {:on-click #(.requestAnimationFrame js/window (fn [] (utils/canvasToImage canvas "graph" "png")))} "as PNG"]]])))
  962. {:search-filters search-graph-filters})]]]]))
  963. (defonce last-node-position (atom nil))
  964. (defn- graph-register-handlers
  965. [graph focus-nodes n-hops dark?]
  966. (.on graph "nodeClick"
  967. (fn [event node]
  968. (let [x (.-x event)
  969. y (.-y event)
  970. drag? (not (let [[last-node last-x last-y] @last-node-position
  971. threshold 5]
  972. (and (= node last-node)
  973. (<= (abs (- x last-x)) threshold)
  974. (<= (abs (- y last-y)) threshold))))]
  975. (graph/on-click-handler graph node event focus-nodes n-hops drag? dark?))))
  976. (.on graph "nodeMousedown"
  977. (fn [event node]
  978. (reset! last-node-position [node (.-x event) (.-y event)]))))
  979. (rum/defc global-graph-inner < rum/reactive
  980. [graph settings forcesettings theme]
  981. (let [[width height] (rum/react layout)
  982. dark? (= theme "dark")
  983. n-hops (rum/react *n-hops)
  984. link-dist (rum/react *link-dist)
  985. charge-strength (rum/react *charge-strength)
  986. charge-range (rum/react *charge-range)
  987. reset? (rum/react *graph-reset?)
  988. forcereset? (rum/react *graph-forcereset?)
  989. focus-nodes (when n-hops (rum/react *focus-nodes))
  990. graph (if (and (integer? n-hops)
  991. (seq focus-nodes)
  992. (not (:orphan-pages? settings)))
  993. (graph-handler/n-hops graph focus-nodes n-hops)
  994. graph)]
  995. [:div.relative#global-graph
  996. (graph/graph-2d {:nodes (:nodes graph)
  997. :links (:links graph)
  998. :width (- width 24)
  999. :height (- height 48)
  1000. :dark? dark?
  1001. :link-dist link-dist
  1002. :charge-strength charge-strength
  1003. :charge-range charge-range
  1004. :register-handlers-fn
  1005. (fn [graph]
  1006. (graph-register-handlers graph *focus-nodes *n-hops dark?))
  1007. :reset? reset?
  1008. :forcereset? forcereset?})
  1009. (graph-filters graph settings forcesettings n-hops)]))
  1010. (defn- filter-graph-nodes
  1011. [nodes filters]
  1012. (if (seq filters)
  1013. (let [filter-patterns (map #(re-pattern (str "(?i)" (util/regex-escape %))) filters)]
  1014. (filter (fn [node] (some #(re-find % (:label node)) filter-patterns)) nodes))
  1015. nodes))
  1016. (rum/defc graph-aux
  1017. [settings forcesettings theme search-graph-filters]
  1018. (let [[graph set-graph!] (hooks/use-state nil)]
  1019. (hooks/use-effect!
  1020. (fn []
  1021. (p/let [result (state/<invoke-db-worker :thread-api/build-graph (state/get-current-repo)
  1022. (assoc settings
  1023. :type :global
  1024. :theme theme))]
  1025. (set-graph! result)))
  1026. [theme settings])
  1027. (when graph
  1028. (let [graph' (update graph :nodes #(filter-graph-nodes % search-graph-filters))]
  1029. (global-graph-inner graph' settings forcesettings theme)))))
  1030. (rum/defcs global-graph < rum/reactive
  1031. (mixins/event-mixin
  1032. (fn [state]
  1033. (mixins/listen state js/window "resize"
  1034. (fn [_e]
  1035. (reset! layout [js/window.innerWidth js/window.innerHeight])))))
  1036. {:will-unmount (fn [state]
  1037. (reset! *n-hops nil)
  1038. (reset! *focus-nodes [])
  1039. (state/set-search-mode! :global)
  1040. state)}
  1041. [state]
  1042. (let [settings (state/graph-settings)
  1043. forcesettings (state/graph-forcesettings)
  1044. theme (state/sub :ui/theme)
  1045. ;; Needed for query to retrigger after reset
  1046. _reset? (rum/react *graph-reset?)
  1047. search-graph-filters (state/sub :search/graph-filters)]
  1048. (graph-aux settings forcesettings theme search-graph-filters)))
  1049. (rum/defc page-graph-inner < rum/reactive
  1050. [_page graph dark?]
  1051. (let [show-journals-in-page-graph? (rum/react *show-journals-in-page-graph?)]
  1052. [:div.sidebar-item.flex-col
  1053. [:div.flex.items-center.justify-between.mb-0
  1054. [:span (t :right-side-bar/show-journals)]
  1055. [:div.mt-1
  1056. (ui/toggle show-journals-in-page-graph? ;my-val;
  1057. (fn []
  1058. (let [value (not show-journals-in-page-graph?)]
  1059. (reset! *show-journals-in-page-graph? value)))
  1060. true)]]
  1061. (graph/graph-2d {:nodes (:nodes graph)
  1062. :links (:links graph)
  1063. :width 600
  1064. :height 600
  1065. :dark? dark?
  1066. :register-handlers-fn
  1067. (fn [graph]
  1068. (graph-register-handlers graph (atom nil) (atom nil) dark?))})]))
  1069. (rum/defc page-graph-aux
  1070. [page opts]
  1071. (let [[graph set-graph!] (hooks/use-state nil)
  1072. dark? (= (:theme opts) "dark")]
  1073. (hooks/use-effect!
  1074. (fn []
  1075. (p/let [result (state/<invoke-db-worker :thread-api/build-graph (state/get-current-repo) opts)]
  1076. (set-graph! result)))
  1077. [opts])
  1078. (when (seq (:nodes graph))
  1079. (page-graph-inner page graph dark?))))
  1080. (rum/defc page-graph < db-mixins/query rum/reactive
  1081. []
  1082. (let [page (or
  1083. (and (= :page (state/sub [:route-match :data :name]))
  1084. (state/sub [:route-match :path-params :name]))
  1085. (date/today))
  1086. theme (:ui/theme @state/state)
  1087. show-journals-in-page-graph (rum/react *show-journals-in-page-graph?)
  1088. page-entity (db/get-page page)]
  1089. (page-graph-aux page
  1090. {:type (if (ldb/page? page-entity) :page :block)
  1091. :block/uuid (:block/uuid page-entity)
  1092. :theme theme
  1093. :show-journals? show-journals-in-page-graph})))
  1094. (defn batch-delete-dialog
  1095. [pages refresh-fn]
  1096. (fn [{:keys [close]}]
  1097. [:div
  1098. [:div.sm:flex.items-center
  1099. [:div.mx-auto.flex-shrink-0.flex.items-center.justify-center.h-12.w-12.rounded-full.bg-error.sm:mx-0.sm:h-10.sm:w-10
  1100. [:span.text-error.text-xl
  1101. (ui/icon "alert-triangle")]]
  1102. [:div.mt-3.text-center.sm:mt-0.sm:ml-4.sm:text-left
  1103. [:h3#modal-headline.text-lg.leading-6.font-medium
  1104. (t :page/delete-confirmation)]]]
  1105. [:ol.p-2.pt-4
  1106. (for [page pages]
  1107. [:li
  1108. [:a {:href (rfe/href :page {:name (:block/uuid page)})}
  1109. (component-block/page-cp {} page)]])]
  1110. [:p.px-2.opacity-50 [:small (str "Total: " (count pages))]]
  1111. [:div.pt-6.flex.justify-end.gap-4
  1112. (ui/button
  1113. (t :cancel)
  1114. :variant :outline
  1115. :on-click close)
  1116. (ui/button
  1117. (t :yes)
  1118. :on-click (fn []
  1119. (close)
  1120. (let [failed-pages (atom [])]
  1121. (p/let [_ (p/all (map (fn [page]
  1122. (page-handler/<delete! (:block/uuid page) nil
  1123. {:error-handler
  1124. (fn []
  1125. (swap! failed-pages conj (:block/name page)))}))
  1126. pages))]
  1127. (if (seq @failed-pages)
  1128. (notification/show! (t :all-pages/failed-to-delete-pages (string/join ", " (map pr-str @failed-pages)))
  1129. :warning false)
  1130. (notification/show! (t :tips/all-done) :success))))
  1131. (js/setTimeout #(refresh-fn) 200)))]]))