events.cljs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. (ns frontend.handler.events
  2. (:refer-clojure :exclude [run!])
  3. (:require [clojure.core.async :as async]
  4. [clojure.set :as set]
  5. [frontend.context.i18n :refer [t]]
  6. [frontend.components.diff :as diff]
  7. [frontend.handler.plugin :as plugin-handler]
  8. [frontend.fs.capacitor-fs :as capacitor-fs]
  9. [frontend.components.plugins :as plugin]
  10. [frontend.components.git :as git-component]
  11. [frontend.components.shell :as shell]
  12. [frontend.components.search :as search]
  13. [frontend.config :as config]
  14. [frontend.db :as db]
  15. [frontend.db-schema :as db-schema]
  16. [frontend.extensions.srs :as srs]
  17. [frontend.fs.nfs :as nfs]
  18. [frontend.fs.watcher-handler :as fs-watcher]
  19. [frontend.handler.common :as common-handler]
  20. [frontend.handler.editor :as editor-handler]
  21. [frontend.handler.notification :as notification]
  22. [frontend.handler.page :as page-handler]
  23. [frontend.handler.search :as search-handler]
  24. [frontend.handler.ui :as ui-handler]
  25. [frontend.handler.repo :as repo-handler]
  26. [frontend.handler.file :as file-handler]
  27. [frontend.handler.route :as route-handler]
  28. [frontend.handler.web.nfs :as nfs-handler]
  29. [frontend.modules.shortcut.core :as st]
  30. [frontend.modules.outliner.file :as outliner-file]
  31. [frontend.commands :as commands]
  32. [frontend.state :as state]
  33. [frontend.ui :as ui]
  34. [frontend.util :as util]
  35. [rum.core :as rum]
  36. [frontend.modules.instrumentation.posthog :as posthog]
  37. [frontend.mobile.util :as mobile-util]
  38. [promesa.core :as p]
  39. [frontend.fs :as fs]
  40. [clojure.string :as string]
  41. [frontend.util.persist-var :as persist-var]
  42. [frontend.fs.sync :as sync]
  43. [frontend.components.encryption :as encryption]
  44. [frontend.encrypt :as encrypt]))
  45. ;; TODO: should we move all events here?
  46. (defmulti handle first)
  47. (defmethod handle :graph/added [[_ repo {:keys [empty-graph?]}]]
  48. (db/set-key-value repo :ast/version db-schema/ast-version)
  49. (search-handler/rebuild-indices!)
  50. (db/persist! repo)
  51. (when (state/setups-picker?)
  52. (if empty-graph?
  53. (route-handler/redirect! {:to :import :query-params {:from "picker"}})
  54. (route-handler/redirect-to-home!))))
  55. (defn- file-sync-stop-when-switch-graph []
  56. (p/do! (persist-var/load-vars)
  57. (sync/sync-stop)
  58. (sync/sync-start)
  59. ))
  60. (defn- graph-switch [graph]
  61. (state/set-current-repo! graph)
  62. ;; load config
  63. (common-handler/reset-config! graph nil)
  64. (st/refresh!)
  65. (when-not (= :draw (state/get-current-route))
  66. (route-handler/redirect-to-home!))
  67. (when-let [dir-name (config/get-repo-dir graph)]
  68. (fs/watch-dir! dir-name))
  69. (srs/update-cards-due-count!)
  70. (state/pub-event! [:graph/ready graph])
  71. (file-sync-stop-when-switch-graph))
  72. (def persist-db-noti-m
  73. {:before #(notification/show!
  74. (ui/loading (t :graph/persist))
  75. :warning)
  76. :on-error #(notification/show!
  77. (t :graph/persist-error)
  78. :error)})
  79. (defn- graph-switch-on-persisted
  80. "Logic for keeping db sync when switching graphs
  81. Only works for electron"
  82. [graph]
  83. (let [current-repo (state/get-current-repo)]
  84. (p/do!
  85. (when (util/electron?)
  86. (p/do!
  87. (repo-handler/persist-db! current-repo persist-db-noti-m)
  88. (repo-handler/broadcast-persist-db! graph)))
  89. (repo-handler/restore-and-setup-repo! graph)
  90. (graph-switch graph))))
  91. (defmethod handle :graph/switch [[_ graph]]
  92. (if (outliner-file/writes-finished?)
  93. (graph-switch-on-persisted graph)
  94. (notification/show!
  95. "Please wait seconds until all changes are saved for the current graph."
  96. :warning)))
  97. (defmethod handle :graph/open-new-window [[ev repo]]
  98. (p/let [current-repo (state/get-current-repo)
  99. target-repo (or repo current-repo)
  100. _ (repo-handler/persist-db! current-repo persist-db-noti-m) ;; FIXME: redundant when opening non-current-graph window
  101. _ (when-not (= current-repo target-repo)
  102. (repo-handler/broadcast-persist-db! repo))]
  103. (ui-handler/open-new-window! ev repo)))
  104. (defmethod handle :graph/migrated [[_ _repo]]
  105. (js/alert "Graph migrated."))
  106. (defmethod handle :graph/save [_]
  107. (repo-handler/persist-db! (state/get-current-repo)
  108. {:before #(notification/show!
  109. (ui/loading (t :graph/save))
  110. :warning)
  111. :on-success #(notification/show!
  112. (ui/loading (t :graph/save-success))
  113. :warning)
  114. :on-error #(notification/show!
  115. (t :graph/save-error)
  116. :error)}))
  117. (defn get-local-repo
  118. []
  119. (when-let [repo (state/get-current-repo)]
  120. (when (config/local-db? repo)
  121. repo)))
  122. (defn ask-permission
  123. [repo]
  124. (when
  125. (and (not (util/electron?))
  126. (not (mobile-util/is-native-platform?)))
  127. (fn [close-fn]
  128. [:div
  129. [:p
  130. "Grant native filesystem permission for directory: "
  131. [:b (config/get-local-dir repo)]]
  132. (ui/button
  133. "Grant"
  134. :class "ui__modal-enter"
  135. :on-click (fn []
  136. (nfs/check-directory-permission! repo)
  137. (close-fn)))])))
  138. (defmethod handle :modal/nfs-ask-permission []
  139. (when-let [repo (get-local-repo)]
  140. (state/set-modal! (ask-permission repo))))
  141. (defonce *query-properties (atom {}))
  142. (rum/defc query-properties-settings-inner < rum/reactive
  143. {:will-unmount (fn [state]
  144. (reset! *query-properties {})
  145. state)}
  146. [block shown-properties all-properties _close-fn]
  147. (let [query-properties (rum/react *query-properties)]
  148. [:div.p-4
  149. [:div.font-bold "Properties settings for this query:"]
  150. (for [property all-properties]
  151. (let [property-value (get query-properties property)
  152. shown? (if (nil? property-value)
  153. (contains? shown-properties property)
  154. property-value)]
  155. [:div.flex.flex-row.m-2.justify-between.align-items
  156. [:div (name property)]
  157. [:div.mt-1 (ui/toggle shown?
  158. (fn []
  159. (let [value (not shown?)]
  160. (swap! *query-properties assoc property value)
  161. (editor-handler/set-block-query-properties!
  162. (:block/uuid block)
  163. all-properties
  164. property
  165. value)))
  166. true)]]))]))
  167. (defn query-properties-settings
  168. [block shown-properties all-properties]
  169. (fn [close-fn]
  170. (query-properties-settings-inner block shown-properties all-properties close-fn)))
  171. (defmethod handle :modal/set-query-properties [[_ block all-properties]]
  172. (let [block-properties (some-> (get-in block [:block/properties :query-properties])
  173. (common-handler/safe-read-string "Parsing query properties failed"))
  174. shown-properties (if (seq block-properties)
  175. (set block-properties)
  176. (set all-properties))
  177. shown-properties (set/intersection (set all-properties) shown-properties)]
  178. (state/set-modal! (query-properties-settings block shown-properties all-properties))))
  179. (defmethod handle :modal/show-cards [_]
  180. (state/set-modal! srs/global-cards {:id :srs
  181. :label "flashcards__cp"}))
  182. (defmethod handle :modal/show-instruction [_]
  183. (state/set-modal! capacitor-fs/instruction {:id :instruction
  184. :label "instruction__cp"}))
  185. (defmethod handle :modal/show-themes-modal [_]
  186. (plugin/open-select-theme!))
  187. (rum/defc modal-output
  188. [content]
  189. content)
  190. (defmethod handle :modal/show [[_ content]]
  191. (state/set-modal! #(modal-output content)))
  192. (defmethod handle :modal/set-git-username-and-email [[_ _content]]
  193. (state/set-modal! git-component/set-git-username-and-email))
  194. (defmethod handle :page/title-property-changed [[_ old-title new-title]]
  195. (page-handler/rename! old-title new-title))
  196. (defmethod handle :page/create [[_ page-name opts]]
  197. (page-handler/create! page-name opts))
  198. (defmethod handle :page/create-today-journal [[_ _repo]]
  199. (p/let [_ (page-handler/create-today-journal!)]
  200. (ui-handler/re-render-root!)))
  201. (defmethod handle :file/not-matched-from-disk [[_ path disk-content db-content]]
  202. (state/clear-edit!)
  203. (when-let [repo (state/get-current-repo)]
  204. (when (and disk-content db-content
  205. (not= (util/trim-safe disk-content) (util/trim-safe db-content)))
  206. (state/set-modal! #(diff/local-file repo path disk-content db-content)
  207. {:label "diff__cp"}))))
  208. (defmethod handle :modal/display-file-version [[_ path content hash]]
  209. (p/let [content (when content (encrypt/decrypt content))]
  210. (state/set-modal! #(git-component/file-specific-version path hash content))))
  211. (defmethod handle :graph/ready [[_ repo]]
  212. (search-handler/rebuild-indices-when-stale! repo)
  213. (repo-handler/graph-ready! repo))
  214. (defmethod handle :notification/show [[_ {:keys [content status clear?]}]]
  215. (notification/show! content status clear?))
  216. (defmethod handle :command/run [_]
  217. (when (util/electron?)
  218. (state/set-modal! shell/shell)))
  219. (defmethod handle :go/search [_]
  220. (state/set-modal! search/search-modal
  221. {:fullscreen? false
  222. :close-btn? false}))
  223. (defmethod handle :go/plugins [_]
  224. (plugin/open-plugins-modal!))
  225. (defmethod handle :go/plugins-waiting-lists [_]
  226. (plugin/open-waiting-updates-modal!))
  227. (defmethod handle :go/plugins-settings [[_ pid nav? title]]
  228. (if pid
  229. (do
  230. (state/set-state! :plugin/focused-settings pid)
  231. (state/set-state! :plugin/navs-settings? (not (false? nav?)))
  232. (plugin/open-focused-settings-modal! title))
  233. (state/close-sub-modal! "ls-focused-settings-modal")))
  234. (defmethod handle :go/proxy-settings [[_ agent-opts]]
  235. (state/set-sub-modal!
  236. (fn [_] (plugin/user-proxy-settings-panel agent-opts))
  237. {:id :https-proxy-panel :center? true}))
  238. (defmethod handle :redirect-to-home [_]
  239. (page-handler/create-today-journal!))
  240. (defmethod handle :instrument [[_ {:keys [type payload]}]]
  241. (posthog/capture type payload))
  242. (defmethod handle :exec-plugin-cmd [[_ {:keys [pid cmd action]}]]
  243. (commands/exec-plugin-simple-command! pid cmd action))
  244. (defmethod handle :shortcut-handler-refreshed [[_]]
  245. (when-not @st/*inited?
  246. (reset! st/*inited? true)
  247. (st/consume-pending-shortcuts!)))
  248. (defmethod handle :mobile/keyboard-will-show [[_]]
  249. (when (and (state/get-left-sidebar-open?)
  250. (state/editing?))
  251. (state/set-left-sidebar-open! false)))
  252. (defmethod handle :mobile/keyboard-did-show [[_]]
  253. (when-let [input (state/get-input)]
  254. (util/make-el-cursor-position-into-center-viewport input)))
  255. (defmethod handle :plugin/consume-updates [[_ id pending? updated?]]
  256. (let [downloading? (:plugin/updates-downloading? @state/state)]
  257. (when-let [coming (and (not downloading?)
  258. (get-in @state/state [:plugin/updates-coming id]))]
  259. (let [error-code (:error-code coming)
  260. error-code (if (= error-code (str :no-new-version)) nil error-code)]
  261. (when (or pending? (not error-code))
  262. (notification/show!
  263. (str "[Checked]<" (:title coming) "> " error-code)
  264. (if error-code :error :success)))))
  265. (if (and updated? downloading?)
  266. ;; try to start consume downloading item
  267. (if-let [n (state/get-next-selected-coming-update)]
  268. (plugin-handler/check-or-update-marketplace-plugin
  269. (assoc n :only-check false :error-code nil)
  270. (fn [^js e] (js/console.error "[Download Err]" n e)))
  271. (plugin-handler/close-updates-downloading))
  272. ;; try to start consume pending item
  273. (if-let [n (second (first (:plugin/updates-pending @state/state)))]
  274. (plugin-handler/check-or-update-marketplace-plugin
  275. (assoc n :only-check true :error-code nil)
  276. (fn [^js e]
  277. (notification/show! (.toString e) :error)
  278. (js/console.error "[Check Err]" n e)))
  279. ;; try to open waiting updates list
  280. (when (and pending? (seq (state/all-available-coming-updates)))
  281. (plugin/open-waiting-updates-modal!))))))
  282. (defmethod handle :plugin/hook-db-tx [[_ {:keys [blocks tx-data tx-meta] :as payload}]]
  283. (when-let [payload (and (seq blocks)
  284. (merge payload {:tx-data (map #(into [] %) tx-data)
  285. :tx-meta (dissoc tx-meta :editor-cursor)}))]
  286. (plugin-handler/hook-plugin-db :changed payload)
  287. (plugin-handler/hook-plugin-block-changes payload)))
  288. (defmethod handle :backup/broken-config [[_ repo content]]
  289. (when (and repo content)
  290. (let [path (config/get-config-path)
  291. broken-path (string/replace path "/config.edn" "/broken-config.edn")]
  292. (p/let [_ (fs/write-file! repo (config/get-repo-dir repo) broken-path content {})
  293. _ (file-handler/alter-file repo path config/config-default-content {:skip-compare? true})]
  294. (notification/show!
  295. [:p.content
  296. "It seems that your config.edn is broken. We've restored it with the default content and saved the previous content to the file logseq/broken-config.edn."]
  297. :warning
  298. false)))))
  299. (defmethod handle :file-watcher/changed [[_ ^js event]]
  300. (let [type (.-event event)
  301. payload (-> event
  302. (js->clj :keywordize-keys true)
  303. (update :path js/decodeURI))]
  304. (fs-watcher/handle-changed! type payload)
  305. (sync/file-watch-handler type payload)))
  306. (defmethod handle :rebuild-slash-commands-list [[_]]
  307. (page-handler/rebuild-slash-commands-list!))
  308. (defmethod handle :graph/ask-for-re-index [[_ *multiple-windows?]]
  309. (if (and (util/atom? *multiple-windows?) @*multiple-windows?)
  310. (handle
  311. [:modal/show
  312. [:div
  313. [:p (t :re-index-multiple-windows-warning)]]])
  314. (handle
  315. [:modal/show
  316. [:div {:style {:max-width 700}}
  317. [:p (t :re-index-discard-unsaved-changes-warning)]
  318. (ui/button
  319. (t :yes)
  320. :autoFocus "on"
  321. :large? true
  322. :on-click (fn []
  323. (state/close-modal!)
  324. (repo-handler/re-index!
  325. nfs-handler/rebuild-index!
  326. page-handler/create-today-journal!)))]])))
  327. ;; encryption
  328. (defmethod handle :modal/encryption-setup-dialog [[_ repo-url close-fn]]
  329. (state/set-modal!
  330. (encryption/encryption-setup-dialog repo-url close-fn)))
  331. (defmethod handle :modal/encryption-input-secret-dialog [[_ repo-url db-encrypted-secret close-fn]]
  332. (state/set-modal!
  333. (encryption/encryption-input-secret-dialog
  334. repo-url
  335. db-encrypted-secret
  336. close-fn)))
  337. (defmethod handle :journal/insert-template [[_ page-name]]
  338. (let [page-name (util/page-name-sanity-lc page-name)]
  339. (when-let [page (db/pull [:block/name page-name])]
  340. (when (db/page-empty? (state/get-current-repo) page-name)
  341. (when-let [template (state/get-default-journal-template)]
  342. (editor-handler/insert-template!
  343. nil
  344. template
  345. {:target page}))))))
  346. (defn run!
  347. []
  348. (let [chan (state/get-events-chan)]
  349. (async/go-loop []
  350. (let [payload (async/<! chan)]
  351. (try
  352. (handle payload)
  353. (catch js/Error error
  354. (let [type :handle-system-events/failed]
  355. (js/console.error (str type) (clj->js payload) "\n" error)
  356. (state/pub-event! [:instrument {:type type
  357. :payload payload
  358. :error error}])))))
  359. (recur))
  360. chan))