Pārlūkot izejas kodu

Merge configs and create global config file if not present

- Cleanup repo and global config with system components
- test merging
- fix edge cases
Gabriel Horner 3 gadi atpakaļ
vecāks
revīzija
b1d210d26b

+ 7 - 0
.clj-kondo/config.edn

@@ -25,6 +25,13 @@
              frontend.format.mldoc mldoc
              frontend.format.block block
              frontend.handler.extract extract
+             frontend.handler.common common-handler
+             frontend.handler.common.file file-common-handler
+             frontend.handler.config config-handler
+             frontend.handler.global-config global-config-handler
+             frontend.handler.repo-config repo-config-handler
+             frontend.mobile.util mobile-util
+             frontend.state state
              logseq.graph-parser graph-parser
              logseq.graph-parser.text text
              logseq.graph-parser.block gp-block

+ 4 - 3
src/main/frontend/components/settings.cljs

@@ -15,6 +15,7 @@
             [frontend.handler.user :as user-handler]
             [frontend.handler.plugin :as plugin-handler]
             [frontend.handler.file-sync :as file-sync-handler]
+            [frontend.handler.global-config :as global-config-handler]
             [frontend.modules.instrumentation.core :as instrument]
             [frontend.modules.shortcut.data-helper :as shortcut-helper]
             [frontend.state :as state]
@@ -140,7 +141,7 @@
   (row-with-button-action
     {:left-label   (t :settings-page/custom-configuration)
      :button-label (t :settings-page/edit-config-edn)
-     :href         (rfe/href :file {:path (config/get-config-path)})
+     :href         (rfe/href :file {:path (config/get-repo-config-path)})
      :on-click     #(js/setTimeout (fn [] (ui-handler/toggle-settings-modal!)))
      :-for         "config_edn"}))
 
@@ -148,7 +149,7 @@
   (row-with-button-action
     {:left-label   (t :settings-page/custom-global-configuration)
      :button-label (t :settings-page/edit-global-config-edn)
-     :href         (rfe/href :file {:path (config/get-global-config-path)})
+     :href         (rfe/href :file {:path (global-config-handler/global-config-path)})
      :on-click     #(js/setTimeout (fn [] (ui-handler/toggle-settings-modal!)))
      :-for         "global_config_edn"}))
 
@@ -546,7 +547,7 @@
      (version-row t version)
      (language-row t preferred-language)
      (theme-modes-row t switch-theme system-theme? dark?)
-     (when (util/electron?) (edit-global-config-edn))
+     (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))

+ 7 - 12
src/main/frontend/config.cljs

@@ -6,8 +6,7 @@
             [frontend.util :as util]
             [logseq.graph-parser.config :as gp-config]
             [logseq.graph-parser.util :as gp-util]
-            [shadow.resource :as rc]
-            ["path" :as path]))
+            [shadow.resource :as rc]))
 
 (goog-define DEV-RELEASE false)
 (defonce dev-release? DEV-RELEASE)
@@ -260,6 +259,10 @@
 
 (def config-default-content (rc/inline "config.edn"))
 
+;; Desktop only as other platforms requires better understanding of their
+;; multi-graph workflows and optimal place for a "global" dir
+(def global-config-enabled? util/electron?)
+
 (defonce idb-db-prefix "logseq-db/")
 (defonce local-db-prefix "logseq_local_")
 (defonce local-handle "handle")
@@ -369,21 +372,13 @@
                         page-name)]
     (get-file-path repo-url (str sub-dir "/" page-basename "." ext))))
 
-(defn get-config-path
+(defn get-repo-config-path
   ([]
-   (get-config-path (state/get-current-repo)))
+   (get-repo-config-path (state/get-current-repo)))
   ([repo]
    (when repo
      (get-file-path repo (str app-name "/" config-file)))))
 
-(defn get-global-config-dir
-  []
-  (path/join (state/get-root-dir) "config"))
-
-(defn get-global-config-path
-  []
-  (path/join (state/get-root-dir) "config" "config.edn"))
-
 (defn get-metadata-path
   ([]
    (get-metadata-path (state/get-current-repo)))

+ 3 - 2
src/main/frontend/context/i18n.cljs

@@ -1,6 +1,7 @@
 (ns frontend.context.i18n
-  "Handles translation for the entire application. The dependencies for this ns
-  must be small since it is used throughout the application."
+  "This ns is a system component that handles translation for the entire
+  application. The ns dependencies for this ns must be small since it is used
+  throughout the application."
   (:require [frontend.dicts :as dicts]
             [frontend.modules.shortcut.dicts :as shortcut-dicts]
             [tongue.core :as tongue]

+ 5 - 2
src/main/frontend/handler.cljs

@@ -21,6 +21,8 @@
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.user :as user-handler]
+            [frontend.handler.repo-config :as repo-config-handler]
+            [frontend.handler.global-config :as global-config-handler]
             [frontend.idb :as idb]
             [frontend.mobile.util :as mobile-util]
             [frontend.modules.instrumentation.core :as instrument]
@@ -84,7 +86,9 @@
        {:repos repos}
        old-db-schema
        (fn [repo]
-         (file-handler/restore-config! repo)))
+         (repo-config-handler/start {:repo repo})
+         (when (config/global-config-enabled?)
+           (global-config-handler/start {:repo repo}))))
       (p/then
        (fn []
          ;; try to load custom css only for current repo
@@ -115,7 +119,6 @@
 
          (watch-for-date!)
          (file-handler/watch-for-current-graph-dir!)
-         (file-handler/watch-for-global-config-dir!)
          (state/pub-event! [:graph/restored (state/get-current-repo)])))
       (p/catch (fn [error]
                  (log/error :exception error)))))

+ 1 - 43
src/main/frontend/handler/common.cljs

