diff --git a/src/main/frontend/components/settings.cljs b/src/main/frontend/components/settings.cljs index 4d5fbf05f4..ff3912dec1 100644 --- a/src/main/frontend/components/settings.cljs +++ b/src/main/frontend/components/settings.cljs @@ -619,7 +619,7 @@ (when (config/global-config-enabled?) (edit-global-config-edn)) (when current-repo (edit-config-edn)) (when current-repo (edit-custom-css)) - (when current-repo (edit-export-css))])) + (when (and current-repo (util/electron?)) (edit-export-css))])) (rum/defcs settings-editor < rum/reactive [_state] diff --git a/src/main/frontend/fs.cljs b/src/main/frontend/fs.cljs index 5b01807621..29bd4e2dbc 100644 --- a/src/main/frontend/fs.cljs +++ b/src/main/frontend/fs.cljs @@ -3,6 +3,7 @@ platforms by delegating to implementations of the fs protocol" (:require [cljs-bean.core :as bean] [clojure.string :as string] + [electron.ipc :as ipc] [frontend.fs.memory-fs :as memory-fs] [frontend.fs.node :as node] [frontend.fs.protocol :as protocol] @@ -98,6 +99,20 @@ ;; (js/alert "Current file can't be saved! Please copy its content to your local file system and click the refresh button.") )))))) +(defn write-file! + "A node only version of write-plain-text-file! to avoid using the fs-protocol + which has file graph assumptions" + [path content] + (when (util/electron?) + (let [file-fpath (common-util/path-normalize path)] + ;; repo is nil because we don't want a backup file written + (-> (ipc/ipc "writeFile" nil file-fpath content) + (p/catch (fn [error] + (state/pub-event! [:capture-error {:error error + :payload {:type :write-file/failed + :user-agent (when js/navigator js/navigator.userAgent) + :content-length (count content)}}]))))))) + ;; read-file should return string on all platforms (defn read-file ([dir path] diff --git a/src/main/frontend/fs/node.cljs b/src/main/frontend/fs/node.cljs index 89af449d60..5e75c0be69 100644 --- a/src/main/frontend/fs/node.cljs +++ b/src/main/frontend/fs/node.cljs @@ -29,6 +29,7 @@ (error-handler error) (log/error :write-file-failed error)))) + ;; TODO: Remove backupDbFile which is a file graph concept (p/let [disk-content (when (not= stat :not-found) (-> (ipc/ipc "readFile" file-fpath) (p/then bean/->clj) diff --git a/src/main/frontend/handler/code.cljs b/src/main/frontend/handler/code.cljs index 16fa6af280..fff678d7b8 100644 --- a/src/main/frontend/handler/code.cljs +++ b/src/main/frontend/handler/code.cljs @@ -2,12 +2,30 @@ "Codemirror editor related." (:require [clojure.string :as string] [frontend.db :as db] + [frontend.fs :as fs] [frontend.handler.db-based.editor :as db-editor-handler] [frontend.handler.editor :as editor-handler] + [frontend.handler.global-config :as global-config-handler] [frontend.state :as state] + [frontend.util :as util] [goog.object :as gobj] + [logseq.common.path :as path] [logseq.graph-parser.utf8 :as utf8])) +(defn- save-file! [path content] + (if (db/entity [:file/path path]) + ;; This fn assumes path is is already in db + (db-editor-handler/save-file! path content) + (when (util/electron?) + (if (path/absolute? path) + (do + ;; Set global state first in case it's invalid edn + (when (= path (global-config-handler/global-config-path)) + (global-config-handler/set-global-config-state! content) + (state/pub-event! [:shortcut/refresh])) + (fs/write-file! path content)) + (js/console.error "Saving relative file ignored" path content))))) + (defn save-code-editor! [] (let [{:keys [config state editor]} (get @state/state :editor/code-block-context)] @@ -42,7 +60,7 @@ (editor-handler/save-block-if-changed! block new-content)) (not-empty (:file-path config)) - (db-editor-handler/save-file! (:file-path config) value) + (save-file! (:file-path config) value) :else nil)))))) diff --git a/src/main/frontend/handler/common/config_edn.cljs b/src/main/frontend/handler/common/config_edn.cljs index 1cd70c66b5..32df85d92b 100644 --- a/src/main/frontend/handler/common/config_edn.cljs +++ b/src/main/frontend/handler/common/config_edn.cljs @@ -89,17 +89,15 @@ nested keys or positional errors e.g. tuples" (defn detect-deprecations "Detects config keys that will or have been deprecated" - [path content {:keys [db-graph?]}] + [path content] (let [body (try (edn/read-string content) (catch :default _ ::failed-to-detect)) - warnings (cond-> + warnings (merge {:editor/command-trigger "is no longer supported. Please use '/' and report bugs on it." :arweave/gateway "is no longer supported."} - db-graph? - (merge - common-config/file-only-config))] + common-config/file-only-config)] (cond (= body ::failed-to-detect) (log/info :msg "Skip deprecation check since config is not valid edn") diff --git a/src/main/frontend/handler/db_based/editor.cljs b/src/main/frontend/handler/db_based/editor.cljs index b6324938eb..73673534f1 100644 --- a/src/main/frontend/handler/db_based/editor.cljs +++ b/src/main/frontend/handler/db_based/editor.cljs @@ -73,7 +73,7 @@ "This fn is the db version of file-handler/alter-file" [path content] (let [file-valid? (if (= path "logseq/config.edn") - (do (config-edn-common-handler/detect-deprecations path content {:db-graph? true}) + (do (config-edn-common-handler/detect-deprecations path content) (config-edn-common-handler/validate-config-edn path content repo-config-schema/Config-edn)) true)] diff --git a/src/main/frontend/handler/global_config.cljs b/src/main/frontend/handler/global_config.cljs index 530dd985ba..4629468d31 100644 --- a/src/main/frontend/handler/global_config.cljs +++ b/src/main/frontend/handler/global_config.cljs @@ -63,16 +63,14 @@ (rewrite/dissoc result (first ks)) (rewrite/assoc-in result ks v)) new-str-content (str new-result)] - (fs/write-plain-text-file! nil nil (global-config-path) new-str-content {:skip-compare? true}) + (fs/write-file! (global-config-path) new-str-content) (state/set-global-config! (rewrite/sexpr new-result) new-str-content))) (defn start - "This component has four responsibilities on start: + "This component has three responsibilities on start: - Fetch root-dir for later use with config paths - Manage ui state of global config -- Create a global config dir and file if it doesn't exist -- Start a file watcher for global config dir if it's not already started. - Watcher ensures client db is seeded with correct file data." +- Create a global config dir and file if it doesn't exist" [{:keys [repo]}] (-> (p/do! (p/let [root-dir' (ipc/ipc "getLogseqDotDirRoot")] diff --git a/src/main/frontend/handler/plugin_config.cljs b/src/main/frontend/handler/plugin_config.cljs index e1130dd969..848af41081 100644 --- a/src/main/frontend/handler/plugin_config.cljs +++ b/src/main/frontend/handler/plugin_config.cljs @@ -1,7 +1,7 @@ (ns frontend.handler.plugin-config "This system component encapsulates the global plugin.edn and depends on the global-config component. This component is only enabled? if both the - global-config and plugin components are enabled. plugin.edn is automatically updated + global-config and plugin components are enabled. plugins.edn is automatically updated when a plugin is installed, updated or removed" (:require [borkdude.rewrite-edn :as rewrite] [cljs-bean.core :as bean] @@ -31,23 +31,21 @@ when a plugin is installed, updated or removed" (->> plugin-config-schema/Plugin rest (mapv first))) (defn add-or-update-plugin - "Adds or updates a plugin from plugin.edn" + "Adds or updates a plugin from plugins.edn" [{:keys [id] :as plugin}] (p/let [content (fs/read-file nil (plugin-config-path)) updated-content (-> content rewrite/parse-string (rewrite/assoc (keyword id) (select-keys plugin common-plugin-keys)) str)] - ;; fs protocols require repo and dir when they aren't necessary. For this component, - ;; neither is needed so these are blank and nil respectively - (fs/write-plain-text-file! "" nil (plugin-config-path) updated-content {:skip-compare? true}))) + (fs/write-file! (plugin-config-path) updated-content))) (defn remove-plugin - "Removes a plugin from plugin.edn" + "Removes a plugin from plugins.edn" [plugin-id] - (p/let [content (fs/read-file "" (plugin-config-path)) + (p/let [content (fs/read-file nil (plugin-config-path)) updated-content (-> content rewrite/parse-string (rewrite/dissoc (keyword plugin-id)) str)] - (fs/write-plain-text-file! "" nil (plugin-config-path) updated-content {:skip-compare? true}))) + (fs/write-file! (plugin-config-path) updated-content))) (defn- create-plugin-config-file-if-not-exists [] diff --git a/src/test/frontend/handler/common/config_edn_test.cljs b/src/test/frontend/handler/common/config_edn_test.cljs index 09a7b19e0d..c0091e7d63 100644 --- a/src/test/frontend/handler/common/config_edn_test.cljs +++ b/src/test/frontend/handler/common/config_edn_test.cljs @@ -21,7 +21,7 @@ (let [error-message (atom nil)] (with-redefs [notification/show! (fn [msg _] (reset! error-message msg)) rfe/href (constantly "")] - (config-edn-common-handler/detect-deprecations "config.edn" config-body {}) + (config-edn-common-handler/detect-deprecations "config.edn" config-body) (str @error-message)))) (deftest validate-config-edn @@ -61,8 +61,8 @@ (deftest detect-deprecations (is (re-find #":editor/command-trigger.*is" - (deprecation-warnings-for "{:preferred-workflow :todo :editor/command-trigger \",\"}")) + (deprecation-warnings-for "{:editor/command-trigger \",\"}")) "Warning when there is a deprecation") - (is (= "" (deprecation-warnings-for "{:preferred-workflow :todo}")) + (is (= "" (deprecation-warnings-for "{}")) "No warning when there is no deprecation")) diff --git a/src/test/frontend/handler/plugin_config_test.cljs b/src/test/frontend/handler/plugin_config_test.cljs index b94bf9227c..6e2828d3d0 100644 --- a/src/test/frontend/handler/plugin_config_test.cljs +++ b/src/test/frontend/handler/plugin_config_test.cljs @@ -1,19 +1,22 @@ (ns frontend.handler.plugin-config-test - (:require [clojure.test :refer [is use-fixtures testing deftest]] - [frontend.test.helper :as test-helper :include-macros true :refer [deftest-async]] - [frontend.test.node-helper :as test-node-helper] - [frontend.test.node-fixtures :as node-fixtures] - [frontend.handler.plugin-config :as plugin-config-handler] - [frontend.handler.global-config :as global-config-handler] - [frontend.schema.handler.plugin-config :as plugin-config-schema] - ["fs" :as fs-node] + (:require ["fs" :as fs-node] + ["fs/promises" :as fsp] ["path" :as node-path] [clojure.edn :as edn] - [malli.generator :as mg] - [promesa.core :as p] [clojure.string :as string] - [frontend.handler.notification :as notification])) + [clojure.test :refer [is use-fixtures testing deftest]] + [frontend.fs :as fs] + [frontend.handler.global-config :as global-config-handler] + [frontend.handler.notification :as notification] + [frontend.handler.plugin-config :as plugin-config-handler] + [frontend.schema.handler.plugin-config :as plugin-config-schema] + [frontend.test.helper :as test-helper :include-macros true :refer [deftest-async]] + [frontend.test.node-fixtures :as node-fixtures] + [frontend.test.node-helper :as test-node-helper] + [malli.generator :as mg] + [promesa.core :as p])) +;; For tests that call fs/readFile (use-fixtures :once node-fixtures/redef-get-fs) (defn- create-global-config-dir @@ -37,13 +40,14 @@ body (pr-str (mg/generate plugin-config-schema/Plugins-edn {:size 10}))] (fs-node/writeFileSync (plugin-config-handler/plugin-config-path) body) - (-> - (p/do! - (plugin-config-handler/add-or-update-plugin plugin-to-add) - (is (= (dissoc plugin-to-add :id) - (:foo (edn/read-string (str (fs-node/readFileSync (plugin-config-handler/plugin-config-path)))))))) + (p/with-redefs [fs/write-file! fsp/writeFile] + (-> + (p/do! + (plugin-config-handler/add-or-update-plugin plugin-to-add) + (is (= (dissoc plugin-to-add :id) + (:foo (edn/read-string (str (fs-node/readFileSync (plugin-config-handler/plugin-config-path)))))))) - (p/finally #(delete-global-config-dir dir))))) + (p/finally #(delete-global-config-dir dir)))))) (deftest-async remove-plugin (let [dir (create-global-config-dir) @@ -53,14 +57,15 @@ some-plugin-id (first (keys plugins))] (fs-node/writeFileSync (plugin-config-handler/plugin-config-path) (pr-str plugins)) - (-> - (p/do! - (plugin-config-handler/remove-plugin some-plugin-id) - (is (= nil - (get (edn/read-string (str (fs-node/readFileSync (plugin-config-handler/plugin-config-path)))) - some-plugin-id)))) + (p/with-redefs [fs/write-file! fsp/writeFile] + (-> + (p/do! + (plugin-config-handler/remove-plugin some-plugin-id) + (is (= nil + (get (edn/read-string (str (fs-node/readFileSync (plugin-config-handler/plugin-config-path)))) + some-plugin-id)))) - (p/finally #(delete-global-config-dir dir))))) + (p/finally #(delete-global-config-dir dir)))))) (deftest-async open-replace-plugins-modal-malformed-edn (let [dir (create-global-config-dir)