ui.cljs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. (ns frontend.handler.ui
  2. (:require [cljs-time.core :refer [plus days weeks]]
  3. [dommy.core :as dom]
  4. [frontend.util :as util]
  5. [frontend.db :as db]
  6. [frontend.db.model :as db-model]
  7. [frontend.config :as config]
  8. [frontend.state :as state]
  9. [frontend.storage :as storage]
  10. [frontend.fs :as fs]
  11. [frontend.loader :refer [load]]
  12. [goog.dom :as gdom]
  13. [goog.object :as gobj]
  14. [clojure.string :as string]
  15. [rum.core :as rum]
  16. [frontend.mobile.util :as mobile]
  17. [logseq.graph-parser.util :as gp-util]
  18. [electron.ipc :as ipc]))
  19. (defn- get-css-var-value
  20. [var-name]
  21. (.getPropertyValue (js/getComputedStyle (.-documentElement js/document)) var-name))
  22. ;; sidebars
  23. (defn- get-right-sidebar-width
  24. []
  25. (or (.. (js/document.getElementById "right-sidebar") -style -width)
  26. (get-css-var-value "--right-sidebar-width")))
  27. (defn persist-right-sidebar-width!
  28. []
  29. (storage/set "ls-right-sidebar-width" (get-right-sidebar-width)))
  30. (defn restore-right-sidebar-width!
  31. []
  32. (when-let [width (storage/get "ls-right-sidebar-width")]
  33. (.setProperty (.-style (js/document.getElementById "right-sidebar")) "width" width)))
  34. (defn close-left-sidebar!
  35. []
  36. (when-let [elem (gdom/getElement "close-left-bar")]
  37. (.click elem)))
  38. (defn toggle-right-sidebar!
  39. []
  40. (when-not (:ui/sidebar-open? @state/state) (restore-right-sidebar-width!))
  41. (state/toggle-sidebar-open?!))
  42. (defn persist-right-sidebar-state!
  43. []
  44. (let [sidebar-open? (:ui/sidebar-open? @state/state)
  45. data (if sidebar-open? {:blocks (:sidebar/blocks @state/state)
  46. :collapsed (:ui/sidebar-collapsed-blocks @state/state)
  47. :open? true} {:open? false})]
  48. (storage/set "ls-right-sidebar-state" data)))
  49. (defn restore-right-sidebar-state!
  50. []
  51. (when-let [data' (storage/get "ls-right-sidebar-state")]
  52. (let [{:keys [open? collapsed blocks]} data']
  53. (when open?
  54. (state/set-state! :ui/sidebar-open? open?)
  55. (state/set-state! :sidebar/blocks blocks)
  56. (state/set-state! :ui/sidebar-collapsed-blocks collapsed)
  57. (restore-right-sidebar-width!)))))
  58. (defn toggle-contents!
  59. []
  60. (when-let [current-repo (state/get-current-repo)]
  61. (let [id "contents"]
  62. (if (state/sidebar-block-exists? id)
  63. (state/sidebar-remove-block! id)
  64. (state/sidebar-add-block! current-repo id :contents)))))
  65. (defn toggle-help!
  66. []
  67. (when-let [current-repo (state/get-current-repo)]
  68. (let [id "help"]
  69. (if (state/sidebar-block-exists? id)
  70. (state/sidebar-remove-block! id)
  71. (state/sidebar-add-block! current-repo id :help)))))
  72. (defn toggle-settings-modal!
  73. []
  74. (when-not (:srs/mode? @state/state)
  75. (state/toggle-settings!)))
  76. ;; FIXME: re-render all embedded blocks since they will not be re-rendered automatically
  77. (defn re-render-root!
  78. ([]
  79. (re-render-root! {}))
  80. ([{:keys [clear-all-query-state?]
  81. :or {clear-all-query-state? false}}]
  82. (when-let [component (state/get-root-component)]
  83. (if clear-all-query-state?
  84. (db/clear-query-state!)
  85. (db/clear-query-state-without-refs-and-embeds!))
  86. (rum/request-render component)
  87. (doseq [component (state/get-custom-query-components)]
  88. (rum/request-render component)))))
  89. (defn re-render-file!
  90. []
  91. (when-let [component (state/get-file-component)]
  92. (when (= :file (state/get-current-route))
  93. (rum/request-render component))))
  94. (defn highlight-element!
  95. [fragment]
  96. (let [id (and
  97. (> (count fragment) 36)
  98. (subs fragment (- (count fragment) 36)))]
  99. (if (and id (gp-util/uuid-string? id))
  100. (let [elements (array-seq (js/document.getElementsByClassName id))]
  101. (when (first elements)
  102. (util/scroll-to-element (gobj/get (first elements) "id")))
  103. (state/exit-editing-and-set-selected-blocks! elements))
  104. (when-let [element (gdom/getElement fragment)]
  105. (util/scroll-to-element fragment)
  106. (dom/add-class! element "block-highlight")
  107. (js/setTimeout #(dom/remove-class! element "block-highlight")
  108. 4000)))))
  109. (defn add-style-if-exists!
  110. []
  111. (when-let [style (or
  112. (state/get-custom-css-link)
  113. (db-model/get-custom-css)
  114. ;; (state/get-custom-css-link)
  115. )]
  116. (util/add-style! style)))
  117. (def *js-execed (atom #{}))
  118. (defn exec-js-if-exists-&-allowed!
  119. [t]
  120. (when-not (mobile/is-native-platform?)
  121. (when-let [href (or
  122. (state/get-custom-js-link)
  123. (config/get-custom-js-path))]
  124. (let [k (str "ls-js-allowed-" href)
  125. execed #(swap! *js-execed conj href)
  126. execed? (contains? @*js-execed href)
  127. ask-allow #(let [r (js/confirm (t :plugin/custom-js-alert))]
  128. (if r
  129. (storage/set k (js/Date.now))
  130. (storage/set k false))
  131. r)
  132. allowed! (storage/get k)
  133. should-ask? (or (nil? allowed!)
  134. (> (- (js/Date.now) allowed!) 604800000))]
  135. (when (and (not execed?)
  136. (not= false allowed!))
  137. (if (string/starts-with? href "http")
  138. (when (or (not should-ask?)
  139. (ask-allow))
  140. (load href #(do (js/console.log "[custom js]" href) (execed))))
  141. (util/p-handle
  142. (fs/read-file (if (util/electron?) "" (config/get-repo-dir (state/get-current-repo))) href)
  143. #(when-let [scripts (and % (string/trim %))]
  144. (when-not (string/blank? scripts)
  145. (when (or (not should-ask?) (ask-allow))
  146. (try
  147. (js/eval scripts)
  148. (execed)
  149. (catch js/Error e
  150. (js/console.error "[custom js]" e)))))))))))))
  151. (defn toggle-wide-mode!
  152. []
  153. (storage/set :ui/wide-mode (not (state/get-wide-mode?)))
  154. (state/toggle-wide-mode!))
  155. ;; auto-complete
  156. (defn auto-complete-prev
  157. [state e]
  158. (let [current-idx (get state :frontend.ui/current-idx)
  159. matched (first (:rum/args state))]
  160. (util/stop e)
  161. (cond
  162. (>= @current-idx 1)
  163. (swap! current-idx dec)
  164. (= @current-idx 0)
  165. (reset! current-idx (dec (count matched)))
  166. :else nil)
  167. (when-let [element (gdom/getElement (str "ac-" @current-idx))]
  168. (let [modal (gobj/get (gdom/getElement "ui__ac") "parentElement")
  169. height (or (gobj/get modal "offsetHeight") 300)
  170. scroll-top (- (gobj/get element "offsetTop") (/ height 2))]
  171. (set! (.-scrollTop modal) scroll-top)))))
  172. (defn auto-complete-next
  173. [state e]
  174. (let [current-idx (get state :frontend.ui/current-idx)
  175. matched (first (:rum/args state))]
  176. (util/stop e)
  177. (let [total (count matched)]
  178. (if (>= @current-idx (dec total))
  179. (reset! current-idx 0)
  180. (swap! current-idx inc)))
  181. (when-let [element (gdom/getElement (str "ac-" @current-idx))]
  182. (let [modal (gobj/get (gdom/getElement "ui__ac") "parentElement")
  183. height (or (gobj/get modal "offsetHeight") 300)
  184. scroll-top (- (gobj/get element "offsetTop") (/ height 2))]
  185. (set! (.-scrollTop modal) scroll-top)))))
  186. (defn auto-complete-complete
  187. [state e]
  188. (let [[matched {:keys [on-chosen on-enter]}] (:rum/args state)
  189. current-idx (get state :frontend.ui/current-idx)]
  190. (util/stop e)
  191. (if (and (seq matched)
  192. (> (count matched)
  193. @current-idx))
  194. (on-chosen (nth matched @current-idx) false)
  195. (and on-enter (on-enter state)))))
  196. (defn auto-complete-shift-complete
  197. [state e]
  198. (let [[matched {:keys [on-chosen on-shift-chosen on-enter]}] (:rum/args state)
  199. current-idx (get state :frontend.ui/current-idx)]
  200. (util/stop e)
  201. (if (and (seq matched)
  202. (> (count matched)
  203. @current-idx))
  204. ((or on-shift-chosen on-chosen) (nth matched @current-idx) false)
  205. (and on-enter (on-enter state)))))
  206. (defn auto-complete-open-link
  207. [state e]
  208. (let [[matched {:keys [on-chosen-open-link]}] (:rum/args state)]
  209. (when (and on-chosen-open-link (not (state/editing?)))
  210. (let [current-idx (get state :frontend.ui/current-idx)]
  211. (util/stop e)
  212. (when (and (seq matched)
  213. (> (count matched)
  214. @current-idx))
  215. (on-chosen-open-link (nth matched @current-idx) false))))))
  216. ;; date-picker
  217. ;; TODO: find a better way
  218. (def *internal-model (rum/cursor state/state :date-picker/date))
  219. (defn- non-edit-input?
  220. []
  221. (when-let [elem js/document.activeElement]
  222. (and (util/input? elem)
  223. (when-let [id (gobj/get elem "id")]
  224. (not (string/starts-with? id "edit-block-"))))))
  225. (defn- input-or-select?
  226. []
  227. (when-let [elem js/document.activeElement]
  228. (or (non-edit-input?)
  229. (util/select? elem))))
  230. (defn- inc-date [date n] (plus date (days n)))
  231. (defn- inc-week [date n] (plus date (weeks n)))
  232. (defn shortcut-complete
  233. [state e]
  234. (let [{:keys [on-change deadline-or-schedule?]} (last (:rum/args state))]
  235. (when (and on-change
  236. (not (input-or-select?)))
  237. (when-not deadline-or-schedule?
  238. (on-change e @*internal-model)))))
  239. (defn shortcut-prev-day
  240. [_state e]
  241. (when-not (input-or-select?)
  242. (util/stop e)
  243. (swap! *internal-model inc-date -1)))
  244. (defn shortcut-next-day
  245. [_state e]
  246. (when-not (input-or-select?)
  247. (util/stop e)
  248. (swap! *internal-model inc-date 1)))
  249. (defn shortcut-prev-week
  250. [_state e]
  251. (when-not (input-or-select?)
  252. (util/stop e)
  253. (swap! *internal-model inc-week -1)))
  254. (defn shortcut-next-week
  255. [_state e]
  256. (when-not (input-or-select?)
  257. (util/stop e)
  258. (swap! *internal-model inc-week 1)))
  259. (defn toggle-cards!
  260. []
  261. (if (:modal/show? @state/state)
  262. (state/close-modal!)
  263. (state/pub-event! [:modal/show-cards])))
  264. (defn open-new-window!
  265. "Open a new Electron window.
  266. No db cache persisting ensured. Should be handled by the caller."
  267. ([_e]
  268. (open-new-window! _e nil))
  269. ([_e repo]
  270. ;; TODO: find out a better way to open a new window with a different repo path. Using local storage for now
  271. ;; TODO: also write local storage with the current repo state, to make behavior consistent
  272. ;; then we can remove the `openNewWindowOfGraph` ipcMain call
  273. (when (string? repo) (storage/set :git/current-repo repo))
  274. (ipc/ipc "openNewWindow")))