@@ -2,16 +2,13 @@
   (:require [cljs-bean.core :as bean]
             [cljs.reader :as reader]
             [clojure.string :as string]
-            [frontend.config :as config]
             [frontend.date :as date]
-            [frontend.db :as db]
             [frontend.state :as state]
             [frontend.util :as util]
             [frontend.util.property :as property]
             [goog.object :as gobj]
             ["ignore" :as Ignore]
-            [lambdaisland.glogi :as log]
-            [borkdude.rewrite-edn :as rewrite]))
+            [lambdaisland.glogi :as log]))
 
 (defn copy-to-clipboard-without-id-property!
   [format raw-text html]
@@ -50,15 +47,6 @@
                 (hidden? path patterns))) files)
     files))
 
-;; TODO: Rename to get-repo-config-content
-(defn get-config
-  [repo-url]
-  (db/get-file repo-url (config/get-config-path)))
-
-(defn get-global-config-content
-  [repo-url]
-  (db/get-file repo-url (config/get-global-config-path)))
-
 (defn safe-read-string
   [content error-message-or-handler]
   (try
@@ -70,26 +58,6 @@
         (println error-message-or-handler))
       {})))
 
-(defn read-config
-  [content]
-  (safe-read-string content
-                    (fn [_e]
-                      (state/pub-event! [:backup/broken-config (state/get-current-repo) content])
-                      (reader/read-string config/config-default-content))))
-
-(defn reset-config!
-  [repo-url content]
-  (when-let [content (or content (get-config repo-url))]
-    (let [config (read-config content)]
-      (state/set-config! repo-url config)
-      config)))
-
-(defn reset-global-config!
-  [content]
-  (let [config (reader/read-string content)]
-    (state/set-global-config! config)
-    config))
-
 (defn read-metadata!
   [content]
   (try
@@ -129,16 +97,6 @@
   (let [position [(gobj/get e "clientX") (gobj/get e "clientY")]]
     (state/show-custom-context-menu! context-menu-content position)))
 
-(defn parse-config
-  "Parse configuration from file `content` such as from config.edn."
-  [content]
-  (try
-    (rewrite/parse-string content)
-    (catch :default e
-      (log/error :parse/config-failed e)
-      (state/pub-event! [:backup/broken-config (state/get-current-repo) content])
-      (rewrite/parse-string config/config-default-content))))
-
 (defn listen-to-scroll!
   [element]
   (let [*scroll-timer (atom nil)]

+ 83 - 0
src/main/frontend/handler/common/file.cljs

@@ -0,0 +1,83 @@
+(ns frontend.handler.common.file
+  "Common file related fns for handlers"
+  (:require [frontend.util :as util]
+            [frontend.config :as config]
+            [frontend.state :as state]
+            [frontend.db :as db]
+            ["/frontend/utils" :as utils]
+            [frontend.mobile.util :as mobile-util]
+            [logseq.graph-parser :as graph-parser]
+            [logseq.graph-parser.util :as gp-util]
+            [logseq.graph-parser.config :as gp-config]
+            [lambdaisland.glogi :as log]))
+
+(defn- page-exists-in-another-file
+  "Conflict of files towards same page"
+  [repo-url page file]
+  (when-let [page-name (:block/name page)]
+    (let [current-file (:file/path (db/get-page-file repo-url page-name))]
+      (when (not= file current-file)
+        current-file))))
+
+(defn- get-delete-blocks [repo-url first-page file]
+  (let [delete-blocks (->
+                       (concat
+                        (db/delete-file-blocks! repo-url file)
+                        (when first-page (db/delete-page-blocks repo-url (:block/name first-page))))
+                       (distinct))]
+    (when-let [current-file (page-exists-in-another-file repo-url first-page file)]
+      (when (not= file current-file)
+        (let [error (str "Page already exists with another file: " current-file ", current file: " file)]
+          (state/pub-event! [:notification/show
+                             {:content error
+                              :status :error
+                              :clear? false}]))))
+    delete-blocks))
+
+(defn reset-file!
+  "Main fn for updating a db with the results of a parsed file"
+  ([repo-url file content]
+   (reset-file! repo-url file content {}))
+  ([repo-url file content {:keys [verbose] :as options}]
+   (try
+     (let [electron-local-repo? (and (util/electron?)
+                                     (config/local-db? repo-url))
+           file (cond
+                  (and electron-local-repo?
+                       util/win32?
+                       (utils/win32 file))
+                  file
+
+                  (and electron-local-repo? (or
+                                             util/win32?
+                                             (not= "/" (first file))))
+                  (str (config/get-repo-dir repo-url) "/" file)
+
+                  (and (mobile-util/native-android?) (not= "/" (first file)))
+                  file
+
+                  (and (mobile-util/native-ios?) (not= "/" (first file)))
+                  file
+
+                  :else
+                  file)
+           file (gp-util/path-normalize file)
+           new? (nil? (db/entity [:file/path file]))]
+       (:tx
+        (graph-parser/parse-file
+         (db/get-db repo-url false)
+         file
+         content
+         (merge (dissoc options :verbose)
+                {:new? new?
+                 :delete-blocks-fn (partial get-delete-blocks repo-url)
+                 :extract-options (merge
+                                   {:user-config (state/get-config)
+                                    :date-formatter (state/get-date-formatter)
+                                    :page-name-order (state/page-name-order)
+                                    :block-pattern (config/get-block-pattern (gp-util/get-format file))
+                                    :supported-formats (gp-config/supported-formats)}
+                                   (when (some? verbose) {:verbose verbose}))}))))
+     (catch :default e
+       (prn "Reset file failed " {:file file})
+       (log/error :exception e)))))

+ 29 - 3
src/main/frontend/handler/config.cljs

@@ -1,12 +1,38 @@
 (ns frontend.handler.config
+  "Fns for setting repo config"
   (:require [frontend.state :as state]
             [frontend.handler.file :as file-handler]
-            [frontend.config :as config]))
+            [frontend.handler.repo-config :as repo-config-handler]
+            [frontend.config :as config]
+            [frontend.db :as db]
+            [borkdude.rewrite-edn :as rewrite]
+            [lambdaisland.glogi :as log]))
+
+(defn parse-repo-config
+  "Parse repo configuration file content"
+  [content]
+  (try
+    (rewrite/parse-string content)
+    (catch :default e
+      (log/error :parse/config-failed e)
+      (state/pub-event! [:backup/broken-config (state/get-current-repo) content])
+      (rewrite/parse-string config/config-default-content))))
+
+(defn- repo-config-set-key-value
+  [path k v]
+  (when-let [repo (state/get-current-repo)]
+    (when-let [content (db/get-file path)]
+      (repo-config-handler/read-repo-config repo content)
+      (let [result (parse-repo-config content)
+            ks (if (vector? k) k [k])
+            new-result (rewrite/assoc-in result ks v)
+            new-content (str new-result)]
+        (file-handler/set-file-content! repo path new-content)))))
 
 (defn set-config!
   [k v]
-  (let [path (config/get-config-path)]
-    (file-handler/edn-file-set-key-value path k v)))
+  (let [path (config/get-repo-config-path)]
+    (repo-config-set-key-value path k v)))
 
 (defn toggle-ui-show-brackets! []
   (let [show-brackets? (state/show-brackets?)]

+ 6 - 3
src/main/frontend/handler/events.cljs

@@ -32,6 +32,8 @@
             [frontend.handler.page :as page-handler]
             [frontend.handler.plugin :as plugin-handler]
             [frontend.handler.repo :as repo-handler]
+            [frontend.handler.global-config :as global-config-handler]
+            [frontend.handler.repo-config :as repo-config-handler]
             [frontend.handler.route :as route-handler]
             [frontend.handler.search :as search-handler]
             [frontend.handler.ui :as ui-handler]
@@ -113,12 +115,13 @@
      (do
        (state/set-current-repo! graph)
        ;; load config
-       (common-handler/reset-config! graph nil)
+       (repo-config-handler/restore-repo-config! graph)
        (st/refresh!)
        (when-not (= :draw (state/get-current-route))
          (route-handler/redirect-to-home!))
        (when-let [dir-name (config/get-repo-dir graph)]
          (fs/watch-dir! dir-name))
+       (global-config-handler/watch-for-global-config-dir! graph)
        (srs/update-cards-due-count!)
        (state/pub-event! [:graph/ready graph])
        (repo-handler/refresh-repos!)
@@ -451,7 +454,7 @@
               (state/set-current-repo! current-repo)
               (db/listen-and-persist! current-repo)
               (db/persist-if-idle! current-repo)
-              (file-handler/restore-config! current-repo)
+              (repo-config-handler/restore-repo-config! current-repo)
               (.watch mobile-util/fs-watcher #js {:path current-repo-dir})
               (when graph-switch-f (graph-switch-f current-repo true))
               (file-sync-restart!))))
@@ -503,7 +506,7 @@
 
 (defmethod handle :backup/broken-config [[_ repo content]]
   (when (and repo content)
-    (let [path (config/get-config-path)
+    (let [path (config/get-repo-config-path)
           broken-path (string/replace path "/config.edn" "/broken-config.edn")]
       (p/let [_ (fs/write-file! repo (config/get-repo-dir repo) broken-path content {})
               _ (file-handler/alter-file repo path config/config-default-content {:skip-compare? true})]

+ 18 - 124
src/main/frontend/handler/file.cljs

@@ -1,13 +1,13 @@
 (ns frontend.handler.file
   (:refer-clojure :exclude [load-file])
-  (:require ["/frontend/utils" :as utils]
-            [borkdude.rewrite-edn :as rewrite]
-            [frontend.config :as config]
+  (:require [frontend.config :as config]
             [frontend.db :as db]
             [frontend.fs :as fs]
             [frontend.fs.nfs :as nfs]
             [frontend.fs.capacitor-fs :as capacitor-fs]
-            [frontend.handler.common :as common-handler]
+            [frontend.handler.common.file :as file-common-handler]
+            [frontend.handler.repo-config :as repo-config-handler]
+            [frontend.handler.global-config :as global-config-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.state :as state]
             [frontend.util :as util]
@@ -15,9 +15,8 @@
             [electron.ipc :as ipc]
             [lambdaisland.glogi :as log]
             [promesa.core :as p]
-            [frontend.mobile.util :as mobile]
+            [frontend.mobile.util :as mobile-util]
             [logseq.graph-parser.config :as gp-config]
-            [logseq.graph-parser :as graph-parser]
             ["path" :as path]))
 
 ;; TODO: extract all git ops using a channel
@@ -53,20 +52,6 @@
   [files]
   (keep-formats files (gp-config/img-formats)))
 
-(defn restore-config!
-  ([repo-url]
-   (restore-config! repo-url nil))
-  ([repo-url config-content]
-   (let [config-content (if config-content config-content
-                          (common-handler/get-config repo-url))]
-     (when config-content
-       (common-handler/reset-config! repo-url config-content)))))
-
-(defn set-global-config-state!
-  [repo-url]
-  (let [config-content (common-handler/get-global-config-content repo-url)]
-    (common-handler/reset-global-config! config-content)))
-
 (defn load-files-contents!
   [repo-url files ok-handler]
   (let [images (only-image-formats files)
@@ -93,82 +78,12 @@
     (util/electron?)
     (ipc/ipc "backupDbFile" repo-url path db-content content)
 
-    (mobile/native-platform?)
+    (mobile-util/native-platform?)
     (capacitor-fs/backup-file-handle-changed! repo-url path db-content)
 
     :else
     nil))
 
-(defn- page-exists-in-another-file
-  "Conflict of files towards same page"
-  [repo-url page file]
-  (when-let [page-name (:block/name page)]
-    (let [current-file (:file/path (db/get-page-file repo-url page-name))]
-      (when (not= file current-file)
-        current-file))))
-
-(defn- get-delete-blocks [repo-url first-page file]
-  (let [delete-blocks (->
-                       (concat
-                        (db/delete-file-blocks! repo-url file)
-                        (when first-page (db/delete-page-blocks repo-url (:block/name first-page))))
-                       (distinct))]
-    (when-let [current-file (page-exists-in-another-file repo-url first-page file)]
-      (when (not= file current-file)
-        (let [error (str "Page already exists with another file: " current-file ", current file: " file)]
-          (state/pub-event! [:notification/show
-                             {:content error
-                              :status :error
-                              :clear? false}]))))
-    delete-blocks))
-
-(defn reset-file!
-  ([repo-url file content]
-   (reset-file! repo-url file content {}))
-  ([repo-url file content {:keys [verbose] :as options}]
-   (try
-     (let [electron-local-repo? (and (util/electron?)
-                                    (config/local-db? repo-url))
-          file (cond
-                 (and electron-local-repo?
-                      util/win32?
-                      (utils/win32 file))
-                 file
-
-                 (and electron-local-repo? (or
-                                            util/win32?
-                                            (not= "/" (first file))))
-                 (str (config/get-repo-dir repo-url) "/" file)
-
-                 (and (mobile/native-android?) (not= "/" (first file)))
-                 file
-
-                 (and (mobile/native-ios?) (not= "/" (first file)))
-                 file
-
-                 :else
-                 file)
-          file (gp-util/path-normalize file)
-          new? (nil? (db/entity [:file/path file]))]
-      (:tx
-       (graph-parser/parse-file
-        (db/get-db repo-url false)
-        file
-        content
-        (merge (dissoc options :verbose)
-               {:new? new?
-                :delete-blocks-fn (partial get-delete-blocks repo-url)
-                :extract-options (merge
-                                  {:user-config (state/get-config)
-                                   :date-formatter (state/get-date-formatter)
-                                   :page-name-order (state/page-name-order)
-                                   :block-pattern (config/get-block-pattern (gp-util/get-format file))
-                                   :supported-formats (gp-config/supported-formats)}
-                                  (when (some? verbose) {:verbose verbose}))}))))
-     (catch :default e
-       (prn "Reset file failed " {:file file})
-       (log/error :exception e)))))
-
 ;; TODO: Remove this function in favor of `alter-files`
 (defn alter-file
   [repo path content {:keys [reset? re-render-root? from-disk? skip-compare? new-graph? verbose]
@@ -176,12 +91,11 @@
                            re-render-root? false
                            from-disk? false
                            skip-compare? false}}]
-  (prn :ALTER repo path)
   (let [original-content (db/get-file repo path)
         write-file! (if from-disk?
                       #(p/resolved nil)
-                      #(let [path-dir (if (= (path/dirname path) (config/get-global-config-dir))
-                                        (config/get-global-config-dir)
+                      #(let [path-dir (if (= (path/dirname path) (global-config-handler/global-config-dir))
+                                        (global-config-handler/global-config-dir)
                                         (config/get-repo-dir repo))]
                          (fs/write-file! repo path-dir path content
                                         (assoc (when original-content {:old-content original-content})
@@ -195,24 +109,24 @@
                         [[:db/retract page-id :block/alias]
                          [:db/retract page-id :block/tags]]
                         opts))
-        (reset-file! repo path content (merge opts
-                                              (when (some? verbose) {:verbose verbose}))))
+        (file-common-handler/reset-file! repo path content (merge opts
+                                                                  (when (some? verbose) {:verbose verbose}))))
       (db/set-file-content! repo path content opts))
     (util/p-handle (write-file!)
                    (fn [_]
                      (cond
-                       (= path (config/get-config-path repo))
-                       (restore-config! repo)
+                       (= path (config/get-repo-config-path repo))
+                       (repo-config-handler/restore-repo-config! repo)
 
-                       (= path (config/get-global-config-path))
-                       (set-global-config-state! repo)
+                       (= path (global-config-handler/global-config-path))
+                       (global-config-handler/restore-global-config! repo)
 
                        (= path (config/get-custom-css-path repo))
                        (ui-handler/add-style-if-exists!))
 
                      (when re-render-root? (ui-handler/re-render-root!)))
                    (fn [error]
-                     (when (= path (config/get-global-config-path))
+                     (when (= path (global-config-handler/global-config-path))
                        (state/pub-event! [:notification/show
                                          {:content (str "Failed to write to file " path)
                                           :status :error}]))
@@ -272,7 +186,7 @@
     (when update-db?
       (doseq [[path content] files]
         (if reset?
-          (reset-file! repo path content)
+          (file-common-handler/reset-file! repo path content)
           (db/set-file-content! repo path content))))
     (alter-files-handler! repo files opts file->content)))
 
@@ -283,15 +197,6 @@
       (fs/unwatch-dir! dir)
       (fs/watch-dir! dir))))
 
-(defn watch-for-global-config-dir!
-  []
-  (let [dir (config/get-global-config-dir)
-        repo-dir (config/get-repo-dir (state/get-current-repo))]
-    (fs/unwatch-dir! dir)
-    ;; Even a global dir needs to know it's current graph in order to send
-    ;; change events to the right window and graph db
-    (fs/watch-dir! dir {:current-repo-dir repo-dir})))
-
 (defn create-metadata-file
   [repo-url encrypted?]
   (let [repo-dir (config/get-repo-dir repo-url)
@@ -301,7 +206,7 @@
     (p/let [_ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir config/app-name))
             file-exists? (fs/create-if-not-exists repo-url repo-dir file-path default-content)]
       (when-not file-exists?
-        (reset-file! repo-url path default-content)))))
+        (file-common-handler/reset-file! repo-url path default-content)))))
 
 (defn create-pages-metadata-file
   [repo-url]
@@ -312,15 +217,4 @@
     (p/let [_ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir config/app-name))
             file-exists? (fs/create-if-not-exists repo-url repo-dir file-path default-content)]
       (when-not file-exists?
-        (reset-file! repo-url path default-content)))))
-
-(defn edn-file-set-key-value
-  [path k v]
-  (when-let [repo (state/get-current-repo)]
-    (when-let [content (db/get-file path)]
-      (common-handler/read-config content)
-      (let [result (common-handler/parse-config content)
-            ks (if (vector? k) k [k])
-            new-result (rewrite/assoc-in result ks v)
-            new-content (str new-result)]
-        (set-file-content! repo path new-content)))))
+        (file-common-handler/reset-file! repo-url path default-content)))))

