page.cljs 52 KB

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