page.cljs 56 KB

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