Browse Source

Merge remote-tracking branch 'upstream/master' into whiteboards

Peng Xiao 3 years ago
parent
commit
4d86f4f53f
84 changed files with 1584 additions and 1078 deletions
  1. 23 4
      .clj-kondo/config.edn
  2. 19 15
      .github/workflows/build-desktop-release.yml
  3. 1 1
      deps.edn
  4. 1 1
      deps/db/deps.edn
  5. 1 1
      deps/graph-parser/deps.edn
  6. 12 15
      deps/graph-parser/src/logseq/graph_parser.cljs
  7. 1 2
      deps/graph-parser/src/logseq/graph_parser/cli.cljs
  8. 10 10
      e2e-tests/accessibility.spec.ts
  9. 1 1
      package.json
  10. 1 1
      resources/css/common.css
  11. 0 2
      resources/css/tooltip.css
  12. 4 1
      resources/forge.config.js
  13. 4 0
      resources/js/preload.js
  14. 10 7
      src/electron/electron/core.cljs
  15. 2 2
      src/electron/electron/exceptions.cljs
  16. 105 60
      src/electron/electron/fs_watcher.cljs
  17. 23 16
      src/electron/electron/handler.cljs
  18. 1 1
      src/electron/electron/search.cljs
  19. 8 7
      src/electron/electron/updater.cljs
  20. 1 1
      src/electron/electron/utils.cljs
  21. 3 1
      src/electron/electron/window.cljs
  22. 1 1
      src/main/electron/listener.cljs
  23. 10 15
      src/main/frontend/components/block.cljs
  24. 15 0
      src/main/frontend/components/block.css
  25. 2 2
      src/main/frontend/components/content.cljs
  26. 2 7
      src/main/frontend/components/editor.cljs
  27. 3 3
      src/main/frontend/components/encryption.cljs
  28. 8 8
      src/main/frontend/components/file_sync.cljs
  29. 8 4
      src/main/frontend/components/header.cljs
  30. 2 2
      src/main/frontend/components/page.cljs
  31. 1 1
      src/main/frontend/components/page_menu.cljs
  32. 2 2
      src/main/frontend/components/plugins.cljs
  33. 6 6
      src/main/frontend/components/plugins_settings.cljs
  34. 53 16
      src/main/frontend/components/right_sidebar.cljs
  35. 12 2
      src/main/frontend/components/settings.cljs
  36. 1 1
      src/main/frontend/components/shortcut.cljs
  37. 10 8
      src/main/frontend/components/sidebar.cljs
  38. 23 12
      src/main/frontend/components/sidebar.css
  39. 11 0
      src/main/frontend/components/theme.css
  40. 7 3
      src/main/frontend/config.cljs
  41. 3 2
      src/main/frontend/context/i18n.cljs
  42. 3 6
      src/main/frontend/db.cljs
  43. 4 3
      src/main/frontend/db/query_react.cljs
  44. 90 39
      src/main/frontend/dicts.cljc
  45. 6 4
      src/main/frontend/extensions/latex.cljs
  46. 1 1
      src/main/frontend/extensions/zotero/setting.cljs
  47. 2 2
      src/main/frontend/fs.cljs
  48. 1 1
      src/main/frontend/fs/bfs.cljs
  49. 7 6
      src/main/frontend/fs/capacitor_fs.cljs
  50. 1 1
      src/main/frontend/fs/nfs.cljs
  51. 2 2
      src/main/frontend/fs/node.cljs
  52. 1 1
      src/main/frontend/fs/protocol.cljs
  53. 59 49
      src/main/frontend/fs/sync.cljs
  54. 8 6
      src/main/frontend/fs/watcher_handler.cljs
  55. 82 85
      src/main/frontend/handler.cljs
  56. 1 32
      src/main/frontend/handler/common.cljs
  57. 79 0
      src/main/frontend/handler/common/file.cljs
  58. 30 3
      src/main/frontend/handler/config.cljs
  59. 26 19
      src/main/frontend/handler/events.cljs
  60. 51 119
      src/main/frontend/handler/file.cljs
  61. 65 0
      src/main/frontend/handler/global_config.cljs
  62. 3 3
      src/main/frontend/handler/page.cljs
  63. 65 49
      src/main/frontend/handler/repo.cljs
  64. 63 0
      src/main/frontend/handler/repo_config.cljs
  65. 3 3
      src/main/frontend/handler/ui.cljs
  66. 14 12
      src/main/frontend/handler/user.cljs
  67. 35 25
      src/main/frontend/handler/web/nfs.cljs
  68. 2 2
      src/main/frontend/modules/instrumentation/posthog.cljs
  69. 1 1
      src/main/frontend/modules/shortcut/config.cljs
  70. 4 4
      src/main/frontend/modules/shortcut/core.cljs
  71. 5 4
      src/main/frontend/modules/shortcut/data_helper.cljs
  72. 32 20
      src/main/frontend/modules/shortcut/dicts.cljc
  73. 3 2
      src/main/frontend/search.cljs
  74. 1 3
      src/main/frontend/spec/storage.cljc
  75. 284 261
      src/main/frontend/state.cljs
  76. 32 0
      src/main/frontend/state_test.cljs
  77. 16 9
      src/main/frontend/ui.cljs
  78. 5 1
      src/main/frontend/ui.css
  79. 2 2
      src/main/frontend/util/cursor.cljs
  80. 25 25
      src/main/frontend/util/fs.cljs
  81. 23 18
      src/main/frontend/util/persist_var.cljs
  82. 3 3
      src/main/logseq/api.cljs
  83. 12 0
      templates/global-config.edn
  84. 21 8
      yarn.lock

+ 23 - 4
.clj-kondo/config.edn