+ 79 - 0
src/main/frontend/handler/global_config.cljs

@@ -0,0 +1,79 @@
+(ns frontend.handler.global-config
+  "This ns is a system component that encapsulates global config functionality
+  and defines how to start and stop it. Unlike repo config, this manages a directory
+for configuration. This app component depends on a repo."
+  (:require [frontend.config :as config]
+            [frontend.fs :as fs]
+            [frontend.handler.common.file :as file-common-handler]
+            [frontend.state :as state]
+            [cljs.reader :as reader]
+            [frontend.db :as db]
+            [promesa.core :as p]
+            [shadow.resource :as rc]
+            [electron.ipc :as ipc]
+            ["path" :as path]))
+
+;; Use defonce to avoid broken state on dev reload
+;; Also known as home directory a.k.a. '~'
+(defonce root-dir
+  (atom nil))
+
+(defn global-config-dir
+  []
+  (path/join @root-dir "config"))
+
+(defn global-config-path
+  []
+  (path/join @root-dir "config" "config.edn"))
+
+(defn- set-global-config-state!
+  [content]
+  (let [config (reader/read-string content)]
+    (state/set-global-config! config)
+    config))
+
+(def default-content (rc/inline "global-config.edn"))
+
+(defn- create-global-config-file-if-not-exists
+  [repo-url]
+  (let [config-dir (global-config-dir)
+        config-path (global-config-path)]
+    (p/let [_ (fs/mkdir-if-not-exists config-dir)
+            file-exists? (fs/create-if-not-exists repo-url config-dir config-path default-content)]
+           (when-not file-exists?
+             (file-common-handler/reset-file! repo-url config-path default-content)
+             (set-global-config-state! default-content)))))
+
+(defn- get-global-config-content
+  [repo-url]
+  (db/get-file repo-url (global-config-path)))
+
+(defn restore-global-config!
+  "Sets global config state from db"
+  [repo-url]
+  (let [config-content (get-global-config-content repo-url)]
+    (set-global-config-state! config-content)))
+
+(defn watch-for-global-config-dir!
+  "Watches global config dir for given repo/db"
+  [repo]
+  (let [dir (global-config-dir)
+        repo-dir (config/get-repo-dir repo)]
+    ;; Don't want multiple file watchers, especially when switching graphs
+    (fs/unwatch-dir! dir)
+    ;; Even a global dir needs to know it's current graph in order to send
+    ;; change events to the right window and graph db
+    (fs/watch-dir! dir {:current-repo-dir repo-dir})))
+
+(defn start
+  "This component has four responsibilities on start:
+- Fetch root-dir for later use with config paths
+- Manage db and 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"
+  [{:keys [repo]}]
+  (p/let [root-dir' (ipc/ipc "getLogseqDotDirRoot")
+          _ (reset! root-dir root-dir')
+          _ (restore-global-config! repo)
+          _ (create-global-config-file-if-not-exists repo)
+          _ (watch-for-global-config-dir! repo)]))

+ 2 - 4
src/main/frontend/handler/plugin.cljs

@@ -595,10 +595,8 @@
 
   (state/set-state! :plugin/indicator-text "LOADING")
 
-  (->
-    (p/let [root (get-ls-dotdir-root)
-            _ (.setupPluginCore js/LSPlugin (bean/->js {:localUserConfigRoot root :dotConfigRoot root}))
-            _ (state/set-state! :config/root-dir root)
+  (-> (p/let [root            (get-ls-dotdir-root)
+              _               (.setupPluginCore js/LSPlugin (bean/->js {:localUserConfigRoot root :dotConfigRoot root}))
 
               clear-commands! (fn [pid]
                                 ;; commands

+ 13 - 23
src/main/frontend/handler/repo.cljs

@@ -9,9 +9,12 @@
             [frontend.fs.nfs :as nfs]
             [frontend.handler.common :as common-handler]
             [frontend.handler.file :as file-handler]
+            [frontend.handler.repo-config :as repo-config-handler]
+            [frontend.handler.common.file :as file-common-handler]
             [frontend.handler.route :as route-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.metadata :as metadata-handler]
+            [frontend.handler.global-config :as global-config-handler]
             [frontend.idb :as idb]
             [frontend.search :as search]
             [frontend.spec :as spec]
@@ -34,20 +37,6 @@
 ;; 1. User changes the config.edn directly in logseq.com (fn: alter-file)
 ;; 2. Git pulls the new change (fn: load-files)
 
-(defn create-config-file-if-not-exists
-  [repo-url]
-  (spec/validate :repos/url repo-url)
-  (let [repo-dir (config/get-repo-dir repo-url)
-        app-dir config/app-name
-        dir (str repo-dir "/" app-dir)]
-    (p/let [_ (fs/mkdir-if-not-exists dir)]
-      (let [default-content config/config-default-content
-            path (str app-dir "/" config/config-file)]
-        (p/let [file-exists? (fs/create-if-not-exists repo-url repo-dir (str app-dir "/" config/config-file) default-content)]
-          (when-not file-exists?
-            (file-handler/reset-file! repo-url path default-content)
-            (common-handler/reset-config! repo-url default-content)))))))
-
 (defn create-contents-file
   [repo-url]
   (spec/validate :repos/url repo-url)
@@ -67,7 +56,7 @@
         (p/let [_ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir pages-dir))
                 file-exists? (fs/create-if-not-exists repo-url repo-dir file-path default-content)]
           (when-not file-exists?
-            (file-handler/reset-file! repo-url path default-content)))))))
+            (file-common-handler/reset-file! repo-url path default-content)))))))
 
 (defn create-custom-theme
   [repo-url]
@@ -79,7 +68,7 @@
     (p/let [_ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir config/app-name))
             file-exists? (fs/create-if-not-exists repo-url repo-dir file-path default-content)]
       (when-not file-exists?
-        (file-handler/reset-file! repo-url path default-content)))))
+        (file-common-handler/reset-file! repo-url path default-content)))))
 
 (defn create-dummy-notes-page
   [repo-url content]
@@ -89,7 +78,7 @@
         file-path (str "/" path)]
     (p/let [_ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir (config/get-pages-directory)))
             _file-exists? (fs/create-if-not-exists repo-url repo-dir file-path content)]
