page.cljs 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036
  1. (ns frontend.components.page
  2. (:require [clojure.string :as string]
  3. [frontend.components.block :as component-block]
  4. [frontend.components.content :as content]
  5. [frontend.components.editor :as editor]
  6. [frontend.components.hierarchy :as hierarchy]
  7. [frontend.components.plugins :as plugins]
  8. [frontend.components.reference :as reference]
  9. [frontend.components.svg :as svg]
  10. [frontend.config :as config]
  11. [frontend.context.i18n :refer [t]]
  12. [frontend.date :as date]
  13. [frontend.db :as db]
  14. [frontend.db-mixins :as db-mixins]
  15. [frontend.db.model :as model]
  16. [frontend.extensions.graph :as graph]
  17. [frontend.extensions.pdf.assets :as pdf-assets]
  18. [frontend.format.block :as block]
  19. [frontend.handler.common :as common-handler]
  20. [frontend.handler.config :as config-handler]
  21. [frontend.handler.editor :as editor-handler]
  22. [frontend.handler.graph :as graph-handler]
  23. [frontend.handler.notification :as notification]
  24. [frontend.handler.page :as page-handler]
  25. [frontend.handler.plugin :as plugin-handler]
  26. [frontend.handler.route :as route-handler]
  27. [frontend.mixins :as mixins]
  28. [frontend.mobile.util :as mobile-util]
  29. [frontend.search :as search]
  30. [frontend.state :as state]
  31. [frontend.ui :as ui]
  32. [frontend.util :as util]
  33. [frontend.util.text :as text-util]
  34. [goog.object :as gobj]
  35. [logseq.graph-parser.util :as gp-util]
  36. [medley.core :as medley]
  37. [reitit.frontend.easy :as rfe]
  38. [rum.core :as rum]))
  39. (defn- get-page-name
  40. [state]
  41. (let [route-match (first (:rum/args state))]
  42. (get-in route-match [:parameters :path :name])))
  43. (defn- get-blocks
  44. [repo page-name block-id]
  45. (when page-name
  46. (let [root (if block-id
  47. (db/pull [:block/uuid block-id])
  48. (model/get-page page-name))
  49. opts (if block-id
  50. {:scoped-block-id (:db/id root)}
  51. {})]
  52. (db/get-paginated-blocks repo (:db/id root) opts))))
  53. (defn- open-first-block!
  54. [state]
  55. (let [[_ blocks _ sidebar? preview?] (:rum/args state)]
  56. (when (and
  57. (or preview?
  58. (not (contains? #{:home :all-journals} (state/get-current-route))))
  59. (not sidebar?))
  60. (let [block (first blocks)]
  61. (when (and (= (count blocks) 1)
  62. (string/blank? (:block/content block))
  63. (not preview?))
  64. (editor-handler/edit-block! block :max (:block/uuid block))))))
  65. state)
  66. (rum/defc page-blocks-inner <
  67. {:did-mount open-first-block!
  68. :did-update open-first-block!
  69. :should-update (fn [prev-state state]
  70. (let [[old-page-name _ old-hiccup _ old-block-uuid] (:rum/args prev-state)
  71. [page-name _ hiccup _ block-uuid] (:rum/args state)]
  72. (or (not= page-name old-page-name)
  73. (not= hiccup old-hiccup)
  74. (not= block-uuid old-block-uuid))))}
  75. [page-name _blocks hiccup sidebar? _block-uuid]
  76. [:div.page-blocks-inner {:style {:margin-left (if sidebar? 0 -20)}}
  77. (rum/with-key
  78. (content/content page-name
  79. {:hiccup hiccup
  80. :sidebar? sidebar?})
  81. (str page-name "-hiccup"))])
  82. (declare page)
  83. (rum/defc dummy-block
  84. [page-name]
  85. (let [handler-fn (fn []
  86. (let [block (editor-handler/insert-first-page-block-if-not-exists! page-name {:redirect? false})]
  87. (js/setTimeout #(editor-handler/edit-block! block :max (:block/uuid block)) 0)))]
  88. [:div.ls-block.flex-1.flex-col.rounded-sm {:style {:width "100%"}}
  89. [:div.flex.flex-row
  90. [:div.flex.flex-row.items-center.mr-2.ml-1 {:style {:height 24}}
  91. [:span.bullet-container.cursor
  92. [:span.bullet]]]
  93. [:div.flex.flex-1 {:tabindex 0
  94. :on-key-press (fn [e]
  95. (when (= "Enter" (util/ekey e))
  96. (handler-fn)))
  97. :on-click handler-fn}
  98. [:span.opacity-50
  99. "Click here to edit..."]]]]))
  100. (rum/defc add-button
  101. [args]
  102. [:div.flex-1.flex-col.rounded-sm.add-button-link-wrap
  103. {:on-click (fn [] (editor-handler/api-insert-new-block! "" args))}
  104. [:div.flex.flex-row
  105. [:div.block {:style {:height 20
  106. :width 20
  107. :margin-left 2}}
  108. [:a.add-button-link.block
  109. (ui/icon "circle-plus")]]]])
  110. (rum/defc page-blocks-cp < rum/reactive db-mixins/query
  111. {:will-mount (fn [state]
  112. (let [page-e (second (:rum/args state))
  113. page-name (:block/name page-e)]
  114. (when (and (db/journal-page? page-name)
  115. (>= (date/journal-title->int page-name)
  116. (date/journal-title->int (date/today))))
  117. (state/pub-event! [:journal/insert-template page-name])))
  118. state)}
  119. [repo page-e {:keys [sidebar?] :as config}]
  120. (when page-e
  121. (let [page-name (or (:block/name page-e)
  122. (str (:block/uuid page-e)))
  123. block-id (parse-uuid page-name)
  124. block? (boolean block-id)
  125. page-blocks (get-blocks repo page-name block-id)]
  126. (if (empty? page-blocks)
  127. (dummy-block page-name)
  128. (let [document-mode? (state/sub :document/mode?)
  129. block-entity (db/entity (if block-id
  130. [:block/uuid block-id]
  131. [:block/name page-name]))
  132. hiccup-config (merge
  133. {:id (if block? (str block-id) page-name)
  134. :db/id (:db/id block-entity)
  135. :block? block?
  136. :editor-box editor/box
  137. :document/mode? document-mode?}
  138. config)
  139. hiccup-config (common-handler/config-with-document-mode hiccup-config)
  140. hiccup (component-block/->hiccup page-blocks hiccup-config {})]
  141. [:div
  142. (page-blocks-inner page-name page-blocks hiccup sidebar? block-id)
  143. (when-not config/publishing?
  144. (let [args (if block-id
  145. {:block-uuid block-id}
  146. {:page page-name})]
  147. (add-button args)))])))))
  148. (defn contents-page
  149. [page]
  150. (when-let [repo (state/get-current-repo)]
  151. (page-blocks-cp repo page {:sidebar? true})))
  152. (rum/defc today-queries < rum/reactive
  153. [repo today? sidebar?]
  154. (when (and today? (not sidebar?))
  155. (let [queries (state/sub [:config repo :default-queries :journals])]
  156. (when (seq queries)
  157. [:div#today-queries.mt-10
  158. (for [query queries]
  159. (rum/with-key
  160. (ui/catch-error
  161. (ui/component-error "Failed default query:" {:content (pr-str query)})
  162. (component-block/custom-query {:attr {:class "mt-10"}
  163. :editor-box editor/box
  164. :page page} query))
  165. (str repo "-custom-query-" (:query query))))]))))
  166. (defn tagged-pages
  167. [repo tag]
  168. (let [pages (db/get-tag-pages repo tag)]
  169. (when (seq pages)
  170. [:div.references.mt-6.flex-1.flex-row
  171. [:div.content
  172. (ui/foldable
  173. [:h2.font-bold.opacity-50 (util/format "Pages tagged with \"%s\"" tag)]
  174. [:ul.mt-2
  175. (for [[original-name name] (sort-by last pages)]
  176. [:li {:key (str "tagged-page-" name)}
  177. [:a {:href (rfe/href :page {:name name})}
  178. original-name]])]
  179. {:default-collapsed? false})]])))
  180. (rum/defcs page-title <
  181. (rum/local false ::edit?)
  182. {:init (fn [state]
  183. (assoc state ::title-value (atom (nth (:rum/args state) 2))))}
  184. [state page-name icon title _format fmt-journal?]
  185. (when title
  186. (let [*title-value (get state ::title-value)
  187. *edit? (get state ::edit?)
  188. input-ref (rum/create-ref)
  189. repo (state/get-current-repo)
  190. hls-file? (pdf-assets/hls-file? title)
  191. title (if hls-file?
  192. (pdf-assets/human-hls-filename-display title)
  193. (if fmt-journal? (date/journal-title->custom-format title) title))
  194. old-name (or title page-name)
  195. confirm-fn (fn []
  196. (let [new-page-name (string/trim @*title-value)
  197. merge? (and (not= (util/page-name-sanity-lc page-name)
  198. (util/page-name-sanity-lc @*title-value))
  199. (db/page-exists? page-name)
  200. (db/page-exists? @*title-value))]
  201. (ui/make-confirm-modal
  202. {:title (if merge?
  203. (str "Page “" @*title-value "” already exists, merge to it?")
  204. (str "Do you really want to change the page name to “" new-page-name "”?"))
  205. :on-confirm (fn [_e {:keys [close-fn]}]
  206. (close-fn)
  207. (page-handler/rename! (or title page-name) @*title-value)
  208. (reset! *edit? false))
  209. :on-cancel (fn []
  210. (reset! *title-value old-name)
  211. (gobj/set (rum/deref input-ref) "value" old-name)
  212. (reset! *edit? true))})))
  213. rollback-fn #(do
  214. (reset! *title-value old-name)
  215. (gobj/set (rum/deref input-ref) "value" old-name)
  216. (reset! *edit? false)
  217. (notification/show! "Illegal page name, can not rename!" :warning))
  218. blur-fn (fn [e]
  219. (when (gp-util/wrapped-by-quotes? @*title-value)
  220. (swap! *title-value gp-util/unquote-string)
  221. (gobj/set (rum/deref input-ref) "value" @*title-value))
  222. (cond
  223. (= old-name @*title-value)
  224. (reset! *edit? false)
  225. (string/blank? @*title-value)
  226. (rollback-fn)
  227. :else
  228. (state/set-modal! (confirm-fn)))
  229. (util/stop e))]
  230. (if @*edit?
  231. [:span
  232. {:class (util/classnames [{:editing @*edit?}])
  233. :style {:width "600px"}}
  234. [:input.edit-input
  235. {:type "text"
  236. :ref input-ref
  237. :auto-focus true
  238. :style {:outline "none"
  239. :width "100%"
  240. :font-weight 600}
  241. :auto-complete (if (util/chrome?) "chrome-off" "off") ; off not working here
  242. :default-value old-name
  243. :on-change (fn [^js e]
  244. (let [value (util/evalue e)]
  245. (reset! *title-value (string/trim value))))
  246. :on-blur blur-fn
  247. :on-key-down (fn [^js e]
  248. (when (= (gobj/get e "key") "Enter")
  249. (blur-fn e)))
  250. :on-key-up (fn [^js e]
  251. ;; Esc
  252. (when (= 27 (.-keyCode e))
  253. (reset! *title-value old-name)
  254. (reset! *edit? false)))
  255. :on-focus (fn [] (js/setTimeout #(.select input-ref.current)))}]]
  256. [:a.page-title {:on-mouse-down (fn [e]
  257. (when (util/right-click? e)
  258. (state/set-state! :page-title/context {:page page-name})))
  259. :on-click (fn [e]
  260. (.preventDefault e)
  261. (if (gobj/get e "shiftKey")
  262. (when-let [page (db/pull repo '[*] [:block/name page-name])]
  263. (state/sidebar-add-block!
  264. repo
  265. (:db/id page)
  266. :page))
  267. (when (and (not hls-file?) (not fmt-journal?))
  268. (reset! *edit? true))))}
  269. [:span.title {:data-ref page-name}
  270. (when (not= icon "") [:span.page-icon icon])
  271. title]]))))
  272. (defn- page-mouse-over
  273. [e *control-show? *all-collapsed?]
  274. (util/stop e)
  275. (reset! *control-show? true)
  276. (let [all-collapsed?
  277. (->> (editor-handler/all-blocks-with-level {:collapse? true})
  278. (filter (fn [b] (editor-handler/collapsable? (:block/uuid b))))
  279. (empty?))]
  280. (reset! *all-collapsed? all-collapsed?)))
  281. (defn- page-mouse-leave
  282. [e *control-show?]
  283. (util/stop e)
  284. (reset! *control-show? false))
  285. (rum/defcs page-blocks-collapse-control <
  286. [state title *control-show? *all-collapsed?]
  287. [:a.page-blocks-collapse-control
  288. {:id (str "control-" title)
  289. :on-click (fn [event]
  290. (util/stop event)
  291. (if @*all-collapsed?
  292. (editor-handler/expand-all!)
  293. (editor-handler/collapse-all!))
  294. (swap! *all-collapsed? not))}
  295. [:span.mt-6 {:class (if @*control-show?
  296. "control-show cursor-pointer" "control-hide")}
  297. (ui/rotating-arrow @*all-collapsed?)]])
  298. (defn get-tldraw-preview [page-name]
  299. ((state/get-component :whiteboard/tldraw-preview) page-name))
  300. ;; A page is just a logical block
  301. (rum/defcs page < rum/reactive
  302. (rum/local false ::all-collapsed?)
  303. (rum/local false ::control-show?)
  304. [state {:keys [repo page-name] :as option}]
  305. (when-let [path-page-name (or page-name
  306. (get-page-name state)
  307. (state/get-current-page))]
  308. (let [current-repo (state/sub :git/current-repo)
  309. repo (or repo current-repo)
  310. page-name (util/page-name-sanity-lc path-page-name)
  311. block-id (parse-uuid page-name)
  312. block? (boolean block-id)
  313. format (let [page (if block-id
  314. (:block/name (:block/page (db/entity [:block/uuid block-id])))
  315. page-name)]
  316. (db/get-page-format page))
  317. journal? (db/journal-page? page-name)
  318. fmt-journal? (boolean (date/journal-title->int page-name))
  319. sidebar? (:sidebar? option)
  320. whiteboard? (:whiteboard? option)
  321. whiteboard-page? (model/whiteboard-page? page-name)
  322. route-page-name path-page-name
  323. page (if block?
  324. (->> (:db/id (:block/page (db/entity repo [:block/uuid block-id])))
  325. (db/entity repo))
  326. (do
  327. (when-not (db/entity repo [:block/name page-name])
  328. (let [m (block/page-name->map path-page-name true)]
  329. (db/transact! repo [m])))
  330. (db/pull [:block/name page-name])))
  331. {:keys [icon]} (:block/properties page)
  332. page-name (:block/name page)
  333. page-original-name (:block/original-name page)
  334. title (or page-original-name page-name)
  335. icon (or icon "")
  336. today? (and
  337. journal?
  338. (= page-name (util/page-name-sanity-lc (date/journal-name))))
  339. *control-show? (::control-show? state)
  340. *all-collapsed? (::all-collapsed? state)]
  341. [:div.flex-1.page.relative
  342. (merge (if (seq (:block/tags page))
  343. (let [page-names (model/get-page-names-by-ids (map :db/id (:block/tags page)))]
  344. {:data-page-tags (text-util/build-data-value page-names)})
  345. {})
  346. {:key path-page-name
  347. :class (util/classnames [{:is-journals (or journal? fmt-journal?)}])})
  348. (if whiteboard-page?
  349. [:div (get-tldraw-preview page-name)]
  350. [:div.relative
  351. (when (and (not sidebar?) (not block?))
  352. [:div.flex.flex-row.space-between
  353. (when (or (mobile-util/native-platform?) (util/mobile?))
  354. [:div.flex.flex-row.pr-2
  355. {:style {:margin-left -15}
  356. :on-mouse-over (fn [e]
  357. (page-mouse-over e *control-show? *all-collapsed?))
  358. :on-mouse-leave (fn [e]
  359. (page-mouse-leave e *control-show?))}
  360. (page-blocks-collapse-control title *control-show? *all-collapsed?)])
  361. (when-not whiteboard?
  362. [:div.flex-1.flex-row
  363. [:h1.title.ls-page-title (page-title page-name icon title format fmt-journal?)]])
  364. (when (not config/publishing?)
  365. [:div.flex.flex-row
  366. (when plugin-handler/lsp-enabled?
  367. (plugins/hook-ui-slot :page-head-actions-slotted nil)
  368. (plugins/hook-ui-items :pagebar))])])
  369. [:div
  370. (when (and block? (not sidebar?) (not whiteboard?))
  371. (let [config {:id "block-parent"
  372. :block? true}]
  373. [:div.mb-4
  374. (component-block/breadcrumb config repo block-id {:level-limit 3})]))
  375. ;; blocks
  376. (let [page (if block?
  377. (db/entity repo [:block/uuid block-id])
  378. page)]
  379. (page-blocks-cp repo page {:sidebar? sidebar?}))]])
  380. (when-not block?
  381. (today-queries repo today? sidebar?))
  382. (when-not block?
  383. (tagged-pages repo page-name))
  384. ;; referenced blocks
  385. (when-not whiteboard?
  386. [:div {:key "page-references"}
  387. (rum/with-key
  388. (reference/references route-page-name)
  389. (str route-page-name "-refs"))])
  390. (when-not (or block? whiteboard?)
  391. [:div
  392. (when (not journal?)
  393. (hierarchy/structures route-page-name))
  394. ;; TODO: or we can lazy load them
  395. (when-not sidebar?
  396. [:div {:key "page-unlinked-references"}
  397. (reference/unlinked-references route-page-name)])])])))
  398. (defonce layout (atom [js/window.innerWidth js/window.innerHeight]))
  399. ;; scrollHeight
  400. (rum/defcs graph-filter-section < (rum/local false ::open?)
  401. [state title content {:keys [search-filters]}]
  402. (let [open? (get state ::open?)]
  403. (when (and (seq search-filters) (not @open?))
  404. (reset! open? true))
  405. [:li.relative
  406. [:div
  407. [:button.w-full.px-4.py-2.text-left.focus:outline-none {:on-click #(swap! open? not)}
  408. [:div.flex.items-center.justify-between
  409. title
  410. (if @open? (svg/caret-down) (svg/caret-right))]]
  411. (content open?)]]))
  412. (rum/defc filter-expand-area
  413. [open? content]
  414. [:div.relative.overflow-hidden.transition-all.max-h-0.duration-700
  415. {:style {:max-height (if @open? 400 0)}}
  416. content])
  417. (defonce *n-hops (atom nil))
  418. (defonce *focus-nodes (atom []))
  419. (defonce *graph-reset? (atom false))
  420. (defonce *journal? (atom nil))
  421. (defonce *orphan-pages? (atom true))
  422. (defonce *builtin-pages? (atom nil))
  423. (defonce *excluded-pages? (atom true))
  424. (defonce *show-journals-in-page-graph? (atom nil))
  425. (rum/defc ^:large-vars/cleanup-todo graph-filters < rum/reactive
  426. [graph settings n-hops]
  427. (let [{:keys [journal? orphan-pages? builtin-pages? excluded-pages?]
  428. :or {orphan-pages? true}} settings
  429. journal?' (rum/react *journal?)
  430. orphan-pages?' (rum/react *orphan-pages?)
  431. builtin-pages?' (rum/react *builtin-pages?)
  432. excluded-pages?' (rum/react *excluded-pages?)
  433. journal? (if (nil? journal?') journal? journal?')
  434. orphan-pages? (if (nil? orphan-pages?') orphan-pages? orphan-pages?')
  435. builtin-pages? (if (nil? builtin-pages?') builtin-pages? builtin-pages?')
  436. excluded-pages? (if (nil? excluded-pages?') excluded-pages? excluded-pages?')
  437. set-setting! (fn [key value]
  438. (let [new-settings (assoc settings key value)]
  439. (config-handler/set-config! :graph/settings new-settings)))
  440. search-graph-filters (state/sub :search/graph-filters)
  441. focus-nodes (rum/react *focus-nodes)]
  442. [:div.absolute.top-4.right-4.graph-filters
  443. [:div.flex.flex-col
  444. [:div.shadow-xl.rounded-sm
  445. [:ul
  446. (graph-filter-section
  447. [:span.font-medium "Nodes"]
  448. (fn [open?]
  449. (filter-expand-area
  450. open?
  451. [:div
  452. [:p.text-sm.opacity-70.px-4
  453. (let [c1 (count (:nodes graph))
  454. s1 (if (> c1 1) "s" "")
  455. ;; c2 (count (:links graph))
  456. ;; s2 (if (> c2 1) "s" "")
  457. ]
  458. ;; (util/format "%d page%s, %d link%s" c1 s1 c2 s2)
  459. (util/format "%d page%s" c1 s1))]
  460. [:div.p-6
  461. ;; [:div.flex.items-center.justify-between.mb-2
  462. ;; [:span "Layout"]
  463. ;; (ui/select
  464. ;; (mapv
  465. ;; (fn [item]
  466. ;; (if (= (:label item) layout)
  467. ;; (assoc item :selected "selected")
  468. ;; item))
  469. ;; [{:label "gForce"}
  470. ;; {:label "dagre"}])
  471. ;; (fn [value]
  472. ;; (set-setting! :layout value))
  473. ;; "graph-layout")]
  474. [:div.flex.items-center.justify-between.mb-2
  475. [:span (t :settings-page/enable-journals)]
  476. ;; FIXME: why it's not aligned well?
  477. [:div.mt-1
  478. (ui/toggle journal?
  479. (fn []
  480. (let [value (not journal?)]
  481. (reset! *journal? value)
  482. (set-setting! :journal? value)))
  483. true)]]
  484. [:div.flex.items-center.justify-between.mb-2
  485. [:span "Orphan pages"]
  486. [:div.mt-1
  487. (ui/toggle orphan-pages?
  488. (fn []
  489. (let [value (not orphan-pages?)]
  490. (reset! *orphan-pages? value)
  491. (set-setting! :orphan-pages? value)))
  492. true)]]
  493. [:div.flex.items-center.justify-between.mb-2
  494. [:span "Built-in pages"]
  495. [:div.mt-1
  496. (ui/toggle builtin-pages?
  497. (fn []
  498. (let [value (not builtin-pages?)]
  499. (reset! *builtin-pages? value)
  500. (set-setting! :builtin-pages? value)))
  501. true)]]
  502. [:div.flex.items-center.justify-between.mb-2
  503. [:span "Excluded pages"]
  504. [:div.mt-1
  505. (ui/toggle excluded-pages?
  506. (fn []
  507. (let [value (not excluded-pages?)]
  508. (reset! *excluded-pages? value)
  509. (set-setting! :excluded-pages? value)))
  510. true)]]
  511. (when (seq focus-nodes)
  512. [:div.flex.flex-col.mb-2
  513. [:p {:title "N hops from selected nodes"}
  514. "N hops from selected nodes"]
  515. (ui/tippy {:html [:div.pr-3 n-hops]}
  516. (ui/slider (or n-hops 10)
  517. {:min 1
  518. :max 10
  519. :on-change #(reset! *n-hops (int %))}))])
  520. [:a.opacity-70.opacity-100 {:on-click (fn []
  521. (swap! *graph-reset? not)
  522. (reset! *focus-nodes [])
  523. (reset! *n-hops nil)
  524. (state/clear-search-filters!))}
  525. "Reset Graph"]]]))
  526. {})
  527. (graph-filter-section
  528. [:span.font-medium "Search"]
  529. (fn [open?]
  530. (filter-expand-area
  531. open?
  532. [:div.p-6
  533. (if (seq search-graph-filters)
  534. [:div
  535. (for [q search-graph-filters]
  536. [:div.flex.flex-row.justify-between.items-center.mb-2
  537. [:span.font-medium q]
  538. [:a.search-filter-close.opacity-70.opacity-100 {:on-click #(state/remove-search-filter! q)}
  539. svg/close]])
  540. [:a.opacity-70.opacity-100 {:on-click state/clear-search-filters!}
  541. "Clear All"]]
  542. [:a.opacity-70.opacity-100 {:on-click #(route-handler/go-to-search! :graph)}
  543. "Click to search"])]))
  544. {:search-filters search-graph-filters})]]]]))
  545. (defonce last-node-position (atom nil))
  546. (defn- graph-register-handlers
  547. [graph focus-nodes n-hops dark?]
  548. (.on graph "nodeClick"
  549. (fn [event node]
  550. (let [x (.-x event)
  551. y (.-y event)
  552. drag? (not= [node x y] @last-node-position)]
  553. (graph/on-click-handler graph node event focus-nodes n-hops drag? dark?))))
  554. (.on graph "nodeMousedown"
  555. (fn [event node]
  556. (reset! last-node-position [node (.-x event) (.-y event)]))))
  557. (rum/defc global-graph-inner < rum/reactive
  558. [graph settings theme]
  559. (let [[width height] (rum/react layout)
  560. dark? (= theme "dark")
  561. n-hops (rum/react *n-hops)
  562. reset? (rum/react *graph-reset?)
  563. focus-nodes (when n-hops (rum/react *focus-nodes))
  564. graph (if (and (integer? n-hops)
  565. (seq focus-nodes)
  566. (not (:orphan-pages? settings)))
  567. (graph-handler/n-hops graph focus-nodes n-hops)
  568. graph)
  569. graph (update graph :links (fn [links]
  570. (let [nodes (set (map :id (:nodes graph)))]
  571. (remove (fn [link]
  572. (and (not (nodes (:source link)))
  573. (not (nodes (:target link)))))
  574. links))))]
  575. [:div.relative#global-graph
  576. (graph/graph-2d {:nodes (:nodes graph)
  577. :links (:links graph)
  578. :width (- width 24)
  579. :height (- height 48)
  580. :dark? dark?
  581. :register-handlers-fn
  582. (fn [graph]
  583. (graph-register-handlers graph *focus-nodes *n-hops dark?))
  584. :reset? reset?})
  585. (graph-filters graph settings n-hops)]))
  586. (defn- filter-graph-nodes
  587. [nodes filters]
  588. (if (seq filters)
  589. (let [filter-patterns (map #(re-pattern (str "(?i)" (util/regex-escape %))) filters)]
  590. (filter (fn [node] (some #(re-find % (:id node)) filter-patterns)) nodes))
  591. nodes))
  592. (rum/defcs global-graph < rum/reactive
  593. (mixins/event-mixin
  594. (fn [state]
  595. (mixins/listen state js/window "resize"
  596. (fn [_e]
  597. (reset! layout [js/window.innerWidth js/window.innerHeight])))))
  598. {:will-mount (fn [state]
  599. (state/set-search-mode! :graph)
  600. state)
  601. :will-unmount (fn [state]
  602. (reset! *n-hops nil)
  603. (reset! *focus-nodes [])
  604. (state/set-search-mode! :global)
  605. state)}
  606. [state]
  607. (let [settings (state/sub-graph-config-settings)
  608. theme (state/sub :ui/theme)
  609. graph (graph-handler/build-global-graph theme settings)
  610. search-graph-filters (state/sub :search/graph-filters)
  611. graph (update graph :nodes #(filter-graph-nodes % search-graph-filters))]
  612. (global-graph-inner graph settings theme)))
  613. (rum/defc page-graph-inner < rum/reactive
  614. [_page graph dark?]
  615. (let [ show-journals-in-page-graph? (rum/react *show-journals-in-page-graph?) ]
  616. [:div.sidebar-item.flex-col
  617. [:div.flex.items-center.justify-between.mb-0
  618. [:span (t :right-side-bar/show-journals)]
  619. [:div.mt-1
  620. (ui/toggle show-journals-in-page-graph? ;my-val;
  621. (fn []
  622. (let [value (not show-journals-in-page-graph?)]
  623. (reset! *show-journals-in-page-graph? value)
  624. ))
  625. true)]
  626. ]
  627. (graph/graph-2d {:nodes (:nodes graph)
  628. :links (:links graph)
  629. :width 600
  630. :height 600
  631. :dark? dark?
  632. :register-handlers-fn
  633. (fn [graph]
  634. (graph-register-handlers graph (atom nil) (atom nil) dark?))})]))
  635. (rum/defc page-graph < db-mixins/query rum/reactive
  636. []
  637. (let [page (or
  638. (and (= :page (state/sub [:route-match :data :name]))
  639. (state/sub [:route-match :path-params :name]))
  640. (date/today))
  641. theme (:ui/theme @state/state)
  642. dark? (= theme "dark")
  643. show-journals-in-page-graph (rum/react *show-journals-in-page-graph?)
  644. graph (if (util/uuid-string? page)
  645. (graph-handler/build-block-graph (uuid page) theme)
  646. (graph-handler/build-page-graph page theme show-journals-in-page-graph))]
  647. (when (seq (:nodes graph))
  648. (page-graph-inner page graph dark?))))
  649. (defn- sort-pages-by
  650. [by-item desc? pages]
  651. (let [comp (if desc? > <)
  652. by-item (if (= by-item :block/name)
  653. (fn [x] (string/lower-case (:block/name x)))
  654. by-item)]
  655. (sort-by by-item comp pages)))
  656. (rum/defc checkbox-opt
  657. [key checked opts]
  658. (let [*input (rum/create-ref)
  659. indeterminate? (boolean (:indeterminate opts))]
  660. (rum/use-effect!
  661. #(set! (.-indeterminate (rum/deref *input)) indeterminate?)
  662. [indeterminate?])
  663. [:label {:for key}
  664. [:input.form-checkbox
  665. (merge {:type "checkbox"
  666. :checked (boolean checked)
  667. :ref *input
  668. :id key} opts)]]))
  669. (rum/defc sortable-title
  670. [title key by-item desc?]
  671. [:th
  672. {:class [(name key)]}
  673. [:a.fade-link {:on-click (fn []
  674. (reset! by-item key)
  675. (swap! desc? not))}
  676. [:span.flex.items-center
  677. [:span.mr-1 title]
  678. (when (= @by-item key)
  679. [:span
  680. (if @desc? (svg/caret-down) (svg/caret-up))])]]])
  681. (defn batch-delete-dialog
  682. [pages orphaned-pages? refresh-fn]
  683. (fn [close-fn]
  684. [:div
  685. [:div.sm:flex.items-center
  686. [:div.mx-auto.flex-shrink-0.flex.items-center.justify-center.h-12.w-12.rounded-full.bg-red-100.sm:mx-0.sm:h-10.sm:w-10
  687. [:span.text-red-600.text-xl
  688. (ui/icon "alert-triangle")]]
  689. [:div.mt-3.text-center.sm:mt-0.sm:ml-4.sm:text-left
  690. [:h3#modal-headline.text-lg.leading-6.font-medium
  691. (if orphaned-pages?
  692. (str (t :remove-orphaned-pages) "?")
  693. (t :page/delete-confirmation))]]]
  694. [:table.table-auto.cp__all_pages_table.mt-4
  695. [:thead
  696. [:tr.opacity-70
  697. [:th [:span "#"]]
  698. [:th [:span (t :block/name)]]
  699. [:th [:span (t :page/backlinks)]]
  700. (when-not orphaned-pages? [:th [:span (t :page/created-at)]])
  701. (when-not orphaned-pages? [:th [:span (t :page/updated-at)]])]]
  702. [:tbody
  703. (for [[n {:block/keys [name created-at updated-at backlinks] :as page}] (medley/indexed pages)]
  704. [:tr {:key name}
  705. [:td.n.w-12 [:span.opacity-70 (str (inc n) ".")]]
  706. [:td.name [:a {:href (rfe/href :page {:name (:block/name page)})}
  707. (component-block/page-cp {} page)]]
  708. [:td.backlinks [:span (or backlinks "0")]]
  709. (when-not orphaned-pages? [:td.created-at [:span (if created-at (date/int->local-time-2 created-at) "Unknown")]])
  710. (when-not orphaned-pages? [:td.updated-at [:span (if updated-at (date/int->local-time-2 updated-at) "Unknown")]])])]]
  711. [:div.pt-6.flex.justify-end
  712. [:span.pr-2
  713. (ui/button
  714. (t :cancel)
  715. :intent "logseq"
  716. :on-click close-fn)]
  717. (ui/button
  718. (t :yes)
  719. :on-click (fn []
  720. (close-fn)
  721. (doseq [page-name (map :block/name pages)]
  722. (page-handler/delete! page-name #()))
  723. (notification/show! (str (t :tips/all-done) "!") :success)
  724. (js/setTimeout #(refresh-fn) 200)))]]))
  725. (rum/defcs ^:large-vars/cleanup-todo all-pages < rum/reactive
  726. (rum/local nil ::pages)
  727. (rum/local nil ::search-key)
  728. (rum/local nil ::results-all)
  729. (rum/local nil ::results)
  730. (rum/local {} ::checks)
  731. (rum/local :block/updated-at ::sort-by-item)
  732. (rum/local true ::desc?)
  733. (rum/local false ::journals)
  734. (rum/local false ::whiteboards)
  735. (rum/local nil ::filter-fn)
  736. (rum/local 1 ::current-page)
  737. [state]
  738. (let [current-repo (state/sub :git/current-repo)
  739. per-page-num 40
  740. *sort-by-item (get state ::sort-by-item)
  741. *desc? (::desc? state)
  742. *journal? (::journals state)
  743. *whiteboard? (::whiteboards state)
  744. *results (::results state)
  745. *results-all (::results-all state)
  746. *checks (::checks state)
  747. *pages (::pages state)
  748. *current-page (::current-page state)
  749. *filter-fn (::filter-fn state)
  750. *search-key (::search-key state)
  751. *search-input (rum/create-ref)
  752. *indeterminate (rum/derived-atom
  753. [*checks] ::indeterminate
  754. (fn [checks]
  755. (when-let [checks (vals checks)]
  756. (if (every? true? checks)
  757. 1 (if (some true? checks) -1 0)))))
  758. mobile? (util/mobile?)
  759. total-pages (if-not @*results-all 0
  760. (js/Math.ceil (/ (count @*results-all) per-page-num)))
  761. to-page (fn [page]
  762. (when (> total-pages 1)
  763. (if (and (> page 0)
  764. (<= page total-pages))
  765. (reset! *current-page page)
  766. (reset! *current-page 1))
  767. (js/setTimeout #(util/scroll-to-top))))
  768. search-key (fn [key]
  769. (when-let [key (and key (string/trim key))]
  770. (if (and (not (string/blank? key))
  771. (seq @*results))
  772. (reset! *search-key key)
  773. (reset! *search-key nil))))
  774. refresh-pages #(do
  775. (reset! *pages nil)
  776. (reset! *current-page 1))]
  777. [:div.flex-1.cp__all_pages
  778. [:h1.title (t :all-pages)]
  779. [:div.text-sm.ml-1.opacity-70.mb-4 (t :paginates/pages (count @*results-all))]
  780. (when current-repo
  781. ;; all pages
  782. (when (nil? @*pages)
  783. (let [pages (->> (page-handler/get-all-pages current-repo)
  784. (map-indexed (fn [idx page] (assoc page
  785. :block/backlinks (count (:block/_refs (db/entity (:db/id page))))
  786. :block/idx idx))))]
  787. (reset! *filter-fn
  788. (memoize (fn [sort-by-item desc? journal? whiteboard?]
  789. (->> pages
  790. (filter #(and
  791. (or (boolean journal?)
  792. (= false (boolean (:block/journal? %))))
  793. (or (boolean whiteboard?)
  794. (= false (boolean (:block/whiteboard? %))))))
  795. (sort-pages-by sort-by-item desc?)))))
  796. (reset! *pages pages)))
  797. ;; filter results
  798. (when @*filter-fn
  799. (let [pages (@*filter-fn @*sort-by-item @*desc? @*journal? @*whiteboard?)
  800. ;; search key
  801. pages (if-not (string/blank? @*search-key)
  802. (search/fuzzy-search pages (util/page-name-sanity-lc @*search-key)
  803. :limit 20
  804. :extract-fn :block/name)
  805. pages)
  806. _ (reset! *results-all pages)
  807. pages (take per-page-num (drop (* per-page-num (dec @*current-page)) pages))]
  808. (reset! *checks (into {} (for [{:block/keys [idx]} pages]
  809. [idx (boolean (get @*checks idx))])))
  810. (reset! *results pages)))
  811. (let [has-prev? (> @*current-page 1)
  812. has-next? (not= @*current-page total-pages)]
  813. [:div
  814. [:div.actions
  815. {:class (util/classnames [{:has-selected (or (nil? @*indeterminate)
  816. (not= 0 @*indeterminate))}])}
  817. [:div.l.flex.items-center
  818. [:div.actions-wrap
  819. (ui/button
  820. [(ui/icon "trash" {:style {:font-size 15}}) (t :delete)]
  821. :on-click (fn []
  822. (let [selected (filter (fn [[_ v]] v) @*checks)
  823. selected (and (seq selected)
  824. (into #{} (for [[k _] selected] k)))]
  825. (when-let [pages (and selected (filter #(contains? selected (:block/idx %)) @*results))]
  826. (state/set-modal! (batch-delete-dialog pages false #(do
  827. (reset! *checks nil)
  828. (refresh-pages)))))))
  829. :class "fade-link"
  830. :small? true)]
  831. [:div.search-wrap.flex.items-center.pl-2
  832. (let [search-fn (fn []
  833. (let [^js input (rum/deref *search-input)]
  834. (search-key (.-value input))
  835. (reset! *current-page 1)))
  836. reset-fn (fn []
  837. (let [^js input (rum/deref *search-input)]
  838. (set! (.-value input) "")
  839. (reset! *search-key nil)))]
  840. [(ui/button (ui/icon "search")
  841. :on-click search-fn
  842. :small? true)
  843. [:input.form-input {:placeholder (t :search/page-names)
  844. :on-key-up (fn [^js e]
  845. (let [^js target (.-target e)]
  846. (if (string/blank? (.-value target))
  847. (reset! *search-key nil)
  848. (cond
  849. (= 13 (.-keyCode e)) (search-fn)
  850. (= 27 (.-keyCode e)) (reset-fn)))))
  851. :ref *search-input
  852. :default-value ""}]
  853. (when (not (string/blank? @*search-key))
  854. [:a.cancel {:on-click reset-fn}
  855. (ui/icon "x")])])]]
  856. [:div.r.flex.items-center.justify-between
  857. [:div
  858. (ui/tippy
  859. {:html [:small (str (t :page/show-whiteboards) " ?")]
  860. :arrow true}
  861. [:a.button.whiteboard
  862. {:class (util/classnames [{:active (boolean @*whiteboard?)}])
  863. :on-click #(reset! *whiteboard? (not @*whiteboard?))}
  864. (ui/icon "artboard" {:style {:fontSize ui/icon-size}})])]
  865. [:div
  866. (ui/tippy
  867. {:html [:small (str (t :page/show-journals) " ?")]
  868. :arrow true}
  869. [:a.button.journal
  870. {:class (util/classnames [{:active (boolean @*journal?)}])
  871. :on-click #(reset! *journal? (not @*journal?))}
  872. (ui/icon "calendar" {:style {:fontSize ui/icon-size}})])]
  873. [:div.paginates
  874. [:span.flex.items-center
  875. {:class (util/classnames [{:is-first (= 1 @*current-page)
  876. :is-last (= @*current-page total-pages)}])}
  877. (when has-prev?
  878. [:a.py-4.pr-2.fade-link {:on-click #(to-page (dec @*current-page))} (ui/icon "caret-left") (str " " (t :paginates/prev))])
  879. [:span.opacity-60 (str @*current-page "/" total-pages)]
  880. (when has-next?
  881. [:a.py-4.pl-2.fade-link {:on-click #(to-page (inc @*current-page))} (str (t :paginates/next) " ") (ui/icon "caret-right")])]]
  882. (ui/dropdown-with-links
  883. (fn [{:keys [toggle-fn]}]
  884. [:a.button.fade-link
  885. {:on-click toggle-fn}
  886. (ui/icon "dots" {:style {:fontSize ui/icon-size}})])
  887. [{:title (t :remove-orphaned-pages)
  888. :options {:on-click (fn []
  889. (let [orphaned-pages (model/get-orphaned-pages {})
  890. orphaned-pages? (seq orphaned-pages)]
  891. (if orphaned-pages?
  892. (state/set-modal!
  893. (batch-delete-dialog
  894. orphaned-pages true
  895. #(do
  896. (reset! *checks nil)
  897. (refresh-pages))))
  898. (notification/show! "Congratulations, no orphaned pages in your graph!" :success))))}
  899. :icon (ui/icon "file-x")}
  900. {:title (t :all-files)
  901. :options {:href (rfe/href :all-files)}
  902. :icon (ui/icon "files")}]
  903. {})]]
  904. [:table.table-auto.cp__all_pages_table
  905. [:thead
  906. [:tr
  907. [:th.selector
  908. (checkbox-opt "all-pages-select-all"
  909. (= 1 @*indeterminate)
  910. {:on-change (fn []
  911. (let [indeterminate? (= -1 @*indeterminate)
  912. all? (= 1 @*indeterminate)]
  913. (doseq [{:block/keys [idx]} @*results]
  914. (swap! *checks assoc idx (or indeterminate? (not all?))))))
  915. :indeterminate (= -1 @*indeterminate)})]
  916. (sortable-title (t :block/name) :block/name *sort-by-item *desc?)
  917. (when-not mobile?
  918. [(sortable-title (t :page/backlinks) :block/backlinks *sort-by-item *desc?)
  919. (sortable-title (t :page/created-at) :block/created-at *sort-by-item *desc?)
  920. (sortable-title (t :page/updated-at) :block/updated-at *sort-by-item *desc?)])]]
  921. [:tbody
  922. (for [{:block/keys [idx name created-at updated-at backlinks] :as page} @*results]
  923. (when-not (string/blank? name)
  924. [:tr {:key name}
  925. [:td.selector
  926. (checkbox-opt (str "label-" idx)
  927. (get @*checks idx)
  928. {:on-change (fn []
  929. (swap! *checks update idx not))})]
  930. [:td.name [:a {:on-click (fn [e]
  931. (let [repo (state/get-current-repo)]
  932. (when (gobj/get e "shiftKey")
  933. (state/sidebar-add-block!
  934. repo
  935. (:db/id page)
  936. :page))))
  937. :href (rfe/href :page {:name (:block/name page)})}
  938. (component-block/page-cp {} page)]]
  939. (when-not mobile?
  940. [:td.backlinks [:span backlinks]])
  941. (when-not mobile?
  942. [:td.created-at [:span (if created-at
  943. (date/int->local-time-2 created-at)
  944. "Unknown")]])
  945. (when-not mobile?
  946. [:td.updated-at [:span (if updated-at
  947. (date/int->local-time-2 updated-at)
  948. "Unknown")]])]))]]
  949. [:div.paginates
  950. [:span]
  951. [:span.flex.items-center
  952. (when has-prev?
  953. [:a.py-4.text-sm.fade-link {:on-click #(to-page (dec @*current-page))} (ui/icon "caret-left") (str " " (t :paginates/prev))])
  954. (when has-next?
  955. [:a.py-4.pl-2.text-sm.fade-link {:on-click #(to-page (inc @*current-page))} (str (t :paginates/next) " ") (ui/icon "caret-right")])]]]))]))