瀏覽代碼

Merge branch 'master' into gesture-support-on-block

llcc 3 年之前
父節點
當前提交
e119fef6bd
共有 100 個文件被更改,包括 1817 次插入1005 次删除
  1. 6 0
      .carve/ignore
  2. 5 1
      .clj-kondo/config.edn
  3. 10 1
      .github/workflows/build.yml
  4. 2 0
      .gitignore
  5. 7 5
      CODEBASE_OVERVIEW.md
  6. 2 2
      android/app/build.gradle
  7. 5 0
      bb.edn
  8. 0 1
      deps.edn
  9. 7 0
      docs/dev-practices.md
  10. 6 0
      libs/src/LSPlugin.ts
  11. 2 0
      package.json
  12. 1 0
      public/index.html
  13. 5 0
      resources/css/common.css
  14. 1 0
      resources/electron.html
  15. 1 0
      resources/index.html
  16. 2 2
      resources/package.json
  17. 30 0
      scripts/src/logseq/tasks/nbb.clj
  18. 6 6
      shadow-cljs.edn
  19. 1 1
      src/electron/electron/core.cljs
  20. 1 1
      src/electron/electron/file_sync_rsapi.cljs
  21. 4 0
      src/electron/electron/handler.cljs
  22. 1 1
      src/electron/electron/utils.cljs
  23. 9 7
      src/main/frontend/commands.cljs
  24. 218 126
      src/main/frontend/components/block.cljs
  25. 0 1
      src/main/frontend/components/command_palette.css
  26. 7 8
      src/main/frontend/components/content.cljs
  27. 4 3
      src/main/frontend/components/editor.cljs
  28. 176 0
      src/main/frontend/components/encryption.cljs
  29. 4 4
      src/main/frontend/components/file.cljs
  30. 7 5
      src/main/frontend/components/journal.cljs
  31. 0 8
      src/main/frontend/components/onboarding/index.css
  32. 6 5
      src/main/frontend/components/onboarding/setups.cljs
  33. 17 11
      src/main/frontend/components/page.cljs
  34. 4 4
      src/main/frontend/components/page_menu.cljs
  35. 88 9
      src/main/frontend/components/plugins.cljs
  36. 1 2
      src/main/frontend/components/query_table.cljs
  37. 26 22
      src/main/frontend/components/reference.cljs
  38. 9 19
      src/main/frontend/components/repo.cljs
  39. 30 40
      src/main/frontend/components/right_sidebar.cljs
  40. 11 13
      src/main/frontend/components/search.cljs
  41. 21 36
      src/main/frontend/components/settings.cljs
  42. 4 5
      src/main/frontend/components/sidebar.cljs
  43. 13 13
      src/main/frontend/config.cljs
  44. 2 1
      src/main/frontend/db/conn.cljs
  45. 1 1
      src/main/frontend/db/default.cljs
  46. 39 72
      src/main/frontend/db/model.cljs
  47. 3 2
      src/main/frontend/db/query_dsl.cljs
  48. 3 2
      src/main/frontend/db/query_react.cljs
  49. 16 6
      src/main/frontend/db/react.cljs
  50. 1 1
      src/main/frontend/db/rules.cljc
  51. 3 2
      src/main/frontend/db/utils.cljs
  52. 3 1
      src/main/frontend/db_schema.cljs
  53. 11 2
      src/main/frontend/dicts.cljc
  54. 2 1
      src/main/frontend/diff.cljs
  55. 104 0
      src/main/frontend/encrypt.cljs
  56. 23 0
      src/main/frontend/extensions/age_encryption.cljs
  57. 2 2
      src/main/frontend/extensions/code.cljs
  58. 2 1
      src/main/frontend/extensions/html_parser.cljs
  59. 8 7
      src/main/frontend/extensions/pdf/assets.cljs
  60. 13 13
      src/main/frontend/extensions/pdf/highlights.cljs
  61. 2 2
      src/main/frontend/extensions/pdf/utils.cljs
  62. 1 1
      src/main/frontend/extensions/srs.cljs
  63. 0 2
      src/main/frontend/external.cljs
  64. 42 50
      src/main/frontend/external/roam.cljs
  65. 14 11
      src/main/frontend/format/block.cljs
  66. 10 10
      src/main/frontend/format/mldoc.cljs
  67. 16 12
      src/main/frontend/fs.cljs
  68. 12 4
      src/main/frontend/fs/capacitor_fs.cljs
  69. 12 4
      src/main/frontend/fs/nfs.cljs
  70. 12 4
      src/main/frontend/fs/node.cljs
  71. 289 236
      src/main/frontend/fs/sync.cljs
  72. 8 5
      src/main/frontend/fs/watcher_handler.cljs
  73. 2 0
      src/main/frontend/handler.cljs
  74. 8 0
      src/main/frontend/handler/common.cljs
  75. 3 2
      src/main/frontend/handler/draw.cljs
  76. 40 40
      src/main/frontend/handler/editor.cljs
  77. 58 8
      src/main/frontend/handler/events.cljs
  78. 5 4
      src/main/frontend/handler/extract.cljs
  79. 14 2
      src/main/frontend/handler/file.cljs
  80. 3 2
      src/main/frontend/handler/graph.cljs
  81. 61 1
      src/main/frontend/handler/metadata.cljs
  82. 32 31
      src/main/frontend/handler/page.cljs
  83. 58 29
      src/main/frontend/handler/repo.cljs
  84. 2 1
      src/main/frontend/handler/route.cljs
  85. 2 2
      src/main/frontend/handler/shell.cljs
  86. 4 3
      src/main/frontend/handler/ui.cljs
  87. 3 7
      src/main/frontend/handler/user.cljs
  88. 12 7
      src/main/frontend/handler/web/nfs.cljs
  89. 5 3
      src/main/frontend/modules/file/core.cljs
  90. 2 2
      src/main/frontend/modules/instrumentation/posthog.cljs
  91. 5 5
      src/main/frontend/modules/instrumentation/sentry.cljs
  92. 6 6
      src/main/frontend/modules/layout/core.cljs
  93. 2 1
      src/main/frontend/modules/outliner/core.cljs
  94. 3 3
      src/main/frontend/modules/outliner/datascript.cljc
  95. 2 2
      src/main/frontend/modules/outliner/tree.cljs
  96. 2 2
      src/main/frontend/modules/shortcut/core.cljs
  97. 27 12
      src/main/frontend/modules/shortcut/data_helper.cljs
  98. 2 2
      src/main/frontend/security.cljs
  99. 34 6
      src/main/frontend/state.cljs
  100. 6 5
      src/main/frontend/text.cljs

+ 6 - 0
.carve/ignore

@@ -15,6 +15,12 @@ frontend.debug/print
 ;; Lazily loaded
 frontend.extensions.code/editor
 ;; Lazily loaded
+frontend.extensions.age-encryption/keygen
+frontend.extensions.age-encryption/encrypt-with-x25519
+frontend.extensions.age-encryption/decrypt-with-x25519
+frontend.extensions.age-encryption/encrypt-with-user-passphrase
+frontend.extensions.age-encryption/decrypt-with-user-passphrase
+;; Lazily loaded
 frontend.extensions.excalidraw/draw
 ;; Referenced in commented TODO
 frontend.extensions.pdf.utils/get-page-bounding

+ 5 - 1
.clj-kondo/config.edn

@@ -17,7 +17,11 @@
              medley.core medley
              frontend.db.query-dsl query-dsl
              frontend.db.react react
-             frontend.db.query-react query-react}}}
+             frontend.db.query-react query-react
+             frontend.util util
+             frontend.config config
+             logseq.graph-parser.util gp-util
+             logseq.graph-parser.config gp-config}}}
 
  :hooks {:analyze-call {rum.core/defc hooks.rum/defc
                          rum.core/defcs hooks.rum/defcs}}

+ 10 - 1
.github/workflows/build.yml

@@ -17,7 +17,7 @@ env:
   JAVA_VERSION: '8'
   # This is the latest node version we can run.
   NODE_VERSION: '16'
-  BABASHKA_VERSION: '0.7.7'
+  BABASHKA_VERSION: '0.8.1'
 
 jobs:
 
@@ -52,6 +52,11 @@ jobs:
         with:
           cli: ${{ env.CLOJURE_VERSION }}
 
+      - name: Setup Babashka
+        uses: turtlequeue/[email protected]
+        with:
+          babashka-version: ${{ env.BABASHKA_VERSION }}
+
       - name: Clojure cache
         uses: actions/cache@v2
         id: clojure-deps
@@ -74,6 +79,10 @@ jobs:
           yarn cljs:test
           node static/tests.js
 
+      # In this job because it depends on an npm package
+      - name: Load nbb compatible namespaces
+        run: bb test:load-nbb-compatible-namespaces
+
   lint:
     runs-on: ubuntu-latest
 

+ 2 - 0
.gitignore

@@ -42,3 +42,5 @@ android/app/src/main/assets/capacitor.plugin.json
 ios/App/App/capacitor.config.json
 
 startup.png
+
+/src/test/docs

+ 7 - 5
CODEBASE_OVERVIEW.md