-      (file-handler/reset-file! repo-url path content))))
+      (file-common-handler/reset-file! repo-url path content))))
 
 (defn- create-today-journal-if-not-exists
   [repo-url {:keys [content]}]
@@ -123,7 +112,7 @@
                 _ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir (config/get-journals-directory)))
                 file-exists? (fs/file-exists? repo-dir file-path)]
           (when-not file-exists?
-            (p/let [_ (file-handler/reset-file! repo-url path content)]
+            (p/let [_ (file-common-handler/reset-file! repo-url path content)]
               (p/let [_ (fs/create-if-not-exists repo-url repo-dir file-path content)]
                 (when-not (state/editing?)
                   (ui-handler/re-render-root!)))))
@@ -140,7 +129,7 @@
              _ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir (str config/app-name "/" config/recycle-dir)))
              _ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir (config/get-journals-directory)))
              _ (file-handler/create-metadata-file repo-url encrypted?)
-             _ (create-config-file-if-not-exists repo-url)
+             _ (repo-config-handler/create-config-file-if-not-exists repo-url)
              _ (create-contents-file repo-url)
              _ (create-custom-theme repo-url)]
        (state/pub-event! [:page/create-today-journal repo-url])))))
@@ -280,9 +269,9 @@
   (spec/validate :repos/url repo-url)
   (route-handler/redirect-to-home!)
   (state/set-parsing-state! {:graph-loading? true})