@@ -1,4 +1,13 @@
-{:linters
+{:ns-groups [{:pattern "frontend.components.*" :name all-components}]
+
+ :config-in-ns
+ ;; :used-underscored-binding is turned off for components because of false positive
+ ;; for rum/defcs and _state.
+ {all-components {:linters {:used-underscored-binding {:level :off}}}
+  ;; false positive with match/match and _
+  frontend.handler.paste {:linters {:used-underscored-binding {:level :off}}}}
+
+ :linters
  {:unresolved-symbol {:exclude [goog.DEBUG
  {:unresolved-symbol {:exclude [goog.DEBUG
                                 goog.string.unescapeEntities
                                 goog.string.unescapeEntities
                                 ;; TODO:lint: Fix when fixing all type hints
                                 ;; TODO:lint: Fix when fixing all type hints
@@ -25,6 +34,13 @@
              frontend.format.mldoc mldoc
              frontend.format.mldoc mldoc
              frontend.format.block block
              frontend.format.block block
              frontend.handler.extract extract
              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 graph-parser
              logseq.graph-parser.text text
              logseq.graph-parser.text text
              logseq.graph-parser.block gp-block
              logseq.graph-parser.block gp-block
@@ -32,12 +48,15 @@
              logseq.graph-parser.util gp-util
              logseq.graph-parser.util gp-util
              logseq.graph-parser.property gp-property
              logseq.graph-parser.property gp-property
              logseq.graph-parser.config gp-config
              logseq.graph-parser.config gp-config
-             logseq.graph-parser.date-time-util date-time-util
              logseq.graph-parser.util.page-ref page-ref
              logseq.graph-parser.util.page-ref page-ref
-             logseq.graph-parser.util.block-ref block-ref}}}
+             logseq.graph-parser.util.block-ref block-ref
+             logseq.graph-parser.date-time-util date-time-util}}
+
+  :namespace-name-mismatch {:level :warning}
+  :used-underscored-binding {:level :warning}}
 
 
  :hooks {:analyze-call {rum.core/defc hooks.rum/defc
  :hooks {:analyze-call {rum.core/defc hooks.rum/defc
-                        rum.core/defcs hooks.rum/defcs}}
+                         rum.core/defcs hooks.rum/defcs}}
  :lint-as {promesa.core/let clojure.core/let
  :lint-as {promesa.core/let clojure.core/let
            promesa.core/loop clojure.core/loop
            promesa.core/loop clojure.core/loop
            promesa.core/recur clojure.core/recur
            promesa.core/recur clojure.core/recur

+ 19 - 15
.github/workflows/build-desktop-release.yml

@@ -248,29 +248,26 @@ jobs:
         run: yarn run postinstall
         run: yarn run postinstall
         working-directory: ./static/node_modules/dugite/
         working-directory: ./static/node_modules/dugite/
 
 
+      - name: Prepare Code Sign
+        if: ${{ github.repository == 'logseq/logseq' }}
+        run: |
+          [IO.File]::WriteAllBytes($(Get-Location).Path + "\codesign.pfx", [Convert]::FromBase64String($env:CERTIFICATE))
+        env:
+          CERTIFICATE: ${{ secrets.CODE_SIGN_CERTIFICATE }}
+
       - name: Build/Release Electron app
       - name: Build/Release Electron app
         run: yarn electron:make
         run: yarn electron:make
         working-directory: ./static
         working-directory: ./static
         env:
         env:
-          CSC_LINK: ${{ secrets.CSC_LINK }}
-          CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
+          CODE_SIGN_CERTIFICATE_FILE: ../codesign.pfx
+          CODE_SIGN_CERTIFICATE_PASSWORD: ${{ secrets.CODE_SIGN_CERTIFICATE_PASSWORD }}
 
 
       - name: Save Artifact
       - name: Save Artifact
         run: |
         run: |
           mkdir builds
           mkdir builds
-          mv static\out\make\squirrel.windows\x64\*.exe builds\Logseq-win-x64-${{ steps.ref.outputs.version }}.exe
-
-      - name: Code Sign the Installer
-        uses: andelf/code-sign-action@master
-        if: ${{ github.repository == 'logseq/logseq' }}
-        with:
-          certificate: ${{ secrets.CODE_SIGN_CERTIFICATE }}
-          password: ${{ secrets.CODE_SIGN_CERTIFICATE_PASSWORD }}
-          certificatesha1: ${{ secrets.CODE_SIGN_CERTIFICATE_SHA1 }}
-          certificatename: "Logseq, Inc."
-          timestampUrl: http://timestamp.digicert.com
-          folder: "builds"
-          recursive: false
+          mv static\out\make\squirrel.windows\x64\*.exe    builds\Logseq-win-x64-${{ steps.ref.outputs.version }}.exe
+          mv static\out\make\squirrel.windows\x64\*.nupkg  builds\Logseq-win-x64-${{ steps.ref.outputs.version }}-full.nupkg
+          mv static\out\make\squirrel.windows\x64\RELEASES builds\RELEASES
 
 
       - name: Upload Artifact
       - name: Upload Artifact
         uses: actions/upload-artifact@v2
         uses: actions/upload-artifact@v2
@@ -546,12 +543,17 @@ jobs:
           pkgver=$(cat VERSION)
           pkgver=$(cat VERSION)
           echo ::set-output name=version::$pkgver
           echo ::set-output name=version::$pkgver
 
 
+      - name: Fix .nupkg name in RELEASES file
+        run: |
+          sed -i "s/Logseq-.*.nupkg/Logseq-win-x64-${{ steps.ref.outputs.version }}-full.nupkg/g" RELEASES
+
       - name: Generate SHA256 checksums
       - name: Generate SHA256 checksums
         run: |
         run: |
           sha256sum *-darwin-* > SHA256SUMS.txt
           sha256sum *-darwin-* > SHA256SUMS.txt
           sha256sum *-win-* >> SHA256SUMS.txt
           sha256sum *-win-* >> SHA256SUMS.txt
           sha256sum *-linux-* >> SHA256SUMS.txt
           sha256sum *-linux-* >> SHA256SUMS.txt
           sha256sum *.apk >> SHA256SUMS.txt
           sha256sum *.apk >> SHA256SUMS.txt
+          sha256sum RELEASES >> SHA256SUMS.txt
           cat SHA256SUMS.txt
           cat SHA256SUMS.txt
 
 
       - name: Create Release Draft
       - name: Create Release Draft
@@ -570,5 +572,7 @@ jobs:
             ./*.zip
             ./*.zip
             ./*.dmg
             ./*.dmg
             ./*.exe
             ./*.exe
+            ./*.nupkg
+            ./RELEASES
             ./*.AppImage
             ./*.AppImage
             ./*.apk
             ./*.apk

+ 1 - 1
deps.edn

@@ -47,5 +47,5 @@
                   :main-opts   ["-m" "shadow.cljs.devtools.cli"]}
                   :main-opts   ["-m" "shadow.cljs.devtools.cli"]}
 
 
            ;; Use :replace-deps for tools. See https://github.com/clj-kondo/clj-kondo/issues/1536#issuecomment-1013006889
            ;; Use :replace-deps for tools. See https://github.com/clj-kondo/clj-kondo/issues/1536#issuecomment-1013006889
-           :clj-kondo {:replace-deps {clj-kondo/clj-kondo {:mvn/version "2022.05.31"}}
+           :clj-kondo {:replace-deps {clj-kondo/clj-kondo {:mvn/version "2022.09.08"}}
                        :main-opts  ["-m" "clj-kondo.main"]}}}
                        :main-opts  ["-m" "clj-kondo.main"]}}}

+ 1 - 1
deps/db/deps.edn

@@ -3,5 +3,5 @@
  {datascript/datascript {:mvn/version "1.3.8"}}
  {datascript/datascript {:mvn/version "1.3.8"}}
  :aliases
  :aliases
  {:clj-kondo
  {:clj-kondo
-  {:replace-deps {clj-kondo/clj-kondo {:mvn/version "2022.05.31"}}
+  {:replace-deps {clj-kondo/clj-kondo {:mvn/version "2022.09.08"}}
    :main-opts  ["-m" "clj-kondo.main"]}}}
    :main-opts  ["-m" "clj-kondo.main"]}}}

+ 1 - 1
deps/graph-parser/deps.edn

@@ -20,5 +20,5 @@
                       org.clojure/clojurescript {:mvn/version "1.11.54"}}
                       org.clojure/clojurescript {:mvn/version "1.11.54"}}
          :main-opts ["-m" "cljs-test-runner.main"]}
          :main-opts ["-m" "cljs-test-runner.main"]}
 
 
-  :clj-kondo {:replace-deps {clj-kondo/clj-kondo {:mvn/version "2022.05.31"}}
+  :clj-kondo {:replace-deps {clj-kondo/clj-kondo {:mvn/version "2022.09.08"}}
               :main-opts  ["-m" "clj-kondo.main"]}}}
               :main-opts  ["-m" "clj-kondo.main"]}}}

+ 12 - 15
deps/graph-parser/src/logseq/graph_parser.cljs

@@ -8,20 +8,13 @@
             [clojure.string :as string]
             [clojure.string :as string]
             [clojure.set :as set]))
             [clojure.set :as set]))
 
 
-(defn- db-set-file-content!
-  "Modified copy of frontend.db.model/db-set-file-content!"
-  [conn path content]
-  (let [tx-data {:file/path path
-                 :file/content content}]
-    (d/transact! conn [tx-data] {:skip-refresh? true})))
-
 (defn parse-file
 (defn parse-file
   "Parse file and save parsed data to the given db. Main parse fn used by logseq app"
   "Parse file and save parsed data to the given db. Main parse fn used by logseq app"
-  [conn file content {:keys [new? delete-blocks-fn extract-options]
+  [conn file content {:keys [new? delete-blocks-fn extract-options skip-db-transact?]
                       :or {new? true
                       :or {new? true
-                           delete-blocks-fn (constantly [])}
+                           delete-blocks-fn (constantly [])
+                           skip-db-transact? false}
                       :as options}]
                       :as options}]
-  (db-set-file-content! conn file content)
   (let [format (gp-util/get-format file)
   (let [format (gp-util/get-format file)
         file-content [{:file/path file}]
         file-content [{:file/path file}]
         {:keys [tx ast]}
         {:keys [tx ast]}
@@ -53,14 +46,18 @@
               pages (extract/with-ref-pages pages blocks)
               pages (extract/with-ref-pages pages blocks)
               pages-index (map #(select-keys % [:block/name]) pages)]
               pages-index (map #(select-keys % [:block/name]) pages)]
                ;; does order matter?
                ;; does order matter?
-          {:tx (concat file-content pages-index delete-blocks pages block-ids blocks)
-           :ast ast})
-        tx (concat tx [(cond-> {:file/path file}
-                         new?
+               {:tx (concat file-content pages-index delete-blocks pages block-ids blocks)
+                :ast ast})
+             {:tx file-content})
+        tx (concat tx [(cond-> {:file/path file
+                                :file/content content}
+                               new?
                                ;; TODO: use file system timestamp?
                                ;; TODO: use file system timestamp?
                          (assoc :file/created-at (date-time-util/time-ms)))])
                          (assoc :file/created-at (date-time-util/time-ms)))])
         tx' (gp-util/remove-nils tx)
         tx' (gp-util/remove-nils tx)
-        result (d/transact! conn tx' (select-keys options [:new-graph? :from-disk?]))]
+        result (if skip-db-transact?
+                 tx'
+                 (d/transact! conn tx' (select-keys options [:new-graph? :from-disk?])))]
     {:tx result
     {:tx result
      :ast ast}))
      :ast ast}))
 
 

+ 1 - 2
deps/graph-parser/src/logseq/graph_parser/cli.cljs

@@ -34,8 +34,7 @@ TODO: Fail fast when process exits 1"
     (mapv #(assoc % :file/content (slurp (:file/path %))) files)))
     (mapv #(assoc % :file/content (slurp (:file/path %))) files)))
 
 
 (defn- read-config
 (defn- read-config
-  "Commandline version of frontend.handler.common/read-config without graceful
-  handling of broken config. Config is assumed to be at $dir/logseq/config.edn "
+  "Reads repo-specific config from logseq/config.edn"
   [dir]
   [dir]
   (let [config-file (str dir "/" gp-config/app-name "/config.edn")]
   (let [config-file (str dir "/" gp-config/app-name "/config.edn")]
     (if (fs/existsSync config-file)
     (if (fs/existsSync config-file)

+ 10 - 10
e2e-tests/accessibility.spec.ts

@@ -1,15 +1,15 @@
-import { injectAxe, checkA11y } from 'axe-playwright'
 import { test } from './fixtures'
 import { test } from './fixtures'
 import { createRandomPage } from './utils'
 import { createRandomPage } from './utils'
+import { expect } from '@playwright/test'
+import AxeBuilder from '@axe-core/playwright'
 
 
-
-test('check a11y for the whole page', async ({ page }) => {
-    await page.waitForTimeout(2000) // wait for everything be ready
-    await injectAxe(page)
-    await page.waitForTimeout(2000) // wait for everything be ready
+test('should not have any automatically detectable accessibility issues', async ({ page }) => {
     await createRandomPage(page)
     await createRandomPage(page)
-    await page.waitForTimeout(2000) // wait for everything be ready
-    await checkA11y(page, null, {
-        detailedReport: true,
-    })
+    await page.waitForTimeout(2000)
+    const accessibilityScanResults = await new AxeBuilder({ page })
+        .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
+        .setLegacyMode()
+        .analyze()
+
+    expect(accessibilityScanResults.violations).toEqual([]);
 })
 })

+ 1 - 1
package.json

@@ -4,11 +4,11 @@
     "private": true,
     "private": true,
     "main": "static/electron.js",
     "main": "static/electron.js",
     "devDependencies": {
     "devDependencies": {
+        "@axe-core/playwright": "^4.4.4",
         "@capacitor/cli": "3.2.2",
         "@capacitor/cli": "3.2.2",
         "@playwright/test": "^1.24.2",
         "@playwright/test": "^1.24.2",
         "@tailwindcss/ui": "0.7.2",
         "@tailwindcss/ui": "0.7.2",
         "@types/gulp": "^4.0.7",
         "@types/gulp": "^4.0.7",
-        "axe-playwright": "^1.1.11",
         "cross-env": "^7.0.3",
         "cross-env": "^7.0.3",
         "cssnano": "^4.1.10",
         "cssnano": "^4.1.10",
         "del": "^6.0.0",
         "del": "^6.0.0",

+ 1 - 1
resources/css/common.css

@@ -3,7 +3,7 @@
   --ls-tag-text-hover-opacity: 1;
   --ls-tag-text-hover-opacity: 1;
   --ls-page-text-size: 1em;
   --ls-page-text-size: 1em;
   --ls-page-title-size: 36px;
   --ls-page-title-size: 36px;
-  --ls-main-content-max-width: 810px;
+  --ls-main-content-max-width: 1200px;
   --ls-main-content-max-width-wide: 100%;
   --ls-main-content-max-width-wide: 100%;
   --ls-font-family: Inter;
   --ls-font-family: Inter;
   --ls-scrollbar-width: 6px;
   --ls-scrollbar-width: 6px;

+ 0 - 2
resources/css/tooltip.css

@@ -588,7 +588,6 @@
   color: var(--ls-primary-text-color);
   color: var(--ls-primary-text-color);
   border-radius: 4px;
   border-radius: 4px;
   text-align: center;
   text-align: center;
-  will-change: transform;
   -webkit-font-smoothing: antialiased;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
   -moz-osx-font-smoothing: grayscale;
   background-color: var(--ls-quaternary-background-color);
   background-color: var(--ls-quaternary-background-color);
@@ -625,7 +624,6 @@
 
 
 .tippy-tooltip [x-circle] {
 .tippy-tooltip [x-circle] {
   position: absolute;
   position: absolute;
-  will-change: transform;
   background-color: var(--ls-quaternary-background-color);
   background-color: var(--ls-quaternary-background-color);
   border-radius: 50%;
   border-radius: 50%;
   width: 130%;
   width: 130%;

+ 4 - 1
resources/forge.config.js

@@ -30,7 +30,10 @@ module.exports = {
       'config': {
       'config': {
         'name': 'Logseq',
         'name': 'Logseq',
         'setupIcon': './icons/logseq.ico',
         'setupIcon': './icons/logseq.ico',
-        'loadingGif': './icons/installing.gif'
+        'loadingGif': './icons/installing.gif',
+        'certificateFile': process.env.CODE_SIGN_CERTIFICATE_FILE,
+        'certificatePassword': process.env.CODE_SIGN_CERTIFICATE_PASSWORD,
+        "rfc3161TimeStampServer": "http://timestamp.digicert.com"
       }
       }
     },
     },
     {
     {

+ 4 - 0
resources/js/preload.js

@@ -168,5 +168,9 @@ contextBridge.exposeInMainWorld('apis', {
     webFrame.setZoomFactor(factor)
     webFrame.setZoomFactor(factor)
   },
   },
 
 
+  setZoomLevel (level) {
+    webFrame.setZoomLevel(level)
+  },
+
   isAbsolutePath: path.isAbsolute.bind(path)
   isAbsolutePath: path.isAbsolute.bind(path)
 })
 })

+ 10 - 7
src/electron/electron/core.cljs

@@ -106,8 +106,10 @@
                              (-> (. fs copy (path/join assets-from-dir filename) (path/join assets-to-dir filename))
                              (-> (. fs copy (path/join assets-from-dir filename) (path/join assets-to-dir filename))
                                  (p/catch
                                  (p/catch
                                   (fn [e]
                                   (fn [e]
-                                    (println (str "Failed to copy " (path/join assets-from-dir filename) " to " (path/join assets-to-dir filename)))
-                                    (js/console.error e)))))
+                                    (.error logger "Failed to copy"
+                                            (str {:from (path/join assets-from-dir filename)
+                                                  :to (path/join assets-to-dir filename)})
+                                            e)))))
                            asset-filenames)
                            asset-filenames)
 
 
                           (map
                           (map
@@ -165,7 +167,7 @@
                  (try
                  (try
                    (js-invoke app type args)
                    (js-invoke app type args)
                    (catch js/Error e
                    (catch js/Error e
-                     (js/console.error e)))))
+                     (.error logger (str call-app-channel " " e))))))
 
 
       (.handle call-win-channel
       (.handle call-win-channel
                (fn [^js e type & args]
                (fn [^js e type & args]
@@ -173,7 +175,7 @@
                    (try
                    (try
                      (js-invoke win type args)
                      (js-invoke win type args)
                      (catch js/Error e
                      (catch js/Error e
-                       (js/console.error e)))))))
+                       (.error logger (str call-win-channel " " e))))))))
 
 
     #(do (clear-win-effects!)
     #(do (clear-win-effects!)
          (.removeHandler ipcMain toggle-win-channel)
          (.removeHandler ipcMain toggle-win-channel)
@@ -270,11 +272,12 @@
                (win/switch-to-window! window))))
                (win/switch-to-window! window))))
 
 
       (.on app "window-all-closed" (fn []
       (.on app "window-all-closed" (fn []
+                                     (.debug logger "window-all-closed" "Quiting...")
                                      (try
                                      (try
                                        (fs-watcher/close-watcher!)
                                        (fs-watcher/close-watcher!)
                                        (search/close!)
                                        (search/close!)
                                        (catch js/Error e
                                        (catch js/Error e
-                                         (js/console.error e)))
+                                         (.error logger "window-all-closed" e)))
                                      (.quit app)))
                                      (.quit app)))
       (.on app "ready"
       (.on app "ready"
            (fn []
            (fn []
@@ -340,9 +343,9 @@
                (.on app "activate" #(when @*win (.show win)))))))))
                (.on app "activate" #(when @*win (.show win)))))))))
 
 
 (defn start []
 (defn start []
-  (js/console.log "Main - start")
+  (.debug logger "Main - start")
   (when @*setup-fn (@*setup-fn)))
   (when @*setup-fn (@*setup-fn)))
 
 
 (defn stop []
 (defn stop []
-  (js/console.log "Main - stop")
+  (.debug logger "Main - stop")
   (when @*teardown-fn (@*teardown-fn)))
   (when @*teardown-fn (@*teardown-fn)))

+ 2 - 2
src/electron/electron/exceptions.cljs

@@ -17,9 +17,9 @@
     (show-error-tip "[Main Exception]" msg stack))
     (show-error-tip "[Main Exception]" msg stack))
 
 
   ;; for debug log
   ;; for debug log
-  (js/console.error uncaughtExceptionChan e))
+  (.error utils/logger uncaughtExceptionChan (str e)))
 
 
 (defn setup-exception-listeners!
 (defn setup-exception-listeners!
   []
   []
   (js/process.on uncaughtExceptionChan app-uncaught-handler)
   (js/process.on uncaughtExceptionChan app-uncaught-handler)
-  #(js/process.off uncaughtExceptionChan app-uncaught-handler))
+  #(js/process.off uncaughtExceptionChan app-uncaught-handler))

+ 105 - 60
src/electron/electron/fs_watcher.cljs

@@ -1,4 +1,7 @@
 (ns electron.fs-watcher
 (ns electron.fs-watcher
+  "This ns is a wrapper around the chokidar file watcher,
+  https://www.npmjs.com/package/chokidar. File watcher events are sent to the
+  `file-watcher` ipc channel"
   (:require [cljs-bean.core :as bean]
   (:require [cljs-bean.core :as bean]
             ["fs" :as fs]
             ["fs" :as fs]
             ["chokidar" :as watcher]
             ["chokidar" :as watcher]
@@ -21,19 +24,25 @@
                         (send file-watcher-chan
                         (send file-watcher-chan
                               (bean/->js {:type type :payload payload})))
                               (bean/->js {:type type :payload payload})))
                     true))
                     true))
-        wins (window/get-graph-all-windows dir)]
-    (if (contains? #{"unlinkDir" "addDir"} type)
+        wins (if (:global-dir payload)
+               (window/get-all-windows)
+               (window/get-graph-all-windows dir))]
+    (if (or (contains? #{"unlinkDir" "addDir"} type)
+            ;; Only change events to a global dir are emitted to all windows.
+            ;; Add* events are not emitted to all since each client adds
+            ;; files at different times.
+            (and (:global-dir payload) (= "change" type)))
       ;; notify every windows
       ;; notify every windows
       (doseq [win wins] (send-fn win))
       (doseq [win wins] (send-fn win))
 
 
       ;; Should only send to one window; then dbsync will do his job
       ;; Should only send to one window; then dbsync will do his job
       ;; If no window is on this graph, just ignore
       ;; If no window is on this graph, just ignore
       (let [sent? (some send-fn wins)]
       (let [sent? (some send-fn wins)]
-        (when-not sent? (prn "unhandled file event will cause uncatched file modifications!.
-                          target:" dir))))))
+        (when-not sent? (.warn utils/logger
+                               (str "unhandled file event will cause uncatched file modifications!. target:" dir)))))))
 
 
 (defn- publish-file-event!
 (defn- publish-file-event!
-  [dir path event]
+  [dir path event options]
   (let [dir-path? (= dir path)
   (let [dir-path? (= dir path)
         content (when (and (not= event "unlink")
         content (when (and (not= event "unlink")
                            (not dir-path?)
                            (not dir-path?)
@@ -42,64 +51,100 @@
         stat (when (and (not= event "unlink")
         stat (when (and (not= event "unlink")
                         (not dir-path?))
                         (not dir-path?))
                (fs/statSync path))]
                (fs/statSync path))]
-    (send-file-watcher! dir event {:dir (utils/fix-win-path! dir)
-                                   :path (utils/fix-win-path! path)
-                                   :content content
-                                   :stat stat})))
+    (send-file-watcher! dir event (merge {:dir (utils/fix-win-path! dir)
+                                          :path (utils/fix-win-path! path)
+                                          :content content
+                                          :stat stat}
+                                         (select-keys options [:global-dir])))))
+(defn- create-dir-watcher
+  [dir options]
+  (let [watcher-opts (clj->js
+                      {:ignored (fn [path]
+                                  (utils/ignored-path? dir path))
+                       :ignoreInitial false
+                       :ignorePermissionErrors true
+                       :interval polling-interval
+                       :binaryInterval polling-interval
+                       :persistent true
+                       :disableGlobbing true
+                       :usePolling false
+                       :awaitWriteFinish true})
+        dir-watcher (.watch watcher dir watcher-opts)]
+    ;; TODO: batch sender
+    (.on dir-watcher "unlinkDir"
+         (fn [path]
+           (when (= dir path)
+             (publish-file-event! dir dir "unlinkDir" options))))
+    (.on dir-watcher "addDir"
+         (fn [path]
+           (when (= dir path)
+             (publish-file-event! dir dir "addDir" options))))
+    (.on dir-watcher "add"
+         (fn [path]
+           (publish-file-event! dir path "add" options)))
+    (.on dir-watcher "change"
+         (fn [path]
+           (publish-file-event! dir path "change" options)))
+    (.on dir-watcher "unlink"
+         ;; delay 500ms for syncing disks
+         (fn [path]
+           (js/setTimeout #(when (not (fs/existsSync path))
+                             (publish-file-event! dir path "unlink" options))
+                          500)))
+    (.on dir-watcher "error"
+         (fn [path]
+           (.warn utils/logger "Watch error happened: " (str {:path path}))))
 
 
-(defn watch-dir!
-  "Watch a directory if no such file watcher exists"
-  [dir]
-  (when-not (get @*file-watcher dir)
-    (if (fs/existsSync dir)
-      (let [watcher-opts (clj->js
-                          {:ignored (fn [path]
-                                      (utils/ignored-path? dir path))
-                           :ignoreInitial false
-                           :ignorePermissionErrors true
-                           :interval polling-interval
-                           :binaryInterval polling-interval
-                           :persistent true
-                           :disableGlobbing true
-                           :usePolling false
-                           :awaitWriteFinish true})
-            dir-watcher (.watch watcher dir watcher-opts)
-            watcher-del-f #(.close dir-watcher)]
-        (swap! *file-watcher assoc dir [dir-watcher watcher-del-f])
-        ;; TODO: batch sender
-        (.on dir-watcher "unlinkDir"
-             (fn [path]
-               (when (= dir path)
-                 (publish-file-event! dir dir "unlinkDir"))))
-        (.on dir-watcher "addDir"
-             (fn [path]
-               (when (= dir path)
-                 (publish-file-event! dir dir "addDir"))))
-        (.on dir-watcher "add"
-             (fn [path]
-               (publish-file-event! dir path "add")))
-        (.on dir-watcher "change"
-             (fn [path]
-               (publish-file-event! dir path "change")))
-        (.on dir-watcher "unlink"
-             ;; delay 500ms for syncing disks
-             (fn [path]
-               (js/setTimeout #(when (not (fs/existsSync path))
-                                 (publish-file-event! dir path "unlink"))
-                              500)))
-        (.on dir-watcher "error"
-             (fn [path]
-               (println "Watch error happened: "
-                        {:path path})))
+    dir-watcher))
+
+(defn- seed-client-with-initial-global-dir-data
+  "Ensures that secondary clients initialize their databases efficiently and in
+  the same way as the primary client. This fn achieves this by creating a
+  temporary watcher whose sole purpose is to seed the db and then close  when
+  its done seeding a.k.a. ready event fires."
+  [dir options]
+  (let [dir-watcher (create-dir-watcher dir options)]
+    (.on dir-watcher "ready" (fn []
+                               (.close dir-watcher)))))
+
+(defn- create-and-save-watcher
+  [dir options]
+  (let [dir-watcher (create-dir-watcher dir options)
+        watcher-del-f #(.close dir-watcher)]
+    (swap! *file-watcher assoc dir [dir-watcher watcher-del-f])
+    ;; electron app extends `EventEmitter`
+    ;; TODO check: duplicated with the logic in "window-all-closed" ?
+    (.on app "quit" watcher-del-f)))
 
 
-        ;; electron app extends `EventEmitter`
-        ;; TODO check: duplicated with the logic in "window-all-closed" ?
-        (.on app "quit" watcher-del-f)
+(defn- watch-global-dir!
+  "Only one watcher exists per global dir so only create the watcher for the
+  primary client. Secondary clients only seed their client database."
+  [dir options]
+  (if (get @*file-watcher dir)
+    (seed-client-with-initial-global-dir-data dir options)
+    (create-and-save-watcher dir options)))
+
+(defn watch-dir!
+  "Watches a directory and emits file events. In addition to file
+  watching, clients rely on watchers to initially seed their database with
+  the file contents of a dir. This is done with the ignoreInitial option
+  set to false, https://github.com/paulmillr/chokidar#path-filtering. The
+  watcher emits addDir and add file events which then seed the client database.
+  This fn has the following options:
 
 
-        true)
-      ;; retry if the `dir` not exists, which is useful when a graph's folder is
-      ;; back after refreshing the window
-      (js/setTimeout #(watch-dir! dir) 5000))))
+* :global-dir - Boolean that indicates the watched directory is global. This
+  type of directory has different behavior then a normal watcher as it
+  broadcasts its change events to all clients. This option needs to be passed to
+  clients in order for them to identify the correct db"
+  [dir options]
+  (if (:global-dir options)
+    (watch-global-dir! dir options)
+    (when-not (get @*file-watcher dir)
+      (if (fs/existsSync dir)
+        (create-and-save-watcher dir options)
+        ;; retry if the `dir` not exists, which is useful when a graph's folder is
+        ;; back after refreshing the window
+        (js/setTimeout #(watch-dir! dir options) 5000)))))
 
 
 (defn close-watcher!
 (defn close-watcher!
   "If no `dir` provided, close all watchers;
   "If no `dir` provided, close all watchers;

+ 23 - 16
src/electron/electron/handler.cljs

@@ -1,4 +1,6 @@
 (ns electron.handler
 (ns electron.handler
+  "This ns starts the event handling for the electron main process and defines
+  all the application-specific event types"
   (:require ["electron" :refer [ipcMain dialog app autoUpdater shell]]
   (:require ["electron" :refer [ipcMain dialog app autoUpdater shell]]
             [cljs-bean.core :as bean]
             [cljs-bean.core :as bean]
             ["fs" :as fs]
             ["fs" :as fs]
@@ -111,8 +113,7 @@
         (let [backup-path (try
         (let [backup-path (try
                             (backup-file/backup-file repo :backup-dir path (path/extname path) content)
                             (backup-file/backup-file repo :backup-dir path (path/extname path) content)
                             (catch :default e
                             (catch :default e
-                              (println "Backup file failed")
-                              (js/console.dir e)))]
+                              (.error utils/logger (str "Backup file failed: " e))))]
           (utils/send-to-renderer window "notification" {:type "error"
           (utils/send-to-renderer window "notification" {:type "error"
                                                          :payload (str "Write to the file " path
                                                          :payload (str "Write to the file " path
                                                                        " failed, "
                                                                        " failed, "
@@ -220,8 +221,8 @@
         (when-let [sync-meta (and (not (string/blank? root))
         (when-let [sync-meta (and (not (string/blank? root))
                                   (.toString (.readFileSync fs txid-path)))]
                                   (.toString (.readFileSync fs txid-path)))]
           (reader/read-string sync-meta))))
           (reader/read-string sync-meta))))
-    (catch js/Error _e
-      (js/console.debug "[read txid meta] #" root (.-message _e)))))
+    (catch js/Error e
+      (js/console.debug "[read txid meta] #" root (.-message e)))))
 
 
 (defmethod handle :inflateGraphsInfo [_win [_ graphs]]
 (defmethod handle :inflateGraphsInfo [_win [_ graphs]]
   (if (seq graphs)
   (if (seq graphs)
@@ -304,7 +305,7 @@
         (try
         (try
           (fs-extra/removeSync path)
           (fs-extra/removeSync path)
           (catch js/Error e
           (catch js/Error e
-            (js/console.error e)))))
+            (.error utils/logger (str "Clear cache: " e))))))
     (utils/send-to-renderer window "redirect" {:payload {:to :home}})))
     (utils/send-to-renderer window "redirect" {:payload {:to :home}})))
 
 
 (defmethod handle :clearCache [window _]
 (defmethod handle :clearCache [window _]
@@ -407,7 +408,7 @@
 
 
 (def *request-abort-signals (atom {}))
 (def *request-abort-signals (atom {}))
 
 
-(defmethod handle :httpRequest [_ [_ _req-id opts]]
+(defmethod handle :httpRequest [_ [_ req-id opts]]
   (let [{:keys [url abortable method data returnType headers]} opts]
   (let [{:keys [url abortable method data returnType headers]} opts]
     (when-let [[method type] (and (not (string/blank? url))
     (when-let [[method type] (and (not (string/blank? url))
                                   [(keyword (string/upper-case (or method "GET")))
                                   [(keyword (string/upper-case (or method "GET")))
@@ -420,7 +421,7 @@
                                     {:body (js/JSON.stringify (bean/->js data))})
                                     {:body (js/JSON.stringify (bean/->js data))})
 
 
                                   (when-let [^js controller (and abortable (AbortController.))]
                                   (when-let [^js controller (and abortable (AbortController.))]
-                                    (swap! *request-abort-signals assoc _req-id controller)
+                                    (swap! *request-abort-signals assoc req-id controller)
                                     {:signal (.-signal controller)}))))
                                     {:signal (.-signal controller)}))))
           (p/then (fn [^js res]
           (p/then (fn [^js res]
                     (case type
                     (case type
@@ -442,10 +443,10 @@
              (throw e)))
              (throw e)))
           (p/finally
           (p/finally
            (fn []
            (fn []
-             (swap! *request-abort-signals dissoc _req-id)))))))
+             (swap! *request-abort-signals dissoc req-id)))))))
 
 
-(defmethod handle :httpRequestAbort [_ [_ _req-id]]
-  (when-let [^js controller (get @*request-abort-signals _req-id)]
+(defmethod handle :httpRequestAbort [_ [_ req-id]]
+  (when-let [^js controller (get @*request-abort-signals req-id)]
     (.abort controller)))
     (.abort controller)))
 
 
 (defmethod handle :quitAndInstall []
 (defmethod handle :quitAndInstall []
@@ -471,18 +472,20 @@
         windows (win/get-graph-all-windows dir)]
         windows (win/get-graph-all-windows dir)]
     (> (count windows) 1)))
     (> (count windows) 1)))
 
 
-(defmethod handle :addDirWatcher [^js _window [_ dir]]
+(defmethod handle :addDirWatcher [^js _window [_ dir options]]
   ;; receive dir path (not repo / graph) from frontend
   ;; receive dir path (not repo / graph) from frontend
   ;; Windows on same dir share the same watcher
   ;; Windows on same dir share the same watcher
   ;; Only close file watcher when:
   ;; Only close file watcher when:
   ;;    1. there is no one window on the same dir
   ;;    1. there is no one window on the same dir
   ;;    2. reset file watcher to resend `add` event on window refreshing
   ;;    2. reset file watcher to resend `add` event on window refreshing
   (when dir
   (when dir
-    (watcher/watch-dir! dir)))
+    (watcher/watch-dir! dir options)
+    nil))
 
 
 (defmethod handle :unwatchDir [^js _window [_ dir]]
 (defmethod handle :unwatchDir [^js _window [_ dir]]
   (when dir
   (when dir
-    (watcher/close-watcher! dir)))
+    (watcher/close-watcher! dir)
+    nil))
 
 
 (defn open-new-window!
 (defn open-new-window!
   "Persist db first before calling! Or may break db persistency"
   "Persist db first before calling! Or may break db persistency"
@@ -564,7 +567,7 @@
   (apply rsapi/decrypt-with-passphrase (rest args)))
   (apply rsapi/decrypt-with-passphrase (rest args)))
 
 
 (defmethod handle :default [args]
 (defmethod handle :default [args]
-  (println "Error: no ipc handler for: " (bean/->js args)))
+  (.error utils/logger "Error: no ipc handler for: " (bean/->js args)))
 
 
 (defn broadcast-persist-graph!
 (defn broadcast-persist-graph!
   "Receive graph-name (not graph path)
   "Receive graph-name (not graph path)
@@ -602,11 +605,15 @@
              (fn [^js event args-js]
              (fn [^js event args-js]
                (try
                (try
                  (let [message (bean/->clj args-js)]
                  (let [message (bean/->clj args-js)]
+                   ;; Be careful with the return values of `handle` defmethods.
+                   ;; Values that are not non-JS objects will cause this
+                   ;; exception -
+                   ;; https://www.electronjs.org/docs/latest/breaking-changes#behavior-changed-sending-non-js-objects-over-ipc-now-throws-an-exception
                    (bean/->js (handle (or (utils/get-win-from-sender event) window) message)))
                    (bean/->js (handle (or (utils/get-win-from-sender event) window) message)))
                  (catch js/Error e
                  (catch js/Error e
                    (when-not (contains? #{"mkdir" "stat"} (nth args-js 0))
                    (when-not (contains? #{"mkdir" "stat"} (nth args-js 0))
-                     (println "IPC error: " {:event event
-                                             :args args-js}
+                     (.error utils/logger "IPC error: " (str {:event event
+                                                              :args args-js})
                               e))
                               e))
                    e))))
                    e))))
     #(.removeHandler ipcMain main-channel)))
     #(.removeHandler ipcMain main-channel)))

+ 1 - 1
src/electron/electron/search.cljs

@@ -225,7 +225,7 @@
   (when-let [database (get-db repo)]
   (when-let [database (get-db repo)]
     (.close database)
     (.close database)
     (let [[db-name db-full-path db-ver-path] (get-db-version-path repo)]
     (let [[db-name db-full-path db-ver-path] (get-db-version-path repo)]
-      (println "Delete search indice: " db-full-path)
+      (.info logger "Delete search indice" (str {:path db-full-path}))
       (fs/unlinkSync db-full-path)
       (fs/unlinkSync db-full-path)
       (fs/unlinkSync db-ver-path)
       (fs/unlinkSync db-ver-path)
       (swap! databases dissoc db-name))))
       (swap! databases dissoc db-name))))

+ 8 - 7
src/electron/electron/updater.cljs

@@ -1,5 +1,5 @@
 (ns electron.updater
 (ns electron.updater
-  (:require [electron.utils :refer [mac? prod? open fetch logger *win]]
+  (:require [electron.utils :refer [mac? win32? prod? open fetch logger *win]]
             [frontend.version :refer [version]]
             [frontend.version :refer [version]]
             [clojure.string :as string]
             [clojure.string :as string]
             [promesa.core :as p]
             [promesa.core :as p]
@@ -13,7 +13,7 @@
 
 
 (def *update-ready-to-install (atom nil))
 (def *update-ready-to-install (atom nil))
 (def *update-pending (atom nil))
 (def *update-pending (atom nil))
-(def debug (partial (.-warn logger) "[updater]"))
+(def debug (partial (.-debug logger) "[updater]"))
 
 
 ;Event: 'error'
 ;Event: 'error'
 ;Event: 'checking-for-update'
 ;Event: 'checking-for-update'
@@ -30,8 +30,7 @@
 
 
 (defn get-latest-artifact-info
 (defn get-latest-artifact-info
   [repo]
   [repo]
-  (let [;endpoint "https://update.electronjs.org/xyhp915/cljs-todo/darwin-x64/0.0.4"
-        endpoint (str "https://update.electronjs.org/" repo "/" js/process.platform "-" js/process.arch "/" electron-version)]
+  (let [endpoint (str "https://update.electronjs.org/" repo "/" js/process.platform "-" js/process.arch "/" electron-version)]
     (debug "checking" endpoint)
     (debug "checking" endpoint)
     (p/catch
     (p/catch
      (p/let [res (fetch endpoint)
      (p/let [res (fetch endpoint)
@@ -42,7 +41,7 @@
            (bean/->clj info))
            (bean/->clj info))
          (throw (js/Error. (str "[" status "] " text)))))
          (throw (js/Error. (str "[" status "] " text)))))
      (fn [e]
      (fn [e]
-       (js/console.warn "[update server error] " e)
+       (.warn logger "[update server error]" e)
        (throw e)))))
        (throw e)))))
 
 
 (defn check-for-updates
 (defn check-for-updates
@@ -128,9 +127,11 @@
            ;; start auto updater
            ;; start auto updater
           (do
           (do
             (debug "Found remote version" remote-version)
             (debug "Found remote version" remote-version)
-            (when mac?
+            (when (or mac? win32?)
+              (debug "forward update to autoUpdater")
+              ;; FIXME: It seems that update-electron-app doesn't work on linux
               (when-let [f (js/require "update-electron-app")]
               (when-let [f (js/require "update-electron-app")]
-                (f #js{:notifyUser false})
+                (f #js{:notifyUser false :logger logger})
                 (.once autoUpdater "update-downloaded"
                 (.once autoUpdater "update-downloaded"
                        new-version-downloaded-cb))))
                        new-version-downloaded-cb))))
 
 

+ 1 - 1
src/electron/electron/utils.cljs

@@ -97,7 +97,7 @@
     (when (fs/existsSync path)
     (when (fs/existsSync path)
       (.toString (fs/readFileSync path)))
       (.toString (fs/readFileSync path)))
     (catch js/Error e
     (catch js/Error e
-      (js/console.error e))))
+      (.error logger (str "Read file: " e)))))
 
 
 (defn get-focused-window
 (defn get-focused-window
   []
   []

+ 3 - 1
src/electron/electron/window.cljs

@@ -143,7 +143,9 @@
 
 
       (doto web-contents
       (doto web-contents
         (.on "new-window" new-win-handler)
         (.on "new-window" new-win-handler)
-        (.on "will-navigate" will-navigate-handler))
+        (.on "will-navigate" will-navigate-handler)
+        (.on "did-start-navigation" #(.send web-contents "persist-zoom-level" (.getZoomLevel web-contents)))
+        (.on "did-navigate-in-page" #(.send web-contents "restore-zoom-level")))
 
 
       (doto win
       (doto win
         (.on "enter-full-screen" #(.send web-contents "full-screen" "enter"))
         (.on "enter-full-screen" #(.send web-contents "full-screen" "enter"))

+ 1 - 1
src/main/electron/listener.cljs

@@ -166,7 +166,7 @@
                      ;; Handle open new window in renderer, until the destination graph doesn't rely on setting local storage
                      ;; Handle open new window in renderer, until the destination graph doesn't rely on setting local storage
                      ;; No db cache persisting ensured. Should be handled by the caller
                      ;; No db cache persisting ensured. Should be handled by the caller
                      (fn [repo]
                      (fn [repo]
-                       (ui-handler/open-new-window! nil repo))))
+                       (ui-handler/open-new-window! repo))))
 
 
 (defn listen!
 (defn listen!
   []
   []

+ 10 - 15
src/main/frontend/components/block.cljs

@@ -211,9 +211,9 @@
                         *exist? (::exist? state)]
                         *exist? (::exist? state)]
                     (when (and sync-on? asset-file? (false? @*exist?))
                     (when (and sync-on? asset-file? (false? @*exist?))
                       (let [sync-state (state/sub [:file-sync/sync-state (state/get-current-repo)])
                       (let [sync-state (state/sub [:file-sync/sync-state (state/get-current-repo)])
-                            _downloading-files (:current-remote->local-files sync-state)
-                            contain-url? (and (seq _downloading-files)
-                                              (some #(string/ends-with? src %) _downloading-files))]
+                            downloading-files (:current-remote->local-files sync-state)
+                            contain-url? (and (seq downloading-files)
+                                              (some #(string/ends-with? src %) downloading-files))]
                         (cond
                         (cond
                           (and (not @*loading?) contain-url?)
                           (and (not @*loading?) contain-url?)
                           (reset! *loading? true)
                           (reset! *loading? true)
@@ -251,8 +251,8 @@
   (let [images (js/document.querySelectorAll ".asset-container img")
   (let [images (js/document.querySelectorAll ".asset-container img")
         images (to-array images)
         images (to-array images)
         images (if-not (= (count images) 1)
         images (if-not (= (count images) 1)
-                 (let [^js _image (.closest (.-target e) ".asset-container")
-                       image (. _image querySelector "img")]
+                 (let [^js image (.closest (.-target e) ".asset-container")
+                       image (. image querySelector "img")]
                    (->> images
                    (->> images
                         (sort-by (juxt #(.-y %) #(.-x %)))
                         (sort-by (juxt #(.-y %) #(.-x %)))
                         (split-with (complement #{image}))
                         (split-with (complement #{image}))
@@ -1896,10 +1896,7 @@
                  (not= "nil" marker))
                  (not= "nil" marker))
         {:class (str (string/lower-case marker))})
         {:class (str (string/lower-case marker))})
       (when bg-color
       (when bg-color
-        {:style {:background-color bg-color
-                 :padding-left 6
-                 :padding-right 6
-                 :color "#FFFFFF"}
+        {:style {:background-color bg-color}
          :class "with-bg-color"}))
          :class "with-bg-color"}))
      (remove-nils
      (remove-nils
       (concat
       (concat
@@ -2635,7 +2632,7 @@
         block (if ref?
         block (if ref?
                 (merge block (db/pull-block (:db/id block)))
                 (merge block (db/pull-block (:db/id block)))
                 block)
                 block)
-        {:block/keys [uuid children pre-block? top? refs heading-level level type format content]} block
+        {:block/keys [uuid children pre-block? top? refs heading-level level format content properties]} block
         config (if navigated? (assoc config :id (str navigating-block)) config)
         config (if navigated? (assoc config :id (str navigating-block)) config)
         block (merge block (block/parse-title-and-body uuid format pre-block? content))
         block (merge block (block/parse-title-and-body uuid format pre-block? content))
         blocks-container-id (:blocks-container-id config)
         blocks-container-id (:blocks-container-id config)
@@ -2644,7 +2641,7 @@
         config (if (nil? (:query-result config))
         config (if (nil? (:query-result config))
                  (assoc config :query-result (atom nil))
                  (assoc config :query-result (atom nil))
                  config)
                  config)
-        heading? (or (= type :heading) (and heading-level (<= heading-level 6)))
+        heading? (or (:heading properties) (and heading-level (<= heading-level 6)))
         *control-show? (get state ::control-show?)
         *control-show? (get state ::control-show?)
         db-collapsed? (util/collapsed? block)
         db-collapsed? (util/collapsed? block)
         collapsed? (cond
         collapsed? (cond
@@ -2933,8 +2930,7 @@
 
 
 (defn built-in-custom-query?
 (defn built-in-custom-query?
   [title]
   [title]
-  (let [repo (state/get-current-repo)
-        queries (state/sub [:config repo :default-queries :journals])]
+  (let [queries (get-in (state/sub-config) [:default-queries :journals])]
     (when (seq queries)
     (when (seq queries)
       (boolean (some #(= % title) (map :title queries))))))
       (boolean (some #(= % title) (map :title queries))))))
 
 
@@ -3010,10 +3006,9 @@
   [state config {:keys [title query view collapsed? children? breadcrumb-show? table-view?] :as q}]
   [state config {:keys [title query view collapsed? children? breadcrumb-show? table-view?] :as q}]
   (let [dsl-query? (:dsl-query? config)
   (let [dsl-query? (:dsl-query? config)
         query-atom (:query-atom state)
         query-atom (:query-atom state)
-        repo (state/get-current-repo)
         query-time (or (react/get-query-time query)
         query-time (or (react/get-query-time query)
                        (react/get-query-time q))
                        (react/get-query-time q))
-        view-fn (if (keyword? view) (state/sub [:config repo :query/views view]) view)
+        view-fn (if (keyword? view) (get-in (state/sub-config) [:query/views view]) view)
         current-block-uuid (or (:block/uuid (:block config))
         current-block-uuid (or (:block/uuid (:block config))
                                (:block/uuid config))
                                (:block/uuid config))
         current-block (db/entity [:block/uuid current-block-uuid])
         current-block (db/entity [:block/uuid current-block-uuid])

+ 15 - 0
src/main/frontend/components/block.css

@@ -304,6 +304,21 @@
   }
   }
 }
 }
 
 
+.with-bg-color {
+  @apply px-1;
+
+  color: #fff;
+
+  a,
+  .page-reference:not(:hover), {
+    color: #aacece;
+
+    .bracket {
+      color: #aacece;
+    }
+  }
+}
+
 .block-properties {
 .block-properties {
   margin: 4px 0;
   margin: 4px 0;
   padding: 4px 8px;
   padding: 4px 8px;

+ 2 - 2
src/main/frontend/components/content.cljs

@@ -186,7 +186,7 @@
            :on-click (fn [_e]
            :on-click (fn [_e]
                        (editor-handler/open-block-in-sidebar! block-id))}
                        (editor-handler/open-block-in-sidebar! block-id))}
           "Open in sidebar"
           "Open in sidebar"
-          ["shift" "click"])
+          ["" "click"])
 
 
          [:hr.menu-separator]
          [:hr.menu-separator]
 
 
@@ -312,7 +312,7 @@
                     block-ref-id
                     block-ref-id
                     :block-ref))}
                     :block-ref))}
       "Open in sidebar"
       "Open in sidebar"
-      ["shift" "click"])
+      ["" "click"])
      (ui/menu-link
      (ui/menu-link
       {:key "copy"
       {:key "copy"
        :on-click (fn [] (editor-handler/copy-current-ref block-ref-id))}
        :on-click (fn [] (editor-handler/copy-current-ref block-ref-id))}

+ 2 - 7
src/main/frontend/components/editor.cljs

@@ -509,11 +509,6 @@
          [:span {:id (str "mock-text_" idx)
          [:span {:id (str "mock-text_" idx)
                  :key idx} c])))])
                  :key idx} c])))])
 
 
-(rum/defc mock-textarea-wrapper < rum/reactive
-  []
-  (let [content (state/sub-edit-content)]
-    (mock-textarea content)))
-
 (rum/defc animated-modal < rum/reactive
 (rum/defc animated-modal < rum/reactive
   [modal-name component set-default-width?]
   [modal-name component set-default-width?]
   (when-let [pos (:pos (state/get-editor-action-data))]
   (when-let [pos (:pos (state/get-editor-action-data))]
@@ -585,7 +580,7 @@
   (shortcut/mixin :shortcut.handler/block-editing-only)
   (shortcut/mixin :shortcut.handler/block-editing-only)
   lifecycle/lifecycle
   lifecycle/lifecycle
   [state {:keys [format block]} id _config]
   [state {:keys [format block]} id _config]
-  (let [content (state/sub-edit-content)
+  (let [content (state/sub-edit-content id)
         heading-class (get-editor-style-class content format)]
         heading-class (get-editor-style-class content format)]
     [:div.editor-inner {:class (if block "block-editor" "non-block-editor")}
     [:div.editor-inner {:class (if block "block-editor" "non-block-editor")}
 
 
@@ -600,7 +595,7 @@
        :auto-focus        false
        :auto-focus        false
        :class             heading-class})
        :class             heading-class})
 
 
-     (mock-textarea-wrapper)
+     (mock-textarea content)
      (modals id format)
      (modals id format)
 
 
      (when format
      (when format

+ 3 - 3
src/main/frontend/components/encryption.cljs

@@ -266,11 +266,11 @@
 (defn input-password
 (defn input-password
   ([repo-url close-fn] (input-password repo-url close-fn {:type :local}))
   ([repo-url close-fn] (input-password repo-url close-fn {:type :local}))
   ([repo-url close-fn opts]
   ([repo-url close-fn opts]
-   (fn [_close-fn]
+   (fn [close-fn']
      (let [close-fn' (if (fn? close-fn)
      (let [close-fn' (if (fn? close-fn)
                        #(do (close-fn %)
                        #(do (close-fn %)
-                            (_close-fn))
-                       _close-fn)]
+                            (close-fn'))
+                       close-fn')]
        (input-password-inner repo-url close-fn' opts)))))
        (input-password-inner repo-url close-fn' opts)))))
 
 
 (rum/defcs encryption-setup-dialog-inner
 (rum/defcs encryption-setup-dialog-inner

+ 8 - 8
src/main/frontend/components/file_sync.cljs

@@ -161,8 +161,8 @@
       (ui/button "Cancel" :background "gray" :class "opacity-50" :on-click close-fn)
       (ui/button "Cancel" :background "gray" :class "opacity-50" :on-click close-fn)
       (ui/button "Create remote graph" :on-click on-confirm)]]))
       (ui/button "Create remote graph" :on-click on-confirm)]]))
 
 
-(rum/defcs ^:large-vars/cleanup-todo indicator <
-  rum/reactive
+(rum/defcs ^:large-vars/cleanup-todo indicator < rum/reactive
+  < {:key-fn #(identity "file-sync-indicator")}
   {:will-mount   (fn [state]
   {:will-mount   (fn [state]
                    (let [unsub-fn (file-sync-handler/setup-file-sync-event-listeners)]
                    (let [unsub-fn (file-sync-handler/setup-file-sync-event-listeners)]
                      (assoc state ::unsub-events unsub-fn)))
                      (assoc state ::unsub-events unsub-fn)))
@@ -308,9 +308,9 @@
             (when (and synced-file-graph? queuing?)
             (when (and synced-file-graph? queuing?)
               [:div.head-ctls
               [:div.head-ctls
                (ui/button "Sync now"
                (ui/button "Sync now"
-                 :class "block cursor-pointer"
-                 :small? true
-                 :on-click #(async/offer! fs-sync/immediately-local->remote-chan true))])
+                          :class "block cursor-pointer"
+                          :small? true
+                          :on-click #(async/offer! fs-sync/immediately-local->remote-chan true))])
 
 
                                         ;(when config/dev?
                                         ;(when config/dev?
                                         ;  [:strong.debug-status (str status)])
                                         ;  [:strong.debug-status (str status)])
@@ -359,7 +359,7 @@
                                 (p/resolved nil)
                                 (p/resolved nil)
                                 (if (util/electron?)
                                 (if (util/electron?)
                                   (ipc/ipc :readGraphTxIdInfo root)
                                   (ipc/ipc :readGraphTxIdInfo root)
-                                  (fs-util/read-graph-txid-info root)))
+                                  (fs-util/read-graphs-txid-info root)))
 
 
                               (p/then (fn [^js info]
                               (p/then (fn [^js info]
                                         (when (and (not empty-dir?)
                                         (when (and (not empty-dir?)
@@ -414,7 +414,7 @@
        [:div.p-4 (ui/loading "Loading...")]
        [:div.p-4 (ui/loading "Loading...")]
        (for [version version-files]
        (for [version version-files]
          (let [version-uuid (get-version-key version)
          (let [version-uuid (get-version-key version)
-               _local?      (some? (:relative-path version))]
+               local?      (some? (:relative-path version))]
            [:div.version-list-item {:key version-uuid}
            [:div.version-list-item {:key version-uuid}
             [:a.item-link.block.fade-link.flex.justify-between
             [:a.item-link.block.fade-link.flex.justify-between
              {:title    version-uuid
              {:title    version-uuid
@@ -427,7 +427,7 @@
                (or (:CreateTime version)
                (or (:CreateTime version)
                    (:create-time version)) nil)]
                    (:create-time version)) nil)]
              [:small.opacity-50.translate-y-1
              [:small.opacity-50.translate-y-1
-              (if _local?
+              (if local?
                 [:<> (ui/icon "git-commit") " local"]
                 [:<> (ui/icon "git-commit") " local"]
                 [:<> (ui/icon "cloud") " remote"])]]])))]))
                 [:<> (ui/icon "cloud") " remote"])]]])))]))
 
 

+ 8 - 4
src/main/frontend/components/header.cljs

@@ -21,7 +21,9 @@
             [reitit.frontend.easy :as rfe]
             [reitit.frontend.easy :as rfe]
             [rum.core :as rum]))
             [rum.core :as rum]))
 
 
-(rum/defc home-button []
+(rum/defc home-button
+  < {:key-fn #(identity "home-button")}
+  []
   (ui/with-shortcut :go/home "left"
   (ui/with-shortcut :go/home "left"
     [:button.button.icon.inline
     [:button.button.icon.inline
      {:title "Home"
      {:title "Home"
@@ -32,6 +34,7 @@
      (ui/icon "home" {:style {:fontSize ui/icon-size}})]))
      (ui/icon "home" {:style {:fontSize ui/icon-size}})]))
 
 
 (rum/defc login < rum/reactive
 (rum/defc login < rum/reactive
+  < {:key-fn #(identity "login-button")}
   []
   []
   (let [_ (state/sub :auth/id-token)
   (let [_ (state/sub :auth/id-token)
         loading? (state/sub [:ui/loading? :login])
         loading? (state/sub [:ui/loading? :login])
@@ -46,14 +49,16 @@
          [:span.ml-2 (ui/loading "")])])))
          [:span.ml-2 (ui/loading "")])])))
 
 
 (rum/defc left-menu-button < rum/reactive
 (rum/defc left-menu-button < rum/reactive
+  < {:key-fn #(identity "left-menu-toggle-button")}
   [{:keys [on-click]}]
   [{:keys [on-click]}]
   (ui/with-shortcut :ui/toggle-left-sidebar "bottom"
   (ui/with-shortcut :ui/toggle-left-sidebar "bottom"
     [:button.#left-menu.cp__header-left-menu.button.icon
     [:button.#left-menu.cp__header-left-menu.button.icon
      {:title "Toggle left menu"
      {:title "Toggle left menu"
       :on-click on-click}
       :on-click on-click}
-      (ui/icon "menu-2" {:style {:fontSize ui/icon-size}})]))
+     (ui/icon "menu-2" {:style {:fontSize ui/icon-size}})]))
 
 
 (rum/defc dropdown-menu < rum/reactive
 (rum/defc dropdown-menu < rum/reactive
+  < {:key-fn #(identity "repos-dropdown-menu")}
   [{:keys [current-repo t]}]
   [{:keys [current-repo t]}]
   (let [page-menu (page-menu/page-menu nil)
   (let [page-menu (page-menu/page-menu nil)
         page-menu-and-hr (when (seq page-menu)
         page-menu-and-hr (when (seq page-menu)
@@ -106,6 +111,7 @@
      {})))
      {})))
 
 
 (rum/defc back-and-forward
 (rum/defc back-and-forward
+  < {:key-fn #(identity "nav-history-buttons")}
   []
   []
   [:div.flex.flex-row
   [:div.flex.flex-row
 
 
@@ -206,8 +212,6 @@
       (when plugin-handler/lsp-enabled?
       (when plugin-handler/lsp-enabled?
         (plugins/hook-ui-items :toolbar))
         (plugins/hook-ui-items :toolbar))
 
 
-
-
       (when (util/electron?)
       (when (util/electron?)
         (back-and-forward))
         (back-and-forward))
 
 

+ 2 - 2
src/main/frontend/components/page.cljs

@@ -163,7 +163,7 @@
 (rum/defc today-queries < rum/reactive
 (rum/defc today-queries < rum/reactive
   [repo today? sidebar?]
   [repo today? sidebar?]
   (when (and today? (not sidebar?))
   (when (and today? (not sidebar?))
-    (let [queries (state/sub [:config repo :default-queries :journals])]
+    (let [queries (get-in (state/sub-config repo) [:default-queries :journals])]
       (when (seq queries)
       (when (seq queries)
         [:div#today-queries.mt-10
         [:div#today-queries.mt-10
          (for [query queries]
          (for [query queries]
@@ -683,7 +683,7 @@
                    (state/set-search-mode! :global)
                    (state/set-search-mode! :global)
                    state)}
                    state)}
   [state]
   [state]
-  (let [settings (state/sub-graph-config-settings)
+  (let [settings (state/graph-settings)
         theme (state/sub :ui/theme)
         theme (state/sub :ui/theme)
         graph (graph-handler/build-global-graph theme settings)
         graph (graph-handler/build-global-graph theme settings)
         search-graph-filters (state/sub :search/graph-filters)
         search-graph-filters (state/sub :search/graph-filters)

+ 1 - 1
src/main/frontend/components/page_menu.cljs

@@ -67,7 +67,7 @@
           contents? (= page-name "contents")
           contents? (= page-name "contents")
           properties (:block/properties page)
           properties (:block/properties page)
           public? (true? (:public properties))
           public? (true? (:public properties))
-          favorites (:favorites (state/sub-graph-config))
+          favorites (:favorites (state/sub-config))
           favorited? (contains? (set (map util/page-name-sanity-lc favorites))
           favorited? (contains? (set (map util/page-name-sanity-lc favorites))
                                 page-name)
                                 page-name)
           developer-mode? (state/sub [:ui/developer-mode?])
           developer-mode? (state/sub [:ui/developer-mode?])

+ 2 - 2
src/main/frontend/components/plugins.cljs

@@ -872,8 +872,8 @@
                             false)}})
                             false)}})
    {:trigger-class "toolbar-plugins-manager-trigger"}))
    {:trigger-class "toolbar-plugins-manager-trigger"}))
 
 
-(rum/defcs hook-ui-items <
-  rum/reactive
+(rum/defcs hook-ui-items < rum/reactive
+  < {:key-fn #(identity "plugin-hook-items")}
   "type of :toolbar, :pagebar"
   "type of :toolbar, :pagebar"
   [_state type]
   [_state type]
   (when (state/sub [:plugin/installed-ui-items])
   (when (state/sub [:plugin/installed-ui-items])

+ 6 - 6
src/main/frontend/components/plugins_settings.cljs

@@ -87,18 +87,18 @@
 
 
 (rum/defc settings-container
 (rum/defc settings-container
   [schema ^js pl]
   [schema ^js pl]
-  (let [^js _settings (.-settings pl)
+  (let [^js settings (.-settings pl)
         pid (.-id pl)
         pid (.-id pl)
-        [settings, set-settings] (rum/use-state (bean/->clj (.toJSON _settings)))
-        update-setting! (fn [k v] (.set _settings (name k) (bean/->js v)))]
+        [settings, set-settings] (rum/use-state (bean/->clj (.toJSON settings)))
+        update-setting! (fn [k v] (.set settings (name k) (bean/->js v)))]
 
 
     (rum/use-effect!
     (rum/use-effect!
       (fn []
       (fn []
         (let [on-change (fn [^js s]
         (let [on-change (fn [^js s]
                           (when-let [s (bean/->clj s)]
                           (when-let [s (bean/->clj s)]
                             (set-settings s)))]
                             (set-settings s)))]
-          (.on _settings "change" on-change)
-          #(.off _settings "change" on-change)))
+          (.on settings "change" on-change)
+          #(.off settings "change" on-change)))
       [pid])
       [pid])
 
 
     (if (seq schema)
     (if (seq schema)
@@ -123,4 +123,4 @@
            [:p (str "#Not Handled#" key)]))]
            [:p (str "#Not Handled#" key)]))]
 
 
       ;; no settings
       ;; no settings
-      [:h2.font-bold.text-lg.py-4.warning "No Settings Schema!"])))
+      [:h2.font-bold.text-lg.py-4.warning "No Settings Schema!"])))

+ 53 - 16
src/main/frontend/components/right_sidebar.cljs

@@ -159,8 +159,17 @@
     (get-page match)))
     (get-page match)))
 
 
 (rum/defc sidebar-resizer
 (rum/defc sidebar-resizer
-  []
-  (let [el-ref (rum/use-ref nil)]
+  [sidebar-open? sidebar-id handler-position]
+  (let [el-ref (rum/use-ref nil)
+        min-ratio 0.1
+        max-ratio 0.7
+        keyboard-step 5
+        set-width! (fn [ratio element]
+                     (when (and el-ref element)
+                       (let [width (str (* ratio 100) "%")]
+                         (#(.setProperty (.-style element) "width" width)
+                          (.setAttribute (rum/deref el-ref) "aria-valuenow" ratio)
+                          (ui-handler/persist-right-sidebar-width!)))))]
     (rum/use-effect!
     (rum/use-effect!
      (fn []
      (fn []
        (when-let [el (and (fn? js/window.interact) (rum/deref el-ref))]
        (when-let [el (and (fn? js/window.interact) (rum/deref el-ref))]
@@ -171,23 +180,51 @@
                 {:move
                 {:move
                  (fn [^js/MouseEvent e]
                  (fn [^js/MouseEvent e]
                    (let [width js/document.documentElement.clientWidth
                    (let [width js/document.documentElement.clientWidth
-                         offset (.-left (.-rect e))
-                         right-el-ratio (- 1 (.toFixed (/ offset width) 6))
-                         right-el-ratio (cond
-                                          (< right-el-ratio 0.2) 0.2
-                                          (> right-el-ratio 0.7) 0.7
-                                          :else right-el-ratio)
-                         right-el (js/document.getElementById "right-sidebar")]
-                     (when right-el
-                       (let [width (str (* right-el-ratio 100) "%")]
-                         (.setProperty (.-style right-el) "width" width)
-                         (ui-handler/persist-right-sidebar-width!)))))}}))
+                         sidebar-el (js/document.getElementById sidebar-id)
+                         offset (.-pageX e)
+                         ratio (.toFixed (/ offset width) 6)
+                         ratio (if (= handler-position :west) (- 1 ratio) ratio)
+                         cursor-class (str "cursor-" (first (name handler-position)) "-resize")]
+                     (if (= (.getAttribute el "data-expanded") "true")
+                       (cond
+                         (< ratio (/ min-ratio 2))
+                         (state/hide-right-sidebar!)
+
+                         (< ratio min-ratio)
+                         (.. js/document.documentElement -classList (add cursor-class))
+
+                         (and (< ratio max-ratio) sidebar-el)
+                         (when sidebar-el
+                           (#(.. js/document.documentElement -classList (remove cursor-class))
+                            (set-width! ratio sidebar-el)))
+                         :else
+                         #(.. js/document.documentElement -classList (remove cursor-class)))
+                       (when (> ratio (/ min-ratio 2)) (state/open-right-sidebar!)))))}}))
              (.styleCursor false)
              (.styleCursor false)
              (.on "dragstart" #(.. js/document.documentElement -classList (add "is-resizing-buf")))
              (.on "dragstart" #(.. js/document.documentElement -classList (add "is-resizing-buf")))
-             (.on "dragend" #(.. js/document.documentElement -classList (remove "is-resizing-buf")))))
+             (.on "dragend" #(.. js/document.documentElement -classList (remove "is-resizing-buf")))
+             (.on "keydown" (fn [e]
+                              (when-let [sidebar-el (js/document.getElementById sidebar-id)]
+                                (let [width js/document.documentElement.clientWidth
+                                      offset (+
+                                              (.-x (.getBoundingClientRect sidebar-el))
+                                              (case (.-code e)
+                                                "ArrowLeft" (- keyboard-step)
+                                                "ArrowRight" keyboard-step
+                                                :else 0))
+                                      ratio (.toFixed (/ offset width) 6)
+                                      ratio (if (= handler-position :west) (- 1 ratio) ratio)]
+                                  (when (and (> ratio min-ratio) (< ratio max-ratio)) (set-width! ratio sidebar-el))))))))
        #())
        #())
      [])
      [])
-    [:span.resizer {:ref el-ref}]))
+    [:.resizer {:ref el-ref
+                :role "separator"
+                :aria-orientation "vertical"
+                :aria-label (t :right-side-bar/separator)
+                :aria-valuemin (* min-ratio 100)
+                :aria-valuemax (* max-ratio 100)
+                :tabIndex "0"
+                :data-expanded sidebar-open?}]))
 
 
 (rum/defcs sidebar-inner <
 (rum/defcs sidebar-inner <
   (rum/local false ::anim-finished?)
   (rum/local false ::anim-finished?)
@@ -198,7 +235,6 @@
   (let [*anim-finished? (get state ::anim-finished?)]
   (let [*anim-finished? (get state ::anim-finished?)]
     [:div.cp__right-sidebar-inner.flex.flex-col.h-full#right-sidebar-container
     [:div.cp__right-sidebar-inner.flex.flex-col.h-full#right-sidebar-container
 
 
-     (sidebar-resizer)
      [:div.cp__right-sidebar-scrollable
      [:div.cp__right-sidebar-scrollable
       [:div.cp__right-sidebar-topbar.flex.flex-row.justify-between.items-center.px-2.h-12
       [:div.cp__right-sidebar-topbar.flex.flex-row.justify-between.items-center.px-2.h-12
        [:div.cp__right-sidebar-settings.hide-scrollbar.gap-1 {:key "right-sidebar-settings"}
        [:div.cp__right-sidebar-settings.hide-scrollbar.gap-1 {:key "right-sidebar-settings"}
@@ -243,5 +279,6 @@
         repo (state/sub :git/current-repo)]
         repo (state/sub :git/current-repo)]
     [:div#right-sidebar.cp__right-sidebar.h-screen
     [:div#right-sidebar.cp__right-sidebar.h-screen
      {:class (if sidebar-open? "open" "closed")}
      {:class (if sidebar-open? "open" "closed")}
+     (sidebar-resizer sidebar-open? "right-sidebar" :west)
      (when sidebar-open?
      (when sidebar-open?
        (sidebar-inner repo t blocks))]))
        (sidebar-inner repo t blocks))]))

+ 12 - 2
src/main/frontend/components/settings.cljs

@@ -15,6 +15,7 @@
             [frontend.handler.user :as user-handler]
             [frontend.handler.user :as user-handler]
             [frontend.handler.plugin :as plugin-handler]
             [frontend.handler.plugin :as plugin-handler]
             [frontend.handler.file-sync :as file-sync-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.instrumentation.core :as instrument]
             [frontend.modules.shortcut.data-helper :as shortcut-helper]
             [frontend.modules.shortcut.data-helper :as shortcut-helper]
             [frontend.state :as state]
             [frontend.state :as state]
@@ -140,10 +141,18 @@
   (row-with-button-action
   (row-with-button-action
     {:left-label   (t :settings-page/custom-configuration)
     {:left-label   (t :settings-page/custom-configuration)
      :button-label (t :settings-page/edit-config-edn)
      :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!)))
      :on-click     #(js/setTimeout (fn [] (ui-handler/toggle-settings-modal!)))
      :-for         "config_edn"}))
      :-for         "config_edn"}))
 
 
+(defn edit-global-config-edn []
+  (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 (global-config-handler/global-config-path)})
+     :on-click     #(js/setTimeout (fn [] (ui-handler/toggle-settings-modal!)))
+     :-for         "global_config_edn"}))
+
 (defn edit-custom-css []
 (defn edit-custom-css []
   (row-with-button-action
   (row-with-button-action
     {:left-label   (t :settings-page/custom-theme)
     {:left-label   (t :settings-page/custom-theme)
@@ -551,6 +560,7 @@
      (version-row t version)
      (version-row t version)
      (language-row t preferred-language)
      (language-row t preferred-language)
      (theme-modes-row t switch-theme system-theme? dark?)
      (theme-modes-row t switch-theme system-theme? dark?)
+     (when (config/global-config-enabled?) (edit-global-config-edn))
      (when current-repo (edit-config-edn))
      (when current-repo (edit-config-edn))
      (when current-repo (edit-custom-css))
      (when current-repo (edit-custom-css))
      (when current-repo (edit-export-css))
      (when current-repo (edit-export-css))
@@ -611,7 +621,7 @@
         developer-mode? (state/sub [:ui/developer-mode?])
         developer-mode? (state/sub [:ui/developer-mode?])
         https-agent-opts (state/sub [:electron/user-cfgs :settings/agent])]
         https-agent-opts (state/sub [:electron/user-cfgs :settings/agent])]
     [:div.panel-wrap.is-advanced
     [:div.panel-wrap.is-advanced
-     (when (and util/mac? (util/electron?)) (app-auto-update-row t))
+     (when (and (or util/mac? util/win32?) (util/electron?)) (app-auto-update-row t))
      (usage-diagnostics-row t instrument-disabled?)
      (usage-diagnostics-row t instrument-disabled?)
      (when-not (mobile-util/native-platform?) (developer-mode-row t developer-mode?))
      (when-not (mobile-util/native-platform?) (developer-mode-row t developer-mode?))
      (when (util/electron?) (https-user-agent-row https-agent-opts))
      (when (util/electron?) (https-user-agent-row https-agent-opts))

+ 1 - 1
src/main/frontend/components/shortcut.cljs

@@ -79,7 +79,7 @@
          [:th.text-right]]]
          [:th.text-right]]]
        [:tbody
        [:tbody
         (map (fn [[k {:keys [binding]}]]
         (map (fn [[k {:keys [binding]}]]
-               [:tr {:key k}
+               [:tr {:key (str k)}
                 [:td.text-left (t (dh/decorate-namespace k))]
                 [:td.text-left (t (dh/decorate-namespace k))]
                 (shortcut-col k binding configurable? (t (dh/decorate-namespace k)))])
                 (shortcut-col k binding configurable? (t (dh/decorate-namespace k)))])
           (dh/binding-by-category name))]]])))
           (dh/binding-by-category name))]]])))

+ 10 - 8
src/main/frontend/components/sidebar.cljs

@@ -140,7 +140,7 @@
       (rfe/push-state :page {:name "Favorites"})
       (rfe/push-state :page {:name "Favorites"})
       (util/stop e))}
       (util/stop e))}
 
 
-   (let [favorites (->> (:favorites (state/sub-graph-config))
+   (let [favorites (->> (:favorites (state/sub-config))
                         (remove string/blank?)
                         (remove string/blank?)
                         (filter string?))]
                         (filter string?))]
      (when (seq favorites)
      (when (seq favorites)
@@ -402,14 +402,16 @@
          [:div.mt-20
          [:div.mt-20
           [:div.ls-center
           [:div.ls-center
            (ui/loading (t :loading))]]
            (ui/loading (t :loading))]]
-
+         
          :else
          :else
-         [:div {:class (if margin-less-pages? "" (util/hiccup->class "max-w-7xl.mx-auto.pb-24"))
-                :style {:margin-bottom (cond
-                                         margin-less-pages? 0
-                                         onboarding-and-home? -48
-                                         :else 120)
-                        :padding-bottom (when (mobile-util/native-iphone?) "7rem")}}
+         [:div
+          {:class (if margin-less-pages? "" (util/hiccup->class "mx-auto.pb-24"))
+           :style {:margin-bottom (cond
+                                    global-graph-pages? 0
+                                    margin-less-pages? 0
+                                    onboarding-and-home? -48
+                                    :else 120)
+                   :padding-bottom (when (mobile-util/native-iphone?) "7rem")}}
           main-content])
           main-content])
 
 
        (when onboarding-and-home?
        (when onboarding-and-home?

+ 23 - 12
src/main/frontend/components/sidebar.css

@@ -304,7 +304,7 @@
   top: var(--ls-headbar-inner-top-padding);
   top: var(--ls-headbar-inner-top-padding);
   left: 0;
   left: 0;
   z-index: var(--ls-z-index-level-5);
   z-index: var(--ls-z-index-level-5);
-  transition: width .5s;
+  transition: width .3s;
 
 
   a {
   a {
     color: var(--ls-primary-text-color);
     color: var(--ls-primary-text-color);
@@ -443,9 +443,31 @@ html[data-theme='dark'] {
   z-index: var(--ls-z-index-level-1);
   z-index: var(--ls-z-index-level-1);
   transition: width 0.3s;
   transition: width 0.3s;
   background-color: var(--ls-secondary-background-color, #d8e1e8);
   background-color: var(--ls-secondary-background-color, #d8e1e8);
+  position: relative;
+
+  .resizer {
+    @apply absolute top-0 bottom-0;
+    left: 0;
+    width: 4px;
+    user-select: none;
+    cursor: col-resize !important;
+    transition: background-color 300ms;
+    transition-delay: 300ms;
+    z-index: 1000;
+
+    &:hover,
+    &:focus,
+    &:active {
+      background-color: var(--ls-active-primary-color);
+    }
+  }
 
 
   &.closed {
   &.closed {
     width: 0 !important;
     width: 0 !important;
+
+    .resizer {
+      left: -4px;
+    }
   }
   }
 
 
   &.open {
   &.open {
@@ -460,17 +482,6 @@ html[data-theme='dark'] {
 
 
   &-inner {
   &-inner {
     padding-top: 0;
     padding-top: 0;
-    position: relative;
-
-    .resizer {
-      position: absolute;
-      top: 0;
-      bottom: 0;
-      left: 0;
-      width: 4px;
-      user-select: none;
-      cursor: col-resize !important;
-    }
   }
   }
 
 
   &-settings {
   &-settings {

+ 11 - 0
src/main/frontend/components/theme.css

@@ -102,4 +102,15 @@ html.is-resizing-buf {
   #right-sidebar {
   #right-sidebar {
     transition: none;
     transition: none;
   }
   }
+
+  * {
+    cursor: col-resize !important;
+    user-select: none;
+  }
+
+  &.cursor-w-resize {
+    * {
+      cursor: w-resize !important;
+    }
+  }
 }
 }

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

@@ -264,6 +264,10 @@
 
 
 (def config-default-content (rc/inline "config.edn"))
 (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 idb-db-prefix "logseq-db/")
 (defonce local-db-prefix "logseq_local_")
 (defonce local-db-prefix "logseq_local_")
 (defonce local-handle "handle")
 (defonce local-handle "handle")
@@ -373,9 +377,9 @@
                         page-name)]
                         page-name)]
     (get-file-path repo-url (str sub-dir "/" page-basename "." ext))))
     (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]
   ([repo]
    (when repo
    (when repo
      (get-file-path repo (str app-name "/" config-file)))))
      (get-file-path repo (str app-name "/" config-file)))))
@@ -430,4 +434,4 @@
 
 
 (defn get-block-hidden-properties
 (defn get-block-hidden-properties
   []
   []
-  (get-in @state/state [:config (state/get-current-repo) :block-hidden-properties]))
+  (:block-hidden-properties (state/get-config)))

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

@@ -1,6 +1,7 @@
 (ns frontend.context.i18n
 (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]
   (:require [frontend.dicts :as dicts]
             [frontend.modules.shortcut.dicts :as shortcut-dicts]
             [frontend.modules.shortcut.dicts :as shortcut-dicts]
             [tongue.core :as tongue]
             [tongue.core :as tongue]

+ 3 - 6
src/main/frontend/db.cljs

@@ -179,12 +179,9 @@
     (restore-graph-from-text! repo stored)))
     (restore-graph-from-text! repo stored)))
 
 
 (defn restore!
 (defn restore!
-  [{:keys [repos]} _old-db-schema restore-config-handler]
-  (let [repo (or (state/get-current-repo) (:url (first repos)))]
-    (when repo
-      (p/let [_ (restore-graph! repo)]
-        (restore-config-handler repo)
-        (listen-and-persist! repo)))))
+  [repo]
+  (p/let [_ (restore-graph! repo)]
+    (listen-and-persist! repo)))
 
 
 (defn run-batch-txs!
 (defn run-batch-txs!
   []
   []

+ 4 - 3
src/main/frontend/db/query_react.cljs

@@ -65,9 +65,10 @@
                                   result)]
                                   result)]
                      (model/with-pages result))
                      (model/with-pages result))
                    result)
                    result)
-          result-transform-fn (:result-transform q)
-          repo (state/get-current-repo)]
-      (if-let [result-transform (if (keyword? result-transform-fn) (state/sub [:config repo :query/result-transforms result-transform-fn]) result-transform-fn)]
+          result-transform-fn (:result-transform q)]
+      (if-let [result-transform (if (keyword? result-transform-fn)
+                                  (get-in (state/sub-config) [:query/result-transforms result-transform-fn])
+                                  result-transform-fn)]
         (if-let [f (sci/eval-string (pr-str result-transform))]
         (if-let [f (sci/eval-string (pr-str result-transform))]
           (try
           (try
             (sci/call-fn f result)
             (sci/call-fn f result)

+ 90 - 39
src/main/frontend/dicts.cljc

@@ -70,6 +70,7 @@
         :right-side-bar/flashcards "Flashcards"
         :right-side-bar/flashcards "Flashcards"
         :right-side-bar/new-page "New page"
         :right-side-bar/new-page "New page"
         :right-side-bar/show-journals "Show Journals"
         :right-side-bar/show-journals "Show Journals"
+        :right-side-bar/separator "Right sidebar resize handler"
         :left-side-bar/journals "Journals"
         :left-side-bar/journals "Journals"
         :left-side-bar/new-page "New page"
         :left-side-bar/new-page "New page"
         :left-side-bar/nav-favorites "Favorites"
         :left-side-bar/nav-favorites "Favorites"
@@ -155,9 +156,11 @@
         :settings-page/git-switcher-label "Enable Git auto commit"
         :settings-page/git-switcher-label "Enable Git auto commit"
         :settings-page/git-commit-delay "Git auto commit seconds"
         :settings-page/git-commit-delay "Git auto commit seconds"
         :settings-page/edit-config-edn "Edit config.edn"
         :settings-page/edit-config-edn "Edit config.edn"
+        :settings-page/edit-global-config-edn "Edit global config.edn"
         :settings-page/edit-custom-css "Edit custom.css"
         :settings-page/edit-custom-css "Edit custom.css"
         :settings-page/edit-export-css "Edit export.css"
         :settings-page/edit-export-css "Edit export.css"
         :settings-page/custom-configuration "Custom configuration"
         :settings-page/custom-configuration "Custom configuration"
+        :settings-page/custom-global-configuration "Custom global configuration"
         :settings-page/custom-theme "Custom theme"
         :settings-page/custom-theme "Custom theme"
         :settings-page/export-theme "Export theme"
         :settings-page/export-theme "Export theme"
         :settings-page/show-brackets "Show brackets"
         :settings-page/show-brackets "Show brackets"
@@ -2392,8 +2395,8 @@
            :settings-page/network-proxy "Nettverksproxy"
            :settings-page/network-proxy "Nettverksproxy"
            :settings-page/plugin-system "System for utvidelser"}
            :settings-page/plugin-system "System for utvidelser"}
 
 
-   :pt-BR {:on-boarding/demo-graph "Esse é um gráfico de demonstração, mudanças não serão salvas enquanto uma pasta local não for aberta."
-           :on-boarding/add-graph "Adicionar gráfico"
+   :pt-BR {:on-boarding/demo-graph "Esse é um grafo de demonstração, mudanças não serão salvas enquanto uma pasta local não for aberta."
+           :on-boarding/add-graph "Adicionar grafo"
            :on-boarding/open-local-dir "Abrir pasta local"
            :on-boarding/open-local-dir "Abrir pasta local"
            :on-boarding/new-graph-desc-1 "Logseq funciona com Markdown e Org-mode. Você pode abrir uma pasta existente ou criar uma nova em seu dispositivo. Seus dados serão armazenados apenas neste dispositivo."
            :on-boarding/new-graph-desc-1 "Logseq funciona com Markdown e Org-mode. Você pode abrir uma pasta existente ou criar uma nova em seu dispositivo. Seus dados serão armazenados apenas neste dispositivo."
            :on-boarding/new-graph-desc-2 "Após abrir sua pasta, três pastas serão criadas nela:"
            :on-boarding/new-graph-desc-2 "Após abrir sua pasta, três pastas serão criadas nela:"
@@ -2444,7 +2447,7 @@
            :right-side-bar/recent "Recente"
            :right-side-bar/recent "Recente"
            :right-side-bar/contents "Conteúdo"
            :right-side-bar/contents "Conteúdo"
            :right-side-bar/favorites "Favoritos"
            :right-side-bar/favorites "Favoritos"
-           :right-side-bar/page-graph "Gráfico da página"
+           :right-side-bar/page-graph "Grafo da página"
            :right-side-bar/block-ref "Referência de bloco"
            :right-side-bar/block-ref "Referência de bloco"
            :right-side-bar/flashcards "Flashcards"
            :right-side-bar/flashcards "Flashcards"
            :right-side-bar/new-page "Nova página"
            :right-side-bar/new-page "Nova página"
@@ -2541,7 +2544,7 @@
            :settings-page/disable-developer-mode "Desativar modo de desenvolvimento"
            :settings-page/disable-developer-mode "Desativar modo de desenvolvimento"
            :settings-page/developer-mode-desc "O modo de desenvolvimento ajuda os contribuidores e programadores de extensões a testar as suas integrações com o Logseq de forma eficiente."
            :settings-page/developer-mode-desc "O modo de desenvolvimento ajuda os contribuidores e programadores de extensões a testar as suas integrações com o Logseq de forma eficiente."
            :settings-page/current-version "Versão atual"
            :settings-page/current-version "Versão atual"
-           :settings-page/current-graph "Gráfico atual"
+           :settings-page/current-graph "Grafo atual"
            :settings-page/tab-general "Geral"
            :settings-page/tab-general "Geral"
            :settings-page/tab-editor "Editor"
            :settings-page/tab-editor "Editor"
            :settings-page/tab-shortcuts "Atalhos"
            :settings-page/tab-shortcuts "Atalhos"
@@ -2562,16 +2565,16 @@
            :search/publishing "Pesquisar"
            :search/publishing "Pesquisar"
            :search "Pesquisar ou Criar Página"
            :search "Pesquisar ou Criar Página"
            :page-search "Pesquisar na página atual"
            :page-search "Pesquisar na página atual"
-           :graph-search "Pesquisar gráfico"
+           :graph-search "Pesquisar grafo"
            :new-page "Nova página"
            :new-page "Nova página"
            :new-file "Novo arquivo"
            :new-file "Novo arquivo"
-           :new-graph "Adicionar novo gráfico"
-           :graph "Gráfico"
-           :graph-view "Ver Gráfico"
+           :new-graph "Adicionar novo grafo"
+           :graph "Grafo"
+           :graph-view "Ver Grafo"
            :cards-view "Ver Cartões"
            :cards-view "Ver Cartões"
            :publishing "Publicando"
            :publishing "Publicando"
            :export "Exportar"
            :export "Exportar"
-           :export-graph "Exportar Gráfico"
+           :export-graph "Exportar Grafo"
            :export-markdown "Exportar como Markdown padrão (sem propriedades de bloco)"
            :export-markdown "Exportar como Markdown padrão (sem propriedades de bloco)"
            :export-opml "Exportar como OPML"
            :export-opml "Exportar como OPML"
            :export-page "Exportar página"
            :export-page "Exportar página"
@@ -2581,7 +2584,7 @@
            :export-edn "Exportar como EDN"
            :export-edn "Exportar como EDN"
            :export-datascript-edn "Exportar datascript EDN"
            :export-datascript-edn "Exportar datascript EDN"
            :convert-markdown "Converter cabeçalhos Markdown para listas não-ordenadas (# -> -)"
            :convert-markdown "Converter cabeçalhos Markdown para listas não-ordenadas (# -> -)"
-           :all-graphs "Todos os gráficos"
+           :all-graphs "Todos os grafos"
            :all-pages "Todas as páginas"
            :all-pages "Todas as páginas"
            :all-files "Todos os arquivos"
            :all-files "Todos os arquivos"
            :all-journals "Todos os diários"
            :all-journals "Todos os diários"
@@ -2610,7 +2613,7 @@
            :white "Claro"
            :white "Claro"
            :dark "Escuro"
            :dark "Escuro"
            :remove-background "Remover fundo"
            :remove-background "Remover fundo"
-           :re-index-detail "Re-indexar gráfico"
+           :re-index-detail "Re-indexar grafo"
            :open "Abrir"
            :open "Abrir"
            :open-a-directory "Abrir uma pasta local"
            :open-a-directory "Abrir uma pasta local"
            :open-new-window "Nova janela"
            :open-new-window "Nova janela"
@@ -2664,13 +2667,13 @@
            :plugin/update-available "Atualização disponível"
            :plugin/update-available "Atualização disponível"
            :plugin/updating "Atualizando"
            :plugin/updating "Atualizando"
            :right-side-bar/all-pages "Todas as páginas"
            :right-side-bar/all-pages "Todas as páginas"
-           :right-side-bar/graph-view "Ver gráfico"
+           :right-side-bar/graph-view "Ver grafo"
            :search/page-names "Procurar nome da página"
            :search/page-names "Procurar nome da página"
            :plugin/stars "Estrelas"
            :plugin/stars "Estrelas"
            :select/default-prompt "Selecione um"
            :select/default-prompt "Selecione um"
-           :select.graph/prompt "Selecione um gráfico"
-           :select.graph/add-graph "Sim, adicionar outro gráfico"
-           :select.graph/empty-placeholder-description "Nenhum gráfico encontrado. Deseja adicionar um novo?"
+           :select.graph/prompt "Selecione um grafo"
+           :select.graph/add-graph "Sim, adicionar outro grafo"
+           :select.graph/empty-placeholder-description "Nenhum grafo encontrado. Deseja adicionar um novo?"
            :settings-page/enable-shortcut-tooltip "Habilitar dicas de atalho"
            :settings-page/enable-shortcut-tooltip "Habilitar dicas de atalho"
            :tips/all-done "Tudo certo"
            :tips/all-done "Tudo certo"
            :updater/new-version-install "Uma nova versão foi baixada"
            :updater/new-version-install "Uma nova versão foi baixada"
@@ -2683,8 +2686,8 @@
            :settings-page/custom-configuration "Configuração personalizada"
            :settings-page/custom-configuration "Configuração personalizada"
            :settings-page/custom-theme "Tema personalizado"
            :settings-page/custom-theme "Tema personalizado"
            :settings-page/edit-custom-css "Editar custom.css"
            :settings-page/edit-custom-css "Editar custom.css"
-           :re-index-multiple-windows-warning "Você precisa fechar as outras janelas antes de reindexar este gráfico."
-           :re-index-discard-unsaved-changes-warning "A reindexação descartará o gráfico atual e processará todos os arquivos novamente conforme estão armazenados no disco. Você perderá as alterações não salvas e pode demorar um pouco. Continuar?"
+           :re-index-multiple-windows-warning "Você precisa fechar as outras janelas antes de reindexar este grafo"
+           :re-index-discard-unsaved-changes-warning "A reindexação descartará o grafo atual e processará todos os arquivos novamente conforme estão armazenados no disco. Você perderá as alterações não salvas e pode demorar um pouco. Continuar?"
            :sync-from-local-changes-detected "Atualizar detecta e processa arquivos modificados em seu disco e que são diferentes do conteúdo atual da página do Logseq. Continuar?"
            :sync-from-local-changes-detected "Atualizar detecta e processa arquivos modificados em seu disco e que são diferentes do conteúdo atual da página do Logseq. Continuar?"
            :page/open-backup-directory "Abra a listagem de backups de página"
            :page/open-backup-directory "Abra a listagem de backups de página"
            :save "Salvar"
            :save "Salvar"
@@ -2700,8 +2703,8 @@
            :settings-page/plugin-system "Sistema de Plugins"
            :settings-page/plugin-system "Sistema de Plugins"
            :settings-page/network-proxy "Proxy de Rede"
            :settings-page/network-proxy "Proxy de Rede"
 
 
-           :file-sync/other-user-graph "O gráfico local atual é obrigado ao gráfico remoto de outro usuário. Portanto, não consigo iniciar a sincronização."
-           :file-sync/graph-deleted "O gráfico remoto atual foi excluído"
+           :file-sync/other-user-graph "O grafo local atual está ligado ao grafo remoto de outro usuário. Portanto, não consigo iniciar a sincronização."
+           :file-sync/graph-deleted "O grafo remoto atual foi excluído"
 
 
            :page/copy-page-url "Copiar URL da página"
            :page/copy-page-url "Copiar URL da página"
            :plugin/not-installed "Não instalado"
            :plugin/not-installed "Não instalado"
@@ -2709,7 +2712,26 @@
            :tutorial/text "tutorial-en.md"
            :tutorial/text "tutorial-en.md"
            :settings-page/edit-export-css "Editar export.css"
            :settings-page/edit-export-css "Editar export.css"
            :settings-page/enable-flashcards "Flashcards"
            :settings-page/enable-flashcards "Flashcards"
-           :settings-page/export-theme "Exportar Tema"}
+           :settings-page/export-theme "Exportar Tema"
+           
+           :discourse-title "Nosso fórum!"
+           :importing "Importando"
+           :asset/copy "Copiar imagem"
+           :asset/delete "Excluir imagem"
+           :asset/maximize "Expandir imagem"
+
+           :asset/open-in-browser "Abrir imagem no navegador"
+           :asset/show-in-folder "Mostrar imagem na pasta"
+           :graph/all-graphs "Todos os grafos"
+           :graph/local-graphs "Grafos locais"
+           :graph/remote-graphs "Grafos remotos"
+           :help/forum-community "Comunidade do fórum"
+           :linked-references/filter-search "Procurar em páginas vinculadas"
+           :right-side-bar/show-journals "Mostrar registros"
+           :settings-page/custom-global-configuration "Configuração global personalizada"
+           :settings-page/edit-global-config-edn "Editar config.edn global"
+           :settings-page/sync "Sincronizar"
+           :settings-page/tab-features "Recursos"}
 
 
    :pt-PT {:on-boarding/demo-graph "Isto é um grafo de demonstração, nenhuma mudança será guardada até abrir uma pasta local."
    :pt-PT {:on-boarding/demo-graph "Isto é um grafo de demonstração, nenhuma mudança será guardada até abrir uma pasta local."
            :on-boarding/add-graph "Adicionar grafo"
            :on-boarding/add-graph "Adicionar grafo"
@@ -3033,7 +3055,26 @@
         :settings-page/enable-flashcards "Flashcards"
         :settings-page/enable-flashcards "Flashcards"
         :settings-page/export-theme "Exportar tema"
         :settings-page/export-theme "Exportar tema"
         :settings-page/network-proxy "Proxy de rede"
         :settings-page/network-proxy "Proxy de rede"
-        :settings-page/plugin-system "Sistema de plugins"}
+        :settings-page/plugin-system "Sistema de plugins"
+        
+        :discourse-title "Nosso fórum!"
+        :importing "Importando"
+        :asset/copy "Copiar imagem"
+        :asset/delete "Excluir imagem"
+        :asset/maximize "Expandir imagem"
+
+        :asset/open-in-browser "Abrir imagem no navegador"
+        :asset/show-in-folder "Mostrar imagem na pasta"
+        :graph/all-graphs "Todos os grafos"
+        :graph/local-graphs "Grafos locais"
+        :graph/remote-graphs "Grafos remotos"
+        :help/forum-community "Comunidade do fórum"
+        :linked-references/filter-search "Procurar em páginas vinculadas"
+        :right-side-bar/show-journals "Mostrar registros"
+        :settings-page/custom-global-configuration "Configuração global personalizada"
+        :settings-page/edit-global-config-edn "Editar config.edn global"
+        :settings-page/sync "Sincronizar"
+        :settings-page/tab-features "Recursos"}
 
 
    :ru {:on-boarding/demo-graph "Это демонстрационный граф, изменения не будут сохранены, пока вы не откроете локальный файл."
    :ru {:on-boarding/demo-graph "Это демонстрационный граф, изменения не будут сохранены, пока вы не откроете локальный файл."
         :on-boarding/add-graph "Добавить новый граф"
         :on-boarding/add-graph "Добавить новый граф"
@@ -4013,8 +4054,8 @@
                                 :default "tutorial-tr.md")
                                 :default "tutorial-tr.md")
         :tutorial/dummy-notes #?(:cljs (rc/inline "dummy-notes-tr.md")
         :tutorial/dummy-notes #?(:cljs (rc/inline "dummy-notes-tr.md")
                                        :default "dummy-notes-tr.md")
                                        :default "dummy-notes-tr.md")
-        :on-boarding/demo-graph "Bu bir demo grafiktir, yerel bir klasör açana kadar değişiklikler kaydedilmeyecektir."
-        :on-boarding/add-graph "Bir grafik ekle"
+        :on-boarding/demo-graph "Bu bir demo çizelgedir, yerel bir klasör açana kadar değişiklikler kaydedilmeyecektir."
+        :on-boarding/add-graph "Bir çizelge ekle"
         :on-boarding/open-local-dir "Yerel bir dizin açın"
         :on-boarding/open-local-dir "Yerel bir dizin açın"
         :on-boarding/new-graph-desc-1 "Logseq, hem Markdown hem de Org modunu destekler. Cihazınızda var olan bir dizini (klasörü) açabilir veya yeni bir tane oluşturabilirsiniz. Verileriniz yalnızca bu cihazda saklanacaktır."
         :on-boarding/new-graph-desc-1 "Logseq, hem Markdown hem de Org modunu destekler. Cihazınızda var olan bir dizini (klasörü) açabilir veya yeni bir tane oluşturabilirsiniz. Verileriniz yalnızca bu cihazda saklanacaktır."
         :on-boarding/new-graph-desc-2 "Dizininizi açtıktan sonra, o dizinde üç klasör oluşturacaktır:"
         :on-boarding/new-graph-desc-2 "Dizininizi açtıktan sonra, o dizinde üç klasör oluşturacaktır:"
@@ -4067,9 +4108,9 @@
         :right-side-bar/recent "En son"
         :right-side-bar/recent "En son"
         :right-side-bar/contents "İçindekiler"
         :right-side-bar/contents "İçindekiler"
         :right-side-bar/favorites "Sık kullanılanlar"
         :right-side-bar/favorites "Sık kullanılanlar"
-        :right-side-bar/page-graph "Sayfa grafiği"
+        :right-side-bar/page-graph "Sayfa çizelgesi"
         :right-side-bar/block-ref "Blok referansı"
         :right-side-bar/block-ref "Blok referansı"
-        :right-side-bar/graph-view "Grafik görünümü"
+        :right-side-bar/graph-view "Çizelge görünümü"
         :right-side-bar/all-pages "Bütün sayfalar"
         :right-side-bar/all-pages "Bütün sayfalar"
         :right-side-bar/flashcards "Bilgi kartları"
         :right-side-bar/flashcards "Bilgi kartları"
         :right-side-bar/new-page "Yeni sayfa"
         :right-side-bar/new-page "Yeni sayfa"
@@ -4137,6 +4178,11 @@
         :draw/more-options "Diğer seçenekler"
         :draw/more-options "Diğer seçenekler"
         :draw/back-to-logseq "Logseq'e geri dön"
         :draw/back-to-logseq "Logseq'e geri dön"
         :text/image "Resim"
         :text/image "Resim"
+        :asset/show-in-folder "Resmi klasörde göster"
+        :asset/open-in-browser "Resmi tarayıcıda aç"
+        :asset/delete "Resmi sil"
+        :asset/copy "Resmi kopyala"
+        :asset/maximize "Resim ekranı kaplasın"
         :asset/confirm-delete "Bu resmi silmek istediğinizden emin misiniz?"
         :asset/confirm-delete "Bu resmi silmek istediğinizden emin misiniz?"
         :asset/physical-delete "Dosyayı da kaldırın (geri getirilemeyeceğine dikkat edin)"
         :asset/physical-delete "Dosyayı da kaldırın (geri getirilemeyeceğine dikkat edin)"
         :content/copy "Kopyala"
         :content/copy "Kopyala"
@@ -4183,15 +4229,17 @@
         :settings-page/disable-developer-mode "Geliştirici modunu devre dışı bırak"
         :settings-page/disable-developer-mode "Geliştirici modunu devre dışı bırak"
         :settings-page/developer-mode-desc "Geliştirici modu, katkıda bulunanların ve eklenti geliştiricilerinin Logseq ile entegrasyonlarını daha verimli bir şekilde test etmesine yardımcı olur."
         :settings-page/developer-mode-desc "Geliştirici modu, katkıda bulunanların ve eklenti geliştiricilerinin Logseq ile entegrasyonlarını daha verimli bir şekilde test etmesine yardımcı olur."
         :settings-page/current-version "Geçerli sürüm"
         :settings-page/current-version "Geçerli sürüm"
-        :settings-page/current-graph "Geçerli grafik"
+        :settings-page/current-graph "Geçerli çizelge"
         :settings-page/tab-general "Genel"
         :settings-page/tab-general "Genel"
         :settings-page/tab-editor "Düzenleyici"
         :settings-page/tab-editor "Düzenleyici"
         :settings-page/tab-shortcuts "Kısayollar"
         :settings-page/tab-shortcuts "Kısayollar"
         :settings-page/tab-version-control "Sürüm denetimi"
         :settings-page/tab-version-control "Sürüm denetimi"
         :settings-page/tab-advanced "Gelişmiş"
         :settings-page/tab-advanced "Gelişmiş"
-        :settings-page/plugin-system "Eklenti sistemi"
+        :settings-page/tab-features "Özellikler"
+        :settings-page/plugin-system "Eklentiler"
         :settings-page/enable-flashcards "Bilgi kartları"
         :settings-page/enable-flashcards "Bilgi kartları"
         :settings-page/network-proxy "Ağ ara sunucusu"
         :settings-page/network-proxy "Ağ ara sunucusu"
+        :settings-page/sync "Eşitle"
         :logseq "Logseq"
         :logseq "Logseq"
         :on "AÇIK"
         :on "AÇIK"
         :more-options "Diğer seçenekler"
         :more-options "Diğer seçenekler"
@@ -4208,7 +4256,7 @@
         :port "Bağlantı Noktası"
         :port "Bağlantı Noktası"
         :re-index "Yeniden dizin oluştur"
         :re-index "Yeniden dizin oluştur"
         :re-index-detail "Grafiği yeniden oluştur"
         :re-index-detail "Grafiği yeniden oluştur"
-        :re-index-multiple-windows-warning "Bu grafik için yeniden dizin oluşturmadan önce diğer pencereleri kapatmanız gerekiyor."
+        :re-index-multiple-windows-warning "Bu çizelge için yeniden dizin oluşturmadan önce diğer pencereleri kapatmanız gerekiyor."
         :re-index-discard-unsaved-changes-warning "Yeniden dizin oluşturmak mevcut grafiği siler ve ardından tüm dosyaları o anda diskte depolandıkları şekilde yeniden işler. Kaydedilmemiş değişiklikleri kaybedeceksiniz ve bu biraz zaman alabilir. Devam edilsin mi?"
         :re-index-discard-unsaved-changes-warning "Yeniden dizin oluşturmak mevcut grafiği siler ve ardından tüm dosyaları o anda diskte depolandıkları şekilde yeniden işler. Kaydedilmemiş değişiklikleri kaybedeceksiniz ve bu biraz zaman alabilir. Devam edilsin mi?"
         :open-new-window "Yeni pencere"
         :open-new-window "Yeni pencere"
         :sync-from-local-files "Yenile"
         :sync-from-local-files "Yenile"
@@ -4219,21 +4267,24 @@
         :search/publishing "Ara"
         :search/publishing "Ara"
         :search "Ara veya sayfa oluştur"
         :search "Ara veya sayfa oluştur"
         :page-search "Geçerli sayfada ara"
         :page-search "Geçerli sayfada ara"
-        :graph-search "Grafikte ara"
+        :graph-search "Çizelgede ara"
         :new-page "Yeni sayfa"
         :new-page "Yeni sayfa"
         :new-file "Yeni dosya"
         :new-file "Yeni dosya"
-        :new-graph "Yeni grafik ekle"
-        :graph "Grafik"
-        :graph-view "Grafiği görüntüle"
+        :new-graph "Yeni çizelge ekle"
+        :graph "Çizelge"
+        :graph-view "Çizelgeyi görüntüle"
         :graph/persist "Logseq dahili durumu senkronize ediyor, lütfen birkaç saniye bekleyin."
         :graph/persist "Logseq dahili durumu senkronize ediyor, lütfen birkaç saniye bekleyin."
         :graph/persist-error "Dahili durum senkronize edilemedi."
         :graph/persist-error "Dahili durum senkronize edilemedi."
         :graph/save "Kaydediliyor..."
         :graph/save "Kaydediliyor..."
         :graph/save-success "Başarıyla Kaydedildi"
         :graph/save-success "Başarıyla Kaydedildi"
         :graph/save-error "Kaydedilemedi"
         :graph/save-error "Kaydedilemedi"
+        :graph/all-graphs "Tüm çizelgeler"
+        :graph/local-graphs "Yerel çizelgeler"
+        :graph/remote-graphs "Uzak çizelgeler"
         :cards-view "Kartları görüntüle"
         :cards-view "Kartları görüntüle"
         :publishing "Yayımlama"
         :publishing "Yayımlama"
         :export "Dışarı aktar"
         :export "Dışarı aktar"
-        :export-graph "Grafiği dışarı aktar"
+        :export-graph "Çizelgeyi dışarı aktar"
         :export-page "Sayfayı dışarı aktar"
         :export-page "Sayfayı dışarı aktar"
         :export-markdown "Standart Markdown olarak dışarı aktar (blok özelliği yok)"
         :export-markdown "Standart Markdown olarak dışarı aktar (blok özelliği yok)"
         :export-opml "OPML olarak dışarı aktar"
         :export-opml "OPML olarak dışarı aktar"
@@ -4243,7 +4294,7 @@
         :export-edn "EDN olarak dışarı aktar"
         :export-edn "EDN olarak dışarı aktar"
         :export-datascript-edn "EDN datascript öğesini dışarı aktar"
         :export-datascript-edn "EDN datascript öğesini dışarı aktar"
         :convert-markdown "Markdown başlıklarını sırasız listelere dönüştürün (# -> -)"
         :convert-markdown "Markdown başlıklarını sırasız listelere dönüştürün (# -> -)"
-        :all-graphs "Tüm grafikler"
+        :all-graphs "Tüm çizelgeler"
         :all-pages "Tüm sayfalar"
         :all-pages "Tüm sayfalar"
         :all-files "Tüm dosyalar"
         :all-files "Tüm dosyalar"
         :remove-orphaned-pages "Yalnız bırakılmış sayfaları kaldır"
         :remove-orphaned-pages "Yalnız bırakılmış sayfaları kaldır"
@@ -4332,12 +4383,12 @@
 
 
         :command-palette/prompt "Bir komut yazın"
         :command-palette/prompt "Bir komut yazın"
         :select/default-prompt "Birini seçin"
         :select/default-prompt "Birini seçin"
-        :select.graph/prompt "Bir grafik seçin"
-        :select.graph/empty-placeholder-description "Eşleşen grafik yok. Bir tane daha eklemek ister misin?"
-        :select.graph/add-graph "Evet, başka bir grafik ekle"
+        :select.graph/prompt "Bir çizelge seçin"
+        :select.graph/empty-placeholder-description "Eşleşen çizelge yok. Bir tane daha eklemek ister misin?"
+        :select.graph/add-graph "Evet, başka bir çizelge ekle"
 
 
-        :file-sync/other-user-graph "Geçerli yerel grafik, diğer kullanıcının uzak grafiğine bağlıdır. Bu yüzden senkronizasyon başlatılamıyor."
-        :file-sync/graph-deleted "Geçerli uzak grafik silindi"}
+        :file-sync/other-user-graph "Geçerli yerel çizelge, diğer kullanıcının uzak çizelgesine bağlıdır. Bu yüzden senkronizasyon başlatılamıyor."
+        :file-sync/graph-deleted "Geçerli uzak çizelge silindi"}
 
 
    :ko {:tutorial/text #?(:cljs (rc/inline "tutorial-ko.md")
    :ko {:tutorial/text #?(:cljs (rc/inline "tutorial-ko.md")
                           :default "tutorial-ko.md")
                           :default "tutorial-ko.md")

+ 6 - 4
src/main/frontend/extensions/latex.cljs

@@ -18,10 +18,12 @@
   [state]
   [state]
   (let [[id s display?] (:rum/args state)]
   (let [[id s display?] (:rum/args state)]
     (try
     (try
-      (js/katex.render s (gdom/getElement id)
-                      #js {:displayMode display?
-                           :throwOnError false
-                           :strict false})
+      (when-let [elem (gdom/getElement id)]
+        (js/katex.render s elem
+                         #js {:displayMode display?
+                              :throwOnError false
+                              :strict false}))
+
       (catch js/Error e
       (catch js/Error e
         (js/console.error e)))))
         (js/console.error e)))))
 
 

+ 1 - 1
src/main/frontend/extensions/zotero/setting.cljs

@@ -19,7 +19,7 @@
 
 
 (defn sub-zotero-config
 (defn sub-zotero-config
   []
   []
-  (:zotero/settings-v2 (get (state/sub-config) (state/get-current-repo))))
+  (:zotero/settings-v2 (state/sub-config)))
 
 
 (defn all-profiles []
 (defn all-profiles []
   (let [profiles (-> (sub-zotero-config) keys set)
   (let [profiles (-> (sub-zotero-config) keys set)

+ 2 - 2
src/main/frontend/fs.cljs

@@ -172,8 +172,8 @@
         result))))
         result))))
 
 
 (defn watch-dir!
 (defn watch-dir!
-  [dir]
-  (protocol/watch-dir! (get-record) dir))
+  ([dir] (watch-dir! dir {}))
+  ([dir options] (protocol/watch-dir! (get-record) dir options)))
 
 
 (defn unwatch-dir!
 (defn unwatch-dir!
   [dir]
   [dir]

+ 1 - 1
src/main/frontend/fs/bfs.cljs

@@ -34,7 +34,7 @@
     nil)
     nil)
   (get-files [_this _path-or-handle _ok-handler]
   (get-files [_this _path-or-handle _ok-handler]
     nil)
     nil)
-  (watch-dir! [_this _dir]
+  (watch-dir! [_this _dir _options]
     nil)
     nil)
   (unwatch-dir! [_this _dir]
   (unwatch-dir! [_this _dir]
     nil))
     nil))

+ 7 - 6
src/main/frontend/fs/capacitor_fs.cljs

@@ -2,6 +2,7 @@
   (:require ["@capacitor/filesystem" :refer [Encoding Filesystem]]
   (:require ["@capacitor/filesystem" :refer [Encoding Filesystem]]
             [cljs-bean.core :as bean]
             [cljs-bean.core :as bean]
             [clojure.string :as string]
             [clojure.string :as string]
+            [goog.string :as gstring]
             [frontend.config :as config]
             [frontend.config :as config]
             [frontend.db :as db]
             [frontend.db :as db]
             [frontend.encrypt :as encrypt]
             [frontend.encrypt :as encrypt]
@@ -126,11 +127,11 @@
 (def backup-dir "logseq/bak")
 (def backup-dir "logseq/bak")
 (defn- get-backup-dir
 (defn- get-backup-dir
   [repo-dir path ext]
   [repo-dir path ext]
-  (let [path (if (string/starts-with? path "file://")
-               (subs path 7)
-               path)
-        relative-path (-> (string/replace path repo-dir "")
-                          (string/replace (str "." ext) ""))]
+  (let [relative-path (-> path
+                          (string/replace (re-pattern (str "^" (gstring/regExpEscape repo-dir)))
+                                          "")
+                          (string/replace (re-pattern (str "(?i)" (gstring/regExpEscape (str "." ext)) "$"))
+                                          ""))]
     (util/safe-path-join repo-dir (str backup-dir "/" relative-path))))
     (util/safe-path-join repo-dir (str backup-dir "/" relative-path))))
 
 
 (defn- truncate-old-versioned-files!
 (defn- truncate-old-versioned-files!
@@ -351,7 +352,7 @@
       (into [] (concat [{:path path}] files))))
       (into [] (concat [{:path path}] files))))
   (get-files [_this path-or-handle _ok-handler]
   (get-files [_this path-or-handle _ok-handler]
     (readdir path-or-handle))
     (readdir path-or-handle))
-  (watch-dir! [_this dir]
+  (watch-dir! [_this dir _options]
     (p/do!
     (p/do!
      (.unwatch mobile-util/fs-watcher)
      (.unwatch mobile-util/fs-watcher)
      (.watch mobile-util/fs-watcher (clj->js {:path dir}))))
      (.watch mobile-util/fs-watcher (clj->js {:path dir}))))

+ 1 - 1
src/main/frontend/fs/nfs.cljs

@@ -239,5 +239,5 @@
     (utils/getFiles path-or-handle true ok-handler))
     (utils/getFiles path-or-handle true ok-handler))
 
 
   ;; TODO:
   ;; TODO:
-  (watch-dir! [_this _dir]
+  (watch-dir! [_this _dir _options]
     nil))
     nil))

+ 2 - 2
src/main/frontend/fs/node.cljs

@@ -124,7 +124,7 @@
     (open-dir))
     (open-dir))
   (get-files [_this path-or-handle _ok-handler]
   (get-files [_this path-or-handle _ok-handler]
     (ipc/ipc "getFiles" path-or-handle))
     (ipc/ipc "getFiles" path-or-handle))
-  (watch-dir! [_this dir]
-    (ipc/ipc "addDirWatcher" dir))
+  (watch-dir! [_this dir options]
+    (ipc/ipc "addDirWatcher" dir options))
   (unwatch-dir! [_this dir]
   (unwatch-dir! [_this dir]
     (ipc/ipc "unwatchDir" dir)))
     (ipc/ipc "unwatchDir" dir)))

+ 1 - 1
src/main/frontend/fs/protocol.cljs

@@ -15,7 +15,7 @@
   (stat [this dir path])
   (stat [this dir path])
   (open-dir [this ok-handler])
   (open-dir [this ok-handler])
   (get-files [this path-or-handle ok-handler])
   (get-files [this path-or-handle ok-handler])
-  (watch-dir! [this dir])
+  (watch-dir! [this dir options])
   (unwatch-dir! [this dir])
   (unwatch-dir! [this dir])
   ;; Ensure the dir is watched, window agnostic.
   ;; Ensure the dir is watched, window agnostic.
   ;; Implementation should handle the actual watcher's construction / destruction.
   ;; Implementation should handle the actual watcher's construction / destruction.

+ 59 - 49
src/main/frontend/fs/sync.cljs

@@ -702,7 +702,7 @@
           (recur (dec n)))
           (recur (dec n)))
         r))))
         r))))
 
 
-(deftype RSAPI [^:mutable _graph-uuid ^:mutable _private-key ^:mutable _public-key]
+(deftype RSAPI [^:mutable graph-uuid' ^:mutable private-key' ^:mutable public-key']
   IToken
   IToken
   (<get-token [this]
   (<get-token [this]
     (go
     (go
@@ -714,15 +714,15 @@
       (state/get-auth-id-token)))
       (state/get-auth-id-token)))
 
 
   IRSAPI
   IRSAPI
-  (rsapi-ready? [_ graph-uuid] (and (= graph-uuid _graph-uuid) _private-key _public-key))
+  (rsapi-ready? [_ graph-uuid] (and (= graph-uuid graph-uuid') private-key' public-key'))
   (<key-gen [_] (go (js->clj (<! (p->c (ipc/ipc "key-gen")))
   (<key-gen [_] (go (js->clj (<! (p->c (ipc/ipc "key-gen")))
                              :keywordize-keys true)))
                              :keywordize-keys true)))
   (<set-env [_ prod? private-key public-key graph-uuid]
   (<set-env [_ prod? private-key public-key graph-uuid]
     (when (not-empty private-key)
     (when (not-empty private-key)
       (print (util/format "[%s] setting sync age-encryption passphrase..." graph-uuid)))
       (print (util/format "[%s] setting sync age-encryption passphrase..." graph-uuid)))
-    (set! _graph-uuid graph-uuid)
-    (set! _private-key private-key)
-    (set! _public-key public-key)
+    (set! graph-uuid' graph-uuid)
+    (set! private-key' private-key)
+    (set! public-key' public-key)
     (p->c (ipc/ipc "set-env" (if prod? "prod" "dev") private-key public-key)))
     (p->c (ipc/ipc "set-env" (if prod? "prod" "dev") private-key public-key)))
   (<get-local-all-files-meta [_ graph-uuid base-path]
   (<get-local-all-files-meta [_ graph-uuid base-path]
     (go
     (go
@@ -786,7 +786,7 @@
                                     (js->clj r))))))
                                     (js->clj r))))))
 
 
 
 
-(deftype ^:large-vars/cleanup-todo CapacitorAPI [^:mutable _graph-uuid ^:mutable _private-key ^:mutable _public-key]
+(deftype ^:large-vars/cleanup-todo CapacitorAPI [^:mutable graph-uuid' ^:mutable private-key ^:mutable public-key']
   IToken
   IToken
   (<get-token [this]
   (<get-token [this]
     (go
     (go
@@ -798,15 +798,15 @@
       (state/get-auth-id-token)))
       (state/get-auth-id-token)))
 
 
   IRSAPI
   IRSAPI
-  (rsapi-ready? [_ graph-uuid] (and (= graph-uuid _graph-uuid) _private-key _public-key))
+  (rsapi-ready? [_ graph-uuid] (and (= graph-uuid graph-uuid') private-key public-key'))
   (<key-gen [_]
   (<key-gen [_]
     (go (let [r (<! (p->c (.keygen mobile-util/file-sync #js {})))]
     (go (let [r (<! (p->c (.keygen mobile-util/file-sync #js {})))]
           (-> r
           (-> r
               (js->clj :keywordize-keys true)))))
               (js->clj :keywordize-keys true)))))
   (<set-env [_ prod? secret-key public-key graph-uuid]
   (<set-env [_ prod? secret-key public-key graph-uuid]
-    (set! _graph-uuid graph-uuid)
-    (set! _private-key secret-key)
-    (set! _public-key public-key)
+    (set! graph-uuid' graph-uuid)
+    (set! private-key secret-key)
+    (set! public-key' public-key)
     (p->c (.setEnv mobile-util/file-sync (clj->js {:env (if prod? "prod" "dev")
     (p->c (.setEnv mobile-util/file-sync (clj->js {:env (if prod? "prod" "dev")
                                                    :secretKey secret-key
                                                    :secretKey secret-key
                                                    :publicKey public-key}))))
                                                    :publicKey public-key}))))
@@ -1423,7 +1423,7 @@
               r)))))))
               r)))))))
 
 
 (defn apply-filetxns-partitions
 (defn apply-filetxns-partitions
-  "won't call update-graph-txid! when *txid is nil"
+  "won't call update-graphs-txid! when *txid is nil"
   [*sync-state user-uuid graph-uuid base-path filetxns-partitions repo *txid *stopped *paused]
   [*sync-state user-uuid graph-uuid base-path filetxns-partitions repo *txid *stopped *paused]
   (assert (some? *sync-state))
   (assert (some? *sync-state))
 
 
@@ -2356,7 +2356,7 @@
  SyncManager [graph-uuid base-path *sync-state
  SyncManager [graph-uuid base-path *sync-state
               ^Local->RemoteSyncer local->remote-syncer ^Remote->LocalSyncer remote->local-syncer remoteapi
               ^Local->RemoteSyncer local->remote-syncer ^Remote->LocalSyncer remote->local-syncer remoteapi
               ^:mutable ratelimit-local-changes-chan
               ^:mutable ratelimit-local-changes-chan
-              *txid ^:mutable state ^:mutable _remote-change-chan ^:mutable _*ws *stopped? *paused?
+              *txid ^:mutable state ^:mutable remote-change-chan ^:mutable *ws *stopped? *paused?
               ^:mutable ops-chan
               ^:mutable ops-chan
               ;; control chans
               ;; control chans
               private-full-sync-chan private-stop-sync-chan private-remote->local-sync-chan
               private-full-sync-chan private-stop-sync-chan private-remote->local-sync-chan
@@ -2389,8 +2389,8 @@
 
 
   (start [this]
   (start [this]
     (set! ops-chan (chan (async/dropping-buffer 10)))
     (set! ops-chan (chan (async/dropping-buffer 10)))
-    (set! _*ws (atom nil))
-    (set! _remote-change-chan (ws-listen! graph-uuid _*ws))
+    (set! *ws (atom nil))
+    (set! remote-change-chan (ws-listen! graph-uuid *ws))
     (set! ratelimit-local-changes-chan (<ratelimit local->remote-syncer local-changes-revised-chan))
     (set! ratelimit-local-changes-chan (<ratelimit local->remote-syncer local-changes-revised-chan))
     (setup-local->remote! local->remote-syncer)
     (setup-local->remote! local->remote-syncer)
     (async/tap full-sync-mult private-full-sync-chan)
     (async/tap full-sync-mult private-full-sync-chan)
@@ -2406,7 +2406,7 @@
               private-remote->local-sync-chan {:remote->local true}
               private-remote->local-sync-chan {:remote->local true}
               private-full-sync-chan {:local->remote-full-sync true}
               private-full-sync-chan {:local->remote-full-sync true}
               private-pause-resume-chan ([v] (if v {:resume true} {:pause true}))
               private-pause-resume-chan ([v] (if v {:resume true} {:pause true}))
-              _remote-change-chan ([v] (println "remote change:" v) {:remote->local v})
+              remote-change-chan ([v] (println "remote change:" v) {:remote->local v})
               ratelimit-local-changes-chan ([v]
               ratelimit-local-changes-chan ([v]
                                             (let [rest-v (util/drain-chan ratelimit-local-changes-chan)
                                             (let [rest-v (util/drain-chan ratelimit-local-changes-chan)
                                                   vs     (cons v rest-v)]
                                                   vs     (cons v rest-v)]
@@ -2655,7 +2655,7 @@
     (go
     (go
       (when-not @*stopped?
       (when-not @*stopped?
         (vreset! *stopped? true)
         (vreset! *stopped? true)
-        (ws-stop! _*ws)
+        (ws-stop! *ws)
         (offer! private-stop-sync-chan true)
         (offer! private-stop-sync-chan true)
         (async/untap full-sync-mult private-full-sync-chan)
         (async/untap full-sync-mult private-full-sync-chan)
         (async/untap stop-sync-mult private-stop-sync-chan)
         (async/untap stop-sync-mult private-stop-sync-chan)
@@ -2730,54 +2730,64 @@
   [local-graph-uuid]
   [local-graph-uuid]
   {:pre [(util/uuid-string? local-graph-uuid)]}
   {:pre [(util/uuid-string? local-graph-uuid)]}
   (go
   (go
-    (let [result (->> (<! (<list-remote-graphs remoteapi))
-                      :Graphs
-                      (mapv :GraphUUID)
-                      set
-                      (#(contains? % local-graph-uuid)))]
+    (let [r (<! (<list-remote-graphs remoteapi))
+          result
+          (or
+           ;; if api call failed, assume this remote graph still exists
+           (instance? ExceptionInfo r)
+           (and
+            (contains? r :Graphs)
+            (->> (:Graphs r)
+                 (mapv :GraphUUID)
+                 set
+                 (#(contains? % local-graph-uuid)))))]
+
       (when-not result
       (when-not result
         (notification/show! (t :file-sync/graph-deleted) :warning false))
         (notification/show! (t :file-sync/graph-deleted) :warning false))
       result)))
       result)))
 
 
+(declare network-online-cursor)
+
 (defn sync-start []
 (defn sync-start []
   (let [*sync-state                 (atom (sync-state))
   (let [*sync-state                 (atom (sync-state))
         current-user-uuid           (user/user-uuid)
         current-user-uuid           (user/user-uuid)
         repo                        (state/get-current-repo)]
         repo                        (state/get-current-repo)]
     (go
     (go
-      ;; stop previous sync
-      (<! (<sync-stop))
-
-      (<! (p->c (persist-var/-load graphs-txid)))
-
-      (let [[user-uuid graph-uuid txid] @graphs-txid]
-        (when (and user-uuid graph-uuid txid
-                   (user/logged-in?)
-                   repo
-                   (not (config/demo-graph? repo)))
-          (when-some [sm (sync-manager-singleton current-user-uuid graph-uuid
-                                                 (config/get-repo-dir repo) repo
-                                                 txid *sync-state)]
-            (when (check-graph-belong-to-current-user current-user-uuid user-uuid)
-              (if-not (<! (<check-remote-graph-exists graph-uuid)) ; remote graph has been deleted
-                (clear-graphs-txid! repo)
-                (do
-                  (state/set-file-sync-state repo @*sync-state)
-                  (state/set-file-sync-manager sm)
+      (when @network-online-cursor
+        ;; stop previous sync
+        (<! (<sync-stop))
+
+        (<! (p->c (persist-var/-load graphs-txid)))
+
+        (let [[user-uuid graph-uuid txid] @graphs-txid]
+          (when (and user-uuid graph-uuid txid
+                     (user/logged-in?)
+                     repo
+                     (not (config/demo-graph? repo)))
+            (when-some [sm (sync-manager-singleton current-user-uuid graph-uuid
+                                                   (config/get-repo-dir repo) repo
+                                                   txid *sync-state)]
+              (when (check-graph-belong-to-current-user current-user-uuid user-uuid)
+                (if-not (<! (<check-remote-graph-exists graph-uuid)) ; remote graph has been deleted
+                  (clear-graphs-txid! repo)
+                  (do
+                    (state/set-file-sync-state repo @*sync-state)
+                    (state/set-file-sync-manager sm)
 
 
-                  ;; update global state when *sync-state changes
-                  (add-watch *sync-state ::update-global-state
-                             (fn [_ _ _ n]
-                               (state/set-file-sync-state repo n)))
+                    ;; update global state when *sync-state changes
+                    (add-watch *sync-state ::update-global-state
+                               (fn [_ _ _ n]
+                                 (state/set-file-sync-state repo n)))
 
 
-                  (.start sm)
+                    (.start sm)
 
 
-                  (offer! remote->local-full-sync-chan true)
-                  (offer! full-sync-chan true))))))))))
+                    (offer! remote->local-full-sync-chan true)
+                    (offer! full-sync-chan true)))))))))))
 
 
 ;;; ### some add-watches
 ;;; ### some add-watches
 
 
 ;; TOOD: replace this logic by pause/resume state
 ;; TOOD: replace this logic by pause/resume state
-(def network-online-cursor (rum/cursor state/state :network/online?))
+(defonce network-online-cursor (rum/cursor state/state :network/online?))
 (add-watch network-online-cursor "sync-manage"
 (add-watch network-online-cursor "sync-manage"
            (fn [_k _r o n]
            (fn [_k _r o n]
              (cond
              (cond
@@ -2790,7 +2800,7 @@
                :else
                :else
                nil)))
                nil)))
 
 
-(def auth-id-token-cursor (rum/cursor state/state :auth/id-token))
+(defonce auth-id-token-cursor (rum/cursor state/state :auth/id-token))
 (add-watch auth-id-token-cursor "sync-manage"
 (add-watch auth-id-token-cursor "sync-manage"
            (fn [_k _r _o n]
            (fn [_k _r _o n]
              (when (nil? n)
              (when (nil? n)

+ 8 - 6
src/main/frontend/fs/watcher_handler.cljs

@@ -45,10 +45,12 @@
     (db/set-file-last-modified-at! repo path mtime)))
     (db/set-file-last-modified-at! repo path mtime)))
 
 
 (defn handle-changed!
 (defn handle-changed!
-  [type {:keys [dir path content stat] :as payload}]
+  [type {:keys [dir path content stat global-dir] :as payload}]
   (when dir
   (when dir
     (let [path (gp-util/path-normalize path)
     (let [path (gp-util/path-normalize path)
-          repo (config/get-local-repo dir)
+          ;; Global directory events don't know their originating repo so we rely
+          ;; on the client to correctly identify it
+          repo (if global-dir (state/get-current-repo) (config/get-local-repo dir))
           pages-metadata-path (config/get-pages-metadata-path)
           pages-metadata-path (config/get-pages-metadata-path)
           {:keys [mtime]} stat
           {:keys [mtime]} stat
           db-content (or (db/get-file repo path) "")]
           db-content (or (db/get-file repo path) "")]
@@ -90,10 +92,10 @@
           (and (= "unlink" type)
           (and (= "unlink" type)
                (db/file-exists? repo path))
                (db/file-exists? repo path))
           (p/let [dir-exists? (fs/file-exists? dir "")]
           (p/let [dir-exists? (fs/file-exists? dir "")]
-            (when dir-exists?
-              (when-let [page-name (db/get-file-page path)]
-                (println "Delete page: " page-name ", file path: " path ".")
-                (page-handler/delete! page-name #() :delete-file? false))))
+                 (when dir-exists?
+                   (when-let [page-name (db/get-file-page path)]
+                     (println "Delete page: " page-name ", file path: " path ".")
+                     (page-handler/delete! page-name #() :delete-file? false))))
 
 
           (and (contains? #{"add" "change" "unlink"} type)
           (and (contains? #{"add" "change" "unlink"} type)
                (string/ends-with? path "logseq/custom.css"))
                (string/ends-with? path "logseq/custom.css"))

+ 82 - 85
src/main/frontend/handler.cljs

@@ -24,6 +24,8 @@
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.user :as user-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.idb :as idb]
             [frontend.mobile.util :as mobile-util]
             [frontend.mobile.util :as mobile-util]
             [frontend.modules.instrumentation.core :as instrument]
             [frontend.modules.instrumentation.core :as instrument]
@@ -31,14 +33,12 @@
             [frontend.modules.outliner.file :as file]
             [frontend.modules.outliner.file :as file]
             [frontend.modules.shortcut.core :as shortcut]
             [frontend.modules.shortcut.core :as shortcut]
             [frontend.state :as state]
             [frontend.state :as state]
-            [frontend.storage :as storage]
             [frontend.ui :as ui]
             [frontend.ui :as ui]
             [frontend.util :as util]
             [frontend.util :as util]
             [frontend.util.persist-var :as persist-var]
             [frontend.util.persist-var :as persist-var]
             [goog.object :as gobj]
             [goog.object :as gobj]
             [lambdaisland.glogi :as log]
             [lambdaisland.glogi :as log]
-            [promesa.core :as p]
-            [logseq.db.schema :as db-schema]))
+            [promesa.core :as p]))
 
 
 (defn set-global-error-notification!
 (defn set-global-error-notification!
   []
   []
@@ -76,51 +76,48 @@
     (state/pub-event! [:instrument {:type :blocks/count
     (state/pub-event! [:instrument {:type :blocks/count
                                     :payload {:total (db/blocks-count)}}])))
                                     :payload {:total (db/blocks-count)}}])))
 
 
-(defn store-schema!
-  []
-  (storage/set :db-schema (assoc db-schema/schema
-                                 :db/version db-schema/version)))
-
 (defn restore-and-setup!
 (defn restore-and-setup!
-  [repos old-db-schema]
-  (-> (db/restore!
-       {:repos repos}
-       old-db-schema
-       (fn [repo]
-         (file-handler/restore-config! repo)))
-      (p/then
-       (fn []
-         ;; try to load custom css only for current repo
-         (ui-handler/add-style-if-exists!)
-
-         ;; install after config is restored
-         (shortcut/unlisten-all)
-         (shortcut/refresh!)
-
-         (cond
-           (and (not (seq (db/get-files config/local-repo)))
-                ;; Not native local directory
-                (not (some config/local-db? (map :url repos)))
-                (not (mobile-util/native-platform?)))
-           ;; will execute `(state/set-db-restoring! false)` inside
-           (repo-handler/setup-local-repo-if-not-exists!)
-
-           :else
-           (state/set-db-restoring! false))))
-      (p/then
-       (fn []
-         (js/console.log "db restored, setting up repo hooks")
-         (store-schema!)
-
-         (state/pub-event! [:modal/nfs-ask-permission])
-
-         (page-handler/init-commands!)
-
-         (watch-for-date!)
-         (file-handler/watch-for-current-graph-dir!)
-         (state/pub-event! [:graph/restored (state/get-current-repo)])))
-      (p/catch (fn [error]
-                 (log/error :exception error)))))
+  [repos]
+  (when-let [repo (or (state/get-current-repo) (:url (first repos)))]
+    (-> (db/restore! repo)
+        (p/then
+         (fn []
+           ;; try to load custom css only for current repo
+           (ui-handler/add-style-if-exists!)
+
+           (->
+            (p/do! (repo-config-handler/start {:repo repo})
+                   (when (config/global-config-enabled?)
+                        (global-config-handler/start {:repo repo})))
+            (p/finally
+              (fn []
+                ;; install after config is restored
+                (shortcut/unlisten-all)
+                (shortcut/refresh!)
+
+                (cond
+                  (and (not (seq (db/get-files config/local-repo)))
+                       ;; Not native local directory
+                       (not (some config/local-db? (map :url repos)))
+                       (not (mobile-util/native-platform?)))
+                  ;; will execute `(state/set-db-restoring! false)` inside
+                  (repo-handler/setup-local-repo-if-not-exists!)
+
+                  :else
+                  (state/set-db-restoring! false)))))))
+        (p/then
+         (fn []
+           (js/console.log "db restored, setting up repo hooks")
+
+           (state/pub-event! [:modal/nfs-ask-permission])
+
+           (page-handler/init-commands!)
+
+           (watch-for-date!)
+           (file-handler/watch-for-current-graph-dir!)
+           (state/pub-event! [:graph/restored (state/get-current-repo)])))
+        (p/catch (fn [error]
+                   (log/error :exception error))))))
 
 
 (defn- handle-connection-change
 (defn- handle-connection-change
   [e]
   [e]
@@ -199,44 +196,44 @@
 (defn start!
 (defn start!
   [render]
   [render]
   (set-global-error-notification!)
   (set-global-error-notification!)
-  (let [db-schema (storage/get :db-schema)]
-    (register-components-fns!)
-    (state/set-db-restoring! true)
-    (render)
-    (i18n/start)
-    (instrument/init)
-    (set-network-watcher!)
-
-    (util/indexeddb-check?
-     (fn [_error]
-       (notification/show! "Sorry, it seems that your browser doesn't support IndexedDB, we recommend to use latest Chrome(Chromium) or Firefox(Non-private mode)." :error false)
-       (state/set-indexedb-support! false)))
-
-    (react/run-custom-queries-when-idle!)
-
-    (events/run!)
-
-    (-> (p/let [repos (get-repos)]
-          (state/set-repos! repos)
-          (restore-and-setup! repos db-schema))
-        (p/catch (fn [e]
-                   (js/console.error "Error while restoring repos: " e)))
-        (p/finally (fn []
-                     (state/set-db-restoring! false))))
-    (when (mobile-util/native-platform?)
-      (p/do! (mobile-util/hide-splash)))
-
-    (db/run-batch-txs!)
-    (file/<ratelimit-file-writes!)
-
-    (when config/dev?
-      (enable-datalog-console))
-    (when (util/electron?)
-      (el/listen!))
-    (persist-var/load-vars)
-    (user-handler/restore-tokens-from-localstorage)
-    (user-handler/refresh-tokens-loop)
-    (js/setTimeout instrument! (* 60 1000))))
+  (register-components-fns!)
+  (state/set-db-restoring! true)
+  (render)
+  (i18n/start)
+  (instrument/init)
+  (state/set-online! js/navigator.onLine)
+  (set-network-watcher!)
+
+  (util/indexeddb-check?
+   (fn [_error]
+     (notification/show! "Sorry, it seems that your browser doesn't support IndexedDB, we recommend to use latest Chrome(Chromium) or Firefox(Non-private mode)." :error false)
+     (state/set-indexedb-support! false)))
+
+  (react/run-custom-queries-when-idle!)
+
+  (events/run!)
+
+  (-> (p/let [repos (get-repos)]
+        (state/set-repos! repos)
+        (restore-and-setup! repos))
+      (p/catch (fn [e]
+                 (js/console.error "Error while restoring repos: " e)))
+      (p/finally (fn []
+                   (state/set-db-restoring! false))))
+  (when (mobile-util/native-platform?)
+    (p/do! (mobile-util/hide-splash)))
+
+  (db/run-batch-txs!)
+  (file/<ratelimit-file-writes!)
+
+  (when config/dev?
+    (enable-datalog-console))
+  (when (util/electron?)
+    (el/listen!))
+  (persist-var/load-vars)
+  (user-handler/restore-tokens-from-localstorage)
+  (user-handler/refresh-tokens-loop)
+  (js/setTimeout instrument! (* 60 1000)))
 
 
 (defn stop! []
 (defn stop! []
   (prn "stop!"))
   (prn "stop!"))

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

@@ -2,16 +2,13 @@
   (:require [cljs-bean.core :as bean]
   (:require [cljs-bean.core :as bean]
             [cljs.reader :as reader]
             [cljs.reader :as reader]
             [clojure.string :as string]
             [clojure.string :as string]
-            [frontend.config :as config]
             [frontend.date :as date]
             [frontend.date :as date]
-            [frontend.db :as db]
             [frontend.state :as state]
             [frontend.state :as state]
             [frontend.util :as util]
             [frontend.util :as util]
             [frontend.util.property :as property]
             [frontend.util.property :as property]
             [goog.object :as gobj]
             [goog.object :as gobj]
             ["ignore" :as Ignore]
             ["ignore" :as Ignore]
-            [lambdaisland.glogi :as log]
-            [borkdude.rewrite-edn :as rewrite]))
+            [lambdaisland.glogi :as log]))
 
 
 (defn copy-to-clipboard-without-id-property!
 (defn copy-to-clipboard-without-id-property!
   [format raw-text html]
   [format raw-text html]
@@ -50,10 +47,6 @@
                 (hidden? path patterns))) files)
                 (hidden? path patterns))) files)
     files))
     files))
 
 
-(defn get-config
-  [repo-url]
-  (db/get-file repo-url (config/get-config-path)))
-
 (defn safe-read-string
 (defn safe-read-string
   [content error-message-or-handler]
   [content error-message-or-handler]
   (try
   (try
@@ -65,20 +58,6 @@
         (println error-message-or-handler))
         (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 read-metadata!
 (defn read-metadata!
   [content]
   [content]
   (try
   (try
@@ -118,16 +97,6 @@
   (let [position [(gobj/get e "clientX") (gobj/get e "clientY")]]
   (let [position [(gobj/get e "clientX") (gobj/get e "clientY")]]
     (state/show-custom-context-menu! context-menu-content position)))
     (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!
 (defn listen-to-scroll!
   [element]
   [element]
   (let [*scroll-timer (atom nil)]
   (let [*scroll-timer (atom nil)]

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

@@ -0,0 +1,79 @@
+(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]))
+           options (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}))})]
+       (:tx (graph-parser/parse-file (db/get-db repo-url false) file content options)))
+     (catch :default e
+       (prn "Reset file failed " {:file file})
+       (log/error :exception e)))))

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

@@ -1,12 +1,39 @@
 (ns frontend.handler.config
 (ns frontend.handler.config
+  "Fns for setting repo config"
   (:require [frontend.state :as state]
   (:require [frontend.state :as state]
             [frontend.handler.file :as file-handler]
             [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!
 (defn set-config!
+  "Sets config state for repo-specific config"
   [k v]
   [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! []
 (defn toggle-ui-show-brackets! []
   (let [show-brackets? (state/show-brackets?)]
   (let [show-brackets? (state/show-brackets?)]

+ 26 - 19
src/main/frontend/handler/events.cljs

@@ -32,6 +32,7 @@
             [frontend.handler.page :as page-handler]
             [frontend.handler.page :as page-handler]
             [frontend.handler.plugin :as plugin-handler]
             [frontend.handler.plugin :as plugin-handler]
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.repo :as repo-handler]
+            [frontend.handler.repo-config :as repo-config-handler]
             [frontend.handler.route :as route-handler]
             [frontend.handler.route :as route-handler]
             [frontend.handler.search :as search-handler]
             [frontend.handler.search :as search-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.ui :as ui-handler]
@@ -72,20 +73,23 @@
   (state/set-state! [:ui/loading? :login] false)
   (state/set-state! [:ui/loading? :login] false)
   (async/go
   (async/go
     (let [result (async/<! (sync/<user-info sync/remoteapi))]
     (let [result (async/<! (sync/<user-info sync/remoteapi))]
-      (when (seq result)
-        (state/set-state! :user/info result)
-
-        (let [status (if (user-handler/alpha-user?) :welcome :unavailable)]
-          (when (= status :welcome)
-            (async/<! (file-sync-handler/load-session-graphs))
-            (p/let [repos (repo-handler/refresh-repos!)]
-              (when-let [repo (state/get-current-repo)]
-                (when (some #(and (= (:url %) repo)
-                                 (vector? (:sync-meta %))
-                                 (util/uuid-string? (first (:sync-meta %)))
-                                 (util/uuid-string? (second (:sync-meta %)))) repos)
-                 (file-sync-restart!)))))
-          (file-sync/maybe-onboarding-show status))))))
+      (cond
+        (instance? ExceptionInfo result)
+        nil
+        (map? result)
+        (do
+          (state/set-state! :user/info result)
+          (let [status (if (user-handler/alpha-user?) :welcome :unavailable)]
+            (when (= status :welcome)
+              (async/<! (file-sync-handler/load-session-graphs))
+              (p/let [repos (repo-handler/refresh-repos!)]
+                (when-let [repo (state/get-current-repo)]
+                  (when (some #(and (= (:url %) repo)
+                                    (vector? (:sync-meta %))
+                                    (util/uuid-string? (first (:sync-meta %)))
+                                    (util/uuid-string? (second (:sync-meta %)))) repos)
+                    (file-sync-restart!)))))
+            (file-sync/maybe-onboarding-show status)))))))
 
 
 (defmethod handle :user/logout [[_]]
 (defmethod handle :user/logout [[_]]
   (file-sync-handler/reset-session-graphs)
   (file-sync-handler/reset-session-graphs)
@@ -113,7 +117,7 @@
      (do
      (do
        (state/set-current-repo! graph)
        (state/set-current-repo! graph)
        ;; load config
        ;; load config
-       (common-handler/reset-config! graph nil)
+       (repo-config-handler/restore-repo-config! graph)
        (st/refresh!)
        (st/refresh!)
        (when-not (= :draw (state/get-current-route))
        (when-not (= :draw (state/get-current-route))
          (route-handler/redirect-to-home!))
          (route-handler/redirect-to-home!))
@@ -166,13 +170,13 @@
    (file-sync/pick-page-histories-panel graph-uuid page-name)
    (file-sync/pick-page-histories-panel graph-uuid page-name)
    {:id :page-histories :label "modal-page-histories"}))
    {:id :page-histories :label "modal-page-histories"}))
 
 
-(defmethod handle :graph/open-new-window [[ev repo]]
+(defmethod handle :graph/open-new-window [[_ev repo]]
   (p/let [current-repo (state/get-current-repo)
   (p/let [current-repo (state/get-current-repo)
           target-repo (or repo current-repo)
           target-repo (or repo current-repo)
           _ (repo-handler/persist-db! current-repo persist-db-noti-m) ;; FIXME: redundant when opening non-current-graph window
           _ (repo-handler/persist-db! current-repo persist-db-noti-m) ;; FIXME: redundant when opening non-current-graph window
           _ (when-not (= current-repo target-repo)
           _ (when-not (= current-repo target-repo)
               (repo-handler/broadcast-persist-db! repo))]
               (repo-handler/broadcast-persist-db! repo))]
-    (ui-handler/open-new-window! ev repo)))
+    (ui-handler/open-new-window! repo)))
 
 
 (defmethod handle :graph/migrated [[_ _repo]]
 (defmethod handle :graph/migrated [[_ _repo]]
   (js/alert "Graph migrated."))
   (js/alert "Graph migrated."))
@@ -451,7 +455,7 @@
               (state/set-current-repo! current-repo)
               (state/set-current-repo! current-repo)
               (db/listen-and-persist! current-repo)
               (db/listen-and-persist! current-repo)
               (db/persist-if-idle! 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})
               (.watch mobile-util/fs-watcher #js {:path current-repo-dir})
               (when graph-switch-f (graph-switch-f current-repo true))
               (when graph-switch-f (graph-switch-f current-repo true))
               (file-sync-restart!))))
               (file-sync-restart!))))
@@ -503,7 +507,7 @@
 
 
 (defmethod handle :backup/broken-config [[_ repo content]]
 (defmethod handle :backup/broken-config [[_ repo content]]
   (when (and 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")]
           broken-path (string/replace path "/config.edn" "/broken-config.edn")]
       (p/let [_ (fs/write-file! repo (config/get-repo-dir repo) broken-path content {})
       (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})]
               _ (file-handler/alter-file repo path config/config-default-content {:skip-compare? true})]
@@ -524,6 +528,9 @@
 (defmethod handle :rebuild-slash-commands-list [[_]]
 (defmethod handle :rebuild-slash-commands-list [[_]]
   (page-handler/rebuild-slash-commands-list!))
   (page-handler/rebuild-slash-commands-list!))
 
 
+(defmethod handle :shortcut/refresh [[_]]
+  (st/refresh!))
+
 (defn- refresh-cb []
 (defn- refresh-cb []
   (page-handler/create-today-journal!)
   (page-handler/create-today-journal!)
   (st/refresh!)
   (st/refresh!)

+ 51 - 119
src/main/frontend/handler/file.cljs

@@ -1,13 +1,13 @@
 (ns frontend.handler.file
 (ns frontend.handler.file
   (:refer-clojure :exclude [load-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.db :as db]
             [frontend.fs :as fs]
             [frontend.fs :as fs]
             [frontend.fs.nfs :as nfs]
             [frontend.fs.nfs :as nfs]
             [frontend.fs.capacitor-fs :as capacitor-fs]
             [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.handler.ui :as ui-handler]
             [frontend.state :as state]
             [frontend.state :as state]
             [frontend.util :as util]
             [frontend.util :as util]
@@ -15,9 +15,9 @@
             [electron.ipc :as ipc]
             [electron.ipc :as ipc]
             [lambdaisland.glogi :as log]
             [lambdaisland.glogi :as log]
             [promesa.core :as p]
             [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.config :as gp-config]
-            [logseq.graph-parser :as graph-parser]))
+            ["path" :as path]))
 
 
 ;; TODO: extract all git ops using a channel
 ;; TODO: extract all git ops using a channel
 
 
@@ -52,15 +52,6 @@
   [files]
   [files]
   (keep-formats files (gp-config/img-formats)))
   (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 load-files-contents!
 (defn load-files-contents!
   [repo-url files ok-handler]
   [repo-url files ok-handler]
   (let [images (only-image-formats files)
   (let [images (only-image-formats files)
@@ -87,85 +78,16 @@
     (util/electron?)
     (util/electron?)
     (ipc/ipc "backupDbFile" repo-url path db-content content)
     (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)
     (capacitor-fs/backup-file-handle-changed! repo-url path db-content)
 
 
     :else
     :else
     nil))
     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`
 ;; TODO: Remove this function in favor of `alter-files`
 (defn alter-file
 (defn alter-file
-  [repo path content {:keys [reset? re-render-root? from-disk? skip-compare? new-graph? verbose]
+  [repo path content {:keys [reset? re-render-root? from-disk? skip-compare? new-graph? verbose
+                             skip-db-transact?]
                       :or {reset? true
                       :or {reset? true
                            re-render-root? false
                            re-render-root? false
                            from-disk? false
                            from-disk? false
@@ -173,31 +95,50 @@
   (let [original-content (db/get-file repo path)
   (let [original-content (db/get-file repo path)
         write-file! (if from-disk?
         write-file! (if from-disk?
                       #(p/resolved nil)
                       #(p/resolved nil)
-                      #(fs/write-file! repo (config/get-repo-dir repo) path content
-                                       (assoc (when original-content {:old-content original-content})
-                                              :skip-compare? skip-compare?)))
+                      #(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})
+                                               :skip-compare? skip-compare?))))
         opts {:new-graph? new-graph?
         opts {:new-graph? new-graph?
-              :from-disk? from-disk?}]
-    (if reset?
-      (do
-        (when-let [page-id (db/get-file-page-id path)]
-          (db/transact! repo
-            [[: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}))))
-      (db/set-file-content! repo path content opts))
+              :from-disk? from-disk?
+              :skip-db-transact? skip-db-transact?}
+        result (if reset?
+                 (do
+                   (when-not skip-db-transact?
+                     (when-let [page-id (db/get-file-page-id path)]
+                       (db/transact! repo
+                         [[:db/retract page-id :block/alias]
+                          [:db/retract page-id :block/tags]]
+                         opts)))
+                   (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!)
     (util/p-handle (write-file!)
                    (fn [_]
                    (fn [_]
-                     (when (= path (config/get-config-path repo))
-                       (restore-config! repo))
-                     (when (= path (config/get-custom-css-path repo))
+                     (cond
+                       (= path (config/get-repo-config-path repo))
+                       (p/let [_ (repo-config-handler/restore-repo-config! repo)]
+                         (state/pub-event! [:shortcut/refresh]))
+
+                       (= path (global-config-handler/global-config-path))
+                       (p/let [_ (global-config-handler/restore-global-config!)]
+                         (state/pub-event! [:shortcut/refresh]))
+
+                       (= path (config/get-custom-css-path repo))
                        (ui-handler/add-style-if-exists!))
                        (ui-handler/add-style-if-exists!))
+
                      (when re-render-root? (ui-handler/re-render-root!)))
                      (when re-render-root? (ui-handler/re-render-root!)))
                    (fn [error]
                    (fn [error]
+                     (when (= path (global-config-handler/global-config-path))
+                       (state/pub-event! [:notification/show
+                                         {:content (str "Failed to write to file " path)
+                                          :status :error}]))
+
                      (println "Write file failed, path: " path ", content: " content)
                      (println "Write file failed, path: " path ", content: " content)
-                     (log/error :write/failed error)))))
+                     (log/error :write/failed error)))
+    result))
 
 
 (defn set-file-content!
 (defn set-file-content!
   [repo path new-content]
   [repo path new-content]
@@ -251,7 +192,7 @@
     (when update-db?
     (when update-db?
       (doseq [[path content] files]
       (doseq [[path content] files]
         (if reset?
         (if reset?
-          (reset-file! repo path content)
+          (file-common-handler/reset-file! repo path content)
           (db/set-file-content! repo path content))))
           (db/set-file-content! repo path content))))
     (alter-files-handler! repo files opts file->content)))
     (alter-files-handler! repo files opts file->content)))
 
 
@@ -259,6 +200,8 @@
   []
   []
   (when-let [repo (state/get-current-repo)]
   (when-let [repo (state/get-current-repo)]
     (when-let [dir (config/get-repo-dir repo)]
     (when-let [dir (config/get-repo-dir repo)]
+      ;; An unwatch shouldn't be needed on startup. However not having this
+      ;; after an app refresh can cause stale page data to load
       (fs/unwatch-dir! dir)
       (fs/unwatch-dir! dir)
       (fs/watch-dir! dir))))
       (fs/watch-dir! dir))))
 
 
@@ -271,7 +214,7 @@
     (p/let [_ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir config/app-name))
     (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)]
             file-exists? (fs/create-if-not-exists repo-url repo-dir file-path default-content)]
       (when-not file-exists?
       (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
 (defn create-pages-metadata-file
   [repo-url]
   [repo-url]
@@ -282,15 +225,4 @@
     (p/let [_ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir config/app-name))
     (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)]
             file-exists? (fs/create-if-not-exists repo-url repo-dir file-path default-content)]
       (when-not file-exists?
       (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)))))

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

@@ -0,0 +1,65 @@
+(ns frontend.handler.global-config
+  "This ns is a system component that encapsulates global config functionality.
+  Unlike repo config, this also manages a directory for configuration. This
+  component depends on a repo."
+  (:require [frontend.fs :as fs]
+            [frontend.handler.common.file :as file-common-handler]
+            [frontend.state :as state]
+            [cljs.reader :as reader]
+            [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 restore-global-config!
+  "Sets global config state from config file"
+  []
+  (let [config-dir (global-config-dir)
+        config-path (global-config-path)]
+    (p/let [config-content (fs/read-file config-dir config-path)]
+      (set-global-config-state! config-content))))
+
+(defn start
+  "This component has four 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."
+  [{:keys [repo]}]
+  (p/let [root-dir' (ipc/ipc "getLogseqDotDirRoot")
+          _ (reset! root-dir root-dir')
+          _ (restore-global-config!)
+          _ (create-global-config-file-if-not-exists repo)
+          _ (fs/watch-dir! (global-config-dir) {:global-dir true})]))

+ 3 - 3
src/main/frontend/handler/page.cljs

@@ -249,10 +249,10 @@
         new-tag (if (re-find #"[\s\t]+" new-name)
         new-tag (if (re-find #"[\s\t]+" new-name)
                   (util/format "#[[%s]]" new-name)
                   (util/format "#[[%s]]" new-name)
                   (str "#" new-name))]
                   (str "#" new-name))]
-    ;; hash tag parsing rules https://github.com/logseq/mldoc/blob/701243eaf9b4157348f235670718f6ad19ebe7f8/test/test_markdown.ml#L631 
+    ;; hash tag parsing rules https://github.com/logseq/mldoc/blob/701243eaf9b4157348f235670718f6ad19ebe7f8/test/test_markdown.ml#L631
     ;; Safari doesn't support look behind, don't use
     ;; Safari doesn't support look behind, don't use
     ;; TODO: parse via mldoc
     ;; TODO: parse via mldoc
-    (string/replace content 
+    (string/replace content
                     (re-pattern (str "(?i)(^|\\s)(" (util/escape-regex-chars old-tag) ")(?=[,\\.]*($|\\s))"))
                     (re-pattern (str "(?i)(^|\\s)(" (util/escape-regex-chars old-tag) ")(?=[,\\.]*($|\\s))"))
                     ;;    case_insense^    ^lhs   ^_grp2                       look_ahead^         ^_grp3
                     ;;    case_insense^    ^lhs   ^_grp2                       look_ahead^         ^_grp3
                     (fn [[_match lhs _grp2 _grp3]]
                     (fn [[_match lhs _grp2 _grp3]]
@@ -330,7 +330,7 @@
 (defn toggle-favorite! []
 (defn toggle-favorite! []
   ;; NOTE: in journals or settings, current-page is nil
   ;; NOTE: in journals or settings, current-page is nil
   (when-let [page-name (state/get-current-page)]
   (when-let [page-name (state/get-current-page)]
-   (let [favorites  (:favorites (state/sub-graph-config))
+   (let [favorites  (:favorites (state/sub-config))
          favorited? (contains? (set (map string/lower-case favorites))
          favorited? (contains? (set (map string/lower-case favorites))
                                (string/lower-case page-name))]
                                (string/lower-case page-name))]
     (if favorited?
     (if favorited?

+ 65 - 49
src/main/frontend/handler/repo.cljs

@@ -9,9 +9,12 @@
             [frontend.fs.nfs :as nfs]
             [frontend.fs.nfs :as nfs]
             [frontend.handler.common :as common-handler]
             [frontend.handler.common :as common-handler]
             [frontend.handler.file :as file-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.route :as route-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.metadata :as metadata-handler]
             [frontend.handler.metadata :as metadata-handler]
+            [frontend.handler.global-config :as global-config-handler]
             [frontend.idb :as idb]
             [frontend.idb :as idb]
             [frontend.search :as search]
             [frontend.search :as search]
             [frontend.spec :as spec]
             [frontend.spec :as spec]
@@ -28,26 +31,13 @@
             [cljs-bean.core :as bean]
             [cljs-bean.core :as bean]
             [clojure.core.async :as async]
             [clojure.core.async :as async]
             [frontend.encrypt :as encrypt]
             [frontend.encrypt :as encrypt]
-            [frontend.mobile.util :as mobile-util]))
+            [frontend.mobile.util :as mobile-util]
+            [medley.core :as medley]))
 
 
 ;; Project settings should be checked in two situations:
 ;; Project settings should be checked in two situations:
 ;; 1. User changes the config.edn directly in logseq.com (fn: alter-file)
 ;; 1. User changes the config.edn directly in logseq.com (fn: alter-file)
 ;; 2. Git pulls the new change (fn: load-files)
 ;; 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
 (defn create-contents-file
   [repo-url]
   [repo-url]
   (spec/validate :repos/url repo-url)
   (spec/validate :repos/url repo-url)
@@ -67,7 +57,7 @@
         (p/let [_ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir pages-dir))
         (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)]
                 file-exists? (fs/create-if-not-exists repo-url repo-dir file-path default-content)]
           (when-not file-exists?
           (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
 (defn create-custom-theme
   [repo-url]
   [repo-url]
@@ -79,7 +69,7 @@
     (p/let [_ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir config/app-name))
     (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)]
             file-exists? (fs/create-if-not-exists repo-url repo-dir file-path default-content)]
       (when-not file-exists?
       (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
 (defn create-dummy-notes-page
   [repo-url content]
   [repo-url content]
@@ -89,7 +79,7 @@
         file-path (str "/" path)]
         file-path (str "/" path)]
     (p/let [_ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir (config/get-pages-directory)))
     (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-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
 (defn- create-today-journal-if-not-exists
   [repo-url {:keys [content]}]
   [repo-url {:keys [content]}]
@@ -123,7 +113,7 @@
                 _ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir (config/get-journals-directory)))
                 _ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir (config/get-journals-directory)))
                 file-exists? (fs/file-exists? repo-dir file-path)]
                 file-exists? (fs/file-exists? repo-dir file-path)]
           (when-not file-exists?
           (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)]
               (p/let [_ (fs/create-if-not-exists repo-url repo-dir file-path content)]
                 (when-not (state/editing?)
                 (when-not (state/editing?)
                   (ui-handler/re-render-root!)))))
                   (ui-handler/re-render-root!)))))
@@ -140,7 +130,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 (str config/app-name "/" config/recycle-dir)))
              _ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir (config/get-journals-directory)))
              _ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir (config/get-journals-directory)))
              _ (file-handler/create-metadata-file repo-url encrypted?)
              _ (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-contents-file repo-url)
              _ (create-custom-theme repo-url)]
              _ (create-custom-theme repo-url)]
        (state/pub-event! [:page/create-today-journal repo-url])))))
        (state/pub-event! [:page/create-today-journal repo-url])))))
@@ -185,21 +175,27 @@
         file-paths [path]]
         file-paths [path]]
     (load-pages-metadata! repo file-paths files force?)))
     (load-pages-metadata! repo file-paths files force?)))
 
 
+(defonce *file-tx (atom nil))
+
 (defn- parse-and-load-file!
 (defn- parse-and-load-file!
-  [repo-url file {:keys [new-graph? verbose]}]
+  [repo-url file {:keys [new-graph? verbose skip-db-transact?]
+                  :or {skip-db-transact? true}}]
   (try
   (try
-    (file-handler/alter-file repo-url
-                             (:file/path file)
-                             (:file/content file)
-                             (merge {:new-graph? new-graph?
-                                     :re-render-root? false
-                                     :from-disk? true}
-                                    (when (some? verbose) {:verbose verbose})))
+    (reset! *file-tx
+            (file-handler/alter-file repo-url
+                                     (:file/path file)
+                                     (:file/content file)
+                                     (merge {:new-graph? new-graph?
+                                             :re-render-root? false
+                                             :from-disk? true
+                                             :skip-db-transact? skip-db-transact?}
+                                            (when (some? verbose) {:verbose verbose}))))
     (catch :default e
     (catch :default e
       (state/set-parsing-state! (fn [m]
       (state/set-parsing-state! (fn [m]
                                   (update m :failed-parsing-files conj [(:file/path file) e])))))
                                   (update m :failed-parsing-files conj [(:file/path file) e])))))
   (state/set-parsing-state! (fn [m]
   (state/set-parsing-state! (fn [m]
-                              (update m :finished inc))))
+                              (update m :finished inc)))
+  @*file-tx)
 
 
 (defn- after-parse
 (defn- after-parse
   [repo-url files file-paths db-encrypted? re-render? re-render-opts opts graph-added-chan]
   [repo-url files file-paths db-encrypted? re-render? re-render-opts opts graph-added-chan]
@@ -221,8 +217,11 @@
   (let [supported-files (graph-parser/filter-files files)
   (let [supported-files (graph-parser/filter-files files)
         delete-data (->> (concat delete-files delete-blocks)
         delete-data (->> (concat delete-files delete-blocks)
                          (remove nil?))
                          (remove nil?))
-        chan (async/to-chan! supported-files)
-        graph-added-chan (async/promise-chan)]
+        indexed-files (medley/indexed supported-files)
+        chan (async/to-chan! indexed-files)
+        graph-added-chan (async/promise-chan)
+        total (count supported-files)
+        large-graph? (> total 1000)]
     (when (seq delete-data) (db/transact! repo-url delete-data))
     (when (seq delete-data) (db/transact! repo-url delete-data))
     (state/set-current-repo! repo-url)
     (state/set-current-repo! repo-url)
     (state/set-parsing-state! {:total (count supported-files)})
     (state/set-parsing-state! {:total (count supported-files)})
@@ -231,18 +230,33 @@
       (do
       (do
         (doseq [file supported-files]
         (doseq [file supported-files]
           (state/set-parsing-state! (fn [m]
           (state/set-parsing-state! (fn [m]
-                                      (assoc m :current-parsing-file (:file/path file))))
-          (parse-and-load-file! repo-url file (select-keys opts [:new-graph? :verbose])))
+                                      (assoc m
+                                             :current-parsing-file (:file/path file))))
+          (parse-and-load-file! repo-url file (assoc
+                                               (select-keys opts [:new-graph? :verbose])
+                                               :skip-db-transact? false)))
         (after-parse repo-url files file-paths db-encrypted? re-render? re-render-opts opts graph-added-chan))
         (after-parse repo-url files file-paths db-encrypted? re-render? re-render-opts opts graph-added-chan))
-      (async/go-loop []
-        (if-let [file (async/<! chan)]
-          (do
+      (async/go-loop [tx []]
+        (if-let [item (async/<! chan)]
+          (let [[idx file] item
+                yield-for-ui? (or (not large-graph?)
+                                  (zero? (rem idx 10))
+                                  (<= (- total idx) 10))]
             (state/set-parsing-state! (fn [m]
             (state/set-parsing-state! (fn [m]
                                         (assoc m :current-parsing-file (:file/path file))))
                                         (assoc m :current-parsing-file (:file/path file))))
-            (async/<! (async/timeout 10))
-            (parse-and-load-file! repo-url file (select-keys opts [:new-graph? :verbose]))
-            (recur))
-          (after-parse repo-url files file-paths db-encrypted? re-render? re-render-opts opts graph-added-chan))))
+
+            (when yield-for-ui? (async/<! (async/timeout 1)))
+
+            (let [result (parse-and-load-file! repo-url file (select-keys opts [:new-graph? :verbose]))
+                  tx' (concat tx result)
+                  tx' (if (zero? (rem (inc idx) 100))
+                        (do (db/transact! repo-url tx' {:from-disk? true})
+                            [])
+                        tx')]
+              (recur tx')))
+          (do
+            (when (seq tx) (db/transact! repo-url tx {:from-disk? true}))
+            (after-parse repo-url files file-paths db-encrypted? re-render? re-render-opts opts graph-added-chan)))))
     graph-added-chan))
     graph-added-chan))
 
 
 (defn- parse-files-and-create-default-files!
 (defn- parse-files-and-create-default-files!
@@ -280,9 +294,9 @@
   (spec/validate :repos/url repo-url)
   (spec/validate :repos/url repo-url)
   (route-handler/redirect-to-home!)
   (route-handler/redirect-to-home!)
   (state/set-parsing-state! {:graph-loading? true})
   (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)]
                                               :file/content)]
-                     (common-handler/read-config content))
+                     (repo-config-handler/read-repo-config repo-url content))
                    (state/get-config repo-url))
                    (state/get-config repo-url))
         ;; NOTE: Use config while parsing. Make sure it's the corrent journal title format
         ;; NOTE: Use config while parsing. Make sure it's the corrent journal title format
         _ (state/set-config! repo-url config)
         _ (state/set-config! repo-url config)
@@ -368,7 +382,7 @@
                (let [tutorial (t :tutorial/text)
                (let [tutorial (t :tutorial/text)
                      tutorial (string/replace-first tutorial "$today" (date/today))]
                      tutorial (string/replace-first tutorial "$today" (date/today))]
                  (create-today-journal-if-not-exists repo {:content tutorial})))
                  (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-contents-file repo)
              (create-custom-theme repo)
              (create-custom-theme repo)
              (state/set-db-restoring! false)
              (state/set-db-restoring! false)
@@ -390,12 +404,14 @@
   conn, or replace the conn in state with a new one."
   conn, or replace the conn in state with a new one."
   [repo]
   [repo]
   (p/let [_ (state/set-db-restoring! true)
   (p/let [_ (state/set-db-restoring! true)
-          _ (db/restore-graph! repo)]
-         (file-handler/restore-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!)
-         (state/set-db-restoring! false)))
+          _ (db/restore-graph! repo)
+          _ (repo-config-handler/restore-repo-config! repo)
+          _ (global-config-handler/restore-global-config!)]
+    ;; Don't have to unlisten the old listener, as it will be destroyed with the conn
+    (db/listen-and-persist! repo)
+    (state/pub-event! [:shortcut/refresh])
+    (ui-handler/add-style-if-exists!)
+    (state/set-db-restoring! false)))
 
 
 (defn rebuild-index!
 (defn rebuild-index!
   [url]
   [url]

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

@@ -0,0 +1,63 @@
+(ns frontend.handler.repo-config
+  "This ns is a system component that encapsulates repo config functionality.
+  This component only concerns itself with one user-facing repo config file,
+  logseq/config.edn. In the future it may manage more files. This 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
+  "Converts file content to edn and handles read failure by backing up file and
+  reverting to a default file"
+  [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!
+  "Sets repo config state using given file content"
+  [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
+  "Creates a default logseq/config.edn if it doesn't exist"
+  [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))

+ 3 - 3
src/main/frontend/handler/ui.cljs

@@ -298,9 +298,9 @@
 (defn open-new-window!
 (defn open-new-window!
   "Open a new Electron window.
   "Open a new Electron window.
    No db cache persisting ensured. Should be handled by the caller."
    No db cache persisting ensured. Should be handled by the caller."
-  ([_e]
-   (open-new-window! _e nil))
-  ([_e repo]
+  ([]
+   (open-new-window! nil))
+  ([repo]
    ;; TODO: find out a better way to open a new window with a different repo path. Using local storage for now
    ;; TODO: find out a better way to open a new window with a different repo path. Using local storage for now
    ;; TODO: also write local storage with the current repo state, to make behavior consistent
    ;; TODO: also write local storage with the current repo state, to make behavior consistent
    ;; then we can remove the `openNewWindowOfGraph` ipcMain call
    ;; then we can remove the `openNewWindowOfGraph` ipcMain call

+ 14 - 12
src/main/frontend/handler/user.cljs

@@ -105,18 +105,20 @@
     (when-let [refresh-token (state/get-auth-refresh-token)]
     (when-let [refresh-token (state/get-auth-refresh-token)]
       (let [resp (<! (http/get (str "https://" config/API-DOMAIN "/auth_refresh_token?refresh_token=" refresh-token)
       (let [resp (<! (http/get (str "https://" config/API-DOMAIN "/auth_refresh_token?refresh_token=" refresh-token)
                                {:with-credentials? false}))]
                                {:with-credentials? false}))]
-        (if (= 400 (:status resp))
-          ;; invalid refresh_token
-          (do
-            (clear-tokens)
-            false)
-          (do
-            (->
-             resp
-             (as-> $ (and (http/unexceptional-status? (:status $)) $))
-             :body
-             (as-> $ (set-tokens! (:id_token $) (:access_token $))))
-            true))))))
+
+        (cond
+          ;; e.g. api return 500, server internal error
+          ;; we shouldn't clear tokens if they aren't expired yet
+          ;; the `refresh-tokens-loop` will retry soon
+          (and (not (http/unexceptional-status? (:status resp)))
+               (not (-> (state/get-auth-id-token) parse-jwt expired?)))
+          nil                           ; do nothing
+
+          (not (http/unexceptional-status? (:status resp)))
+          (clear-tokens)
+
+          :else                         ; ok
+          (set-tokens! (:id_token (:body resp)) (:access_token (:body resp))))))))
 
 
 (defn restore-tokens-from-localstorage
 (defn restore-tokens-from-localstorage
   "restore id-token, access-token, refresh-token from localstorage,
   "restore id-token, access-token, refresh-token from localstorage,

+ 35 - 25
src/main/frontend/handler/web/nfs.cljs

@@ -11,6 +11,7 @@
             [frontend.fs :as fs]
             [frontend.fs :as fs]
             [frontend.fs.nfs :as nfs]
             [frontend.fs.nfs :as nfs]
             [frontend.handler.common :as common-handler]
             [frontend.handler.common :as common-handler]
+            [frontend.handler.global-config :as global-config-handler]
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.route :as route-handler]
             [frontend.handler.route :as route-handler]
             [frontend.idb :as idb]
             [frontend.idb :as idb]
@@ -38,10 +39,10 @@
                                   %) files)]
                                   %) files)]
       (if-let [file (:file/file ignore-file)]
       (if-let [file (:file/file ignore-file)]
         (p/let [content (.text file)]
         (p/let [content (.text file)]
-          (when content
-            (let [paths (set (common-handler/ignore-files content (map :file/path files)))]
-              (when (seq paths)
-                (filter (fn [f] (contains? paths (:file/path f))) files)))))
+               (when content
+                 (let [paths (set (common-handler/ignore-files content (map :file/path files)))]
+                   (when (seq paths)
+                     (filter (fn [f] (contains? paths (:file/path f))) files)))))
         (p/resolved files))
         (p/resolved files))
       (p/resolved files))))
       (p/resolved files))))
 
 
@@ -186,8 +187,8 @@
                                 (assoc file :file/content content))) markup-files))
                                 (assoc file :file/content content))) markup-files))
                 (p/then (fn [result]
                 (p/then (fn [result]
                           (p/let [files (map #(dissoc % :file/file) result)
                           (p/let [files (map #(dissoc % :file/file) result)
-                                  graph-txid-meta (util-fs/read-graph-txid-info dir-name)
-                                  graph-uuid (and (vector? graph-txid-meta) (second graph-txid-meta))]
+                                  graphs-txid-meta (util-fs/read-graphs-txid-info dir-name)
+                                  graph-uuid (and (vector? graphs-txid-meta) (second graphs-txid-meta))]
                             (if-let [exists-graph (state/get-sync-graph-by-uuid graph-uuid)]
                             (if-let [exists-graph (state/get-sync-graph-by-uuid graph-uuid)]
                               (state/pub-event!
                               (state/pub-event!
                                [:notification/show
                                [:notification/show
@@ -330,25 +331,34 @@
          (state/set-graph-syncing? true))
          (state/set-graph-syncing? true))
        (->
        (->
         (p/let [handle (when-not electron? (idb/get-item handle-path))]
         (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))
-                    files-result (fs/get-files (if nfs? handle
-                                                   (config/get-local-dir repo))
-                                               (fn [path handle]
-                                                 (when nfs?
-                                                   (swap! path-handles assoc path handle))))
-                    new-files (-> (->db-files mobile-native? electron? dir-name files-result)
-                                  (remove-ignore-files dir-name nfs?))
-                    _ (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 (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?))))
         (p/catch (fn [error]
         (p/catch (fn [error]
                    (log/error :nfs/load-files-error repo)
                    (log/error :nfs/load-files-error repo)
                    (log/error :exception error)))
                    (log/error :exception error)))

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

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

+ 1 - 1
src/main/frontend/modules/shortcut/config.cljs

@@ -252,7 +252,7 @@
    :go/electron-find-in-page       {:binding "mod+f"
    :go/electron-find-in-page       {:binding "mod+f"
                                     :inactive (not (util/electron?))
                                     :inactive (not (util/electron?))
                                     :fn      #(search-handler/open-find-in-page!)}
                                     :fn      #(search-handler/open-find-in-page!)}
-   
+
    :go/electron-jump-to-the-next {:binding ["enter" "mod+g"]
    :go/electron-jump-to-the-next {:binding ["enter" "mod+g"]
                                   :inactive (not (util/electron?))
                                   :inactive (not (util/electron?))
                                   :fn      #(search-handler/loop-find-in-page! false)}
                                   :fn      #(search-handler/loop-find-in-page! false)}

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

@@ -1,6 +1,6 @@
 (ns frontend.modules.shortcut.core
 (ns frontend.modules.shortcut.core
   (:require [clojure.string :as str]
   (:require [clojure.string :as str]
-            [frontend.handler.config :as config]
+            [frontend.handler.config :as config-handler]
             [frontend.handler.notification :as notification]
             [frontend.handler.notification :as notification]
             [frontend.modules.shortcut.data-helper :as dh]
             [frontend.modules.shortcut.data-helper :as dh]
             [frontend.modules.shortcut.config :as shortcut-config]
             [frontend.modules.shortcut.config :as shortcut-config]
@@ -224,9 +224,9 @@
      (let [k (first args)
      (let [k (first args)
            keystroke (str/trim @local)]
            keystroke (str/trim @local)]
        (when-not (empty? keystroke)
        (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)]
      (when-let [^js handler (::key-record-handler state)]
        (.dispose handler))
        (.dispose handler))

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

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

+ 32 - 20
src/main/frontend/modules/shortcut/dicts.cljc

@@ -219,10 +219,11 @@
              :command.pdf/close                       "关闭当前PDF文档"
              :command.pdf/close                       "关闭当前PDF文档"
              :command.ui/toggle-help                  "显示/关闭帮助"
              :command.ui/toggle-help                  "显示/关闭帮助"
              :command.git/commit                      "提交消息"
              :command.git/commit                      "提交消息"
-             :command.go/search                       "全文搜索"
+             :command.go/search                       "搜索页面和块"
              :command.go/backward                     "回退"
              :command.go/backward                     "回退"
              :command.go/forward                      "前进"
              :command.go/forward                      "前进"
-             :command.go/search-in-page               "在当前页面搜索"
+             :command.go/search-in-page               "在当前页面搜索块"
+             :command.go/electron-find-in-page        "在当前页面查找文本"
              :command.ui/toggle-document-mode         "切换文档模式"
              :command.ui/toggle-document-mode         "切换文档模式"
              :command.ui/toggle-contents              "打开/关闭目录"
              :command.ui/toggle-contents              "打开/关闭目录"
              :command.ui/toggle-theme                 "在暗色/亮色主题之间切换"
              :command.ui/toggle-theme                 "在暗色/亮色主题之间切换"
@@ -870,15 +871,15 @@
              :command.go/backward                     "Voltar"
              :command.go/backward                     "Voltar"
              :command.go/flashcards                   "Trocar flashcards"
              :command.go/flashcards                   "Trocar flashcards"
              :command.go/forward                      "Avançar"
              :command.go/forward                      "Avançar"
-             :command.go/graph-view                   "Ir para o gráfico"
+             :command.go/graph-view                   "Ir para o grafo"
              :command.go/home                         "Volar para o inicio"
              :command.go/home                         "Volar para o inicio"
              :command.go/keyboard-shortcuts           "Ir para os atalhos do teclado"
              :command.go/keyboard-shortcuts           "Ir para os atalhos do teclado"
              :command.go/next-journal                 "Ir ao proximo jornal"
              :command.go/next-journal                 "Ir ao proximo jornal"
              :command.go/prev-journal                 "Ir ao jornal anterior"
              :command.go/prev-journal                 "Ir ao jornal anterior"
              :command.go/tomorrow                     "Ir para amanhã"
              :command.go/tomorrow                     "Ir para amanhã"
-             :command.graph/add                       "Adicionar um gráfico"
-             :command.graph/open                      "Selecionar gráfico para abrir"
-             :command.graph/remove                    "Remover um gráfico"
+             :command.graph/add                       "Adicionar um grafo"
+             :command.graph/open                      "Selecionar grafo para abrir"
+             :command.graph/remove                    "Remover um grafo"
              :command.pdf/next-page                   "Próxima página do atual pdf doc"
              :command.pdf/next-page                   "Próxima página do atual pdf doc"
              :command.pdf/previous-page               "Página anterior do atual pdf doc"
              :command.pdf/previous-page               "Página anterior do atual pdf doc"
              :command.sidebar/clear                   "Limpar tudo da barra lateral direita"
              :command.sidebar/clear                   "Limpar tudo da barra lateral direita"
@@ -886,9 +887,14 @@
              :command.ui/select-theme-color           "Selecionar as cores do tema disponível"
              :command.ui/select-theme-color           "Selecionar as cores do tema disponível"
              :command.ui/toggle-cards                 "Trocar cartões"
              :command.ui/toggle-cards                 "Trocar cartões"
              :command.ui/toggle-left-sidebar          "Trocar barra lateral esquerda"
              :command.ui/toggle-left-sidebar          "Trocar barra lateral esquerda"
-             :command.graph/save                      "Salvar gráfico atual no computador"
+             :command.graph/save                      "Salvar grafo atual no computador"
              :command.misc/copy                       "Copiar (copiar seleção ou referência do bloco)"
              :command.misc/copy                       "Copiar (copiar seleção ou referência do bloco)"
-             :command.ui/goto-plugins                 "Ir para o painel de plugins"}
+             :command.ui/goto-plugins                 "Ir para o painel de plugins"
+             :command.go/all-graphs                   "Ir à todos os grafos"
+             :command.go/electron-find-in-page        "Procurar texto na página"
+             :command.go/electron-jump-to-the-next    "Ir para a próxima correspondência da sua pesquisa"
+             :command.go/electron-jump-to-the-previous "Voltar para a correspondência anterior da sua pesquisa"
+             :command.graph/re-index                  "Reindexar o grafo atual"}
 
 
    :pt-BR   {:shortcut.category/formatting            "Formatação"
    :pt-BR   {:shortcut.category/formatting            "Formatação"
              :shortcut.category/basics                "Básico"
              :shortcut.category/basics                "Básico"
@@ -978,15 +984,15 @@
              :command.go/backward                     "Voltar"
              :command.go/backward                     "Voltar"
              :command.go/flashcards                   "Trocar flashcards"
              :command.go/flashcards                   "Trocar flashcards"
              :command.go/forward                      "Avançar"
              :command.go/forward                      "Avançar"
-             :command.go/graph-view                   "Ir para o gráfico"
+             :command.go/graph-view                   "Ir para o grafo"
              :command.go/home                         "Volar para o inicio"
              :command.go/home                         "Volar para o inicio"
              :command.go/keyboard-shortcuts           "Ir para os atalhos do teclado"
              :command.go/keyboard-shortcuts           "Ir para os atalhos do teclado"
              :command.go/next-journal                 "Ir ao proximo jornal"
              :command.go/next-journal                 "Ir ao proximo jornal"
              :command.go/prev-journal                 "Ir ao jornal anterior"
              :command.go/prev-journal                 "Ir ao jornal anterior"
              :command.go/tomorrow                     "Ir para amanhã"
              :command.go/tomorrow                     "Ir para amanhã"
-             :command.graph/add                       "Adicionar um gráfico"
-             :command.graph/open                      "Selecionar gráfico para abrir"
-             :command.graph/remove                    "Remover um gráfico"
+             :command.graph/add                       "Adicionar um grafo"
+             :command.graph/open                      "Selecionar grafo para abrir"
+             :command.graph/remove                    "Remover um grafo"
              :command.pdf/next-page                   "Próxima página do atual pdf doc"
              :command.pdf/next-page                   "Próxima página do atual pdf doc"
              :command.pdf/previous-page               "Página anterior do atual pdf doc"
              :command.pdf/previous-page               "Página anterior do atual pdf doc"
              :command.sidebar/clear                   "Limpar tudo da barra lateral direita"
              :command.sidebar/clear                   "Limpar tudo da barra lateral direita"
@@ -999,7 +1005,7 @@
              :command.editor/copy-current-file        "Copiar o arquivo atual"
              :command.editor/copy-current-file        "Copiar o arquivo atual"
              :command.editor/open-file-in-default-app "Abra o arquivo no aplicativo padrão"
              :command.editor/open-file-in-default-app "Abra o arquivo no aplicativo padrão"
              :command.editor/open-file-in-directory   "Abra o arquivo na pasta"
              :command.editor/open-file-in-directory   "Abra o arquivo na pasta"
-             :command.graph/save                      "Salvar gráfico atual no computador"
+             :command.graph/save                      "Salvar grafo atual no computador"
              :command.misc/copy                       "Copiar (copiar seleção ou referência do bloco)"
              :command.misc/copy                       "Copiar (copiar seleção ou referência do bloco)"
              :command.ui/goto-plugins                 "Ir para o painel de plugins"
              :command.ui/goto-plugins                 "Ir para o painel de plugins"
              ;;  :command.ui/open-new-window              "Abra uma nova janela"
              ;;  :command.ui/open-new-window              "Abra uma nova janela"
@@ -1007,7 +1013,12 @@
              :command.editor/select-up                "Selecione o conteúdo acima"
              :command.editor/select-up                "Selecione o conteúdo acima"
              :command.editor/copy-embed               "Copiar uma incorporação do bloco, apontando para o bloco atual"
              :command.editor/copy-embed               "Copiar uma incorporação do bloco, apontando para o bloco atual"
              :command.editor/copy-text                "Copiar seleção como texto"
              :command.editor/copy-text                "Copiar seleção como texto"
-             :command.pdf/close                       "Fechar visualização do PDF"}
+             :command.pdf/close                       "Fechar visualização do PDF"
+             :command.go/all-graphs                   "Ir à todos os grafos"
+             :command.go/electron-find-in-page        "Localizar texto na página"
+             :command.go/electron-jump-to-the-next    "Ir para a próxima correspondência da sua pesquisa"
+             :command.go/electron-jump-to-the-previous "Voltar para a correspondência anterior da sua pesquisa"
+             :command.graph/re-index                  "Reindexar o grafo atual"}
 
 
    :ja      {:shortcut.category/formatting            "フォーマット"
    :ja      {:shortcut.category/formatting            "フォーマット"
              :shortcut.category/basics                "基本操作"
              :shortcut.category/basics                "基本操作"
@@ -1334,15 +1345,16 @@
              :command.sidebar/clear                  "Sağ kenar çubuğundaki herşeyi temizle"
              :command.sidebar/clear                  "Sağ kenar çubuğundaki herşeyi temizle"
              :command.misc/copy                      "mod+c"
              :command.misc/copy                      "mod+c"
              :command.command-palette/toggle         "Komut paletini aç"
              :command.command-palette/toggle         "Komut paletini aç"
-             :command.graph/open                     "Açılacak grafiği seçin"
-             :command.graph/remove                   "Bir grafiği kaldır"
-             :command.graph/add                      "Grafik ekle"
-             :command.graph/save                     "Mevcut grafiği diske kaydet"
-             :command.graph/re-index                 "Mevcut grafiği yeniden oluştur"
+             :command.graph/open                     "Açılacak çizelgeyi seçin"
+             :command.graph/remove                   "Bir çizelgeyi kaldır"
+             :command.graph/add                      "Çizelge ekle"
+             :command.graph/save                     "Mevcut çizelgeyi diske kaydet"
+             :command.graph/re-index                 "Mevcut çizelgeyi yeniden oluştur"
              :command.command/run                    "Git komutunu çalıştır"
              :command.command/run                    "Git komutunu çalıştır"
              :command.go/home                        "Ana sayfaya git"
              :command.go/home                        "Ana sayfaya git"
+             :command.go/all-graphs                  "Bütün çizelgelere git"
              :command.go/all-pages                   "Bütün sayfalara git"
              :command.go/all-pages                   "Bütün sayfalara git"
-             :command.go/graph-view                  "Grafik görünümüne git"
+             :command.go/graph-view                  "Çizelge görünümüne git"
              :command.go/keyboard-shortcuts          "Klavye kısayollarına git"
              :command.go/keyboard-shortcuts          "Klavye kısayollarına git"
              :command.go/tomorrow                    "Yarının günlüğüne git"
              :command.go/tomorrow                    "Yarının günlüğüne git"
              :command.go/next-journal                "Sonraki günlüğe git"
              :command.go/next-journal                "Sonraki günlüğe git"

+ 3 - 2
src/main/frontend/search.cljs

@@ -96,7 +96,7 @@
       (when-not (string/blank? q)
       (when-not (string/blank? q)
         (protocol/query engine q option)))))
         (protocol/query engine q option)))))
 
 
-(defn transact-blocks!
+(defn- transact-blocks!
   [repo data]
   [repo data]
   (when-let [engine (get-engine repo)]
   (when-let [engine (get-engine repo)]
     (protocol/transact-blocks! engine data)))
     (protocol/transact-blocks! engine data)))
@@ -237,7 +237,8 @@
                 blocks-to-add (->> (filter (fn [block]
                 blocks-to-add (->> (filter (fn [block]
                                              (contains? blocks-to-add-set (:db/id block)))
                                              (contains? blocks-to-add-set (:db/id block)))
                                            blocks-result)
                                            blocks-result)
-                                   (map search-db/block->index))
+                                   (map search-db/block->index)
+                                   (remove nil?))
                 blocks-to-remove-set (->> (remove :added blocks)
                 blocks-to-remove-set (->> (remove :added blocks)
                                           (map :e)
                                           (map :e)
                                           (set))]
                                           (set))]

+ 1 - 3
src/main/frontend/spec/storage.cljc

@@ -3,7 +3,6 @@
   #?(:cljs (:require [cljs.spec.alpha :as s])
   #?(:cljs (:require [cljs.spec.alpha :as s])
      :default (:require [clojure.spec.alpha :as s])))
      :default (:require [clojure.spec.alpha :as s])))
 
 
-(s/def ::db-schema map?)
 (s/def ::ls-right-sidebar-state map?)
 (s/def ::ls-right-sidebar-state map?)
 (s/def ::ls-right-sidebar-width string?)
 (s/def ::ls-right-sidebar-width string?)
 (s/def ::ls-left-sidebar-open? boolean?)
 (s/def ::ls-left-sidebar-open? boolean?)
@@ -36,8 +35,7 @@
 (s/def ::local-storage
 (s/def ::local-storage
   ;; All these keys are optional since we usually only validate one key at a time
   ;; All these keys are optional since we usually only validate one key at a time
   (s/keys
   (s/keys
-   :opt-un [::db-schema
-            ::ls-right-sidebar-state
+   :opt-un [::ls-right-sidebar-state
             ::ls-right-sidebar-width
             ::ls-right-sidebar-width
             ::ls-left-sidebar-open?
             ::ls-left-sidebar-open?
             :ui/theme
             :ui/theme

+ 284 - 261
src/main/frontend/state.cljs

@@ -16,6 +16,7 @@
             [logseq.graph-parser.config :as gp-config]
             [logseq.graph-parser.config :as gp-config]
             [frontend.mobile.util :as mobile-util]))
             [frontend.mobile.util :as mobile-util]))
 
 
+;; Stores main application state
 (defonce ^:large-vars/data-var state
 (defonce ^:large-vars/data-var state
   (let [document-mode? (or (storage/get :document/mode?) false)
   (let [document-mode? (or (storage/get :document/mode?) false)
         current-graph  (let [graph (storage/get :git/current-repo)]
         current-graph  (let [graph (storage/get :git/current-repo)]
@@ -251,6 +252,9 @@
      :graph/importing-state                 {}
      :graph/importing-state                 {}
      })))
      })))
 
 
+;; Block ast state
+;; ===============
+
 ;; block uuid -> {content(String) -> ast}
 ;; block uuid -> {content(String) -> ast}
 (def blocks-ast-cache (atom {}))
 (def blocks-ast-cache (atom {}))
 (defn add-block-ast-cache!
 (defn add-block-ast-cache!
@@ -267,68 +271,53 @@
   (when (and block-uuid content)
   (when (and block-uuid content)
     (get-in @blocks-ast-cache [block-uuid content])))
     (get-in @blocks-ast-cache [block-uuid content])))
 
 
-(defn sub
-  [ks]
-  (if (coll? ks)
-    (util/react (rum/cursor-in state ks))
-    (util/react (rum/cursor state ks))))
-
-(defn get-route-match
-  []
-  (:route-match @state))
-
-(defn get-current-route
-  []
-  (get-in (get-route-match) [:data :name]))
-
-(defn home?
-  []
-  (= :home (get-current-route)))
-
-(defn setups-picker?
-  []
-  (= :repo-add (get-current-route)))
-
-(defn get-current-page
-  []
-  (when (= :page (get-current-route))
-    (get-in (get-route-match)
-            [:path-params :name])))
-
-(defn route-has-p?
-  []
-  (get-in (get-route-match) [:query-params :p]))
-
-(defn set-state!
-  [path value]
-  (if (vector? path)
-    (swap! state assoc-in path value)
-    (swap! state assoc path value)))
-
-(defn update-state!
-  [path f]
-  (if (vector? path)
-    (swap! state update-in path f)
-    (swap! state update path f)))
-
-(defn get-current-repo
-  []
-  (or (:git/current-repo @state)
-      (when-not (mobile-util/native-platform?)
-        "local")))
+;; User configuration getters under :config (and sometimes :me)
+;; ========================================
+;; TODO: Refactor default config values to be data driven. Currently they are all
+;;  buried in getters
+;; TODO: Refactor our access to be more data driven. Currently each getter
+;;  (re-)fetches get-current-repo needlessly
+;; TODO: Add consistent validation. Only a few config options validate at get time
 
 
 (def default-config
 (def default-config
   "Default config for a repo-specific, user config"
   "Default config for a repo-specific, user config"
   {:feature/enable-search-remove-accents? true
   {:feature/enable-search-remove-accents? true
    :default-arweave-gateway "https://arweave.net"})
    :default-arweave-gateway "https://arweave.net"})
 
 
+;; 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
 (defn get-config
-  "User config for the given repo or current repo if none given"
+  "User config for the given repo or current repo if none given. All config fetching
+should be done through this fn in order to get global config and config defaults"
   ([]
   ([]
    (get-config (get-current-repo)))
    (get-config (get-current-repo)))
   ([repo-url]
   ([repo-url]
-   (merge default-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))
+
+(defn publishing-enable-editing?
+  []
+  (and @publishing? (:publishing/enable-editing? (get-config))))
+
+(defn enable-editing?
+  []
+  (or (not @publishing?) (:publishing/enable-editing? (get-config))))
 
 
 (defn get-arweave-gateway
 (defn get-arweave-gateway
   []
   []
@@ -343,10 +332,6 @@
     built-in-macros
     built-in-macros
     (:macros (get-config))))
     (:macros (get-config))))
 
 
-(defn sub-config
-  []
-  (sub :config))
-
 (defn get-custom-css-link
 (defn get-custom-css-link
   []
   []
   (:custom-css-url (get-config)))
   (:custom-css-url (get-config)))
@@ -367,93 +352,10 @@
         value (if (some? value) value (:all-pages-public? (get-config)))]
         value (if (some? value) value (:all-pages-public? (get-config)))]
     (true? value)))
     (true? value)))
 
 
-(defn enable-grammarly?
-  []
-  (true? (:feature/enable-grammarly?
-           (get (sub-config) (get-current-repo)))))
-
-;; (defn store-block-id-in-file?
-;;   []
-;;   (true? (:block/store-id-in-file? (get-config))))
-
-(defn scheduled-deadlines-disabled?
-  []
-  (true? (:feature/disable-scheduled-and-deadline-query?
-           (get (sub-config) (get-current-repo)))))
-
-(defn enable-timetracking?
-  []
-  (not (false? (:feature/enable-timetracking?
-                 (get (sub-config) (get-current-repo))))))
-
-(defn enable-journals?
-  ([]
-   (enable-journals? (get-current-repo)))
-  ([repo]
-   (not (false? (:feature/enable-journals?
-                 (get (sub-config) repo))))))
-
-(defn enable-flashcards?
-  ([]
-   (enable-flashcards? (get-current-repo)))
-  ([repo]
-   (not (false? (:feature/enable-flashcards?
-                 (get (sub-config) repo))))))
-
-(defn user-groups
-  []
-  (set (sub [:user/info :UserGroups])))
-
-(defn enable-sync?
-  []
-  (sub :feature/enable-sync?))
-
-(defn export-heading-to-list?
-  []
-  (not (false? (:export/heading-to-list?
-                 (get (sub-config) (get-current-repo))))))
-
-(defn enable-git-auto-push?
-  [repo]
-  (not (false? (:git-auto-push
-                 (get (sub-config) repo)))))
-
-(defn enable-block-timestamps?
-  []
-  (true? (:feature/enable-block-timestamps?
-           (get (sub-config) (get-current-repo)))))
-
-(defn enable-whiteboards?
-  ([]
-   (enable-whiteboards? (get-current-repo)))
-  ([repo]
-   (and
-    (util/electron?)
-    (true? (:feature/enable-whiteboards?
-            (get (sub-config) repo))))))
-
-(defn sub-graph-config
-  []
-  (get (sub-config) (get-current-repo)))
-
-(defn sub-graph-config-settings
-  []
-  (:graph/settings (sub-graph-config)))
-
-;; Enable by default
-(defn show-brackets?
-  []
-  (not (false? (:ui/show-brackets?
-                 (get (sub-config) (get-current-repo))))))
-
 (defn get-default-home
 (defn get-default-home
   []
   []
   (:default-home (get-config)))
   (:default-home (get-config)))
 
 
-(defn sub-default-home-page
-  []
-  (get-in (sub-config) [(get-current-repo) :default-home :page] ""))
-
 (defn custom-home-page?
 (defn custom-home-page?
   []
   []
   (some? (:page (get-default-home))))
   (some? (:page (get-default-home))))
@@ -536,6 +438,241 @@
   []
   []
   (:page-name-order (get-config)))
   (:page-name-order (get-config)))
 
 
+(defn get-date-formatter
+  []
+  (gp-config/get-date-formatter (get-config)))
+
+(defn shortcuts []
+  (:shortcuts (get-config)))
+
+(defn get-commands
+  []
+  (:commands (get-config)))
+
+(defn get-scheduled-future-days
+  []
+  (let [days (:scheduled/future-days (get-config))]
+    (or (when (int? days) days) 0)))
+
+(defn get-start-of-week
+  []
+  (or (:start-of-week (get-config))
+      (get-in @state [:me :settings :start-of-week])
+      6))
+
+(defn get-ref-open-blocks-level
+  []
+  (or
+    (when-let [value (:ref/default-open-blocks-level (get-config))]
+      (when (integer? value)
+        value))
+    2))
+
+(defn get-linked-references-collapsed-threshold
+  []
+  (or
+    (when-let [value (:ref/linked-references-collapsed-threshold (get-config))]
+      (when (integer? value)
+        value))
+    100))
+
+(defn get-export-bullet-indentation
+  []
+  (case (get (get-config) :export/bullet-indentation :tab)
+    :eight-spaces
+    "        "
+    :four-spaces
+    "    "
+    :two-spaces
+    "  "
+    :tab
+    "\t"))
+
+(defn enable-search-remove-accents?
+  []
+  (:feature/enable-search-remove-accents? (get-config)))
+
+;; State cursor fns for use with rum components
+;; ============================================
+
+(declare document-mode?)
+
+(defn sub
+  "Creates a rum cursor, https://github.com/tonsky/rum#cursors, for use in rum components.
+Similar to re-frame subscriptions"
+  [ks]
+  (if (coll? ks)
+    (util/react (rum/cursor-in state ks))
+    (util/react (rum/cursor state ks))))
+
+(defn sub-config
+  "Sub equivalent to get-config which should handle all sub user-config access"
+  ([] (sub-config (get-current-repo)))
+  ([repo]
+   (let [config (sub :config)]
+     (merge-configs default-config
+                    (get config ::global-config)
+                    (get config repo)))))
+
+(defn enable-grammarly?
+  []
+  (true? (:feature/enable-grammarly? (sub-config))))
+
+(defn scheduled-deadlines-disabled?
+  []
+  (true? (:feature/disable-scheduled-and-deadline-query? (sub-config))))
+
+(defn enable-timetracking?
+  []
+  (not (false? (:feature/enable-timetracking? (sub-config)))))
+
+(defn enable-journals?
+  ([]
+   (enable-journals? (get-current-repo)))
+  ([repo]
+   (not (false? (:feature/enable-journals? (sub-config repo))))))
+
+(defn enable-flashcards?
+  ([]
+   (enable-flashcards? (get-current-repo)))
+  ([repo]
+   (not (false? (:feature/enable-flashcards? (sub-config repo))))))
+
+(defn enable-sync?
+  []
+  (sub :feature/enable-sync?))
+
+(defn export-heading-to-list?
+  []
+  (not (false? (:export/heading-to-list? (sub-config)))))
+
+(defn enable-git-auto-push?
+  [repo]
+  (not (false? (:git-auto-push (sub-config repo)))))
+
+(defn enable-block-timestamps?
+  []
+  (true? (:feature/enable-block-timestamps? (sub-config))))
+
+(defn graph-settings
+  []
+  (:graph/settings (sub-config)))
+
+;; Enable by default
+(defn show-brackets?
+  []
+  (not (false? (:ui/show-brackets? (sub-config)))))
+
+(defn sub-default-home-page
+  []
+  (get-in (sub-config) [:default-home :page] ""))
+
+(defn sub-edit-content
+  [id]
+  (sub [:editor/content id]))
+
+(defn- get-selected-block-ids
+  [blocks]
+  (->> blocks
+       (keep #(when-let [id (dom/attr % "blockid")]
+                (uuid id)))
+       (distinct)))
+
+(defn sub-block-selected?
+  [block-uuid]
+  (rum/react
+   (rum/derived-atom [state] [::select-block block-uuid]
+     (fn [state]
+       (contains? (set (get-selected-block-ids (:selection/blocks state)))
+                  block-uuid)))))
+
+(defn block-content-max-length
+  [repo]
+  (or (:block/content-max-length (sub-config repo)) 5000))
+
+(defn mobile?
+  []
+  (or (util/mobile?) (mobile-util/native-platform?)))
+
+(defn enable-tooltip?
+  []
+  (if (mobile?)
+    false
+    (get (sub-config) :ui/enable-tooltip? true)))
+
+(defn show-command-doc?
+  []
+  (get (sub-config) :ui/show-command-doc? true))
+
+(defn logical-outdenting?
+  []
+  (:editor/logical-outdenting? (sub-config)))
+
+(defn enable-encryption?
+  [repo]
+  (:feature/enable-encryption? (sub-config repo)))
+
+(defn doc-mode-enter-for-new-line?
+  []
+  (and (document-mode?)
+       (not (:shortcut/doc-mode-enter-for-new-block? (get-config)))))
+
+(defn user-groups
+  []
+  (set (sub [:user/info :UserGroups])))
+
+;; State mutation helpers
+;; ======================
+
+(defn set-state!
+  [path value]
+  (if (vector? path)
+    (swap! state assoc-in path value)
+    (swap! state assoc path value)))
+
+(defn update-state!
+  [path f]
+  (if (vector? path)
+    (swap! state update-in path f)
+    (swap! state update path f)))
+
+;; State getters and setters
+;; =========================
+;; These fns handle any key except :config.
+;; Some state is also stored in local storage and/or sent to electron's main process
+
+(defn get-route-match
+  []
+  (:route-match @state))
+
+(defn get-current-route
+  []
+  (get-in (get-route-match) [:data :name]))
+
+(defn home?
+  []
+  (= :home (get-current-route)))
+
+(defn setups-picker?
+  []
+  (= :repo-add (get-current-route)))
+
+(defn get-current-page
+  []
+  (when (= :page (get-current-route))
+    (get-in (get-route-match)
+            [:path-params :name])))
+
+(defn route-has-p?
+  []
+  (get-in (get-route-match) [:query-params :p]))
+
+(defn get-current-repo
+  []
+  (or (:git/current-repo @state)
+      (when-not (mobile-util/native-platform?)
+        "local")))
+
 (defn get-remote-repos
 (defn get-remote-repos
   []
   []
   (get-in @state [:file-sync/remote-graphs :graphs]))
   (get-in @state [:file-sync/remote-graphs :graphs]))
@@ -627,10 +764,6 @@
   []
   []
   (get (:editor/content @state) (get-edit-input-id)))
   (get (:editor/content @state) (get-edit-input-id)))
 
 
-(defn sub-edit-content
-  []
-  (sub [:editor/content (get-edit-input-id)]))
-
 (defn get-cursor-range
 (defn get-cursor-range
   []
   []
   (:cursor-range @state))
   (:cursor-range @state))
@@ -684,13 +817,16 @@
     (do
     (do
       (set-editor-action! nil)
       (set-editor-action! nil)
       (set-editor-action-data! nil))))
       (set-editor-action-data! nil))))
+
 (defn get-editor-show-input
 (defn get-editor-show-input
   []
   []
   (when (= (get-editor-action) :input)
   (when (= (get-editor-action) :input)
     (get @state :editor/action-data)))
     (get @state :editor/action-data)))
+
 (defn set-editor-show-commands!
 (defn set-editor-show-commands!
   []
   []
   (when-not (get-editor-action) (set-editor-action! :commands)))
   (when-not (get-editor-action) (set-editor-action! :commands)))
+
 (defn set-editor-show-block-commands!
 (defn set-editor-show-block-commands!
   []
   []
   (when-not (get-editor-action) (set-editor-action! :block-commands)))
   (when-not (get-editor-action) (set-editor-action! :block-commands)))
@@ -745,25 +881,10 @@
   []
   []
   (:selection/blocks @state))
   (:selection/blocks @state))
 
 
-(defn- get-selected-block-ids
-  [blocks]
-  (->> blocks
-       (keep #(when-let [id (dom/attr % "blockid")]
-                (uuid id)))
-       (distinct)))
-
 (defn get-selection-block-ids
 (defn get-selection-block-ids
   []
   []
   (get-selected-block-ids (get-selection-blocks)))
   (get-selected-block-ids (get-selection-blocks)))
 
 
-(defn sub-block-selected?
-  [block-uuid]
-  (rum/react
-   (rum/derived-atom [state] [::select-block block-uuid]
-     (fn [state]
-       (contains? (set (get-selected-block-ids (:selection/blocks state)))
-                  block-uuid)))))
-
 (defn get-selection-start-block-or-first
 (defn get-selection-start-block-or-first
   []
   []
   (or (get-selection-start-block)
   (or (get-selection-start-block)
@@ -894,20 +1015,6 @@
        :container       (gobj/get container "id")
        :container       (gobj/get container "id")
        :pos             (cursor/pos (gdom/getElement edit-input-id))})))
        :pos             (cursor/pos (gdom/getElement edit-input-id))})))
 
 
-(defonce publishing? (atom nil))
-
-(defn publishing-enable-editing?
-  []
-  (and @publishing? (:publishing/enable-editing? (get-config))))
-
-(defn enable-editing?
-  []
-  (or (not @publishing?) (:publishing/enable-editing? (get-config))))
-
-(defn block-content-max-length
-  [repo]
-  (or (:block/content-max-length (get (sub-config) repo)) 5000))
-
 (defn clear-edit!
 (defn clear-edit!
   []
   []
   (swap! state merge {:editor/editing? nil
   (swap! state merge {:editor/editing? nil
@@ -1060,13 +1167,6 @@
   [value]
   [value]
   (set-state! :today value))
   (set-state! :today value))
 
 
-(defn get-date-formatter
-  []
-  (gp-config/get-date-formatter (get-config)))
-
-(defn shortcuts []
-  (get-in @state [:config (get-current-repo) :shortcuts]))
-
 (defn get-me
 (defn get-me
   []
   []
   (:me @state))
   (:me @state))
@@ -1200,11 +1300,6 @@
   []
   []
   (get @state :document/mode?))
   (get @state :document/mode?))
 
 
-(defn doc-mode-enter-for-new-line?
-  []
-  (and (document-mode?)
-       (not (:shortcut/doc-mode-enter-for-new-block? (sub-graph-config)))))
-
 (defn toggle-document-mode!
 (defn toggle-document-mode!
   []
   []
   (let [mode (document-mode?)]
   (let [mode (document-mode?)]
@@ -1221,28 +1316,15 @@
     (set-state! :ui/shortcut-tooltip? (not mode))
     (set-state! :ui/shortcut-tooltip? (not mode))
     (storage/set :ui/shortcut-tooltip? (not mode))))
     (storage/set :ui/shortcut-tooltip? (not mode))))
 
 
-(defn mobile?
-  []
-  (or (util/mobile?) (mobile-util/native-platform?)))
-
-(defn enable-tooltip?
-  []
-  (if (mobile?)
-    false
-    (get (get (sub-config) (get-current-repo))
-         :ui/enable-tooltip?
-         true)))
-
-(defn show-command-doc?
-  []
-  (get (get (sub-config) (get-current-repo))
-       :ui/show-command-doc?
-       true))
-
 (defn set-config!
 (defn set-config!
   [repo-url value]
   [repo-url value]
   (set-state! [:config repo-url] value))
   (set-state! [:config repo-url] value))
 
 
+(defn set-global-config!
+  [value]
+  ;; Placed under :config so cursors can work seamlessly
+  (set-config! ::global-config value))
+
 (defn get-wide-mode?
 (defn get-wide-mode?
   []
   []
   (:ui/wide-mode? @state))
   (:ui/wide-mode? @state))
@@ -1255,10 +1337,6 @@
   [value]
   [value]
   (set-state! :network/online? value))
   (set-state! :network/online? value))
 
 
-(defn get-commands
-  []
-  (:commands (get-config)))
-
 (defn get-plugins-commands
 (defn get-plugins-commands
   []
   []
   (mapcat seq (flatten (vals (:plugin/installed-slash-commands @state)))))
   (mapcat seq (flatten (vals (:plugin/installed-slash-commands @state)))))
@@ -1310,11 +1388,6 @@
     true))
     true))
 
 
 
 
-(defn get-scheduled-future-days
-  []
-  (let [days (:scheduled/future-days (get-config))]
-    (or (when (int? days) days) 0)))
-
 (defn set-graph-syncing?
 (defn set-graph-syncing?
   [value]
   [value]
   (set-state! :graph/syncing? value))
   (set-state! :graph/syncing? value))
@@ -1437,30 +1510,6 @@
   []
   []
   @editor-op)
   @editor-op)
 
 
-(defn get-start-of-week
-  []
-  (or
-    (when-let [repo (get-current-repo)]
-      (get-in @state [:config repo :start-of-week]))
-    (get-in @state [:me :settings :start-of-week])
-    6))
-
-(defn get-ref-open-blocks-level
-  []
-  (or
-    (when-let [value (:ref/default-open-blocks-level (get-config))]
-      (when (integer? value)
-        value))
-    2))
-
-(defn get-linked-references-collapsed-threshold
-  []
-  (or
-    (when-let [value (:ref/linked-references-collapsed-threshold (get-config))]
-      (when (integer? value)
-        value))
-    100))
-
 (defn get-events-chan
 (defn get-events-chan
   []
   []
   (:system/events @state))
   (:system/events @state))
@@ -1511,27 +1560,10 @@
   [value]
   [value]
   (set-state! :block/component-editing-mode? value))
   (set-state! :block/component-editing-mode? value))
 
 
-(defn logical-outdenting?
-  []
-  (:editor/logical-outdenting?
-    (get (sub-config) (get-current-repo))))
-
 (defn get-editor-args
 (defn get-editor-args
   []
   []
   (:editor/args @state))
   (:editor/args @state))
 
 
-(defn get-export-bullet-indentation
-  []
-  (case (get (get-config) :export/bullet-indentation :tab)
-    :eight-spaces
-    "        "
-    :four-spaces
-    "    "
-    :two-spaces
-    "  "
-    :tab
-    "\t"))
-
 (defn set-page-blocks-cp!
 (defn set-page-blocks-cp!
   [value]
   [value]
   (set-state! [:view/components :page-blocks] value))
   (set-state! [:view/components :page-blocks] value))
@@ -1757,11 +1789,6 @@
     (when (every? not-empty (vals agent-opts))
     (when (every? not-empty (vals agent-opts))
       (str (:protocol agent-opts) "://" (:host agent-opts) ":" (:port agent-opts)))))
       (str (:protocol agent-opts) "://" (:host agent-opts) ":" (:port agent-opts)))))
 
 
-(defn enable-encryption?
-  [repo]
-  (:feature/enable-encryption?
-   (get (sub-config) repo)))
-
 (defn set-mobile-app-state-change
 (defn set-mobile-app-state-change
   [is-active?]
   [is-active?]
   (set-state! :mobile/app-state-change
   (set-state! :mobile/app-state-change
@@ -1782,10 +1809,6 @@
   [dir]
   [dir]
   (contains? (:file/unlinked-dirs @state) dir))
   (contains? (:file/unlinked-dirs @state) dir))
 
 
-(defn enable-search-remove-accents?
-  []
-  (:feature/enable-search-remove-accents? (get-config)))
-
 (defn get-file-rename-event-chan
 (defn get-file-rename-event-chan
   []
   []
   (:file/rename-event-chan @state))
   (:file/rename-event-chan @state))

+ 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"))

+ 16 - 9
src/main/frontend/ui.cljs

@@ -7,6 +7,7 @@
             [frontend.modules.shortcut.core :as shortcut]
             [frontend.modules.shortcut.core :as shortcut]
             [frontend.rum :as r]
             [frontend.rum :as r]
             [frontend.state :as state]
             [frontend.state :as state]
+            [frontend.storage :as storage]
             [frontend.ui.date-picker]
             [frontend.ui.date-picker]
             [frontend.util :as util]
             [frontend.util :as util]
             [frontend.util.cursor :as cursor]
             [frontend.util.cursor :as cursor]
@@ -85,14 +86,14 @@
     (textarea props)))
     (textarea props)))
 
 
 (rum/defc dropdown-content-wrapper
 (rum/defc dropdown-content-wrapper
-  < {:did-mount    (fn [_state]
+  < {:did-mount    (fn [state]
                      (let [k    (inc (count (state/sub :modal/dropdowns)))
                      (let [k    (inc (count (state/sub :modal/dropdowns)))
-                           args (:rum/args _state)]
+                           args (:rum/args state)]
                        (state/set-state! [:modal/dropdowns k] (second args))
                        (state/set-state! [:modal/dropdowns k] (second args))
-                       (assoc _state ::k k)))
-     :will-unmount (fn [_state]
-                     (state/update-state! :modal/dropdowns #(dissoc % (::k _state)))
-                     _state)}
+                       (assoc state ::k k)))
+     :will-unmount (fn [state]
+                     (state/update-state! :modal/dropdowns #(dissoc % (::k state)))
+                     state)}
   [dropdown-state _close-fn content class]
   [dropdown-state _close-fn content class]
   (let [class (or class
   (let [class (or class
                   (util/hiccup->class "origin-top-right.absolute.right-0.mt-2"))]
                   (util/hiccup->class "origin-top-right.absolute.right-0.mt-2"))]
@@ -287,8 +288,8 @@
   (let [time-fn (fn []
   (let [time-fn (fn []
                   (try
                   (try
                     (util/time-ago input)
                     (util/time-ago input)
-                    (catch js/Error _e
-                      (js/console.error _e)
+                    (catch js/Error e
+                      (js/console.error e)
                       input)))
                       input)))
         [time set-time] (rum/use-state (time-fn))]
         [time set-time] (rum/use-state (time-fn))]
 
 
@@ -337,7 +338,12 @@
     (when (mobile-util/native-iphone-without-notch?) (.add cl "is-native-iphone-without-notch"))
     (when (mobile-util/native-iphone-without-notch?) (.add cl "is-native-iphone-without-notch"))
     (when (mobile-util/native-ipad?) (.add cl "is-native-ipad"))
     (when (mobile-util/native-ipad?) (.add cl "is-native-ipad"))
     (when (util/electron?)
     (when (util/electron?)
-      (js/window.apis.on "full-screen" #(js-invoke cl (if (= % "enter") "add" "remove") "is-fullscreen"))
+      (doseq [[event function]
+              [["persist-zoom-level" #(storage/set :zoom-level %)]
+               ["restore-zoom-level" #(when-let [zoom-level (storage/get :zoom-level)] (js/window.apis.setZoomLevel zoom-level))]
+               ["full-screen" #(js-invoke cl (if (= % "enter") "add" "remove") "is-fullscreen")]]]
+        (.on js/window.apis event function))
+
       (p/then (ipc/ipc :getAppBaseInfo) #(let [{:keys [isFullScreen]} (js->clj % :keywordize-keys true)]
       (p/then (ipc/ipc :getAppBaseInfo) #(let [{:keys [isFullScreen]} (js->clj % :keywordize-keys true)]
                                            (and isFullScreen (.add cl "is-fullscreen")))))))
                                            (and isFullScreen (.add cl "is-fullscreen")))))))
 
 
@@ -925,6 +931,7 @@
               (dissoc opts :class :extension?))]))
               (dissoc opts :class :extension?))]))
 
 
 (rum/defc with-shortcut < rum/reactive
 (rum/defc with-shortcut < rum/reactive
+  < {:key-fn (fn [key pos] (str "shortcut-" key pos))}
   [shortcut-key position content]
   [shortcut-key position content]
   (let [tooltip? (state/sub :ui/shortcut-tooltip?)]
   (let [tooltip? (state/sub :ui/shortcut-tooltip?)]
     (if tooltip?
     (if tooltip?

+ 5 - 1
src/main/frontend/ui.css

@@ -117,8 +117,12 @@
         padding: 2rem;
         padding: 2rem;
         width: auto;
         width: auto;
 
 
-        .ls-card, .ls-search {
+        .ls-card,
+        .ls-search {
           width: 740px;
           width: 740px;
+        }
+
+        .ls-card {
           min-height: 60vh;
           min-height: 60vh;
         }
         }
 
 

+ 2 - 2
src/main/frontend/util/cursor.cljs

@@ -39,8 +39,8 @@
                  (util/nth-safe pos)
                  (util/nth-safe pos)
                  mock-char-pos
                  mock-char-pos
                  (assoc :rect rect))
                  (assoc :rect rect))
-         (catch :default _e
-           (js/console.log "index error" _e)
+         (catch :default e
+           (js/console.log "index error" e)
            {:pos pos
            {:pos pos
             :rect rect
             :rect rect
             :left js/Number.MAX_SAFE_INTEGER
             :left js/Number.MAX_SAFE_INTEGER

+ 25 - 25
src/main/frontend/util/fs.cljs

@@ -17,44 +17,44 @@
   "Ignore path for ls-dir-files-with-handler! and reload-dir!"
   "Ignore path for ls-dir-files-with-handler! and reload-dir!"
   [dir path]
   [dir path]
   (let [ignores ["." ".recycle" "node_modules" "logseq/bak"
   (let [ignores ["." ".recycle" "node_modules" "logseq/bak"
-                    "logseq/version-files" "logseq/graphs-txid.edn"]]
+                 "logseq/version-files" "logseq/graphs-txid.edn"]]
     (when (string? path)
     (when (string? path)
-     (or
-      (some #(string/starts-with? path (str dir "/" %)) ignores)
-      (some #(string/includes? path (str "/" % "/")) ignores)
-      (some #(string/ends-with? path %)
-            [".DS_Store" "logseq/graphs-txid.edn" "logseq/broken-config.edn"])
+      (or
+       (some #(string/starts-with? path (str dir "/" %)) ignores)
+       (some #(string/includes? path (str "/" % "/")) ignores)
+       (some #(string/ends-with? path %)
+             [".DS_Store" "logseq/graphs-txid.edn" "logseq/broken-config.edn"])
       ;; hidden directory or file
       ;; hidden directory or file
-      (let [relpath (path/relative dir path)]
-        (or (re-find #"/\.[^.]+" relpath)
-            (re-find #"^\.[^.]+" relpath)))
-      (let [path (string/lower-case path)]
-        (and
-         (not (string/blank? (path/extname path)))
-         (not
-          (some #(string/ends-with? path %)
-                [".md" ".markdown" ".org" ".js" ".edn" ".css"]))))))))
+       (let [relpath (path/relative dir path)]
+         (or (re-find #"/\.[^.]+" relpath)
+             (re-find #"^\.[^.]+" relpath)))
+       (let [path (string/lower-case path)]
+         (and
+          (not (string/blank? (path/extname path)))
+          (not
+           (some #(string/ends-with? path %)
+                 [".md" ".markdown" ".org" ".js" ".edn" ".css"]))))))))
 
 
-(defn read-graph-txid-info
+(defn read-graphs-txid-info
   [root]
   [root]
   (when (string? root)
   (when (string? root)
     (-> (p/let [txid-str (fs/read-file root "logseq/graphs-txid.edn")
     (-> (p/let [txid-str (fs/read-file root "logseq/graphs-txid.edn")
                 txid-meta (and txid-str (reader/read-string txid-str))]
                 txid-meta (and txid-str (reader/read-string txid-str))]
-               txid-meta)
+          txid-meta)
         (p/catch
         (p/catch
-          (fn [^js e]
-            (js/console.error "[fs read txid data error]" e))))))
+         (fn [^js e]
+           (js/console.error "[fs read txid data error]" e))))))
 
 
 (defn inflate-graphs-info
 (defn inflate-graphs-info
   [graphs]
   [graphs]
   (if (seq graphs)
   (if (seq graphs)
     (p/all (for [{:keys [root] :as graph} graphs]
     (p/all (for [{:keys [root] :as graph} graphs]
-             (p/let [sync-meta (read-graph-txid-info root)]
-                    (if sync-meta
-                      (assoc graph
-                             :sync-meta sync-meta
-                             :GraphUUID (second sync-meta))
-                      graph))))
+             (p/let [sync-meta (read-graphs-txid-info root)]
+               (if sync-meta
+                 (assoc graph
+                        :sync-meta sync-meta
+                        :GraphUUID (second sync-meta))
+                 graph))))
     []))
     []))
 
 
 (defn read-repo-file
 (defn read-repo-file

+ 23 - 18
src/main/frontend/util/persist_var.cljs

@@ -27,36 +27,41 @@
 
 
   ILoad
   ILoad
   (-load [_]
   (-load [_]
-    (when-not (config/demo-graph?)
+    (if (config/demo-graph?)
+      (p/resolved nil)
       (let [repo (state/get-current-repo)
       (let [repo (state/get-current-repo)
             dir (config/get-repo-dir repo)
             dir (config/get-repo-dir repo)
             path (load-path location)]
             path (load-path location)]
-        (p/let [stat (p/catch (fs/stat dir path)
-                              (constantly nil))
-                content (when stat
-                          (p/catch
-                           (fs/read-file dir path)
-                           (constantly nil)))]
-          (when-let [content (and (some? content)
-                                  (try (cljs.reader/read-string content)
-                                       (catch js/Error e
-                                         (println (util/format "load persist-var failed: %s"  (load-path location)))
-                                         (js/console.dir e))))]
-            (swap! *value (fn [o]
-                            (-> o
-                                (assoc-in [repo :loaded?] true)
-                                (assoc-in [repo :value] content)))))))))
+        (-> (p/chain (fs/stat dir path)
+                     (fn [stat]
+                       (when stat
+                         (fs/read-file dir path)))
+                     (fn [content]
+                       (when (not-empty content)
+                         (try (cljs.reader/read-string content)
+                              (catch js/Error e
+                                (println (util/format "read persist-var failed: %s" (load-path location)))
+                                (js/console.dir e)))))
+                     (fn [value]
+                       (when (some? value)
+                         (swap! *value (fn [o]
+                                         (-> o
+                                             (assoc-in [repo :loaded?] true)
+                                             (assoc-in [repo :value] value)))))))
+            (p/catch (fn [e]
+                       (println (util/format "load persist-var failed: %s: %s" (load-path location) e))))))))
   (-loaded? [_]
   (-loaded? [_]
     (get-in @*value [(state/get-current-repo) :loaded?]))
     (get-in @*value [(state/get-current-repo) :loaded?]))
 
 
   ISave
   ISave
   (-save [_]
   (-save [_]
-    (when-not (config/demo-graph?)
+    (if (config/demo-graph?)
+      (p/resolved nil)
       (let [path (load-path location)
       (let [path (load-path location)
             repo (state/get-current-repo)
             repo (state/get-current-repo)
             content (str (get-in @*value [repo :value]))
             content (str (get-in @*value [repo :value]))
             dir (config/get-repo-dir repo)]
             dir (config/get-repo-dir repo)]
-        (fs/write-file! repo dir path content nil))))
+        (fs/write-file! repo dir path content {:skip-compare? true}))))
 
 
   IDeref
   IDeref
   (-deref [_this]
   (-deref [_this]

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

@@ -113,7 +113,7 @@
 
 
 (def ^:export get_current_graph_configs
 (def ^:export get_current_graph_configs
   (fn []
   (fn []
-    (some-> (get (:config @state/state) (state/get-current-repo))
+    (some-> (state/get-config)
             (normalize-keyword-for-json)
             (normalize-keyword-for-json)
             (bean/->js))))
             (bean/->js))))
 
 
@@ -852,9 +852,9 @@
 
 
 (defn ^:export exper_request
 (defn ^:export exper_request
   [pid ^js options]
   [pid ^js options]
-  (when-let [^js _pl (plugin-handler/get-plugin-inst pid)]
+  (when-let [^js pl (plugin-handler/get-plugin-inst pid)]
     (let [req-id (vreset! *request-k (inc @*request-k))
     (let [req-id (vreset! *request-k (inc @*request-k))
-          req-cb #(plugin-handler/request-callback _pl req-id %)]
+          req-cb #(plugin-handler/request-callback pl req-id %)]
       (-> (ipc/ipc :httpRequest req-id options)
       (-> (ipc/ipc :httpRequest req-id options)
           (p/then #(req-cb %))
           (p/then #(req-cb %))
           (p/catch #(req-cb %)))
           (p/catch #(req-cb %)))

+ 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"}}
+
+{}

+ 21 - 8
yarn.lock

@@ -14,6 +14,14 @@
   dependencies:
   dependencies:
     "@jridgewell/trace-mapping" "^0.3.0"
     "@jridgewell/trace-mapping" "^0.3.0"
 
 
+"@axe-core/playwright@^4.4.4":
+  version "4.4.4"
+  resolved "https://registry.yarnpkg.com/@axe-core/playwright/-/playwright-4.4.4.tgz#3786c5f6bba38d1991b608584b00ae2744544573"
+  integrity sha512-VA7MR1WCqW5tFcUGCXDaaqV9pJUCdOGIR4DiZJrOxGjeRYxz3VwyMc1MDg/yiJ5fQA/QYMx+w0mvqYEr3CPx7w==
+  dependencies:
+    axe-core "^4.4.2"
+    playwright ">= 1.0.0"
+
 "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.7":
 "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.7":
   version "7.16.7"
   version "7.16.7"
   resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789"
   resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789"
@@ -1226,18 +1234,11 @@ autoprefixer@^9.8.6:
     postcss "^7.0.32"
     postcss "^7.0.32"
     postcss-value-parser "^4.1.0"
     postcss-value-parser "^4.1.0"
 
 
-axe-core@^4.0.1:
+axe-core@^4.4.2:
   version "4.4.3"
   version "4.4.3"
   resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.3.tgz#11c74d23d5013c0fa5d183796729bc3482bd2f6f"
   resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.3.tgz#11c74d23d5013c0fa5d183796729bc3482bd2f6f"
   integrity sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==
   integrity sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==
 
 
-axe-playwright@^1.1.11:
-  version "1.1.11"
-  resolved "https://registry.yarnpkg.com/axe-playwright/-/axe-playwright-1.1.11.tgz#e57638f08d29b58d157a2aeb34cf81730eab2cff"
-  integrity sha512-YHmUouvF/dFNxoFFwbCjPFmEPwoJSzPgZsD0KZs3xjsR03Rf2mAh771ugre950MaBYuiyxYDlurH5BOEJBK34Q==
-  dependencies:
-    axe-core "^4.0.1"
-
 bach@^1.0.0:
 bach@^1.0.0:
   version "1.2.0"
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/bach/-/bach-1.2.0.tgz#4b3ce96bf27134f79a1b414a51c14e34c3bd9880"
   resolved "https://registry.yarnpkg.com/bach/-/bach-1.2.0.tgz#4b3ce96bf27134f79a1b414a51c14e34c3bd9880"
@@ -5565,6 +5566,18 @@ [email protected]:
   resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.24.2.tgz#47bc5adf3dcfcc297a5a7a332449c9009987db26"
   resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.24.2.tgz#47bc5adf3dcfcc297a5a7a332449c9009987db26"
   integrity sha512-zfAoDoPY/0sDLsgSgLZwWmSCevIg1ym7CppBwllguVBNiHeixZkc1AdMuYUPZC6AdEYc4CxWEyLMBTw2YcmRrA==
   integrity sha512-zfAoDoPY/0sDLsgSgLZwWmSCevIg1ym7CppBwllguVBNiHeixZkc1AdMuYUPZC6AdEYc4CxWEyLMBTw2YcmRrA==
 
 
[email protected]:
+  version "1.25.2"
+  resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.25.2.tgz#ea4baa398a4d45fcdfe48799482b599e3d0f033f"
+  integrity sha512-0yTbUE9lIddkEpLHL3u8PoCL+pWiZtj5A/j3U7YoNjcmKKDGBnCrgHJMzwd2J5vy6l28q4ki3JIuz7McLHhl1A==
+
+"playwright@>= 1.0.0":
+  version "1.25.2"
+  resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.25.2.tgz#0fc67e4385a52a51371ff9114bf68e3ad50a7f41"
+  integrity sha512-RwMB5SFRV/8wSfK+tK8ycpqdzORvoqUNz9DUeRfSgZFrZej5uuBl9wFjWcc+OkXFEtaPmx1acAVGG7hA4IJ1kg==
+  dependencies:
+    playwright-core "1.25.2"
+
 playwright@^1.24.2:
 playwright@^1.24.2:
   version "1.24.2"
   version "1.24.2"
   resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.24.2.tgz#51e60f128b386023e5ee83deca23453aaf73ba6d"
   resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.24.2.tgz#51e60f128b386023e5ee83deca23453aaf73ba6d"