@@ -36,15 +36,17 @@ After cloning the [Logseq repository](https://github.com/logseq/logseq), there a
 
 - Config files are located at the root directory. `package.json` contains the JavaScript dependencies while `deps.edn` contains their Clojure counterparts. `shadow-cljs.edn` and `gulpfile.js` contain all the build scripts.
 
-- `/public` and `/resources` contain all the static assets
+- `public/` and `resources/` contain all the static assets
 
-- `/src` is where most of the code locates.
+- `src/` is where most of the code is located.
 
-  - `/src/electron` and `/src/main/electron` contains code specific to the desktop app.
+  - `src/electron/` and `src/main/electron/` contains code specific to the desktop app.
 
-  - `/src/test` contains all the test and `/src/dev-cljs` contains some development utilities.
+  - `src/test/` contains all the tests and `src/dev-cljs/` contains some development utilities.
 
-  - `/src/main/frontend` contains code that powers the Logseq editor. Folders and files inside are organized by features or functions. For example, `components` contains all the UI components and `handler` contains all the event-handling code. You can explore on your own interest.
+  - `src/main/frontend/` contains code that powers the Logseq editor. Folders and files inside are organized by features or functions. For example, `components` contains all the UI components and `handler` contains all the event-handling code. You can explore on your own interest.
+
+  - `src/main/logseq/` contains the api used by plugins and the graph-parser.
 
 ## Data Flow
 

+ 2 - 2
android/app/build.gradle

@@ -6,8 +6,8 @@ android {
         applicationId "com.logseq.app"
         minSdkVersion rootProject.ext.minSdkVersion
         targetSdkVersion rootProject.ext.targetSdkVersion
-        versionCode 20
-        versionName "0.6.7"
+        versionCode 21
+        versionName "0.6.8"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         aaptOptions {
              // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.

+ 5 - 0
bb.edn

@@ -4,6 +4,8 @@
   {:git/url "https://github.com/babashka/spec.alpha"
    :sha "1a841c4cc1d4f6dab7505a98ed2d532dd9d56b78"}
   medley/medley {:mvn/version "1.3.0"}}
+ :pods
+ {clj-kondo/clj-kondo {:version "2022.02.09"}}
  :tasks
  {dev:watch
   logseq.tasks.dev/watch
@@ -22,6 +24,9 @@
   dev:validate-local-storage
   logseq.tasks.spec/validate-local-storage
 
+  test:load-nbb-compatible-namespaces
+  logseq.tasks.nbb/load-compatible-namespaces
+
   lang:list
   logseq.tasks.lang/list-langs
 

+ 0 - 1
deps.edn

@@ -1,7 +1,6 @@
 {:paths ["src/main" "src/electron" "src/workspaces" "templates"]
  :deps
  {org.clojure/clojure                   {:mvn/version "1.10.0"}
-  cheshire/cheshire                     {:mvn/version "5.10.0"}
   rum/rum                               {:mvn/version "0.12.9"}
   datascript/datascript                 {:mvn/version "1.3.8"}
   datascript-transit/datascript-transit {:mvn/version "0.3.0"}

+ 7 - 0
docs/dev-practices.md

@@ -58,6 +58,13 @@ We use [datascript](https://github.com/tonsky/datascript)'s datalog to power our
 scripts/lint_rules.clj
 ```
 
+### Nbb compatible
+
+Namespaces have the metadata flag `^:nbb-compatible` indicate they are compatible with https://github.com/logseq/nbb-logseq. This compatibility is necessary in order for namespaces to be reused by the frontend and CLIs. To confirm these compatibilities, run:
+```
+bb test:load-nbb-compatible-namespaces
+```
+
 ## Testing
 
 We have unit and end to end tests.

+ 6 - 0
libs/src/LSPlugin.ts

@@ -308,6 +308,12 @@ export interface IAppProxy {
     action: SimpleCommandCallback
   ) => void
 
+  /**
+   * Supported key names
+   * @link https://gist.github.com/xyhp915/d1a6d151a99f31647a95e59cdfbf4ddc
+   * @param keybinding
+   * @param action
+   */
   registerCommandShortcut: (
     keybinding: SimpleCommandKeybinding,
     action: SimpleCommandCallback

+ 2 - 0
package.json

@@ -5,6 +5,7 @@
     "main": "static/electron.js",
     "devDependencies": {
         "@capacitor/cli": "3.2.2",
+        "@logseq/nbb-logseq": "^0.3.10",
         "@playwright/test": "^1.19.2",
         "@tailwindcss/ui": "0.7.2",
         "@types/gulp": "^4.0.7",
@@ -113,6 +114,7 @@
         "react-textarea-autosize": "8.3.3",
         "react-tippy": "1.4.0",
         "react-transition-group": "4.3.0",
+        "react-visibility-sensor": "^5.1.1",
         "reakit": "0.11.1",
         "remove-accents": "0.4.2",
         "send-intent": "3.0.11",

+ 1 - 0
public/index.html

@@ -49,6 +49,7 @@
 <script defer src="/static/js/lsplugin.core.js"></script>
 <script defer src="/static/js/main.js"></script>
 <script defer src="/static/js/code-editor.js"></script>
+<script defer src="/static/js/age-encryption.js"></script>
 <script defer src="/static/js/excalidraw.js"></script>
 <script>
   /*!

+ 5 - 0
resources/css/common.css

@@ -1169,3 +1169,8 @@ html[data-theme='dark'] .keyboard-shortcut > code {
     overflow: hidden;
     text-overflow: ellipsis;
 }
+
+.lazy-visibility {
+    min-width: 1px;
+    min-height: 1px;
+}

+ 1 - 0
resources/electron.html

@@ -53,6 +53,7 @@ const portal = new MagicPortal(worker);
 <script defer src="./js/lsplugin.core.js"></script>
 <script defer src="./js/main.js"></script>
 <script defer src="./js/code-editor.js"></script>
+<script defer src="./js/age-encryption.js"></script>
 <script defer src="./js/excalidraw.js"></script>
 </body>
 </html>

+ 1 - 0
resources/index.html

@@ -52,6 +52,7 @@ const portal = new MagicPortal(worker);
 <script defer src="./js/lsplugin.core.js"></script>
 <script defer src="./js/main.js"></script>
 <script defer src="./js/code-editor.js"></script>
+<script defer src="./js/age-encryption.js"></script>
 <script defer src="./js/excalidraw.js"></script>
 </body>
 </html>

+ 2 - 2
resources/package.json

@@ -1,6 +1,6 @@
 {
   "name": "Logseq",
-  "version": "0.6.7",
+  "version": "0.6.8",
   "main": "electron.js",
   "author": "Logseq",
   "license": "AGPL-3.0",
@@ -36,7 +36,7 @@
     "https-proxy-agent": "5.0.0",
     "@sentry/electron": "2.5.1",
     "posthog-js": "1.10.2",
-    "@andelf/rsapi": "0.0.7",
+    "@logseq/rsapi": "0.0.11",
     "electron-deeplink": "1.0.9"
   },
   "devDependencies": {

+ 30 - 0
scripts/src/logseq/tasks/nbb.clj

@@ -0,0 +1,30 @@
+(ns logseq.tasks.nbb
+  (:require [pod.borkdude.clj-kondo :as clj-kondo]
+            [babashka.tasks :refer [shell]]))
+
+(defn- fetch-meta-namespaces
+  "Return namespaces with metadata"
+  [paths]
+  (let [paths (or (seq paths) ["src"])
+        {{:keys [namespace-definitions]} :analysis}
+        (clj-kondo/run!
+         {:lint paths
+          :config {:output {:analysis {:namespace-definitions {:meta true}}}}})
+        matches (keep (fn [m]
+                        (when (:meta m)
+                          {:ns   (:name m)
+                           :meta (:meta m)}))
+                      namespace-definitions)]
+    matches))
+
+(defn load-compatible-namespaces
+  "Check nbb-compatible namespaces can be required by nbb-logseq"
+  []
+  (let [namespaces (map :ns
+                        (filter #(get-in % [:meta :nbb-compatible])
+                                (fetch-meta-namespaces ["src/main"])))]
+    (assert (seq namespaces) "There must be some nbb namespaces to check")
+    (doseq [n namespaces]
+      (println "Requiring" n "...")
+      (shell "yarn nbb-logseq -cp src/main -e" (format "(require '[%s])" n)))
+    (println "Success!")))

+ 6 - 6
shadow-cljs.edn

@@ -12,12 +12,12 @@
         :js-options    {:ignore-asset-requires true} ;; handle `require(xxx.css)`
         :modules       {:main
                         {:init-fn    frontend.core/init}
-                        ;; :graph
-                        ;; {:entries [frontend.extensions.graph.force]
-                        ;;  :depends-on #{:main}}
                         :code-editor
                         {:entries    [frontend.extensions.code]
                          :depends-on #{:main}}
+                        :age-encryption
+                        {:entries    [frontend.extensions.age-encryption]
+                         :depends-on #{:main}}
                         :excalidraw
                         {:entries    [frontend.extensions.excalidraw]
                          :depends-on #{:main}}}
@@ -69,12 +69,12 @@
                :js-options    {:ignore-asset-requires true}
                :modules       {:main
                                {:init-fn    frontend.publishing/init}
-                               ;; :graph
-                               ;; {:entries [frontend.extensions.graph.force]
-                               ;;  :depends-on #{:main}}
                                :code-editor
                                {:entries    [frontend.extensions.code]
                                 :depends-on #{:main}}
+                               :age-encryption
+                               {:entries    [frontend.extensions.age-encryption]
+                                :depends-on #{:main}}
                                :excalidraw
                                {:entries    [frontend.extensions.excalidraw]
                                 :depends-on #{:main}}}

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

@@ -127,7 +127,7 @@
                 ;; TODO: ugly, replace with ls-files and filter with ".map"
                 _ (p/all (map (fn [file]
                                 (. fs removeSync (path/join static-dir "js" (str file ".map"))))
-                           ["main.js" "code-editor.js" "excalidraw.js"]))]
+                           ["main.js" "code-editor.js" "excalidraw.js" "age-encryption.js"]))]
           (. dialog showMessageBox (clj->js {:message (str "Export public pages and publish assets to " root-dir " successfully")})))))))
 
 (defn setup-app-manager!

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

@@ -1,5 +1,5 @@
 (ns electron.file-sync-rsapi
-  (:require ["@andelf/rsapi" :as rsapi]))
+  (:require ["@logseq/rsapi" :as rsapi]))
 
 (defn set-env [env] (rsapi/setEnv env))
 

+ 4 - 0
src/electron/electron/handler.cljs

@@ -314,6 +314,10 @@
 (defmethod handle :getLogseqDotDirRoot []
   (utils/get-ls-dotdir-root))
 
+(defmethod handle :testProxyUrl [win [_ url]]
+  (p/let [_ (utils/fetch url)]
+    (utils/send-to-renderer win :notification {:type "success" :payload (str "Successfully: " url)})))
+
 (defmethod handle :getUserDefaultPlugins []
   (utils/get-ls-default-plugins))
 

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

@@ -118,7 +118,7 @@
   ([window kind payload]
    (when window
      (.. ^js window -webContents
-         (send kind (bean/->js payload))))))
+         (send (name kind) (bean/->js payload))))))
 
 (defn get-graph-dir
   [graph-name]

+ 9 - 7
src/main/frontend/commands.cljs

@@ -15,6 +15,8 @@
             [frontend.util.marker :as marker]
             [frontend.util.priority :as priority]
             [frontend.util.property :as property]
+            [logseq.graph-parser.util :as gp-util]
+            [logseq.graph-parser.config :as gp-config]
             [goog.dom :as gdom]
             [goog.object :as gobj]
             [promesa.core :as p]))
@@ -274,7 +276,7 @@
                     [:codemirror/focus]] "Insert a calculator"]
      ["Draw" (fn []
                (let [file (draw/file-name)
-                     path (str config/default-draw-directory "/" file)
+                     path (str gp-config/default-draw-directory "/" file)
                      text (util/format "[[%s]]" path)]
                  (p/let [_ (draw/create-draw-with-default-content path)]
                    (println "draw file created, " path))
@@ -333,13 +335,13 @@
           current-pos (cursor/pos input)
           current-pos (or
                        (when (and end-pattern (string? end-pattern))
-                         (when-let [i (string/index-of (util/safe-subs edit-content current-pos) end-pattern)]
+                         (when-let [i (string/index-of (gp-util/safe-subs edit-content current-pos) end-pattern)]
                            (+ current-pos i)))
                        current-pos)
           orig-prefix (subs edit-content 0 current-pos)
           space? (when (and last-pattern orig-prefix)
                    (let [s (when-let [last-index (string/last-index-of orig-prefix last-pattern)]
-                             (util/safe-subs orig-prefix 0 last-index))]
+                             (gp-util/safe-subs orig-prefix 0 last-index))]
                      (not
                       (or
                        (and s
@@ -352,7 +354,7 @@
                    space?)
           prefix (cond
                    (and backward-truncate-number (integer? backward-truncate-number))
-                   (str (util/safe-subs orig-prefix 0 (- (count orig-prefix) backward-truncate-number))
+                   (str (gp-util/safe-subs orig-prefix 0 (- (count orig-prefix) backward-truncate-number))
                         (when-not (zero? backward-truncate-number)
                           value))
 
@@ -515,7 +517,7 @@
 
 (defn compute-pos-delta-when-change-marker
   [edit-content marker pos]
-  (let [old-marker (some->> (first (util/safe-re-find marker/bare-marker-pattern edit-content))
+  (let [old-marker (some->> (first (gp-util/safe-re-find marker/bare-marker-pattern edit-content))
                             (string/trim))
         pos-delta (- (count marker)
                      (count old-marker))
@@ -540,7 +542,7 @@
                   (if-let [matches (seq (util/re-pos new-line-re-pattern prefix))]
                     (let [[start-pos content] (last matches)]
                       (+ start-pos (count content)))
-                    (count (util/safe-re-find re-pattern prefix))))
+                    (count (gp-util/safe-re-find re-pattern prefix))))
             new-value (str (subs edit-content 0 pos)
                            (string/replace-first (subs edit-content pos)
                                                  (marker/marker-pattern format)
@@ -581,7 +583,7 @@
       (let [edit-content (gobj/get current-input "value")
             heading-pattern #"^#+\s+"
             new-value (cond
-                        (util/safe-re-find heading-pattern edit-content)
+                        (gp-util/safe-re-find heading-pattern edit-content)
                         (string/replace-first edit-content
                                               heading-pattern
                                               (str heading " "))

+ 218 - 126
src/main/frontend/components/block.cljs

@@ -53,6 +53,8 @@
             [frontend.util.clock :as clock]
             [frontend.util.drawer :as drawer]
             [frontend.util.property :as property]
+            [logseq.graph-parser.config :as gp-config]
+            [logseq.graph-parser.util :as gp-util]
             [goog.dom :as gdom]
             [goog.object :as gobj]
             [lambdaisland.glogi :as log]
@@ -281,7 +283,7 @@
         title (second (first label))]
     (ui/catch-error
      [:span.warning full_text]
-     (if (and (config/local-asset? href)
+     (if (and (gp-config/local-asset? href)
               (config/local-db? (state/get-current-repo)))
        (asset-link config title href metadata full_text)
        (let [href (cond
@@ -389,14 +391,19 @@
       :on-mouse-down
       (fn [e]
         (util/stop e)
-        (if (gobj/get e "shiftKey")
+        (cond
+          (gobj/get e "shiftKey")
           (when-let [page-entity (db/entity [:block/name redirect-page-name])]
             (state/sidebar-add-block!
              (state/get-current-repo)
              (:db/id page-entity)
-             :page
-             {:page page-entity}))
-          (state/pub-event! [:page/create redirect-page-name]))
+             :page))
+
+          (not= redirect-page-name page-name)
+          (route-handler/redirect-to-page! redirect-page-name)
+
+          :else
+          (state/pub-event! [:page/create page-name-in-block]))
         (when (and contents-page?
                    (util/mobile?)
                    (state/get-left-sidebar-open?))
@@ -482,8 +489,16 @@
                             page-name-in-block
                             page-name
                             redirect-page-name page-entity contents-page? children html-export? label)]
-      (if (and (not (util/mobile?)) (not preview?))
+      (cond
+        (:breadcrumb? config)
+        (or (:block/original-name page)
+            (:block/name page))
+
+        (and (not (util/mobile?))
+             (not preview?))
         (page-preview-trigger (assoc config :children inner) page-name)
+
+        :else
         inner))))
 
 (rum/defc asset-reference
@@ -640,14 +655,14 @@
 
 (declare block-content)
 (declare block-container)
-(declare block-parents)
+(declare breadcrumb)
 
 (rum/defc block-reference < rum/reactive
   db-mixins/query
   [config id label]
   (when (and
          (not (string/blank? id))
-         (util/uuid-string? id))
+         (gp-util/uuid-string? id))
     (let [block-id (uuid id)
           block (db/pull-block block-id)
           block-type (keyword (get-in block [:block/properties :ls-type]))
@@ -682,8 +697,7 @@
                     (state/sidebar-add-block!
                      (state/get-current-repo)
                      (:db/id block)
-                     :block-ref
-                     {:block block})
+                     :block-ref)
 
                     (match [block-type (util/electron?)]
                       ;; pdf annotation
@@ -698,7 +712,7 @@
                                         {:style {:width      735
                                                  :text-align "left"
                                                  :max-height 600}}
-                                        [(block-parents config repo block-id {:indent? true})
+                                        [(breadcrumb config repo block-id {:indent? true})
                                          (blocks-container
                                           (db/get-block-and-children repo block-id)
                                           (assoc config :id (str id) :preview? true))]])
@@ -763,7 +777,7 @@
        (and
         (nil? metadata-show)
         (or
-         (config/local-asset? s)
+         (gp-config/local-asset? s)
          (text/media-link? media-formats s)))
        (true? (boolean metadata-show))))
 
@@ -785,7 +799,7 @@
 
 (rum/defc audio-link
   [config url href _label metadata full_text]
-  (if (and (config/local-asset? href)
+  (if (and (gp-config/local-asset? href)
            (config/local-db? (state/get-current-repo)))
     (asset-link config nil href metadata full_text)
     (let [href (cond
@@ -919,7 +933,7 @@
                (= "Complex" protocol)
                (= (string/lower-case (:protocol path)) "id")
                (string? (:link path))
-               (util/uuid-string? (:link path))) ; org mode id
+               (gp-util/uuid-string? (:link path))) ; org mode id
           (let [id (uuid (:link path))
                 block (db/entity [:block/uuid id])]
             (if (:block/pre-block? block)
@@ -1035,7 +1049,7 @@
                        string/trim)]
         (when-let [id (and s
                            (let [s (string/trim s)]
-                             (and (util/uuid-string? s)
+                             (and (gp-util/uuid-string? s)
                                   (uuid s))))]
           (block-embed (assoc config :link-depth (inc link-depth)) id)))
 
@@ -1046,7 +1060,7 @@
   [_config arguments]
   (when-let [url (first arguments)]
     (let [Vimeo-regex #"^((?:https?:)?//)?((?:www).)?((?:player.vimeo.com|vimeo.com)?)((?:/video/)?)([\w-]+)(\S+)?$"]
-      (when-let [vimeo-id (nth (util/safe-re-find Vimeo-regex url) 5)]
+      (when-let [vimeo-id (nth (gp-util/safe-re-find Vimeo-regex url) 5)]
         (when-not (string/blank? vimeo-id)
           (let [width (min (- (util/get-width) 96)
                            560)
@@ -1067,7 +1081,7 @@
       (when-let [id (cond
                       (<= (count url) 15) url
                       :else
-                      (last (util/safe-re-find id-regex url)))]
+                      (last (gp-util/safe-re-find id-regex url)))]
         (when-not (string/blank? id)
           (let [width (min (- (util/get-width) 96)
                            560)
@@ -1197,7 +1211,7 @@
           (when-let [youtube-id (cond
                                   (== 11 (count url)) url
                                   :else
-                                  (nth (util/safe-re-find YouTube-regex url) 5))]
+                                  (nth (gp-util/safe-re-find YouTube-regex url) 5))]
             (when-not (string/blank? youtube-id)
               (youtube/youtube-video youtube-id)))))
 
@@ -1228,7 +1242,7 @@
           (when-let [id (cond
                           (<= (count url) 15) url
                           :else
-                          (last (util/safe-re-find id-regex url)))]
+                          (last (gp-util/safe-re-find id-regex url)))]
             (ui/tweet-embed id))))
 
       (= name "embed")
@@ -1382,8 +1396,7 @@
       (state/sidebar-add-block!
        (state/get-current-repo)
        (:db/id block)
-       :block
-       block)
+       :block)
       (util/stop e))
     (route-handler/redirect-to-page! uuid)))
 
@@ -1398,6 +1411,7 @@
 (rum/defc block-children < rum/reactive
   [config children collapsed?]
   (let [ref? (:ref? config)
+        query? (:custom-query? config)
         children (and (coll? children) (filter map? children))]
     (when (and (coll? children)
                (seq children)
@@ -1413,8 +1427,8 @@
                             (-> config
                                 (assoc :block/uuid (:block/uuid child))
                                 (dissoc :breadcrumb-show? :embed-parent))
-                             ref?
-                             (assoc :ref-child? true))]
+                             (or ref? query?)
+                             (assoc :ref-query-child? true))]
                 (rum/with-key (block-container config child)
                   (:block/uuid child)))))]]))))
 
@@ -1953,19 +1967,20 @@
         nil)]]))
 
 (rum/defc block-refs-count < rum/static
-  [block]
+  [block *hide-block-refs?]
   (let [block-refs-count (count (:block/_refs block))]
     (when (> block-refs-count 0)
       [:div
        [:a.open-block-ref-link.bg-base-2.text-sm.ml-2.fade-link
         {:title "Open block references"
          :style {:margin-top -1}
-         :on-click (fn []
-                     (state/sidebar-add-block!
-                      (state/get-current-repo)
-                      (:db/id block)
-                      :block-ref
-                      {:block block}))}
+         :on-click (fn [e]
+                     (if (gobj/get e "shiftKey")
+                       (state/sidebar-add-block!
+                        (state/get-current-repo)
+                        (:db/id block)
+                        :block-ref)
+                       (swap! *hide-block-refs? not)))}
         block-refs-count]])))
 
 (rum/defc block-left-menu < rum/reactive
@@ -1983,9 +1998,11 @@
     [:div.outdent (ui/icon "indent-decrease" {:style {:fontSize 16}})]
     [:div.more (ui/icon "dots-circle-horizontal" {:style {:fontSize 16}})]]])
 
-(rum/defc block-content-or-editor < rum/reactive
-  [config {:block/keys [uuid format] :as block} edit-input-id block-id heading-level edit?]
-  (let [editor-box (get config :editor-box)
+(rum/defcs block-content-or-editor < rum/reactive
+  (rum/local true :hide-block-refs?)
+  [state config {:block/keys [uuid format] :as block} edit-input-id block-id heading-level edit?]
+  (let [*hide-block-refs? (get state :hide-block-refs?)
+        editor-box (get config :editor-box)
         editor-id (str "editor-" edit-input-id)
         slide? (:slide? config)]
     (if (and edit? editor-box)
@@ -2004,25 +2021,31 @@
                                     (editor-handler/escape-editing select?))))}
                     edit-input-id
                     config))]
-      [:div.flex.flex-row.block-content-wrapper
-       [:div.flex-1.w-full {:style {:display (if (:slide? config) "block" "flex")}}
-        (ui/catch-error
-         (ui/block-error "Block Render Error:"
-                         {:content (:block/content block)
-                          :section-attrs
-                          {:on-click #(state/set-editing! edit-input-id (:block/content block) block "")}})
-         (block-content config block edit-input-id block-id slide?))]
-       [:div.flex.flex-row.items-center
-        (when (and (:embed? config)
-                   (:embed-parent config))
-          [:a.opacity-30.hover:opacity-100.svg-small.inline
-           {:on-mouse-down (fn [e]
-                             (util/stop e)
-                             (when-let [block (:embed-parent config)]
-                               (editor-handler/edit-block! block :max (:block/uuid block))))}
-           svg/edit])
-
-        (block-refs-count block)]])))
+      (let [refs-count (count (:block/_refs block))]
+        [:div.flex.flex-col.block-content-wrapper
+         [:div.flex.flex-row
+          [:div.flex-1.w-full {:style {:display (if (:slide? config) "block" "flex")}}
+           (ui/catch-error
+            (ui/block-error "Block Render Error:"
+                            {:content (:block/content block)
+                             :section-attrs
+                             {:on-click #(state/set-editing! edit-input-id (:block/content block) block "")}})
+            (block-content config block edit-input-id block-id slide?))]
+          [:div.flex.flex-row.items-center
+           (when (and (:embed? config)
+                      (:embed-parent config))
+             [:a.opacity-30.hover:opacity-100.svg-small.inline
+              {:on-mouse-down (fn [e]
+                                (util/stop e)
+                                (when-let [block (:embed-parent config)]
+                                  (editor-handler/edit-block! block :max (:block/uuid block))))}
+              svg/edit])
+
+           (block-refs-count block *hide-block-refs?)]]
+
+         (when (and (not @*hide-block-refs?) (> refs-count 0))
+           (let [refs-cp (state/get-component :block/linked-references)]
+             (refs-cp uuid)))]))))
 
 (defn non-dragging?
   [e]
@@ -2032,30 +2055,43 @@
        (not @*dragging?)))
 
 (rum/defc breadcrumb-fragment
-  [config block label]
-  (if (= block :page)                   ; page
-    (when label
-      (let [page (db/entity [:block/name (util/page-name-sanity-lc label)])]
-        (page-cp config page)))
-    [:a {:on-mouse-down
-         (fn [e]
-           (if (gobj/get e "shiftKey")
-             (do
-               (util/stop e)
-               (state/sidebar-add-block!
-                (state/get-current-repo)
-                (:db/id block)
-                :block-ref
-                {:block block}))
-             (route-handler/redirect-to-page! (:block/uuid block))))}
-     label]))
+  [config block label opts]
+  [:a {:on-mouse-down
+       (fn [e]
+         (cond
+           (gobj/get e "shiftKey")
+           (do
+             (util/stop e)
+             (state/sidebar-add-block!
+              (state/get-current-repo)
+              (:db/id block)
+              :block-ref))
+
+           (util/atom? (:navigating-block opts))
+           (do
+             (util/stop e)
+             (reset! (:navigating-block opts) (:block/uuid block)))
+
+           (some? (:sidebar-key config))
+           (do
+             (util/stop e)
+             (state/sidebar-replace-block!
+              (:sidebar-key config)
+              [(state/get-current-repo)
+               (:db/id block)
+               (if (:block/name block) :page :block)]))
+
+           :else
+           (route-handler/redirect-to-page! (:block/uuid block))))}
+   label])
 
 (rum/defc breadcrumb-separator [] [:span.mx-2.opacity-50 "➤"])
 
-(defn block-parents
-  [config repo block-id {:keys [show-page? indent? level-limit]
+(defn breadcrumb
+  [config repo block-id {:keys [show-page? indent? level-limit _navigating-block]
                          :or {show-page? true
-                              level-limit 3}}]
+                              level-limit 3}
+                         :as opts}]
   (let [parents (db/get-block-parents repo block-id (inc level-limit))
         page (db/get-block-page repo block-id)
         page-name (:block/name page)
@@ -2065,11 +2101,13 @@
                   (rest parents)
                   parents)
         more? (> (count parents) level-limit)
-        parents (if more? (take-last level-limit parents) parents)]
+        parents (if more? (take-last level-limit parents) parents)
+        config (assoc config :breadcrumb? true)]
     (when show?
       (let [page-name-props (when show-page?
-                              [:page
-                               (or page-original-name page-name)])
+                              [page
+                               (page-cp (dissoc config :breadcrumb? true) page)
+                               {:block/name (or page-original-name page-name)}])
             parents-props (doall
                            (for [{:block/keys [uuid name content] :as block} parents]
                              (when-not name ; not page
@@ -2088,10 +2126,10 @@
                             (filterv identity)
                             (map (fn [x] (if (vector? x)
                                            (let [[block label] x]
-                                             (breadcrumb-fragment config block label))
+                                             (breadcrumb-fragment config block label opts))
                                            [:span.opacity-70 "⋯"])))
                             (interpose (breadcrumb-separator)))]
-        [:div.block-parents.flex-row.flex-1
+        [:div.breadcrumb.block-parents.flex-row.flex-1
          {:class (when (seq breadcrumb)
                    (str (when-not (:search? config)
                           " my-2")
@@ -2213,37 +2251,25 @@
        (= (:id config)
           (str (:block/uuid block)))))
 
-(rum/defcs ^:large-vars/cleanup-todo block-container < rum/reactive
-  (rum/local false ::show-block-left-menu?)
+(rum/defc ^:large-vars/cleanup-todo block-container-inner < rum/reactive db-mixins/query
+  (rum/local false ::show-block-left-menu?) 
   (rum/local false ::show-block-right-menu?)
-  {:init (fn [state]
-           (let [[config block] (:rum/args state)
-                 block-id (:block/uuid block)]
-             (cond
-               (root-block? config block)
-               (state/set-collapsed-block! block-id false)
-
-               (:ref? config)
-               (state/set-collapsed-block! block-id
-                                           (editor-handler/block-default-collapsed? block config))
-
-               :else
-               nil)
-             (assoc state ::control-show? (atom false))))
-   :should-update (fn [old-state new-state]
-                    (let [compare-keys [:block/uuid :block/content :block/parent :block/collapsed?
-                                        :block/properties :block/left :block/children :block/_refs :ui/selected?]
-                          config-compare-keys [:show-cloze?]
-                          b1 (second (:rum/args old-state))
-                          b2 (second (:rum/args new-state))
-                          result (or
-                                  (not= (select-keys b1 compare-keys)
-                                        (select-keys b2 compare-keys))
-                                  (not= (select-keys (first (:rum/args old-state)) config-compare-keys)
-                                        (select-keys (first (:rum/args new-state)) config-compare-keys)))]
-                      (boolean result)))}
-  [state config {:block/keys [uuid children pre-block? top? refs heading-level level type format content] :as block}]
-  (let [repo (state/get-current-repo)
+  [state repo config block]
+  (let [ref? (:ref? config)
+        custom-query? (boolean (:custom-query? config))
+        ref-or-custom-query? (or ref? custom-query?)
+        *navigating-block (get state ::navigating-block)
+        navigating-block (rum/react *navigating-block)
+        navigated? (and (not= (:block/uuid block) navigating-block) navigating-block)
+        block (if navigated?
+                (let [block (db/pull [:block/uuid navigating-block])
+                      blocks (db/get-paginated-blocks repo (:db/id block)
+                                                      {:scoped-block-id (:db/id block)})
+                      tree (tree/blocks->vec-tree blocks (:block/uuid (first blocks)))]
+                  (first tree))
+                block)
+        {:block/keys [uuid children pre-block? top? refs heading-level level type format content]} block
+        config (if navigated? (assoc config :id (str navigating-block)) config)
         block (merge block (block/parse-title-and-body uuid format pre-block? content))
         blocks-container-id (:blocks-container-id config)
         config (update config :block merge block)
@@ -2253,28 +2279,29 @@
                  config)
         heading? (or (= type :heading) (and heading-level (<= heading-level 6)))
         *control-show? (get state ::control-show?)
-        ref? (:ref? config)
         db-collapsed? (util/collapsed? block)
         collapsed? (cond
-                     (or ref? (root-block? config block))
+                     (or ref-or-custom-query? (root-block? config block))
                      (state/sub-collapsed uuid)
 
                      :else
                      db-collapsed?)
+        children (if (and ref-or-custom-query?
+                          (not collapsed?))
+                   (map
+                     (fn [b] (assoc b
+                                    :block/level (inc (:block/level block))))
+                     (model/sub-block-direct-children repo uuid))
+                   children)
         breadcrumb-show? (:breadcrumb-show? config)
         *show-left-menu? (::show-block-left-menu? state)
         *show-right-menu? (::show-block-right-menu? state)
         slide? (boolean (:slide? config))
-        custom-query? (boolean (:custom-query? config))
         doc-mode? (:document/mode? config)
         embed? (:embed? config)
         reference? (:reference? config)
         block-id (str "ls-block-" blocks-container-id "-" uuid)
-        has-child? (boolean
-                    (and
-                     (not pre-block?)
-                     (coll? children)
-                     (seq children)))
+        has-child? (first (:block/_parent (db/entity (:db/id block))))
         attrs (on-drag-and-mouse-attrs block uuid top? block-id *move-to)
         children-refs (get-children-refs children)
         data-refs (build-refs-data-value children-refs)
@@ -2312,8 +2339,9 @@
        (assoc :data-query true))
 
      (when (and ref? breadcrumb-show?)
-       (block-parents config repo uuid {:show-page? false
-                                        :indent? true}))
+       (breadcrumb config repo uuid {:show-page? false
+                                     :indent? true
+                                     :navigating-block *navigating-block}))
 
      ;; only render this for the first block in each container
      (when top?
@@ -2344,6 +2372,48 @@
 
      (dnd-separator-wrapper block block-id slide? false false)]))
 
+(rum/defcs block-container < rum/reactive
+  {:init (fn [state]
+           (let [[config block] (:rum/args state)
+                 block-id (:block/uuid block)]
+             (cond
+               (root-block? config block)
+               (state/set-collapsed-block! block-id false)
+
+               (or (:ref? config) (:custom-query? config))
+               (state/set-collapsed-block! block-id
+                                           (editor-handler/block-default-collapsed? block config))
+
+               :else
+               nil)
+             (assoc state
+                    ::control-show? (atom false)
+                    ::navigating-block (atom (:block/uuid block)))))
+   :should-update (fn [old-state new-state]
+                    (let [compare-keys [:block/uuid :block/content :block/parent :block/collapsed?
+                                        :block/properties :block/left :block/children :block/_refs :ui/selected?]
+                          config-compare-keys [:show-cloze?]
+                          b1 (second (:rum/args old-state))
+                          b2 (second (:rum/args new-state))
+                          result (or
+                                  (not= (select-keys b1 compare-keys)
+                                        (select-keys b2 compare-keys))
+                                  (not= (select-keys (first (:rum/args old-state)) config-compare-keys)
+                                        (select-keys (first (:rum/args new-state)) config-compare-keys)))]
+                      (boolean result)))}
+  [state config block]
+  (let [repo (state/get-current-repo)
+        ref? (:ref? config)
+        custom-query? (boolean (:custom-query? config))
+        ref-or-custom-query? (or ref? custom-query?)]
+    (if (and ref-or-custom-query? (not (:ref-query-child? config)))
+      (ui/lazy-visible
+       nil
+       (fn []
+         (block-container-inner state repo config block))
+       nil)
+      (block-container-inner state repo config block))))
+
 (defn divide-lists
   [[f & l]]
   (loop [l l
@@ -2668,7 +2738,10 @@
   [config q]
   (ui/catch-error
    (ui/block-error "Query Error:" {:content (:query q)})
-   (custom-query* config q)))
+   (ui/lazy-visible
+    "loading ..."
+    (fn [] (custom-query* config q))
+    nil)))
 
 (defn admonition
   [config type result]
@@ -2786,7 +2859,7 @@
 
         ["Paragraph" l]
              ;; TODO: speedup
-        (if (util/safe-re-find #"\"Export_Snippet\" \"embed\"" (str l))
+        (if (gp-util/safe-re-find #"\"Export_Snippet\" \"embed\"" (str l))
           (->elem :div (map-inline config l))
           (->elem :div.is-paragraph (map-inline config l)))
 
@@ -2983,7 +3056,8 @@
 
 (rum/defcs blocks-container <
   {:init (fn [state]
-           (assoc state ::init-blocks-container-id (atom nil)))}
+           (assoc state
+                  ::init-blocks-container-id (atom nil)))}
   [state blocks config]
   (let [*init-blocks-container-id (::init-blocks-container-id state)
         blocks-container-id (if @*init-blocks-container-id
@@ -3001,6 +3075,29 @@
          {:class (when doc-mode? "document-mode")}
          (lazy-blocks config flat-blocks blocks->vec-tree)]))))
 
+(rum/defcs breadcrumb-with-container < rum/reactive
+  {:init (fn [state]
+           (assoc state ::navigating-block (atom (:block/uuid (ffirst (:rum/args state))))))}
+  [state blocks config]
+  (let [repo (state/get-current-repo)
+        *navigating-block (::navigating-block state)
+        navigating-block (rum/react *navigating-block)
+        block (first blocks)
+        navigated? (and (not= (:block/uuid block) navigating-block) navigating-block)
+        blocks (if navigated?
+                 (let [block (db/pull [:block/uuid navigating-block])]
+                   (db/get-paginated-blocks repo (:db/id block)
+                                           {:scoped-block-id (:db/id block)}))
+                 blocks)]
+    [:div
+     (when (:breadcrumb-show? config)
+       (breadcrumb config (state/get-current-repo) navigating-block
+                   {:show-page? false
+                    :navigating-block *navigating-block}))
+     (blocks-container blocks (assoc config
+                                     :breadcrumb-show? false
+                                     :navigating-block *navigating-block))]))
+
 ;; headers to hiccup
 (defn ->hiccup
   [blocks config option]
@@ -3024,12 +3121,7 @@
                (page-cp config page)
                (when alias? [:span.text-sm.font-medium.opacity-50 " Alias"])]
               (for [[_parent blocks] parent-blocks]
-                (let [block (first blocks)]
-                  [:div
-                   (when (:breadcrumb-show? config)
-                     (block-parents config (state/get-current-repo) (:block/uuid block)
-                                    {:show-page? false}))
-                   (blocks-container blocks (assoc config :breadcrumb-show? false))]))
+                (breadcrumb-with-container blocks config))
               {})])))]
 
      (and (:group-by-page? config)

+ 0 - 1
src/main/frontend/components/command_palette.css

@@ -16,7 +16,6 @@
     }
 
     .menu-link {
-      background-color: transparent;
       transition: none;
       border: none;
       border-radius: unset !important;

+ 7 - 8
src/main/frontend/components/content.cljs

@@ -21,6 +21,7 @@
             [frontend.state :as state]
             [frontend.ui :as ui]
             [frontend.util :as util]
+            [logseq.graph-parser.util :as gp-util]
             [frontend.util.url :as url-util]
             [goog.dom :as gdom]
             [goog.object :as gobj]
@@ -214,7 +215,7 @@
             :on-click (fn [_e]
                         (editor-handler/copy-block-ref! block-id #(util/format "{{embed ((%s))}}" %)))}
            "Copy block embed")
-          
+
           ;; TODO Logseq protocol mobile support
           (when (util/electron?)
             (ui/menu-link
@@ -295,12 +296,10 @@
       (ui/menu-link
        {:key "open-in-sidebar"
         :on-click (fn []
-                    (let [block (db/pull [:block/uuid block-ref-id])]
-                      (state/sidebar-add-block!
-                       (state/get-current-repo)
-                       block-ref-id
-                       :block-ref
-                       {:block block}))                    )}
+                    (state/sidebar-add-block!
+                     (state/get-current-repo)
+                     block-ref-id
+                     :block-ref))}
        "Open in sidebar")
       (ui/menu-link
        {:key "copy"
@@ -364,7 +363,7 @@
                            e
                            (custom-context-menu-content))
 
-                          (and block-id (util/uuid-string? block-id))
+                          (and block-id (gp-util/uuid-string? block-id))
                           (let [block (.closest target ".ls-block")]
                             (when block
                               (util/select-highlight! [block]))

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

@@ -23,6 +23,7 @@
             [frontend.util :as util]
             [frontend.util.cursor :as cursor]
             [frontend.util.keycode :as keycode]
+            [logseq.graph-parser.util :as gp-util]
             [goog.dom :as gdom]
             [promesa.core :as p]
             [rum.core :as rum]
@@ -112,9 +113,9 @@
               q (or
                  @editor-handler/*selected-text
                  (when (state/sub :editor/show-page-search-hashtag?)
-                   (util/safe-subs edit-content pos current-pos))
+                   (gp-util/safe-subs edit-content pos current-pos))
                  (when (> (count edit-content) current-pos)
-                   (util/safe-subs edit-content pos current-pos))
+                   (gp-util/safe-subs edit-content pos current-pos))
                  "")
               matched-pages (when-not (string/blank? q)
                               (editor-handler/get-matched-pages q))
@@ -621,7 +622,7 @@
                     config/mobile?)
                 (not (:review-cards? config)))
        (mobile-bar state id))
-     
+
      (ui/ls-textarea
       {:id                id
        :cacheMeasurements (editor-row-height-unchanged?) ;; check when content updated (as the content variable is binded)

+ 176 - 0
src/main/frontend/components/encryption.cljs

@@ -0,0 +1,176 @@
+(ns frontend.components.encryption
+  (:require [clojure.string :as string]
+            [frontend.context.i18n :refer [t]]
+            [frontend.encrypt :as e]
+            [frontend.handler.metadata :as metadata-handler]
+            [frontend.handler.notification :as notification]
+            [frontend.state :as state]
+            [frontend.ui :as ui]
+            [frontend.util :as util]
+            [promesa.core :as p]
+            [rum.core :as rum]))
+
+(rum/defcs encryption-dialog-inner <
+  (rum/local false ::reveal-secret-phrase?)
+  [state repo-url close-fn]
+  (let [reveal-secret-phrase? (get state ::reveal-secret-phrase?)
+        public-key (e/get-public-key repo-url)
+        private-key (e/get-secret-key repo-url)]
+    [:div
+     [:div.sm:flex.sm:items-start
+      [:div.mt-3.text-center.sm:mt-0.sm:text-left
+       [:h3#modal-headline.text-lg.leading-6.font-medium
+        "This graph is encrypted with " [:a {:href "https://age-encryption.org/" :target "_blank" :rel "noopener"} "age-encryption.org/v1"]]]]
+
+     [:div.mt-1
+      [:div.max-w-2xl.rounded-md.shadow-sm.sm:max-w-xl
+       [:div.cursor-pointer.block.w-full.rounded-sm.p-2
+        {:on-click (fn []
+                     (when (not @reveal-secret-phrase?)
+                       (reset! reveal-secret-phrase? true)))}
+        [:div.font-medium "Public Key:"]
+        [:div.font-mono.select-all.break-all public-key]
+        (if @reveal-secret-phrase?
+          [:div
+           [:div.mt-1.font-medium "Private Key:"]
+           [:div.font-mono.select-all.break-all private-key]]
+          [:div.underline "click to view the private key"])]]]
+
+     [:div.mt-5.sm:mt-4.sm:flex.sm:flex-row-reverse
+      [:span.mt-3.flex.w-full.rounded-md.shadow-sm.sm:mt-0.sm:w-auto
+       [:button.inline-flex.justify-center.w-full.rounded-md.border.border-gray-300.px-4.py-2.bg-white.text-base.leading-6.font-medium.text-gray-700.shadow-sm.hover:text-gray-500.focus:outline-none.focus:border-blue-300.focus:shadow-outline-blue.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
+        {:type "button"
+         :on-click close-fn}
+        (t :close)]]]]))
+
+(defn encryption-dialog
+  [repo-url]
+  (fn [close-fn]
+    (encryption-dialog-inner repo-url close-fn)))
+
+(rum/defcs input-password-inner <
+  (rum/local "" ::password)
+  (rum/local "" ::password-confirm)
+  [state repo-url close-fn]
+  (let [password (get state ::password)
+        password-confirm (get state ::password-confirm)]
+    [:div
+     [:div.sm:flex.sm:items-start
+      [:div.mt-3.text-center.sm:mt-0.sm:text-left
+       [:h3#modal-headline.text-lg.leading-6.font-medium.font-bold
+        "Enter a password"]]]
+
+     (ui/admonition
+      :warning
+      [:div.opacity-70
+       "Choose a strong and hard to guess password.\nIf you lose your password, all the data can't be decrypted!! Please make sure you remember the password you have set, or you can keep a secure backup of the password."])
+     [:input.form-input.block.w-full.sm:text-sm.sm:leading-5.my-2
+      {:type "password"
+       :placeholder "Password"
+       :auto-focus true
+       :on-change (fn [e]
+                    (reset! password (util/evalue e)))}]
+     [:input.form-input.block.w-full.sm:text-sm.sm:leading-5.my-2
+      {:type "password"
+       :placeholder "Re-enter the password"
+       :on-change (fn [e]
+                    (reset! password-confirm (util/evalue e)))}]
+
+     [:div.mt-5.sm:mt-4.sm:flex.sm:flex-row-reverse
+      [:span.flex.w-full.rounded-md.shadow-sm.sm:ml-3.sm:w-auto
+       [:button.inline-flex.justify-center.w-full.rounded-md.border.border-transparent.px-4.py-2.bg-indigo-600.text-base.leading-6.font-medium.text-white.shadow-sm.hover:bg-indigo-500.focus:outline-none.focus:border-indigo-700.focus:shadow-outline-indigo.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
+        {:type "button"
+         :on-click (fn []
+                     (let [value @password]
+                       (cond
+                         (string/blank? value)
+                         nil
+
+                         (not= @password @password-confirm)
+                         (notification/show! "The passwords are not matched." :error)
+
+                         :else
+                         (p/let [keys (e/generate-key-pair-and-save! repo-url)
+                                 db-encrypted-secret (e/encrypt-with-passphrase value keys)]
+                           (metadata-handler/set-db-encrypted-secret! db-encrypted-secret)
+                           (close-fn true)))))}
+        "Submit"]]]]))
+
+(defn input-password
+  [repo-url close-fn]
+  (fn [_close-fn]
+    (input-password-inner repo-url close-fn)))
+
+(rum/defcs encryption-setup-dialog-inner
+  [state repo-url close-fn]
+  [:div
+   [:div.sm:flex.sm:items-start
+    [:div.mt-3.text-center.sm:mt-0.sm:text-left
+     [:h3#modal-headline.text-lg.leading-6.font-medium
+      "Do you want to create an encrypted graph?"]]]
+
+   [:div.mt-5.sm:mt-4.sm:flex.sm:flex-row-reverse
+    [:span.flex.w-full.rounded-md.shadow-sm.sm:ml-3.sm:w-auto
+     [:button.inline-flex.justify-center.w-full.rounded-md.border.border-transparent.px-4.py-2.bg-indigo-600.text-base.leading-6.font-medium.text-white.shadow-sm.hover:bg-indigo-500.focus:outline-none.focus:border-indigo-700.focus:shadow-outline-indigo.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
+      {:type "button"
+       :on-click (fn []
+                   (state/set-modal! (input-password repo-url close-fn)))}
+      (t :yes)]]
+    [:span.mt-3.flex.w-full.rounded-md.shadow-sm.sm:mt-0.sm:w-auto
+     [:button.inline-flex.justify-center.w-full.rounded-md.border.border-gray-300.px-4.py-2.bg-white.text-base.leading-6.font-medium.text-gray-700.shadow-sm.hover:text-gray-500.focus:outline-none.focus:border-blue-300.focus:shadow-outline-blue.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
+      {:type "button"
+       :on-click (fn [] (close-fn false))}
+      (t :no)]]]])
+
+(defn encryption-setup-dialog
+  [repo-url close-fn]
+  (fn [close-modal-fn]
+    (let [close-fn (fn [encrypted?]
+                     (close-fn encrypted?)
+                     (close-modal-fn))]
+      (encryption-setup-dialog-inner repo-url close-fn))))
+
+(rum/defcs encryption-input-secret-inner <
+  (rum/local "" ::secret)
+  (rum/local false ::loading)
+  [state _repo-url db-encrypted-secret close-fn]
+  (let [secret (::secret state)
+        loading (::loading state)]
+    [:div
+     [:div.sm:flex.sm:items-start
+      [:div.mt-3.text-center.sm:mt-0.sm:text-left
+       [:h3#modal-headline.text-lg.leading-6.font-medium
+        "Enter your password"]]]
+
+     [:input.form-input.block.w-full.sm:text-sm.sm:leading-5.my-2
+      {:type "password"
+       :auto-focus true
+       :on-change (fn [e]
+                    (reset! secret (util/evalue e)))}]
+
+     [:div.mt-5.sm:mt-4.sm:flex.sm:flex-row-reverse
+      [:span.flex.w-full.rounded-md.shadow-sm.sm:ml-3.sm:w-auto
+       [:button.inline-flex.justify-center.w-full.rounded-md.border.border-transparent.px-4.py-2.bg-indigo-600.text-base.leading-6.font-medium.text-white.shadow-sm.hover:bg-indigo-500.focus:outline-none.focus:border-indigo-700.focus:shadow-outline-indigo.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
+        {:type "button"
+         :on-click (fn []
+                     (reset! loading true)
+                     (let [value @secret]
+                       (when-not (string/blank? value) ; TODO: length or other checks
+                         (let [repo (state/get-current-repo)]
+                           (p/do!
+                            (-> (e/decrypt-with-passphrase value db-encrypted-secret)
+                                (p/then (fn [keys]
+                                          (e/save-key-pair! repo keys)
+                                          (close-fn true)
+                                          (state/set-state! :encryption/graph-parsing? false)))
+                                (p/catch #(notification/show! "The password is not matched." :warning true))
+                                (p/finally #(reset! loading false))))))))}
+        (if @loading (ui/loading "Decrypting") "Decrypt")]]]]))
+
+(defn encryption-input-secret-dialog
+  [repo-url db-encrypted-secret close-fn]
+  (fn [close-modal-fn]
+    (let [close-fn (fn [encrypted?]
+                     (close-fn encrypted?)
+                     (close-modal-fn))]
+      (encryption-input-secret-inner repo-url db-encrypted-secret close-fn))))

+ 4 - 4
src/main/frontend/components/file.cljs

@@ -13,6 +13,7 @@
             [frontend.handler.export :as export-handler]
             [frontend.state :as state]
             [frontend.util :as util]
+            [logseq.graph-parser.config :as gp-config]
             [goog.object :as gobj]
             [reitit.frontend.easy :as rfe]
             [rum.core :as rum]))
@@ -43,8 +44,8 @@
            (let [file-id file]
              [:tr {:key file-id}
               [:td
-               (let [href (if (config/draw? file)
-                            (rfe/href :draw nil {:file (string/replace file (str config/default-draw-directory "/") "")})
+               (let [href (if (gp-config/draw? file)
+                            (rfe/href :draw nil {:file (string/replace file (str gp-config/default-draw-directory "/") "")})
                             (rfe/href :file {:path file-id}))]
                  [:a {:href href}
                   file])]
@@ -86,8 +87,7 @@
                                                 (state/sidebar-add-block!
                                                  (state/get-current-repo)
                                                  (:db/id page)
-                                                 :page
-                                                 {:page page}))
+                                                 :page))
                                               (util/stop e)))}
          original-name]])
 

+ 7 - 5
src/main/frontend/components/journal.cljs

@@ -21,7 +21,7 @@
   (when-let [page-e (db/pull [:block/name (util/page-name-sanity-lc page)])]
     (page/page-blocks-cp repo page-e {})))
 
-(rum/defc journal-cp < rum/reactive
+(rum/defc journal-cp-inner < rum/reactive
   [[title format]]
   (let [;; Don't edit the journal title
         page (string/lower-case title)
@@ -48,9 +48,7 @@
                         (state/sidebar-add-block!
                          (state/get-current-repo)
                          (:db/id page)
-                         :page
-                         {:page     page
-                          :journal? true}))
+                         :page))
                       (.preventDefault e)))}
        [:h1.title
         (util/capitalize-all title)]]
@@ -62,9 +60,13 @@
      (page/today-queries repo today? false)
 
      (rum/with-key
-       (reference/references title)
+       (reference/references title false)
        (str title "-refs"))]))
 
+(rum/defc journal-cp
+  [journal]
+  (ui/lazy-visible nil (fn [] (journal-cp-inner journal)) nil))
+
 (rum/defc journals < rum/reactive
   [latest-journals]
   [:div#journals

+ 0 - 8
src/main/frontend/components/onboarding/index.css

@@ -174,7 +174,6 @@ body[data-page=import] {
 
                 &.importer {
                     background-color: var(--ls-tertiary-background-color);
-                    padding-top: 80px;
                     position: relative;
 
                     > section {
@@ -200,7 +199,6 @@ body[data-page=import] {
                                 width: unset;
                                 height: 80px;
                                 flex: 1;
-                                margin: 0 15px;
                                 margin-bottom: 10px;
 
                                 > span {
@@ -274,8 +272,6 @@ body[data-page=import] {
                     }
 
                     &.importer {
-                        padding-top: 150px;
-
                         > section {
                             padding: 0;
 
@@ -289,10 +285,6 @@ body[data-page=import] {
                                 padding: unset;
                             }
 
-                            &.d {
-                                padding: 40px 150px;
-                            }
-
                             &.e {
                                 position: absolute;
                                 bottom: -50px;

+ 6 - 5
src/main/frontend/components/onboarding/setups.cljs

@@ -140,18 +140,19 @@
 
     (setups-container
      :importer
-     [:article.flex.flex-col.items-center.importer
+     [:article.flex.flex-col.items-center.importer.py-16.px-8
       [:section.c.text-center
        [:h1 "Do you already have notes that you want to import?"]
        [:h2 "If they are in a JSON or Markdown format Logseq can work with them."]]
       [:section.d.md:flex
-       [:label.action-input.flex.items-center
+       [:label.action-input.flex.items-center.mx-2.my-2
         {:disabled (or roam-importing? opml-importing?)}
         [:span.as-flex-center [:i (svg/roam-research 28)]]
-        [:span.flex.flex-col
+        [:div.flex.flex-col
          (if roam-importing?
            (ui/loading "Importing ...")
-           [[:strong "RoamResearch"]
+           [
+            [:strong "RoamResearch"]
             [:small "Import a JSON Export of your Roam graph"]])]
         [:input.absolute.hidden
          {:id        "import-roam"
@@ -172,7 +173,7 @@
                            (notification/show! "Please choose a JSON file."
                                                :error))))}]]
 
-       [:label.action-input.flex.items-center
+       [:label.action-input.flex.items-center.mx-2.my-2
         {:disabled (or roam-importing? opml-importing?)}
         [:span.as-flex-center (ui/icon "sitemap" {:style {:fontSize "26px"}})]
         [:span.flex.flex-col

+ 17 - 11
src/main/frontend/components/page.cljs

@@ -34,6 +34,7 @@
             [reitit.frontend.easy :as rfe]
             [medley.core :as medley]
             [rum.core :as rum]
+            [logseq.graph-parser.util :as gp-util]
             [frontend.mobile.util :as mobile-util]))
 
 (defn- get-page-name
@@ -108,13 +109,20 @@
      [:a.add-button-link.block
       (ui/icon "circle-plus")]]]])
 
-(rum/defc page-blocks-cp < rum/reactive
-  db-mixins/query
+(rum/defc page-blocks-cp < rum/reactive db-mixins/query
+  {:will-mount (fn [state]
+                 (let [page-e (second (:rum/args state))
+                       page-name (:block/name page-e)]
+                   (when (and (db/journal-page? page-name)
+                              (>= (date/journal-title->int page-name)
+                                  (date/journal-title->int (date/today))))
+                     (state/pub-event! [:journal/insert-template page-name])))
+                 state)}
   [repo page-e {:keys [sidebar?] :as config}]
   (when page-e
     (let [page-name (or (:block/name page-e)
                         (str (:block/uuid page-e)))
-          block? (util/uuid-string? page-name)
+          block? (gp-util/uuid-string? page-name)
           block-id (and block? (uuid page-name))
           page-blocks (get-blocks repo page-name block-id)]
       (if (empty? page-blocks)
@@ -262,8 +270,7 @@
                                         (state/sidebar-add-block!
                                          repo
                                          (:db/id page)
-                                         :page
-                                         {:page page}))
+                                         :page))
                                       (when (and (not hls-file?) (not fmt-journal?))
                                         (reset! *edit? true))))}
          [:h1.title.ls-page-title {:data-ref page-name}
@@ -310,7 +317,7 @@
     (let [current-repo (state/sub :git/current-repo)
           repo (or repo current-repo)
           page-name (util/page-name-sanity-lc path-page-name)
-          block? (util/uuid-string? page-name)
+          block? (gp-util/uuid-string? page-name)
           block-id (and block? (uuid page-name))
           format (let [page (if block-id
                               (:block/name (:block/page (db/entity [:block/uuid block-id])))
@@ -370,7 +377,7 @@
            (let [config {:id "block-parent"
                          :block? true}]
              [:div.mb-4
-              (block/block-parents config repo block-id {:level-limit 3})]))
+              (block/breadcrumb config repo block-id {:level-limit 3})]))
 
          ;; blocks
          (let [page (if block?
@@ -387,7 +394,7 @@
        ;; referenced blocks
        [:div {:key "page-references"}
         (rum/with-key
-          (reference/references route-page-name)
+          (reference/references route-page-name sidebar?)
           (str route-page-name "-refs"))]
 
        (when-not block?
@@ -633,7 +640,7 @@
               (date/today))
         theme (:ui/theme @state/state)
         dark? (= theme "dark")
-        graph (if (util/uuid-string? page)
+        graph (if (gp-util/uuid-string? page)
                 (graph-handler/build-block-graph (uuid page) theme)
                 (graph-handler/build-page-graph page theme))]
     (when (seq (:nodes graph))
@@ -945,8 +952,7 @@
                                               (state/sidebar-add-block!
                                                repo
                                                (:db/id page)
-                                               :page
-                                               {:page (:block/name page)}))))
+                                               :page))))
                               :href     (rfe/href :page {:name (:block/name page)})}
                           (block/page-cp {} page)]]
 

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

@@ -14,6 +14,7 @@
             [frontend.handler.shell :as shell]
             [frontend.handler.plugin :as plugin-handler]
             [frontend.mobile.util :as mobile-util]
+            [logseq.graph-parser.util :as gp-util]
             [electron.ipc :as ipc]
             [frontend.config :as config]
             [frontend.handler.user :as user-handler]
@@ -63,7 +64,7 @@
           repo (state/sub :git/current-repo)
           page (db/entity repo [:block/name page-name])
           page-original-name (:block/original-name page)
-          block? (and page (util/uuid-string? page-name))
+          block? (and page (gp-util/uuid-string? page-name))
           contents? (= page-name "contents")
           properties (:block/properties page)
           public? (true? (:public properties))
@@ -90,8 +91,7 @@
                                    (state/sidebar-add-block!
                                     repo
                                     (:db/id page)
-                                    :page-presentation
-                                    {:page page}))}})
+                                    :page-presentation))}})
 
           ;; TODO: In the future, we'd like to extract file-related actions
           ;; (such as open-in-finder & open-with-default-app) into a sub-menu of
@@ -102,7 +102,7 @@
               :options {:on-click #(js/window.apis.showItemInFolder file-path)}}
              {:title   (t :page/open-with-default-app)
               :options {:on-click #(js/window.apis.openPath file-path)}}])
-          
+
           (when (util/electron?)
             {:title   (t :page/copy-page-url)
               :options {:on-click #(util/copy-to-clipboard!

+ 88 - 9
src/main/frontend/components/plugins.cljs

@@ -8,6 +8,7 @@
             [frontend.search :as search]
             [frontend.util :as util]
             [frontend.mixins :as mixins]
+            [electron.ipc :as ipc]
             [promesa.core :as p]
             [frontend.components.svg :as svg]
             [frontend.components.plugins-settings :as plugins-settings]
@@ -334,10 +335,69 @@
     :intent "logseq"
     :target "_blank"))
 
+(rum/defc user-proxy-settings-panel
+  [{:keys [protocol] :as agent-opts}]
+  (let [[opts set-opts!] (rum/use-state agent-opts)
+        [testing? set-testing?!] (rum/use-state false)
+        *test-input (rum/create-ref)
+        disabled? (string/blank? (:protocol opts))]
+    [:div.cp__settings-network-proxy-panel
+     [:h1.mb-2.text-2xl.font-bold (t :settings-page/network-proxy)]
+     [:div.p-2
+      [:p [:label [:strong (t :type)]
+           (ui/select [{:label "Disabled" :value "" :selected disabled?}
+                       {:label "http" :value "http" :selected (= protocol "http")}
+                       {:label "https" :value "https" :selected (= protocol "https")}
+                       {:label "socks5" :value "socks5" :selected (= protocol "socks5")}]
+                      #(set-opts!
+                         (assoc opts :protocol (if (= "disabled" (util/safe-lower-case %)) nil %))) nil)]]
+      [:p.flex
+       [:label.pr-4 [:strong (t :host)]
+        [:input.form-input.is-small
+         {:value     (:host opts) :disabled disabled?
+          :on-change #(set-opts!
+                        (assoc opts :host (util/trim-safe (util/evalue %))))}]]
+
+       [:label [:strong (t :port)]
+        [:input.form-input.is-small
+         {:value (:port opts) :type "number" :disabled disabled?
+          :on-change #(set-opts!
+                        (assoc opts :port (util/trim-safe (util/evalue %))))}]]]
+
+      [:hr]
+      [:p.flex.items-center.space-x-2
+       [:span.w-60
+        [:input.form-input.is-small
+         {:ref *test-input
+          :placeholder "http://"
+          :on-change #(set-opts!
+                        (assoc opts :test (util/trim-safe (util/evalue %))))
+          :value (:test opts)}]]
+
+       (ui/button (if testing? (ui/loading "Testing") "Test URL")
+         :intent "logseq" :large? false
+         :style {:margin-top 0 :padding "5px 15px"}
+         :on-click #(let [val (util/trim-safe (.-value (rum/deref *test-input)))]
+                      (when (and (not testing?) (not (string/blank? val)))
+                        (set-testing?! true)
+                        (-> (p/let [_ (ipc/ipc :setHttpsAgent opts)
+                                    _ (ipc/ipc :testProxyUrl val)])
+                          (p/catch (fn [e] (notification/show! (str e) :error)))
+                          (p/finally (fn [] (set-testing?! false)))))
+                      ))]
+
+      [:p.pt-2
+       (ui/button (t :save)
+         :on-click (fn []
+                     (p/let [_ (ipc/ipc :setHttpsAgent opts)]
+                       (state/set-state! [:electron/user-cfgs :settings/agent] opts)
+                       (state/close-sub-modal! :https-proxy-panel))))]]]))
+
 (rum/defc ^:large-vars/cleanup-todo panel-control-tabs < rum/static
   [search-key *search-key category *category
    sort-by *sort-by filter-by *filter-by
-   selected-unpacked-pkg market? develop-mode? reload-market-fn]
+   selected-unpacked-pkg market? develop-mode?
+   reload-market-fn agent-opts]
 
   (let [*search-ref (rum/create-ref)]
     [:div.mb-2.flex.justify-between.control-tabs.relative
@@ -357,6 +417,16 @@
          (unpacked-plugin-loader selected-unpacked-pkg)])]
 
      [:div.flex.items-center.r
+      ;; extra info
+      (let [{:keys [protocol host port]} agent-opts]
+        (when (every? not-empty [protocol host port])
+          (ui/button
+            [:span.flex.items-center.text-indigo-500
+             (ui/icon "world-download") (str protocol "://" host ":" port)]
+            :small? true
+            :intent "link"
+            :on-click #(state/pub-event! [:go/proxy-settings agent-opts]))))
+
       ;; search
       (panel-tab-search search-key *search-key *search-ref)
 
@@ -440,6 +510,10 @@
                     :options {:on-click #(reload-market-fn)}}]
                   [{:title   [:span (ui/icon "rotate-clockwise") (t :plugin/check-all-updates)]
                     :options {:on-click #(plugin-handler/check-enabled-for-updates (not= :plugins category))}}])
+
+                [{:title   [:span (ui/icon "world") (t :settings-page/network-proxy)]
+                  :options {:on-click #(state/pub-event! [:go/proxy-settings agent-opts])}}]
+
                 (when (state/developer-mode?)
                   [{:hr true}
                    {:title   [:span (ui/icon "file-code") "Open Preferences"]
@@ -481,6 +555,7 @@
         installing (state/sub :plugin/installing)
         online? (state/sub :network/online?)
         develop-mode? (state/sub :ui/developer-mode?)
+        agent-opts (state/sub [:electron/user-cfgs :settings/agent])
         *search-key (::search-key state)
         *category (::category state)
         *sort-by (::sort-by state)
@@ -526,20 +601,18 @@
        @*search-key *search-key
        @*category *category
        @*sort-by *sort-by @*filter-by *filter-by
-       nil true develop-mode? (::reload state))
+       nil true develop-mode? (::reload state)
+       agent-opts)
 
      (cond
        (not online?)
-       [:p.flex.justify-center.pt-20.opacity-50
-        (svg/offline 30)]
+       [:p.flex.justify-center.pt-20.opacity-50 (svg/offline 30)]
 
        @*fetching
-       [:p.flex.justify-center.pt-20
-        svg/loading]
+       [:p.flex.justify-center.pt-20 svg/loading]
 
        @*error
-       [:p.flex.justify-center.pt-20.opacity-50
-        "Remote error: " (.-message @*error)]
+       [:p.flex.justify-center.pt-20.opacity-50 "Remote error: " (.-message @*error)]
 
        :else
        [:div.cp__plugins-marketplace-cnt
@@ -568,6 +641,7 @@
         develop-mode? (state/sub :ui/developer-mode?)
         selected-unpacked-pkg (state/sub :plugin/selected-unpacked-pkg)
         coming-updates (state/sub :plugin/updates-coming)
+        agent-opts (state/sub [:electron/user-cfgs :settings/agent])
         *filter-by (::filter-by state)
         *sort-by (::sort-by state)
         *search-key (::search-key state)
@@ -611,7 +685,8 @@
        @*sort-by *sort-by
        @*filter-by *filter-by
        selected-unpacked-pkg
-       false develop-mode? nil)
+       false develop-mode? nil
+       agent-opts)
 
      [:div.cp__plugins-item-lists.grid-cols-1.md:grid-cols-2.lg:grid-cols-3
       (for [item sorted-plugins]
@@ -752,6 +827,10 @@
         market? (= active :marketplace)
         *el-ref (rum/create-ref)]
 
+    (rum/use-effect!
+      #(state/load-app-user-cfgs)
+      [])
+
     [:div.cp__plugins-page
      {:ref       *el-ref
       :tab-index "-1"}

+ 1 - 2
src/main/frontend/components/query_table.cljs

@@ -159,8 +159,7 @@
                                                            (state/sidebar-add-block!
                                                             (state/get-current-repo)
                                                             (:db/id item)
-                                                            :block-ref
-                                                            {:block item})))}
+                                                            :block-ref)))}
                    (when value
                      (if (= :element (first value))
                        (second value)

+ 26 - 22
src/main/frontend/components/reference.cljs

@@ -12,6 +12,7 @@
             [frontend.state :as state]
             [frontend.ui :as ui]
             [frontend.util :as util]
+            [logseq.graph-parser.util :as gp-util]
             [medley.core :as medley]
             [rum.core :as rum]))
 
@@ -48,23 +49,21 @@
   (fn [close-fn]
     (filter-dialog-inner filters-atom close-fn references page-name)))
 
-(defn- block-with-ref-level
-  [block level]
-  (if (:block/children block)
-    (-> (update block :block/children
-                (fn [blocks]
-                  (map (fn [block]
-                         (let [level (inc level)
-                               block (assoc block :ref/level level)]
-                           (block-with-ref-level block level))) blocks)))
-        (assoc :ref/level level))
-    (assoc block :ref/level level)))
-
-(defn- blocks-with-ref-level
-  [page-blocks]
-  (map (fn [[page blocks]]
-         [page (map #(block-with-ref-level % 1) blocks)])
-    page-blocks))
+(rum/defc block-linked-references < rum/reactive db-mixins/query
+  [block-id]
+  (let [refed-blocks-ids (model-db/get-referenced-blocks-ids (str block-id))]
+    (when (seq refed-blocks-ids)
+      (let [ref-blocks (db/get-block-referenced-blocks block-id)
+            ref-hiccup (block/->hiccup ref-blocks
+                                       {:id (str block-id)
+                                        :ref? true
+                                        :breadcrumb-show? true
+                                        :group-by-page? true
+                                        :editor-box editor/box}
+                                       {})]
+        [:div.references-blocks
+         (content/content block-id
+                          {:hiccup ref-hiccup})]))))
 
 (rum/defcs references* < rum/reactive db-mixins/query
   (rum/local nil ::n-ref)
@@ -84,7 +83,7 @@
           default-collapsed? (>= (count refed-blocks-ids) threshold)
           filters-atom (get state ::filters)
           filter-state (rum/react filters-atom)
-          block? (util/uuid-string? page-name)
+          block? (gp-util/uuid-string? page-name)
           block-id (and block? (uuid page-name))
           page-name (string/lower-case page-name)
           journal? (date/valid-journal-title? (string/capitalize page-name))
@@ -146,8 +145,7 @@
                      filters (when (seq filter-state)
                                (->> (group-by second filter-state)
                                     (medley/map-vals #(map first %))))
-                     filtered-ref-blocks (->> (block-handler/filter-blocks repo ref-blocks filters true)
-                                              blocks-with-ref-level)
+                     filtered-ref-blocks (block-handler/filter-blocks repo ref-blocks filters true)
                      n-ref (apply +
                              (for [[_ rfs] filtered-ref-blocks]
                                (count rfs)))]
@@ -168,10 +166,16 @@
               :title-trigger? true}))]]))))
 
 (rum/defc references
-  [page-name]
+  [page-name sidebar?]
   (ui/catch-error
    (ui/component-error "Linked References: Unexpected error")
-   (references* page-name)))
+   (ui/lazy-visible
+    (if (or sidebar? (gp-util/uuid-string? page-name))
+      nil
+      "loading references...")
+    (fn []
+      (references* page-name))
+    nil)))
 
 (rum/defcs unlinked-references-aux
   < rum/reactive db-mixins/query

+ 9 - 19
src/main/frontend/components/repo.cljs

@@ -17,7 +17,9 @@
             [frontend.text :as text]
             [promesa.core :as p]
             [electron.ipc :as ipc]
-            [goog.object :as gobj]))
+            [goog.object :as gobj]
+            [frontend.components.encryption :as encryption]
+            [frontend.encrypt :as e]))
 
 (rum/defc add-repo
   [args]
@@ -60,6 +62,11 @@
                     :href url}
                 (db/get-repo-path url)])
              [:div.controls
+              (when (e/encrypted-db? url)
+                [:a.control {:title "Show encryption information about this graph"
+                             :on-click (fn []
+                                         (state/set-modal! (encryption/encryption-dialog url)))}
+                 "🔐"])
               [:a.text-gray-400.ml-4.font-medium.text-sm
                {:title "No worries, unlink this graph will clear its cache only, it does not remove your files on the disk."
                 :on-click (fn []
@@ -116,24 +123,7 @@
                       :options (cond->
                                 {:on-click
                                  (fn []
-                                   (if @*multiple-windows?
-                                     (state/pub-event!
-                                      [:modal/show
-                                       [:div
-                                        [:p (t :re-index-multiple-windows-warning)]]])
-                                     (state/pub-event!
-                                      [:modal/show
-                                       [:div {:style {:max-width 700}}
-                                        [:p (t :re-index-discard-unsaved-changes-warning)]
-                                        (ui/button
-                                         (t :yes)
-                                         :autoFocus "on"
-                                         :large? true
-                                         :on-click (fn []
-                                                     (state/close-modal!)
-                                                     (repo-handler/re-index!
-                                                      nfs-handler/rebuild-index!
-                                                      page-handler/create-today-journal!)))]])))})}
+                                   (state/pub-event! [:graph/ask-for-re-index *multiple-windows?]))})}
         new-window-link (when (util/electron?)
                           {:title        (t :open-new-window)
                            :options {:on-click #(state/pub-event! [:graph/open-new-window nil])}})]

+ 30 - 40
src/main/frontend/components/right_sidebar.cljs

@@ -48,8 +48,18 @@
    (when-let [contents (db/entity [:block/name "contents"])]
      (page/contents-page contents))])
 
+(defn- block-with-breadcrumb
+  [repo block idx sidebar-key ref?]
+  (let [block-id (:block/uuid block)]
+    [[:div.mt-1 {:class (if ref? "ml-8" "ml-1")}
+      (block/breadcrumb {:id     "block-parent"
+                         :block? true
+                         :sidebar-key sidebar-key} repo block-id {})]
+     [:div.ml-2
+      (block-cp repo idx block)]]))
+
 (defn build-sidebar-item
-  [repo idx db-id block-type block-data t]
+  [repo idx db-id block-type]
   (case block-type
     :contents
     [(t :right-side-bar/contents)
@@ -64,33 +74,21 @@
 
     :block-ref
     #_:clj-kondo/ignore
-    (when-let [block (db/entity repo [:block/uuid (:block/uuid (:block block-data))])]
-      [(t :right-side-bar/block-ref)
-       (let [block (:block block-data)
-             block-id (:block/uuid block)
-             format (:block/format block)]
-         [[:div.ml-2.mt-1
-           (block/block-parents {:id     "block-parent"
-                                 :block? true} repo block-id {})]
-          [:div.ml-2
-           (block-cp repo idx block)]])])
+    (let [lookup (if (integer? db-id) db-id [:block/uuid db-id])]
+      (when-let [block (db/entity repo lookup)]
+       [(t :right-side-bar/block-ref)
+        (block-with-breadcrumb repo block idx [repo db-id block-type] true)]))
 
     :block
     #_:clj-kondo/ignore
-    (when-let [block (db/entity repo [:block/uuid (:block/uuid block-data)])]
-      (let [block-id (:block/uuid block-data)
-            format (:block/format block-data)]
-        [(block/block-parents {:id     "block-parent"
-                               :block? true} repo block-id {})
-         [:div.ml-2
-          (block-cp repo idx block-data)]]))
+    (let [lookup (if (integer? db-id) db-id [:block/uuid db-id])]
+      (when-let [block (db/entity repo lookup)]
+        (block-with-breadcrumb repo block idx [repo db-id block-type] false)))
 
     :page
-    (let [page-name (or (:block/name block-data)
-                        db-id)
-          page-name (if (integer? db-id)
-                      (:block/name (db/entity db-id))
-                      page-name)]
+    (when-let [page-name (if (integer? db-id)
+                           (:block/name (db/entity db-id))
+                           db-id)]
       [[:a.page-title {:href     (rfe/href :page {:name page-name})
                        :on-click (fn [e]
                                    (when (gobj/get e "shiftKey")
@@ -100,7 +98,7 @@
         (page-cp repo page-name)]])
 
     :page-presentation
-    (let [page-name (get-in block-data [:page :block/name])]
+    (let [page-name (:block/name (db/entity db-id))]
       [[:a {:href (rfe/href :page {:name page-name})}
         (db-model/get-page-original-name page-name)]
        [:div.ml-2.slide.mt-2
@@ -120,15 +118,8 @@
     svg/close]))
 
 (rum/defc sidebar-item < rum/reactive
-  [repo idx db-id block-type block-data t]
-
-  (let [item
-        (if (= :page block-type)
-          (let [lookup-ref (if (number? db-id) db-id [:block/name (util/page-name-sanity-lc db-id)])
-                page (db/query-entity-in-component lookup-ref)]
-            (when (seq page)
-              (build-sidebar-item repo idx db-id block-type page t)))
-          (build-sidebar-item repo idx db-id block-type block-data t))]
+  [repo idx db-id block-type]
+  (let [item (build-sidebar-item repo idx db-id block-type)]
     (when item
       (let [collapse? (state/sub [:ui/sidebar-collapsed-blocks db-id])]
         [:div.sidebar-item.content.color-level.px-4.shadow-lg
@@ -210,7 +201,7 @@
        [:div.cp__right-sidebar-settings.hide-scrollbar {:key "right-sidebar-settings"}
         [:div.ml-4.text-sm
          [:a.cp__right-sidebar-settings-btn {:on-click (fn [_e]
-                                                         (state/sidebar-add-block! repo "contents" :contents nil))}
+                                                         (state/sidebar-add-block! repo "contents" :contents))}
           (t :right-side-bar/contents)]]
 
         [:div.ml-4.text-sm
@@ -218,14 +209,13 @@
                                                          (when-let [page (get-current-page)]
                                                            (state/sidebar-add-block!
                                                             repo
-                                                            (str "page-graph-" page)
-                                                            :page-graph
-                                                            page)))}
+                                                            page
+                                                            :page-graph)))}
           (t :right-side-bar/page)]]
 
         [:div.ml-4.text-sm
          [:a.cp__right-sidebar-settings-btn {:on-click (fn [_e]
-                                                         (state/sidebar-add-block! repo "help" :help nil))}
+                                                         (state/sidebar-add-block! repo "help" :help))}
           (t :right-side-bar/help)]]]
 
        [:div.flex.align-items {:style {:z-index 999
@@ -234,9 +224,9 @@
 
       [:.sidebar-item-list.flex-1.scrollbar-spacing
        (if @*anim-finished?
-         (for [[idx [repo db-id block-type block-data]] (medley/indexed blocks)]
+         (for [[idx [repo db-id block-type]] (medley/indexed blocks)]
            (rum/with-key
-             (sidebar-item repo idx db-id block-type block-data t)
+             (sidebar-item repo idx db-id block-type)
              (str "sidebar-block-" idx)))
          [:div.p-4
           [:span.font-medium.opacity-50 "Loading ..."]])]]]))

+ 11 - 13
src/main/frontend/components/search.cljs

@@ -20,6 +20,7 @@
             [clojure.string :as string]
             [frontend.context.i18n :refer [t]]
             [frontend.date :as date]
+            [logseq.graph-parser.util :as gp-util]
             [reitit.frontend.easy :as rfe]
             [frontend.modules.shortcut.core :as shortcut]))
 
@@ -32,7 +33,7 @@
             lc-content (util/search-normalize content)
             lc-q (util/search-normalize q)]
         (if (and (string/includes? lc-content lc-q)
-                 (not (util/safe-re-find #" " q)))
+                 (not (gp-util/safe-re-find #" " q)))
           (let [i (string/index-of lc-content lc-q)
                 [before after] [(subs content 0 i) (subs content (+ i (count q)))]]
             [:div
@@ -74,12 +75,12 @@
     [:div
      (when (not= search-mode :page)
        [:div {:class "mb-1" :key "parents"}
-        (block/block-parents {:id "block-search-block-parent"
-                              :block? true
-                              :search? true}
-                             repo
-                             (clojure.core/uuid uuid)
-                             {:indent? false})])
+        (block/breadcrumb {:id "block-search-block-parent"
+                           :block? true
+                           :search? true}
+                          repo
+                          (clojure.core/uuid uuid)
+                          {:indent? false})])
      [:div {:class "font-medium" :key "content"}
       (highlight-exact-query content q)]]))
 
@@ -157,8 +158,7 @@
         (state/sidebar-add-block!
          repo
          (:db/id page)
-         :page
-         {:page page})))
+         :page)))
 
     :block
     (let [block-uuid (uuid (:block/uuid data))
@@ -166,8 +166,7 @@
       (state/sidebar-add-block!
        repo
        (:db/id block)
-       :block
-       block))
+       :block))
 
     :new-page
     (page-handler/create! search-q)
@@ -325,8 +324,7 @@
                                  (state/sidebar-add-block!
                                   (state/get-current-repo)
                                   (:db/id page)
-                                  :page
-                                  {:page page}))))
+                                  :page))))
 
                             nil))
        :item-render (fn [{:keys [type data]}]

+ 21 - 36
src/main/frontend/components/settings.cljs

@@ -1,6 +1,7 @@
 (ns frontend.components.settings
   (:require [clojure.string :as string]
             [frontend.components.svg :as svg]
+            [frontend.components.plugins :as plugins]
             [frontend.config :as config]
             [frontend.context.i18n :refer [t]]
             [frontend.storage :as storage]
@@ -134,7 +135,6 @@
                   (mobile-util/is-native-platform?))
       [:div.text-sm desc])]])
 
-
 (defn edit-config-edn []
   (row-with-button-action
     {:left-label   (t :settings-page/custom-configuration)
@@ -390,6 +390,23 @@
 ;;             (let [value (not enable-block-timestamps?)]
 ;;               (config-handler/set-config! :feature/enable-block-timestamps? value)))))
 
+(defn encryption-row [t enable-encryption?]
+  (toggle "enable_encryption"
+          (t :settings-page/enable-encryption)
+          enable-encryption?
+          #(let [value (not enable-encryption?)]
+             (config-handler/set-config! :feature/enable-encryption? value)
+             (when value
+               (state/close-modal!)
+               (js/setTimeout (fn [] (state/pub-event! [:graph/ask-for-re-index (atom false)]))
+                              100)))
+          [:p.text-sm.opacity-50 "⚠️ This feature is experimental! "
+           [:span "You can use "]
+           [:a {:href "https://github.com/kanru/logseq-encrypt-ui"
+                :target "_blank"}
+            "logseq-encrypt-ui"]
+           [:span " to decrypt your graph."]]))
+
 (rum/defc keyboard-shortcuts-row [t]
   (row-with-button-action
     {:left-label   (t :settings-page/customize-shortcuts)
@@ -468,40 +485,6 @@
                     :on-click #(js/logseq.api.relaunch)
                     :small? true :intent "logseq")]])]))
 
-(rum/defc user-proxy-settings-panel
-  [{:keys [protocol] :as agent-opts}]
-  (let [[opts set-opts!] (rum/use-state agent-opts)
-        disabled? (string/blank? (:protocol opts))]
-    [:div.cp__settings-network-proxy-panel
-     [:h1.mb-2.text-2xl.font-bold (t :settings-page/network-proxy)]
-     [:div.p-2
-      [:p [:label [:strong (t :type)]
-           (ui/select [{:label "Disabled" :value "" :selected disabled?}
-                       {:label "http" :value "http" :selected (= protocol "http")}
-                       {:label "https" :value "https" :selected (= protocol "https")}
-                       {:label "socks5" :value "socks5" :selected (= protocol "socks5")}]
-                      #(set-opts!
-                        (assoc opts :protocol (if (= "disabled" (util/safe-lower-case %)) nil %))) nil)]]
-      [:p.flex
-       [:label.pr-4 [:strong (t :host)]
-        [:input.form-input.is-small
-         {:value     (:host opts) :disabled disabled?
-          :on-change #(set-opts!
-                       (assoc opts :host (util/trim-safe (util/evalue %))))}]]
-
-       [:label [:strong (t :port)]
-        [:input.form-input.is-small
-         {:value (:port opts) :type "number" :disabled disabled?
-          :on-change #(set-opts!
-                       (assoc opts :port (util/trim-safe (util/evalue %))))}]]]
-
-      [:p.pt-2
-       (ui/button (t :save)
-        :on-click (fn []
-                    (p/let [_ (ipc/ipc :setHttpsAgent opts)]
-                      (state/set-state! [:electron/user-cfgs :settings/agent] opts)
-                      (state/close-sub-modal! :https-proxy-panel))))]]]))
-
 (rum/defc user-proxy-settings
   [{:keys [protocol host port] :as agent-opts}]
   (ui/button [:span
@@ -509,7 +492,7 @@
                 [:strong.pr-1 e])
               (ui/icon "edit")]
              :on-click #(state/set-sub-modal!
-                         (fn [_] (user-proxy-settings-panel agent-opts))
+                         (fn [_] (plugins/user-proxy-settings-panel agent-opts))
                          {:id :https-proxy-panel :center? true})))
 
 (defn plugin-system-switcher-row []
@@ -544,6 +527,7 @@
         preferred-workflow (state/get-preferred-workflow)
         enable-timetracking? (state/enable-timetracking?)
         enable-journals? (state/enable-journals? current-repo)
+        enable-encryption? (state/enable-encryption? current-repo)
         enable-all-pages-public? (state/all-pages-public?)
         logical-outdenting? (state/logical-outdenting?)
         enable-tooltip? (state/enable-tooltip?)
@@ -578,6 +562,7 @@
             :on-key-press  (fn [e]
                              (when (= "Enter" (util/ekey e))
                                (update-home-page e)))}]]]])
+     (encryption-row t enable-encryption?)
      (enable-all-pages-public-row t enable-all-pages-public?)
      (zotero-settings-row t)
      (auto-push-row t current-repo enable-git-auto-push?)]))

+ 4 - 5
src/main/frontend/components/sidebar.cljs

@@ -74,8 +74,7 @@
                            (state/sidebar-add-block!
                             (state/get-current-repo)
                             (:db/id page-entity)
-                            :page
-                            {:page page-entity}))
+                            :page))
                          (route-handler/redirect-to-page! name))))}
      [:span.page-icon icon]
      (pdf-assets/fix-local-asset-filename original-name)]))
@@ -360,7 +359,7 @@
                          [db-id block-type] (if (= page "contents")
                                               ["contents" :contents]
                                               [page :page])]
-                     (state/sidebar-add-block! current-repo db-id block-type nil)))
+                     (state/sidebar-add-block! current-repo db-id block-type)))
                  (reset! sidebar-inited? true))))
            state)}
   []
@@ -400,7 +399,7 @@
 
          ;; FIXME: why will this happen?
          :else
-         [:div "bingo"])])))
+         [:div])])))
 
 (rum/defc custom-context-menu < rum/reactive
   []
@@ -435,7 +434,7 @@
     [:div.cp__sidebar-help-btn
      {:title (t :help-shortcut-title)
       :on-click (fn []
-                  (state/sidebar-add-block! (state/get-current-repo) "help" :help nil))}
+                  (state/sidebar-add-block! (state/get-current-repo) "help" :help))}
      "?"]))
 
 (defn- hide-context-menu-and-clear-selection

+ 13 - 13
src/main/frontend/config.cljs

@@ -4,6 +4,7 @@
             [frontend.state :as state]
             [frontend.util :as util]
             [shadow.resource :as rc]
+            [logseq.graph-parser.util :as gp-util]
             [frontend.mobile.util :as mobile-util]))
 
 (goog-define DEV-RELEASE false)
@@ -22,11 +23,12 @@
 ;; (goog-define LOGIN-URL
 ;;              "https://logseq.auth.us-east-1.amazoncognito.com/login?client_id=7ns5v1pu8nrbs04rvdg67u4a7c&response_type=code&scope=email+openid+phone&redirect_uri=logseq%3A%2F%2Fauth-callback")
 ;; (goog-define API-DOMAIN "api-prod.logseq.com")
+;; (goog-define WS-URL "wss://b2rp13onu2.execute-api.us-east-1.amazonaws.com/production?graphuuid=%s")
 
 ;; dev env
 (goog-define FILE-SYNC-PROD? false)
 (goog-define LOGIN-URL
-             "https://logseq-test.auth.us-east-2.amazoncognito.com/login?client_id=4fi79en9aurclkb92e25hmu9ts&response_type=code&scope=email+openid+phone&redirect_uri=logseq%3A%2F%2Fauth-callback")
+             "https://logseq-test2.auth.us-east-2.amazoncognito.com/login?client_id=3ji1a0059hspovjq5fhed3uil8&response_type=code&scope=email+openid+phone&redirect_uri=logseq%3A%2F%2Fauth-callback")
 (goog-define API-DOMAIN "api.logseq.com")
 (goog-define WS-URL "wss://og96xf1si7.execute-api.us-east-2.amazonaws.com/production?graphuuid=%s")
 
@@ -105,7 +107,7 @@
 
 (def mobile?
   (when-not util/node-test?
-    (util/safe-re-find #"Mobi" js/navigator.userAgent)))
+    (gp-util/safe-re-find #"Mobi" js/navigator.userAgent)))
 
 ;; TODO: protocol design for future formats support
 
@@ -254,7 +256,6 @@
 
 (defonce default-journals-directory "journals")
 (defonce default-pages-directory "pages")
-(defonce default-draw-directory "draws")
 
 (defn get-pages-directory
   []
@@ -264,10 +265,6 @@
   []
   (or (state/get-journals-directory) default-journals-directory))
 
-(defn draw?
-  [path]
-  (util/starts-with? path default-draw-directory))
-
 (defonce local-repo "local")
 
 (defn demo-graph?
@@ -276,11 +273,11 @@
   ([graph]
    (= graph local-repo)))
 
-(defonce local-assets-dir "assets")
 (defonce recycle-dir ".recycle")
 (def config-file "config.edn")
 (def custom-css-file "custom.css")
 (def custom-js-file "custom.js")
+(def metadata-file "metadata.edn")
 (def pages-metadata-file "pages-metadata.edn")
 
 (def config-default-content (rc/inline "config.edn"))
@@ -299,10 +296,6 @@
   (and (string? s)
        (string/starts-with? s local-db-prefix)))
 
-(defn local-asset?
-  [s]
-  (util/safe-re-find (re-pattern (str "^[./]*" local-assets-dir)) s))
-
 (defn get-local-asset-absolute-path
   [s]
   (str "/" (string/replace s #"^[./]*" "")))
@@ -368,7 +361,7 @@
 
                  :else
                  relative-path)]
-      (util/path-normalize path))))
+      (gp-util/path-normalize path))))
 
 (defn get-config-path
   ([]
@@ -377,6 +370,13 @@
    (when repo
      (get-file-path repo (str app-name "/" config-file)))))
 
+(defn get-metadata-path
+  ([]
+   (get-metadata-path (state/get-current-repo)))
+  ([repo]
+   (when repo
+     (get-file-path repo (str app-name "/" metadata-file)))))
+
 (defn get-pages-metadata-path
   ([]
    (get-pages-metadata-path (state/get-current-repo)))

+ 2 - 1
src/main/frontend/db/conn.cljs

@@ -8,6 +8,7 @@
             [frontend.state :as state]
             [frontend.config :as config]
             [frontend.text :as text]
+            [logseq.graph-parser.util :as gp-util]
             [datascript.core :as d]))
 
 (defonce conns (atom {}))
@@ -69,7 +70,7 @@
 
 (defn me-tx
   [_db {:keys [name email avatar]}]
-  (util/remove-nils {:me/name name
+  (gp-util/remove-nils {:me/name name
                      :me/email email
                      :me/avatar avatar}))
 

+ 1 - 1
src/main/frontend/db/default.cljs

@@ -1,4 +1,4 @@
-(ns frontend.db.default
+(ns ^:nbb-compatible frontend.db.default
   (:require [clojure.string :as string]))
 
 (defonce built-in-pages-names

+ 39 - 72
src/main/frontend/db/model.cljs

@@ -15,6 +15,7 @@
             [frontend.format :as format]
             [frontend.state :as state]
             [frontend.util :as util :refer [react]]
+            [logseq.graph-parser.util :as gp-util]
             [frontend.db.rules :refer [rules]]
             [frontend.db.default :as default-db]
             [frontend.util.drawer :as drawer]))
@@ -61,7 +62,7 @@
    (db-utils/transact! (state/get-current-repo) tx-data))
   ([repo-url tx-data]
    (when-not config/publishing?
-     (let [tx-data (->> (util/remove-nils tx-data)
+     (let [tx-data (->> (gp-util/remove-nils tx-data)
                         (remove nil?)
                         (map #(dissoc % :file/handle :file/type)))]
        (when (seq tx-data)
@@ -814,6 +815,20 @@
           block-uuid)
         (sort-by-left (db-utils/entity [:block/uuid block-uuid])))))
 
+(defn sub-block-direct-children
+  "Doesn't include nested children."
+  [repo block-uuid]
+  (when-let [db (conn/get-db repo)]
+    (-> (react/q repo [:frontend.db.react/block-direct-children block-uuid] {}
+          '[:find [(pull ?b [*]) ...]
+            :in $ ?parent-id
+            :where
+            [?parent :block/uuid ?parent-id]
+            [?b :block/parent ?parent]]
+          block-uuid)
+        react
+        (sort-by-left (db-utils/entity [:block/uuid block-uuid])))))
+
 (defn get-block-children
   "Including nested children."
   [repo block-uuid]
@@ -898,7 +913,7 @@
 
 (defn get-page
   [page-name]
-  (if (util/uuid-string? page-name)
+  (if (gp-util/uuid-string? page-name)
     (db-utils/entity [:block/uuid (uuid page-name)])
     (db-utils/entity [:block/name (util/page-name-sanity-lc page-name)])))
 
@@ -969,16 +984,6 @@
         (reverse)
         (take n))))))
 
-(defn journal-day-exists?
-  [graph day]
-  (d/q
-    '[:find ?p .
-      :in $ ?day
-      :where
-      [?p :block/journal-day ?day]]
-    (conn/get-db graph)
-    day))
-
 ;; get pages that this page referenced
 (defn get-page-referenced-pages
   [repo page]
@@ -1081,38 +1086,20 @@
        (let [page-id (:db/id (db-utils/entity [:block/name (util/safe-page-name-sanity-lc page)]))
              pages (page-alias-set repo page)
              aliases (set/difference pages #{page-id})
-             query-result (if (seq aliases)
-                            (let [rules '[[(find-blocks ?block ?ref-page ?pages ?alias ?aliases)
-                                           [?block :block/page ?alias]
-                                           [(contains? ?aliases ?alias)]]
-                                          [(find-blocks ?block ?ref-page ?pages ?alias ?aliases)
-                                           [?block :block/refs ?ref-page]
-                                           [(contains? ?pages ?ref-page)]]]]
-                              (react/q repo
-                                       [:frontend.db.react/page<-blocks-or-block<-blocks page-id]
-                                       {}
-                                       '[:find [(pull ?block ?block-attrs) ...]
-                                         :in $ % ?pages ?aliases ?block-attrs
-                                         :where
-                                         (find-blocks ?block ?ref-page ?pages ?alias ?aliases)]
-                                       rules
-                                       pages
-                                       aliases
-                                       block-attrs))
-                            (react/q repo
-                                     [:frontend.db.react/page<-blocks-or-block<-blocks page-id]
-                                     {:use-cache? false}
-                                     '[:find [(pull ?ref-block ?block-attrs) ...]
-                                       :in $ ?page ?block-attrs
-                                       :where
-                                       [?ref-block :block/refs ?page]]
-                                     page-id
-                                     block-attrs))
+             query-result (react/q repo
+                            [:frontend.db.react/page<-blocks-or-block<-blocks page-id]
+                            {}
+                            '[:find [(pull ?block ?block-attrs) ...]
+                              :in $ [?ref-page ...] ?block-attrs
+                              :where
+                              [?block :block/refs ?ref-page]]
+                            pages
+                            (butlast block-attrs))
              result (->> query-result
                          react
-                         (sort-by-left-recursive)
                          (remove (fn [block]
                                    (= page-id (:db/id (:block/page block)))))
+                         (sort-by-left-recursive)
                          db-utils/group-by-page
                          (map (fn [[k blocks]]
                                 (let [k (if (contains? aliases (:db/id k))
@@ -1128,35 +1115,14 @@
   ([repo page]
    (when repo
      (when-let [db (conn/get-db repo)]
-       (let [page-id (:db/id (db-utils/entity [:block/name (util/safe-page-name-sanity-lc page)]))
-             pages (page-alias-set repo page)
-             aliases (set/difference pages #{page-id})
-             query-result (if (seq aliases)
-                            (let [rules '[[(find-blocks ?block ?ref-page ?pages ?alias ?aliases)
-                                           [?block :block/page ?alias]
-                                           [(contains? ?aliases ?alias)]]
-                                          [(find-blocks ?block ?ref-page ?pages ?alias ?aliases)
-                                           [?block :block/refs ?ref-page]
-                                           [(contains? ?pages ?ref-page)]]]]
-                              (d/q
-                                '[:find ?block
-                                  :in $ % ?pages ?aliases ?block-attrs
-                                  :where
-                                  (find-blocks ?block ?ref-page ?pages ?alias ?aliases)]
-                                db
-                                rules
-                                pages
-                                aliases
-                                block-attrs))
-                            (d/q
-                              '[:find ?ref-block
-                                :in $ ?page ?block-attrs
-                                :where
-                                [?ref-block :block/refs ?page]]
-                              db
-                              page-id
-                              block-attrs))]
-         query-result)))))
+       (let [pages (page-alias-set repo page)]
+         (d/q
+           '[:find ?block
+             :in $ [?ref-page ...]
+             :where
+             [?block :block/refs ?ref-page]]
+           db
+           pages))))))
 
 (defn get-date-scheduled-or-deadlines
   [journal-title]
@@ -1256,7 +1222,7 @@
 
 (defn get-referenced-blocks-ids
   [page-name-or-block-uuid]
-  (if (util/uuid-string? (str page-name-or-block-uuid))
+  (if (gp-util/uuid-string? (str page-name-or-block-uuid))
     (let [id (uuid page-name-or-block-uuid)]
       (get-block-referenced-blocks-ids id))
     (get-page-referenced-blocks-ids page-name-or-block-uuid)))
@@ -1355,7 +1321,7 @@
   [name]
   (when (string? name)
     (->> (d/q
-           '[:find (pull ?b [*])
+           '[:find [(pull ?b [*]) ...]
              :in $ ?name
              :where
              [?b :block/properties ?p]
@@ -1363,7 +1329,8 @@
              [(= ?t ?name)]]
            (conn/get-db)
            name)
-         ffirst)))
+         (sort-by :block/name)
+         (first))))
 
 (defonce blocks-count-cache (atom nil))
 

+ 3 - 2
src/main/frontend/db/query_dsl.cljs

@@ -13,7 +13,8 @@
             [frontend.db.rules :as rules]
             [frontend.template :as template]
             [frontend.text :as text]
-            [frontend.util :as util]))
+            [frontend.util :as util]
+            [logseq.graph-parser.util :as gp-util]))
 
 
 ;; Query fields:
@@ -447,7 +448,7 @@ Some bindings in this fn:
                                                  (remove string/blank?)
                                                  (map (fn [x]
                                                         (if (or (contains? #{"+" "-"} (first x))
-                                                                (and (util/safe-re-find #"\d" (first x))
+                                                                (and (gp-util/safe-re-find #"\d" (first x))
                                                                      (some #(string/ends-with? x %) ["y" "m" "d" "h" "min"])))
                                                           (keyword (name x))
                                                           x)))

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

@@ -12,6 +12,7 @@
             [frontend.state :as state]
             [frontend.text :as text]
             [frontend.util :as util]
+            [logseq.graph-parser.util :as gp-util]
             [lambdaisland.glogi :as log]))
 
 (defn resolve-input
@@ -31,12 +32,12 @@
     ;; This sometimes runs when there isn't a current page e.g. :home route
     (some-> (state/get-current-page) string/lower-case)
     (and (keyword? input)
-         (util/safe-re-find #"^\d+d(-before)?$" (name input)))
+         (gp-util/safe-re-find #"^\d+d(-before)?$" (name input)))
     (let [input (name input)
           days (util/parse-int (subs input 0 (dec (count input))))]
       (date->int (t/minus (t/today) (t/days days))))
     (and (keyword? input)
-         (util/safe-re-find #"^\d+d(-after)?$" (name input)))
+         (gp-util/safe-re-find #"^\d+d(-after)?$" (name input)))
     (let [input (name input)
           days (util/parse-int (subs input 0 (dec (count input))))]
       (date->int (t/plus (t/today) (t/days days))))

+ 16 - 6
src/main/frontend/db/react.cljs

@@ -10,6 +10,7 @@
             [frontend.db.utils :as db-utils]
             [frontend.state :as state]
             [frontend.util :as util :refer [react]]
+            [logseq.graph-parser.util :as gp-util]
             [cljs.spec.alpha :as s]
             [clojure.core.async :as async]))
 
@@ -23,6 +24,8 @@
 ;; ::block-and-children
 ;; get block&children react-query
 (s/def ::block-and-children (s/tuple #(= ::block-and-children %) uuid?))
+
+(s/def ::block-direct-children (s/tuple #(= ::block-direct-children %) uuid?))
 ;; ::journals
 ;; get journal-list react-query
 (s/def ::journals (s/tuple #(= ::journals %)))
@@ -47,6 +50,7 @@
 (s/def ::react-query-keys (s/or :block ::block
                                 :page-blocks ::page-blocks
                                 :block-and-children ::block-and-children
+                                :block-direct-children ::block-direct-children
                                 :journals ::journals
                                 :page->pages ::page->pages
                                 :page<-pages ::page<-pages
@@ -212,9 +216,8 @@
 
 (defn get-affected-queries-keys
   "Get affected queries through transaction datoms."
-  [{:keys [tx-data]}]
+  [{:keys [tx-data db-before]}]
   {:post [(s/valid? ::affected-keys %)]}
-
   (let [blocks (->> (filter (fn [datom] (contains? #{:block/left :block/parent :block/page} (:a datom))) tx-data)
                     (map :v)
                     (distinct))
@@ -227,7 +230,7 @@
         affected-keys (concat
                        (mapcat
                         (fn [block-id]
-                          (let [block-id (if (and (string? block-id) (util/uuid-string? block-id))
+                          (let [block-id (if (and (string? block-id) (gp-util/uuid-string? block-id))
                                            [:block/uuid block-id]
                                            block-id)]
                             (when-let [block (db-utils/entity block-id)]
@@ -236,8 +239,15 @@
                                              (:db/id (:block/page block)))
                                     blocks [[::block (:block/uuid block)]]
                                     others (when page-id
-                                             [[::page-blocks page-id]
-                                              [::page->pages page-id]])]
+                                             (let [db-after-parent-uuid (:block/uuid (:block/parent block))
+                                                   db-before-parent-uuid (:block/uuid (:block/parent (d/entity db-before
+                                                                                                               [:block/uuid (:block/uuid block)])))]
+                                               [[::page-blocks page-id]
+                                                [::page->pages page-id]
+                                                [::block-direct-children db-after-parent-uuid]
+                                                (when (and db-before-parent-uuid
+                                                           (not= db-before-parent-uuid db-after-parent-uuid))
+                                                  [::block-direct-children db-before-parent-uuid])]))]
                                 (concat blocks others)))))
                         blocks)
 
@@ -250,7 +260,7 @@
                                 (if (:block/name entity) ; page
                                   [::page-blocks ref]
                                   [::page-blocks (:db/id (:block/page entity))])))
-                            refs))
+                         refs))
         others (->>
                 (keys @query-state)
                 (filter (fn [ks]

+ 1 - 1
src/main/frontend/db/rules.cljc

@@ -1,4 +1,4 @@
-(ns ^:bb-compatible frontend.db.rules)
+(ns ^:bb-compatible ^:nbb-compatible frontend.db.rules)
 
 (def rules
   ;; rule "parent" is optimized for child node -> parent node nesting queries

+ 3 - 2
src/main/frontend/db/utils.cljs

@@ -7,7 +7,8 @@
             [frontend.util :as util]
             [frontend.date :as date]
             [frontend.db.conn :as conn]
-            [frontend.config :as config]))
+            [frontend.config :as config]
+            [logseq.graph-parser.util :as gp-util]))
 
 ;; transit serialization
 
@@ -88,7 +89,7 @@
    (transact! repo-url tx-data nil))
   ([repo-url tx-data tx-meta]
    (when-not config/publishing?
-     (let [tx-data (->> (util/remove-nils tx-data)
+     (let [tx-data (->> (gp-util/remove-nils tx-data)
                         (remove nil?))]
        (when (seq tx-data)
          (when-let [conn (conn/get-db repo-url false)]

+ 3 - 1
src/main/frontend/db_schema.cljs

@@ -1,4 +1,4 @@
-(ns frontend.db-schema)
+(ns ^:nbb-compatible frontend.db-schema)
 
 (defonce version 1)
 (defonce ast-version 1)
@@ -8,6 +8,8 @@
    :ast/version     {}
    :db/type         {}
    :db/ident        {:db/unique :db.unique/identity}
+   :db/encrypted?    {}
+   :db/encryption-keys {}
 
    :recent/pages {}
 

+ 11 - 2
src/main/frontend/dicts.cljc

@@ -62,7 +62,7 @@
         :right-side-bar/contents "Contents"
         :right-side-bar/favorites "Favorites"
         :right-side-bar/page-graph "Page graph"
-        :right-side-bar/block-ref "Block reference"
+        :right-side-bar/block-ref "Block references"
         :right-side-bar/graph-view "Graph view"
         :right-side-bar/all-pages "All pages"
         :right-side-bar/flashcards "Flashcards"
@@ -162,6 +162,7 @@
         :settings-page/enable-tooltip "Tooltips"
         :settings-page/enable-journals "Journals"
         :settings-page/enable-all-pages-public "All pages public when publishing"
+        :settings-page/enable-encryption "Encryption"
         :settings-page/customize-shortcuts "Keyboard shortcuts"
         :settings-page/shortcut-settings "Customize shortcuts"
         :settings-page/home-default-page "Set the default home page"
@@ -585,6 +586,7 @@
         :settings-page/customize-shortcuts "Tastaturbefehle"
         :settings-page/disable-sentry "Nutzungs- und Diagnostik-Daten an Logseq senden"
         :settings-page/edit-custom-css "custom.css bearbeiten"
+        :settings-page/enable-encryption "Verschlüsselung"
         :settings-page/enable-shortcut-tooltip "Tooltips für Verknüpfungen aktivieren"
         :settings-page/enable-tooltip "Tooltips"
         :settings-page/shortcut-settings "Verknüpfungen anpassen"
@@ -895,6 +897,7 @@
            :settings-page/enable-tooltip "开启提示框"
            :settings-page/enable-journals "开启日记"
            :settings-page/enable-all-pages-public "发布所有页面"
+           :settings-page/enable-encryption "激活加密功能"
            :settings-page/customize-shortcuts "自定义快捷键"
            :settings-page/shortcut-settings "快捷键设置"
            :settings-page/home-default-page "设置首页默认页面"
@@ -1476,6 +1479,7 @@
         :settings-page/enable-timetracking "Habilitar rastreo de tiempo"
         :settings-page/enable-tooltip "Habilitar descripción emergente"
         :settings-page/enable-journals "Habilitar diarios"
+        :settings-page/enable-encryption "Habilitar función de cifrado"
         :settings-page/home-default-page "Establecer página de inicio"
         :settings-page/enable-block-time "Habilitar marcas temporales de bloque"
         :settings-page/clear-cache "Limpiar caché"
@@ -1706,6 +1710,7 @@
            :settings-page/preferred-workflow "Foretrukket arbeidslflyt"
            :settings-page/enable-shortcut-tooltip "Skru på tooltip for snarveier"
            :settings-page/enable-timetracking "Aktiver tidssporing"
+           :settings-page/enable-encryption "Aktiver kryptering"
            :settings-page/enable-tooltip "Aktiver verktøytips"
            :settings-page/enable-journals "Aktiver dagbøker"
            :settings-page/enable-all-pages-public "Aktiver alle sider som offentlige ved publisering"
@@ -1994,6 +1999,7 @@
            :settings-page/enable-tooltip "Ativar dicas de ferramentas"
            :settings-page/enable-journals "Ativar diários"
            :settings-page/enable-all-pages-public "Ativar todas as páginas públicas ao publicar"
+           :settings-page/enable-encryption "Ativar funcionalidade de criptografia"
            :settings-page/customize-shortcuts "Atalhos de teclado"
            :settings-page/shortcut-settings "Personalizar atalhos"
            :settings-page/home-default-page "Definir a página inicial padrão"
@@ -2315,6 +2321,7 @@
            :settings-page/enable-tooltip "Dicas de atalhos"
            :settings-page/enable-journals "Diários"
            :settings-page/enable-all-pages-public "Todas as páginas públicas ao publicar"
+           :settings-page/enable-encryption "Encriptação"
            :settings-page/customize-shortcuts "Atalhos de teclado"
            :settings-page/shortcut-settings "Personalizar atalhos"
            :settings-page/home-default-page "Definir a página inicial predefinida"
@@ -2610,6 +2617,7 @@
         :settings-page/enable-shortcut-tooltip "Всплывающие подсказки горячих клавиш"
         :settings-page/enable-journals "Включить Дневники"
         :settings-page/enable-all-pages-public "Все страницы общедоступны при публикации"
+        :settings-page/enable-encryption "Функции шифрования"
         :settings-page/customize-shortcuts "Горячие клавиши"
         :settings-page/shortcut-settings "Настроить горячие клавиши"
         :settings-page/home-default-page "Установить домашнюю страницу по умолчанию"
@@ -2897,6 +2905,7 @@
         :settings-page/enable-tooltip "ツールチップ"
         :settings-page/enable-journals "日誌"
         :settings-page/enable-all-pages-public "パブリッシュ時には全てのページを公開する"
+        :settings-page/enable-encryption "暗号化"
         :settings-page/customize-shortcuts "キーボードショートカット"
         :settings-page/shortcut-settings "ショートカットをカスタマイズ"
         :settings-page/home-default-page "デフォルトのホームページを設定"
@@ -3059,7 +3068,7 @@
 
         :file-sync/other-user-graph "現在のローカルグラフは他のユーザーのリモートグラフにバインドされています。同期を開始できません。"
         :file-sync/graph-deleted "現在のリモートグラフが削除されました"}
-   
+
    :it {:tutorial/text #?(:cljs (rc/inline "tutorial-en.md")
                           :default "tutorial-en.md")
         :tutorial/dummy-notes #?(:cljs (rc/inline "dummy-notes-en.md")

+ 2 - 1
src/main/frontend/diff.cljs

@@ -5,6 +5,7 @@
             [lambdaisland.glogi :as log]
             [cljs-bean.core :as bean]
             [frontend.util :as util]
+            [logseq.graph-parser.util :as gp-util]
             [frontend.text :as text]))
 
 (defn diff
@@ -54,7 +55,7 @@
           (+ pos 2)
 
           (contains? inline-special-chars (util/nth-safe markup pos))
-          (let [matched (->> (take-while inline-special-chars (util/safe-subs markup pos))
+          (let [matched (->> (take-while inline-special-chars (gp-util/safe-subs markup pos))
                              (apply str))
                 matched? (and current-line (string/includes? current-line (string/reverse matched)))]
             (if matched?

+ 104 - 0
src/main/frontend/encrypt.cljs

@@ -0,0 +1,104 @@
+(ns frontend.encrypt
+  (:require [frontend.utf8 :as utf8]
+            [frontend.db.utils :as db-utils]
+            [frontend.db :as db]
+            [promesa.core :as p]
+            [frontend.state :as state]
+            [clojure.string :as str]
+            [cljs.reader :as reader]
+            [shadow.loader :as loader]
+            [lambdaisland.glogi :as log]))
+
+(defonce age-pem-header-line "-----BEGIN AGE ENCRYPTED FILE-----")
+(defonce age-version-line "age-encryption.org/v1")
+
+(defn content-encrypted?
+  [content]
+  (when content
+    (or (str/starts-with? content age-pem-header-line)
+        (str/starts-with? content age-version-line))))
+
+(defn encrypted-db?
+  [repo-url]
+  (db-utils/get-key-value repo-url :db/encrypted?))
+
+(defn get-key-pair
+  [repo-url]
+  (db-utils/get-key-value repo-url :db/encryption-keys))
+
+(defn save-key-pair!
+  [repo-url keys]
+  (let [keys (if (string? keys) (reader/read-string keys) keys)]
+    (db/set-key-value repo-url :db/encryption-keys keys)
+    (db/set-key-value repo-url :db/encrypted? true)))
+
+(defn generate-key-pair
+  []
+  (p/let [_ (loader/load :age-encryption)
+          lazy-keygen (resolve 'frontend.extensions.age-encryption/keygen)
+          js-keys (lazy-keygen)]
+    (array-seq js-keys)))
+
+(defn generate-key-pair-and-save!
+  [repo-url]
+  (when-not (get-key-pair repo-url)
+    (p/let [keys (generate-key-pair)]
+      (save-key-pair! repo-url keys)
+      (pr-str keys))))
+
+(defn get-public-key
+  [repo-url]
+  (second (get-key-pair repo-url)))
+
+(defn get-secret-key
+  [repo-url]
+  (first (get-key-pair repo-url)))
+
+(defn encrypt
+  ([content]
+   (encrypt (state/get-current-repo) content))
+  ([repo-url content]
+   (cond
+     (encrypted-db? repo-url)
+     (p/let [_ (loader/load :age-encryption)
+             lazy-encrypt-with-x25519 (resolve 'frontend.extensions.age-encryption/encrypt-with-x25519)
+             content (utf8/encode content)
+             public-key (get-public-key repo-url)
+             encrypted (lazy-encrypt-with-x25519 public-key content true)]
+       (utf8/decode encrypted))
+     :else
+     (p/resolved content))))
+
+(defn decrypt
+  ([content]
+   (decrypt (state/get-current-repo) content))
+  ([repo-url content]
+   (cond
+     (and (encrypted-db? repo-url)
+          (content-encrypted? content))
+     (let [content (utf8/encode content)]
+       (if-let [secret-key (get-secret-key repo-url)]
+         (p/let [_ (loader/load :age-encryption)
+                 lazy-decrypt-with-x25519 (resolve 'frontend.extensions.age-encryption/decrypt-with-x25519)
+                 decrypted (lazy-decrypt-with-x25519 secret-key content)]
+           (utf8/decode decrypted))
+         (log/error :encrypt/empty-secret-key (str "Can't find the secret key for repo: " repo-url))))
+     :else
+     (p/resolved content))))
+
+(defn encrypt-with-passphrase
+  [passphrase content]
+  (p/let [_ (loader/load :age-encryption)
+          lazy-encrypt-with-user-passphrase (resolve 'frontend.extensions.age-encryption/encrypt-with-user-passphrase)
+          content (utf8/encode content)
+          encrypted (@lazy-encrypt-with-user-passphrase passphrase content true)]
+    (utf8/decode encrypted)))
+
+;; ;; TODO: What if decryption failed
+(defn decrypt-with-passphrase
+  [passphrase content]
+  (p/let [_ (loader/load :age-encryption)
+          lazy-decrypt-with-user-passphrase (resolve 'frontend.extensions.age-encryption/decrypt-with-user-passphrase)
+          content (utf8/encode content)
+          decrypted (lazy-decrypt-with-user-passphrase passphrase content)]
+    (utf8/decode decrypted)))

+ 23 - 0
src/main/frontend/extensions/age_encryption.cljs

@@ -0,0 +1,23 @@
+(ns frontend.extensions.age-encryption
+  (:require ["regenerator-runtime/runtime"] ;; required for async npm module
+            ["@kanru/rage-wasm" :as rage]))
+
+(defn keygen
+  []
+  (rage/keygen))
+
+(defn encrypt-with-x25519
+  [public-key content armor]
+  (rage/encrypt_with_x25519 public-key content armor))
+
+(defn decrypt-with-x25519
+  [secret-key content]
+  (rage/decrypt_with_x25519 secret-key content))
+
+(defn encrypt-with-user-passphrase
+  [passphrase content armor]
+  (rage/encrypt_with_user_passphrase passphrase content armor))
+
+(defn decrypt-with-user-passphrase
+  [passphrase content]
+  (rage/decrypt_with_user_passphrase passphrase content))

+ 2 - 2
src/main/frontend/extensions/code.cljs

@@ -134,7 +134,7 @@
             [frontend.state :as state]
             [frontend.utf8 :as utf8]
             [frontend.util :as util]
-            [frontend.config :as ui-config]
+            [frontend.config :as config]
             [goog.dom :as gdom]
             [goog.object :as gobj]
             [rum.core :as rum]))
@@ -238,7 +238,7 @@
                                                    (when-let [block-id (:block/uuid config)]
                                                      (let [block (db/pull [:block/uuid block-id])]
                                                        (editor-handler/edit-block! block :max block-id))))}}
-                          (when ui-config/publishing?
+                          (when config/publishing?
                             {:readOnly true
                              :cursorBlinkRate -1}))
         editor (when textarea

+ 2 - 1
src/main/frontend/extensions/html_parser.cljs

@@ -4,6 +4,7 @@
             [clojure.walk :as walk]
             [frontend.config :as config]
             [frontend.util :as util]
+            [logseq.graph-parser.util :as gp-util]
             [hickory.core :as hickory]))
 
 (defonce *inside-pre? (atom false))
@@ -74,7 +75,7 @@
                                 :h6 (block-transform 6 children)
                                 :a (let [href (:href attrs)
                                          label (map-join children)
-                                         has-img-tag? (util/safe-re-find #"\[:img" (str x))]
+                                         has-img-tag? (gp-util/safe-re-find #"\[:img" (str x))]
                                      (if has-img-tag?
                                        (export-hiccup x)
                                        (case format

+ 8 - 7
src/main/frontend/extensions/pdf/assets.cljs

@@ -9,6 +9,7 @@
             [frontend.handler.page :as page-handler]
             [frontend.state :as state]
             [frontend.util :as util]
+            [logseq.graph-parser.config :as gp-config]
             [medley.core :as medley]
             [promesa.core :as p]
             [reitit.frontend.easy :as rfe]
@@ -35,8 +36,8 @@
 
               :else
               (let [full-path (string/replace full-path #"^[.\/\\]+" "")
-                    full-path (if-not (string/starts-with? full-path config/local-assets-dir)
-                                (util/node-path.join config/local-assets-dir full-path)
+                    full-path (if-not (string/starts-with? full-path gp-config/local-assets-dir)
+                                (util/node-path.join gp-config/local-assets-dir full-path)
                                 full-path)]
                 (str "file://"  ;; TODO: bfs
                      (util/node-path.join
@@ -75,7 +76,7 @@
 (defn resolve-hls-data-by-key$
   [target-key]
   ;; TODO: fuzzy match
-  (when-let [hls-file (and target-key (str config/local-assets-dir "/" target-key ".edn"))]
+  (when-let [hls-file (and target-key (str gp-config/local-assets-dir "/" target-key ".edn"))]
     (load-hls-data$ {:hls-file hls-file})))
 
 (defn area-highlight?
@@ -113,7 +114,7 @@
                                    fstamp (get-in new-hl [:content :image])
                                    old-fstamp (and old-hl (get-in old-hl [:content :image]))
                                    fname (str (:page new-hl) "_" (:id new-hl))
-                                   fdir (str config/local-assets-dir "/" key)
+                                   fdir (str gp-config/local-assets-dir "/" key)
                                    _ (fs/mkdir-if-not-exists (str repo-dir "/" fdir))
                                    new-fpath (str fdir "/" fname "_" fstamp ".png")
                                    old-fpath (and old-fstamp (str fdir "/" fname "_" old-fstamp ".png"))
@@ -142,7 +143,7 @@
           repo-dir (config/get-repo-dir repo-cur)
           fstamp (get-in hl [:content :image])
           fname (str (:page hl) "_" (:id hl))
-          fdir (str config/local-assets-dir "/" fkey)
+          fdir (str gp-config/local-assets-dir "/" fkey)
           fpath (util/node-path.join repo-dir (str fdir "/" fname "_" fstamp ".png"))]
 
       (fs/unlink! repo-cur fpath {}))))
@@ -156,7 +157,7 @@
         format (state/get-preferred-format)]
     (if-not page
       (let [repo-dir (config/get-repo-dir (state/get-current-repo))
-            asset-dir (util/node-path.join repo-dir config/local-assets-dir)
+            asset-dir (util/node-path.join repo-dir gp-config/local-assets-dir)
             url (if (string/includes? url asset-dir)
                   (str ".." (last (string/split url repo-dir)))
                   url)
@@ -245,7 +246,7 @@
       (when-let [group-key (string/replace-first (:block/original-name page) #"^hls__" "")]
         (when-let [hl-page (:hl-page props)]
           (let [asset-path (editor-handler/make-asset-url
-                             (str "/" config/local-assets-dir "/" group-key "/" (str hl-page "_" id "_" stamp ".png")))]
+                             (str "/" gp-config/local-assets-dir "/" group-key "/" (str hl-page "_" id "_" stamp ".png")))]
             [:span.hl-area
              [:img {:src asset-path}]]))))))
 

+ 13 - 13
src/main/frontend/extensions/pdf/highlights.cljs

@@ -11,7 +11,7 @@
             [frontend.state :as state]
             [frontend.storage :as storage]
             [frontend.ui :as ui]
-            [frontend.util :as front-utils]
+            [frontend.util :as util]
             [medley.core :as medley]
             [promesa.core :as p]
             [rum.core :as rum]))
@@ -44,7 +44,7 @@
       (let [active-hl (:pdf/ref-highlight @state/state)
             page-key (:filename current)
             last-page (and page-key
-                           (front-utils/safe-parse-int (storage/get (str "ls-pdf-last-page-" page-key))))]
+                           (util/safe-parse-int (storage/get (str "ls-pdf-last-page-" page-key))))]
 
         (when (and last-page (nil? active-hl))
           (set! (.-currentPageNumber viewer) last-page)))))
@@ -54,7 +54,7 @@
   [^js viewer]
   (let [el-ref (rum/use-ref nil)
         adjust-main-size!
-        (front-utils/debounce
+        (util/debounce
           200 (fn [width]
                 (let [root-el js/document.documentElement]
                   (.setProperty (.-style root-el) "--ph-view-container-width" width)
@@ -119,7 +119,7 @@
 
                         "copy"
                         (do
-                          (front-utils/copy-to-clipboard!
+                          (util/copy-to-clipboard!
                             (or (:text content) (.toString range)))
                           (pdf-utils/clear-all-selection))
 
@@ -155,7 +155,7 @@
         (if (and @*highlight-mode? new?)
           (action-fn! @*highlight-last-color true)
           (let [^js el (rum/deref *el)
-                {:keys [x y]} (front-utils/calc-delta-rect-offset el (.closest el ".extensions__pdf-viewer"))]
+                {:keys [x y]} (util/calc-delta-rect-offset el (.closest el ".extensions__pdf-viewer"))]
             (set! (.. el -style -transform)
                   (str "translate3d(" (if (neg? x) (- x 5) 0) "px," (if (neg? y) (- y 5) 0) "px" ",0)"))))
         #())
@@ -341,7 +341,7 @@
                                       (.contains (.-classList target) "extensions__pdf-hls-area-region"))
                                     (.closest target ".page"))
                            (and e (or (.-metaKey e)
-                                      (and front-utils/win32? (.-shiftKey e))
+                                      (and util/win32? (.-shiftKey e))
                                       @*area-mode?)))))
 
         reset-coords #(do
@@ -688,7 +688,7 @@
         expanded? (boolean expanded)]
 
     [:div.extensions__pdf-outline-item
-     {:class (front-utils/classnames [{:has-children has-child? :is-expand expanded?}])}
+     {:class (util/classnames [{:has-children has-child? :is-expand expanded?}])}
      [:div.inner
       [:a
        {:href      "javascript:void(0);"
@@ -755,7 +755,7 @@
         [])
 
       [:div.extensions__pdf-outline-wrap.hls-popup-wrap
-       {:class    (front-utils/classnames [{:visible visible?}])
+       {:class    (util/classnames [{:visible visible?}])
         :on-click (fn [^js/MouseEvent e]
                     (let [target (.-target e)]
                       (when-not (.contains (rum/deref *el-outline) target)
@@ -790,7 +790,7 @@
                (fn []
                  (let [text (.-innerText (js/document.querySelector "#pdf-docinfo > .inner-text"))
                        text (string/replace text #"[\n\t]+" "\n")]
-                   (front-utils/copy-to-clipboard! text)
+                   (util/copy-to-clipboard! text)
                    (notification/show! "Copied!" :success)
                    (close-fn!))))]])
 
@@ -852,7 +852,7 @@
 
        ;; selection
        [:a.button
-        {:title    (str "Area highlight (" (if front-utils/mac? "⌘" "Shift") ")")
+        {:title    (str "Area highlight (" (if util/mac? "⌘" "Shift") ")")
          :class    (when area-mode? "is-active")
          :on-click #(set-area-mode! (not area-mode?))}
         (svg/icon-area 18)]
@@ -902,7 +902,7 @@
                   :on-mouse-enter #(.select ^js (.-target %))
                   :on-key-up      (fn [^js e]
                                     (let [^js input (.-target e)
-                                          value (front-utils/safe-parse-int (.-value input))]
+                                          value (util/safe-parse-int (.-value input))]
                                       (when (and (= (.-keyCode e) 13) value (> value 0))
                                         (set! (. viewer -currentPageNumber)
                                               (if (> value total-page-num) total-page-num value)))))}]
@@ -962,7 +962,7 @@
                ;;TODO: destroy
                (fn []
                  (when-let [last-page (.-currentPageNumber viewer)]
-                   (storage/set (str "ls-pdf-last-page-" (front-utils/node-path.basename url)) last-page))
+                   (storage/set (str "ls-pdf-last-page-" (util/node-path.basename url)) last-page))
 
                  (when pdf-document (.destroy pdf-document)))))
       [])
@@ -995,7 +995,7 @@
     (let [^js viewer (:viewer state)]
       [:div.extensions__pdf-viewer-cnt
        [:div.extensions__pdf-viewer
-        {:ref *el-ref :class (front-utils/classnames [{:is-area-dashed @*area-dashed?}])}
+        {:ref *el-ref :class (util/classnames [{:is-area-dashed @*area-dashed?}])}
         [:div.pdfViewer "viewer pdf"]
         [:div.pp-holder]
 

+ 2 - 2
src/main/frontend/extensions/pdf/utils.cljs

@@ -1,7 +1,7 @@
 (ns frontend.extensions.pdf.utils
   (:require [promesa.core :as p]
             [cljs-bean.core :as bean]
-            [frontend.util :as front-utils]
+            [frontend.util :as util]
             ["/frontend/extensions/pdf/utils" :as js-utils]
             [frontend.db :as front-db]
             [frontend.loader :refer [load]]))
@@ -105,7 +105,7 @@
   (.removeAllRanges (js/window.getSelection)))
 
 (def adjust-viewer-size!
-  (front-utils/debounce
+  (util/debounce
     200 (fn [^js viewer] (set! (. viewer -currentScaleValue) "auto"))))
 
 (defn fix-nested-js

+ 1 - 1
src/main/frontend/extensions/srs.cljs

@@ -436,7 +436,7 @@
                    (util/hiccup->class ".flex.flex-col.resize.overflow-y-auto"))}
          (let [repo (state/get-current-repo)]
            [:div {:style {:margin-top 20}}
-            (component-block/block-parents {} repo root-block-id {})])
+            (component-block/breadcrumb {} repo root-block-id {})])
          (component-block/blocks-container
           blocks
           (merge (show-cycle-config card @phase)

+ 0 - 2
src/main/frontend/external.cljc → src/main/frontend/external.cljs

@@ -1,6 +1,4 @@
 (ns frontend.external
-  ;; Wonky cljs detection
-  #_:clj-kondo/ignore
   (:require [frontend.external.roam :refer [->Roam]]
             [frontend.external.protocol :as protocol]))
 

+ 42 - 50
src/main/frontend/external/roam.cljc → src/main/frontend/external/roam.cljs

@@ -1,15 +1,12 @@
 (ns frontend.external.roam
-  (:require #?(:cljs [cljs-bean.core :as bean]
-               :clj [cheshire.core :as json])
-            ;; TODO: clj-kondo incorrectly thinks these requires are unused
-            #_:clj-kondo/ignore
+  (:require [cljs-bean.core :as bean]
             [frontend.external.protocol :as protocol]
-            #_:clj-kondo/ignore
             [frontend.date :as date]
             [medley.core :as medley]
             [clojure.walk :as walk]
             [clojure.string :as string]
             [frontend.util :as util]
+            [logseq.graph-parser.util :as gp-util]
             [frontend.text :as text]))
 
 (defonce all-refed-uids (atom #{}))
@@ -39,10 +36,8 @@
 (defn macro-transform
   [text]
   (string/replace text macro-pattern (fn [[original text]]
-                                       (let [[name arg] (util/split-first ":" text)]
+                                       (let [[name arg] (gp-util/split-first ":" text)]
                                          (if name
-                                           ;; TODO: Why unresolved var
-                                           #_:clj-kondo/ignore
                                            (let [name (text/page-ref-un-brackets! name)]
                                              (util/format "{{%s %s}}" name arg))
                                            original)))))
@@ -104,48 +99,45 @@
 
 (defn json->edn
   [raw-string]
-  #?(:cljs (-> raw-string js/JSON.parse bean/->clj)
-     :clj (-> raw-string json/parse-string clojure.walk/keywordize-keys)))
-
-#?(:cljs
-   (do
-     (defn ->file
-      [page-data]
-      (let [{:keys [create-time title children edit-time]} page-data
-            initial-level 1
-            text (when (seq children)
-                   (when-let [text (children->text children (dec initial-level))]
-                     (let [journal? (date/valid-journal-title? title)
-                           front-matter (if journal?
-                                          ""
-                                          (util/format "---\ntitle: %s\n---\n\n" title))]
-                       (str front-matter (transform text)))))]
-        (when (and (not (string/blank? title))
-                   text)
-          {:title title
-           :created-at create-time
-           :last-modified-at edit-time
-           :text text})))
-
-     (defn ->files
-       [edn-data]
-       (load-all-refed-uids! edn-data)
-       (let [files (map ->file edn-data)
-             files (remove #(nil? (:title %)) files)
-             files (group-by (fn [f] (string/lower-case (:title f)))
-                             files)]
-         (map
-          (fn [[_ [fst & others]]]
-            (assoc fst :text
-                   (->> (map :text (cons fst others))
-                        (interpose "\n")
-                        (apply str))))
-          files)))
-
-     (defrecord Roam []
-       protocol/External
-       (toMarkdownFiles [_this content _config]
-                        (-> content json->edn ->files)))))
+  (-> raw-string js/JSON.parse bean/->clj))
+
+(defn ->file
+  [page-data]
+  (let [{:keys [create-time title children edit-time]} page-data
+        initial-level 1
+        text (when (seq children)
+               (when-let [text (children->text children (dec initial-level))]
+                 (let [journal? (date/valid-journal-title? title)
+                       front-matter (if journal?
+                                      ""
+                                      (util/format "---\ntitle: %s\n---\n\n" title))]
+                   (str front-matter (transform text)))))]
+    (when (and (not (string/blank? title))
+               text)
+      {:title title
+       :created-at create-time
+       :last-modified-at edit-time
+       :text text})))
+
+(defn ->files
+  [edn-data]
+  (load-all-refed-uids! edn-data)
+  (let [files (map ->file edn-data)
+        files (remove #(nil? (:title %)) files)
+        files (group-by (fn [f] (string/lower-case (:title f)))
+                        files)]
+    (map
+     (fn [[_ [fst & others]]]
+       (assoc fst :text
+              (->> (map :text (cons fst others))
+                   (interpose "\n")
+                   (apply str))))
+     files)))
+
+(defrecord Roam []
+  protocol/External
+  (toMarkdownFiles [_this content _config]
+                   (-> content json->edn ->files)))
 
 (comment
   (defonce test-roam-json (frontend.db/get-file "same.json"))

+ 14 - 11
src/main/frontend/format/block.cljs

@@ -11,6 +11,8 @@
             [frontend.utf8 :as utf8]
             [frontend.util :as util]
             [frontend.util.property :as property]
+            [logseq.graph-parser.util :as gp-util]
+            [logseq.graph-parser.config :as gp-config]
             [lambdaisland.glogi :as log]
             [medley.core :as medley]
             [frontend.format.mldoc :as mldoc]))
@@ -53,8 +55,8 @@
                   (and
                    (= typ "Page_ref")
                    (and (string? value)
-                        (not (or (config/local-asset? value)
-                                 (config/draw? value))))
+                        (not (or (gp-config/local-asset? value)
+                                 (gp-config/draw? value))))
                    value)
 
                   (and
@@ -69,7 +71,7 @@
                      (when (and (not (util/starts-with? value "http:"))
                                 (not (util/starts-with? value "https:"))
                                 (not (util/starts-with? value "file:"))
-                                (not (config/local-asset? value))
+                                (not (gp-config/local-asset? value))
                                 (or (= ext :excalidraw)
                                     (not (contains? (config/supported-formats) ext))))
                        value)))
@@ -136,7 +138,7 @@
                         :else
                         nil)]
     (when (and block-id
-               (util/uuid-string? block-id))
+               (gp-util/uuid-string? block-id))
       block-id)))
 
 (defn paragraph-block?
@@ -256,16 +258,17 @@
            [original-page-name page-name journal-day] (convert-page-if-journal original-page-name)
            namespace? (and (not (boolean (text/get-nested-page-name original-page-name)))
                            (text/namespace-page? original-page-name))
-           page-entity (db/entity [:block/name page-name])]
+           page-entity (db/entity [:block/name page-name])
+           original-page-name (or (:block/original-name page-entity) original-page-name)]
        (merge
         {:block/name page-name
          :block/original-name original-page-name}
         (when with-id?
           (if page-entity
-            {}
+            {:block/uuid (:block/uuid page-entity)}
             {:block/uuid (db/new-block-id)}))
         (when namespace?
-          (let [namespace (first (util/split-last "/" original-page-name))]
+          (let [namespace (first (gp-util/split-last "/" original-page-name))]
             (when-not (string/blank? namespace)
               {:block/namespace {:block/name (util/page-name-sanity-lc namespace)}})))
         (when (and with-timestamp? (not page-entity)) ;; Only assign timestamp on creating new entity
@@ -302,7 +305,7 @@
            (swap! refs conj page))
          (when-let [tag (get-tag form)]
            (let [tag (text/page-ref-un-brackets! tag)]
-             (when (util/tag-valid? tag)
+             (when (gp-util/tag-valid? tag)
                (swap! refs conj tag))))
          form))
      (concat title body))
@@ -333,7 +336,7 @@
        form)
      (concat title body))
     (let [ref-blocks (->> @ref-blocks
-                          (filter util/uuid-string?))
+                          (filter gp-util/uuid-string?))
           ref-blocks (map
                        (fn [id]
                          [:block/uuid (medley/uuid id)])
@@ -354,7 +357,7 @@
   [blocks]
   (map (fn [block]
          (if (map? block)
-           (block-keywordize (util/remove-nils block))
+           (block-keywordize (gp-util/remove-nils block))
            block))
        blocks))
 
@@ -437,7 +440,7 @@
                                (get-in properties [:properties :custom_id])
                                (get-in properties [:properties :id]))]
         (let [custom-id (and (string? custom-id) (string/trim custom-id))]
-          (when (and custom-id (util/uuid-string? custom-id))
+          (when (and custom-id (gp-util/uuid-string? custom-id))
             (uuid custom-id))))
       (db/new-block-id)))
 

+ 10 - 10
src/main/frontend/format/mldoc.cljs

@@ -3,13 +3,13 @@
             [clojure.string :as string]
             [frontend.format.protocol :as protocol]
             [frontend.utf8 :as utf8]
-            [frontend.util :as util]
             [goog.object :as gobj]
             [lambdaisland.glogi :as log]
             [medley.core :as medley]
             ["mldoc" :as mldoc :refer [Mldoc]]
             [linked.core :as linked]
-            [frontend.config :as config]))
+            [logseq.graph-parser.util :as gp-util]
+            [logseq.graph-parser.config :as gp-config]))
 
 (defonce parseJson (gobj/get Mldoc "parseJson"))
 (defonce parseInlineJson (gobj/get Mldoc "parseInlineJson"))
@@ -92,8 +92,8 @@
   (let [lines (string/split-lines s)
         [f & r] lines
         body (map (fn [line]
-                    (if (string/blank? (util/safe-subs line 0 level))
-                      (util/safe-subs line level)
+                    (if (string/blank? (gp-util/safe-subs line 0 level))
+                      (gp-util/safe-subs line level)
                       line))
                (if remove-first-line? lines r))
         content (if remove-first-line? body (cons f body))]
@@ -138,7 +138,7 @@
                    (->>
                     (map
                      (fn [[_ v]]
-                       (let [[k v] (util/split-first " " v)]
+                       (let [[k v] (gp-util/split-first " " v)]
                          (mapv
                           string/trim
                           [k v])))
@@ -202,7 +202,7 @@
         []
         (-> content
             (parse-json config)
-            (util/json->clj)
+            (gp-util/json->clj)
             (update-src-full-content content)
             (collect-page-properties parse-property)))
       (catch js/Error e
@@ -215,7 +215,7 @@
   (try
     (if (string/blank? content)
       {}
-      (let [[headers blocks] (-> content (parse-opml) (util/json->clj))]
+      (let [[headers blocks] (-> content (parse-opml) (gp-util/json->clj))]
         [headers (collect-page-properties blocks parse-property)]))
     (catch js/Error e
       (log/error :edn/convert-failed e)
@@ -228,7 +228,7 @@
       {}
       (-> text
           (inline-parse-json config)
-          (util/json->clj)))
+          (gp-util/json->clj)))
     (catch js/Error _e
       [])))
 
@@ -273,7 +273,7 @@
             (and (contains? #{"Page_ref"} ref-type)
                  (or
                   ;; 2. excalidraw link
-                  (config/draw? ref-value)
+                  (gp-config/draw? ref-value)
 
                   ;; 3. local asset link
-                  (boolean (config/local-asset? ref-value)))))))))
+                  (boolean (gp-config/local-asset? ref-value)))))))))

+ 16 - 12
src/main/frontend/fs.cljs

@@ -11,7 +11,8 @@
             [lambdaisland.glogi :as log]
             [promesa.core :as p]
             [frontend.db :as db]
-            [clojure.string :as string]))
+            [clojure.string :as string]
+            [frontend.encrypt :as encrypt]))
 
 (defonce nfs-record (nfs/->Nfs))
 (defonce bfs-record (bfs/->Bfs))
@@ -74,17 +75,20 @@
 (defn write-file!
   [repo dir path content opts]
   (when content
-    (->
-     (p/let [_ (protocol/write-file! (get-fs dir) repo dir path content opts)]
-       (when (= bfs-record (get-fs dir))
-         (db/set-file-last-modified-at! repo (config/get-file-path repo path) (js/Date.))))
-     (p/catch (fn [error]
-                (log/error :file/write-failed {:dir dir
-                                               :path path
-                                               :error error})
-                ;; Disable this temporarily
-                ;; (js/alert "Current file can't be saved! Please copy its content to your local file system and click the refresh button.")
-                )))))
+    (let [fs-record (get-fs dir)]
+      (p/let [md-or-org? (contains? #{"md" "markdown" "org"} (util/get-file-ext path))
+              content (if-not md-or-org? content (encrypt/encrypt content))]
+        (->
+         (p/let [_ (protocol/write-file! (get-fs dir) repo dir path content opts)]
+           (when (= bfs-record fs-record)
+             (db/set-file-last-modified-at! repo (config/get-file-path repo path) (js/Date.))))
+         (p/catch (fn [error]
+                    (log/error :file/write-failed {:dir dir
+                                                   :path path
+                                                   :error error})
+                    ;; Disable this temporarily
+                    ;; (js/alert "Current file can't be saved! Please copy its content to your local file system and click the refresh button.")
+                    )))))))
 
 (defn read-file
   ([dir path]

+ 12 - 4
src/main/frontend/fs/capacitor_fs.cljs

@@ -9,7 +9,8 @@
             [promesa.core :as p]
             [rum.core :as rum]
             [frontend.state :as state]
-            [frontend.db :as db]))
+            [frontend.db :as db]
+            [frontend.encrypt :as encrypt]))
 
 (when (mobile-util/native-ios?)
   (defn iOS-ensure-documents!
@@ -101,7 +102,10 @@
 (defn- contents-matched?
   [disk-content db-content]
   (when (and (string? disk-content) (string? db-content))
-    (p/resolved (= (string/trim disk-content) (string/trim db-content)))))
+    (if (encrypt/encrypted-db? (state/get-current-repo))
+      (p/let [decrypted-content (encrypt/decrypt disk-content)]
+        (= (string/trim decrypted-content) (string/trim db-content)))
+      (p/resolved (= (string/trim disk-content) (string/trim db-content))))))
 
 (defn- write-file-impl!
   [_this repo _dir path content {:keys [ok-handler error-handler old-content skip-compare?]} stat]
@@ -136,7 +140,8 @@
          (not (contains? #{"excalidraw" "edn" "css"} ext))
          (not (string/includes? path "/.recycle/"))
          (zero? pending-writes))
-        (state/pub-event! [:file/not-matched-from-disk path disk-content content])
+        (p/let [disk-content (encrypt/decrypt disk-content)]
+          (state/pub-event! [:file/not-matched-from-disk path disk-content content]))
 
         :else
         (->
@@ -144,7 +149,10 @@
                                                          :data content
                                                          :encoding (.-UTF8 Encoding)
                                                          :recursive true}))]
-           (db/set-file-content! repo (js/decodeURI path) content)
+           (p/let [content (if (encrypt/encrypted-db? (state/get-current-repo))
+                             (encrypt/decrypt content)
+                             content)]
+             (db/set-file-content! repo (js/decodeURI path) content))
            (when ok-handler
              (ok-handler repo path result))
            result)

+ 12 - 4
src/main/frontend/fs/nfs.cljs

@@ -10,7 +10,8 @@
             [frontend.config :as config]
             [frontend.state :as state]
             [frontend.handler.notification :as notification]
-            ["/frontend/utils" :as utils]))
+            ["/frontend/utils" :as utils]
+            [frontend.encrypt :as encrypt]))
 
 ;; We need to cache the file handles in the memory so that
 ;; the browser will not keep asking permissions.
@@ -57,7 +58,10 @@
 (defn- contents-matched?
   [disk-content db-content]
   (when (and (string? disk-content) (string? db-content))
-    (p/resolved (= (string/trim disk-content) (string/trim db-content)))))
+    (if (encrypt/encrypted-db? (state/get-current-repo))
+      (p/let [decrypted-content (encrypt/decrypt disk-content)]
+        (= (string/trim decrypted-content) (string/trim db-content)))
+      (p/resolved (= (string/trim disk-content) (string/trim db-content))))))
 
 (defrecord ^:large-vars/cleanup-todo Nfs []
   protocol/Fs
@@ -171,12 +175,16 @@
                          (not (contains? #{"excalidraw" "edn" "css"} ext))
                          (not (string/includes? path "/.recycle/"))
                          (zero? pending-writes))
-                      (state/pub-event! [:file/not-matched-from-disk path local-content content])
+                      (p/let [local-content (encrypt/decrypt local-content)]
+                        (state/pub-event! [:file/not-matched-from-disk path local-content content]))
                       (p/let [_ (verify-permission repo file-handle true)
                               _ (utils/writeFile file-handle content)
                               file (.getFile file-handle)]
                         (when file
-                          (db/set-file-content! repo path content)
+                          (p/let [content (if (encrypt/encrypted-db? (state/get-current-repo))
+                                            (encrypt/decrypt content)
+                                            content)]
+                            (db/set-file-content! repo path content))
                           (nfs-saved-handler repo path file))))))
                 (p/catch (fn [e]
                            (js/console.error e))))

+ 12 - 4
src/main/frontend/fs/node.cljs

@@ -8,7 +8,8 @@
             [frontend.util :as util]
             [goog.object :as gobj]
             [lambdaisland.glogi :as log]
-            [promesa.core :as p]))
+            [promesa.core :as p]
+            [frontend.encrypt :as encrypt]))
 
 (defn concat-path
   [dir path]
@@ -27,7 +28,10 @@
 (defn- contents-matched?
   [disk-content db-content]
   (when (and (string? disk-content) (string? db-content))
-    (p/resolved (= (string/trim disk-content) (string/trim db-content)))))
+    (if (encrypt/encrypted-db? (state/get-current-repo))
+      (p/let [decrypted-content (encrypt/decrypt disk-content)]
+        (= (string/trim decrypted-content) (string/trim db-content)))
+      (p/resolved (= (string/trim disk-content) (string/trim db-content))))))
 
 (defn- write-file-impl!
   [this repo dir path content {:keys [ok-handler error-handler old-content skip-compare?]} stat]
@@ -58,14 +62,18 @@
          (not (contains? #{"excalidraw" "edn" "css"} ext))
          (not (string/includes? path "/.recycle/"))
          (zero? pending-writes))
-        (state/pub-event! [:file/not-matched-from-disk path disk-content content])
+        (p/let [disk-content (encrypt/decrypt disk-content)]
+          (state/pub-event! [:file/not-matched-from-disk path disk-content content]))
 
         :else
         (->
          (p/let [result (ipc/ipc "writeFile" repo path content)
                  mtime (gobj/get result "mtime")]
            (db/set-file-last-modified-at! repo path mtime)
-           (db/set-file-content! repo path content)
+           (p/let [content (if (encrypt/encrypted-db? (state/get-current-repo))
+                             (encrypt/decrypt content)
+                             content)]
+             (db/set-file-content! repo path content))
            (when ok-handler
              (ok-handler repo path result))
            result)

+ 289 - 236
src/main/frontend/fs/sync.cljs

@@ -33,7 +33,8 @@
 ;; files in these `get-monitored-dirs` dirs will be synchronized.
 ;;
 ;; sync strategy:
-;; - when toggle file-sync on, trigger a local->remote-full-sync first,
+;; - when toggle file-sync on,
+;;   trigger remote->local first, then local->remote-full-sync
 ;;   local->remote-full-sync will compare local-files with remote-files (by md5 & size),
 ;;   and upload new-added-files to remote server.
 ;; - if local->remote sync(normal-sync or full-sync) return :need-sync-remote,
@@ -58,12 +59,8 @@
                  ::remote->local
                  ;; local->remote full sync
                  ::local->remote-full-sync
-                 ;; exec remote->local, then local->remote
-                 ::remote->local=>local->remote
-                 ;; exec remote->local, then local->remote-full-sync
-                 ::remote->local=>local->remote-full-sync
-                 ;; exec remote->local-full-sync, then local->remote-full-sync
-                 ::remote->local-full-sync=>local->remote-full-sync
+                 ;; remote->local full sync
+                 ::remote->local-full-sync
                  ::stop})
 (s/def ::path string?)
 (s/def ::time t/date?)
@@ -74,6 +71,7 @@
 (s/def ::sync-state (s/keys :req-un [::state
                                      ::current-local->remote-files
                                      ::current-remote->local-files
+                                     ::queued-local->remote-files
                                      ::history]))
 
 ;; diff
@@ -109,7 +107,9 @@
 
 (def graphs-txid (persist-var/persist-var nil "graphs-txid"))
 
-(defn update-graphs-txid! [latest-txid graph-uuid user-uuid repo]
+(defn update-graphs-txid!
+  [latest-txid graph-uuid user-uuid repo]
+  {:pre [(int? latest-txid) (>= latest-txid 0)]}
   (persist-var/-reset-value! graphs-txid [user-uuid graph-uuid latest-txid] repo)
   (persist-var/persist-save graphs-txid))
 
@@ -117,6 +117,19 @@
   (persist-var/-reset-value! graphs-txid nil repo)
   (persist-var/persist-save graphs-txid))
 
+(defn- ws-ping-loop [ws]
+  (go-loop []
+    (let [state (.-readyState ws)]
+      ;; not closing or closed state
+      (when (not (contains? #{2 3} state))
+        (if (not= 1 state)
+          ;; when connecting, wait 1s
+          (do (<! (timeout 1000))
+              (recur))
+          (do (.send ws "PING")
+              (<! (timeout 30000))
+              (recur)))))))
+
 (defn- ws-stop! [*ws]
   (swap! *ws (fn [o] (assoc o :stop true)))
   (.close (:ws @*ws)))
@@ -124,6 +137,7 @@
 (defn- ws-listen!*
   [graph-uuid *ws remote-changes-chan]
   (reset! *ws {:ws (js/WebSocket. (util/format ws-addr graph-uuid)) :stop false})
+  (ws-ping-loop (:ws @*ws))
   ;; (set! (.-onopen (:ws @*ws)) #(println (util/format "ws opened: graph '%s'" graph-uuid %)))
   (set! (.-onclose (:ws @*ws)) (fn [_e]
                                  (when-not (true? (:stop @*ws))
@@ -133,13 +147,14 @@
                                      (ws-listen!* graph-uuid *ws remote-changes-chan)))))
   (set! (.-onmessage (:ws @*ws)) (fn [e]
                                    (let [data (js->clj (js/JSON.parse (.-data e)) :keywordize-keys true)]
-                                     (if-let [v (poll! remote-changes-chan)]
-                                       (let [last-txid (:txid v)
-                                             current-txid (:txid data)]
-                                         (if (> last-txid current-txid)
-                                           (offer! remote-changes-chan v)
-                                           (offer! remote-changes-chan data)))
-                                       (offer! remote-changes-chan data))))))
+                                     (when (some? (:txid data))
+                                       (if-let [v (poll! remote-changes-chan)]
+                                         (let [last-txid (:txid v)
+                                               current-txid (:txid data)]
+                                           (if (> last-txid current-txid)
+                                             (offer! remote-changes-chan v)
+                                             (offer! remote-changes-chan data)))
+                                         (offer! remote-changes-chan data)))))))
 
 (defn ws-listen!
   "return channal which output messages from server"
@@ -825,6 +840,71 @@
                 not)
     (go (>! local-changes-chan (->FileChangeEvent type dir path stat)))))
 
+;;; ### sync state
+
+
+(defn sync-state
+  "create a new sync-state"
+  []
+  {:post [(s/valid? ::sync-state %)]}
+  {:state ::idle
+   :current-local->remote-files #{}
+   :current-remote->local-files #{}
+   :queued-local->remote-files #{}
+   :history '()})
+
+(defn- sync-state--update-state
+  [sync-state next-state]
+  {:pre [(s/valid? ::state next-state)]
+   :post [(s/valid? ::sync-state %)]}
+  (assoc sync-state :state next-state))
+
+(defn sync-state--add-current-remote->local-files
+  [sync-state paths]
+  {:post [(s/valid? ::sync-state %)]}
+  (update sync-state :current-remote->local-files into paths))
+
+(defn sync-state--add-current-local->remote-files
+  [sync-state paths]
+  {:post [(s/valid? ::sync-state %)]}
+  (update sync-state :current-local->remote-files into paths))
+
+(defn sync-state--update-queued-local->remote-files
+  [sync-state paths]
+  {:post [(s/valid? ::sync-state %)]}
+  (update sync-state :queued-local->remote-files (fn [_ n] (set n)) paths))
+
+(defn- add-history-items
+  [history paths now]
+  (sequence
+   (comp
+    ;; only reserve the latest one of same-path-items
+    (dedupe-by :path)
+    ;; reserve the latest 20 history items
+    (take 20))
+   (into history
+         (map (fn [path] {:path path :time now}) paths))))
+
+(defn sync-state--remove-current-remote->local-files
+  [sync-state paths]
+  {:post [(s/valid? ::sync-state %)]}
+  (let [now (t/now)]
+    (-> sync-state
+        (update :current-remote->local-files set/difference paths)
+        (update :history add-history-items paths now))))
+
+(defn sync-state--remove-current-local->remote-files
+  [sync-state paths]
+  {:post [(s/valid? ::sync-state %)]}
+  (let [now (t/now)]
+    (-> sync-state
+        (update :current-local->remote-files set/difference paths)
+        (update :history add-history-items paths now))))
+
+(defn sync-state--stopped?
+  [sync-state]
+  (= ::stop (:state sync-state)))
+
 ;;; ### remote->local syncer & local->remote syncer
 
 (defprotocol IRemote->LocalSync
@@ -941,32 +1021,26 @@
 (defn- contains-path? [regexps path]
   (reduce #(when (re-find %2 path) (reduced true)) false regexps))
 
-(defn- filter-local-changes
+(defn- filter-local-changes-pred
   "filter local-change events:
   - for 'unlink' event
     - when related file exists on local dir, ignore this event
   - for 'add' | 'change' event
     - when related file's content is same as remote file, ignore it"
-  [to-ch from-ch basepath graph-uuid]
-  (async/pipeline-async
-   1 to-ch
-   (fn [^FileChangeEvent e result]
-     (go
-       (case (.-type e)
-         "unlink"
-         (let [r (<! (get-local-files-meta rsapi "" basepath [(relative-path e)]))]
-           (when (some-> r ex-cause ;; str (string/index-of "No such file or directory")
-                         )
-             (>! result e)))
-
-         ("add" "change")
-         (let [path (relative-path e)]
-           (when (and (<! (local-file-exists? path basepath))
-                      (<! (file-changed? graph-uuid path basepath)))
-             (>! result e))))
-       (async/close! result)))
-   from-ch false))
-
+  [^FileChangeEvent e basepath graph-uuid]
+  (go
+    (let [r-path (relative-path e)]
+      (case (.-type e)
+        "unlink"
+        (let [r (<! (get-local-files-meta rsapi "" basepath [r-path]))]
+          ;; keep this e when it's not found
+          (some-> r ex-cause))
+
+        ("add" "change")
+        ;; 1. local file exists
+        ;; 2. compare with remote file, and changed
+        (and (<! (local-file-exists? r-path basepath))
+             (<! (file-changed? graph-uuid r-path basepath)))))))
 
 (defrecord ^:large-vars/cleanup-todo
     Local->RemoteSyncer [user-uuid graph-uuid base-path repo *sync-state
@@ -997,7 +1071,7 @@
       (let [c (.filtered-chan this 10000)
             filter-e-fn (.filter-file-change-events-fn this)]
         (go-loop [timeout-c (timeout rate)
-                  tcoll (transient [])]
+                  coll []]
           (let [{:keys [timeout ^FileChangeEvent e stop]}
                 (async/alt! timeout-c {:timeout true}
                             from-chan ([e] {:e e})
@@ -1007,22 +1081,22 @@
               (async/close! c)
 
               timeout
-              (let [from-c (chan 10000)]
-                (<! (async/onto-chan! from-c (distinct (persistent! tcoll)) false))
-                (filter-local-changes c from-c base-path graph-uuid)
-                (async/close! from-c)
-                (recur (async/timeout rate) (transient [])))
+              (do (async/onto-chan! c coll false)
+                  (swap! *sync-state sync-state--update-queued-local->remote-files nil)
+                  (recur (async/timeout rate) []))
 
               (some? e)
-              (do
-                (when (filter-e-fn e)
-                  (conj! tcoll e))
-                (recur timeout-c tcoll))
+              (if (and (filter-e-fn e)
+                           (<! (filter-local-changes-pred e base-path graph-uuid)))
+                    (let [coll* (distinct (conj coll e))]
+                      (swap! *sync-state sync-state--update-queued-local->remote-files
+                             (mapv relative-path coll*))
+                      (recur timeout-c coll*))
+                    (recur timeout-c coll))
 
               (nil? e)
-              (do
-                (println "close ratelimit chan")
-                (async/close! c)))))
+              (do (println "close ratelimit chan")
+                  (async/close! c)))))
         c))
 
 
@@ -1102,206 +1176,186 @@
                     (or need-sync-remote unknown) r)))))))))
 
 
-;;; ### sync state
-
-
-(defn sync-state
-  "create a new sync-state"
-  []
-  {:post [(s/valid? ::sync-state %)]}
-  {:state ::idle
-   :current-local->remote-files #{}
-   :current-remote->local-files #{}
-   :history '()})
-
-(defn- sync-state--update-state
-  [sync-state next-state]
-  {:pre [(s/valid? ::state next-state)]
-   :post [(s/valid? ::sync-state %)]}
-  (assoc sync-state :state next-state))
-
-(defn sync-state--add-current-remote->local-files
-  [sync-state paths]
-  {:post [(s/valid? ::sync-state %)]}
-  (update sync-state :current-remote->local-files into paths))
-
-(defn sync-state--add-current-local->remote-files
-  [sync-state paths]
-  {:post [(s/valid? ::sync-state %)]}
-  (update sync-state :current-local->remote-files into paths))
-
-(defn- add-history-items
-  [history paths now]
-  (sequence
-   (comp
-    ;; only reserve the latest one of same-path-items
-    (dedupe-by :path)
-    ;; reserve the latest 20 history items
-    (take 20))
-   (into history
-         (map (fn [path] {:path path :time now}) paths))))
-
-(defn sync-state--remove-current-remote->local-files
-  [sync-state paths]
-  {:post [(s/valid? ::sync-state %)]}
-  (let [now (t/now)]
-    (-> sync-state
-        (update :current-remote->local-files set/difference paths)
-        (update :history add-history-items paths now))))
-
-(defn sync-state--remove-current-local->remote-files
-  [sync-state paths]
-  {:post [(s/valid? ::sync-state %)]}
-  (let [now (t/now)]
-    (-> sync-state
-        (update :current-local->remote-files set/difference paths)
-        (update :history add-history-items paths now))))
 
-(defn sync-state--stopped?
-  [sync-state]
-  (= ::stop (:state sync-state)))
 
 
 ;;; ### put all stuff together
 
+(defn- drain-chan
+  "drop all stuffs in CH"
+  [ch]
+  (->> (repeatedly #(poll! ch))
+       (take-while identity)))
+
 (defrecord ^:large-vars/cleanup-todo
     SyncManager [graph-uuid base-path *sync-state
                  ^Local->RemoteSyncer local->remote-syncer ^Remote->LocalSyncer remote->local-syncer
                  full-sync-chan stop-sync-chan remote->local-sync-chan local->remote-sync-chan
                  local-changes-chan ^:mutable ratelimit-local-changes-chan
-                 *txid ^:mutable state ^:mutable _remote-change-chan ^:mutable _*ws ^:mutable stopped]
-  Object
-  (schedule [this next-state args]
-    {:pre [(s/valid? ::state next-state)]}
-    (println "[SyncManager" graph-uuid "]" (and state (name state)) "->" (and next-state (name next-state)))
-    (set! state next-state)
-    (swap! *sync-state sync-state--update-state next-state)
-    (go
-      (case state
-        ::idle
-        (<! (.idle this))
-        ::local->remote
-        (<! (.local->remote this args))
-        ::remote->local
-        (<! (.remote->local this nil args))
-        ::local->remote-full-sync
-        (<! (.full-sync this))
-        ::remote->local=>local->remote
-        (<! (.remote->local this ::local->remote args))
-        ::remote->local=>local->remote-full-sync
-        (<! (.remote->local this ::local->remote-full-sync args))
-        ::remote->local-full-sync=>local->remote-full-sync
-        (<! (.remote->local-full-sync this ::local->remote-full-sync))
-        ::stop
-        (-stop! this))))
-
-  (start [this]
-    (set! _*ws (atom nil))
-    (set! _remote-change-chan (ws-listen! graph-uuid _*ws))
-    (set! ratelimit-local-changes-chan (ratelimit local->remote-syncer local-changes-chan))
-    (.schedule this ::idle nil))
-
-  (idle [this]
-    (go
-      (let [{:keys [stop full-sync ;; trigger-remote trigger-local
-                    remote local trigger-full-sync]}
-            (async/alt!
-              stop-sync-chan {:stop true}
-              full-sync-chan {:full-sync true}
-              remote->local-sync-chan {:trigger-remote true}
-              local->remote-sync-chan {:trigger-local true}
-              _remote-change-chan ([v] (println "remote changes:" v) {:remote v})
-              ratelimit-local-changes-chan ([v] (println "local changes:" v) {:local v})
-              (timeout (* 20 60 1000)) {:trigger-full-sync true}
-              :priority true)]
-        (cond
-          stop
-          (<! (.schedule this ::stop nil))
-          (or full-sync trigger-full-sync)
-          (<! (.schedule this ::local->remote-full-sync nil))
-          remote
-          (<! (.schedule this ::remote->local {:remote remote}))
-          local
-          (<! (.schedule this ::local->remote {:local local}))
-          :else
-          (<! (.schedule this :idle nil))))))
+                 *txid ^:mutable state ^:mutable _remote-change-chan ^:mutable _*ws ^:mutable stopped
+                 ^:mutable ops-chan]
+    Object
+    (schedule [this next-state args]
+      {:pre [(s/valid? ::state next-state)]}
+      (println "[SyncManager" graph-uuid "]" (and state (name state)) "->" (and next-state (name next-state)))
+      (set! state next-state)
+      (swap! *sync-state sync-state--update-state next-state)
+      (go
+        (case state
+          ::idle
+          (<! (.idle this))
+          ::local->remote
+          (<! (.local->remote this args))
+          ::remote->local
+          (<! (.remote->local this nil args))
+          ::local->remote-full-sync
+          (<! (.full-sync this))
+          ::remote->local-full-sync
+          (<! (.remote->local-full-sync this nil))
+          ::stop
+          (-stop! this))))
+
+    (start [this]
+      (set! ops-chan (chan (async/dropping-buffer 10)))
+      (set! _*ws (atom nil))
+      (set! _remote-change-chan (ws-listen! graph-uuid _*ws))
+      (set! ratelimit-local-changes-chan (ratelimit local->remote-syncer local-changes-chan))
+      (go-loop []
+        (let [{:keys [stop remote->local local->remote-full-sync local->remote]}
+              (async/alt!
+                stop-sync-chan {:stop true}
+                remote->local-sync-chan {:remote->local true}
+                full-sync-chan {:local->remote-full-sync true}
+                _remote-change-chan ([v] (println "remote change:" v) {:remote->local v})
+                ratelimit-local-changes-chan ([v] (println "local changes:" v) {:local->remote v})
+                (timeout (* 20 60 1000)) {:local->remote-full-sync true}
+                :priority true)]
+          (cond
+            stop
+            (do
+              (drain-chan ops-chan)
+              (>! ops-chan {:stop true}))
+            remote->local
+            (let [txid
+                  (if (true? remote->local)
+                    {:txid (:TXId (<! (get-remote-graph remoteapi nil graph-uuid)))}
+                    remote->local)]
+              (when (some? txid)
+                (>! ops-chan {:remote->local txid}))
+              (recur))
+            local->remote
+            (do (>! ops-chan {:local->remote local->remote})
+                (recur))
+            local->remote-full-sync
+            (do (drain-chan ops-chan)
+                (>! ops-chan {:local->remote-full-sync true})
+                (recur)))))
+      (.schedule this ::idle nil))
+
+    (idle [this]
+      (go
+        (let [{:keys [stop remote->local local->remote local->remote-full-sync remote->local-full-sync]}
+              (<! ops-chan)]
+          (cond
+            stop
+            (<! (.schedule this ::stop nil))
+            remote->local
+            (<! (.schedule this ::remote->local {:remote remote->local}))
+            local->remote
+            (<! (.schedule this ::local->remote {:local local->remote}))
+            local->remote-full-sync
+            (<! (.schedule this ::local->remote-full-sync nil))
+            remote->local-full-sync
+            (<! (.schedule this ::remote->local-full-sync nil))
+            :else
+            (<! (.schedule this ::stop nil))))))
 
-  (full-sync [this]
-    (go
-      (let [{:keys [succ need-sync-remote unknown stop] :as r}
-            (<! (sync-local->remote-all-files! local->remote-syncer))]
-        (s/assert ::sync-local->remote-all-files!-result r)
-        (cond
-          succ
-          (.schedule this ::idle nil)
-          need-sync-remote
-          (.schedule this ::remote->local=>local->remote-full-sync nil)
-          stop
-          (.schedule this ::stop nil)
-          unknown
-          (do
-            (debug/pprint "full-sync" unknown)
-            (.schedule this ::idle nil))))))
-
-  (remote->local-full-sync [this next-state]
-    (go
-      (let [{:keys [succ unknown stop]}
-            (<! (sync-remote->local-all-files! remote->local-syncer))]
-        (cond
-          succ
-          (.schedule this next-state nil)
-          stop
-          (.schedule this ::stop nil)
-          unknown
-          (do
-            (debug/pprint "remote->local-full-sync" unknown)
-            (.schedule this ::idle nil))))))
-
-  (remote->local [this next-state {remote-val :remote :as args}]
-    (go
-      (if (some-> remote-val :txid (<= @*txid))
-        (.schedule this ::idle nil)
-        (let [{:keys [succ unknown stop need-remote->local-full-sync] :as r}
-              (<! (sync-remote->local! remote->local-syncer))]
-          (s/assert ::sync-remote->local!-result r)
+    (full-sync [this]
+      (go
+        (let [{:keys [succ need-sync-remote unknown stop] :as r}
+              (<! (sync-local->remote-all-files! local->remote-syncer))]
+          (s/assert ::sync-local->remote-all-files!-result r)
           (cond
-            need-remote->local-full-sync
-            (.schedule this ::remote->local-full-sync=>local->remote-full-sync nil)
             succ
-            (.schedule this (or next-state ::idle) args)
+            (.schedule this ::idle nil)
+            need-sync-remote
+            (do (drain-chan ops-chan)
+                (>! ops-chan {:remote->local true})
+                (>! ops-chan {:local->remote-full-sync true})
+                (.schedule this ::idle nil))
             stop
             (.schedule this ::stop nil)
             unknown
-            (do (prn "remote->local err" unknown)
-                (.schedule this ::idle nil)))))))
+            (do
+              (debug/pprint "full-sync" unknown)
+              (.schedule this ::idle nil))))))
 
-  (local->remote [this {^FileChangeEvents local-change :local}]
-    (assert (some? local-change) local-change)
-    (go
-      (let [{:keys [succ need-sync-remote unknown] :as r}
-            (<! (sync-local->remote! local->remote-syncer [local-change]))]
-        (s/assert ::sync-local->remote!-result r)
-        (cond
-          succ
+    (remote->local-full-sync [this _next-state]
+      (go
+        (let [{:keys [succ unknown stop]}
+              (<! (sync-remote->local-all-files! remote->local-syncer))]
+          (cond
+            succ
+            (.schedule this ::idle nil)
+            stop
+            (.schedule this ::stop nil)
+            unknown
+            (do
+              (debug/pprint "remote->local-full-sync" unknown)
+              (.schedule this ::idle nil))))))
+
+    (remote->local [this _next-state {remote-val :remote}]
+      (go
+        (if (some-> remote-val :txid (<= @*txid))
           (.schedule this ::idle nil)
+          (let [{:keys [succ unknown stop need-remote->local-full-sync] :as r}
+                (<! (sync-remote->local! remote->local-syncer))]
+            (s/assert ::sync-remote->local!-result r)
+            (cond
+              need-remote->local-full-sync
+              (do (drain-chan ops-chan)
+                  (>! ops-chan {:remote->local-full-sync true})
+                  (>! ops-chan {:local->remote-full-sync true})
+                  (.schedule this ::idle nil))
+              succ
+              (.schedule this ::idle nil)
+              stop
+              (.schedule this ::stop nil)
+              unknown
+              (do (prn "remote->local err" unknown)
+                  (.schedule this ::idle nil)))))))
+
+    (local->remote [this {^FileChangeEvents local-change :local}]
+      (assert (some? local-change) local-change)
+      (go
+        (let [{:keys [succ need-sync-remote unknown] :as r}
+              (<! (sync-local->remote! local->remote-syncer [local-change]))]
+          (s/assert ::sync-local->remote!-result r)
+          (cond
+            succ
+            (.schedule this ::idle nil)
 
-          need-sync-remote
-          (.schedule this ::remote->local=>local->remote nil)
-
-          unknown
-          (do
-            (debug/pprint "local->remote" unknown)
-            (.schedule this ::idle nil))))))
-  IStoppable
-  (-stop! [_]
-    (when-not stopped
-      (set! stopped true)
-      (ws-stop! _*ws)
-      (offer! stop-sync-chan true)
-      (stop-local->remote! local->remote-syncer)
-      (stop-remote->local! remote->local-syncer)
-      (debug/pprint ["stop sync-manager, graph-uuid" graph-uuid "base-path" base-path])
-      (swap! *sync-state sync-state--update-state ::stop))))
+            need-sync-remote
+            (do (drain-chan ops-chan)
+                (>! ops-chan {:remote->local true})
+                (>! ops-chan {:local->remote {:local local-change}})
+                (.schedule this ::idle nil))
+
+            unknown
+            (do
+              (debug/pprint "local->remote" unknown)
+              (.schedule this ::idle nil))))))
+    IStoppable
+    (-stop! [_]
+      (when-not stopped
+        (set! stopped true)
+        (ws-stop! _*ws)
+        (offer! stop-sync-chan true)
+        (async/close! ops-chan)
+        (stop-local->remote! local->remote-syncer)
+        (stop-remote->local! remote->local-syncer)
+        (debug/pprint ["stop sync-manager, graph-uuid" graph-uuid "base-path" base-path])
+        (swap! *sync-state sync-state--update-state ::stop))))
 
 (defn sync-manager [user-uuid graph-uuid base-path repo txid *sync-state full-sync-chan stop-sync-chan
                     remote->local-sync-chan local->remote-sync-chan local-changes-chan]
@@ -1318,11 +1372,11 @@
     (.set-local->remote-syncer! remote->local-syncer local->remote-syncer)
     (->SyncManager graph-uuid base-path *sync-state local->remote-syncer remote->local-syncer
                    full-sync-chan stop-sync-chan
-                   remote->local-sync-chan local->remote-sync-chan local-changes-chan nil *txid nil nil nil false)))
+                   remote->local-sync-chan local->remote-sync-chan local-changes-chan nil *txid nil nil nil false nil)))
 
 (def full-sync-chan (chan 1))
 (def stop-sync-chan (chan 1))
-(def remote->local-sync-chan (chan))
+(def remote->local-sync-chan (chan 1))
 (def local->remote-sync-chan (chan))
 
 (defn sync-stop []
@@ -1371,10 +1425,9 @@
               ;; set-env
               (set-env rsapi config/FILE-SYNC-PROD?)
 
-              ;; drain `local-changes-chan`
-              (->> (repeatedly #(poll! local-changes-chan))
-                   (take-while identity))
+              (drain-chan local-changes-chan)
               (poll! stop-sync-chan)
+              (poll! remote->local-sync-chan)
               ;; update global state when *sync-state changes
               (add-watch *sync-state ::update-global-state
                          (fn [_ _ _ n]
@@ -1382,7 +1435,7 @@
               (.start sm)
 
               (state/set-file-sync-manager sm)
-
+              (offer! remote->local-sync-chan true)
               (offer! full-sync-chan true)
 
               ;; watch :network/online?

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

@@ -9,13 +9,14 @@
             [frontend.handler.page :as page-handler]
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.ui :as ui-handler]
-            [frontend.util :as util]
+            [logseq.graph-parser.util :as gp-util]
             [lambdaisland.glogi :as log]
             [electron.ipc :as ipc]
             [promesa.core :as p]
-            [frontend.state :as state]))
+            [frontend.state :as state]
+            [frontend.encrypt :as encrypt]))
 
-;; all IPC paths must be normalized! (via util/path-normalize)
+;; all IPC paths must be normalized! (via gp-util/path-normalize)
 
 (defn- set-missing-block-ids!
   [content]
@@ -42,12 +43,14 @@
 (defn handle-changed!
   [type {:keys [dir path content stat] :as payload}]
   (when dir
-    (let [path (util/path-normalize path)
+    (let [path (gp-util/path-normalize path)
           repo (config/get-local-repo dir)
           pages-metadata-path (config/get-pages-metadata-path)
           {:keys [mtime]} stat
           db-content (or (db/get-file repo path) "")]
-      (when (or content (= type "unlink"))
+      (when (and (or content (= type "unlink"))
+                 (not (encrypt/content-encrypted? content))
+                 (not (:encryption/graph-parsing? @state/state)))
         (cond
           (and (= "add" type)
                (not= (string/trim content) (string/trim db-content))

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

@@ -2,6 +2,7 @@
   (:require [electron.ipc :as ipc]
             [electron.listener :as el]
             [frontend.components.page :as page]
+            [frontend.components.reference :as reference]
             [frontend.config :as config]
             [frontend.context.i18n :as i18n]
             [frontend.db :as db]
@@ -164,6 +165,7 @@
 (defn- register-components-fns!
   []
   (state/set-page-blocks-cp! page/page-blocks-cp)
+  (state/set-component! :block/linked-references reference/block-linked-references)
   (command-palette/register-global-shortcut-commands))
 
 (defn start!

+ 8 - 0
src/main/frontend/handler/common.cljs

@@ -80,6 +80,14 @@
       (state/set-config! repo-url config)
       config)))
 
+(defn read-metadata!
+  [content]
+  (try
+    (reader/read-string content)
+    (catch :default e
+      (log/error :parse/metadata-failed e)
+      {})))
+
 (defn get-page-default-properties
   [page-name]
   {:title page-name

+ 3 - 2
src/main/frontend/handler/draw.cljs

@@ -7,6 +7,7 @@
             [frontend.handler.file :as file-handler]
             [frontend.state :as state]
             [frontend.util :as util]
+            [logseq.graph-parser.config :as gp-config]
             [promesa.core :as p]))
 
 (defn create-draws-directory!
@@ -14,7 +15,7 @@
   (when repo
     (let [repo-dir (config/get-repo-dir repo)]
       (util/p-handle
-       (fs/mkdir! (str repo-dir (str "/" config/default-draw-directory)))
+       (fs/mkdir! (str repo-dir (str "/" gp-config/default-draw-directory)))
        (fn [_result] nil)
        (fn [_error] nil)))))
 
@@ -61,6 +62,6 @@
   [current-file]
   (when-let [repo (state/get-current-repo)]
     (p/let [exists? (fs/file-exists? (config/get-repo-dir repo)
-                                     (str config/default-draw-directory current-file))]
+                                     (str gp-config/default-draw-directory current-file))]
       (when-not exists?
         (save-excalidraw! current-file default-content)))))

+ 40 - 40
src/main/frontend/handler/editor.cljs

@@ -52,6 +52,7 @@
             [medley.core :as medley]
             [promesa.core :as p]
             [frontend.util.keycode :as keycode]
+            [logseq.graph-parser.util :as gp-util]
             ["path" :as path]))
 
 ;; FIXME: should support multiple images concurrently uploading
@@ -157,13 +158,12 @@
 (defn open-block-in-sidebar!
   [block-id]
   (when block-id
-    (when-let [block (db/pull [:block/uuid block-id])]
+    (when-let [block (db/entity [:block/uuid block-id])]
       (let [page? (nil? (:block/page block))]
         (state/sidebar-add-block!
          (state/get-current-repo)
          (:db/id block)
-         (if page? :page :block)
-         block)))))
+         (if page? :page :block))))))
 
 (defn reset-cursor-range!
   [node]
@@ -255,7 +255,7 @@
 (defn- another-block-with-same-id-exists?
   [current-id block-id]
   (and (string? block-id)
-       (util/uuid-string? block-id)
+       (gp-util/uuid-string? block-id)
        (not= current-id (cljs.core/uuid block-id))
        (db/entity [:block/uuid (cljs.core/uuid block-id)])))
 
@@ -336,7 +336,7 @@
   (if (and (state/enable-timetracking?)
            (not= (:block/content block) value))
     (let [format (:block/format block)
-          new-marker (last (util/safe-re-find (marker/marker-pattern format) (or value "")))
+          new-marker (last (gp-util/safe-re-find (marker/marker-pattern format) (or value "")))
           new-value (with-marker-time value block format
                       new-marker
                       (:block/marker block))]
@@ -453,11 +453,12 @@
 (declare save-current-block!)
 (defn outliner-insert-block!
   [config current-block new-block {:keys [sibling? keep-uuid? replace-empty-target?]}]
-  (let [ref-top-block? (and (:ref? config)
-                            (not (:ref-child? config)))
+  (let [ref-query-top-block? (and (or (:ref? config)
+                                      (:custom-query? config))
+                                  (not (:ref-query-child? config)))
         has-children? (db/has-children? (:block/uuid current-block))
         sibling? (cond
-                   ref-top-block?
+                   ref-query-top-block?
                    false
 
                    (boolean? sibling?)
@@ -480,10 +481,10 @@
   (let [current-page (state/get-current-page)
         block-id (or
                   (and (:id config)
-                       (util/uuid-string? (:id config))
+                       (gp-util/uuid-string? (:id config))
                        (:id config))
                   (and current-page
-                       (util/uuid-string? current-page)
+                       (gp-util/uuid-string? current-page)
                        current-page))]
     (= uuid (and block-id (medley/uuid block-id)))))
 
@@ -657,12 +658,12 @@
             new-block))))))
 
 (defn insert-first-page-block-if-not-exists!
-  ([page-name]
-   (insert-first-page-block-if-not-exists! page-name {}))
-  ([page-name opts]
-   (when (and (string? page-name)
-              (not (string/blank? page-name)))
-     (state/pub-event! [:page/create page-name opts]))))
+  ([page-title]
+   (insert-first-page-block-if-not-exists! page-title {}))
+  ([page-title opts]
+   (when (and (string? page-title)
+              (not (string/blank? page-title)))
+     (state/pub-event! [:page/create page-title opts]))))
 
 (defn properties-block
   [properties format page]
@@ -1140,19 +1141,17 @@
   []
   (when-let [page (get-nearest-page)]
     (let [page-name (string/lower-case page)
-          block? (util/uuid-string? page-name)]
+          block? (gp-util/uuid-string? page-name)]
       (when-let [page (db/get-page page-name)]
         (if block?
           (state/sidebar-add-block!
            (state/get-current-repo)
            (:db/id page)
-           :block
-           page)
+           :block)
           (state/sidebar-add-block!
            (state/get-current-repo)
            (:db/id page)
-           :page
-           {:page page}))))))
+           :page))))))
 
 (defn zoom-in! []
   (if (state/editing?)
@@ -1172,7 +1171,7 @@
     (let [page (state/get-current-page)
           block-id (and
                     (string? page)
-                    (util/uuid-string? page)
+                    (gp-util/uuid-string? page)
                     (medley/uuid page))]
       (when block-id
         (let [block-parent (db/get-block-parent block-id)]
@@ -1293,7 +1292,7 @@
                  elem (and input-id (gdom/getElement input-id))
                  db-content (:block/content db-block)
                  db-content-without-heading (and db-content
-                                                 (util/safe-subs db-content (:block/level db-block)))
+                                                 (gp-util/safe-subs db-content (:block/level db-block)))
                  value (and elem (gobj/get elem "value"))]
              (cond
                force?
@@ -1760,7 +1759,7 @@
             edit-content (or (state/sub [:editor/content id]) "")]
         (or
          @*selected-text
-         (util/safe-subs edit-content pos current-pos))))))
+         (gp-util/safe-subs edit-content pos current-pos))))))
 
 (defn close-autocomplete-if-outside
   [input]
@@ -1773,7 +1772,7 @@
       (let [value (gobj/get input "value")
             pos (state/get-editor-last-pos)
             current-pos (cursor/pos input)
-            between (util/safe-subs value (min pos current-pos) (max pos current-pos))]
+            between (gp-util/safe-subs value (min pos current-pos) (max pos current-pos))]
         (when (and between
                    (or
                     (string/includes? between "[")
@@ -2034,7 +2033,7 @@
 (defn- last-top-level-child?
   [{:keys [id]} current-node]
   (when id
-    (when-let [entity (if (util/uuid-string? (str id))
+    (when-let [entity (if (gp-util/uuid-string? (str id))
                         (db/entity [:block/uuid (uuid id)])
                         (db/entity [:block/name (util/page-name-sanity-lc id)]))]
       (= (:block/uuid entity) (tree/-get-parent-id current-node)))))
@@ -2861,7 +2860,7 @@
         (string/join "\n"
                      (mapv (fn [p] (->> (string/trim p)
                                         ((fn [p]
-                                           (if (util/safe-re-find (if (= format :org)
+                                           (if (gp-util/safe-re-find (if (= format :org)
                                                                     #"\s*\*+\s+"
                                                                     #"\s*-\s+") p)
                                              p
@@ -2883,11 +2882,13 @@
   [text e]
   (let [copied-blocks (state/get-copied-blocks)
         copied-block-ids (:copy/block-ids copied-blocks)
+        copied-graph (:copy/graph copied-blocks)
         input (state/get-input)
         *stop-event? (atom true)]
     (cond
       ;; Internal blocks by either copy or cut blocks
       (and
+       (= copied-graph (state/get-current-repo))
        (or (seq copied-block-ids)
            (seq (:copy/full-blocks copied-blocks)))
        text
@@ -2924,9 +2925,9 @@
       ;; from external
       (let [format (or (db/get-page-format (state/get-current-page)) :markdown)]
         (match [format
-                (nil? (util/safe-re-find #"(?m)^\s*(?:[-+*]|#+)\s+" text))
-                (nil? (util/safe-re-find #"(?m)^\s*\*+\s+" text))
-                (nil? (util/safe-re-find #"(?:\r?\n){2,}" text))]
+                (nil? (gp-util/safe-re-find #"(?m)^\s*(?:[-+*]|#+)\s+" text))
+                (nil? (gp-util/safe-re-find #"(?m)^\s*\*+\s+" text))
+                (nil? (gp-util/safe-re-find #"(?:\r?\n){2,}" text))]
           [:markdown false _ _]
           (paste-text-parseable format text)
 
@@ -3211,7 +3212,7 @@
     :or {collapse? false expanded? false incremental? true root-block nil}}]
   (when-let [page (or (state/get-current-page)
                       (date/today))]
-    (let [block? (util/uuid-string? page)
+    (let [block? (gp-util/uuid-string? page)
           block-id (or root-block (and block? (uuid page)))
           blocks (if block-id
                    (db/get-block-and-children (state/get-current-repo) block-id)
@@ -3473,12 +3474,11 @@
   1. References.
   2. Custom queries."
   [block config]
-  (if (or (:ref? config)
-          (:custom-query? config))
-    (and
-     (seq (:block/children block))
-     (or
-      (:custom-query? config)
-      (>= (:ref/level block)
-          (state/get-ref-open-blocks-level))))
-    (util/collapsed? block)))
+  (or
+   (and
+    (or (:ref? config) (:custom-query? config))
+    (>= (inc (:block/level block))
+        (state/get-ref-open-blocks-level))
+    ;; has children
+    (first (:block/_parent (db/entity (:db/id block)))))
+   (util/collapsed? block)))

+ 58 - 8
src/main/frontend/handler/events.cljs

@@ -25,6 +25,7 @@
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.file :as file-handler]
             [frontend.handler.route :as route-handler]
+            [frontend.handler.web.nfs :as nfs-handler]
             [frontend.modules.shortcut.core :as st]
             [frontend.modules.outliner.file :as outliner-file]
             [frontend.commands :as commands]
@@ -38,7 +39,9 @@
             [frontend.fs :as fs]
             [clojure.string :as string]
             [frontend.util.persist-var :as persist-var]
-            [frontend.fs.sync :as sync]))
+            [frontend.fs.sync :as sync]
+            [frontend.components.encryption :as encryption]
+            [frontend.encrypt :as encrypt]))
 
 ;; TODO: should we move all events here?
 
@@ -233,7 +236,8 @@
                         {:label "diff__cp"}))))
 
 (defmethod handle :modal/display-file-version [[_ path content hash]]
-  (state/set-modal! #(git-component/file-specific-version path hash content)))
+  (p/let [content (when content (encrypt/decrypt content))]
+    (state/set-modal! #(git-component/file-specific-version path hash content))))
 
 (defmethod handle :graph/ready [[_ repo]]
   (search-handler/rebuild-indices-when-stale! repo)
@@ -265,6 +269,11 @@
       (plugin/open-focused-settings-modal! title))
     (state/close-sub-modal! "ls-focused-settings-modal")))
 
+(defmethod handle :go/proxy-settings [[_ agent-opts]]
+  (state/set-sub-modal!
+    (fn [_] (plugin/user-proxy-settings-panel agent-opts))
+    {:id :https-proxy-panel :center? true}))
+
 
 (defmethod handle :redirect-to-home [_]
   (page-handler/create-today-journal!))
@@ -342,16 +351,57 @@
 
 (defmethod handle :file-watcher/changed [[_ ^js event]]
   (let [type (.-event event)
-        payload (js->clj event :keywordize-keys true)
-        payload' (-> payload
-                     (update :path js/decodeURI))]
-    (prn ::fs-watcher payload)
-    (fs-watcher/handle-changed! type payload')
-    (sync/file-watch-handler type payload')))
+        payload (-> event
+                    (js->clj :keywordize-keys true)
+                    (update :path js/decodeURI))]
+    (fs-watcher/handle-changed! type payload)
+    (sync/file-watch-handler type payload)))
 
 (defmethod handle :rebuild-slash-commands-list [[_]]
   (page-handler/rebuild-slash-commands-list!))
 
+(defmethod handle :graph/ask-for-re-index [[_ *multiple-windows?]]
+  (if (and (util/atom? *multiple-windows?) @*multiple-windows?)
+    (handle
+     [:modal/show
+      [:div
+       [:p (t :re-index-multiple-windows-warning)]]])
+    (handle
+     [:modal/show
+      [:div {:style {:max-width 700}}
+       [:p (t :re-index-discard-unsaved-changes-warning)]
+       (ui/button
+         (t :yes)
+         :autoFocus "on"
+         :large? true
+         :on-click (fn []
+                     (state/close-modal!)
+                     (repo-handler/re-index!
+                      nfs-handler/rebuild-index!
+                      page-handler/create-today-journal!)))]])))
+
+;; encryption
+(defmethod handle :modal/encryption-setup-dialog [[_ repo-url close-fn]]
+  (state/set-modal!
+   (encryption/encryption-setup-dialog repo-url close-fn)))
+
+(defmethod handle :modal/encryption-input-secret-dialog [[_ repo-url db-encrypted-secret close-fn]]
+  (state/set-modal!
+   (encryption/encryption-input-secret-dialog
+    repo-url
+    db-encrypted-secret
+    close-fn)))
+
+(defmethod handle :journal/insert-template [[_ page-name]]
+  (let [page-name (util/page-name-sanity-lc page-name)]
+    (when-let [page (db/pull [:block/name page-name])]
+      (when (db/page-empty? (state/get-current-repo) page-name)
+        (when-let [template (state/get-default-journal-template)]
+          (editor-handler/insert-template!
+           nil
+           template
+           {:target page}))))))
+
 (defn run!
   []
   (let [chan (state/get-events-chan)]

+ 5 - 4
src/main/frontend/handler/extract.cljs

@@ -11,6 +11,7 @@
             [frontend.state :as state]
             [frontend.text :as text]
             [frontend.util :as util]
+            [logseq.graph-parser.util :as gp-util]
             [frontend.util.property :as property]
             [lambdaisland.glogi :as log]))
 
@@ -29,9 +30,9 @@
                                     (string? title)
                                     title))
             file-name (when-let [file-name (last (string/split file #"/"))]
-                        (let [result (first (util/split-last "." file-name))]
+                        (let [result (first (gp-util/split-last "." file-name))]
                           (if (config/mldoc-support? (string/lower-case (util/get-file-ext file)))
-                            (string/replace result "." "/")
+                            (util/url-decode (string/replace result "." "/"))
                             result)))]
         (or property-name
             (if (= (state/page-name-order) "heading")
@@ -90,10 +91,10 @@
                                        aliases)
                                      (remove nil?))]
                         (cond->
-                          (util/remove-nils
+                          (gp-util/remove-nils
                            (assoc
                             (block/page-name->map page false)
-                            :block/file {:file/path (util/path-normalize file)}))
+                            :block/file {:file/path (gp-util/path-normalize file)}))
                           (seq properties)
                           (assoc :block/properties properties)
 

+ 14 - 2
src/main/frontend/handler/file.cljs

@@ -16,6 +16,7 @@
             [frontend.handler.ui :as ui-handler]
             [frontend.state :as state]
             [frontend.util :as util]
+            [logseq.graph-parser.util :as gp-util]
             [lambdaisland.glogi :as log]
             [promesa.core :as p]
             [frontend.mobile.util :as mobile]
@@ -75,7 +76,7 @@
                                         (seq images)
                                         (merge (zipmap images (repeat (count images) ""))))
                         file-contents (for [[file content] file-contents]
-                                        {:file/path (util/path-normalize file)
+                                        {:file/path (gp-util/path-normalize file)
                                          :file/content content})]
                     (ok-handler file-contents))))
         (p/catch (fn [error]
@@ -115,7 +116,7 @@
 
                 :else
                 file)
-         file (util/path-normalize file)
+         file (gp-util/path-normalize file)
          new? (nil? (db/entity [:file/path file]))]
      (db/set-file-content! repo-url file content)
      (let [format (format/get-format file)
@@ -271,6 +272,17 @@
     (when-let [dir (config/get-repo-dir repo)]
       (fs/watch-dir! dir))))
 
+(defn create-metadata-file
+  [repo-url encrypted?]
+  (let [repo-dir (config/get-repo-dir repo-url)
+        path (str config/app-name "/" config/metadata-file)
+        file-path (str "/" path)
+        default-content (if encrypted? "{:db/encrypted? true}" "{}")]
+    (p/let [_ (fs/mkdir-if-not-exists (str repo-dir "/" config/app-name))
+            file-exists? (fs/create-if-not-exists repo-url repo-dir file-path default-content)]
+      (when-not file-exists?
+        (reset-file! repo-url path default-content)))))
+
 (defn create-pages-metadata-file
   [repo-url]
   (let [repo-dir (config/get-repo-dir repo-url)

+ 3 - 2
src/main/frontend/handler/graph.cljs

@@ -4,7 +4,8 @@
             [frontend.db :as db]
             [frontend.db.default :as default-db]
             [frontend.state :as state]
-            [frontend.util :as util]))
+            [frontend.util :as util]
+            [logseq.graph-parser.util :as gp-util]))
 
 (defn- build-links
   [links]
@@ -45,7 +46,7 @@
                   ;; slow
 (defn- uuid-or-asset?
   [id]
-  (or (util/uuid-string? id)
+  (or (gp-util/uuid-string? id)
       (string/starts-with? id "../assets/")
       (= id "..")
       (string/starts-with? id "assets/")

+ 61 - 1
src/main/frontend/handler/metadata.cljs

@@ -1,12 +1,44 @@
 (ns frontend.handler.metadata
-  (:require [cljs.pprint]
+  (:require [cljs.reader :as reader]
+            [cljs.pprint]
+            [clojure.string :as string]
+            [datascript.db :as ddb]
             [frontend.config :as config]
             [frontend.db :as db]
             [frontend.fs :as fs]
             [frontend.handler.common :as common-handler]
             [frontend.handler.file :as file-handler]
+            [frontend.state :as state]
             [promesa.core :as p]))
 
+(def default-metadata-str "{}")
+
+(defn set-metadata!
+  [k v]
+  (when-let [repo (state/get-current-repo)]
+    (let [encrypted? (= k :db/encrypted-secret)
+          path (config/get-metadata-path)
+          file-content (db/get-file path)]
+      (p/let [_ (file-handler/create-metadata-file repo false)]
+        (let [metadata-str (or file-content default-metadata-str)
+              metadata (try
+                         (reader/read-string metadata-str)
+                         (catch js/Error e
+                           (println "Parsing metadata.edn failed: ")
+                           (js/console.dir e)
+                           {}))
+              new-metadata (cond
+                             (= k :block/properties)
+                             (update metadata :block/properties v) ; v should be a function
+                             :else
+                             (let [ks (if (vector? k) k [k])]
+                               (assoc-in metadata ks v)))
+              new-metadata (if encrypted?
+                             (assoc new-metadata :db/encrypted? true)
+                             new-metadata)
+              new-content (pr-str new-metadata)]
+          (file-handler/set-file-content! repo path new-content))))))
+
 (defn set-pages-metadata!
   [repo]
   (let [path (config/get-pages-metadata-path repo)
@@ -23,3 +55,31 @@
                         path
                         new-content
                         {})))))
+
+(defn set-db-encrypted-secret!
+  [encrypted-secret]
+  (when-not (string/blank? encrypted-secret)
+    (set-metadata! :db/encrypted-secret encrypted-secret)))
+
+(defn- handler-properties!
+  [all-properties properties-tx]
+  (reduce
+   (fn [acc datom]
+     (let [v (:v datom)
+           id (or (get v :id)
+                  (get v :title))]
+       (if id
+         (let [added? (ddb/datom-added datom)
+               remove-all-properties? (and (not added?)
+                                           ;; only id
+                                           (= 1 (count v)))]
+           (if remove-all-properties?
+             (dissoc acc id)
+             (assoc acc id v)))
+         acc)))
+   all-properties
+   properties-tx))
+
+(defn update-properties!
+  [properties-tx]
+  (set-metadata! :block/properties #(handler-properties! % properties-tx)))

+ 32 - 31
src/main/frontend/handler/page.cljs

@@ -33,6 +33,8 @@
             [lambdaisland.glogi :as log]
             [promesa.core :as p]
             [frontend.mobile.util :as mobile-util]
+            [logseq.graph-parser.util :as gp-util]
+            [logseq.graph-parser.config :as gp-config]
             [goog.functions :refer [debounce]]))
 
 (defn- get-directory
@@ -47,7 +49,7 @@
                  (date/journal-title->default title)
                  (util/page-name-sanity (string/lower-case title)))]
     ;; Win10 file path has a length limit of 260 chars
-    (util/safe-subs s 0 200)))
+    (gp-util/safe-subs s 0 200)))
 
 (defn get-page-file-path
   ([] (get-page-file-path (state/get-current-page)))
@@ -89,12 +91,16 @@
 (defn- build-page-tx [format properties page journal?]
   (when (:block/uuid page)
     (let [page-entity [:block/uuid (:block/uuid page)]
-          create-title? (create-title-property? journal? (:block/name page))
+          create-title? (create-title-property? journal?
+                                                (or
+                                                 (:block/original-name page)
+                                                 (:block/name page)))
           page (if (seq properties) (assoc page :block/properties properties) page)]
       (cond
         create-title?
-        [page
-         (default-properties-block (build-title page) format page-entity properties)]
+        (let [properties-block (default-properties-block (build-title page) format page-entity properties)]
+          [page
+           properties-block])
 
         (seq properties)
         [page (editor-handler/properties-block properties format page-entity)]
@@ -115,7 +121,7 @@
          title (util/remove-boundary-slashes title)
          page-name (util/page-name-sanity-lc title)
          repo (state/get-current-repo)]
-     (when-not (db/page-exists? page-name)
+     (when (db/page-empty? repo page-name)
        (let [pages    (if split-namespace?
                         (util/split-namespace-pages title)
                         [title])
@@ -128,16 +134,19 @@
                            ;; for namespace pages, only last page need properties
                            drop-last
                            (mapcat #(build-page-tx format nil % journal?))
-                           (remove nil?))
+                           (remove nil?)
+                           (remove (fn [m]
+                                     (some? (db/entity [:block/name (:block/name m)])))))
              last-txs (build-page-tx format properties (last pages) journal?)
              txs      (concat txs last-txs)]
-         (db/transact! txs)))
+         (when (seq txs)
+           (db/transact! txs)))
 
-     (when create-first-block?
-       (when (or
-              (db/page-empty? repo (:db/id (db/entity [:block/name page-name])))
-              (create-title-property? journal? page-name))
-         (editor-handler/api-insert-new-block! "" {:page page-name})))
+       (when create-first-block?
+         (when (or
+                (db/page-empty? repo (:db/id (db/entity [:block/name page-name])))
+                (create-title-property? journal? page-name))
+           (editor-handler/api-insert-new-block! "" {:page page-name}))))
 
      (when redirect?
        (route-handler/redirect-to-page! page-name))
@@ -359,7 +368,7 @@
   "Only accepts unsanitized page names"
   [old-name new-name redirect?]
   (let [old-page-name       (util/page-name-sanity-lc old-name)
-        new-file-name       (util/page-name-sanity new-name true)
+        new-file-name       (util/file-name-sanity new-name)
         new-page-name       (util/page-name-sanity-lc new-name)
         repo                (state/get-current-repo)
         page                (db/pull [:block/name old-page-name])]
@@ -625,8 +634,8 @@
   (->> (db/get-all-pages repo)
        (remove (fn [p]
                  (let [name (:block/name p)]
-                   (or (util/uuid-string? name)
-                       (config/draw? name)
+                   (or (gp-util/uuid-string? name)
+                       (gp-config/draw? name)
                        (db/built-in-pages-names (string/upper-case name))))))
        (common-handler/fix-pages-timestamps)))
 
@@ -669,17 +678,17 @@
         q (or
            @editor-handler/*selected-text
            (when (state/sub :editor/show-page-search-hashtag?)
-             (util/safe-subs edit-content pos current-pos))
+             (gp-util/safe-subs edit-content pos current-pos))
            (when (> (count edit-content) current-pos)
-             (util/safe-subs edit-content pos current-pos)))]
+             (gp-util/safe-subs edit-content pos current-pos)))]
     (if (state/sub :editor/show-page-search-hashtag?)
       (fn [chosen _click?]
         (state/set-editor-show-page-search! false)
-        (let [wrapped? (= "[[" (util/safe-subs edit-content (- pos 2) pos))
+        (let [wrapped? (= "[[" (gp-util/safe-subs edit-content (- pos 2) pos))
               chosen (if (string/starts-with? chosen "New page: ") ;; FIXME: What if a page named "New page: XXX"?
                        (subs chosen 10)
                        chosen)
-              chosen (if (and (util/safe-re-find #"\s+" chosen) (not wrapped?))
+              chosen (if (and (gp-util/safe-re-find #"\s+" chosen) (not wrapped?))
                        (util/format "[[%s]]" chosen)
                        chosen)
               q (if @editor-handler/*selected-text "" q)
@@ -719,31 +728,24 @@
                 (and (= "local" repo) (not (mobile-util/is-native-platform?))))
         (let [title (date/today)
               today-page (util/page-name-sanity-lc title)
-              template (state/get-default-journal-template)
               format (state/get-preferred-format repo)
               file-name (date/journal-title->default title)
               path (str (config/get-journals-directory) "/" file-name "."
                         (config/get-file-extension format))
               file-path (str "/" path)
-              repo-dir (config/get-repo-dir repo)]
+              repo-dir (config/get-repo-dir repo)
+              template (state/get-default-journal-template)]
           (p/let [file-exists? (fs/file-exists? repo-dir file-path)
                   file-content (when file-exists?
                                  (fs/read-file repo-dir file-path))]
             (when (and (db/page-empty? repo today-page)
-                       (not (model/journal-day-exists? repo
-                                                       (date/journal-title->int (date/today))))
                        (or (not file-exists?)
                            (and file-exists? (string/blank? file-content))))
               (create! title {:redirect? false
                               :split-namespace? false
                               :create-first-block? (not template)
                               :journal? true})
-              (when template
-                (let [page (db/pull [:block/name today-page])]
-                  (editor-handler/insert-template!
-                   nil
-                   template
-                   {:target page})))
+              (state/pub-event! [:journal/insert-template today-page])
               (ui-handler/re-render-root!))))))))
 
 (defn open-today-in-sidebar
@@ -752,8 +754,7 @@
     (state/sidebar-add-block!
      (state/get-current-repo)
      (:db/id page)
-     :page
-     page)))
+     :page)))
 
 (defn open-file-in-default-app []
   (when-let [file-path (and (util/electron?) (get-page-file-path))]

+ 58 - 29
src/main/frontend/handler/repo.cljs

@@ -22,9 +22,11 @@
             [promesa.core :as p]
             [shadow.resource :as rc]
             [frontend.db.persist :as db-persist]
+            [logseq.graph-parser.util :as gp-util]
             [electron.ipc :as ipc]
             [clojure.set :as set]
-            [clojure.core.async :as async]))
+            [clojure.core.async :as async]
+            [frontend.encrypt :as encrypt]))
 
 ;; Project settings should be checked in two situations:
 ;; 1. User changes the config.edn directly in logseq.com (fn: alter-file)
@@ -127,16 +129,19 @@
             (ui-handler/re-render-root!)))))))
 
 (defn create-default-files!
-  [repo-url]
-  (spec/validate :repos/url repo-url)
-  (let [repo-dir (config/get-repo-dir repo-url)]
-    (p/let [_ (fs/mkdir-if-not-exists (str repo-dir "/" config/app-name))
-            _ (fs/mkdir-if-not-exists (str repo-dir "/" config/app-name "/" config/recycle-dir))
-            _ (fs/mkdir-if-not-exists (str repo-dir "/" (config/get-journals-directory)))
-            _ (create-config-file-if-not-exists repo-url)
-            _ (create-contents-file repo-url)
-            _ (create-custom-theme repo-url)]
-      (state/pub-event! [:page/create-today-journal repo-url]))))
+  ([repo-url]
+   (create-default-files! repo-url false))
+  ([repo-url encrypted?]
+   (spec/validate :repos/url repo-url)
+   (let [repo-dir (config/get-repo-dir repo-url)]
+     (p/let [_ (fs/mkdir-if-not-exists (str repo-dir "/" config/app-name))
+             _ (fs/mkdir-if-not-exists (str repo-dir "/" config/app-name "/" config/recycle-dir))
+             _ (fs/mkdir-if-not-exists (str repo-dir "/" (config/get-journals-directory)))
+             _ (file-handler/create-metadata-file repo-url encrypted?)
+             _ (create-config-file-if-not-exists repo-url)
+             _ (create-contents-file repo-url)
+             _ (create-custom-theme repo-url)]
+       (state/pub-event! [:page/create-today-journal repo-url])))))
 
 (defn- load-pages-metadata!
   "force?: if set true, skip the metadata timestamp range check"
@@ -180,8 +185,6 @@
 
 (defn- parse-and-load-file!
   [repo-url file new-graph?]
-  (state/set-parsing-state! (fn [m]
-                              (assoc m :current-parsing-file (:file/path file))))
   (try
     (file-handler/alter-file repo-url
                              (:file/path file)
@@ -196,18 +199,22 @@
                               (update m :finished inc))))
 
 (defn- after-parse
-  [repo-url files file-paths 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]
   (load-pages-metadata! repo-url file-paths files true)
-  (when (:new-graph? opts)
-    (create-default-files! repo-url))
+  (when (or (:new-graph? opts) (not (:refresh? opts)))
+    (if (and (not db-encrypted?) (state/enable-encryption? repo-url))
+      (state/pub-event! [:modal/encryption-setup-dialog repo-url
+                         #(create-default-files! repo-url %)])
+      (create-default-files! repo-url db-encrypted?)))
   (when re-render?
     (ui-handler/re-render-root! re-render-opts))
   (state/pub-event! [:graph/added repo-url opts])
   (state/reset-parsing-state!)
+  (state/set-loading-files! repo-url false)
   (async/offer! graph-added-chan true))
 
-(defn- parse-files-and-create-default-files!
-  [repo-url files delete-files delete-blocks file-paths re-render? re-render-opts opts]
+(defn- parse-files-and-create-default-files-inner!
+  [repo-url files delete-files delete-blocks file-paths db-encrypted? re-render? re-render-opts opts]
   (let [support-files (filter
                        (fn [file]
                          (let [format (format/get-format (:file/path file))]
@@ -232,28 +239,50 @@
     (if util/node-test?
       (do
         (doseq [file support-files']
+          (state/set-parsing-state! (fn [m]
+                                      (assoc m :current-parsing-file (:file/path file))))
           (parse-and-load-file! repo-url file new-graph?))
-        (after-parse repo-url files file-paths 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
-            (parse-and-load-file! repo-url file new-graph?)
+            (state/set-parsing-state! (fn [m]
+                                        (assoc m :current-parsing-file (:file/path file))))
             (async/<! (async/timeout 10))
+            (parse-and-load-file! repo-url file new-graph?)
             (recur))
-          (after-parse repo-url files file-paths 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))))
     graph-added-chan))
 
-(defn- update-parsing-state!
-  [repo-url]
-  (state/set-loading-files! repo-url false))
+(defn- parse-files-and-create-default-files!
+  [repo-url files delete-files delete-blocks file-paths db-encrypted? re-render? re-render-opts opts]
+  (if db-encrypted?
+    (p/let [files (p/all
+                   (map (fn [file]
+                          (p/let [content (encrypt/decrypt (:file/content file))]
+                            (assoc file :file/content content)))
+                     files))]
+      (parse-files-and-create-default-files-inner! repo-url files delete-files delete-blocks file-paths db-encrypted? re-render? re-render-opts opts))
+    (parse-files-and-create-default-files-inner! repo-url files delete-files delete-blocks file-paths db-encrypted? re-render? re-render-opts opts)))
 
 (defn parse-files-and-load-to-db!
   [repo-url files {:keys [delete-files delete-blocks re-render? re-render-opts _refresh?] :as opts
                    :or {re-render? true}}]
-  (update-parsing-state! repo-url)
-
-  (let [file-paths (map :file/path files)]
-    (parse-files-and-create-default-files! repo-url files delete-files delete-blocks file-paths re-render? re-render-opts opts)))
+  (let [file-paths (map :file/path files)
+        metadata-file (config/get-metadata-path)
+        metadata-content (some #(when (= (:file/path %) metadata-file)
+                                  (:file/content %)) files)
+        metadata (when metadata-content
+                   (common-handler/read-metadata! metadata-content))
+        db-encrypted? (:db/encrypted? metadata)
+        db-encrypted-secret (if db-encrypted? (:db/encrypted-secret metadata) nil)]
+    (if db-encrypted?
+      (let [close-fn #(parse-files-and-create-default-files! repo-url files delete-files delete-blocks file-paths db-encrypted? re-render? re-render-opts opts)]
+        (state/set-state! :encryption/graph-parsing? true)
+        (state/pub-event! [:modal/encryption-input-secret-dialog repo-url
+                           db-encrypted-secret
+                           close-fn]))
+      (parse-files-and-create-default-files! repo-url files delete-files delete-blocks file-paths db-encrypted? re-render? re-render-opts opts))))
 
 (defn load-repo-to-db!
   [repo-url {:keys [diffs nfs-files refresh? new-graph? empty-graph?]}]
@@ -300,7 +329,7 @@
                              [])
               add-or-modify-files (some->>
                                    (concat modify-files add-files)
-                                   (util/remove-nils))
+                                   (gp-util/remove-nils))
               options {:delete-files (concat delete-files delete-pages)
                        :delete-blocks delete-blocks
                        :re-render? true}]

+ 2 - 1
src/main/frontend/handler/route.cljs

@@ -8,6 +8,7 @@
             [frontend.state :as state]
             [frontend.text :as text]
             [frontend.util :as util]
+            [logseq.graph-parser.util :as gp-util]
             [medley.core :as medley]
             [reitit.frontend.easy :as rfe]))
 
@@ -77,7 +78,7 @@
     "Create a new page"
     :page
     (let [name (:name path-params)
-          block? (util/uuid-string? name)]
+          block? (gp-util/uuid-string? name)]
       (if block?
         (if-let [block (db/entity [:block/uuid (medley/uuid name)])]
           (let [content (text/remove-level-spaces (:block/content block)

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

@@ -1,7 +1,7 @@
 (ns frontend.handler.shell
   (:require [electron.ipc :as ipc]
             [clojure.string :as string]
-            [frontend.util :as util]
+            [logseq.graph-parser.util :as gp-util]
             [frontend.handler.notification :as notification]
             [promesa.core :as p]
             [frontend.db :as db]
@@ -34,7 +34,7 @@
 
 (defn run-command!
   [command]
-  (let [[command args] (util/split-first " " command)
+  (let [[command args] (gp-util/split-first " " command)
         command (and command (string/lower-case command))]
     (when (and (not (string/blank? command)) (not (string/blank? args)))
       (let [args (string/trim args)]

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

@@ -14,6 +14,7 @@
             [clojure.string :as string]
             [rum.core :as rum]
             [frontend.mobile.util :as mobile]
+            [logseq.graph-parser.util :as gp-util]
             [electron.ipc :as ipc]))
 
 (defn- get-css-var-value
@@ -69,7 +70,7 @@
     (let [id "contents"]
       (if (state/sidebar-block-exists? id)
         (state/sidebar-remove-block! id)
-        (state/sidebar-add-block! current-repo id :contents nil)))))
+        (state/sidebar-add-block! current-repo id :contents)))))
 
 (defn toggle-help!
   []
@@ -77,7 +78,7 @@
     (let [id "help"]
       (if (state/sidebar-block-exists? id)
         (state/sidebar-remove-block! id)
-        (state/sidebar-add-block! current-repo id :help nil)))))
+        (state/sidebar-add-block! current-repo id :help)))))
 
 (defn toggle-settings-modal!
   []
@@ -111,7 +112,7 @@
   (let [id (and
             (> (count fragment) 36)
             (subs fragment (- (count fragment) 36)))]
-    (if (and id (util/uuid-string? id))
+    (if (and id (gp-util/uuid-string? id))
       (let [elements (array-seq (js/document.getElementsByClassName id))]
         (when (first elements)
           (util/scroll-to-element (gobj/get (first elements) "id")))

+ 3 - 7
src/main/frontend/handler/user.cljs

@@ -85,10 +85,8 @@
                              {:with-credentials? false}))]
       (if (= 200 (:status resp))
         (-> resp
-              (:body)
-              (js/JSON.parse)
-              (js->clj :keywordize-keys true)
-              (as-> $ (set-tokens! (:id_token $) (:access_token $) (:refresh_token $))))
+            :body
+            (as-> $ (set-tokens! (:id_token $) (:access_token $) (:refresh_token $))))
         (debug/pprint "login-callback" resp)))))
 
 (defn logout []
@@ -111,9 +109,7 @@
             (->
              resp
              (as-> $ (and (http/unexceptional-status? (:status $)) $))
-             (:body)
-             (js/JSON.parse)
-             (js->clj :keywordize-keys true)
+             :body
              (as-> $ (set-tokens! (:id_token $) (:access_token $))))
             true))))))
 

+ 12 - 7
src/main/frontend/handler/web/nfs.cljs

@@ -20,7 +20,10 @@
             [lambdaisland.glogi :as log]
             [promesa.core :as p]
             [frontend.mobile.util :as mobile-util]
-            [clojure.core.async :as async]))
+            [logseq.graph-parser.util :as gp-util]
+            [logseq.graph-parser.config :as gp-config]
+            [clojure.core.async :as async]
+            [frontend.encrypt :as encrypt]))
 
 (defn remove-ignore-files
   [files]
@@ -48,7 +51,7 @@
    (cond
      mobile-native?
      (map (fn [{:keys [uri content size mtime]}]
-            {:file/path             (util/path-normalize uri)
+            {:file/path             (gp-util/path-normalize uri)
              :file/last-modified-at mtime
              :file/size             size
              :file/content content})
@@ -57,7 +60,7 @@
      electron?
      (map (fn [{:keys [path stat content]}]
             (let [{:keys [mtime size]} stat]
-              {:file/path             (util/path-normalize path)
+              {:file/path             (gp-util/path-normalize path)
                :file/last-modified-at mtime
                :file/size             size
                :file/content content}))
@@ -71,7 +74,7 @@
                     path (-> (get-attr "webkitRelativePath")
                              (string/replace-first (str dir-name "/") ""))]
                 {:file/name             (get-attr "name")
-                 :file/path             (util/path-normalize path)
+                 :file/path             (gp-util/path-normalize path)
                  :file/last-modified-at (get-attr "lastModified")
                  :file/size             (get-attr "size")
                  :file/type             (get-attr "type")
@@ -155,7 +158,7 @@
                                                                         (string/replace-first path (str dir-name "/") ""))
                                                              (let [last-part (last (string/split path "/"))]
                                                                (contains? #{config/app-name
-                                                                            config/default-draw-directory
+                                                                            gp-config/default-draw-directory
                                                                             (config/get-journals-directory)
                                                                             (config/get-pages-directory)}
                                                                           last-part)))))
@@ -166,7 +169,8 @@
            (-> (p/all (map (fn [file]
                              (p/let [content (if nfs?
                                                (.text (:file/file file))
-                                               (:file/content file))]
+                                               (:file/content file))
+                                     content (encrypt/decrypt content)]
                                (assoc file :file/content content))) markup-files))
                (p/then (fn [result]
                          (let [files (map #(dissoc % :file/file) result)]
@@ -245,7 +249,8 @@
                       (when-let [file (get-file-f path new-files)]
                         (p/let [content (if nfs?
                                           (.text (:file/file file))
-                                          (:file/content file))]
+                                          (:file/content file))
+                                content (encrypt/decrypt content)]
                           (assoc file :file/content content)))) added-or-modified))
         (p/then (fn [result]
                   (let [files (map #(dissoc % :file/file :file/handle) result)

+ 5 - 3
src/main/frontend/modules/file/core.cljs

@@ -30,8 +30,10 @@
 (defn transform-content
   [{:block/keys [collapsed? format pre-block? unordered content heading-level left page parent properties]} level {:keys [heading-to-list?]}]
   (let [content (or content "")
-        first-block? (= left page)
-        pre-block? (and first-block? pre-block?)
+        pre-block? (or pre-block?
+                       (and (= page parent left) ; first block
+                            (= :markdown format)
+                            (string/includes? (first (string/split-lines content)) ":: ")))
         markdown? (= format :markdown)
         content (cond
                   pre-block?
@@ -125,7 +127,7 @@
                   (if journal-page?
                     (date/journal-title->default title)
                     (-> (or (:block/original-name page) (:block/name page))
-                        (util/page-name-sanity true))) "."
+                        (util/file-name-sanity))) "."
                   (if (= format "markdown") "md" format))
             file-path (config/get-file-path repo path)
             file {:file/path file-path}

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

@@ -1,5 +1,5 @@
 (ns frontend.modules.instrumentation.posthog
-  (:require [frontend.config :as cfg]
+  (:require [frontend.config :as config]
             [frontend.util :as util]
             [frontend.mobile.util :as mobile]
             [frontend.version :refer [version]]
@@ -22,7 +22,7 @@
 
                    :else
                    "web"))
-     :app_env (if cfg/dev? "development" "production")
+     :app_env (if config/dev? "development" "production")
      :app_ver version
      :schema_ver 0
      ;; hack, did not find ways to hack data on-the-fly with posthog-js

+ 5 - 5
src/main/frontend/modules/instrumentation/sentry.cljs

@@ -1,7 +1,7 @@
 (ns frontend.modules.instrumentation.sentry
   (:require [frontend.version :refer [version]]
             [frontend.util :as util]
-            [frontend.config :as cfg]
+            [frontend.config :as config]
             ["@sentry/react" :as Sentry]
             ["@sentry/tracing" :refer [BrowserTracing]]
             ["posthog-js" :as posthog]
@@ -14,16 +14,16 @@
                                          (mobile-util/native-ios?) "-ios"
                                          :else "")
                          version)
-   :environment (if cfg/dev? "development" "production")
+   :environment (if config/dev? "development" "production")
    :initialScope {:tags
                   {:platform (cond
                                (util/electron?) "electron"
                                (mobile-util/is-native-platform?) "mobile"
                                :else "web")
-                   :publishing cfg/publishing?}}
+                   :publishing config/publishing?}}
    :integrations [(new posthog/SentryIntegration posthog "logseq" 5311485)
                   (new BrowserTracing)]
-   :debug cfg/dev?
+   :debug config/dev?
    :tracesSampleRate 1.0
    :beforeSend (fn [^js event]
                  (try
@@ -43,6 +43,6 @@
                  event)})
 
 (defn init []
-  (when-not cfg/dev?
+  (when-not config/dev?
     (let [config (clj->js config)]
      (Sentry/init config))))

+ 6 - 6
src/main/frontend/modules/layout/core.cljs

@@ -1,6 +1,6 @@
 (ns frontend.modules.layout.core
   (:require [cljs-bean.core :as bean]
-            [frontend.util :as frontend-utils]))
+            [frontend.util :as util]))
 
 (defonce *movable-containers (atom {}))
 
@@ -24,7 +24,7 @@
                    (remove nil?))
           zdx (bean/->js zdx)
           zdx (and zdx (js/Math.max.apply nil zdx))
-          zdx' (frontend-utils/safe-parse-int (.. container -style -zIndex))]
+          zdx' (util/safe-parse-int (.. container -style -zIndex))]
 
       (when (or (nil? zdx') (not= zdx zdx'))
         (set! (.. container -style -zIndex) (inc zdx))))))
@@ -46,8 +46,8 @@
                         (let [^js dset (.-dataset el)
                               dx (.-dx e)
                               dy (.-dy e)
-                              dx' (frontend-utils/safe-parse-float (.-dx dset))
-                              dy' (frontend-utils/safe-parse-float (.-dy dset))
+                              dx' (util/safe-parse-float (.-dx dset))
+                              dy' (util/safe-parse-float (.-dy dset))
                               x (+ dx (if dx' dx' 0))
                               y (+ dy (if dy' dy' 0))]
 
@@ -92,8 +92,8 @@
                              dx (.. e -deltaRect -left)
                              dy (.. e -deltaRect -top)
 
-                             dx' (frontend-utils/safe-parse-float (.-dx dset))
-                             dy' (frontend-utils/safe-parse-float (.-dy dset))
+                             dx' (util/safe-parse-float (.-dx dset))
+                             dy' (util/safe-parse-float (.-dy dset))
 
                              x (+ dx (if dx' dx' 0))
                              y (+ dy (if dy' dy' 0))]

+ 2 - 1
src/main/frontend/modules/outliner/core.cljs

@@ -12,6 +12,7 @@
             [frontend.modules.outliner.utils :as outliner-u]
             [frontend.state :as state]
             [frontend.util :as util]
+            [logseq.graph-parser.util :as gp-util]
             [cljs.spec.alpha :as s]))
 
 (s/def ::block-map (s/keys :req [:db/id :block/uuid]
@@ -135,7 +136,7 @@
     (let [m (-> (:data this)
                 (dissoc :block/children :block/meta :block/top? :block/bottom?
                         :block/title :block/body :block/level)
-                (util/remove-nils))
+                (gp-util/remove-nils))
           m (if (state/enable-block-timestamps?) (block-with-timestamps m) m)
           other-tx (:db/other-tx m)
           id (:db/id (:data this))

+ 3 - 3
src/main/frontend/modules/outliner/datascript.cljc

@@ -7,8 +7,8 @@
                      [frontend.modules.editor.undo-redo :as undo-redo]
                      [frontend.state :as state]
                      [frontend.config :as config]
+                     [logseq.graph-parser.util :as gp-util]
                      [lambdaisland.glogi :as log]
-                     [frontend.util :as util]
                      [medley.core :as medley])))
 
 #?(:cljs
@@ -30,7 +30,7 @@
 #?(:cljs
    (defn- remove-nil-from-transaction
      [txs]
-     (some->> (util/remove-nils txs)
+     (some->> (gp-util/remove-nils txs)
               (map (fn [x]
                      (if (map? x)
                        (medley/map-vals (fn [v] (if (vector? v)
@@ -45,7 +45,7 @@
            txs (map (fn [m] (if (map? m)
                               (dissoc m
                                       :block/children :block/meta :block/top? :block/bottom? :block/anchor
-                                      :block/title :block/body :block/level :block/container)
+                                      :block/title :block/body :block/level :block/container :db/other-tx)
                               m)) txs)]
        (when (and (seq txs)
                   (not (:skip-transact? opts)))

+ 2 - 2
src/main/frontend/modules/outliner/tree.cljs

@@ -1,6 +1,6 @@
 (ns frontend.modules.outliner.tree
   (:require [frontend.db :as db]
-            [frontend.util :as util]
+            [logseq.graph-parser.util :as gp-util]
             [clojure.string :as string]
             [frontend.state :as state]))
 
@@ -45,7 +45,7 @@
 (defn- get-root-and-page
   [repo root-id]
   (if (string? root-id)
-    (if (util/uuid-string? root-id)
+    (if (gp-util/uuid-string? root-id)
       [false (db/entity repo [:block/uuid (uuid root-id)])]
       [true (db/entity repo [:block/name (string/lower-case root-id)])])
     [false root-id]))

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

@@ -66,7 +66,7 @@
          (doseq [k (dh/shortcut-binding id)]
            (try
              (log/debug :shortcut/register-shortcut {:id id :binding k})
-             (.registerShortcut handler (util/keyname id) k)
+             (.registerShortcut handler (util/keyname id) (dh/normalize-user-keyname k))
              (catch js/Object e
                (log/error :shortcut/register-shortcut {:id      id
                                                        :binding k
@@ -81,7 +81,7 @@
   (when-let [handler (get-handler-by-id handler-id)]
     (when-let [ks (dh/shortcut-binding shortcut-id)]
       (doseq [k ks]
-        (.unregisterShortcut ^js handler k)))
+        (.unregisterShortcut ^js handler (dh/normalize-user-keyname k))))
     (shortcut-config/remove-shortcut! handler-id shortcut-id)))
 
 (defn uninstall-shortcut!

+ 27 - 12
src/main/frontend/modules/shortcut/data_helper.cljs

@@ -2,10 +2,10 @@
   (:require [borkdude.rewrite-edn :as rewrite]
             [clojure.string :as str]
             [clojure.set :refer [rename-keys]]
-            [frontend.config :as cfg]
+            [frontend.config :as config]
             [frontend.db :as db]
             [frontend.handler.file :as file]
-            [frontend.modules.shortcut.config :as config]
+            [frontend.modules.shortcut.config :as shortcut-config]
             [frontend.state :as state]
             [frontend.util :as util]
             [lambdaisland.glogi :as log]
@@ -14,7 +14,7 @@
 
 (defn get-bindings
   []
-  (->> (vals @config/config)
+  (->> (vals @shortcut-config/config)
        (into {})
        (map (fn [[k {:keys [binding]}]]
               {k binding}))
@@ -44,21 +44,32 @@
          shortcut)
        (mapv mod-key)))))
 
+(defn normalize-user-keyname
+  [k]
+  (some-> k
+          (util/safe-lower-case)
+          (str/replace #";+" "semicolon")
+          (str/replace #"=+" "equals")
+          (str/replace #"~+" "dash")
+          (str/replace "[" "open-square-bracket")
+          (str/replace "]" "close-square-bracket")
+          (str/replace "'" "single-quote")))
+
 ;; returns a vector to preserve order
 (defn binding-by-category [name]
-  (let [dict (->> (vals @config/config)
+  (let [dict (->> (vals @shortcut-config/config)
                   (apply merge)
                   (map (fn [[k _]]
                          {k {:binding (shortcut-binding k)}}))
                   (into {}))]
-    (->> (config/category name)
+    (->> (shortcut-config/category name)
          (mapv (fn [k] [k (k dict)])))))
 
 (defn shortcut-map
   ([handler-id]
    (shortcut-map handler-id nil))
   ([handler-id state]
-   (let [raw       (get @config/config handler-id)
+   (let [raw       (get @shortcut-config/config handler-id)
          handler-m (->> raw
                         (map (fn [[k {:keys [fn]}]]
                                {k fn}))
@@ -125,7 +136,7 @@
 
 (defn remove-shortcut [k]
   (let [repo (state/get-current-repo)
-        path (cfg/get-config-path)]
+        path (config/get-config-path)]
     (when-let [content (db/get-file path)]
       (let [result (common-handler/parse-config content)
             new-result (rewrite/update
@@ -140,7 +151,7 @@
   "Given shortcut key, return handler group
   eg: :editor/new-line -> :shortcut.handler/block-editing-only"
   [k]
-  (->> @config/config
+  (->> @shortcut-config/config
        (filter (fn [[_ v]] (contains? v k)))
        (map key)
        (first)))
@@ -150,9 +161,13 @@
     false
     (let [handler-id    (get-group k)
           shortcut-m    (shortcut-map handler-id)
+          parse-shortcut #(try
+                           (KeyboardShortcutHandler/parseStringShortcut %)
+                           (catch js/Error e
+                             (js/console.error "[shortcut/parse-error]" (str % " - " (.-message e)))))
           bindings      (->> (shortcut-binding k)
                              (map mod-key)
-                             (map KeyboardShortcutHandler/parseStringShortcut)
+                             (map parse-shortcut)
                              (map js->clj))
           rest-bindings (->> (map key shortcut-m)
                              (remove #{k})
@@ -160,14 +175,14 @@
                              (filter vector?)
                              (mapcat identity)
                              (map mod-key)
-                             (map KeyboardShortcutHandler/parseStringShortcut)
+                             (map parse-shortcut)
                              (map js->clj))]
 
       (some? (some (fn [b] (some #{b} rest-bindings)) bindings)))))
 
 (defn shortcut-data-by-id [id]
   (let [binding (shortcut-binding id)
-        data    (->> (vals @config/config)
+        data    (->> (vals @shortcut-config/config)
                      (into  {})
                      id)]
     (assoc
@@ -176,7 +191,7 @@
       (binding-for-display id binding))))
 
 (defn shortcuts->commands [handler-id]
-  (let [m (get @config/config handler-id)]
+  (let [m (get @shortcut-config/config handler-id)]
     (->> m
          (map (fn [[id _]] (-> (shortcut-data-by-id id)
                                (assoc :id id :handler-id handler-id)

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

@@ -1,6 +1,6 @@
 (ns frontend.security
   (:require [clojure.walk :as walk]
-            [frontend.util :as util]))
+            [logseq.graph-parser.util :as gp-util]))
 
 ;; To prevent from cross-site scripting vulnerability, we should add security checks for both hiccup and raw html.
 ;; Hiccup: [:a {:href "javascript:alert('hei')"} "click me"]
@@ -12,7 +12,7 @@
    (= :a (first f))
    (:href (second f))
    (:href (second f))
-   (util/safe-re-find #"(?i)javascript" (:href (second f)))))
+   (gp-util/safe-re-find #"(?i)javascript" (:href (second f)))))
 
 (defn remove-javascript-links-in-href
   [hiccup]

+ 34 - 6
src/main/frontend/state.cljs

@@ -13,6 +13,7 @@
             [goog.object :as gobj]
             [promesa.core :as p]
             [rum.core :as rum]
+            [logseq.graph-parser.util :as gp-util]
             [frontend.mobile.util :as mobile-util]))
 
 (defonce ^:large-vars/data-var state
@@ -184,7 +185,9 @@
      :graph/parsing-state                   {}
 
      ;; copied blocks
-     :copy/blocks                           {:copy/content nil :copy/block-ids nil}
+     :copy/blocks                           {:copy/content nil
+                                             :copy/block-ids nil
+                                             :copy/graph nil}
 
      :copy/export-block-text-indent-style   (or (storage/get :copy/export-block-text-indent-style)
                                                 "dashes")
@@ -218,6 +221,8 @@
      :file-sync/sync-state                  nil
      :file-sync/sync-uploading-files        nil
      :file-sync/sync-downloading-files      nil
+
+     :encryption/graph-parsing?             false
      })))
 
 ;; block uuid -> {content(String) -> ast}
@@ -450,7 +455,7 @@
     (or
       (when-let [workflow (:preferred-workflow (get-config))]
         (let [workflow (name workflow)]
-          (if (util/safe-re-find #"now|NOW" workflow)
+          (if (gp-util/safe-re-find #"now|NOW" workflow)
             :now
             :todo)))
       (get-in @state [:me :preferred_workflow] :now))))
@@ -751,12 +756,12 @@
   (swap! state assoc :ui/sidebar-open? false))
 
 (defn sidebar-add-block!
-  [repo db-id block-type block-data]
+  [repo db-id block-type]
   (when (not (util/sm-breakpoint?))
     (when db-id
       (update-state! :sidebar/blocks (fn [blocks]
                                        (->> (remove #(= (second %) db-id) blocks)
-                                            (cons [repo db-id block-type block-data])
+                                            (cons [repo db-id block-type])
                                             (distinct))))
       (open-right-sidebar!)
       (when-let [elem (gdom/getElementByClass "cp__right-sidebar-scrollable")]
@@ -771,6 +776,13 @@
   (when (empty? (:sidebar/blocks @state))
     (hide-right-sidebar!)))
 
+(defn sidebar-replace-block!
+  [old-sidebar-key new-sidebar-key]
+  (update-state! :sidebar/blocks (fn [blocks]
+                                   (map #(if (= % old-sidebar-key)
+                                           new-sidebar-key
+                                           %) blocks))))
+
 (defn sidebar-block-exists?
   [idx]
   (some #(= (second %) idx) (:sidebar/blocks @state)))
@@ -1420,13 +1432,15 @@
 
 (defn set-copied-blocks
   [content ids]
-  (set-state! :copy/blocks {:copy/content content
+  (set-state! :copy/blocks {:copy/graph (get-current-repo)
+                            :copy/content content
                             :copy/block-ids ids
                             :copy/full-blocks nil}))
 
 (defn set-copied-full-blocks
   [content blocks]
-  (set-state! :copy/blocks {:copy/content content
+  (set-state! :copy/blocks {:copy/graph (get-current-repo)
+                            :copy/content content
                             :copy/full-blocks blocks}))
 
 (defn set-copied-full-blocks!
@@ -1493,6 +1507,15 @@
   []
   (get-in @state [:view/components :page-blocks]))
 
+;; To avoid circular dependencies
+(defn set-component!
+  [k value]
+  (set-state! [:view/components k] value))
+
+(defn get-component
+  [k]
+  (get-in @state [:view/components k]))
+
 (defn exit-editing-and-set-selected-blocks!
   ([blocks]
    (exit-editing-and-set-selected-blocks! blocks :down))
@@ -1661,3 +1684,8 @@
   (update-state! [:graph/parsing-state (get-current-repo)]
                  (if (fn? m) m
                    (fn [old-value] (merge old-value m)))))
+
+(defn enable-encryption?
+  [repo]
+  (:feature/enable-encryption?
+   (get (sub-config) repo)))

+ 6 - 5
src/main/frontend/text.cljs

@@ -4,6 +4,7 @@
             [clojure.string :as string]
             [frontend.format.mldoc :as mldoc]
             [clojure.set :as set]
+            [logseq.graph-parser.util :as gp-util]
             [frontend.state :as state]))
 
 (def page-ref-re-0 #"\[\[(.*)\]\]")
@@ -136,8 +137,8 @@
 
      (and (string? s)
             ;; Either a page ref, a tag or a comma separated collection
-            (or (util/safe-re-find page-ref-re s)
-                (util/safe-re-find #"[\,|,|#|\"]+" s)))
+            (or (gp-util/safe-re-find page-ref-re s)
+                (gp-util/safe-re-find #"[\,|,|#|\"]+" s)))
      (let [result (->> (sep-by-quotes s)
                        (mapcat
                         (fn [s]
@@ -192,7 +193,7 @@
     (let [pattern (util/format
                    "^[%s]+\\s?"
                    (config/get-block-pattern format))]
-      (util/safe-re-find (re-pattern pattern) text))
+      (gp-util/safe-re-find (re-pattern pattern) text))
     ""))
 
 (defn- remove-level-space-aux!
@@ -231,7 +232,7 @@
 
 (defn media-link?
   [media-formats s]
-  (some (fn [fmt] (util/safe-re-find (re-pattern (str "(?i)\\." fmt "(?:\\?([^#]*))?(?:#(.*))?$")) s)) media-formats))
+  (some (fn [fmt] (gp-util/safe-re-find (re-pattern (str "(?i)\\." fmt "(?:\\?([^#]*))?(?:#(.*))?$")) s)) media-formats))
 
 (defn namespace-page?
   [p]
@@ -356,7 +357,7 @@
        (= v "false")
        false
 
-       (and (not= k "alias") (util/safe-re-find #"^\d+$" v))
+       (and (not= k "alias") (gp-util/safe-re-find #"^\d+$" v))
        (util/safe-parse-int v)
 
        (util/wrapped-by-quotes? v) ; wrapped in ""

部分文件因文件數量過多而無法顯示