-  (let [config (or (when-let [content (some-> (first (filter #(= (config/get-config-path repo-url) (:file/path %)) nfs-files))
+  (let [config (or (when-let [content (some-> (first (filter #(= (config/get-repo-config-path repo-url) (:file/path %)) nfs-files))
                                               :file/content)]
-                     (common-handler/read-config content))
+                     (repo-config-handler/read-repo-config repo-url content))
                    (state/get-config repo-url))
         ;; NOTE: Use config while parsing. Make sure it's the corrent journal title format
         _ (state/set-config! repo-url config)
@@ -368,7 +357,7 @@
                (let [tutorial (t :tutorial/text)
                      tutorial (string/replace-first tutorial "$today" (date/today))]
                  (create-today-journal-if-not-exists repo {:content tutorial})))
-             (create-config-file-if-not-exists repo)
+             (repo-config-handler/create-config-file-if-not-exists repo)
              (create-contents-file repo)
              (create-custom-theme repo)
              (state/set-db-restoring! false)
@@ -391,7 +380,8 @@
   [repo]
   (p/let [_ (state/set-db-restoring! true)
           _ (db/restore-graph! repo)]
-         (file-handler/restore-config! repo)
+         (repo-config-handler/restore-repo-config! repo)
+         (global-config-handler/restore-global-config! repo)
          ;; Don't have to unlisten the old listerner, as it will be destroyed with the conn
          (db/listen-and-persist! repo)
          (ui-handler/add-style-if-exists!)

+ 59 - 0
src/main/frontend/handler/repo_config.cljs

@@ -0,0 +1,59 @@
+(ns frontend.handler.repo-config
+  "This ns is a system component that encapsulates repo config functionality and
+  defines how to start it. This component only concerns itself with one
+  user-facing repo config, logseq/config.edn. In the future it may manage more.
+  This app component depends on a repo."
+  (:require [frontend.db :as db]
+            [frontend.config :as config]
+            [frontend.state :as state]
+            [frontend.handler.common :as common-handler]
+            [frontend.handler.common.file :as file-common-handler]
+            [cljs.reader :as reader]
+            [frontend.fs :as fs]
+            [promesa.core :as p]
+            [frontend.spec :as spec]))
+
+(defn- get-repo-config-content
+  [repo-url]
+  (db/get-file repo-url (config/get-repo-config-path)))
+
+(defn read-repo-config
+  [repo content]
+  (common-handler/safe-read-string
+   content
+   (fn [_e]
+     (state/pub-event! [:backup/broken-config repo content])
+     (reader/read-string config/config-default-content))))
+
+(defn set-repo-config-state!
+  [repo-url content]
+  (let [config (read-repo-config repo-url content)]
+    (state/set-config! repo-url config)
+    config))
+
+(defn create-config-file-if-not-exists
+  [repo-url]
+  (spec/validate :repos/url repo-url)
+  (let [repo-dir (config/get-repo-dir repo-url)
+        app-dir config/app-name
+        dir (str repo-dir "/" app-dir)]
+    (p/let [_ (fs/mkdir-if-not-exists dir)]
+           (let [default-content config/config-default-content
+                  path (str app-dir "/" config/config-file)]
+             (p/let [file-exists? (fs/create-if-not-exists repo-url repo-dir (str app-dir "/" config/config-file) default-content)]
+                    (when-not file-exists?
+                      (file-common-handler/reset-file! repo-url path default-content)
+                      (set-repo-config-state! repo-url default-content)))))))
+
+(defn restore-repo-config!
+  "Sets repo config state from db"
+  [repo-url]
+  (let [config-content (get-repo-config-content repo-url)]
+    (set-repo-config-state! repo-url config-content)))
+
+(defn start
+  "This component only has one reponsibility on start, to manage db and ui state
+  from repo config. It does not manage the repo directory, logseq/, as that is
+  loosely done by repo-handler"
+  [{:keys [repo]}]
+  (restore-repo-config! repo))

+ 28 - 28
src/main/frontend/handler/web/nfs.cljs

@@ -11,6 +11,7 @@
             [frontend.fs :as fs]
             [frontend.fs.nfs :as nfs]
             [frontend.handler.common :as common-handler]
+            [frontend.handler.global-config :as global-config-handler]
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.route :as route-handler]
             [frontend.idb :as idb]
@@ -329,35 +330,34 @@
          (state/set-graph-syncing? true))
        (->
         (p/let [handle (when-not electron? (idb/get-item handle-path))]
-          (when (or handle electron? mobile-native?)   ; electron doesn't store the file handle
-            (p/let [_ (when handle (nfs/verify-permission repo handle true))
-                    local-files-result
-                    (fs/get-files (if nfs? handle
-                                    (config/get-local-dir repo))
-                                  (fn [path handle]
-                                    (when nfs?
-                                      (swap! path-handles assoc path handle))))
-                    global-dir (config/get-global-config-dir)
-                    global-config-supported? (and electron? (fs/dir-exists? global-dir))
-                    global-files-result (if global-config-supported?
-                                          (fs/get-files global-dir (constantly nil))
-                                          [])
-                    new-local-files (-> (->db-files mobile-native? electron? dir-name local-files-result)
-                                        (remove-ignore-files dir-name nfs?))
-                    new-global-files (-> (->db-files mobile-native? electron? global-dir global-files-result)
-                                         (remove-ignore-files global-dir nfs?))
-                    new-files (concat new-local-files new-global-files)
+               (when (or handle electron? mobile-native?)   ; electron doesn't store the file handle
+                 (p/let [_ (when handle (nfs/verify-permission repo handle true))
+                         local-files-result
+                         (fs/get-files (if nfs? handle
+                                         (config/get-local-dir repo))
+                                       (fn [path handle]
+                                         (when nfs?
+                                           (swap! path-handles assoc path handle))))
+                         global-dir (global-config-handler/global-config-dir)
+                         global-files-result (if (config/global-config-enabled?)
+                                               (fs/get-files global-dir (constantly nil))
+                                               [])
+                         new-local-files (-> (->db-files mobile-native? electron? dir-name local-files-result)
+                                             (remove-ignore-files dir-name nfs?))
+                         new-global-files (-> (->db-files mobile-native? electron? global-dir global-files-result)
+                                              (remove-ignore-files global-dir nfs?))
+                         new-files (concat new-local-files new-global-files)
 
-                    _ (when nfs?
-                        (let [file-paths (set (map :file/path new-files))]
-                          (swap! path-handles (fn [handles]
-                                                (->> handles
-                                                     (filter (fn [[path _handle]]
-                                                               (contains? file-paths
-                                                                          (string/replace-first path (str dir-name "/") ""))))
-                                                     (into {})))))
-                        (set-files! @path-handles))]
-                   (handle-diffs! repo nfs? old-files new-files handle-path path-handles re-index?))))
+                         _ (when nfs?
+                             (let [file-paths (set (map :file/path new-files))]
+                               (swap! path-handles (fn [handles]
+                                                     (->> handles
+                                                          (filter (fn [[path _handle]]
+                                                                    (contains? file-paths
+                                                                               (string/replace-first path (str dir-name "/") ""))))
+                                                          (into {})))))
+                             (set-files! @path-handles))]
+                        (handle-diffs! repo nfs? old-files new-files handle-path path-handles re-index?))))
         (p/catch (fn [error]
                    (log/error :nfs/load-files-error repo)
                    (log/error :exception error)))

+ 2 - 2
src/main/frontend/modules/instrumentation/posthog.cljs

@@ -1,7 +1,7 @@
 (ns frontend.modules.instrumentation.posthog
   (:require [frontend.config :as config]
             [frontend.util :as util]
-            [frontend.mobile.util :as mobile]
+            [frontend.mobile.util :as mobile-util]
             [frontend.version :refer [version]]
             ["posthog-js" :as posthog]
             [cljs-bean.core :as bean]))
@@ -12,7 +12,7 @@
 (defn register []
   (posthog/register
    (clj->js
-    {:app_type (let [platform (mobile/platform)]
+    {:app_type (let [platform (mobile-util/platform)]
                  (cond
                    (util/electron?)
                    "electron"

+ 4 - 4
src/main/frontend/modules/shortcut/core.cljs

@@ -1,6 +1,6 @@
 (ns frontend.modules.shortcut.core
   (:require [clojure.string :as str]
-            [frontend.handler.config :as config]
+            [frontend.handler.config :as config-handler]
             [frontend.handler.notification :as notification]
             [frontend.modules.shortcut.data-helper :as dh]
             [frontend.modules.shortcut.config :as shortcut-config]
@@ -224,9 +224,9 @@
      (let [k (first args)
            keystroke (str/trim @local)]
        (when-not (empty? keystroke)
-         (config/set-config! :shortcuts (merge
-                                         (:shortcuts (state/get-config))
-                                         {k keystroke}))))
+         (config-handler/set-config! :shortcuts (merge
+                                                 (:shortcuts (state/get-config))
+                                                 {k keystroke}))))
 
      (when-let [^js handler (::key-record-handler state)]
        (.dispose handler))

+ 5 - 4
src/main/frontend/modules/shortcut/data_helper.cljs

@@ -9,7 +9,8 @@
             [frontend.state :as state]
             [frontend.util :as util]
             [lambdaisland.glogi :as log]
-            [frontend.handler.common :as common-handler])
+            [frontend.handler.repo-config :as repo-config-handler]
+            [frontend.handler.config :as config-handler])
   (:import [goog.ui KeyboardShortcutHandler]))
 
 (defn get-bindings
@@ -138,15 +139,15 @@
 
 (defn remove-shortcut [k]
   (let [repo (state/get-current-repo)
-        path (config/get-config-path)]
+        path (config/get-repo-config-path)]
     (when-let [content (db/get-file path)]
-      (let [result (common-handler/parse-config content)
+      (let [result (config-handler/parse-repo-config content)
             new-result (rewrite/update
                         result
                         :shortcuts
                         #(dissoc (rewrite/sexpr %) k))
             new-content (str new-result)]
-        (common-handler/reset-config! repo new-content)
+        (repo-config-handler/set-repo-config-state! repo new-content)
         (file/set-file-content! repo path new-content)))))
 
 (defn get-group

+ 15 - 9
src/main/frontend/state.cljs

@@ -100,7 +100,6 @@
      :document/mode?                        document-mode?
 
      :config                                {}
-     :config/root-dir                       nil
      :block/component-editing-mode?         false
      :editor/hidden-editors                 #{}             ;; page names
      :editor/draw-mode?                     false
@@ -288,15 +287,26 @@
 ;; State that most user config is dependent on
 (declare get-current-repo)
 
+(defn merge-configs
+  "Merges user configs in given orders. All values are overriden except for maps
+  which are merged."
+  [& configs]
+  (apply merge-with
+    (fn merge-config [current new]
+      (if (and (map? current) (map? new))
+        (merge current new)
+        new))
+    configs))
+
 (defn get-config
   "User config for the given repo or current repo if none given"
   ([]
    (get-config (get-current-repo)))
   ([repo-url]
-   (merge default-config
-          ;; TODO: Confirm merging works for all cases
-          (get-in @state [:config ::global-config])
-          (get-in @state [:config repo-url]))))
+   (merge-configs
+    default-config
+    (get-in @state [:config ::global-config])
+    (get-in @state [:config repo-url]))))
 
 (defonce publishing? (atom nil))
 
@@ -654,10 +664,6 @@ Similar to re-frame subscriptions"
       (when-not (mobile-util/native-platform?)
         "local")))
 
-(defn get-root-dir
-  []
-  (:config/root-dir @state))
-
 (defn get-remote-repos
   []
   (get-in @state [:file-sync/remote-graphs :graphs]))

+ 32 - 0
src/main/frontend/state_test.cljs

@@ -0,0 +1,32 @@
+(ns frontend.state-test
+  (:require [clojure.test :refer [deftest is]]
+            [frontend.state :as state]))
+
+(deftest merge-configs
+  (let [global-config
+        {:shortcuts {:ui/toggle-theme "t z"}
+         :hidden []
+         :ui/enable-tooltip? true
+         :preferred-workflow :todo
+         :git-pull-secs 60}
+        local-config {:hidden ["foo" "bar"]
+                      :ui/enable-tooltip? false
+                      :preferred-workflow :now
+                      :git-pull-secs 120}]
+    (is (= local-config
+           (dissoc (state/merge-configs global-config local-config) :shortcuts))
+        "Later config overrides all non-map values")
+    (is (= {:start-of-week 6 :shortcuts {:ui/toggle-theme "t z"}}
+           (select-keys (state/merge-configs {:start-of-week 6}
+                                             global-config
+                                             local-config)
+                        [:start-of-week :shortcuts]))
+        "Earlier configs set default values"))
+
+  (is (= {:shortcuts {:ui/toggle-theme "t z"
+                      :ui/toggle-brackets "t b"
+                      :editor/up ["ctrl+p" "up"]}}
+         (state/merge-configs {:shortcuts {:ui/toggle-theme "t z"}}
+                              {:shortcuts {:ui/toggle-brackets "t b"}}
+                              {:shortcuts {:editor/up ["ctrl+p" "up"]}}))
+      "Map values get merged across configs"))

+ 1 - 1
src/main/logseq/api.cljs

@@ -113,7 +113,7 @@
 
 (def ^:export get_current_graph_configs
   (fn []
-    (some-> (get (:config @state/state) (state/get-current-repo))
+    (some-> (state/get-config)
             (normalize-keyword-for-json)
             (bean/->js))))
 

+ 12 - 0
templates/global-config.edn

@@ -0,0 +1,12 @@
+;; This global config file is used by all graphs.
+;; Your graph's logseq/config.edn overrides config keys in this file
+;; except for maps which are merged.
+;; As an example of merging, the following global and local configs:
+;;   {:shortcuts {:ui/toggle-theme "t z"}}
+;;   {:shortcuts {:ui/toggle-brackets "t b"}}
+;;
+;;  would result in the final config:
+;;   {:shortcuts {:ui/toggle-theme "t z"
+;;                :ui/toggle-brackets "t b"}}
+
+{}