diff --git a/src/main/frontend/handler.cljs b/src/main/frontend/handler.cljs index 3fb2b748f3..347387c5ee 100644 --- a/src/main/frontend/handler.cljs +++ b/src/main/frontend/handler.cljs @@ -17,6 +17,7 @@ [frontend.error :as error] [frontend.handler.command-palette :as command-palette] [frontend.handler.events :as events] + [frontend.handler.events.ui] [frontend.handler.file-based.events] [frontend.handler.file-based.file :as file-handler] [frontend.handler.global-config :as global-config-handler] diff --git a/src/main/frontend/handler/events.cljs b/src/main/frontend/handler/events.cljs index f8ab9bb804..786f016f3f 100644 --- a/src/main/frontend/handler/events.cljs +++ b/src/main/frontend/handler/events.cljs @@ -10,24 +10,7 @@ [clojure.core.async.interop :refer [p->c]] [clojure.string :as string] [frontend.commands :as commands] - [frontend.components.block :as block] - [frontend.components.cmdk.core :as cmdk] - [frontend.components.diff :as diff] - [frontend.components.encryption :as encryption] - [frontend.components.file-based.git :as git-component] - [frontend.components.file-sync :as file-sync] - [frontend.components.page :as component-page] - [frontend.components.plugins :as plugin] - [frontend.components.property.dialog :as property-dialog] - [frontend.components.repo :as repo] - [frontend.components.select :as select] - [frontend.components.selection :as selection] - [frontend.components.settings :as settings] - [frontend.components.shell :as shell] - [frontend.components.user.login :as login] - [frontend.components.whiteboard :as whiteboard] [frontend.config :as config] - [frontend.context.i18n :refer [t]] [frontend.date :as date] [frontend.db :as db] [frontend.db.async :as db-async] @@ -36,10 +19,7 @@ [frontend.db.persist :as db-persist] [frontend.db.transact :as db-transact] [frontend.extensions.fsrs :as fsrs] - [frontend.extensions.srs :as srs] [frontend.fs :as fs] - [frontend.fs.capacitor-fs :as capacitor-fs] - [frontend.fs.nfs :as nfs] [frontend.fs.sync :as sync] [frontend.fs.watcher-handler :as fs-watcher] [frontend.handler.assets :as assets-handler] @@ -50,8 +30,6 @@ [frontend.handler.db-based.rtc-flows :as rtc-flows] [frontend.handler.editor :as editor-handler] [frontend.handler.export :as export] - [frontend.handler.file-based.file :as file-handler] - [frontend.handler.file-based.nfs :as nfs-handler] [frontend.handler.file-sync :as file-sync-handler] [frontend.handler.graph :as graph-handler] [frontend.handler.notification :as notification] @@ -63,12 +41,9 @@ [frontend.handler.search :as search-handler] [frontend.handler.shell :as shell-handler] [frontend.handler.ui :as ui-handler] - [frontend.handler.user :as user-handler] [frontend.mobile.core :as mobile] - [frontend.mobile.graph-picker :as graph-picker] [frontend.mobile.util :as mobile-util] [frontend.modules.instrumentation.posthog :as posthog] - [frontend.modules.instrumentation.sentry :as sentry-event] [frontend.modules.outliner.pipeline :as pipeline] [frontend.modules.outliner.ui :as ui-outliner-tx] [frontend.modules.shortcut.core :as st] @@ -76,16 +51,11 @@ [frontend.quick-capture :as quick-capture] [frontend.search :as search] [frontend.state :as state] - [frontend.ui :as ui] [frontend.util :as util] [frontend.util.persist-var :as persist-var] [goog.dom :as gdom] [lambdaisland.glogi :as log] - [logseq.common.config :as common-config] - [logseq.common.util :as common-util] - [logseq.shui.ui :as shui] - [promesa.core :as p] - [rum.core :as rum])) + [promesa.core :as p])) ;; TODO: should we move all events here? @@ -96,54 +66,10 @@ (async/ (sync/c (persist-var/load-vars))) (async/c (rtc-handler/ - (p/let [exists? (fs/dir-exists? graph-path)] - (let [overwrite? (if exists? - (js/confirm (str "There's already a directory with the name \"" graph-name "\", do you want to overwrite it? Make sure to backup it first if you're not sure about it.")) - true)] - (if overwrite? - (p/let [_ (fs/mkdir-if-not-exists graph-path)] - (nfs-handler/ls-dir-files-with-path! - graph-path - {:ok-handler (fn [] - (file-sync-handler/init-remote-graph graph-path graph) - (js/setTimeout (fn [] (repo-handler/refresh-repos!)) 200))})) - (let [graph-name (-> (js/prompt "Please specify a new directory name to download the graph:") - str - string/trim)] - (when-not (string/blank? graph-name) - (state/pub-event! [:graph/pull-down-remote-graph graph graph-name])))))) - (p/catch (fn [^js e] - (notification/show! (str e) :error) - (js/console.error e))))))))) - (when (:GraphName graph) - (shui/dialog-open! - (file-sync/pick-dest-to-sync-panel graph))))) - -(defmethod handle :graph/pick-page-histories [[_ graph-uuid page-name]] - (shui/dialog-open! - (file-sync/pick-page-histories-panel graph-uuid page-name) - {:id :page-histories :label "modal-page-histories"})) - (defmethod handle :graph/open-new-window [[_ev target-repo]] (p/let [current-repo (state/get-current-repo)] (ui-handler/open-new-window-or-tab! current-repo target-repo))) @@ -261,67 +148,6 @@ (defmethod handle :graph/migrated [[_ _repo]] (js/alert "Graph migrated.")) -(defn get-local-repo - [] - (when-let [repo (state/get-current-repo)] - (when (config/local-file-based-graph? repo) - repo))) - -(defn ask-permission - [repo] - (when - (and (not (util/electron?)) - (not (mobile-util/native-platform?))) - (fn [{:keys [close]}] - [:div - ;; TODO: fn translation with args - [:p - "Grant native filesystem permission for directory: " - [:b (config/get-local-dir repo)]] - (ui/button - (t :settings-permission/start-granting) - :class "ui__modal-enter" - :on-click (fn [] - (nfs/check-directory-permission! repo) - (close)))]))) - -(defmethod handle :modal/nfs-ask-permission [] - (when-let [repo (get-local-repo)] - (some-> (ask-permission repo) - (shui/dialog-open! {:align :top})))) - -(defmethod handle :modal/show-cards [[_ cards-id]] - (let [db-based? (config/db-based-graph? (state/get-current-repo))] - (shui/dialog-open! - (if db-based? (fn [] (fsrs/cards-view cards-id)) srs/global-cards) - {:id :srs - :label "flashcards__cp"}))) - -(defmethod handle :modal/show-instruction [_] - (shui/dialog-open! - capacitor-fs/instruction - {:id :instruction - :label "instruction__cp"})) - -(defmethod handle :modal/show-themes-modal [[_ classic?]] - (if classic? - (plugin/open-select-theme!) - (route-handler/go-to-search! :themes))) - -(defmethod handle :ui/toggle-appearance [_] - (let [popup-id "appearance_settings"] - (if (gdom/getElement popup-id) - (shui/popup-hide! popup-id) - (shui/popup-show! - (js/document.querySelector ".toolbar-dots-btn") - (fn [] - (settings/appearance)) - {:id popup-id - :align :end})))) - -(defmethod handle :modal/set-git-username-and-email [[_ _content]] - (shui/dialog-open! git-component/set-git-username-and-email)) - (defmethod handle :page/create [[_ page-name opts]] (if (= page-name (date/today)) (page-handler/create-today-journal!) @@ -333,26 +159,10 @@ (defmethod handle :page/renamed [[_ repo data]] (page-common-handler/after-page-renamed! repo data)) -(defmethod handle :page/show-delete-dialog [[_ selected-rows ok-handler]] - (shui/dialog-open! - (component-page/batch-delete-dialog - selected-rows false - ok-handler))) - (defmethod handle :page/create-today-journal [[_ _repo]] (p/let [_ (page-handler/create-today-journal!)] (ui-handler/re-render-root!))) -(defmethod handle :file/not-matched-from-disk [[_ path disk-content db-content]] - (when-let [repo (state/get-current-repo)] - (shui/dialog-open! - #(diff/local-file repo path disk-content db-content) - {:label "diff__cp"}))) - -(defmethod handle :modal/display-file-version-selector [[_ versions path get-content]] - (shui/dialog-open! - #(git-component/file-version-selector versions path get-content))) - (defmethod handle :graph/sync-context [] (let [context {:dev? config/dev? :node-test? util/node-test? @@ -391,48 +201,6 @@ (export/auto-db-backup! repo {:backup-now? true}) (fs-watcher/load-graph-files! repo))))) -(defmethod handle :notification/show [[_ {:keys [content status clear?]}]] - (notification/show! content status clear?)) - -(defmethod handle :command/run [_] - (when (util/electron?) - (shui/dialog-open! shell/shell))) - -(defmethod handle :go/search [_] - (shui/dialog-open! - cmdk/cmdk-modal - {:id :ls-dialog-cmdk - :align :top - :content-props {:class "ls-dialog-cmdk"} - :close-btn? false})) - -(defmethod handle :go/plugins [_] - (plugin/open-plugins-modal!)) - -(defmethod handle :go/plugins-waiting-lists [_] - (plugin/open-waiting-updates-modal!)) - -(defmethod handle :go/plugins-from-file [[_ plugins]] - (plugin/open-plugins-from-file-modal! plugins)) - -(defmethod handle :go/install-plugin-from-github [[_]] - (shui/dialog-open! - (plugin/install-from-github-release-container))) - -(defmethod handle :go/plugins-settings [[_ pid nav? title]] - (when pid - (state/set-state! :plugin/focused-settings pid) - (state/set-state! :plugin/navs-settings? (not (false? nav?))) - (plugin/open-focused-settings-modal! title))) - -(defmethod handle :go/proxy-settings [[_ agent-opts]] - (shui/dialog-open! - (plugin/user-proxy-settings-container agent-opts) - {:id :https-proxy-panel :center? true :class "lg:max-w-2xl"})) - -(defmethod handle :redirect-to-home [_] - (page-handler/create-today-journal!)) - (defmethod handle :instrument [[_ {:keys [type payload] :as opts}]] (when-not (empty? (dissoc opts :type :payload)) (js/console.error "instrument data-map should only contains [:type :payload]")) @@ -557,57 +325,12 @@ (file-sync-restart!)))) (state/pub-event! [:graph/ready (state/get-current-repo)]))))) -(defmethod handle :plugin/consume-updates [[_ id prev-pending? updated?]] - (let [downloading? (:plugin/updates-downloading? @state/state) - auto-checking? (plugin-handler/get-auto-checking?)] - (when-let [coming (and (not downloading?) - (get-in @state/state [:plugin/updates-coming id]))] - (let [error-code (:error-code coming) - error-code (if (= error-code (str :no-new-version)) nil error-code) - title (:title coming)] - (when (and prev-pending? (not auto-checking?)) - (if-not error-code - (plugin/set-updates-sub-content! (str title "...") 0) - (notification/show! - (str "[Checked]<" title "> " error-code) :error))))) - - (if (and updated? downloading?) - ;; try to start consume downloading item - (if-let [next-coming (state/get-next-selected-coming-update)] - (plugin-handler/check-or-update-marketplace-plugin! - (assoc next-coming :only-check false :error-code nil) - (fn [^js e] (js/console.error "[Download Err]" next-coming e))) - (plugin-handler/close-updates-downloading)) - - ;; try to start consume pending item - (if-let [next-pending (second (first (:plugin/updates-pending @state/state)))] - (do - (println "Updates: take next pending - " (:id next-pending)) - (js/setTimeout - #(plugin-handler/check-or-update-marketplace-plugin! - (assoc next-pending :only-check true :auto-check auto-checking? :error-code nil) - (fn [^js e] - (notification/show! (.toString e) :error) - (js/console.error "[Check Err]" next-pending e))) 500)) - - ;; try to open waiting updates list - (do (when (and prev-pending? (not auto-checking?) - (seq (state/all-available-coming-updates))) - (plugin/open-waiting-updates-modal!)) - (plugin-handler/set-auto-checking! false)))))) - (defmethod handle :plugin/hook-db-tx [[_ {:keys [blocks tx-data] :as payload}]] (when-let [payload (and (seq blocks) (merge payload {:tx-data (map #(into [] %) tx-data)}))] (plugin-handler/hook-plugin-db :changed payload) (plugin-handler/hook-plugin-block-changes payload))) -(defmethod handle :plugin/loader-perf-tip [[_ {:keys [^js o _s _e]}]] - (when-let [opts (.-options o)] - (notification/show! - (plugin/perf-tip-content (.-id o) (.-name opts) (.-url opts)) - :warning false (.-id o)))) - (defmethod handle :mobile-file-watcher/changed [[_ ^js event]] (let [type (.-event event) payload (js->clj event :keywordize-keys true)] @@ -621,51 +344,6 @@ (defmethod handle :shortcut/refresh [[_]] (st/refresh!)) -(defn- refresh-cb [] - (page-handler/create-today-journal!) - (file-sync-restart!)) - -(defmethod handle :graph/ask-for-re-fresh [_] - (shui/dialog-open! - [:div {:style {:max-width 700}} - [:p (t :sync-from-local-changes-detected)] - [:div.flex.justify-end - (ui/button - (t :yes) - :autoFocus "on" - :class "ui__modal-enter" - :on-click (fn [] - (shui/dialog-close!) - (nfs-handler/refresh! (state/get-current-repo) refresh-cb)))]])) - -(defmethod handle :sync/create-remote-graph [[_ current-repo]] - (let [graph-name (js/decodeURI (util/node-path.basename current-repo))] - (async/go - (async/ opts - editing-block - (assoc :original-block editing-block - :edit-original-block - (fn [{:keys [editing-default-property?]}] - (when editing-block - (let [content (:block/title (db/entity (:db/id editing-block))) - esc? (= "Escape" (state/get-ui-last-key-code)) - [content' pos] (cond - esc? - [nil pos] - (and (>= (count content) pos) - (>= pos 2) - (= (util/nth-safe content (dec pos)) - (util/nth-safe content (- pos 2)) - ";")) - [(str (common-util/safe-subs content 0 (- pos 2)) - (common-util/safe-subs content pos)) - (- pos 2)] - :else - [nil pos])] - (when content' - (if editing-default-property? - (editor-handler/save-block! (state/get-current-repo) (:block/uuid editing-block) content') - (editor-handler/edit-block! editing-block (or pos :max) - (cond-> {} - content' - (assoc :custom-content content'))))))))))] - (when (seq blocks) - (let [target' (or target - (some-> (state/get-edit-input-id) - (gdom/getElement)) - (first (state/get-selection-blocks)))] - (if target' - (shui/popup-show! target' - #(property-dialog/dialog blocks opts') - {:align "start" - :auto-focus? true}) - (shui/dialog-open! #(property-dialog/dialog blocks opts') - {:id :property-dialog - :align "start"})))))) - -(defmethod handle :editor/new-property [[_ {:keys [block target] :as opts}]] - (when-not config/publishing? - (p/do! - (editor-handler/save-current-block!) - (editor-new-property block target opts)))) - (defmethod handle :editor/upsert-type-block [[_ {:keys [block type lang update-current-block?]}]] (p/do! (when-not update-current-block? @@ -1026,25 +466,6 @@ (db/entity [:block/uuid (:block/uuid block)])))] (js/setTimeout #(editor-handler/edit-block! block :max) 100))))) -(rum/defc multi-tabs-dialog - [] - (let [word (if (util/electron?) "window" "tab")] - [:div.flex.p-4.flex-col.gap-4.h-64 - [:span.warning.text-lg - (util/format "Logseq doesn't support multiple %ss access to the same graph yet, please close this %s or switch to another graph." - word word)] - [:div.text-lg - [:p "Switch to another repo: "] - [:div.border.rounded.bg-gray-01.overflow-hidden.w-60 - (repo/repos-dropdown {:on-click (fn [e] - (util/stop e) - (state/set-state! :error/multiple-tabs-access-opfs? false) - (shui/dialog-close!))})]]])) - -(defmethod handle :show/multiple-tabs-error-dialog [_] - (state/set-state! :error/multiple-tabs-access-opfs? true) - (shui/dialog-open! multi-tabs-dialog)) - (defmethod handle :rtc/sync-state [[_ state]] (state/update-state! :rtc/state (fn [old] (merge old state)))) @@ -1075,24 +496,6 @@ (defmethod handle :editor/run-query-command [_] (editor-handler/run-query-command!)) -(defmethod handle :editor/show-action-bar [] - (let [selection (state/get-selection-blocks) - first-visible-block (some #(when (util/el-visible-in-viewport? % true) %) selection)] - (when first-visible-block - (shui/popup-hide! :selection-action-bar) - (shui/popup-show! - first-visible-block - (fn [] - (selection/action-bar)) - {:id :selection-action-bar - :content-props {:side "top" - :class "!py-0 !px-0 !border-none"} - :auto-side? false - :align :start})))) - -(defmethod handle :editor/hide-action-bar [] - (shui/popup-hide! :selection-action-bar)) - (defmethod handle :editor/load-blocks [[_ ids]] (when (seq ids) ;; not using ` (ask-permission repo) + (shui/dialog-open! {:align :top})))) + +(defmethod events/handle :modal/show-cards [[_ cards-id]] + (let [db-based? (config/db-based-graph? (state/get-current-repo))] + (shui/dialog-open! + (if db-based? (fn [] (fsrs/cards-view cards-id)) srs/global-cards) + {:id :srs + :label "flashcards__cp"}))) + +(defmethod events/handle :modal/show-instruction [_] + (shui/dialog-open! + capacitor-fs/instruction + {:id :instruction + :label "instruction__cp"})) + +(defmethod events/handle :modal/show-themes-modal [[_ classic?]] + (if classic? + (plugin/open-select-theme!) + (route-handler/go-to-search! :themes))) + +(defmethod events/handle :ui/toggle-appearance [_] + (let [popup-id "appearance_settings"] + (if (gdom/getElement popup-id) + (shui/popup-hide! popup-id) + (shui/popup-show! + (js/document.querySelector ".toolbar-dots-btn") + (fn [] + (settings/appearance)) + {:id popup-id + :align :end})))) + +(defmethod events/handle :plugin/consume-updates [[_ id prev-pending? updated?]] + (let [downloading? (:plugin/updates-downloading? @state/state) + auto-checking? (plugin-handler/get-auto-checking?)] + (when-let [coming (and (not downloading?) + (get-in @state/state [:plugin/updates-coming id]))] + (let [error-code (:error-code coming) + error-code (if (= error-code (str :no-new-version)) nil error-code) + title (:title coming)] + (when (and prev-pending? (not auto-checking?)) + (if-not error-code + (plugin/set-updates-sub-content! (str title "...") 0) + (notification/show! + (str "[Checked]<" title "> " error-code) :error))))) + + (if (and updated? downloading?) + ;; try to start consume downloading item + (if-let [next-coming (state/get-next-selected-coming-update)] + (plugin-handler/check-or-update-marketplace-plugin! + (assoc next-coming :only-check false :error-code nil) + (fn [^js e] (js/console.error "[Download Err]" next-coming e))) + (plugin-handler/close-updates-downloading)) + + ;; try to start consume pending item + (if-let [next-pending (second (first (:plugin/updates-pending @state/state)))] + (do + (println "Updates: take next pending - " (:id next-pending)) + (js/setTimeout + #(plugin-handler/check-or-update-marketplace-plugin! + (assoc next-pending :only-check true :auto-check auto-checking? :error-code nil) + (fn [^js e] + (notification/show! (.toString e) :error) + (js/console.error "[Check Err]" next-pending e))) 500)) + + ;; try to open waiting updates list + (do (when (and prev-pending? (not auto-checking?) + (seq (state/all-available-coming-updates))) + (plugin/open-waiting-updates-modal!)) + (plugin-handler/set-auto-checking! false)))))) + +(defmethod events/handle :plugin/loader-perf-tip [[_ {:keys [^js o _s _e]}]] + (when-let [opts (.-options o)] + (notification/show! + (plugin/perf-tip-content (.-id o) (.-name opts) (.-url opts)) + :warning false (.-id o)))) + +(defn- refresh-cb [] + (page-handler/create-today-journal!) + (events/file-sync-restart!)) + +(defmethod events/handle :graph/ask-for-re-fresh [_] + (shui/dialog-open! + [:div {:style {:max-width 700}} + [:p (t :sync-from-local-changes-detected)] + [:div.flex.justify-end + (ui/button + (t :yes) + :autoFocus "on" + :class "ui__modal-enter" + :on-click (fn [] + (shui/dialog-close!) + (nfs-handler/refresh! (state/get-current-repo) refresh-cb)))]])) + +(defn- editor-new-property [block target {:keys [selected-blocks] :as opts}] + (let [editing-block (state/get-edit-block) + pos (state/get-edit-pos) + edit-block-or-selected (cond + editing-block + [editing-block] + (seq selected-blocks) + selected-blocks + :else + (seq (keep #(db/entity [:block/uuid %]) (state/get-selection-block-ids)))) + current-block (when-let [s (state/get-current-page)] + (when (util/uuid-string? s) + (db/entity [:block/uuid (uuid s)]))) + blocks (or (when block [block]) + edit-block-or-selected + (when current-block [current-block])) + opts' (cond-> opts + editing-block + (assoc :original-block editing-block + :edit-original-block + (fn [{:keys [editing-default-property?]}] + (when editing-block + (let [content (:block/title (db/entity (:db/id editing-block))) + esc? (= "Escape" (state/get-ui-last-key-code)) + [content' pos] (cond + esc? + [nil pos] + (and (>= (count content) pos) + (>= pos 2) + (= (util/nth-safe content (dec pos)) + (util/nth-safe content (- pos 2)) + ";")) + [(str (common-util/safe-subs content 0 (- pos 2)) + (common-util/safe-subs content pos)) + (- pos 2)] + :else + [nil pos])] + (when content' + (if editing-default-property? + (editor-handler/save-block! (state/get-current-repo) (:block/uuid editing-block) content') + (editor-handler/edit-block! editing-block (or pos :max) + (cond-> {} + content' + (assoc :custom-content content'))))))))))] + (when (seq blocks) + (let [target' (or target + (some-> (state/get-edit-input-id) + (gdom/getElement)) + (first (state/get-selection-blocks)))] + (if target' + (shui/popup-show! target' + #(property-dialog/dialog blocks opts') + {:align "start" + :auto-focus? true}) + (shui/dialog-open! #(property-dialog/dialog blocks opts') + {:id :property-dialog + :align "start"})))))) + +(defmethod events/handle :editor/new-property [[_ {:keys [block target] :as opts}]] + (when-not config/publishing? + (p/do! + (editor-handler/save-current-block!) + (editor-new-property block target opts)))) + +(defmethod events/handle :graph/new-db-graph [[_ _opts]] + (shui/dialog-open! + repo/new-db-graph + {:id :new-db-graph + :title [:h2 "Create a new graph"] + :style {:max-width "500px"}})) + +(defmethod events/handle :dialog-select/graph-open [] + (select/dialog-select! :graph-open)) + +(defmethod events/handle :dialog-select/graph-remove [] + (select/dialog-select! :graph-remove)) + +(defmethod events/handle :dialog-select/db-graph-replace [] + (select/dialog-select! :db-graph-replace)) + +(rum/defc multi-tabs-dialog + [] + (let [word (if (util/electron?) "window" "tab")] + [:div.flex.p-4.flex-col.gap-4.h-64 + [:span.warning.text-lg + (util/format "Logseq doesn't support multiple %ss access to the same graph yet, please close this %s or switch to another graph." + word word)] + [:div.text-lg + [:p "Switch to another repo: "] + [:div.border.rounded.bg-gray-01.overflow-hidden.w-60 + (repo/repos-dropdown {:on-click (fn [e] + (util/stop e) + (state/set-state! :error/multiple-tabs-access-opfs? false) + (shui/dialog-close!))})]]])) + +(defmethod events/handle :show/multiple-tabs-error-dialog [_] + (state/set-state! :error/multiple-tabs-access-opfs? true) + (shui/dialog-open! multi-tabs-dialog)) + +(defmethod events/handle :editor/show-action-bar [] + (let [selection (state/get-selection-blocks) + first-visible-block (some #(when (util/el-visible-in-viewport? % true) %) selection)] + (when first-visible-block + (shui/popup-hide! :selection-action-bar) + (shui/popup-show! + first-visible-block + (fn [] + (selection/action-bar)) + {:id :selection-action-bar + :content-props {:side "top" + :class "!py-0 !px-0 !border-none"} + :auto-side? false + :align :start})))) + +(defmethod events/handle :editor/hide-action-bar [] + (shui/popup-hide! :selection-action-bar)) + +(defmethod events/handle :user/logout [[_]] + (file-sync-handler/reset-session-graphs) + (sync/remove-all-pwd!) + (file-sync-handler/reset-user-state!) + (login/sign-out!)) + +(defmethod events/handle :user/login [[_ host-ui?]] + (if (or host-ui? (not util/electron?)) + (js/window.open config/LOGIN-URL) + (if (mobile-util/native-platform?) + (route-handler/redirect! {:to :user-login}) + (login/open-login-modal!)))) + +(defmethod events/handle :whiteboard/onboarding [[_ opts]] + (shui/dialog-open! + (fn [{:keys [close]}] (whiteboard/onboarding-welcome close)) + (merge {:close-btn? false + :center? true + :close-backdrop? false} opts))) diff --git a/src/main/frontend/handler/file_based/events.cljs b/src/main/frontend/handler/file_based/events.cljs index cb87891a39..f6330d5946 100644 --- a/src/main/frontend/handler/file_based/events.cljs +++ b/src/main/frontend/handler/file_based/events.cljs @@ -1,21 +1,42 @@ (ns frontend.handler.file-based.events "Events that are only for file graphs" (:require [clojure.core.async :as async] + [clojure.core.async.interop :refer [p->c]] [clojure.set :as set] + [clojure.string :as string] + [frontend.components.diff :as diff] + [frontend.components.encryption :as encryption] + [frontend.components.file-based.git :as git-component] + [frontend.components.file-sync :as file-sync] + [frontend.config :as config] [frontend.context.i18n :refer [t]] [frontend.db :as db] - [frontend.handler.events :as events] - [frontend.handler.page :as page-handler] - [frontend.handler.repo :as repo-handler] - [frontend.handler.file-based.nfs :as nfs-handler] - [frontend.handler.common :as common-handler] - [frontend.handler.property :as property-handler] + [frontend.db.conn :as conn] + [frontend.fs :as fs] [frontend.fs.sync :as sync] + [frontend.handler.common :as common-handler] + [frontend.handler.db-based.rtc :as rtc-handler] + [frontend.handler.events :as events] + [frontend.handler.file-based.file :as file-handler] + [frontend.handler.file-based.nfs :as nfs-handler] + [frontend.handler.file-sync :as file-sync-handler] + [frontend.handler.notification :as notification] + [frontend.handler.page :as page-handler] + [frontend.handler.property :as property-handler] + [frontend.handler.repo :as repo-handler] + [frontend.handler.route :as route-handler] + [frontend.handler.ui :as ui-handler] + [frontend.handler.user :as user-handler] + [frontend.mobile.graph-picker :as graph-picker] + [frontend.mobile.util :as mobile-util] + [frontend.modules.instrumentation.sentry :as sentry-event] + [frontend.modules.shortcut.core :as st] [frontend.state :as state] [frontend.ui :as ui] [frontend.util :as util] - [frontend.config :as config] + [logseq.common.config :as common-config] [logseq.shui.ui :as shui] + [promesa.core :as p] [rum.core :as rum])) (defmethod events/handle :graph/ask-for-re-index [[_ *multiple-windows? ui]] @@ -23,32 +44,32 @@ ;; ui - custom message to show on asking for re-index (if (and (util/atom? *multiple-windows?) @*multiple-windows?) (shui/dialog-open! - [:div - (when (not (nil? ui)) ui) - [:p (t :re-index-multiple-windows-warning)]]) + [:div + (when (not (nil? ui)) ui) + [:p (t :re-index-multiple-windows-warning)]]) (shui/dialog-open! - [:div {:style {:max-width 700}} - (when (not (nil? ui)) ui) - [:p (t :re-index-discard-unsaved-changes-warning)] - [:div.flex.justify-end.pt-2 - (ui/button - (t :yes) - :autoFocus "on" - :class "ui__modal-enter" - :on-click (fn [] - (shui/dialog-close!) - (state/pub-event! [:graph/re-index])))]]))) + [:div {:style {:max-width 700}} + (when (not (nil? ui)) ui) + [:p (t :re-index-discard-unsaved-changes-warning)] + [:div.flex.justify-end.pt-2 + (ui/button + (t :yes) + :autoFocus "on" + :class "ui__modal-enter" + :on-click (fn [] + (shui/dialog-close!) + (state/pub-event! [:graph/re-index])))]]))) (defmethod events/handle :graph/re-index [[_]] ;; Ensure the graph only has ONE window instance (when (config/local-file-based-graph? (state/get-current-repo)) (async/go - (async/c (rtc-handler/ + (p/let [exists? (fs/dir-exists? graph-path)] + (let [overwrite? (if exists? + (js/confirm (str "There's already a directory with the name \"" graph-name "\", do you want to overwrite it? Make sure to backup it first if you're not sure about it.")) + true)] + (if overwrite? + (p/let [_ (fs/mkdir-if-not-exists graph-path)] + (nfs-handler/ls-dir-files-with-path! + graph-path + {:ok-handler (fn [] + (file-sync-handler/init-remote-graph graph-path graph) + (js/setTimeout (fn [] (repo-handler/refresh-repos!)) 200))})) + (let [graph-name (-> (js/prompt "Please specify a new directory name to download the graph:") + str + string/trim)] + (when-not (string/blank? graph-name) + (state/pub-event! [:graph/pull-down-remote-graph graph graph-name])))))) + (p/catch (fn [^js e] + (notification/show! (str e) :error) + (js/console.error e))))))))) + (when (:GraphName graph) + (shui/dialog-open! + (file-sync/pick-dest-to-sync-panel graph))))) + +(defmethod events/handle :graph/pick-page-histories [[_ graph-uuid page-name]] + (shui/dialog-open! + (file-sync/pick-page-histories-panel graph-uuid page-name) + {:id :page-histories :label "modal-page-histories"})) + +(defmethod events/handle :file-sync/onboarding-tip [[_ type opts]] + (let [type (keyword type)] + (when-not (config/db-based-graph? (state/get-current-repo)) + (shui/dialog-open! + (file-sync/make-onboarding-panel type) + (merge {:close-btn? false + :center? true + :close-backdrop? (not= type :welcome)} opts))))) + +(defmethod events/handle :file-sync/maybe-onboarding-show [[_ type]] + (file-sync/maybe-onboarding-show type)) + +(defmethod events/handle :file-sync/storage-exceed-limit [[_]] + (notification/show! "file sync storage exceed limit" :warning false) + (events/file-sync-stop!)) + +(defmethod events/handle :file-sync/graph-count-exceed-limit [[_]] + (notification/show! "file sync graph count exceed limit" :warning false) + (events/file-sync-stop!)) + +(defmethod events/handle :graph/dir-gone [[_ dir]] + (state/pub-event! [:notification/show + {:content (str "The directory " dir " has been renamed or deleted, the editor will be disabled for this graph, you can unlink the graph.") + :status :error + :clear? false}]) + (state/update-state! :file/unlinked-dirs (fn [dirs] (conj dirs dir)))) + +(defmethod events/handle :graph/dir-back [[_ repo dir]] + (when (contains? (:file/unlinked-dirs @state/state) dir) + (notification/clear-all!) + (state/pub-event! [:notification/show + {:content (str "The directory " dir " has been back, you can edit your graph now.") + :status :success + :clear? true}]) + (state/update-state! :file/unlinked-dirs (fn [dirs] (disj dirs dir))) + (when (= dir (config/get-repo-dir repo)) + (fs/watch-dir! dir)))) + +(defmethod events/handle :ui/notify-skipped-downloading-files [[_ paths]] + (notification/show! + [:div + [:div.mb-4 + [:div.font-semibold.mb-4.text-xl "It seems that some of your filenames are in the outdated format."] + [:p + "The files below that have reserved characters can't be saved on this device."] + [:div.overflow-y-auto.max-h-96 + [:ol.my-2 + (for [path paths] + [:li path])]] + + [:div + [:p + "Check " [:a {:href "https://docs.logseq.com/#/page/logseq%20file%20and%20folder%20naming%20rules" + :target "_blank"} + "Logseq file and folder naming rules"] + " for more details."]]]] + :warning + false)) + +(defmethod events/handle :graph/setup-a-repo [[_ opts]] + (let [opts' (merge {:picked-root-fn #(state/close-modal!) + :native-icloud? (not (string/blank? (state/get-icloud-container-root-url))) + :logged? (user-handler/logged-in?)} opts)] + (if (mobile-util/native-ios?) + (shui/dialog-open! + #(graph-picker/graph-picker-cp opts') + {:label "graph-setup"}) + (page-handler/ls-dir-files! st/refresh! opts')))) + +(defmethod events/handle :file/alter [[_ repo path content]] + (p/let [_ (file-handler/alter-file repo path content {:from-disk? true})] + (ui-handler/re-render-root!))) + +(rum/defcs file-id-conflict-item < + (rum/local false ::resolved?) + [state repo file data] + (let [resolved? (::resolved? state) + id (last (:assertion data))] + [:li {:key file} + [:div + [:a {:on-click #(js/window.apis.openPath file)} file] + (if @resolved? + [:div.flex.flex-row.items-center + (ui/icon "circle-check" {:style {:font-size 20}}) + [:div.ml-1 "Resolved"]] + [:div + [:p + (str "It seems that another whiteboard file already has the ID \"" id + "\". You can fix it by changing the ID in this file with another UUID.")] + [:p + "Or, let me" + (ui/button "Fix" + :on-click (fn [] + (let [dir (config/get-repo-dir repo)] + (p/let [content (fs/read-file dir file)] + (let [new-content (string/replace content (str id) (str (random-uuid)))] + (p/let [_ (fs/write-file! repo + dir + file + new-content + {})] + (reset! resolved? true)))))) + :class "inline mx-1") + "it."]])]])) + +(defmethod events/handle :file/parse-and-load-error [[_ repo parse-errors]] + (state/pub-event! [:notification/show + {:content + [:div + [:h2.title "Oops. These files failed to import to your graph:"] + [:ol.my-2 + (for [[file error] parse-errors] + (let [data (ex-data error)] + (cond + (and (common-config/whiteboard? file) + (= :transact/upsert (:error data)) + (uuid? (last (:assertion data)))) + (rum/with-key (file-id-conflict-item repo file data) file) + + :else + (do + (state/pub-event! [:capture-error {:error error + :payload {:type :file/parse-and-load-error}}]) + [:li.my-1 {:key file} + [:a {:on-click #(js/window.apis.openPath file)} file] + [:p (.-message error)]]))))] + [:p "Don't forget to re-index your graph when all the conflicts are resolved."]] + :status :error}])) + +(defmethod events/handle :file-sync-graph/restore-file [[_ graph page-entity content]] + (when (db/get-db graph) + (let [file (:block/file page-entity)] + (when-let [path (:file/path file)] + (when (and (not= content (:file/content file)) + (:file/content file)) + (sync/add-new-version-file graph path (:file/content file))) + (p/let [_ (file-handler/alter-file graph + path + content + {:re-render-root? true + :skip-compare? true})] + (state/close-modal!) + (route-handler/redirect! {:to :page + :path-params {:name (:block/name page-entity)}})))))) + +(defmethod events/handle :sync/create-remote-graph [[_ current-repo]] + (let [graph-name (js/decodeURI (util/node-path.basename current-repo))] + (async/go + (async/