page.cljs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. (ns frontend.components.page
  2. (:require [rum.core :as rum]
  3. [frontend.util :as util :refer [profile]]
  4. [frontend.util.marker :as marker]
  5. [frontend.tools.html-export :as html-export]
  6. [frontend.handler.page :as page-handler]
  7. [frontend.handler.ui :as ui-handler]
  8. [frontend.handler.common :as common-handler]
  9. [frontend.commands :as commands]
  10. [frontend.handler.plugin :as plugin-handler]
  11. [frontend.handler.route :as route-handler]
  12. [frontend.handler.graph :as graph-handler]
  13. [frontend.handler.notification :as notification]
  14. [frontend.handler.editor :as editor-handler]
  15. [frontend.handler.config :as config-handler]
  16. [frontend.state :as state]
  17. [clojure.string :as string]
  18. [frontend.components.block :as block]
  19. [frontend.components.editor :as editor]
  20. [frontend.components.plugins :as plugins]
  21. [frontend.components.reference :as reference]
  22. [frontend.components.svg :as svg]
  23. [frontend.components.export :as export]
  24. [frontend.extensions.graph :as graph]
  25. [frontend.components.hierarchy :as hierarchy]
  26. [frontend.ui :as ui]
  27. [frontend.components.content :as content]
  28. [frontend.config :as config]
  29. [frontend.db :as db]
  30. [frontend.db.model :as model]
  31. [frontend.db.utils :as db-utils]
  32. [frontend.mixins :as mixins]
  33. [frontend.db-mixins :as db-mixins]
  34. [goog.dom :as gdom]
  35. [goog.object :as gobj]
  36. [frontend.utf8 :as utf8]
  37. [frontend.date :as date]
  38. [frontend.format.mldoc :as mldoc]
  39. [cljs-time.coerce :as tc]
  40. [cljs-time.core :as t]
  41. [cljs.pprint :as pprint]
  42. [frontend.context.i18n :as i18n]
  43. [reitit.frontend.easy :as rfe]
  44. [frontend.text :as text]
  45. [frontend.modules.shortcut.core :as shortcut]
  46. [frontend.handler.block :as block-handler]
  47. [cljs-bean.core :as bean]))
  48. (defn- get-page-name
  49. [state]
  50. (let [route-match (first (:rum/args state))]
  51. (get-in route-match [:parameters :path :name])))
  52. (defn- get-blocks
  53. [repo page-name page-original-name block? block-id]
  54. (when page-name
  55. (if block?
  56. (db/get-block-and-children repo block-id)
  57. (do
  58. (page-handler/add-page-to-recent! repo page-original-name)
  59. (db/get-page-blocks repo page-name)))))
  60. (defn- open-first-block!
  61. [state]
  62. (let [blocks (nth (:rum/args state) 1)
  63. block (first blocks)
  64. preview? (nth (:rum/args state) 4)]
  65. (when (and (= (count blocks) 1)
  66. (string/blank? (:block/content block))
  67. (not preview?))
  68. (editor-handler/edit-block! block :max (:block/format block) (:block/uuid block))))
  69. state)
  70. (rum/defc page-blocks-inner <
  71. {:did-mount open-first-block!
  72. :did-update open-first-block!}
  73. [page-name page-blocks hiccup sidebar? preview?]
  74. [:div.page-blocks-inner
  75. (rum/with-key
  76. (content/content page-name
  77. {:hiccup hiccup
  78. :sidebar? sidebar?})
  79. (str page-name "-hiccup"))])
  80. (declare page)
  81. (defn- get-page-format
  82. [page-name]
  83. (let [block? (util/uuid-string? page-name)
  84. block-id (and block? (uuid page-name))
  85. page (if block-id
  86. (:block/name (:block/page (db/entity [:block/uuid block-id])))
  87. page-name)]
  88. (db/get-page-format page)))
  89. (rum/defc dummy-block
  90. [page-name]
  91. [:div.ls-block.flex-1.flex-col.rounded-sm {:style {:width "100%"}}
  92. [:div.flex.flex-row
  93. [:div.flex.flex-row.items-center.mr-2.ml-1 {:style {:height 24}}
  94. [:span.bullet-container.cursor
  95. [:span.bullet]]]
  96. [:div.flex.flex-1 {:on-click #(editor-handler/insert-first-page-block-if-not-exists! page-name)}
  97. [:span.opacity-50
  98. "Click here to edit..."]]]])
  99. (rum/defc page-blocks-cp < rum/reactive
  100. db-mixins/query
  101. [repo page-e {:keys [sidebar? preview?] :as config}]
  102. (when page-e
  103. (let [page-name (or (:block/name page-e)
  104. (str (:block/uuid page-e)))
  105. page-original-name (or (:block/original-name page-e) page-name)
  106. format (get-page-format page-name)
  107. journal? (db/journal-page? page-name)
  108. block? (util/uuid-string? page-name)
  109. block-id (and block? (uuid page-name))
  110. page-empty? (and (not block?) (db/page-empty? repo (:db/id page-e)))
  111. page-e (if (and page-e (:db/id page-e))
  112. {:db/id (:db/id page-e)}
  113. page-e)
  114. page-blocks (get-blocks repo page-name page-original-name block? block-id)]
  115. (if (empty? page-blocks)
  116. (dummy-block page-name)
  117. (let [document-mode? (state/sub :document/mode?)
  118. hiccup-config (merge
  119. {:id (if block? (str block-id) page-name)
  120. :block? block?
  121. :editor-box editor/box
  122. :page page
  123. :document/mode? document-mode?}
  124. config)
  125. hiccup-config (common-handler/config-with-document-mode hiccup-config)
  126. hiccup (block/->hiccup page-blocks hiccup-config {})]
  127. (page-blocks-inner page-name page-blocks hiccup sidebar? preview?))))))
  128. (defn contents-page
  129. [page]
  130. (when-let [repo (state/get-current-repo)]
  131. (page-blocks-cp repo page {:sidebar? true})))
  132. (rum/defc today-queries < rum/reactive
  133. [repo today? sidebar?]
  134. (when (and today? (not sidebar?))
  135. (let [queries (state/sub [:config repo :default-queries :journals])]
  136. (when (seq queries)
  137. [:div#today-queries.mt-10
  138. (for [{:keys [title] :as query} queries]
  139. (rum/with-key
  140. (block/custom-query {:attr {:class "mt-10"}
  141. :editor-box editor/box
  142. :page page} query)
  143. (str repo "-custom-query-" (:query query))))]))))
  144. (defn- delete-page!
  145. [page-name]
  146. (page-handler/delete! page-name
  147. (fn []
  148. (notification/show! (str "Page " page-name " was deleted successfully!")
  149. :success)))
  150. (state/close-modal!)
  151. (route-handler/redirect-to-home!))
  152. (defn delete-page-dialog
  153. [page-name]
  154. (fn [close-fn]
  155. (rum/with-context [[t] i18n/*tongue-context*]
  156. [:div
  157. [:div.sm:flex.sm:items-start
  158. [: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
  159. [:svg.h-6.w-6.text-red-600
  160. {:stroke "currentColor", :view-box "0 0 24 24", :fill "none"}
  161. [:path
  162. {:d
  163. "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
  164. :stroke-width "2"
  165. :stroke-linejoin "round"
  166. :stroke-linecap "round"}]]]
  167. [:div.mt-3.text-center.sm:mt-0.sm:ml-4.sm:text-left
  168. [:h3#modal-headline.text-lg.leading-6.font-medium
  169. (t :page/delete-confirmation)]]]
  170. [:div.mt-5.sm:mt-4.sm:flex.sm:flex-row-reverse
  171. [:span.flex.w-full.rounded-md.shadow-sm.sm:ml-3.sm:w-auto
  172. [:button.inline-flex.justify-center.w-full.rounded-md.border.border-transparent.px-4.py-2.bg-indigo-600.text-base.leading-6.font-medium.text-white.shadow-sm.hover:bg-indigo-500.focus:outline-none.focus:border-indigo-700.focus:shadow-outline-indigo.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
  173. {:type "button"
  174. :class "ui__modal-enter"
  175. :on-click (fn []
  176. (delete-page! page-name))}
  177. (t :yes)]]
  178. [:span.mt-3.flex.w-full.rounded-md.shadow-sm.sm:mt-0.sm:w-auto
  179. [:button.inline-flex.justify-center.w-full.rounded-md.border.border-gray-300.px-4.py-2.bg-white.text-base.leading-6.font-medium.text-gray-700.shadow-sm.hover:text-gray-500.focus:outline-none.focus:border-blue-300.focus:shadow-outline-blue.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
  180. {:type "button"
  181. :on-click close-fn}
  182. (t :cancel)]]]])))
  183. (rum/defcs rename-page-dialog-inner <
  184. (shortcut/disable-all-shortcuts)
  185. (rum/local "" ::input)
  186. [state title page-name close-fn]
  187. (let [input (get state ::input)]
  188. (rum/with-context [[t] i18n/*tongue-context*]
  189. [:div.w-full.sm:max-w-lg.sm:w-96
  190. [:div.sm:flex.sm:items-start
  191. [:div.mt-3.text-center.sm:mt-0.sm:text-left
  192. [:h3#modal-headline.text-lg.leading-6.font-medium
  193. (t :page/rename-to title)]]]
  194. [:input.form-input.block.w-full.sm:text-sm.sm:leading-5.my-2
  195. {:auto-focus true
  196. :default-value title
  197. :on-change (fn [e]
  198. (reset! input (util/evalue e)))}]
  199. [:div.mt-5.sm:mt-4.sm:flex.sm:flex-row-reverse
  200. [:span.flex.w-full.rounded-md.shadow-sm.sm:ml-3.sm:w-auto
  201. [:button.inline-flex.justify-center.w-full.rounded-md.border.border-transparent.px-4.py-2.bg-indigo-600.text-base.leading-6.font-medium.text-white.shadow-sm.hover:bg-indigo-500.focus:outline-none.focus:border-indigo-700.focus:shadow-outline-indigo.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
  202. {:type "button"
  203. :class "ui__modal-enter"
  204. :on-click (fn []
  205. (let [value (string/trim @input)]
  206. (when-not (string/blank? value)
  207. (page-handler/rename! page-name value)
  208. (state/close-modal!))))}
  209. (t :submit)]]
  210. [:span.mt-3.flex.w-full.rounded-md.shadow-sm.sm:mt-0.sm:w-auto
  211. [:button.inline-flex.justify-center.w-full.rounded-md.border.border-gray-300.px-4.py-2.bg-white.text-base.leading-6.font-medium.text-gray-700.shadow-sm.hover:text-gray-500.focus:outline-none.focus:border-blue-300.focus:shadow-outline-blue.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
  212. {:type "button"
  213. :on-click close-fn}
  214. (t :cancel)]]]])))
  215. (defn rename-page-dialog
  216. [title page-name]
  217. (fn [close-fn]
  218. (rename-page-dialog-inner title page-name close-fn)))
  219. (defn tagged-pages
  220. [repo tag]
  221. (let [pages (db/get-tag-pages repo tag)]
  222. (when (seq pages)
  223. [:div.references.mt-6.flex-1.flex-row
  224. [:div.content
  225. (ui/foldable
  226. [:h2.font-bold.opacity-50 (util/format "Pages tagged with \"%s\"" tag)]
  227. [:ul.mt-2
  228. (for [[original-name name] pages]
  229. [:li {:key (str "tagged-page-" name)}
  230. [:a {:href (rfe/href :page {:name name})}
  231. original-name]])] false)]])))
  232. ;; A page is just a logical block
  233. (rum/defcs page < rum/reactive
  234. [state {:keys [repo page-name preview?] :as option}]
  235. (when-let [path-page-name (or page-name
  236. (get-page-name state)
  237. (state/get-current-page))]
  238. (let [current-repo (state/sub :git/current-repo)
  239. repo (or repo current-repo)
  240. page-name (string/lower-case path-page-name)
  241. block? (util/uuid-string? page-name)
  242. block-id (and block? (uuid page-name))
  243. format (let [page (if block-id
  244. (:block/name (:block/page (db/entity [:block/uuid block-id])))
  245. page-name)]
  246. (db/get-page-format page))
  247. journal? (db/journal-page? page-name)
  248. sidebar? (:sidebar? option)]
  249. (rum/with-context [[t] i18n/*tongue-context*]
  250. (let [route-page-name path-page-name
  251. page (if block?
  252. (->> (:db/id (:block/page (db/entity repo [:block/uuid block-id])))
  253. (db/entity repo))
  254. (do
  255. (when-not (db/entity repo [:block/name page-name])
  256. (db/transact! repo [{:block/name page-name
  257. :block/original-name path-page-name
  258. :block/uuid (db/new-block-id)}]))
  259. (db/pull [:block/name page-name])))
  260. {:keys [title] :as properties} (:block/properties page)
  261. page-name (:block/name page)
  262. page-original-name (:block/original-name page)
  263. title (or title page-original-name page-name)
  264. today? (and
  265. journal?
  266. (= page-name (string/lower-case (date/journal-name))))
  267. developer-mode? (state/sub [:ui/developer-mode?])
  268. public? (true? (:public properties))]
  269. [:div.flex-1.page.relative (if (seq (:block/tags page))
  270. (let [page-names (model/get-page-names-by-ids (map :db/id (:block/tags page)))]
  271. {:data-page-tags (text/build-data-value page-names)})
  272. {})
  273. [:div.relative
  274. (when (and (not sidebar?)
  275. (not block?))
  276. [:div.flex.flex-row.space-between
  277. [:div.flex-1.flex-row
  278. [:a.page-title {:on-click (fn [e]
  279. (.preventDefault e)
  280. (when (gobj/get e "shiftKey")
  281. (when-let [page (db/pull repo '[*] [:block/name page-name])]
  282. (state/sidebar-add-block!
  283. repo
  284. (:db/id page)
  285. :page
  286. {:page page}))))}
  287. [:h1.title {:style {:margin-left -2}}
  288. (if page-original-name
  289. (if (and (string/includes? page-original-name "[[")
  290. (string/includes? page-original-name "]]"))
  291. (let [ast (mldoc/->edn page-original-name (mldoc/default-config format))]
  292. (block/markup-element-cp {} (ffirst ast)))
  293. page-original-name)
  294. (or
  295. page-name
  296. path-page-name))]]]
  297. (when (not config/publishing?)
  298. (let [contents? (= (string/lower-case (str page-name)) "contents")
  299. links (fn [] (->>
  300. [(when-not contents?
  301. {:title (t :page/add-to-favorites)
  302. :options {:on-click (fn [] (page-handler/handle-add-page-to-contents! page-original-name))}})
  303. {:title "Go to presentation mode"
  304. :options {:on-click (fn []
  305. (state/sidebar-add-block!
  306. repo
  307. (:db/id page)
  308. :page-presentation
  309. {:page page}))}}
  310. (when-not contents?
  311. {:title (t :page/rename)
  312. :options {:on-click #(state/set-modal! (rename-page-dialog title page-name))}})
  313. (when-let [file-path (and (util/electron?) (page-handler/get-page-file-path))]
  314. [{:title (t :page/open-in-finder)
  315. :options {:on-click #(js/window.apis.showItemInFolder file-path)}}
  316. {:title (t :page/open-with-default-app)
  317. :options {:on-click #(js/window.apis.openPath file-path)}}])
  318. (when-not contents?
  319. {:title (t :page/delete)
  320. :options {:on-click #(state/set-modal! (delete-page-dialog page-name))}})
  321. (when (state/get-current-page)
  322. {:title (t :export)
  323. :options {:on-click #(state/set-modal! export/export-page)}})
  324. (when (util/electron?)
  325. {:title (t (if public? :page/make-private :page/make-public))
  326. :options {:on-click
  327. (fn []
  328. (page-handler/update-public-attribute!
  329. page-name
  330. (if public? false true))
  331. (state/close-modal!))}})
  332. (when plugin-handler/lsp-enabled?
  333. (for [[_ {:keys [key label] :as cmd} action pid] (state/get-plugins-commands-with-type :page-menu-item)]
  334. {:title label
  335. :options {:on-click #(commands/exec-plugin-simple-command!
  336. pid (assoc cmd :page (state/get-current-page)) action)}}))
  337. (when developer-mode?
  338. {:title "(Dev) Show page data"
  339. :options {:on-click (fn []
  340. (let [page-data (with-out-str (pprint/pprint (db/pull (:db/id page))))]
  341. (println page-data)
  342. (notification/show!
  343. [:div
  344. [:pre.code page-data]
  345. [:br]
  346. (ui/button "Copy to clipboard"
  347. :on-click #(.writeText js/navigator.clipboard page-data))]
  348. :success
  349. false)))}})]
  350. (flatten)
  351. (remove nil?)))]
  352. [:div.flex.flex-row
  353. (when plugin-handler/lsp-enabled?
  354. (plugins/hook-ui-slot :page-head-actions-slotted nil)
  355. (plugins/hook-ui-items :pagebar))
  356. [:a.opacity-60.hover:opacity-100.page-op.mr-1
  357. {:title "Search in current page"
  358. :on-click #(route-handler/go-to-search! :page)}
  359. svg/search]
  360. (ui/dropdown-with-links
  361. (fn [{:keys [toggle-fn]}]
  362. [:a.cp__vertical-menu-button
  363. {:title "More options"
  364. :on-click toggle-fn}
  365. (svg/vertical-dots nil)])
  366. links
  367. {:modal-class (util/hiccup->class
  368. "origin-top-right.absolute.right-0.top-10.mt-2.rounded-md.shadow-lg.whitespace-no-wrap.dropdown-overflow-auto.page-drop-options")
  369. :z-index 1})]))])
  370. [:div
  371. (when (and block? (not sidebar?))
  372. (let [config {:id "block-parent"
  373. :block? true}]
  374. [:div.mb-4
  375. (block/block-parents config repo block-id format)]))
  376. ;; blocks
  377. (let [page (if block?
  378. (db/entity repo [:block/uuid block-id])
  379. page)]
  380. (page-blocks-cp repo page {:sidebar? sidebar?}))]]
  381. (when-not block?
  382. (today-queries repo today? sidebar?))
  383. (tagged-pages repo page-name)
  384. ;; referenced blocks
  385. [:div {:key "page-references"}
  386. (rum/with-key
  387. (reference/references route-page-name false)
  388. (str route-page-name "-refs"))]
  389. (when (text/namespace-page? route-page-name)
  390. (hierarchy/structures route-page-name))
  391. ;; TODO: or we can lazy load them
  392. (when-not sidebar?
  393. [:div {:key "page-unlinked-references"}
  394. (reference/unlinked-references route-page-name)])])))))
  395. (defonce layout (atom [js/window.innerWidth js/window.innerHeight]))
  396. (defonce show-journal? (atom false))
  397. ;; scrollHeight
  398. (rum/defcs graph-filter-section < (rum/local false ::open?)
  399. [state title content {:keys [search-filters]}]
  400. (let [open? (get state ::open?)]
  401. (when (and (seq search-filters) (not @open?))
  402. (reset! open? true))
  403. [:li.relative
  404. [:div
  405. [:button.w-full.px-4.py-2.text-left.focus:outline-none {:on-click #(swap! open? not)}
  406. [:div.flex.items-center.justify-between
  407. title
  408. (if @open? (svg/caret-down) (svg/caret-right))]]
  409. (content open?)]]))
  410. (rum/defc filter-expand-area
  411. [open? content]
  412. [:div.relative.overflow-hidden.transition-all.max-h-0.duration-700
  413. {:style {:max-height (if @open? 400 0)}}
  414. content])
  415. (defonce *n-hops (atom nil))
  416. (defonce *focus-nodes (atom []))
  417. (defonce *graph-reset? (atom false))
  418. (rum/defc graph-filters < rum/reactive
  419. [graph settings n-hops]
  420. (let [{:keys [layout journal? orphan-pages? builtin-pages?]
  421. :or {layout "gForce"
  422. orphan-pages? true}} settings
  423. set-setting! (fn [key value]
  424. (let [new-settings (assoc settings key value)]
  425. (config-handler/set-config! :graph/settings new-settings)))
  426. search-graph-filters (state/sub :search/graph-filters)
  427. focus-nodes (rum/react *focus-nodes)]
  428. (rum/with-context [[t] i18n/*tongue-context*]
  429. [:div.absolute.top-4.right-4.graph-filters
  430. [:div.flex.flex-col
  431. [:div.shadow-xl.rounded-sm
  432. [:ul
  433. (graph-filter-section
  434. [:span.font-medium "Nodes"]
  435. (fn [open?]
  436. (filter-expand-area
  437. open?
  438. [:div
  439. [:p.text-sm.opacity-70.px-4
  440. (let [c1 (count (:nodes graph))
  441. s1 (if (> c1 1) "s" "")
  442. ;; c2 (count (:links graph))
  443. ;; s2 (if (> c2 1) "s" "")
  444. ]
  445. ;; (util/format "%d page%s, %d link%s" c1 s1 c2 s2)
  446. (util/format "%d page%s" c1 s1)
  447. )]
  448. [:div.p-6
  449. ;; [:div.flex.items-center.justify-between.mb-2
  450. ;; [:span "Layout"]
  451. ;; (ui/select
  452. ;; (mapv
  453. ;; (fn [item]
  454. ;; (if (= (:label item) layout)
  455. ;; (assoc item :selected "selected")
  456. ;; item))
  457. ;; [{:label "gForce"}
  458. ;; {:label "dagre"}])
  459. ;; (fn [value]
  460. ;; (set-setting! :layout value))
  461. ;; "graph-layout")]
  462. [:div.flex.items-center.justify-between.mb-2
  463. [:span "Journals"]
  464. ;; FIXME: why it's not aligned well?
  465. [:div.mt-1
  466. (ui/toggle journal?
  467. #(set-setting! :journal? (not journal?))
  468. true)]]
  469. [:div.flex.items-center.justify-between.mb-2
  470. [:span "Orphan pages"]
  471. [:div.mt-1
  472. (ui/toggle orphan-pages?
  473. #(set-setting! :orphan-pages? (not orphan-pages?))
  474. true)]]
  475. [:div.flex.items-center.justify-between.mb-2
  476. [:span "Built-in pages"]
  477. [:div.mt-1
  478. (ui/toggle builtin-pages?
  479. #(set-setting! :builtin-pages? (not builtin-pages?))
  480. true)]]
  481. (when (seq focus-nodes)
  482. [:div.flex.flex-col.mb-2
  483. [:p {:title "N hops from selected nodes"}
  484. "N hops from selected nodes"]
  485. (ui/tippy {:html [:div.pr-3 n-hops]}
  486. (ui/slider (or n-hops 10)
  487. {:min 1
  488. :max 10
  489. :on-change #(reset! *n-hops (int %))}))])
  490. [:a.opacity-70.opacity-100 {:on-click (fn []
  491. (swap! *graph-reset? not)
  492. (reset! *focus-nodes [])
  493. (reset! *n-hops nil)
  494. (state/clear-search-filters!))}
  495. "Reset Graph"]]])))
  496. (graph-filter-section
  497. [:span.font-medium "Search"]
  498. (fn [open?]
  499. (filter-expand-area
  500. open?
  501. [:div.p-6
  502. (if (seq search-graph-filters)
  503. [:div
  504. (for [q search-graph-filters]
  505. [:div.flex.flex-row.justify-between.items-center.mb-2
  506. [:span.font-medium q]
  507. [:a.search-filter-close.opacity-70.opacity-100 {:on-click #(state/remove-search-filter! q)}
  508. svg/close]])
  509. [:a.opacity-70.opacity-100 {:on-click state/clear-search-filters!}
  510. "Clear All"]]
  511. [:a.opacity-70.opacity-100 {:on-click #(route-handler/go-to-search! :graph)}
  512. "Click to search"])]))
  513. {:search-filters search-graph-filters})]]]])))
  514. (defn- graph-register-handlers
  515. [graph focus-nodes n-hops]
  516. (.on graph "nodeClick"
  517. (fn [event node]
  518. (graph/on-click-handler graph node event focus-nodes n-hops))))
  519. (rum/defc global-graph-inner < rum/reactive
  520. [graph settings theme]
  521. (let [[width height] (rum/react layout)
  522. dark? (= theme "dark")
  523. n-hops (rum/react *n-hops)
  524. reset? (rum/react *graph-reset?)
  525. focus-nodes (when n-hops (rum/react *focus-nodes))
  526. graph (if (and (integer? n-hops)
  527. (seq focus-nodes)
  528. (not (:orphan-pages? settings)))
  529. (graph-handler/n-hops graph focus-nodes n-hops)
  530. graph)
  531. graph (update graph :links (fn [links]
  532. (let [nodes (set (map :id (:nodes graph)))]
  533. (remove (fn [link]
  534. (and (not (nodes (:source link)))
  535. (not (nodes (:target link)))))
  536. links))))]
  537. (rum/with-context [[t] i18n/*tongue-context*]
  538. [:div.relative#global-graph
  539. (graph/graph-2d {:nodes (:nodes graph)
  540. :links (:links graph)
  541. :width (- width 24)
  542. :height (- height 48)
  543. :dark? dark?
  544. :register-handlers-fn
  545. (fn [graph]
  546. (graph-register-handlers graph *focus-nodes *n-hops))
  547. :reset? reset?})
  548. (graph-filters graph settings n-hops)])))
  549. (defn- filter-graph-nodes
  550. [nodes filters]
  551. (if (seq filters)
  552. (let [filter-patterns (map #(re-pattern (str "(?i)" (util/regex-escape %))) filters)]
  553. (filter (fn [node] (some #(re-find % (:id node)) filter-patterns)) nodes))
  554. nodes))
  555. (rum/defcs global-graph < rum/reactive
  556. (mixins/event-mixin
  557. (fn [state]
  558. (mixins/listen state js/window "resize"
  559. (fn [e]
  560. (reset! layout [js/window.innerWidth js/window.innerHeight])))))
  561. {:will-mount (fn [state]
  562. (state/set-search-mode! :graph)
  563. state)
  564. :will-unmount (fn [state]
  565. (reset! *n-hops nil)
  566. (reset! *focus-nodes [])
  567. (state/set-search-mode! :global)
  568. state)}
  569. [state]
  570. (let [settings (state/sub-graph-config)
  571. theme (state/sub :ui/theme)
  572. graph (graph-handler/build-global-graph theme settings)
  573. search-graph-filters (state/sub :search/graph-filters)
  574. graph (update graph :nodes #(filter-graph-nodes % search-graph-filters))
  575. reset? (rum/react *graph-reset?)]
  576. (global-graph-inner graph settings theme)))
  577. (rum/defc page-graph < db-mixins/query rum/reactive
  578. []
  579. (let [page (or
  580. (and (= :page (state/sub [:route-match :data :name]))
  581. (state/sub [:route-match :path-params :name]))
  582. (date/today))
  583. theme (:ui/theme @state/state)
  584. dark? (= theme "dark")
  585. graph (if (util/uuid-string? page)
  586. (graph-handler/build-block-graph (uuid page) theme)
  587. (graph-handler/build-page-graph page theme))]
  588. (when (seq (:nodes graph))
  589. [:div.sidebar-item.flex-col
  590. (graph/graph-2d {:nodes (:nodes graph)
  591. :links (:links graph)
  592. :width 600
  593. :height 600
  594. :dark? dark?
  595. :register-handlers-fn
  596. (fn [graph]
  597. (graph-register-handlers graph (atom nil) (atom nil)))})])))
  598. (rum/defc all-pages < rum/reactive
  599. ;; {:did-mount (fn [state]
  600. ;; (let [current-repo (state/sub :git/current-repo)]
  601. ;; (js/setTimeout #(db/remove-orphaned-pages! current-repo) 0))
  602. ;; state)}
  603. []
  604. (let [current-repo (state/sub :git/current-repo)]
  605. (rum/with-context [[t] i18n/*tongue-context*]
  606. [:div.flex-1
  607. [:h1.title (t :all-pages)]
  608. (when current-repo
  609. (let [pages (page-handler/get-pages-with-modified-at current-repo)]
  610. [:table.table-auto
  611. [:thead
  612. [:tr
  613. [:th (t :block/name)]
  614. [:th (t :file/last-modified-at)]]]
  615. [:tbody
  616. (for [page pages]
  617. [:tr {:key page}
  618. [:td [:a {:on-click (fn [e]
  619. (let [repo (state/get-current-repo)
  620. page (db/pull repo '[*] [:block/name (string/lower-case page)])]
  621. (when (gobj/get e "shiftKey")
  622. (state/sidebar-add-block!
  623. repo
  624. (:db/id page)
  625. :page
  626. {:page page}))))
  627. :href (rfe/href :page {:name page})}
  628. page]]
  629. [:td [:span.text-gray-500.text-sm
  630. (t :file/no-data)]]])]]))])))