ui.cljs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. (ns ^:no-doc frontend.handler.ui
  2. (:require [clojure.string :as string]
  3. [dommy.core :as dom]
  4. [electron.ipc :as ipc]
  5. [frontend.config :as config]
  6. [frontend.db :as db]
  7. [frontend.db.model :as db-model]
  8. [frontend.db.react :as react]
  9. [frontend.handler.assets :as assets-handler]
  10. [frontend.loader :refer [load]]
  11. [frontend.state :as state]
  12. [frontend.storage :as storage]
  13. [frontend.util :as util]
  14. [goog.dom :as gdom]
  15. [goog.object :as gobj]
  16. [logseq.shui.dialog.core :as shui-dialog]
  17. [logseq.shui.ui :as shui]
  18. [promesa.core :as p]
  19. [rum.core :as rum]))
  20. ;; sidebars
  21. (def *right-sidebar-resized-at (atom (js/Date.now)))
  22. (defn persist-right-sidebar-width!
  23. [width]
  24. (state/set-state! :ui/sidebar-width width)
  25. (storage/set "ls-right-sidebar-width" width))
  26. (defn restore-right-sidebar-width!
  27. []
  28. (when-let [width (storage/get "ls-right-sidebar-width")]
  29. (state/set-state! :ui/sidebar-width width)))
  30. (defn close-left-sidebar!
  31. []
  32. (when-let [elem (gdom/getElement "close-left-bar")]
  33. (.click elem)))
  34. (defn toggle-right-sidebar!
  35. []
  36. (when-not (:ui/sidebar-open? @state/state) (restore-right-sidebar-width!))
  37. (state/toggle-sidebar-open?!))
  38. (defn persist-right-sidebar-state!
  39. []
  40. (let [sidebar-open? (:ui/sidebar-open? @state/state)
  41. data (if sidebar-open? {:blocks (:sidebar/blocks @state/state)
  42. :collapsed (:ui/sidebar-collapsed-blocks @state/state)
  43. :open? true} {:open? false})]
  44. (storage/set "ls-right-sidebar-state" data)))
  45. (defn restore-right-sidebar-state!
  46. []
  47. (when-let [data' (storage/get "ls-right-sidebar-state")]
  48. (let [{:keys [open? collapsed blocks]} data']
  49. (when open?
  50. (state/set-state! :ui/sidebar-open? open?)
  51. (state/set-state! :sidebar/blocks blocks)
  52. (state/set-state! :ui/sidebar-collapsed-blocks collapsed)
  53. (restore-right-sidebar-width!)))))
  54. (defn toggle-contents!
  55. []
  56. (when-let [current-repo (state/get-current-repo)]
  57. (let [id "contents"]
  58. (if (state/sidebar-block-exists? id)
  59. (state/sidebar-remove-block! id)
  60. (state/sidebar-add-block! current-repo id :contents)))))
  61. (defn toggle-help!
  62. []
  63. (state/toggle! :ui/help-open?))
  64. (defn toggle-settings-modal!
  65. []
  66. (when-not (:srs/mode? @state/state)
  67. (state/toggle-settings!)))
  68. (defn re-render-root!
  69. ([]
  70. (re-render-root! {}))
  71. ([{:keys [clear-query-state?]
  72. :or {clear-query-state? true}}]
  73. {:post [(nil? %)]}
  74. (when clear-query-state?
  75. (react/clear-query-state!))
  76. (doseq [component (keys @react/component->query-key)]
  77. (rum/request-render component))
  78. (when-let [component (state/get-root-component)]
  79. (rum/request-render component))
  80. nil))
  81. (defn highlight-element!
  82. [fragment]
  83. (let [id (and
  84. (> (count fragment) 36)
  85. (subs fragment (- (count fragment) 36)))]
  86. (if (and id (util/uuid-string? id))
  87. (let [elements (util/get-blocks-by-id id)]
  88. (when (first elements)
  89. (util/scroll-to-element (gobj/get (first elements) "id")))
  90. (state/exit-editing-and-set-selected-blocks! elements))
  91. (when-let [element (gdom/getElement fragment)]
  92. (util/scroll-to-element fragment)
  93. (dom/add-class! element "block-highlight")
  94. (js/setTimeout #(dom/remove-class! element "block-highlight")
  95. 4000)))))
  96. (defn add-style-if-exists!
  97. []
  98. (when-let [style (or (state/get-custom-css-link)
  99. (db-model/get-custom-css))]
  100. (p/let [style (assets-handler/<expand-assets-links-for-db-graph style)]
  101. (util/add-style! style))))
  102. (defn reset-custom-css!
  103. []
  104. (when-let [el-style (gdom/getElement "logseq-custom-theme-id")]
  105. (dom/remove! el-style))
  106. (add-style-if-exists!))
  107. (def *js-execed (atom #{}))
  108. (defn exec-js-if-exists-&-allowed!
  109. [t]
  110. (when-let [href (or
  111. (state/get-custom-js-link)
  112. (config/get-custom-js-path))]
  113. (let [k (str "ls-js-allowed-" href)
  114. execed #(swap! *js-execed conj href)
  115. execed? (contains? @*js-execed href)
  116. ask-allow #(let [r (js/confirm (t :plugin/custom-js-alert))]
  117. (if r
  118. (storage/set k (js/Date.now))
  119. (storage/set k false))
  120. r)
  121. allowed! (storage/get k)
  122. should-ask? (or (nil? allowed!)
  123. (> (- (js/Date.now) allowed!) 604800000))
  124. exec-fn #(when-let [scripts (and % (string/trim %))]
  125. (when-not (string/blank? scripts)
  126. (when (or (not should-ask?) (ask-allow))
  127. (try
  128. (js/eval scripts)
  129. (execed)
  130. (catch :default e
  131. (js/console.error "[custom js]" e))))))]
  132. (when (and (not execed?)
  133. (not= false allowed!))
  134. (cond
  135. (string/starts-with? href "http")
  136. (when (or (not should-ask?)
  137. (ask-allow))
  138. (load href #(do (js/console.log "[custom js]" href) (execed))))
  139. :else
  140. (when-let [script (db/get-file href)]
  141. (exec-fn script)))))))
  142. (defn toggle-wide-mode!
  143. []
  144. (storage/set :ui/wide-mode (not (state/get-wide-mode?)))
  145. (state/toggle-wide-mode!))
  146. ;; auto-complete
  147. (defn- reorder-matched
  148. "Reorder matched if grouped"
  149. [state]
  150. (let [[matched {:keys [grouped?]}] (:rum/args state)]
  151. (if grouped?
  152. (let [*idx (atom -1)
  153. inc-idx #(swap! *idx inc)]
  154. (->>
  155. (for [[_group matched] (group-by :group matched)]
  156. (doall (map (fn [item] (inc-idx) item) matched)))
  157. (apply concat)))
  158. matched)))
  159. (defn auto-complete-prev
  160. [state e]
  161. (let [current-idx (get state :frontend.ui/current-idx)
  162. matched (reorder-matched state)]
  163. (util/stop e)
  164. (cond
  165. (>= @current-idx 1)
  166. (swap! current-idx dec)
  167. (= @current-idx 0)
  168. (reset! current-idx (dec (count matched)))
  169. :else nil)
  170. (when-let [element (gdom/getElement (str "ac-" @current-idx))]
  171. (let [modal (gobj/get (gdom/getElement "ui__ac") "parentElement")
  172. height (or (gobj/get modal "offsetHeight") 300)
  173. scroll-top (- (gobj/get element "offsetTop") (/ height 2))]
  174. (set! (.-scrollTop modal) scroll-top)))))
  175. (defn auto-complete-next
  176. [state e]
  177. (let [current-idx (get state :frontend.ui/current-idx)
  178. matched (reorder-matched state)]
  179. (util/stop e)
  180. (let [total (count matched)]
  181. (if (>= @current-idx (dec total))
  182. (reset! current-idx 0)
  183. (swap! current-idx inc)))
  184. (when-let [element (gdom/getElement (str "ac-" @current-idx))]
  185. (let [modal (gobj/get (gdom/getElement "ui__ac") "parentElement")
  186. height (or (gobj/get modal "offsetHeight") 300)
  187. scroll-top (- (gobj/get element "offsetTop") (/ height 2))]
  188. (set! (.-scrollTop modal) scroll-top)))))
  189. (defn auto-complete-complete
  190. [state e]
  191. (let [[_matched {:keys [on-chosen on-enter]}] (:rum/args state)
  192. matched (reorder-matched state)
  193. current-idx (get state :frontend.ui/current-idx)]
  194. (util/stop e)
  195. (if (and (seq matched)
  196. (> (count matched)
  197. @current-idx))
  198. (on-chosen (nth matched @current-idx) e)
  199. (and on-enter (on-enter state)))))
  200. (defn auto-complete-shift-complete
  201. [state e]
  202. (let [[_matched {:keys [on-chosen on-shift-chosen on-enter]}] (:rum/args state)
  203. matched (reorder-matched state)
  204. current-idx (get state :frontend.ui/current-idx)]
  205. (util/stop e)
  206. (if (and (seq matched)
  207. (> (count matched)
  208. @current-idx))
  209. ((or on-shift-chosen on-chosen) (nth matched @current-idx) false)
  210. (and on-enter (on-enter state)))))
  211. (defn toggle-cards!
  212. []
  213. (if (shui-dialog/get-modal :srs)
  214. (shui/dialog-close!)
  215. (state/pub-event! [:modal/show-cards])))
  216. (defn open-new-window-or-tab!
  217. "Open a new Electron window."
  218. [target-repo]
  219. (when target-repo
  220. (if (util/electron?)
  221. (ipc/ipc "openNewWindow" target-repo)
  222. (js/window.open (str config/app-website "#/?graph=" target-repo) "_blank"))))
  223. (defn toggle-show-empty-hidden-properties!
  224. []
  225. (let [editing-block (state/get-edit-block)
  226. selected-ids (state/get-selection-block-ids)
  227. block-ids (if editing-block
  228. (conj selected-ids (:block/uuid editing-block))
  229. selected-ids)
  230. *state (:ui/show-empty-and-hidden-properties? @state/state)
  231. {:keys [ids mode show?]} @*state]
  232. (if (seq block-ids)
  233. (let [block-ids' (set block-ids)]
  234. (reset! *state
  235. {:mode :block
  236. :ids block-ids'
  237. :show? (cond
  238. (= mode :global)
  239. true
  240. (not= ids block-ids')
  241. true
  242. :else
  243. (not show?))}))
  244. (reset! *state
  245. {:mode :global
  246. :show? (if (= mode :block)
  247. true
  248. (not show?))}))))
  249. (defn scroll-to-anchor-block
  250. [^js ref blocks gallery?]
  251. (when ref
  252. (let [anchor (get-in (state/get-route-match) [:query-params :anchor])
  253. anchor-id (when (and anchor (string/starts-with? anchor "ls-block-"))
  254. (let [id (subs anchor 9)]
  255. (when (util/uuid-string? id)
  256. (uuid id))))]
  257. (when (and ref anchor-id)
  258. (let [block-ids (map :block/uuid blocks)
  259. find-idx (fn [anchor-id]
  260. (let [idx (.indexOf block-ids anchor-id)]
  261. (when (pos? idx) idx)))
  262. idx (or (find-idx anchor-id)
  263. (let [block (db/entity [:block/uuid anchor-id])
  264. parents (map :block/uuid (db/get-block-parents (state/get-current-repo) (:block/uuid block) {}))]
  265. (some find-idx parents)))]
  266. (when idx
  267. (js/setTimeout
  268. (fn []
  269. (.scrollToIndex ref #js {:index idx})
  270. ;; wait until this block has been rendered.
  271. (js/setTimeout #(highlight-element! anchor) 200))
  272. ;; BUG: grid scrollToIndex not working in useEffect on first render
  273. ;; https://github.com/petyosi/react-virtuoso/issues/757
  274. (if gallery? 100 0))))))))