Browse Source

Merge branch 'master' into feature/add-more-graph-commands

Gabriel Horner 3 years ago
parent
commit
be46506ba3
73 changed files with 4747 additions and 4920 deletions
  1. 6 1
      .clj-kondo/config.edn
  2. 2 6
      deps.edn
  3. 0 0
      docs/mobile.md
  4. 12 10
      package.json
  5. 0 8
      shadow-cljs.edn
  6. 20 0
      src/dev-cljs/dev.cljs
  7. 9 3
      src/electron/electron/core.cljs
  8. 91 92
      src/main/frontend/components/block.cljs
  9. 4 0
      src/main/frontend/components/block.css
  10. 28 30
      src/main/frontend/components/command_palette.cljs
  11. 99 100
      src/main/frontend/components/content.cljs
  12. 1 3
      src/main/frontend/components/editor.cljs
  13. 119 123
      src/main/frontend/components/encryption.cljs
  14. 30 31
      src/main/frontend/components/export.cljs
  15. 76 78
      src/main/frontend/components/file.cljs
  16. 99 101
      src/main/frontend/components/header.cljs
  17. 7 8
      src/main/frontend/components/journal.cljs
  18. 192 194
      src/main/frontend/components/onboarding.cljs
  19. 402 409
      src/main/frontend/components/page.cljs
  20. 23 26
      src/main/frontend/components/page_menu.cljs
  21. 222 237
      src/main/frontend/components/plugins.cljs
  22. 187 190
      src/main/frontend/components/repo.cljs
  23. 5 6
      src/main/frontend/components/right_sidebar.cljs
  24. 187 193
      src/main/frontend/components/search.cljs
  25. 19 20
      src/main/frontend/components/select.cljs
  26. 159 165
      src/main/frontend/components/settings.cljs
  27. 98 102
      src/main/frontend/components/shortcut.cljs
  28. 186 194
      src/main/frontend/components/sidebar.cljs
  29. 123 129
      src/main/frontend/components/widgets.cljs
  30. 0 4
      src/main/frontend/config.cljs
  31. 11 22
      src/main/frontend/context/i18n.cljs
  32. 20 19
      src/main/frontend/db.cljs
  33. 0 18
      src/main/frontend/db/model.cljs
  34. 8 8
      src/main/frontend/db/outliner.cljs
  35. 28 16
      src/main/frontend/db/query_dsl.cljs
  36. 1 0
      src/main/frontend/db/react.cljs
  37. 5 1
      src/main/frontend/db/utils.cljs
  38. 1094 1041
      src/main/frontend/dicts.cljs
  39. 123 129
      src/main/frontend/extensions/pdf/highlights.cljs
  40. 9 44
      src/main/frontend/format/block.cljs
  41. 3 27
      src/main/frontend/format/mldoc.cljs
  42. 12 7
      src/main/frontend/fs/capacitor_fs.cljs
  43. 0 2
      src/main/frontend/handler.cljs
  44. 1 2
      src/main/frontend/handler/editor.cljs
  45. 3 6
      src/main/frontend/handler/events.cljs
  46. 47 89
      src/main/frontend/handler/extract.cljs
  47. 80 99
      src/main/frontend/handler/file.cljs
  48. 20 10
      src/main/frontend/handler/link.cljs
  49. 49 67
      src/main/frontend/handler/repo.cljs
  50. 24 8
      src/main/frontend/handler/search.cljs
  51. 3 3
      src/main/frontend/handler/web/nfs.cljs
  52. 3 40
      src/main/frontend/modules/outliner/core.cljs
  53. 1 1
      src/main/frontend/modules/outliner/file.cljs
  54. 0 16
      src/main/frontend/modules/outliner/state.cljs
  55. 1 1
      src/main/frontend/modules/shortcut/config.cljs
  56. 483 484
      src/main/frontend/modules/shortcut/dict.cljs
  57. 8 10
      src/main/frontend/page.cljs
  58. 3 15
      src/main/frontend/search/db.cljs
  59. 4 3
      src/main/frontend/state.cljs
  60. 1 9
      src/main/frontend/text.cljs
  61. 51 53
      src/main/frontend/ui.cljs
  62. 0 78
      src/main/frontend/util/pool.cljs
  63. 3 2
      src/main/frontend/util/property.cljs
  64. 0 14
      src/main/frontend/worker/parser.cljs
  65. 1 1
      src/main/logseq/api.cljs
  66. 98 83
      src/test/frontend/db/query_dsl_test.cljs
  67. 1 1
      src/test/frontend/format/block_test.cljs
  68. 24 0
      src/test/frontend/fs/capacitor_fs_test.cljs
  69. 1 1
      src/test/frontend/handler/extract_test.cljs
  70. 10 12
      src/test/frontend/handler/link_test.cljs
  71. 2 2
      src/test/frontend/modules/outliner/core_test.cljs
  72. 1 1
      src/test/frontend/text_test.cljs
  73. 104 12
      yarn.lock

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

@@ -7,7 +7,12 @@
   :unresolved-var {:exclude [frontend.util/node-path.basename
                              frontend.util/node-path.dirname
                              frontend.util/node-path.join
-                             frontend.util/node-path.name]}}
+                             frontend.util/node-path.name]}
+
+  :consistent-alias
+  {:aliases {datascript.core d
+             datascript.transit dt
+             datascript.db ddb}}}
 
  :hooks {:analyze-call {rum.core/defc hooks.rum/defc
                          rum.core/defcs hooks.rum/defcs}}

+ 2 - 6
deps.edn

@@ -3,12 +3,8 @@
  {org.clojure/clojure                   {:mvn/version "1.10.0"}
   cheshire/cheshire                     {:mvn/version "5.10.0"}
   rum/rum                               {:mvn/version "0.12.3"}
-  ;; rum                         {:local/root "/home/tienson/codes/source/clj/rum"}
-  ;; persistent-sorted-set       {:mvn/version "0.1.2"}
-  datascript/datascript                 {:git/url "https://github.com/logseq/datascript"
-                                         :sha     "de8df66e69ecd847c048f548fa0d5de0d4378bd0"}
-  datascript-transit/datascript-transit {:mvn/version "0.3.0"
-                                         :exclusions  [datascript/datascript]}
+  datascript/datascript                 {:mvn/version "1.3.8"}
+  datascript-transit/datascript-transit {:mvn/version "0.3.0"}
   borkdude/rewrite-edn                  {:git/url "https://github.com/borkdude/rewrite-edn"
                                          :sha     "edd87dc7f045f28d7afcbfc44bc0f0a2683dde62"}
   funcool/promesa                       {:mvn/version "4.0.2"}

+ 0 - 0
mobile.md → docs/mobile.md


+ 12 - 10
package.json

@@ -48,18 +48,18 @@
         "gulp:build": "cross-env NODE_ENV=production gulp build",
         "css:build": "postcss tailwind.all.css -o static/css/style.css --verbose --env production",
         "css:watch": "cross-env TAILWIND_MODE=watch postcss tailwind.all.css -o static/css/style.css --verbose --watch",
-        "cljs:watch": "clojure -M:cljs watch parser-worker app electron",
-        "cljs:app-watch": "clojure -M:cljs watch parser-worker app",
-        "cljs:electron-watch": "clojure -M:cljs watch parser-worker app electron",
-        "cljs:release": "clojure -M:cljs release parser-worker app publishing electron",
-        "cljs:release-electron": "clojure -M:cljs release parser-worker app publishing electron --debug",
-        "cljs:release-app": "clojure -M:cljs release parser-worker app",
+        "cljs:watch": "clojure -M:cljs watch app electron",
+        "cljs:app-watch": "clojure -M:cljs watch app",
+        "cljs:electron-watch": "clojure -M:cljs watch app electron",
+        "cljs:release": "clojure -M:cljs release app publishing electron",
+        "cljs:release-electron": "clojure -M:cljs release app publishing electron --debug",
+        "cljs:release-app": "clojure -M:cljs release app",
         "cljs:test": "clojure -M:test compile test",
         "cljs:run-test": "node static/tests.js",
         "cljs:dev-release-app": "clojure -M:cljs release app --config-merge '{:closure-defines {frontend.config/DEV-RELEASE true}}'",
-        "cljs:debug": "clojure -M:cljs release parser-worker app --debug",
-        "cljs:report": "clojure -M:cljs run shadow.cljs.build-report parser-worker app report.html",
-        "cljs:build-electron": "clojure -A:cljs compile parser-worker app electron",
+        "cljs:debug": "clojure -M:cljs release app --debug",
+        "cljs:report": "clojure -M:cljs run shadow.cljs.build-report app report.html",
+        "cljs:build-electron": "clojure -A:cljs compile app electron",
         "cljs:lint": "clojure -M:clj-kondo --parallel --lint src"
     },
     "dependencies": {
@@ -84,6 +84,7 @@
         "d3-force": "3.0.0",
         "diff": "5.0.0",
         "electron": "15.1.2",
+        "electron-context-menu": "3.1.1",
         "fs": "0.0.1-security",
         "fs-extra": "9.1.0",
         "fuse.js": "6.4.6",
@@ -94,7 +95,7 @@
         "ignore": "5.1.8",
         "is-svg": "4.3.0",
         "jszip": "3.5.0",
-        "mldoc": "1.2.7",
+        "mldoc": "1.3.0",
         "remove-accents": "0.4.2",
         "path": "0.12.7",
         "pixi-graph-fork": "0.2.0",
@@ -110,6 +111,7 @@
         "react-transition-group": "4.3.0",
         "react-tweet-embed": "1.3.1",
         "reakit": "0.11.1",
+        "remove-accents": "0.4.2",
         "threads": "1.6.5",
         "url": "^0.11.0",
         "yargs-parser": "20.2.4"

+ 0 - 8
shadow-cljs.edn

@@ -58,14 +58,6 @@
                                            "externs.js"]
                                 :warnings {:fn-deprecated false}}}
 
-  :parser-worker {:target           :browser
-                  :output-dir       "./static/js"
-                  :asset-path       "./js"
-                  :compiler-options {:source-map false}
-                  :modules          {:parser-worker {:entries    [frontend.worker.parser]
-                                                     :web-worker true}}
-                  :release          {:compiler-options {:infer-externs :auto}}}
-
   :test {:target          :node-test
          :output-to       "static/tests.js"
          :closure-defines {frontend.util/NODETEST true}

+ 20 - 0
src/dev-cljs/dev.cljs

@@ -0,0 +1,20 @@
+(ns dev)
+
+
+(comment
+
+ ;; In dict to get unequal counts
+ (->> (dissoc dicts :tongue/fallback) (map (fn [[k v]] [k (count v)])) (sort-by second >))
+
+ (def q #queue [1 2 3])
+ (pop q)
+
+ ;; Current repos
+ (require '[frontend.state :as state])
+ (state/sub [:me :repos])
+
+;; Debugging in query-dsl-test
+ (let [db (frontend.db.conn/get-conn test-db)]
+   (prn :DB (datascript.core/q '[:find (pull ?b [*]) :where [?b :block/properties ?prop] [(get ?prop :prop-num)]]
+                               db)))
+ )

+ 9 - 3
src/electron/electron/core.cljs

@@ -16,7 +16,8 @@
             [electron.git :as git]
             [electron.window :as win]
             [electron.exceptions :as exceptions]
-            ["/electron/utils" :as utils]))
+            ["/electron/utils" :as utils]
+            ["electron-context-menu" :as init-context-menu]))
 
 (defonce LSP_SCHEME "lsp")
 (defonce LSP_PROTOCOL (str LSP_SCHEME "://"))
@@ -31,6 +32,10 @@
 ;; Handle creating/removing shortcuts on Windows when installing/uninstalling.
 (when (js/require "electron-squirrel-startup") (.quit app))
 
+(defn setup-context-menu
+  []
+  (init-context-menu #js {:showSaveImageAs true :showInspectElement false}))
+
 (defn setup-updater! [^js win]
   ;; manual/auto updater
   (when-not linux?
@@ -214,11 +219,12 @@
                         (fn []
                           (let [t1 (setup-updater! win)
                                 t2 (setup-app-manager! win)
-                                t3 (handler/set-ipc-handler! win)
+                                t3 (setup-context-menu)
+                                t4 (handler/set-ipc-handler! win)
                                 tt (exceptions/setup-exception-listeners!)]
 
                             (vreset! *teardown-fn
-                                     #(doseq [f [t0 t1 t2 t3 tt]]
+                                     #(doseq [f [t0 t1 t2 t3 t4 tt]]
                                         (and f (f)))))))
 
                ;; setup effects

+ 91 - 92
src/main/frontend/components/block.cljs

@@ -6,15 +6,15 @@
             [cljs.reader :as reader]
             [clojure.string :as string]
             [clojure.walk :as walk]
-            [datascript.core :as dc]
-            [dommy.core :as d]
+            [datascript.core :as d]
+            [dommy.core :as dom]
             [frontend.commands :as commands]
             [frontend.components.datetime :as datetime-comp]
             [frontend.components.lazy-editor :as lazy-editor]
             [frontend.components.svg :as svg]
             [frontend.components.macro :as macro]
             [frontend.config :as config]
-            [frontend.context.i18n :as i18n]
+            [frontend.context.i18n :refer [t]]
             [frontend.date :as date]
             [frontend.db :as db]
             [frontend.db.utils :as db-utils]
@@ -178,78 +178,77 @@
                    (reset! *resizing-image? false)
                    state)}
   [state config title src metadata full_text local?]
-  (rum/with-context [[t] i18n/*tongue-context*]
-    (let [size (get state ::size)]
-      (ui/resize-provider
-       (ui/resize-consumer
-        (cond->
-          {:className "resize image-resize"
-           :onSizeChanged (fn [value]
-                            (when (and (not @*resizing-image?)
-                                       (some? @size)
-                                       (not= value @size))
-                              (reset! *resizing-image? true))
-                            (reset! size value))
-           :onMouseUp (fn []
-                        (when (and @size @*resizing-image?)
-                          (when-let [block-id (:block/uuid config)]
-                            (let [size (bean/->clj @size)]
-                              (editor-handler/resize-image! block-id metadata full_text size))))
-                        (when @*resizing-image?
-                          ;; TODO: need a better way to prevent the clicking to edit current block
-                          (js/setTimeout #(reset! *resizing-image? false) 200)))
-           :onClick (fn [e]
-                      (when @*resizing-image? (util/stop e)))}
-          (and (:width metadata) (not (util/mobile?)))
-          (assoc :style {:width (:width metadata)}))
-        [:div.asset-container
-         [:img.rounded-sm.shadow-xl.relative
-          (merge
-           {:loading "lazy"
-            :src     src
-            :title   title}
-           metadata)]
-         [:span.ctl
-          [:a.delete
-           {:title "Delete this image"
-            :on-click
-            (fn [e]
-              (when-let [block-id (:block/uuid config)]
-                (let [confirm-fn (ui/make-confirm-modal
-                                  {:title         (t :asset/confirm-delete (.toLocaleLowerCase (t :text/image)))
-                                   :sub-title     (if local? :asset/physical-delete "")
-                                   :sub-checkbox? local?
-                                   :on-confirm    (fn [_e {:keys [close-fn sub-selected]}]
-                                                    (close-fn)
-                                                    (editor-handler/delete-asset-of-block!
-                                                     {:block-id    block-id
-                                                      :local?      local?
-                                                      :delete-local? (first sub-selected)
-                                                      :repo        (state/get-current-repo)
-                                                      :href        src
-                                                      :title       title
-                                                      :full-text   full_text}))})]
-                  (state/set-modal! confirm-fn)
-                  (util/stop e))))}
-           svg/trash-sm]
-
-          [:a.delete.ml-1
-           {:title    "maximize image"
-            :on-click (fn [^js e] (let [images (js/document.querySelectorAll ".asset-container img")
-                                        images (to-array images)
-                                        images (if-not (= (count images) 1)
-                                                 (let [^js _image (.closest (.-target e) ".asset-container")
-                                                       image (. _image querySelector "img")]
-                                                   (cons image (remove #(= image %) images)))
-                                                 images)
-                                        images (for [^js it images] {:src (.-src it)
-                                                                     :w (.-naturalWidth it)
-                                                                     :h (.-naturalHeight it)})]
-
-                                    (when (seq images)
-                                      (lightbox/preview-images! images))))}
-
-           (svg/maximize)]]])))))
+  (let [size (get state ::size)]
+    (ui/resize-provider
+     (ui/resize-consumer
+      (cond->
+        {:className "resize image-resize"
+         :onSizeChanged (fn [value]
+                          (when (and (not @*resizing-image?)
+                                     (some? @size)
+                                     (not= value @size))
+                            (reset! *resizing-image? true))
+                          (reset! size value))
+         :onMouseUp (fn []
+                      (when (and @size @*resizing-image?)
+                        (when-let [block-id (:block/uuid config)]
+                          (let [size (bean/->clj @size)]
+                            (editor-handler/resize-image! block-id metadata full_text size))))
+                      (when @*resizing-image?
+                        ;; TODO: need a better way to prevent the clicking to edit current block
+                        (js/setTimeout #(reset! *resizing-image? false) 200)))
+         :onClick (fn [e]
+                    (when @*resizing-image? (util/stop e)))}
+        (and (:width metadata) (not (util/mobile?)))
+        (assoc :style {:width (:width metadata)}))
+      [:div.asset-container
+       [:img.rounded-sm.shadow-xl.relative
+        (merge
+         {:loading "lazy"
+          :src     src
+          :title   title}
+         metadata)]
+       [:span.ctl
+        [:a.delete
+         {:title "Delete this image"
+          :on-click
+          (fn [e]
+            (when-let [block-id (:block/uuid config)]
+              (let [confirm-fn (ui/make-confirm-modal
+                                {:title         (t :asset/confirm-delete (.toLocaleLowerCase (t :text/image)))
+                                 :sub-title     (if local? :asset/physical-delete "")
+                                 :sub-checkbox? local?
+                                 :on-confirm    (fn [_e {:keys [close-fn sub-selected]}]
+                                                  (close-fn)
+                                                  (editor-handler/delete-asset-of-block!
+                                                   {:block-id    block-id
+                                                    :local?      local?
+                                                    :delete-local? (first sub-selected)
+                                                    :repo        (state/get-current-repo)
+                                                    :href        src
+                                                    :title       title
+                                                    :full-text   full_text}))})]
+                (state/set-modal! confirm-fn)
+                (util/stop e))))}
+         svg/trash-sm]
+
+        [:a.delete.ml-1
+         {:title    "maximize image"
+          :on-click (fn [^js e] (let [images (js/document.querySelectorAll ".asset-container img")
+                                      images (to-array images)
+                                      images (if-not (= (count images) 1)
+                                               (let [^js _image (.closest (.-target e) ".asset-container")
+                                                     image (. _image querySelector "img")]
+                                                 (cons image (remove #(= image %) images)))
+                                               images)
+                                      images (for [^js it images] {:src (.-src it)
+                                                                   :w (.-naturalWidth it)
+                                                                   :h (.-naturalHeight it)})]
+
+                                  (when (seq images)
+                                    (lightbox/preview-images! images))))}
+
+         (svg/maximize)]]]))))
 
 (rum/defcs asset-link < rum/reactive
   (rum/local nil ::src)
@@ -648,7 +647,7 @@
           block-type (keyword (get-in block [:block/properties :ls-type]))
           hl-type (get-in block [:block/properties :hl-type])
           repo (state/get-current-repo)]
-      (if block
+      (if (and block (:block/content block))
         (let [title [:span {:class "block-ref"}
                      (block-content (assoc config :block-ref? true)
                                     block nil (:block/uuid block)
@@ -848,12 +847,12 @@
     ["Latex_Fragment" ["Displayed" s]]
     (if html-export?
       (latex/html-export s false true)
-      (latex/latex (str (dc/squuid)) s false true))
+      (latex/latex (str (d/squuid)) s false true))
 
     ["Latex_Fragment" ["Inline" s]]
     (if html-export?
       (latex/html-export s false true)
-      (latex/latex (str (dc/squuid)) s false false))
+      (latex/latex (str (d/squuid)) s false false))
 
     ["Target" s]
     [:a {:id s} s]
@@ -1761,16 +1760,16 @@
 (defn- target-forbidden-edit?
   [target]
   (or
-   (d/has-class? target "forbid-edit")
-   (d/has-class? target "bullet")
-   (d/has-class? target "logbook")
+   (dom/has-class? target "forbid-edit")
+   (dom/has-class? target "bullet")
+   (dom/has-class? target "logbook")
    (util/link? target)
    (util/time? target)
    (util/input? target)
    (util/details-or-summary? target)
    (and (util/sup? target)
-        (d/has-class? target "fn"))
-   (d/has-class? target "image-resize")))
+        (dom/has-class? target "fn"))
+   (dom/has-class? target "image-resize")))
 
 (defn- block-content-on-mouse-down
   [e block block-id _content edit-input-id]
@@ -1995,8 +1994,8 @@
 (defn non-dragging?
   [e]
   (and (= (gobj/get e "buttons") 1)
-       (not (d/has-class? (gobj/get e "target") "bullet-container"))
-       (not (d/has-class? (gobj/get e "target") "bullet"))
+       (not (dom/has-class? (gobj/get e "target") "bullet-container"))
+       (not (dom/has-class? (gobj/get e "target") "bullet"))
        (not @*dragging?)))
 
 (rum/defc breadcrumb-fragment
@@ -2123,7 +2122,7 @@
   (when-let [parent (gdom/getElement block-id)]
     (let [node (.querySelector parent ".bullet-container")]
       (when doc-mode?
-        (d/remove-class! node "hide-inner-bullet"))))
+        (dom/remove-class! node "hide-inner-bullet"))))
   (when (and
          (state/in-selection-mode?)
          (non-dragging? e))
@@ -2137,7 +2136,7 @@
   (when doc-mode?
     (when-let [parent (gdom/getElement block-id)]
       (when-let [node (.querySelector parent ".bullet-container")]
-        (d/add-class! node "hide-inner-bullet"))))
+        (dom/add-class! node "hide-inner-bullet"))))
   (when (and (non-dragging? e)
              (not @*resizing-image?))
     (state/into-selection-mode!)))
@@ -2647,7 +2646,7 @@
                                   :data-lang language}
                                  code)
             [:div
-             (lazy-editor/editor config (str (dc/squuid)) attr code options)
+             (lazy-editor/editor config (str (d/squuid)) attr code options)
              (let [options (:options options)]
                (when (and (= language "clojure") (contains? (set options) ":results"))
                  (sci/eval-result code)))]))))))
@@ -2728,7 +2727,7 @@
         ["Math" s]
         (if html-export?
           (latex/html-export s true true)
-          (latex/latex (str (dc/squuid)) s true true))
+          (latex/latex (str (d/squuid)) s true true))
         ["Example" l]
         [:pre.pre-wrap-white-space
          (join-lines l)]
@@ -2754,7 +2753,7 @@
         ["Export" "latex" _options content]
         (if html-export?
           (latex/html-export content true false)
-          (latex/latex (str (dc/squuid)) content true false))
+          (latex/latex (str (d/squuid)) content true false))
 
         ["Custom" "query" _options _result content]
         (try
@@ -2806,12 +2805,12 @@
         (let [content (latex-environment-content name option content)]
           (if html-export?
             (latex/html-export content true true)
-            (latex/latex (str (dc/squuid)) content true true)))
+            (latex/latex (str (d/squuid)) content true true)))
 
         ["Displayed_Math" content]
         (if html-export?
           (latex/html-export content true true)
-          (latex/latex (str (dc/squuid)) content true true))
+          (latex/latex (str (d/squuid)) content true true))
 
         ["Footnote_Definition" name definition]
         (let [id (util/url-encode name)]

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

@@ -386,6 +386,10 @@ html.is-native-android {
   margin: 1.67em 0;
 }
 
+.document-mode .block-children {
+    border-left: 0px solid;
+}
+
 .document-mode .ls-block {
     margin-bottom: 1rem;
 }

+ 28 - 30
src/main/frontend/components/command_palette.cljs

@@ -2,7 +2,7 @@
   (:require [frontend.handler.command-palette :as cp]
             [frontend.modules.shortcut.core :as shortcut]
             [frontend.modules.shortcut.data-helper :as shortcut-helper]
-            [frontend.context.i18n :as i18n]
+            [frontend.context.i18n :refer [t]]
             [frontend.search :as search]
             [frontend.state :as state]
             [frontend.ui :as ui]
@@ -22,17 +22,16 @@
 
 (rum/defc render-command
   [{:keys [id shortcut] :as cmd} chosen?]
-  (let [first-shortcut (first (string/split shortcut #" \| "))]
-    (rum/with-context [[t] i18n/*tongue-context*]
-                      (let [desc (translate t cmd)]
-                        [:div.inline-grid.grid-cols-4.gap-x-4.w-full
-        {:class (when chosen? "chosen")}
-        [:span.col-span-3 desc]
-        [:div.col-span-1.justify-end.tip.flex
-         (when (and (keyword? id) (namespace id))
-           [:code.opacity-20.bg-transparent (namespace id)])
-         (when-not (string/blank? first-shortcut)
-           [:code.ml-1 first-shortcut])]]))))
+  (let [first-shortcut (first (string/split shortcut #" \| "))
+        desc (translate t cmd)]
+    [:div.inline-grid.grid-cols-4.gap-x-4.w-full
+     {:class (when chosen? "chosen")}
+     [:span.col-span-3 desc]
+     [:div.col-span-1.justify-end.tip.flex
+      (when (and (keyword? id) (namespace id))
+        [:code.opacity-20.bg-transparent (namespace id)])
+      (when-not (string/blank? first-shortcut)
+        [:code.ml-1 first-shortcut])]]))
 
 (rum/defcs command-palette <
   (shortcut/disable-all-shortcuts)
@@ -42,25 +41,24 @@
                    state)}
   [state {:keys [commands limit]
           :or {limit 100}}]
-  (rum/with-context [[t] i18n/*tongue-context*]
-    (let [input (::input state)]
-      [:div.cp__palette.cp__palette-main
-       [:div.input-wrap
-        [:input.cp__palette-input.w-full
-         {:type        "text"
-          :placeholder (t :command-palette/prompt)
-          :auto-focus  true
-          :value       @input
-          :on-change   (fn [e] (reset! input (util/evalue e)))}]]
+  (let [input (::input state)]
+    [:div.cp__palette.cp__palette-main
+     [:div.input-wrap
+      [:input.cp__palette-input.w-full
+       {:type        "text"
+        :placeholder (t :command-palette/prompt)
+        :auto-focus  true
+        :value       @input
+        :on-change   (fn [e] (reset! input (util/evalue e)))}]]
 
-       [:div.command-results-wrap
-        (ui/auto-complete
-         (if (string/blank? @input)
-           (cp/top-commands limit)
-           (get-matched-commands commands @input limit t))
-         {:item-render render-command
-          :class       "cp__palette-results"
-          :on-chosen   (fn [cmd] (cp/invoke-command cmd))})]])))
+     [:div.command-results-wrap
+      (ui/auto-complete
+       (if (string/blank? @input)
+         (cp/top-commands limit)
+         (get-matched-commands commands @input limit t))
+       {:item-render render-command
+        :class       "cp__palette-results"
+        :on-chosen   (fn [cmd] (cp/invoke-command cmd))})]]))
 
 (rum/defc command-palette-modal < rum/reactive
   []

+ 99 - 100
src/main/frontend/components/content.cljs

@@ -7,7 +7,7 @@
             [frontend.components.page-menu :as page-menu]
             [frontend.components.export :as export]
             [frontend.config :as config]
-            [frontend.context.i18n :as i18n]
+            [frontend.context.i18n :refer [t]]
             [frontend.db :as db]
             [frontend.extensions.srs :as srs]
             [frontend.format :as format]
@@ -160,116 +160,115 @@
        #())
      [])
 
-    (rum/with-context [[t] i18n/*tongue-context*]
-      (when-let [block (db/entity [:block/uuid block-id])]
-        (let [properties (:block/properties block)
-              heading? (true? (:heading properties))]
-          [:div#custom-context-menu
-           {:ref *el-ref}
-           [:div.py-1.rounded-md.bg-base-3.shadow-xs
-            [:div.flex-row.flex.justify-between.py-4.pl-2
-             [:div.flex-row.flex.justify-between
-              (for [color block-background-colors]
-                [:a.m-2.shadow-sm
-                 {:on-click (fn [_e]
-                              (editor-handler/set-block-property! block-id "background-color" color))}
-                 [:div.heading-bg {:style {:background-color color}}]])]
-             [:a.text-sm
-              {:title    (t :remove-background)
-               :style    {:margin-right 14
-                          :margin-top   4}
-               :on-click (fn [_e]
-                           (editor-handler/remove-block-property! block-id "background-color"))}
-              "Clear"]]
+    (when-let [block (db/entity [:block/uuid block-id])]
+      (let [properties (:block/properties block)
+            heading? (true? (:heading properties))]
+        [:div#custom-context-menu
+         {:ref *el-ref}
+         [:div.py-1.rounded-md.bg-base-3.shadow-xs
+          [:div.flex-row.flex.justify-between.py-4.pl-2
+           [:div.flex-row.flex.justify-between
+            (for [color block-background-colors]
+              [:a.m-2.shadow-sm
+               {:on-click (fn [_e]
+                            (editor-handler/set-block-property! block-id "background-color" color))}
+               [:div.heading-bg {:style {:background-color color}}]])]
+           [:a.text-sm
+            {:title    (t :remove-background)
+             :style    {:margin-right 14
+                        :margin-top   4}
+             :on-click (fn [_e]
+                         (editor-handler/remove-block-property! block-id "background-color"))}
+            "Clear"]]
 
-            (ui/menu-link
-             {:key      "Convert heading"
-              :on-click (fn [_e]
-                          (if heading?
-                            (editor-handler/remove-block-property! block-id :heading)
-                            (editor-handler/set-block-property! block-id :heading true)))}
-             (if heading?
-               "Convert back to a block"
-               "Convert to a heading"))
+          (ui/menu-link
+           {:key      "Convert heading"
+            :on-click (fn [_e]
+                        (if heading?
+                          (editor-handler/remove-block-property! block-id :heading)
+                          (editor-handler/set-block-property! block-id :heading true)))}
+           (if heading?
+             "Convert back to a block"
+             "Convert to a heading"))
 
-            (ui/menu-link
-             {:key      "Open in sidebar"
-              :on-click (fn [_e]
-                          (editor-handler/open-block-in-sidebar! block-id))}
-             "Open in sidebar")
+          (ui/menu-link
+           {:key      "Open in sidebar"
+            :on-click (fn [_e]
+                        (editor-handler/open-block-in-sidebar! block-id))}
+           "Open in sidebar")
 
-            (ui/menu-link
-             {:key      "Copy block ref"
-              :on-click (fn [_e]
-                          (editor-handler/copy-block-ref! block-id #(str "((" % "))")))}
-             "Copy block ref")
+          (ui/menu-link
+           {:key      "Copy block ref"
+            :on-click (fn [_e]
+                        (editor-handler/copy-block-ref! block-id #(str "((" % "))")))}
+           "Copy block ref")
 
-            (ui/menu-link
-             {:key      "Copy block embed"
-              :on-click (fn [_e]
-                          (editor-handler/copy-block-ref! block-id #(util/format "{{embed ((%s))}}" %)))}
-             "Copy block embed")
+          (ui/menu-link
+           {:key      "Copy block embed"
+            :on-click (fn [_e]
+                        (editor-handler/copy-block-ref! block-id #(util/format "{{embed ((%s))}}" %)))}
+           "Copy block embed")
 
-            (block-template block-id)
+          (block-template block-id)
 
-            (ui/menu-link
-             {:key      "Copy as"
-              :on-click (fn [_]
-                          (state/set-modal! #(export/export-blocks [block-id])))}
-             "Copy as")
-
-            (if (srs/card-block? block)
-              (ui/menu-link
-               {:key      "Preview Card"
-                :on-click #(srs/preview [(db/pull [:block/uuid block-id])])}
-               "Preview Card")
-              (ui/menu-link
-               {:key      "Make a Card"
-                :on-click #(srs/make-block-a-card! block-id)}
-               "Make a Card"))
+          (ui/menu-link
+           {:key      "Copy as"
+            :on-click (fn [_]
+                        (state/set-modal! #(export/export-blocks [block-id])))}
+           "Copy as")
 
+          (if (srs/card-block? block)
             (ui/menu-link
-             {:key      "Cut"
-              :on-click (fn [_e]
-                          (editor-handler/cut-block! block-id))}
-             "Cut")
-
+             {:key      "Preview Card"
+              :on-click #(srs/preview [(db/pull [:block/uuid block-id])])}
+             "Preview Card")
             (ui/menu-link
-             {:key      "Expand all"
-              :on-click (fn [_e]
-                          (editor-handler/expand-all! block-id))}
-             "Expand all")
+             {:key      "Make a Card"
+              :on-click #(srs/make-block-a-card! block-id)}
+             "Make a Card"))
+
+          (ui/menu-link
+           {:key      "Cut"
+            :on-click (fn [_e]
+                        (editor-handler/cut-block! block-id))}
+           "Cut")
+
+          (ui/menu-link
+           {:key      "Expand all"
+            :on-click (fn [_e]
+                        (editor-handler/expand-all! block-id))}
+           "Expand all")
 
+          (ui/menu-link
+           {:key      "Collapse all"
+            :on-click (fn [_e]
+                        (editor-handler/collapse-all! block-id))}
+           "Collapse all")
+
+          (when (state/sub [:plugin/simple-commands])
+            (when-let [cmds (state/get-plugins-commands-with-type :block-context-menu-item)]
+              (for [[_ {:keys [key label] :as cmd} action pid] cmds]
+                (ui/menu-link
+                 {:key      key
+                  :on-click #(commands/exec-plugin-simple-command!
+                              pid (assoc cmd :uuid block-id) action)}
+                 label))))
+
+          (when (state/sub [:ui/developer-mode?])
             (ui/menu-link
-             {:key      "Collapse all"
-              :on-click (fn [_e]
-                          (editor-handler/collapse-all! block-id))}
-             "Collapse all")
-
-            (when (state/sub [:plugin/simple-commands])
-              (when-let [cmds (state/get-plugins-commands-with-type :block-context-menu-item)]
-                (for [[_ {:keys [key label] :as cmd} action pid] cmds]
-                  (ui/menu-link
-                   {:key      key
-                    :on-click #(commands/exec-plugin-simple-command!
-                                pid (assoc cmd :uuid block-id) action)}
-                   label))))
-
-            (when (state/sub [:ui/developer-mode?])
-              (ui/menu-link
-               {:key      "(Dev) Show block data"
-                :on-click (fn []
-                            (let [block-data (with-out-str (pprint/pprint (db/pull [:block/uuid block-id])))]
-                              (println block-data)
-                              (notification/show!
-                               [:div
-                                [:pre.code block-data]
-                                [:br]
-                                (ui/button "Copy to clipboard"
-                                           :on-click #(.writeText js/navigator.clipboard block-data))]
-                               :success
-                               false)))}
-               "(Dev) Show block data"))]])))))
+             {:key      "(Dev) Show block data"
+              :on-click (fn []
+                          (let [block-data (with-out-str (pprint/pprint (db/pull [:block/uuid block-id])))]
+                            (println block-data)
+                            (notification/show!
+                             [:div
+                              [:pre.code block-data]
+                              [:br]
+                              (ui/button "Copy to clipboard"
+                                :on-click #(.writeText js/navigator.clipboard block-data))]
+                             :success
+                             false)))}
+             "(Dev) Show block data"))]]))))
 
 (rum/defc block-ref-custom-context-menu-content
   [block block-ref-id]

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

@@ -18,7 +18,6 @@
             [frontend.mixins :as mixins]
             [frontend.modules.shortcut.core :as shortcut]
             [frontend.state :as state]
-            [frontend.search.db :as search-db]
             [frontend.ui :as ui]
             [frontend.util :as util]
             [frontend.util.cursor :as cursor]
@@ -177,8 +176,7 @@
                              repo (state/sub :git/current-repo)
                              format (db/get-page-format page)
                              block (db-model/query-block-by-uuid uuid)
-                             content (search-db/block->content block)]
-
+                             content (:block/content block)]
                          [:.py-2 (search/block-search-result-item repo uuid format content q :block)]))
         :class       "black"}))))
 

+ 119 - 123
src/main/frontend/components/encryption.cljs

@@ -1,6 +1,6 @@
 (ns frontend.components.encryption
   (:require [clojure.string :as string]
-            [frontend.context.i18n :as i18n]
+            [frontend.context.i18n :refer [t]]
             [frontend.encrypt :as e]
             [frontend.handler.metadata :as metadata-handler]
             [frontend.handler.notification :as notification]
@@ -16,33 +16,32 @@
   (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)]
-    (rum/with-context [[t] i18n/*tongue-context*]
-      [: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)]]]])))
+    [: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]
@@ -53,77 +52,75 @@
   (rum/local "" ::password)
   (rum/local "" ::password-confirm)
   [state repo-url close-fn]
-  (rum/with-context [[_t] i18n/*tongue-context*]
-    (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]
-  (rum/with-context [[t] i18n/*tongue-context*]
+  (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
-        "Do you want to create an encrypted graph?"]]]
+       [: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 []
-                     (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)]]]]))
+                     (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]
@@ -137,39 +134,38 @@
   (rum/local "" ::secret)
   (rum/local false ::loading)
   [state _repo-url db-encrypted-secret close-fn]
-  (rum/with-context [[_t] i18n/*tongue-context*]
-    (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")]]]])))
+  (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]

+ 30 - 31
src/main/frontend/components/export.cljs

@@ -4,43 +4,42 @@
             [frontend.util :as util]
             [frontend.handler.export :as export]
             [frontend.state :as state]
-            [frontend.context.i18n :as i18n]))
+            [frontend.context.i18n :refer [t]]))
 
 (rum/defc export
   []
   (when-let [current-repo (state/get-current-repo)]
-    (rum/with-context [[t] i18n/*tongue-context*]
-      [:div.export
-       [:h1.title "Export"]
+    [:div.export
+     [:h1.title "Export"]
 
-       [:ul.mr-1
-        (when (util/electron?)
-          [:li.mb-4
-           [:a.font-medium {:on-click #(export/export-repo-as-html! current-repo)}
-            (t :export-public-pages)]])
+     [:ul.mr-1
+      (when (util/electron?)
         [:li.mb-4
-         [:a.font-medium {:on-click #(export/export-repo-as-markdown! current-repo)}
-          (t :export-markdown)]]
-        [:li.mb-4
-         [:a.font-medium {:on-click #(export/export-repo-as-opml! current-repo)}
-          (t :export-opml)]]
-        [:li.mb-4
-         [:a.font-medium {:on-click #(export/export-repo-as-edn-v2! current-repo)}
-          (t :export-edn)]]
-        [:li.mb-4
-         [:a.font-medium {:on-click #(export/export-repo-as-json-v2! current-repo)}
-          (t :export-json)]]
-        [:li.mb-4
-         [:a.font-medium {:on-click #(export/export-repo-as-roam-json! current-repo)}
-          (t :export-roam-json)]]]
-       [:a#download-as-edn-v2.hidden]
-       [:a#download-as-json-v2.hidden]
-       [:a#download-as-roam-json.hidden]
-       [:a#download-as-html.hidden]
-       [:a#download-as-zip.hidden]
-       [:a#export-as-markdown.hidden]
-       [:a#export-as-opml.hidden]
-       [:a#convert-markdown-to-unordered-list-or-heading.hidden]])))
+         [:a.font-medium {:on-click #(export/export-repo-as-html! current-repo)}
+          (t :export-public-pages)]])
+      [:li.mb-4
+       [:a.font-medium {:on-click #(export/export-repo-as-markdown! current-repo)}
+        (t :export-markdown)]]
+      [:li.mb-4
+       [:a.font-medium {:on-click #(export/export-repo-as-opml! current-repo)}
+        (t :export-opml)]]
+      [:li.mb-4
+       [:a.font-medium {:on-click #(export/export-repo-as-edn-v2! current-repo)}
+        (t :export-edn)]]
+      [:li.mb-4
+       [:a.font-medium {:on-click #(export/export-repo-as-json-v2! current-repo)}
+        (t :export-json)]]
+      [:li.mb-4
+       [:a.font-medium {:on-click #(export/export-repo-as-roam-json! current-repo)}
+        (t :export-roam-json)]]]
+     [:a#download-as-edn-v2.hidden]
+     [:a#download-as-json-v2.hidden]
+     [:a#download-as-roam-json.hidden]
+     [:a#download-as-html.hidden]
+     [:a#download-as-zip.hidden]
+     [:a#export-as-markdown.hidden]
+     [:a#export-as-opml.hidden]
+     [:a#convert-markdown-to-unordered-list-or-heading.hidden]]))
 
 
 (def *export-block-type (atom :text))

+ 76 - 78
src/main/frontend/components/file.cljs

@@ -2,11 +2,11 @@
   (:require [cljs-time.coerce :as tc]
             [cljs-time.core :as t]
             [clojure.string :as string]
-            [datascript.core :as dc]
+            [datascript.core :as d]
             [frontend.components.lazy-editor :as lazy-editor]
             [frontend.components.svg :as svg]
             [frontend.config :as config]
-            [frontend.context.i18n :as i18n]
+            [frontend.context.i18n :refer [t]]
             [frontend.date :as date]
             [frontend.db :as db]
             [frontend.format :as format]
@@ -24,43 +24,42 @@
 
 (rum/defc files < rum/reactive
   []
-  (rum/with-context [[tongue] i18n/*tongue-context*]
-    [:div.flex-1.overflow-hidden
-     [:h1.title
-      (tongue :all-files)]
-     (when-let [current-repo (state/sub :git/current-repo)]
-       (let [files (db/get-files current-repo)
-             mobile? (util/mobile?)]
-         [:table.table-auto
-          [:thead
-           [:tr
-            [:th (tongue :file/name)]
-            (when-not mobile?
-              [:th (tongue :file/last-modified-at)])
-            (when-not mobile?
-              [:th ""])]]
-          [:tbody
-           (for [[file modified-at] files]
-             (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 "/") "")})
-                              (rfe/href :file {:path file-id}))]
-                   [:a {:href href}
-                    file])]
-                (when-not mobile?
-                  [:td [:span.text-gray-500.text-sm
-                       (if (zero? modified-at)
-                         (tongue :file/no-data)
-                         (date/get-date-time-string
-                          (t/to-default-time-zone (tc/to-date-time modified-at))))]])
+  [:div.flex-1.overflow-hidden
+   [:h1.title
+    (t :all-files)]
+   (when-let [current-repo (state/sub :git/current-repo)]
+     (let [files (db/get-files current-repo)
+           mobile? (util/mobile?)]
+       [:table.table-auto
+        [:thead
+         [:tr
+          [:th (t :file/name)]
+          (when-not mobile?
+            [:th (t :file/last-modified-at)])
+          (when-not mobile?
+            [:th ""])]]
+        [:tbody
+         (for [[file modified-at] files]
+           (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 "/") "")})
+                            (rfe/href :file {:path file-id}))]
+                 [:a {:href href}
+                  file])]
+              (when-not mobile?
+                [:td [:span.text-gray-500.text-sm
+                      (if (zero? modified-at)
+                        (t :file/no-data)
+                        (date/get-date-time-string
+                         (t/to-default-time-zone (tc/to-date-time modified-at))))]])
 
-                (when-not mobile?
-                  [:td [:a.text-sm
-                       {:on-click (fn [_e]
-                                    (export-handler/download-file! file))}
-                       [:span (tongue :download)]]])]))]]))]))
+              (when-not mobile?
+                [:td [:a.text-sm
+                      {:on-click (fn [_e]
+                                   (export-handler/download-file! file))}
+                      [:span (t :download)]]])]))]]))])
 
 (rum/defcs file < rum/reactive
   {:did-mount (fn [state]
@@ -73,47 +72,46 @@
   (let [path (get-path state)
         format (format/get-format path)
         original-name (db/get-file-page path)
-        random-id (str (dc/squuid))]
-    (rum/with-context [[tongue] i18n/*tongue-context*]
-      [:div.file {:id (str "file-edit-wrapper-" random-id)}
-       [:h1.title
-        [:bdi (js/decodeURI path)]]
-       (when original-name
-         [:div.text-sm.mb-4.ml-1 "Page: "
-          [:a.bg-base-2.p-1.ml-1 {:style {:border-radius 4}
-                                  :href (rfe/href :page {:name original-name})
-                                  :on-click (fn [e]
-                                              (when (gobj/get e "shiftKey")
-                                                (when-let [page (db/entity [:block/name (util/page-name-sanity-lc original-name)])]
-                                                  (state/sidebar-add-block!
-                                                   (state/get-current-repo)
-                                                   (:db/id page)
-                                                   :page
-                                                   {:page page}))
-                                                (util/stop e)))}
-           original-name]])
+        random-id (str (d/squuid))]
+    [:div.file {:id (str "file-edit-wrapper-" random-id)}
+     [:h1.title
+      [:bdi (js/decodeURI path)]]
+     (when original-name
+       [:div.text-sm.mb-4.ml-1 "Page: "
+        [:a.bg-base-2.p-1.ml-1 {:style {:border-radius 4}
+                                :href (rfe/href :page {:name original-name})
+                                :on-click (fn [e]
+                                            (when (gobj/get e "shiftKey")
+                                              (when-let [page (db/entity [:block/name (util/page-name-sanity-lc original-name)])]
+                                                (state/sidebar-add-block!
+                                                 (state/get-current-repo)
+                                                 (:db/id page)
+                                                 :page
+                                                 {:page page}))
+                                              (util/stop e)))}
+         original-name]])
 
-       (when (and original-name (not (string/starts-with? original-name "logseq/")))
-         [:p.text-sm.ml-1.mb-4
-          (svg/warning {:style {:width "1em"
-                                :display "inline-block"}})
-          [:span.ml-1 "Please don't remove the page's title property (you can still modify it)."]])
+     (when (and original-name (not (string/starts-with? original-name "logseq/")))
+       [:p.text-sm.ml-1.mb-4
+        (svg/warning {:style {:width "1em"
+                              :display "inline-block"}})
+        [:span.ml-1 "Please don't remove the page's title property (you can still modify it)."]])
 
-       (cond
-         ;; image type
-         (and format (contains? (config/img-formats) format))
-         [:img {:src path}]
+     (cond
+       ;; image type
+       (and format (contains? (config/img-formats) format))
+       [:img {:src path}]
 
-         (and format (contains? (config/text-formats) format))
-         (when-let [file-content (db/get-file path)]
-           (let [content (string/trim file-content)
-                 mode (util/get-file-ext path)]
-             (lazy-editor/editor {:file? true
-                                  :file-path path}
-                                 (str "file-edit-" random-id)
-                                 {:data-lang mode}
-                                 content
-                                 {})))
+       (and format (contains? (config/text-formats) format))
+       (when-let [file-content (db/get-file path)]
+         (let [content (string/trim file-content)
+               mode (util/get-file-ext path)]
+           (lazy-editor/editor {:file? true
+                                :file-path path}
+                               (str "file-edit-" random-id)
+                               {:data-lang mode}
+                               content
+                               {})))
 
-         :else
-         [:div (tongue :file/format-not-supported (name format))])])))
+       :else
+       [:div (t :file/format-not-supported (name format))])]))

+ 99 - 101
src/main/frontend/components/header.cljs

@@ -6,7 +6,7 @@
             [frontend.components.right-sidebar :as sidebar]
             [frontend.components.svg :as svg]
             [frontend.config :as config]
-            [frontend.context.i18n :as i18n]
+            [frontend.context.i18n :refer [t]]
             [frontend.handler :as handler]
             [frontend.handler.page :as page-handler]
             [frontend.handler.plugin :as plugin-handler]
@@ -34,24 +34,23 @@
 
 (rum/defc login
   [logged?]
-  (rum/with-context [[t] i18n/*tongue-context*]
-    (when (and (not logged?)
-               (not config/publishing?))
-
-      (ui/dropdown-with-links
-       (fn [{:keys [toggle-fn]}]
-         [:a.button.text-sm.font-medium.block {:on-click toggle-fn}
-          [:span (t :login)]])
-       (let [list [{:title (t :login-github)
-                    :url (str config/website "/login/github")}]]
-         (mapv
-          (fn [{:keys [title url]}]
-            {:title title
-             :options
-             {:on-click
-              (fn [_] (set! (.-href js/window.location) url))}})
-          list))
-       nil))))
+  (when (and (not logged?)
+             (not config/publishing?))
+
+    (ui/dropdown-with-links
+     (fn [{:keys [toggle-fn]}]
+       [:a.button.text-sm.font-medium.block {:on-click toggle-fn}
+        [:span (t :login)]])
+     (let [list [{:title (t :login-github)
+                  :url (str config/website "/login/github")}]]
+       (mapv
+        (fn [{:keys [title url]}]
+          {:title title
+           :options
+           {:on-click
+            (fn [_] (set! (.-href js/window.location) url))}})
+        list))
+     nil)))
 
 (rum/defc left-menu-button < rum/reactive
   [{:keys [on-click]}]
@@ -164,90 +163,89 @@
                                (not (mobile-util/is-native-platform?))
                                (not config/publishing?))
         refreshing? (state/sub :nfs/refreshing?)]
-    (rum/with-context [[t] i18n/*tongue-context*]
-      [:div.cp__header#head
-       {:class           (util/classnames [{:electron-mac   electron-mac?
-                                            :native-ios     (mobile-util/native-ios?)
-                                            :native-android (mobile-util/native-android?)}])
-        :on-double-click (fn [^js e]
-                           (when-let [target (.-target e)]
-                             (when (and (util/electron?)
-                                        (.. target -classList (contains "cp__header")))
-                               (js/window.apis.toggleMaxOrMinActiveWindow))))
-        :style           {:fontSize  50
-                          :transform (str "translateY(" (or (:offset-top vw-state) 0) "px)")}}
-       [:div.l.flex
-        (left-menu-button {:on-click (fn []
-                                       (open-fn)
-                                       (state/set-left-sidebar-open!
-                                        (not (:ui/left-sidebar-open? @state/state))))})
-
-        (when current-repo ;; this is for the Search button
-          (ui/with-shortcut :go/search "right"
-            [:a.button#search-button
-             {:on-click #(do (when (or (mobile-util/native-android?)
-                                       (mobile-util/native-iphone?))
-                               (state/set-left-sidebar-open! false))
-                             (state/pub-event! [:go/search]))}
-             (ui/icon "search" {:style {:fontSize ui/icon-size}})]))]
-
-       [:div.r.flex
-        (when (and (not (mobile-util/is-native-platform?))
-                   (not (util/electron?)))
-          (login logged?))
-
-        (when plugin-handler/lsp-enabled?
-          (plugins/hook-ui-items :toolbar))
-
-        (when (not= (state/get-current-route) :home)
-          (home-button))
-
-        (when (or (util/electron?)
-                  (mobile-util/native-ios?))
-          (back-and-forward))
-
-        (new-block-mode)
-
-        (when (and (mobile-util/is-native-platform?) (seq repos))
-          [:a.text-sm.font-medium.button
-           {:on-click
-            (fn []
-              (state/pub-event!
-               [:modal/show
-                [:div {:style {:max-width 700}}
-                 [:p "Refresh detects and processes files modified on your disk and diverged from the actual Logseq page content. Continue?"]
-                 (ui/button
-                  "Yes"
-                  :on-click (fn []
-                              (state/close-modal!)
-                              (nfs/refresh! (state/get-current-repo) repo/refresh-cb)))]]))}
-           (if refreshing?
-             [:div {:class "animate-spin-reverse"}
-              svg/refresh]
-             [:div.flex.flex-row.text-center.open-button__inner.items-center
-              (ui/icon "refresh" {:style {:fontSize ui/icon-size}})])])
-
-        (repo/sync-status current-repo)
-
-        (when show-open-folder?
-          [:a.text-sm.font-medium.button
-           {:on-click #(page-handler/ls-dir-files! shortcut/refresh!)}
+    [:div.cp__header#head
+     {:class           (util/classnames [{:electron-mac   electron-mac?
+                                          :native-ios     (mobile-util/native-ios?)
+                                          :native-android (mobile-util/native-android?)}])
+      :on-double-click (fn [^js e]
+                         (when-let [target (.-target e)]
+                           (when (and (util/electron?)
+                                      (.. target -classList (contains "cp__header")))
+                             (js/window.apis.toggleMaxOrMinActiveWindow))))
+      :style           {:fontSize  50
+                        :transform (str "translateY(" (or (:offset-top vw-state) 0) "px)")}}
+     [:div.l.flex
+      (left-menu-button {:on-click (fn []
+                                     (open-fn)
+                                     (state/set-left-sidebar-open!
+                                      (not (:ui/left-sidebar-open? @state/state))))})
+
+      (when current-repo ;; this is for the Search button
+        (ui/with-shortcut :go/search "right"
+          [:a.button#search-button
+           {:on-click #(do (when (or (mobile-util/native-android?)
+                                     (mobile-util/native-iphone?))
+                             (state/set-left-sidebar-open! false))
+                           (state/pub-event! [:go/search]))}
+           (ui/icon "search" {:style {:fontSize ui/icon-size}})]))]
+
+     [:div.r.flex
+      (when (and (not (mobile-util/is-native-platform?))
+                 (not (util/electron?)))
+        (login logged?))
+
+      (when plugin-handler/lsp-enabled?
+        (plugins/hook-ui-items :toolbar))
+
+      (when (not= (state/get-current-route) :home)
+        (home-button))
+
+      (when (or (util/electron?)
+                (mobile-util/native-ios?))
+        (back-and-forward))
+
+      (new-block-mode)
+
+      (when (and (mobile-util/is-native-platform?) (seq repos))
+        [:a.text-sm.font-medium.button
+         {:on-click
+          (fn []
+            (state/pub-event!
+             [:modal/show
+              [:div {:style {:max-width 700}}
+               [:p "Refresh detects and processes files modified on your disk and diverged from the actual Logseq page content. Continue?"]
+               (ui/button
+                 "Yes"
+                 :on-click (fn []
+                             (state/close-modal!)
+                             (nfs/refresh! (state/get-current-repo) repo/refresh-cb)))]]))}
+         (if refreshing?
+           [:div {:class "animate-spin-reverse"}
+            svg/refresh]
            [:div.flex.flex-row.text-center.open-button__inner.items-center
-            (ui/icon "folder-plus")
-            (when-not config/mobile?
-              [:span.ml-1 {:style {:margin-top (if electron-mac? 0 2)}}
-               (t :open)])]])
+            (ui/icon "refresh" {:style {:fontSize ui/icon-size}})])])
+
+      (repo/sync-status current-repo)
+
+      (when show-open-folder?
+        [:a.text-sm.font-medium.button
+         {:on-click #(page-handler/ls-dir-files! shortcut/refresh!)}
+         [:div.flex.flex-row.text-center.open-button__inner.items-center
+          (ui/icon "folder-plus")
+          (when-not config/mobile?
+            [:span.ml-1 {:style {:margin-top (if electron-mac? 0 2)}}
+             (t :open)])]])
 
-        (when config/publishing?
-          [:a.text-sm.font-medium.button {:href (rfe/href :graph)}
-           (t :graph)])
+      (when config/publishing?
+        [:a.text-sm.font-medium.button {:href (rfe/href :graph)}
+         (t :graph)])
 
-        (dropdown-menu {:me           me
-                        :t            t
-                        :current-repo current-repo
-                        :default-home default-home})
+      (dropdown-menu {:me           me
+                      :t            t
+                      :current-repo current-repo
+                      :default-home default-home})
 
-        (when (not (state/sub :ui/sidebar-open?))
-          (sidebar/toggle))
+      (when (not (state/sub :ui/sidebar-open?))
+        (sidebar/toggle))
 
-        (updater-tips-new-version t)]])))
+      (updater-tips-new-version t)]]))

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

@@ -19,7 +19,7 @@
             [rum.core :as rum]
             [frontend.mobile.util :as mobile-util]
             [frontend.modules.shortcut.core :as shortcut]
-            [frontend.context.i18n :as i18n]))
+            [frontend.context.i18n :refer [t]]))
 
 (rum/defc blocks-cp < rum/reactive db-mixins/query
   {}
@@ -43,14 +43,13 @@
                          (let [page-names (model/get-page-names-by-ids (map :db/id (:block/tags page)))]
                            (text/build-data-value page-names)))]
     (if (and (mobile-util/is-native-platform?) intro?)
-      (rum/with-context [[t] i18n/*tongue-context*]
-        [:div
-         [:h1.title
-          (t :new-graph)]
+      [:div
+       [:h1.title
+        (t :new-graph)]
 
-         (ui/button
-           (t :open-a-directory)
-           :on-click #(page-handler/ls-dir-files! shortcut/refresh!))])
+       (ui/button
+         (t :open-a-directory)
+         :on-click #(page-handler/ls-dir-files! shortcut/refresh!))]
       [:div.flex-1.journal.page (cond->
                                   {:class (if intro? "logseq-intro" "")}
                                   data-page-tags

+ 192 - 194
src/main/frontend/components/onboarding.cljs

@@ -1,217 +1,215 @@
 (ns frontend.components.onboarding
   (:require [frontend.components.svg :as svg]
-            [frontend.context.i18n :as i18n]
+            [frontend.context.i18n :refer [t]]
             [frontend.handler.route :as route-handler]
             [rum.core :as rum]
             [frontend.ui :as ui]))
 
 (rum/defc intro
   []
-  (rum/with-context [[t] i18n/*tongue-context*]
-    [:div#logseq-intro.pl-1
-     [:div.flex-1
-      [:div.flex.flex-col.pl-1.ls-block
-       [:hr {:style {:margin-top 200}}]
-       [:div.flex.flex-row.admonitionblock.align-items {:class "important"}
-        [:div.pr-4.admonition-icon.flex.flex-col.justify-center
-         {:title "Important"} (svg/tip)]
-        [:div.ml-4.text-lg
-         (t :on-boarding/notice)]]
-       [:p
-        {}
-        (t :on-boarding/features-desc)]
-       [:p
-        {}
-        (t :on-boarding/privacy)]
-       [:p
-        {}
-        [:strong {} "Logseq"]
-        (t :on-boarding/inspired-by)
-        [:a {:href "https://roamresearch.com/"
-             :target "_blank"} "Roam Research"]
-        ", "
-        [:a {:href "https://orgmode.org/"
-             :target "_blank"} "Org Mode"]
-        ", "
-        [:a {:href "https://tiddlywiki.com/"
-             :target "_blank"} "Tiddlywiki"]
-        " and "
-        [:a {:href "https://workflowy.com/"
-             :target "_blank"} "Workflowy"]
-        ", hats off to all of them!"]]
+  [:div#logseq-intro.pl-1
+   [:div.flex-1
+    [:div.flex.flex-col.pl-1.ls-block
+     [:hr {:style {:margin-top 200}}]
+     [:div.flex.flex-row.admonitionblock.align-items {:class "important"}
+      [:div.pr-4.admonition-icon.flex.flex-col.justify-center
+       {:title "Important"} (svg/tip)]
+      [:div.ml-4.text-lg
+       (t :on-boarding/notice)]]
+     [:p
+      {}
+      (t :on-boarding/features-desc)]
+     [:p
+      {}
+      (t :on-boarding/privacy)]
+     [:p
+      {}
+      [:strong {} "Logseq"]
+      (t :on-boarding/inspired-by)
+      [:a {:href "https://roamresearch.com/"
+           :target "_blank"} "Roam Research"]
+      ", "
+      [:a {:href "https://orgmode.org/"
+           :target "_blank"} "Org Mode"]
+      ", "
+      [:a {:href "https://tiddlywiki.com/"
+           :target "_blank"} "Tiddlywiki"]
+      " and "
+      [:a {:href "https://workflowy.com/"
+           :target "_blank"} "Workflowy"]
+      ", hats off to all of them!"]]
 
-      [:img.shadow-2xl
-       {:src
-        "https://asset.logseq.com/static/img/screenshot.png"
-        :alt "screenshot"}]
+    [:img.shadow-2xl
+     {:src
+      "https://asset.logseq.com/static/img/screenshot.png"
+      :alt "screenshot"}]
 
-      [:div.flex.flex-col.ls-block.intro-docs
-       [:h2 {} (t :on-boarding/features)]
-       [:ul
-        {}
-        [:li {} (t :on-boarding/features-backlinks)]
-        [:li {} (t :on-boarding/features-block-embed)]
-        [:li {} (t :on-boarding/features-page-embed)]
-        [:li {} (t :on-boarding/features-graph-vis)]
-        [:li {} "PDF annotations"]
-        [:li {} "Zotero integration"]
-        [:li {} "Spaced repetition cards"]
-        [:li {} (t :on-boarding/features-heading-properties)]
-        [:li
-         {}
-         (t :on-boarding/features-datalog)
-         [:a {:href "https://github.com/tonsky/datascript"
-              :target "_blank"} "Datascript"]]
-        [:li {} (t :on-boarding/features-custom-view-component)]
-        [:li
-         {}
-         [:a {:href "https://excalidraw.com/"
-              :target "_blank"} "Excalidraw"]
-         (t :on-boarding/integration)]
-        [:li
-         {}
-         [:a {:href "https://revealjs.com/"
-              :target "_blank"} "reveal.js"]
-         (t :on-boarding/slide-support)]
-        [:li
-         {}
-         (t :on-boarding/built-in-supports)
-         [:ul
-          {}
-          [:li {} (t :on-boarding/supports-code-highlights)]
-          [:li {} (t :on-boarding/supports-katex-latex)]
-          [:li
-           {}
-           (t :on-boarding/raw)
-           [:a {:href "https://github.com/weavejester/hiccup"
-                :target "_blank"} "hiccup"]]
-          [:li {} (t :on-boarding/raw-html)]]]]
-       [:h2 {} (t :on-boarding/learn-more)]
+    [:div.flex.flex-col.ls-block.intro-docs
+     [:h2 {} (t :on-boarding/features)]
+     [:ul
+      {}
+      [:li {} (t :on-boarding/features-backlinks)]
+      [:li {} (t :on-boarding/features-block-embed)]
+      [:li {} (t :on-boarding/features-page-embed)]
+      [:li {} (t :on-boarding/features-graph-vis)]
+      [:li {} "PDF annotations"]
+      [:li {} "Zotero integration"]
+      [:li {} "Spaced repetition cards"]
+      [:li {} (t :on-boarding/features-heading-properties)]
+      [:li
+       {}
+       (t :on-boarding/features-datalog)
+       [:a {:href "https://github.com/tonsky/datascript"
+            :target "_blank"} "Datascript"]]
+      [:li {} (t :on-boarding/features-custom-view-component)]
+      [:li
+       {}
+       [:a {:href "https://excalidraw.com/"
+            :target "_blank"} "Excalidraw"]
+       (t :on-boarding/integration)]
+      [:li
+       {}
+       [:a {:href "https://revealjs.com/"
+            :target "_blank"} "reveal.js"]
+       (t :on-boarding/slide-support)]
+      [:li
+       {}
+       (t :on-boarding/built-in-supports)
        [:ul
         {}
+        [:li {} (t :on-boarding/supports-code-highlights)]
+        [:li {} (t :on-boarding/supports-katex-latex)]
         [:li
          {}
-         "Twitter: "
-         [:a
-          {:href "https://twitter.com/logseq"
-           :target "_blank"}
-          "https://twitter.com/logseq"]]
-        [:li
-         {}
-         "Discord: "
-         [:a
-          {:href "https://discord.gg/KpN4eHY"
-           :target "_blank"}
-          "https://discord.gg/KpN4eHY"]
-         (t :on-boarding/discord-desc)]
-        [:li
-         {}
-         "GitHub: "
-         [:a
-          {:href "https://github.com/logseq/logseq"
-           :target "_blank"}
-          "https://github.com/logseq/logseq"]
-         (t :on-boarding/github-desc)]
-        [:li
-         {}
-         (t :on-boarding/our-blog)
-         [:a
-          {:href "https://docs.logseq.com/"
-           :target "_blank"}
-          "https://docs.logseq.com/"]]]
-       [:h2 (t :on-boarding/credits-to)]
-       [:ul
-        {}
-        [:li [:a {:href "https://roamresearch.com/"
-                  :target "_blank"} "Roam Research"]]
-        [:li [:a {:href "https://orgmode.org/"
-                  :target "_blank"} "Org Mode"]]
-        [:li [:a {:href "https://tiddlywiki.com/"
-                  :target "_blank"} "Tiddlywiki"]]
-        [:li
-         [:a {:href "https://workflowy.com/"
-              :target "_blank"} "Workflowy"]]
-        [:li
-         [:a
-          {:href "https://clojure.org"
-           :target "_blank"}
-          "Clojure && Clojurescript"]
-         (t :on-boarding/clojure-desc)]
-        [:li
-         [:a {:href "https://github.com/tonsky/datascript"
-              :target "_blank"} "Datascript"]
-         (t :on-boarding/datascript-desc)]
-        [:li
-         [:a {:href "https://ocaml.org/"
-              :target "_blank"} "OCaml"]
-         " && "
-         [:a
-          {:href "https://github.com/inhabitedtype/angstrom"
-           :target "_blank"}
-          "Angstrom"]
-         (t :on-boarding/angstrom-desc-1)
-         [:a {:href "https://github.com/mldoc/mldoc"
-              :target "_blank"} (t :on-boarding/angstrom-desc-2)]
-         (t :on-boarding/angstrom-desc-3)]
-        [:li
-         [:a {:href "https://github.com/talex5/cuekeeper"
-              :target "_blank"} "Cuekeeper"]
-         (t :on-boarding/cuekeeper-desc)]
-        [:li
-         [:a {:href "https://github.com/borkdude/sci"
-              :target "_blank"} "sci"]
-         (t :on-boarding/sci-desc)]
-        [:li
-         [:a {:href "https://isomorphic-git.org/"
-              :target "_blank"} "isomorphic-git"]
-         (t :on-boarding/isomorphic-git-desc)]]
+         (t :on-boarding/raw)
+         [:a {:href "https://github.com/weavejester/hiccup"
+              :target "_blank"} "hiccup"]]
+        [:li {} (t :on-boarding/raw-html)]]]]
+     [:h2 {} (t :on-boarding/learn-more)]
+     [:ul
+      {}
+      [:li
+       {}
+       "Twitter: "
+       [:a
+        {:href "https://twitter.com/logseq"
+         :target "_blank"}
+        "https://twitter.com/logseq"]]
+      [:li
+       {}
+       "Discord: "
+       [:a
+        {:href "https://discord.gg/KpN4eHY"
+         :target "_blank"}
+        "https://discord.gg/KpN4eHY"]
+       (t :on-boarding/discord-desc)]
+      [:li
+       {}
+       "GitHub: "
+       [:a
+        {:href "https://github.com/logseq/logseq"
+         :target "_blank"}
+        "https://github.com/logseq/logseq"]
+       (t :on-boarding/github-desc)]
+      [:li
+       {}
+       (t :on-boarding/our-blog)
+       [:a
+        {:href "https://docs.logseq.com/"
+         :target "_blank"}
+        "https://docs.logseq.com/"]]]
+     [:h2 (t :on-boarding/credits-to)]
+     [:ul
+      {}
+      [:li [:a {:href "https://roamresearch.com/"
+                :target "_blank"} "Roam Research"]]
+      [:li [:a {:href "https://orgmode.org/"
+                :target "_blank"} "Org Mode"]]
+      [:li [:a {:href "https://tiddlywiki.com/"
+                :target "_blank"} "Tiddlywiki"]]
+      [:li
+       [:a {:href "https://workflowy.com/"
+            :target "_blank"} "Workflowy"]]
+      [:li
+       [:a
+        {:href "https://clojure.org"
+         :target "_blank"}
+        "Clojure && Clojurescript"]
+       (t :on-boarding/clojure-desc)]
+      [:li
+       [:a {:href "https://github.com/tonsky/datascript"
+            :target "_blank"} "Datascript"]
+       (t :on-boarding/datascript-desc)]
+      [:li
+       [:a {:href "https://ocaml.org/"
+            :target "_blank"} "OCaml"]
+       " && "
+       [:a
+        {:href "https://github.com/inhabitedtype/angstrom"
+         :target "_blank"}
+        "Angstrom"]
+       (t :on-boarding/angstrom-desc-1)
+       [:a {:href "https://github.com/mldoc/mldoc"
+            :target "_blank"} (t :on-boarding/angstrom-desc-2)]
+       (t :on-boarding/angstrom-desc-3)]
+      [:li
+       [:a {:href "https://github.com/talex5/cuekeeper"
+            :target "_blank"} "Cuekeeper"]
+       (t :on-boarding/cuekeeper-desc)]
+      [:li
+       [:a {:href "https://github.com/borkdude/sci"
+            :target "_blank"} "sci"]
+       (t :on-boarding/sci-desc)]
+      [:li
+       [:a {:href "https://isomorphic-git.org/"
+            :target "_blank"} "isomorphic-git"]
+       (t :on-boarding/isomorphic-git-desc)]]
 
-       [:img {:src
-              "https://asset.logseq.com/static/img/credits.png"
-              :style {:margin "12px 0 0 0"}}]]]]))
+     [:img {:src
+            "https://asset.logseq.com/static/img/credits.png"
+            :style {:margin "12px 0 0 0"}}]]]])
 
 (defn help
   []
-  (rum/with-context [[t] i18n/*tongue-context*]
-    [:div.help.cp__sidebar-help-docs
-     (let [discord-with-icon [:div.flex-row.inline-flex.items-center
-                              [:span.mr-1 (t :help/community)]
-                              (ui/icon "brand-discord" {:style {:font-size 20}})]
-           list
-           [{:title "Usage"
-             :children [[[:a
-                          {:on-click (fn [] (route-handler/redirect! {:to :shortcut-setting}))}
-                          [:div.flex-row.inline-flex.items-center
-                           [:span.mr-1 (t :help/shortcuts)]
-                           (ui/icon "command" {:style {:font-size 20}})]]]
-                        [(t :help/docs) "https://docs.logseq.com/"]
-                        ["FAQ" "https://docs.logseq.com/#/page/faq"]]}
+  [:div.help.cp__sidebar-help-docs
+   (let [discord-with-icon [:div.flex-row.inline-flex.items-center
+                            [:span.mr-1 (t :help/community)]
+                            (ui/icon "brand-discord" {:style {:font-size 20}})]
+         list
+         [{:title "Usage"
+           :children [[[:a
+                        {:on-click (fn [] (route-handler/redirect! {:to :shortcut-setting}))}
+                        [:div.flex-row.inline-flex.items-center
+                         [:span.mr-1 (t :help/shortcuts)]
+                         (ui/icon "command" {:style {:font-size 20}})]]]
+                      [(t :help/docs) "https://docs.logseq.com/"]
+                      ["FAQ" "https://docs.logseq.com/#/page/faq"]]}
 
-            {:title "About"
-             :children [[(t :help/start) "https://docs.logseq.com/#/page/getting%20started"]
-                        [(t :help/about) "https://logseq.com/blog/about"]]}
+          {:title "About"
+           :children [[(t :help/start) "https://docs.logseq.com/#/page/getting%20started"]
+                      [(t :help/about) "https://logseq.com/blog/about"]]}
 
-            {:title "Development"
-             :children [[(t :help/roadmap) "https://trello.com/b/8txSM12G/roadmap"]
-                        [(t :help/bug) "https://github.com/logseq/logseq/issues/new?assignees=&labels=&template=bug_report.md&title="]
-                        [(t :help/feature) "https://github.com/logseq/logseq/issues/new?assignees=&labels=&template=feature_request.md&title="]
-                        [(t :help/changelog) "https://docs.logseq.com/#/page/changelog"]]}
+          {:title "Development"
+           :children [[(t :help/roadmap) "https://trello.com/b/8txSM12G/roadmap"]
+                      [(t :help/bug) "https://github.com/logseq/logseq/issues/new?assignees=&labels=&template=bug_report.md&title="]
+                      [(t :help/feature) "https://github.com/logseq/logseq/issues/new?assignees=&labels=&template=feature_request.md&title="]
+                      [(t :help/changelog) "https://docs.logseq.com/#/page/changelog"]]}
 
-            {:title "Terms"
-             :children [[(t :help/privacy) "https://logseq.com/blog/privacy-policy"]
-                        [(t :help/terms) "https://logseq.com/blog/terms"]]}
+          {:title "Terms"
+           :children [[(t :help/privacy) "https://logseq.com/blog/privacy-policy"]
+                      [(t :help/terms) "https://logseq.com/blog/terms"]]}
 
-            {:title "Community"
-             :children [[(t :help/awesome-logseq) "https://github.com/logseq/awesome-logseq"]
-                        [discord-with-icon "https://discord.gg/KpN4eHY"]]}]]
+          {:title "Community"
+           :children [[(t :help/awesome-logseq) "https://github.com/logseq/awesome-logseq"]
+                      [discord-with-icon "https://discord.gg/KpN4eHY"]]}]]
 
-       (map (fn [sublist]
-              [[:p.mt-4.mb-1 [:b (:title sublist)]]
-              [:ul
-               (map (fn [[title href]]
-                      [:li
-                       (if href
-                         [:a {:href href :target "_blank"} title]
-                         title)])
-                    (:children sublist))]])
-            list))]))
+     (map (fn [sublist]
+            [[:p.mt-4.mb-1 [:b (:title sublist)]]
+             [:ul
+              (map (fn [[title href]]
+                     [:li
+                      (if href
+                        [:a {:href href :target "_blank"} title]
+                        title)])
+                (:children sublist))]])
+       list))])

+ 402 - 409
src/main/frontend/components/page.cljs

@@ -8,7 +8,7 @@
             [frontend.components.reference :as reference]
             [frontend.components.svg :as svg]
             [frontend.config :as config]
-            [frontend.context.i18n :as i18n]
+            [frontend.context.i18n :refer [t]]
             [frontend.date :as date]
             [frontend.db :as db]
             [frontend.db-mixins :as db-mixins]
@@ -256,79 +256,78 @@
                    (db/get-page-format page))
           journal? (db/journal-page? page-name)
           fmt-journal? (boolean (date/journal-title->int page-name))
-          sidebar? (:sidebar? option)]
-      (rum/with-context [[_t] i18n/*tongue-context*]
-        (let [route-page-name path-page-name
-              page (if block?
-                     (->> (:db/id (:block/page (db/entity repo [:block/uuid block-id])))
-                          (db/entity repo))
-                     (do
-                       (when-not (db/entity repo [:block/name page-name])
-                         (let [m (format-block/page-name->map path-page-name true)]
-                           (db/transact! repo [m])))
-                       (db/pull [:block/name page-name])))
-              {:keys [icon]} (:block/properties page)
-              page-name (:block/name page)
-              page-original-name (:block/original-name page)
-              title (or page-original-name page-name)
-              icon (or icon "")
-              today? (and
-                      journal?
-                      (= page-name (util/page-name-sanity-lc (date/journal-name))))]
-          [:div.flex-1.page.relative
-           (merge (if (seq (:block/tags page))
-                    (let [page-names (model/get-page-names-by-ids (map :db/id (:block/tags page)))]
-                      {:data-page-tags (text/build-data-value page-names)})
-                    {})
-
-                  {:key path-page-name
-                   :class (util/classnames [{:is-journals (or journal? fmt-journal?)}])})
-
-           [:div.relative
-            (when (and (not sidebar?)
-                       (not block?))
-              [:div.flex.flex-row.space-between
-               [:div.flex-1.flex-row
-                (page-title page-name icon title format fmt-journal?)]
-               (when (not config/publishing?)
-                 [:div.flex.flex-row
-                  (when plugin-handler/lsp-enabled?
-                    (plugins/hook-ui-slot :page-head-actions-slotted nil)
-                    (plugins/hook-ui-items :pagebar))])])
-            [:div
-             (when (and block? (not sidebar?))
-               (let [config {:id "block-parent"
-                             :block? true}]
-                 [:div.mb-4
-                  (block/block-parents config repo block-id {:level-limit 3})]))
-
-             ;; blocks
-             (let [page (if block?
-                          (db/entity repo [:block/uuid block-id])
-                          page)]
-               (page-blocks-cp repo page {:sidebar? sidebar?}))]]
-
-           (when-not block?
-             (today-queries repo today? sidebar?))
-
-           (when-not block?
-             (tagged-pages repo page-name))
-
-           ;; referenced blocks
-           [:div {:key "page-references"}
-            (rum/with-key
-              (reference/references route-page-name false)
-              (str route-page-name "-refs"))]
-
-           (when-not block?
-             [:div
-              (when (not journal?)
-                (hierarchy/structures route-page-name))
-
-              ;; TODO: or we can lazy load them
-              (when-not sidebar?
-                [:div {:key "page-unlinked-references"}
-                 (reference/unlinked-references route-page-name)])])])))))
+          sidebar? (:sidebar? option)
+          route-page-name path-page-name
+          page (if block?
+                 (->> (:db/id (:block/page (db/entity repo [:block/uuid block-id])))
+                      (db/entity repo))
+                 (do
+                   (when-not (db/entity repo [:block/name page-name])
+                     (let [m (format-block/page-name->map path-page-name true)]
+                       (db/transact! repo [m])))
+                   (db/pull [:block/name page-name])))
+          {:keys [icon]} (:block/properties page)
+          page-name (:block/name page)
+          page-original-name (:block/original-name page)
+          title (or page-original-name page-name)
+          icon (or icon "")
+          today? (and
+                  journal?
+                  (= page-name (util/page-name-sanity-lc (date/journal-name))))]
+      [:div.flex-1.page.relative
+       (merge (if (seq (:block/tags page))
+                (let [page-names (model/get-page-names-by-ids (map :db/id (:block/tags page)))]
+                  {:data-page-tags (text/build-data-value page-names)})
+                {})
+
+              {:key path-page-name
+               :class (util/classnames [{:is-journals (or journal? fmt-journal?)}])})
+
+       [:div.relative
+        (when (and (not sidebar?)
+                   (not block?))
+          [:div.flex.flex-row.space-between
+           [:div.flex-1.flex-row
+            (page-title page-name icon title format fmt-journal?)]
+           (when (not config/publishing?)
+             [:div.flex.flex-row
+              (when plugin-handler/lsp-enabled?
+                (plugins/hook-ui-slot :page-head-actions-slotted nil)
+                (plugins/hook-ui-items :pagebar))])])
+        [:div
+         (when (and block? (not sidebar?))
+           (let [config {:id "block-parent"
+                         :block? true}]
+             [:div.mb-4
+              (block/block-parents config repo block-id {:level-limit 3})]))
+
+         ;; blocks
+         (let [page (if block?
+                      (db/entity repo [:block/uuid block-id])
+                      page)]
+           (page-blocks-cp repo page {:sidebar? sidebar?}))]]
+
+       (when-not block?
+         (today-queries repo today? sidebar?))
+
+       (when-not block?
+         (tagged-pages repo page-name))
+
+       ;; referenced blocks
+       [:div {:key "page-references"}
+        (rum/with-key
+          (reference/references route-page-name false)
+          (str route-page-name "-refs"))]
+
+       (when-not block?
+         [:div
+          (when (not journal?)
+            (hierarchy/structures route-page-name))
+
+          ;; TODO: or we can lazy load them
+          (when-not sidebar?
+            [:div {:key "page-unlinked-references"}
+             (reference/unlinked-references route-page-name)])])])))
 
 (defonce layout (atom [js/window.innerWidth js/window.innerHeight]))
 
@@ -374,103 +373,102 @@
                          (config-handler/set-config! :graph/settings new-settings)))
         search-graph-filters (state/sub :search/graph-filters)
         focus-nodes (rum/react *focus-nodes)]
-    (rum/with-context [[t] i18n/*tongue-context*]
-      [:div.absolute.top-4.right-4.graph-filters
-       [:div.flex.flex-col
-        [:div.shadow-xl.rounded-sm
-         [:ul
-          (graph-filter-section
-           [:span.font-medium "Nodes"]
-           (fn [open?]
-             (filter-expand-area
-              open?
-              [:div
-               [:p.text-sm.opacity-70.px-4
-                (let [c1 (count (:nodes graph))
-                      s1 (if (> c1 1) "s" "")
-                      ;; c2 (count (:links graph))
-                      ;; s2 (if (> c2 1) "s" "")
-                      ]
-                  ;; (util/format "%d page%s, %d link%s" c1 s1 c2 s2)
-                  (util/format "%d page%s" c1 s1))]
-               [:div.p-6
-                ;; [:div.flex.items-center.justify-between.mb-2
-                ;;  [:span "Layout"]
-                ;;  (ui/select
-                ;;    (mapv
-                ;;     (fn [item]
-                ;;       (if (= (:label item) layout)
-                ;;         (assoc item :selected "selected")
-                ;;         item))
-                ;;     [{:label "gForce"}
-                ;;      {:label "dagre"}])
-                ;;    (fn [value]
-                ;;      (set-setting! :layout value))
-                ;;    "graph-layout")]
-                [:div.flex.items-center.justify-between.mb-2
-                 [:span (t :right-side-bar/journals)]
-                 ;; FIXME: why it's not aligned well?
-                 [:div.mt-1
-                  (ui/toggle journal?
-                             (fn []
-                               (let [value (not journal?)]
-                                 (reset! *journal? value)
-                                 (set-setting! :journal? value)))
-                             true)]]
-                [:div.flex.items-center.justify-between.mb-2
-                 [:span "Orphan pages"]
-                 [:div.mt-1
-                  (ui/toggle orphan-pages?
-                             (fn []
-                               (let [value (not orphan-pages?)]
-                                 (reset! *orphan-pages? value)
-                                 (set-setting! :orphan-pages? value)))
-                             true)]]
-                [:div.flex.items-center.justify-between.mb-2
-                 [:span "Built-in pages"]
-                 [:div.mt-1
-                  (ui/toggle builtin-pages?
-                             (fn []
-                               (let [value (not builtin-pages?)]
-                                 (reset! *builtin-pages? value)
-                                 (set-setting! :builtin-pages? value)))
-                             true)]]
-                (when (seq focus-nodes)
-                  [:div.flex.flex-col.mb-2
-                   [:p {:title "N hops from selected nodes"}
-                    "N hops from selected nodes"]
-                   (ui/tippy {:html [:div.pr-3 n-hops]}
-                             (ui/slider (or n-hops 10)
-                                        {:min 1
-                                         :max 10
-                                         :on-change #(reset! *n-hops (int %))}))])
-
-                [:a.opacity-70.opacity-100 {:on-click (fn []
-                                                        (swap! *graph-reset? not)
-                                                        (reset! *focus-nodes [])
-                                                        (reset! *n-hops nil)
-                                                        (state/clear-search-filters!))}
-                 "Reset Graph"]]]))
-           {})
-          (graph-filter-section
-           [:span.font-medium "Search"]
-           (fn [open?]
-             (filter-expand-area
-              open?
-              [:div.p-6
-               (if (seq search-graph-filters)
-                 [:div
-                  (for [q search-graph-filters]
-                    [:div.flex.flex-row.justify-between.items-center.mb-2
-                     [:span.font-medium q]
-                     [:a.search-filter-close.opacity-70.opacity-100 {:on-click #(state/remove-search-filter! q)}
-                      svg/close]])
-
-                  [:a.opacity-70.opacity-100 {:on-click state/clear-search-filters!}
-                   "Clear All"]]
-                 [:a.opacity-70.opacity-100 {:on-click #(route-handler/go-to-search! :graph)}
-                  "Click to search"])]))
-           {:search-filters search-graph-filters})]]]])))
+    [:div.absolute.top-4.right-4.graph-filters
+     [:div.flex.flex-col
+      [:div.shadow-xl.rounded-sm
+       [:ul
+        (graph-filter-section
+         [:span.font-medium "Nodes"]
+         (fn [open?]
+           (filter-expand-area
+            open?
+            [:div
+             [:p.text-sm.opacity-70.px-4
+              (let [c1 (count (:nodes graph))
+                    s1 (if (> c1 1) "s" "")
+                    ;; c2 (count (:links graph))
+                    ;; s2 (if (> c2 1) "s" "")
+                    ]
+                ;; (util/format "%d page%s, %d link%s" c1 s1 c2 s2)
+                (util/format "%d page%s" c1 s1))]
+             [:div.p-6
+              ;; [:div.flex.items-center.justify-between.mb-2
+              ;;  [:span "Layout"]
+              ;;  (ui/select
+              ;;    (mapv
+              ;;     (fn [item]
+              ;;       (if (= (:label item) layout)
+              ;;         (assoc item :selected "selected")
+              ;;         item))
+              ;;     [{:label "gForce"}
+              ;;      {:label "dagre"}])
+              ;;    (fn [value]
+              ;;      (set-setting! :layout value))
+              ;;    "graph-layout")]
+              [:div.flex.items-center.justify-between.mb-2
+               [:span (t :right-side-bar/journals)]
+               ;; FIXME: why it's not aligned well?
+               [:div.mt-1
+                (ui/toggle journal?
+                           (fn []
+                             (let [value (not journal?)]
+                               (reset! *journal? value)
+                               (set-setting! :journal? value)))
+                           true)]]
+              [:div.flex.items-center.justify-between.mb-2
+               [:span "Orphan pages"]
+               [:div.mt-1
+                (ui/toggle orphan-pages?
+                           (fn []
+                             (let [value (not orphan-pages?)]
+                               (reset! *orphan-pages? value)
+                               (set-setting! :orphan-pages? value)))
+                           true)]]
+              [:div.flex.items-center.justify-between.mb-2
+               [:span "Built-in pages"]
+               [:div.mt-1
+                (ui/toggle builtin-pages?
+                           (fn []
+                             (let [value (not builtin-pages?)]
+                               (reset! *builtin-pages? value)
+                               (set-setting! :builtin-pages? value)))
+                           true)]]
+              (when (seq focus-nodes)
+                [:div.flex.flex-col.mb-2
+                 [:p {:title "N hops from selected nodes"}
+                  "N hops from selected nodes"]
+                 (ui/tippy {:html [:div.pr-3 n-hops]}
+                           (ui/slider (or n-hops 10)
+                                      {:min 1
+                                       :max 10
+                                       :on-change #(reset! *n-hops (int %))}))])
+
+              [:a.opacity-70.opacity-100 {:on-click (fn []
+                                                      (swap! *graph-reset? not)
+                                                      (reset! *focus-nodes [])
+                                                      (reset! *n-hops nil)
+                                                      (state/clear-search-filters!))}
+               "Reset Graph"]]]))
+         {})
+        (graph-filter-section
+         [:span.font-medium "Search"]
+         (fn [open?]
+           (filter-expand-area
+            open?
+            [:div.p-6
+             (if (seq search-graph-filters)
+               [:div
+                (for [q search-graph-filters]
+                  [:div.flex.flex-row.justify-between.items-center.mb-2
+                   [:span.font-medium q]
+                   [:a.search-filter-close.opacity-70.opacity-100 {:on-click #(state/remove-search-filter! q)}
+                    svg/close]])
+
+                [:a.opacity-70.opacity-100 {:on-click state/clear-search-filters!}
+                 "Clear All"]]
+               [:a.opacity-70.opacity-100 {:on-click #(route-handler/go-to-search! :graph)}
+                "Click to search"])]))
+         {:search-filters search-graph-filters})]]]]))
 
 (defonce last-node-position (atom nil))
 (defn- graph-register-handlers
@@ -503,18 +501,17 @@
                                                  (and (not (nodes (:source link)))
                                                       (not (nodes (:target link)))))
                                                links))))]
-    (rum/with-context [[_t] i18n/*tongue-context*]
-      [:div.relative#global-graph
-       (graph/graph-2d {:nodes (:nodes graph)
-                        :links (:links graph)
-                        :width (- width 24)
-                        :height (- height 48)
-                        :dark? dark?
-                        :register-handlers-fn
-                        (fn [graph]
-                          (graph-register-handlers graph *focus-nodes *n-hops dark?))
-                        :reset? reset?})
-       (graph-filters graph settings n-hops)])))
+    [:div.relative#global-graph
+     (graph/graph-2d {:nodes (:nodes graph)
+                      :links (:links graph)
+                      :width (- width 24)
+                      :height (- height 48)
+                      :dark? dark?
+                      :register-handlers-fn
+                      (fn [graph]
+                        (graph-register-handlers graph *focus-nodes *n-hops dark?))
+                      :reset? reset?})
+     (graph-filters graph settings n-hops)]))
 
 (defn- filter-graph-nodes
   [nodes filters]
@@ -612,55 +609,52 @@
 (defn batch-delete-dialog
   [pages orphaned-pages? refresh-fn]
   (fn [close-fn]
-    (rum/with-context
-      [[t] i18n/*tongue-context*]
-
-      [:div
-       [:div.sm:flex.items-center
-        [:div.mx-auto.flex-shrink-0.flex.items-center.justify-center.h-12.w-12.rounded-full.bg-red-100.sm:mx-0.sm:h-10.sm:w-10
-         [:span.text-red-600.text-xl
-          (ui/icon "alert-triangle")]]
-        [:div.mt-3.text-center.sm:mt-0.sm:ml-4.sm:text-left
-         [:h3#modal-headline.text-lg.leading-6.font-medium
-          (if orphaned-pages?
-            (str (t :remove-orphaned-pages) "?")
-            (t :page/delete-confirmation))]]]
-
-       [:table.table-auto.cp__all_pages_table.mt-4
-        [:thead
-         [:tr.opacity-70
-          [:th [:span "#"]]
-          [:th [:span (t :block/name)]]
-          [:th [:span (t :page/backlinks)]]
-          (when-not orphaned-pages? [:th [:span (t :page/created-at)]])
-          (when-not orphaned-pages? [:th [:span (t :page/updated-at)]])]]
-
-        [:tbody
-         (for [[n {:block/keys [name created-at updated-at backlinks] :as page}] (medley/indexed pages)]
-           [:tr {:key name}
-            [:td.n.w-10 [:span.opacity-70 (str (inc n) ". ")]]
-            [:td.name [:a {:href     (rfe/href :page {:name (:block/name page)})}
-                       (block/page-cp {} page)]]
-            [:td.backlinks [:span (or backlinks "0")]]
-            (when-not orphaned-pages? [:td.created-at [:span (if created-at (date/int->local-time-2 created-at) "Unknown")]])
-            (when-not orphaned-pages? [:td.updated-at [:span (if updated-at (date/int->local-time-2 updated-at) "Unknown")]])])]]
-
-       [:div.pt-6.flex.justify-end
-
-        [:span.pr-2
-         (ui/button
-           (t :cancel)
-           :intent "logseq"
-           :on-click close-fn)]
-
-        (ui/button
-          (t :yes)
-          :on-click (fn []
-                      (close-fn)
-                      (doseq [page-name (map :block/name pages)]
-                        (page-handler/delete! page-name #()))
-                      (notification/show! (str (t :tips/all-done) "!") :success)
-                      (js/setTimeout #(refresh-fn) 200)))]])))
+    [:div
+     [:div.sm:flex.items-center
+      [:div.mx-auto.flex-shrink-0.flex.items-center.justify-center.h-12.w-12.rounded-full.bg-red-100.sm:mx-0.sm:h-10.sm:w-10
+       [:span.text-red-600.text-xl
+        (ui/icon "alert-triangle")]]
+      [:div.mt-3.text-center.sm:mt-0.sm:ml-4.sm:text-left
+       [:h3#modal-headline.text-lg.leading-6.font-medium
+        (if orphaned-pages?
+          (str (t :remove-orphaned-pages) "?")
+          (t :page/delete-confirmation))]]]
+
+     [:table.table-auto.cp__all_pages_table.mt-4
+      [:thead
+       [:tr.opacity-70
+        [:th [:span "#"]]
+        [:th [:span (t :block/name)]]
+        [:th [:span (t :page/backlinks)]]
+        (when-not orphaned-pages? [:th [:span (t :page/created-at)]])
+        (when-not orphaned-pages? [:th [:span (t :page/updated-at)]])]]
+
+      [:tbody
+       (for [[n {:block/keys [name created-at updated-at backlinks] :as page}] (medley/indexed pages)]
+         [:tr {:key name}
+          [:td.n.w-10 [:span.opacity-70 (str (inc n) ". ")]]
+          [:td.name [:a {:href     (rfe/href :page {:name (:block/name page)})}
+                     (block/page-cp {} page)]]
+          [:td.backlinks [:span (or backlinks "0")]]
+          (when-not orphaned-pages? [:td.created-at [:span (if created-at (date/int->local-time-2 created-at) "Unknown")]])
+          (when-not orphaned-pages? [:td.updated-at [:span (if updated-at (date/int->local-time-2 updated-at) "Unknown")]])])]]
+
+     [:div.pt-6.flex.justify-end
+
+      [:span.pr-2
+       (ui/button
+         (t :cancel)
+         :intent "logseq"
+         :on-click close-fn)]
+
+      (ui/button
+        (t :yes)
+        :on-click (fn []
+                    (close-fn)
+                    (doseq [page-name (map :block/name pages)]
+                      (page-handler/delete! page-name #()))
+                    (notification/show! (str (t :tips/all-done) "!") :success)
+                    (js/setTimeout #(refresh-fn) 200)))]]))
 
 (rum/defcs all-pages < rum/reactive
   (rum/local nil ::pages)
@@ -721,187 +715,186 @@
                          (reset! *pages nil)
                          (reset! *current-page 1))]
 
-    (rum/with-context [[t] i18n/*tongue-context*]
-      [:div.flex-1.cp__all_pages
-       [:h1.title (t :all-pages)]
-
-       (when current-repo
-
-         ;; all pages
-         (when (nil? @*pages)
-           (let [pages (->> (page-handler/get-all-pages current-repo)
-                            (map-indexed (fn [idx page] (assoc page
-                                                               :block/backlinks (count (:block/_refs (db/entity (:db/id page))))
-                                                               :block/idx idx))))]
-             (reset! *filter-fn
-                     (memoize (fn [sort-by-item desc? journal?]
-                                (->> pages
-                                     (filter #(or (boolean journal?)
-                                                  (= false (boolean (:block/journal? %)))))
-                                     (sort-pages-by sort-by-item desc?)))))
-             (reset! *pages pages)))
-
-         ;; filter results
-         (when @*filter-fn
-           (let [pages (@*filter-fn @*sort-by-item @*desc? @*journal?)
-
-                 ;; search key
-                 pages (if-not (string/blank? @*search-key)
-                         (search/fuzzy-search pages (util/page-name-sanity-lc @*search-key)
-                                              :limit 20
-                                              :extract-fn :block/name)
-                         pages)
-
-                 _ (reset! *results-all pages)
-
-                 pages (take per-page-num (drop (* per-page-num (dec @*current-page)) pages))]
-
-             (reset! *checks (into {} (for [{:block/keys [idx]} pages]
-                                        [idx (boolean (get @*checks idx))])))
-             (reset! *results pages)))
-
-         [[:div.actions
-           {:class (util/classnames [{:has-selected (or (nil? @*indeterminate)
-                                                        (not= 0 @*indeterminate))}])}
-           [:div.l.flex.items-center
-            [:div.actions-wrap
-             (ui/button
-               [(ui/icon "trash") (t :delete)]
-               :on-click (fn []
-                           (let [selected (filter (fn [[_ v]] v) @*checks)
-                                 selected (and (seq selected)
-                                               (into #{} (for [[k _] selected] k)))]
-                             (when-let [pages (and selected (filter #(contains? selected (:block/idx %)) @*results))]
-                               (state/set-modal! (batch-delete-dialog pages false #(do
-                                                                                     (reset! *checks nil)
-                                                                                     (refresh-pages)))))))
-               :small? true)]
-
-            [:div.search-wrap.flex.items-center.pl-2
-             (let [search-fn (fn []
-                               (let [^js input (rum/deref *search-input)]
-                                 (search-key (.-value input))
-                                 (reset! *current-page 1)))
-                   reset-fn (fn []
-                              (let [^js input (rum/deref *search-input)]
-                                (set! (.-value input) "")
-                                (reset! *search-key nil)))]
-
-               [(ui/button (ui/icon "search")
-                  :on-click search-fn
-                  :small? true)
-                [:input.form-input {:placeholder   (t :search/page-names)
-                                    :on-key-up     (fn [^js e]
-                                                     (let [^js target (.-target e)]
-                                                       (if (string/blank? (.-value target))
-                                                         (reset! *search-key nil)
-                                                         (cond
-                                                           (= 13 (.-keyCode e)) (search-fn)
-                                                           (= 27 (.-keyCode e)) (reset-fn)))))
-                                    :ref           *search-input
-                                    :default-value ""}]
-
-                (when (not (string/blank? @*search-key))
-                  [:a.cancel {:on-click reset-fn}
-                   (ui/icon "x")])])]]
-
-           [:div.r.flex.items-center.justify-between
-            (let [orphaned-pages (model/get-orphaned-pages {})
-                  orphaned-pages? (seq orphaned-pages)]
-              [:a.ml-1.pr-2.opacity-70.hover:opacity-100
-               {:on-click (fn []
-                            (if orphaned-pages?
-                              (state/set-modal!
-                               (batch-delete-dialog
-                                orphaned-pages  true
-                                #(do
-                                   (reset! *checks nil)
-                                   (refresh-pages))))
-                              (notification/show! "Congratulations, no orphaned pages in your graph!" :success)))}
-               [:span
-                (ui/icon "file-x")
-                [:span.ml-1 (t :remove-orphaned-pages)]]])
-
-            [:a.ml-1.pr-2.opacity-70.hover:opacity-100 {:href (rfe/href :all-files)}
+    [:div.flex-1.cp__all_pages
+     [:h1.title (t :all-pages)]
+
+     (when current-repo
+
+       ;; all pages
+       (when (nil? @*pages)
+         (let [pages (->> (page-handler/get-all-pages current-repo)
+                          (map-indexed (fn [idx page] (assoc page
+                                                             :block/backlinks (count (:block/_refs (db/entity (:db/id page))))
+                                                             :block/idx idx))))]
+           (reset! *filter-fn
+                   (memoize (fn [sort-by-item desc? journal?]
+                              (->> pages
+                                   (filter #(or (boolean journal?)
+                                                (= false (boolean (:block/journal? %)))))
+                                   (sort-pages-by sort-by-item desc?)))))
+           (reset! *pages pages)))
+
+       ;; filter results
+       (when @*filter-fn
+         (let [pages (@*filter-fn @*sort-by-item @*desc? @*journal?)
+
+               ;; search key
+               pages (if-not (string/blank? @*search-key)
+                       (search/fuzzy-search pages (util/page-name-sanity-lc @*search-key)
+                                            :limit 20
+                                            :extract-fn :block/name)
+                       pages)
+
+               _ (reset! *results-all pages)
+
+               pages (take per-page-num (drop (* per-page-num (dec @*current-page)) pages))]
+
+           (reset! *checks (into {} (for [{:block/keys [idx]} pages]
+                                      [idx (boolean (get @*checks idx))])))
+           (reset! *results pages)))
+
+       [[:div.actions
+         {:class (util/classnames [{:has-selected (or (nil? @*indeterminate)
+                                                      (not= 0 @*indeterminate))}])}
+         [:div.l.flex.items-center
+          [:div.actions-wrap
+           (ui/button
+             [(ui/icon "trash") (t :delete)]
+             :on-click (fn []
+                         (let [selected (filter (fn [[_ v]] v) @*checks)
+                               selected (and (seq selected)
+                                             (into #{} (for [[k _] selected] k)))]
+                           (when-let [pages (and selected (filter #(contains? selected (:block/idx %)) @*results))]
+                             (state/set-modal! (batch-delete-dialog pages false #(do
+                                                                                   (reset! *checks nil)
+                                                                                   (refresh-pages)))))))
+             :small? true)]
+
+          [:div.search-wrap.flex.items-center.pl-2
+           (let [search-fn (fn []
+                             (let [^js input (rum/deref *search-input)]
+                               (search-key (.-value input))
+                               (reset! *current-page 1)))
+                 reset-fn (fn []
+                            (let [^js input (rum/deref *search-input)]
+                              (set! (.-value input) "")
+                              (reset! *search-key nil)))]
+
+             [(ui/button (ui/icon "search")
+                :on-click search-fn
+                :small? true)
+              [:input.form-input {:placeholder   (t :search/page-names)
+                                  :on-key-up     (fn [^js e]
+                                                   (let [^js target (.-target e)]
+                                                     (if (string/blank? (.-value target))
+                                                       (reset! *search-key nil)
+                                                       (cond
+                                                         (= 13 (.-keyCode e)) (search-fn)
+                                                         (= 27 (.-keyCode e)) (reset-fn)))))
+                                  :ref           *search-input
+                                  :default-value ""}]
+
+              (when (not (string/blank? @*search-key))
+                [:a.cancel {:on-click reset-fn}
+                 (ui/icon "x")])])]]
+
+         [:div.r.flex.items-center.justify-between
+          (let [orphaned-pages (model/get-orphaned-pages {})
+                orphaned-pages? (seq orphaned-pages)]
+            [:a.ml-1.pr-2.opacity-70.hover:opacity-100
+             {:on-click (fn []
+                          (if orphaned-pages?
+                            (state/set-modal!
+                             (batch-delete-dialog
+                              orphaned-pages  true
+                              #(do
+                                 (reset! *checks nil)
+                                 (refresh-pages))))
+                            (notification/show! "Congratulations, no orphaned pages in your graph!" :success)))}
              [:span
-              (ui/icon "files")
-              [:span.ml-1 (t :all-files)]]]
+              (ui/icon "file-x")
+              [:span.ml-1 (t :remove-orphaned-pages)]]])
 
-            [:div
-             (ui/tippy
-              {:html  [:small (str (t :page/show-journals) " ?")]
-               :arrow true}
-              [:a.button.journal
-               {:class    (util/classnames [{:active (boolean @*journal?)}])
-                :on-click #(reset! *journal? (not @*journal?))}
-               (ui/icon "calendar")])]
-
-            [:div.paginates
-             [:span.flex.items-center.opacity-60.text-sm
-              [:span.pr-1 (t :paginates/pages (count @*results-all))]]
-             [:span.flex.items-center
-              {:class (util/classnames [{:is-first (= 1 @*current-page)
-                                         :is-last  (= @*current-page total-pages)}])}
-              [:a.py-4.pr-2 {:on-click #(to-page (dec @*current-page))} (ui/icon "caret-left") (str " " (t :paginates/prev))]
-              [:span.opacity-30 (str @*current-page "/" total-pages)]
-              [:a.py-4.pl-2 {:on-click #(to-page (inc @*current-page))} (str (t :paginates/next) " ") (ui/icon "caret-right")]]]]]
-
-          [:table.table-auto.cp__all_pages_table
-           [:thead
-            [:tr
-             [:th.selector
-              (checkbox-opt "all-pages-select-all"
-                            (= 1 @*indeterminate)
-                            {:on-change     (fn []
-                                              (let [indeterminate? (= -1 @*indeterminate)
-                                                    all? (= 1 @*indeterminate)]
-                                                (doseq [{:block/keys [idx]} @*results]
-                                                  (swap! *checks assoc idx (or indeterminate? (not all?))))))
-                             :indeterminate (= -1 @*indeterminate)})]
-
-             (sortable-title (t :block/name) :block/name *sort-by-item *desc?)
-             (when-not mobile?
-               [(sortable-title (t :page/backlinks) :block/backlinks *sort-by-item *desc?)
-                (sortable-title (t :page/created-at) :block/created-at *sort-by-item *desc?)
-                (sortable-title (t :page/updated-at) :block/updated-at *sort-by-item *desc?)])]]
-
-           [:tbody
-            (for [{:block/keys [idx name created-at updated-at backlinks] :as page} @*results]
-              (when-not (string/blank? name)
-                [:tr {:key name}
-                 [:td.selector
-                  (checkbox-opt (str "label-" idx)
-                                (get @*checks idx)
-                                {:on-change (fn []
-                                              (swap! *checks update idx not))})]
-
-                 [:td.name [:a {:on-click (fn [e]
-                                            (let [repo (state/get-current-repo)]
-                                              (when (gobj/get e "shiftKey")
-                                                (state/sidebar-add-block!
-                                                 repo
-                                                 (:db/id page)
-                                                 :page
-                                                 {:page (:block/name page)}))))
-                                :href     (rfe/href :page {:name (:block/name page)})}
-                            (block/page-cp {} page)]]
-
-                 (when-not mobile?
-                   [:td.backlinks [:span backlinks]])
-
-                 (when-not mobile?
-                   [:td.created-at [:span (if created-at
-                                            (date/int->local-time-2 created-at)
-                                            "Unknown")]])
-                 (when-not mobile?
-                   [:td.updated-at [:span (if updated-at
-                                            (date/int->local-time-2 updated-at)
-                                            "Unknown")]])]))]]
+          [:a.ml-1.pr-2.opacity-70.hover:opacity-100 {:href (rfe/href :all-files)}
+           [:span
+            (ui/icon "files")
+            [:span.ml-1 (t :all-files)]]]
+
+          [:div
+           (ui/tippy
+            {:html  [:small (str (t :page/show-journals) " ?")]
+             :arrow true}
+            [:a.button.journal
+             {:class    (util/classnames [{:active (boolean @*journal?)}])
+              :on-click #(reset! *journal? (not @*journal?))}
+             (ui/icon "calendar")])]
 
           [:div.paginates
-           [:span]
+           [:span.flex.items-center.opacity-60.text-sm
+            [:span.pr-1 (t :paginates/pages (count @*results-all))]]
            [:span.flex.items-center
             {:class (util/classnames [{:is-first (= 1 @*current-page)
                                        :is-last  (= @*current-page total-pages)}])}
-            [:a.py-4.text-sm {:on-click #(to-page (dec @*current-page))} (ui/icon "caret-left") (str " " (t :paginates/prev))]
-            [:a.py-4.pl-2.text-sm {:on-click #(to-page (inc @*current-page))} (str (t :paginates/next) " ") (ui/icon "caret-right")]]]])])))
+            [:a.py-4.pr-2 {:on-click #(to-page (dec @*current-page))} (ui/icon "caret-left") (str " " (t :paginates/prev))]
+            [:span.opacity-30 (str @*current-page "/" total-pages)]
+            [:a.py-4.pl-2 {:on-click #(to-page (inc @*current-page))} (str (t :paginates/next) " ") (ui/icon "caret-right")]]]]]
+
+        [:table.table-auto.cp__all_pages_table
+         [:thead
+          [:tr
+           [:th.selector
+            (checkbox-opt "all-pages-select-all"
+                          (= 1 @*indeterminate)
+                          {:on-change     (fn []
+                                            (let [indeterminate? (= -1 @*indeterminate)
+                                                  all? (= 1 @*indeterminate)]
+                                              (doseq [{:block/keys [idx]} @*results]
+                                                (swap! *checks assoc idx (or indeterminate? (not all?))))))
+                           :indeterminate (= -1 @*indeterminate)})]
+
+           (sortable-title (t :block/name) :block/name *sort-by-item *desc?)
+           (when-not mobile?
+             [(sortable-title (t :page/backlinks) :block/backlinks *sort-by-item *desc?)
+              (sortable-title (t :page/created-at) :block/created-at *sort-by-item *desc?)
+              (sortable-title (t :page/updated-at) :block/updated-at *sort-by-item *desc?)])]]
+
+         [:tbody
+          (for [{:block/keys [idx name created-at updated-at backlinks] :as page} @*results]
+            (when-not (string/blank? name)
+              [:tr {:key name}
+               [:td.selector
+                (checkbox-opt (str "label-" idx)
+                              (get @*checks idx)
+                              {:on-change (fn []
+                                            (swap! *checks update idx not))})]
+
+               [:td.name [:a {:on-click (fn [e]
+                                          (let [repo (state/get-current-repo)]
+                                            (when (gobj/get e "shiftKey")
+                                              (state/sidebar-add-block!
+                                               repo
+                                               (:db/id page)
+                                               :page
+                                               {:page (:block/name page)}))))
+                              :href     (rfe/href :page {:name (:block/name page)})}
+                          (block/page-cp {} page)]]
+
+               (when-not mobile?
+                 [:td.backlinks [:span backlinks]])
+
+               (when-not mobile?
+                 [:td.created-at [:span (if created-at
+                                          (date/int->local-time-2 created-at)
+                                          "Unknown")]])
+               (when-not mobile?
+                 [:td.updated-at [:span (if updated-at
+                                          (date/int->local-time-2 updated-at)
+                                          "Unknown")]])]))]]
+
+        [:div.paginates
+         [:span]
+         [:span.flex.items-center
+          {:class (util/classnames [{:is-first (= 1 @*current-page)
+                                     :is-last  (= @*current-page total-pages)}])}
+          [:a.py-4.text-sm {:on-click #(to-page (dec @*current-page))} (ui/icon "caret-left") (str " " (t :paginates/prev))]
+          [:a.py-4.pl-2.text-sm {:on-click #(to-page (inc @*current-page))} (str (t :paginates/next) " ") (ui/icon "caret-right")]]]])]))

+ 23 - 26
src/main/frontend/components/page_menu.cljs

@@ -2,7 +2,7 @@
   (:require [cljs.pprint :as pprint]
             [frontend.commands :as commands]
             [frontend.components.export :as export]
-            [frontend.context.i18n :as i18n]
+            [frontend.context.i18n :refer [t]]
             [frontend.db :as db]
             [frontend.handler.notification :as notification]
             [frontend.handler.page :as page-handler]
@@ -10,7 +10,6 @@
             [frontend.state :as state]
             [frontend.ui :as ui]
             [frontend.util :as util]
-            [rum.core :as rum]
             [frontend.handler.shell :as shell]
             [frontend.handler.plugin :as plugin-handler]
             [frontend.mobile.util :as mobile-util]))
@@ -27,37 +26,35 @@
 (defn delete-page-dialog
   [page-name]
   (fn [close-fn]
-    (rum/with-context [[t] i18n/*tongue-context*]
-      [:div
-       [:div.sm:flex.items-center
-        [:div.mx-auto.flex-shrink-0.flex.items-center.justify-center.h-12.w-12.rounded-full.bg-red-100.sm:mx-0.sm:h-10.sm:w-10
-         [:span.text-red-600.text-xl
-          (ui/icon "alert-triangle")]]
-        [:div.mt-3.text-center.sm:mt-0.sm:ml-4.sm:text-left
-         [:h3#modal-headline.text-lg.leading-6.font-medium
-          (t :page/delete-confirmation)]]]
+    [:div
+     [:div.sm:flex.items-center
+      [:div.mx-auto.flex-shrink-0.flex.items-center.justify-center.h-12.w-12.rounded-full.bg-red-100.sm:mx-0.sm:h-10.sm:w-10
+       [:span.text-red-600.text-xl
+        (ui/icon "alert-triangle")]]
+      [:div.mt-3.text-center.sm:mt-0.sm:ml-4.sm:text-left
+       [:h3#modal-headline.text-lg.leading-6.font-medium
+        (t :page/delete-confirmation)]]]
 
-       [: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"
-           :class "ui__modal-enter"
-           :on-click (fn []
-                       (delete-page! page-name))}
-          (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 close-fn}
-          (t :cancel)]]]])))
+     [: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"
+         :class "ui__modal-enter"
+         :on-click (fn []
+                     (delete-page! page-name))}
+        (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 close-fn}
+        (t :cancel)]]]]))
 
 (defn page-menu
   [page-name]
   (when-let [page-name (or
                         page-name
                         (state/get-current-page))]
-    (let [t i18n/t
-          page-name (util/page-name-sanity-lc page-name)
+    (let [page-name (util/page-name-sanity-lc page-name)
           repo (state/sub :git/current-repo)
           page (db/entity repo [:block/name page-name])
           page-original-name (:block/original-name page)

+ 222 - 237
src/main/frontend/components/plugins.cljs

@@ -2,7 +2,7 @@
   (:require [rum.core :as rum]
             [frontend.state :as state]
             [cljs-bean.core :as bean]
-            [frontend.context.i18n :as i18n]
+            [frontend.context.i18n :refer [t]]
             [frontend.ui :as ui]
             [frontend.handler.ui :as ui-handler]
             [frontend.search :as search]
@@ -50,31 +50,28 @@
         themes (sort #(:selected %) (map #(assoc % :selected (= (:url %) selected)) themes))
         _ (reset! *total (count themes))]
 
-    (rum/with-context
-      [[t] i18n/*tongue-context*]
-
-      [:div.cp__themes-installed
-       {:tab-index -1}
-       [:h1.mb-4.text-2xl.p-1 (t :themes)]
-       (map-indexed
-         (fn [idx opt]
-           (let [current-selected (:selected opt)
-                 plg (get (:plugin/installed-plugins @state/state) (keyword (:pid opt)))]
-             [:div.it.flex.px-3.py-1.5.rounded-sm.justify-between
-              {:key      (str idx (:url opt))
-               :title    (when current-selected "Cancel selected theme")
-               :class    (util/classnames
-                           [{:is-selected current-selected
-                             :is-active   (= idx @*cursor)}])
-               :on-click #(do (js/LSPluginCore.selectTheme (if current-selected nil (clj->js opt)))
-                              (state/close-modal!))}
-              [:section
-               [:strong.block
-                [:small.opacity-60 (str (or (:name plg) "Logseq") " • ")]
-                (:name opt)]]
-              [:small.flex-shrink-0.flex.items-center.opacity-10
-               (when current-selected (ui/icon "check"))]]))
-         themes)])))
+    [:div.cp__themes-installed
+     {:tab-index -1}
+     [:h1.mb-4.text-2xl.p-1 (t :themes)]
+     (map-indexed
+      (fn [idx opt]
+        (let [current-selected (:selected opt)
+              plg (get (:plugin/installed-plugins @state/state) (keyword (:pid opt)))]
+          [:div.it.flex.px-3.py-1.5.rounded-sm.justify-between
+           {:key      (str idx (:url opt))
+            :title    (when current-selected "Cancel selected theme")
+            :class    (util/classnames
+                       [{:is-selected current-selected
+                         :is-active   (= idx @*cursor)}])
+            :on-click #(do (js/LSPluginCore.selectTheme (if current-selected nil (clj->js opt)))
+                           (state/close-modal!))}
+           [:section
+            [:strong.block
+             [:small.opacity-60 (str (or (:name plg) "Logseq") " • ")]
+             (:name opt)]]
+           [:small.flex-shrink-0.flex.items-center.opacity-10
+            (when current-selected (ui/icon "check"))]]))
+      themes)]))
 
 (rum/defc unpacked-plugin-loader
   [unpacked-pkg-path]
@@ -164,136 +161,133 @@
         name (or title name "Untitled")
         unpacked? (not iir)
         new-version (state/coming-update-new-version? coming-update)]
-    (rum/with-context
-      [[t] i18n/*tongue-context*]
-
-      [:div.cp__plugins-item-card
-       {:class (util/classnames
-                 [{:market          market?
-                   :installed       installed?
-                   :updating        installing-or-updating?
-                   :has-new-version new-version}])}
-
-       [:div.l.link-block
-        {:on-click #(plugin-handler/open-readme!
-                      url item (if repo remote-readme-display local-markdown-display))}
-        (if (and icon (not (string/blank? icon)))
-          [:img.icon {:src (if market? (plugin-handler/pkg-asset id icon) icon)}]
-          svg/folder)
-
-        (when (and (not market?) unpacked?)
-          [:span.flex.justify-center.text-xs.text-red-500.pt-2 (t :plugin/unpacked)])]
-
-       [:div.r
-        [:h3.head.text-xl.font-bold.pt-1.5
-
-         [:span name]
-         (when (not market?) [:sup.inline-block.px-1.text-xs.opacity-50 version])]
-
-        [:div.desc.text-xs.opacity-70
-         [:p description]
-         ;;[:small (js/JSON.stringify (bean/->js settings))]
-         ]
-
-        ;; Author & Identity
-        [:div.flag
-         [:p.text-xs.pr-2.flex.justify-between
-          [:small {:on-click #(when-let [^js el (js/document.querySelector ".cp__plugins-page .search-ctls input")]
-                                (reset! *search-key (str "@" author))
-                                (.select el))} author]
-          [:small {:on-click #(do
-                                (notification/show! "Copied!" :success)
-                                (util/copy-to-clipboard! id))}
-           (str "ID: " id)]]]
-
-        ;; Github repo
-        [:div.flag.is-top.opacity-50
-         (when repo
-           [:a.flex {:target "_blank"
-                     :href   (plugin-handler/gh-repo-url repo)}
-            (svg/github {:width 16 :height 16})])]
-
-        (if market?
-          ;; market ctls
-          [:div.ctl
-           [:ul.l.flex.items-center
-            ;; stars
-            [:li.flex.text-sm.items-center.pr-3
-             (svg/star 16) [:span.pl-1 (:stargazers_count stat)]]
-
-            ;; downloads
-            (when-let [downloads (and stat (:total_downloads stat))]
-              (when (and downloads (> downloads 0))
-                [:li.flex.text-sm.items-center.pr-3
-                 (svg/cloud-down 16) [:span.pl-1 downloads]]))]
-
-           [:div.r.flex.items-center
+    [:div.cp__plugins-item-card
+     {:class (util/classnames
+              [{:market          market?
+                :installed       installed?
+                :updating        installing-or-updating?
+                :has-new-version new-version}])}
+
+     [:div.l.link-block
+      {:on-click #(plugin-handler/open-readme!
+                   url item (if repo remote-readme-display local-markdown-display))}
+      (if (and icon (not (string/blank? icon)))
+        [:img.icon {:src (if market? (plugin-handler/pkg-asset id icon) icon)}]
+        svg/folder)
+
+      (when (and (not market?) unpacked?)
+        [:span.flex.justify-center.text-xs.text-red-500.pt-2 (t :plugin/unpacked)])]
+
+     [:div.r
+      [:h3.head.text-xl.font-bold.pt-1.5
+
+       [:span name]
+       (when (not market?) [:sup.inline-block.px-1.text-xs.opacity-50 version])]
+
+      [:div.desc.text-xs.opacity-70
+       [:p description]
+       ;;[:small (js/JSON.stringify (bean/->js settings))]
+       ]
+
+      ;; Author & Identity
+      [:div.flag
+       [:p.text-xs.pr-2.flex.justify-between
+        [:small {:on-click #(when-let [^js el (js/document.querySelector ".cp__plugins-page .search-ctls input")]
+                              (reset! *search-key (str "@" author))
+                              (.select el))} author]
+        [:small {:on-click #(do
+                              (notification/show! "Copied!" :success)
+                              (util/copy-to-clipboard! id))}
+         (str "ID: " id)]]]
+
+      ;; Github repo
+      [:div.flag.is-top.opacity-50
+       (when repo
+         [:a.flex {:target "_blank"
+                   :href   (plugin-handler/gh-repo-url repo)}
+          (svg/github {:width 16 :height 16})])]
+
+      (if market?
+        ;; market ctls
+        [:div.ctl
+         [:ul.l.flex.items-center
+          ;; stars
+          [:li.flex.text-sm.items-center.pr-3
+           (svg/star 16) [:span.pl-1 (:stargazers_count stat)]]
+
+          ;; downloads
+          (when-let [downloads (and stat (:total_downloads stat))]
+            (when (and downloads (> downloads 0))
+              [:li.flex.text-sm.items-center.pr-3
+               (svg/cloud-down 16) [:span.pl-1 downloads]]))]
+
+         [:div.r.flex.items-center
+
+          [:a.btn
+           {:class    (util/classnames [{:disabled   (or installed? installing-or-updating?)
+                                         :installing installing-or-updating?}])
+            :on-click #(plugin-handler/install-marketplace-plugin item)}
+           (if installed?
+             (t :plugin/installed)
+             (if installing-or-updating?
+               [:span.flex.items-center [:small svg/loading]
+                (t :plugin/installing)]
+               (t :plugin/install)))]]]
+
+        ;; installed ctls
+        [:div.ctl
+         [:div.l
+          [:div.de
+           [:strong (ui/icon "settings")]
+           [:ul.menu-list
+            [:li {:on-click #(when usf (js/apis.openPath usf))} (t :plugin/open-settings)]
+            [:li {:on-click #(js/apis.openPath url)} (t :plugin/open-package)]
+            [:li {:on-click
+                  #(let [confirm-fn
+                         (ui/make-confirm-modal
+                          {:title      (t :plugin/delete-alert name)
+                           :on-confirm (fn [_ {:keys [close-fn]}]
+                                         (close-fn)
+                                         (plugin-handler/unregister-plugin id))})]
+                     (state/set-sub-modal! confirm-fn {:center? true}))}
+             (t :plugin/uninstall)]]]
+
+          (when (seq sponsors)
+            [:div.de.sponsors
+             [:strong (ui/icon "coffee")]
+             [:ul.menu-list
+              (for [link sponsors]
+                [:li [:a {:href link :target "_blank"}
+                      [:span.flex.items-center link (ui/icon "external-link")]]])]])
+          ]
 
+         [:div.r.flex.items-center
+          (when (and unpacked? (not disabled))
             [:a.btn
-             {:class    (util/classnames [{:disabled   (or installed? installing-or-updating?)
-                                           :installing installing-or-updating?}])
-              :on-click #(plugin-handler/install-marketplace-plugin item)}
-             (if installed?
-               (t :plugin/installed)
-               (if installing-or-updating?
-                 [:span.flex.items-center [:small svg/loading]
-                  (t :plugin/installing)]
-                 (t :plugin/install)))]]]
-
-          ;; installed ctls
-          [:div.ctl
-           [:div.l
-            [:div.de
-             [:strong (ui/icon "settings")]
-             [:ul.menu-list
-              [:li {:on-click #(when usf (js/apis.openPath usf))} (t :plugin/open-settings)]
-              [:li {:on-click #(js/apis.openPath url)} (t :plugin/open-package)]
-              [:li {:on-click
-                    #(let [confirm-fn
-                           (ui/make-confirm-modal
-                             {:title      (t :plugin/delete-alert name)
-                              :on-confirm (fn [_ {:keys [close-fn]}]
-                                            (close-fn)
-                                            (plugin-handler/unregister-plugin id))})]
-                       (state/set-sub-modal! confirm-fn {:center? true}))}
-               (t :plugin/uninstall)]]]
-
-            (when (seq sponsors)
-              [:div.de.sponsors
-               [:strong (ui/icon "coffee")]
-               [:ul.menu-list
-                (for [link sponsors]
-                  [:li [:a {:href link :target "_blank"}
-                        [:span.flex.items-center link (ui/icon "external-link")]]])]])
-            ]
-
-           [:div.r.flex.items-center
-            (when (and unpacked? (not disabled))
-              [:a.btn
-               {:on-click #(js-invoke js/LSPluginCore "reload" id)}
-               (t :plugin/reload)])
-
-            (when (not unpacked?)
-              [:div.updates-actions
-               [:a.btn
-                {:class    (util/classnames [{:disabled installing-or-updating?}])
-                 :on-click #(when-not has-other-pending?
-                              (plugin-handler/check-or-update-marketplace-plugin
-                                (assoc item :only-check (not new-version))
-                                (fn [e] (notification/show! e :error))))}
-
-                (if installing-or-updating?
-                  (t :plugin/updating)
-                  (if new-version
-                    (str (t :plugin/update) " 👉 " new-version)
-                    (t :plugin/check-update))
-                  )]])
-
-            (ui/toggle (not disabled)
-                       (fn []
-                         (js-invoke js/LSPluginCore (if disabled "enable" "disable") id)
-                         (page-handler/init-commands!))
-                       true)]])]])))
+             {:on-click #(js-invoke js/LSPluginCore "reload" id)}
+             (t :plugin/reload)])
+
+          (when (not unpacked?)
+            [:div.updates-actions
+             [:a.btn
+              {:class    (util/classnames [{:disabled installing-or-updating?}])
+               :on-click #(when-not has-other-pending?
+                            (plugin-handler/check-or-update-marketplace-plugin
+                             (assoc item :only-check (not new-version))
+                             (fn [e] (notification/show! e :error))))}
+
+              (if installing-or-updating?
+                (t :plugin/updating)
+                (if new-version
+                  (str (t :plugin/update) " 👉 " new-version)
+                  (t :plugin/check-update))
+                )]])
+
+          (ui/toggle (not disabled)
+                     (fn []
+                       (js-invoke js/LSPluginCore (if disabled "enable" "disable") id)
+                       (page-handler/init-commands!))
+                     true)]])]]))
 
 (rum/defc panel-control-tabs
   < rum/static
@@ -485,44 +479,41 @@
                                [@*sort-by #(compare %2 %1)])
                              filtered-pkgs))]
 
-    (rum/with-context
-      [[t] i18n/*tongue-context*]
-
-      [:div.cp__plugins-marketplace
-
-       (panel-control-tabs
-         t
-         @*search-key *search-key
-         @*category *category
-         @*sort-by *sort-by nil true
-         develop-mode? (::reload state))
-
-       (cond
-         (not online?)
-         [:p.flex.justify-center.pt-20.opacity-50
-          (svg/offline 30)]
-
-         @*fetching
-         [:p.flex.justify-center.pt-20
-          svg/loading]
-
-         @*error
-         [:p.flex.justify-center.pt-20.opacity-50
-          "Remote error: " (.-message @*error)]
-
-         :else
-         [:div.cp__plugins-marketplace-cnt
-          {:class (util/classnames [{:has-installing (boolean installing)}])}
-          [:div.cp__plugins-item-lists.grid-cols-1.md:grid-cols-2.lg:grid-cols-3
-           (for [item sorted-pkgs]
-             (rum/with-key
-               (let [pid (keyword (:id item))
-                     stat (:stat item)]
-                 (plugin-item-card
-                   item true *search-key installing
-                   (and installing (= (keyword (:id installing)) pid))
-                   (contains? installed-plugins pid) stat nil))
-               (:id item)))]])])))
+    [:div.cp__plugins-marketplace
+
+     (panel-control-tabs
+      t
+      @*search-key *search-key
+      @*category *category
+      @*sort-by *sort-by nil true
+      develop-mode? (::reload state))
+
+     (cond
+       (not online?)
+       [:p.flex.justify-center.pt-20.opacity-50
+        (svg/offline 30)]
+
+       @*fetching
+       [:p.flex.justify-center.pt-20
+        svg/loading]
+
+       @*error
+       [:p.flex.justify-center.pt-20.opacity-50
+        "Remote error: " (.-message @*error)]
+
+       :else
+       [:div.cp__plugins-marketplace-cnt
+        {:class (util/classnames [{:has-installing (boolean installing)}])}
+        [:div.cp__plugins-item-lists.grid-cols-1.md:grid-cols-2.lg:grid-cols-3
+         (for [item sorted-pkgs]
+           (rum/with-key
+             (let [pid (keyword (:id item))
+                   stat (:stat item)]
+               (plugin-item-card
+                item true *search-key installing
+                (and installing (= (keyword (:id installing)) pid))
+                (contains? installed-plugins pid) stat nil))
+             (:id item)))]])]))
 
 (rum/defcs installed-plugins
   < rum/static rum/reactive
@@ -570,27 +561,24 @@
                               (#(update % 0 (fn [coll] (sort-by :iir coll))))
                               (flatten))
                          filtered-plugins)]
-    (rum/with-context
-      [[t] i18n/*tongue-context*]
-
-      [:div.cp__plugins-installed
-
-       (panel-control-tabs
-         t
-         @*search-key *search-key
-         @*category *category
-         @*filter-by *filter-by
-         selected-unpacked-pkg
-         false develop-mode? nil)
-
-       [:div.cp__plugins-item-lists.grid-cols-1.md:grid-cols-2.lg:grid-cols-3
-        (for [item sorted-plugins]
-          (rum/with-key
-            (let [pid (keyword (:id item))]
-              (plugin-item-card
-                item false *search-key updating
-                (and updating (= (keyword (:id updating)) pid))
-                true nil (get coming-updates pid))) (:id item)))]])))
+    [:div.cp__plugins-installed
+
+     (panel-control-tabs
+      t
+      @*search-key *search-key
+      @*category *category
+      @*filter-by *filter-by
+      selected-unpacked-pkg
+      false develop-mode? nil)
+
+     [:div.cp__plugins-item-lists.grid-cols-1.md:grid-cols-2.lg:grid-cols-3
+      (for [item sorted-plugins]
+        (rum/with-key
+          (let [pid (keyword (:id item))]
+            (plugin-item-card
+             item false *search-key updating
+             (and updating (= (keyword (:id updating)) pid))
+             true nil (get coming-updates pid))) (:id item)))]]))
 
 (rum/defcs waiting-coming-updates
   < rum/reactive
@@ -720,30 +708,27 @@
          (js/setTimeout (fn [] (.focus el)) 100))
       [])
 
-    (rum/with-context
-      [[t] i18n/*tongue-context*]
-
-      [:div.cp__plugins-page
-       {:ref       *el-ref
-        :tab-index "-1"}
-       [:h1 (t :plugins)]
-       (security-warning)
-       [:hr]
-
-       [:div.tabs.flex.items-center.justify-center
-        [:div.tabs-inner.flex.items-center
-         (ui/button [:span.it (t :plugin/installed)]
-                    :on-click #(set-active! :installed)
-                    :intent "logseq" :class (if-not market? "active" ""))
-
-         (ui/button [:span.mk (svg/apps 16) (t :plugin/marketplace)]
-                    :on-click #(set-active! :marketplace)
-                    :intent "logseq" :class (if market? "active" ""))]]
-
-       [:div.panels
-        (if market?
-          (marketplace-plugins)
-          (installed-plugins))]])))
+    [:div.cp__plugins-page
+     {:ref       *el-ref
+      :tab-index "-1"}
+     [:h1 (t :plugins)]
+     (security-warning)
+     [:hr]
+
+     [:div.tabs.flex.items-center.justify-center
+      [:div.tabs-inner.flex.items-center
+       (ui/button [:span.it (t :plugin/installed)]
+         :on-click #(set-active! :installed)
+         :intent "logseq" :class (if-not market? "active" ""))
+
+       (ui/button [:span.mk (svg/apps 16) (t :plugin/marketplace)]
+         :on-click #(set-active! :marketplace)
+         :intent "logseq" :class (if market? "active" ""))]]
+
+     [:div.panels
+      (if market?
+        (marketplace-plugins)
+        (installed-plugins))]]))
 
 (rum/defc custom-js-installer
   [{:keys [t current-repo db-restoring? nfs-granted?]}]

+ 187 - 190
src/main/frontend/components/repo.cljs

@@ -5,7 +5,7 @@
             [frontend.components.svg :as svg]
             [frontend.components.widgets :as widgets]
             [frontend.config :as config]
-            [frontend.context.i18n :as i18n]
+            [frontend.context.i18n :refer [t]]
             [frontend.db :as db]
             [frontend.encrypt :as e]
             [frontend.handler.common :as common-handler]
@@ -40,52 +40,51 @@
   (let [repos (->> (state/sub [:me :repos])
                    (remove #(= (:url %) config/local-repo)))
         repos (util/distinct-by :url repos)]
-    (rum/with-context [[t] i18n/*tongue-context*]
-      (if (seq repos)
-        [:div#graphs
-         [:h1.title "All Graphs"]
-         [:p.ml-2.opacity-70
-          (if (state/github-authed?)
-            "A \"graph\" in Logseq could be either a local directory or a git repo."
-            "A \"graph\" in Logseq means a local directory.")]
+    (if (seq repos)
+      [:div#graphs
+       [:h1.title "All Graphs"]
+       [:p.ml-2.opacity-70
+        (if (state/github-authed?)
+          "A \"graph\" in Logseq could be either a local directory or a git repo."
+          "A \"graph\" in Logseq means a local directory.")]
 
-         [:div.pl-1.content.mt-3
-          [:div.flex.flex-row.my-4
-           (when (or (nfs-handler/supported?)
-                     (mobile-util/is-native-platform?))
-             [:div.mr-8
-              (ui/button
-                (t :open-a-directory)
-                :on-click #(page-handler/ls-dir-files! shortcut/refresh!))])
-           (when (and (state/logged?) (not (util/electron?)))
-             (ui/button
-               "Add another git repo"
-               :href (rfe/href :repo-add nil {:graph-types "github"})
-               :intent "logseq"))]
-          (for [{:keys [id url] :as repo} repos]
-            (let [local? (config/local-db? url)]
-              [:div.flex.justify-between.mb-4 {:key id}
-               (if local?
-                 (let [local-dir (config/get-local-dir url)
-                       graph-name (text/get-graph-name-from-path local-dir)]
-                   [:a {:title local-dir
-                        :on-click #(state/pub-event! [:graph/switch url])}
-                    graph-name])
-                 [:a {:target "_blank"
-                      :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 []
-                              (repo-handler/remove-repo! repo))}
-                 "Unlink"]]]))]]
-        (widgets/add-graph)))))
+       [:div.pl-1.content.mt-3
+        [:div.flex.flex-row.my-4
+         (when (or (nfs-handler/supported?)
+                   (mobile-util/is-native-platform?))
+           [:div.mr-8
+            (ui/button
+              (t :open-a-directory)
+              :on-click #(page-handler/ls-dir-files! shortcut/refresh!))])
+         (when (and (state/logged?) (not (util/electron?)))
+           (ui/button
+             "Add another git repo"
+             :href (rfe/href :repo-add nil {:graph-types "github"})
+             :intent "logseq"))]
+        (for [{:keys [id url] :as repo} repos]
+          (let [local? (config/local-db? url)]
+            [:div.flex.justify-between.mb-4 {:key id}
+             (if local?
+               (let [local-dir (config/get-local-dir url)
+                     graph-name (text/get-graph-name-from-path local-dir)]
+                 [:a {:title local-dir
+                      :on-click #(state/pub-event! [:graph/switch url])}
+                  graph-name])
+               [:a {:target "_blank"
+                    :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 []
+                            (repo-handler/remove-repo! repo))}
+               "Unlink"]]]))]]
+      (widgets/add-graph))))
 
 (defn refresh-cb []
   (page-handler/create-today-journal!)
@@ -136,48 +135,47 @@
               (toggle-fn)
               (js/setTimeout common-handler/check-changed-files-status 0))}])
         (fn [{:keys [toggle-fn]}]
-          (rum/with-context [[t] i18n/*tongue-context*]
-            [:div.p-2.rounded-md.shadow-xs.bg-base-3.flex.flex-col.sync-content
-             {:on-mouse-leave toggle-fn}
-             [:div
-              [:div
-               (cond
-                 push-failed?
-                 [:p (t :git/push-failed)]
-                 (and should-push? (seq changed-files))
-                 [:div.changes
-                  [:ul.overflow-y-auto {:style {:max-height 250}}
-                   (for [file changed-files]
-                     [:li {:key (str "sync-" file)}
-                      [:div.flex.flex-row.justify-between.align-items
-                       [:a {:href (rfe/href :file {:path file})}
-                        file]
-                       [:a.ml-4.text-sm.mt-1
-                        {:on-click (fn [_e]
-                                     (export-handler/download-file! file))}
-                        [:span (t :download)]]]])]]
-                 :else
-                 [:p (t :git/local-changes-synced)])]
-              ;; [:a.text-sm.font-bold {:href "/diff"} "Check diff"]
-              [:div.flex.flex-row.justify-between.align-items.mt-2
-               (ui/button (t :git/push)
-                 :on-click (fn [] (state/set-modal! commit/add-commit-message)))
-               (when pushing? svg/loading)]]
-             [:hr]
-             [:div
-              (when-not (string/blank? last-pulled-at)
-                [:p {:style {:font-size 12}} (t :git/last-pull)
-                 (str ": " last-pulled-at)])
-              [:div.flex.flex-row.justify-between.align-items
-               (ui/button (t :git/pull)
-                 :on-click (fn [] (repo-handler/pull-current-repo)))
-               (when pulling? svg/loading)]
-              [:a.mt-5.text-sm.opacity-50.block
-               {:on-click (fn []
-                            (export-handler/export-repo-as-zip! repo))}
-               (t :repo/download-zip)]
-              [:p.pt-2.text-sm.opacity-50
-               (t :git/version) (str " " version/version)]]])))])))
+          [:div.p-2.rounded-md.shadow-xs.bg-base-3.flex.flex-col.sync-content
+           {:on-mouse-leave toggle-fn}
+           [:div
+            [:div
+             (cond
+               push-failed?
+               [:p (t :git/push-failed)]
+               (and should-push? (seq changed-files))
+               [:div.changes
+                [:ul.overflow-y-auto {:style {:max-height 250}}
+                 (for [file changed-files]
+                   [:li {:key (str "sync-" file)}
+                    [:div.flex.flex-row.justify-between.align-items
+                     [:a {:href (rfe/href :file {:path file})}
+                      file]
+                     [:a.ml-4.text-sm.mt-1
+                      {:on-click (fn [_e]
+                                   (export-handler/download-file! file))}
+                      [:span (t :download)]]]])]]
+               :else
+               [:p (t :git/local-changes-synced)])]
+            ;; [:a.text-sm.font-bold {:href "/diff"} "Check diff"]
+            [:div.flex.flex-row.justify-between.align-items.mt-2
+             (ui/button (t :git/push)
+               :on-click (fn [] (state/set-modal! commit/add-commit-message)))
+             (when pushing? svg/loading)]]
+           [:hr]
+           [:div
+            (when-not (string/blank? last-pulled-at)
+              [:p {:style {:font-size 12}} (t :git/last-pull)
+               (str ": " last-pulled-at)])
+            [:div.flex.flex-row.justify-between.align-items
+             (ui/button (t :git/pull)
+               :on-click (fn [] (repo-handler/pull-current-repo)))
+             (when pulling? svg/loading)]
+            [:a.mt-5.text-sm.opacity-50.block
+             {:on-click (fn []
+                          (export-handler/export-repo-as-zip! repo))}
+             (t :repo/download-zip)]
+            [:p.pt-2.text-sm.opacity-50
+             (t :git/version) (str " " version/version)]]]))])))
 
 (defn- check-multiple-windows?
   [state]
@@ -190,108 +188,107 @@
   [state]
   (let [multiple-windows? (::electron-multiple-windows? state)]
     (when-let [current-repo (state/sub :git/current-repo)]
-      (rum/with-context [[t] i18n/*tongue-context*]
-        (let [get-repo-name (fn [repo]
-                              (cond
-                                (mobile-util/is-native-platform?)
-                                (text/get-graph-name-from-path repo)
+      (let [get-repo-name (fn [repo]
+                            (cond
+                              (mobile-util/is-native-platform?)
+                              (text/get-graph-name-from-path repo)
 
-                                (config/local-db? repo)
-                                (config/get-local-dir repo)
+                              (config/local-db? repo)
+                              (config/get-local-dir repo)
 
-                                :else
-                                (db/get-repo-path repo)))
-              repos (state/sub [:me :repos])
-              repos (remove (fn [r] (= config/local-repo (:url r))) repos)
-              switch-repos (remove (fn [repo]
-                                     (= current-repo (:url repo)))
-                                   repos)
-              repo-links (mapv
-                          (fn [{:keys [url]}]
-                            (let [repo-path (get-repo-name url)
-                                  short-repo-name (text/get-graph-name-from-path repo-path)]
-                              {:title short-repo-name
-                               :hover-detail repo-path ;; show full path on hover
-                               :options {:class "ml-1"
-                                         :on-click #(state/pub-event! [:graph/switch url])}}))
-                          switch-repos)
-              links (->>
-                     (concat repo-links
-                             [(when (seq switch-repos)
-                                {:hr true})
-                              {:title (t :new-graph)
-                               :options {:href (rfe/href :repo-add)}}
-                              {:title (t :all-graphs)
-                               :options {:href (rfe/href :repos)}}
-                              (let [nfs-repo? (config/local-db? current-repo)]
-                                (when (and nfs-repo?
-                                           (not= current-repo config/local-repo)
-                                           (or (nfs-handler/supported?)
-                                               (mobile-util/is-native-platform?)))
-                                  {:title (t :sync-from-local-files)
-                                   :hover-detail (t :sync-from-local-files-detail)
-                                   :options {:on-click
-                                             (fn []
-                                               (state/pub-event!
-                                                [:modal/show
-                                                 [:div {:style {:max-width 700}}
-                                                  [:p "Refresh detects and processes files modified on your disk and diverged from the actual Logseq page content. Continue?"]
-                                                  (ui/button
-                                                    "Yes"
-                                                    :autoFocus "on"
-                                                    :large? true
-                                                    :on-click (fn []
-                                                                (state/close-modal!)
-                                                                (nfs-handler/refresh! (state/get-current-repo) refresh-cb)))]]))}}))
-                              {:title        (t :re-index)
-                               :hover-detail (t :re-index-detail)
-                               :options (cond->
-                                          {:on-click
+                              :else
+                              (db/get-repo-path repo)))
+            repos (state/sub [:me :repos])
+            repos (remove (fn [r] (= config/local-repo (:url r))) repos)
+            switch-repos (remove (fn [repo]
+                                   (= current-repo (:url repo)))
+                                 repos)
+            repo-links (mapv
+                        (fn [{:keys [url]}]
+                          (let [repo-path (get-repo-name url)
+                                short-repo-name (text/get-graph-name-from-path repo-path)]
+                            {:title short-repo-name
+                             :hover-detail repo-path ;; show full path on hover
+                             :options {:class "ml-1"
+                                       :on-click #(state/pub-event! [:graph/switch url])}}))
+                        switch-repos)
+            links (->>
+                   (concat repo-links
+                           [(when (seq switch-repos)
+                              {:hr true})
+                            {:title (t :new-graph)
+                             :options {:href (rfe/href :repo-add)}}
+                            {:title (t :all-graphs)
+                             :options {:href (rfe/href :repos)}}
+                            (let [nfs-repo? (config/local-db? current-repo)]
+                              (when (and nfs-repo?
+                                         (not= current-repo config/local-repo)
+                                         (or (nfs-handler/supported?)
+                                             (mobile-util/is-native-platform?)))
+                                {:title (t :sync-from-local-files)
+                                 :hover-detail (t :sync-from-local-files-detail)
+                                 :options {:on-click
                                            (fn []
-                                             (if @multiple-windows?
-                                               (state/pub-event!
-                                                [:modal/show
-                                                 [:div
-                                                  [:p "You need to close the other windows before re-index this graph."]]])
-                                               (state/pub-event!
-                                                [:modal/show
-                                                 [:div {:style {:max-width 700}}
-                                                  [:p "Re-index will discard the current graph, and then processes all the files again as they are currently stored on disk. You will lose unsaved changes and it might take a while. Continue?"]
-                                                  (ui/button
-                                                    "Yes"
-                                                    :autoFocus "on"
-                                                    :large? true
-                                                    :on-click (fn []
-                                                                (state/close-modal!)
-                                                                (repo-handler/re-index!
-                                                                 nfs-handler/rebuild-index!
-                                                                 page-handler/create-today-journal!)))]])))})}
-                              (when (util/electron?)
-                                {:title        (t :open-new-window)
-                                 :options {:on-click ui-handler/open-new-window!}})])
-                     (remove nil?))]
-          (when (seq repos)
-            (ui/dropdown-with-links
-             (fn [{:keys [toggle-fn]}]
-               (let [repo-path (get-repo-name current-repo)
-                     short-repo-name (if (or (util/electron?)
-                                             (mobile-util/is-native-platform?))
-                                       (text/get-file-basename repo-path)
-                                       repo-path)]
-                 [:a.item.group.flex.items-center.px-2.py-2.text-sm.font-medium.rounded-md
-                  {:on-click (fn []
-                               (check-multiple-windows? state)
-                               (toggle-fn))
-                   :title repo-path} ;; show full path on hover
-                  (ui/icon "database mr-3" {:style {:font-size 20} :id "database-icon"})
-                  [:div.graphs
-                   [:span#repo-switch.block.pr-2.whitespace-nowrap
-                    [:span [:span#repo-name.font-medium short-repo-name]]
-                    [:span.dropdown-caret.ml-2 {:style {:border-top-color "#6b7280"}}]]]]))
-             links
-             (cond->
-               {:modal-class (util/hiccup->class
-                              "origin-top-right.absolute.left-0.mt-2.rounded-md.shadow-lg")}
-               (seq switch-repos)
-               (assoc :links-header [:div.font-medium.text-sm.opacity-60.px-4.pt-2
-                                     "Switch to:"])))))))))
+                                             (state/pub-event!
+                                              [:modal/show
+                                               [:div {:style {:max-width 700}}
+                                                [:p "Refresh detects and processes files modified on your disk and diverged from the actual Logseq page content. Continue?"]
+                                                (ui/button
+                                                  "Yes"
+                                                  :autoFocus "on"
+                                                  :large? true
+                                                  :on-click (fn []
+                                                              (state/close-modal!)
+                                                              (nfs-handler/refresh! (state/get-current-repo) refresh-cb)))]]))}}))
+                            {:title        (t :re-index)
+                             :hover-detail (t :re-index-detail)
+                             :options (cond->
+                                        {:on-click
+                                         (fn []
+                                           (if @multiple-windows?
+                                             (state/pub-event!
+                                              [:modal/show
+                                               [:div
+                                                [:p "You need to close the other windows before re-index this graph."]]])
+                                             (state/pub-event!
+                                              [:modal/show
+                                               [:div {:style {:max-width 700}}
+                                                [:p "Re-index will discard the current graph, and then processes all the files again as they are currently stored on disk. You will lose unsaved changes and it might take a while. Continue?"]
+                                                (ui/button
+                                                  "Yes"
+                                                  :autoFocus "on"
+                                                  :large? true
+                                                  :on-click (fn []
+                                                              (state/close-modal!)
+                                                              (repo-handler/re-index!
+                                                               nfs-handler/rebuild-index!
+                                                               page-handler/create-today-journal!)))]])))})}
+                            (when (util/electron?)
+                              {:title        (t :open-new-window)
+                               :options {:on-click ui-handler/open-new-window!}})])
+                   (remove nil?))]
+        (when (seq repos)
+          (ui/dropdown-with-links
+           (fn [{:keys [toggle-fn]}]
+             (let [repo-path (get-repo-name current-repo)
+                   short-repo-name (if (or (util/electron?)
+                                           (mobile-util/is-native-platform?))
+                                     (text/get-file-basename repo-path)
+                                     repo-path)]
+               [:a.item.group.flex.items-center.px-2.py-2.text-sm.font-medium.rounded-md
+                {:on-click (fn []
+                             (check-multiple-windows? state)
+                             (toggle-fn))
+                 :title repo-path} ;; show full path on hover
+                (ui/icon "database mr-3" {:style {:font-size 20} :id "database-icon"})
+                [:div.graphs
+                 [:span#repo-switch.block.pr-2.whitespace-nowrap
+                  [:span [:span#repo-name.font-medium short-repo-name]]
+                  [:span.dropdown-caret.ml-2 {:style {:border-top-color "#6b7280"}}]]]]))
+           links
+           (cond->
+             {:modal-class (util/hiccup->class
+                            "origin-top-right.absolute.left-0.mt-2.rounded-md.shadow-lg")}
+             (seq switch-repos)
+             (assoc :links-header [:div.font-medium.text-sm.opacity-60.px-4.pt-2
+                                   "Switch to:"]))))))))

+ 5 - 6
src/main/frontend/components/right_sidebar.cljs

@@ -5,7 +5,7 @@
             [frontend.components.onboarding :as onboarding]
             [frontend.components.page :as page]
             [frontend.components.svg :as svg]
-            [frontend.context.i18n :as i18n]
+            [frontend.context.i18n :refer [t]]
             [frontend.date :as date]
             [frontend.db :as db]
             [frontend.db-mixins :as db-mixins]
@@ -247,8 +247,7 @@
                  blocks)
         sidebar-open? (state/sub :ui/sidebar-open?)
         repo (state/sub :git/current-repo)]
-    (rum/with-context [[t] i18n/*tongue-context*]
-      [:div#right-sidebar.cp__right-sidebar.h-screen
-       {:class (if sidebar-open? "open" "closed")}
-       (when sidebar-open?
-         (sidebar-inner repo t blocks))])))
+    [:div#right-sidebar.cp__right-sidebar.h-screen
+     {:class (if sidebar-open? "open" "closed")}
+     (when sidebar-open?
+       (sidebar-inner repo t blocks))]))

+ 187 - 193
src/main/frontend/components/search.cljs

@@ -12,11 +12,10 @@
             [frontend.extensions.pdf.assets :as pdf-assets]
             [frontend.ui :as ui]
             [frontend.state :as state]
-            [frontend.search.db :as search-db]
             [frontend.mixins :as mixins]
             [frontend.config :as config]
             [clojure.string :as string]
-            [frontend.context.i18n :as i18n]
+            [frontend.context.i18n :refer [t]]
             [frontend.date :as date]
             [reitit.frontend.easy :as rfe]
             [frontend.modules.shortcut.core :as shortcut]
@@ -68,160 +67,163 @@
    content])
 
 (rum/defc block-search-result-item
-  [repo uuid _format content q search-mode]
-  [: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})])
-         [:div {:class "font-medium" :key "content"} (highlight-exact-query content q)]]])
+  [repo uuid format content q search-mode]
+  (let [content (search-handler/sanity-search-content format content)]
+    [: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})])
+     [:div {:class "font-medium" :key "content"}
+      (highlight-exact-query content q)]]))
 
 (defonce search-timeout (atom nil))
 
 (rum/defc search-auto-complete
   [{:keys [pages files blocks has-more?] :as result} search-q all?]
-  (rum/with-context [[t] i18n/*tongue-context*]
-    (let [pages (when-not all? (map (fn [page]
-                                      (let [alias (model/get-redirect-page-name page)]
-                                        (cond->
-                                          {:type :page
-                                           :data page}
-                                          (and alias
-                                               (not= (util/page-name-sanity-lc page)
-                                                     (util/page-name-sanity-lc alias)))
-                                          (assoc :alias alias))))
-                                 (remove nil? pages)))
-          files (when-not all? (map (fn [file] {:type :file :data file}) files))
-          blocks (map (fn [block] {:type :block :data block}) blocks)
-          search-mode (state/sub :search/mode)
-          new-page (if (or
-                        (and (seq pages)
-                             (= (util/safe-page-name-sanity-lc search-q)
-                                (util/safe-page-name-sanity-lc (:data (first pages)))))
-                        (nil? result)
-                        all?)
-                     []
-                     [{:type :new-page}])
-          result (if config/publishing?
-                   (concat pages files blocks)
-                   (concat new-page pages files blocks))
-          result (if (= search-mode :graph)
-                   [{:type :graph-add-filter}]
-                   result)
-          repo (state/get-current-repo)]
-      [:div
-       (ui/auto-complete
-        result
-        {:class "search-results"
-         :on-chosen (fn [{:keys [type data alias]}]
-                      (search-handler/add-search-to-recent! repo search-q)
-                      (search-handler/clear-search!)
-                      (case type
-                        :graph-add-filter
-                        (state/add-graph-search-filter! search-q)
-
-                        :new-page
-                        (page-handler/create! search-q)
+  (let [pages (when-not all? (map (fn [page]
+                                    (let [alias (model/get-redirect-page-name page)]
+                                      (cond->
+                                        {:type :page
+                                         :data page}
+                                        (and alias
+                                             (not= (util/page-name-sanity-lc page)
+                                                   (util/page-name-sanity-lc alias)))
+                                        (assoc :alias alias))))
+                               (remove nil? pages)))
+        files (when-not all? (map (fn [file] {:type :file :data file}) files))
+        blocks (map (fn [block] {:type :block :data block}) blocks)
+        search-mode (state/sub :search/mode)
+        new-page (if (or
+                      (and (seq pages)
+                           (= (util/safe-page-name-sanity-lc search-q)
+                              (util/safe-page-name-sanity-lc (:data (first pages)))))
+                      (nil? result)
+                      all?)
+                   []
+                   [{:type :new-page}])
+        result (if config/publishing?
+                 (concat pages files blocks)
+                 (concat new-page pages files blocks))
+        result (if (= search-mode :graph)
+                 [{:type :graph-add-filter}]
+                 result)
+        repo (state/get-current-repo)]
+    [:div
+     (ui/auto-complete
+      result
+      {:class "search-results"
+       :on-chosen (fn [{:keys [type data alias]}]
+                    (search-handler/add-search-to-recent! repo search-q)
+                    (search-handler/clear-search!)
+                    (case type
+                      :graph-add-filter
+                      (state/add-graph-search-filter! search-q)
 
-                        :page
-                        (let [data (or alias data)]
-                          (route/redirect-to-page! data))
+                      :new-page
+                      (page-handler/create! search-q)
 
-                        :file
-                        (route/redirect! {:to :file
-                                          :path-params {:path data}})
+                      :page
+                      (let [data (or alias data)]
+                        (route/redirect-to-page! data))
 
-                        :block
-                        (let [repo (state/get-current-repo)
-                              block-uuid (uuid (:block/uuid data))
-                              collapsed? (db/parents-collapsed? repo block-uuid)
-                              page (:block/page (db/entity [:block/uuid block-uuid]))
-                              long-page? (block-handler/long-page? repo (:db/id page))]
-                          (if page
-                            (if (or collapsed? long-page?)
-                             (route/redirect-to-page! block-uuid)
-                             (route/redirect-to-page! (:block/name page) (str "ls-block-" (:block/uuid data))))
-                            ;; search indice outdated
-                            (println "[Error] Block page missing: "
-                                     {:block-id block-uuid
-                                      :block (db/pull [:block/uuid block-uuid])})))
-                        nil)
-                      (state/close-modal!))
-         :on-shift-chosen (fn [{:keys [type data alias]}]
-                            (search-handler/add-search-to-recent! repo search-q)
-                            (case type
-                              :page
-                              (let [data (or alias data)
-                                    page (when data (db/entity [:block/name (util/page-name-sanity-lc data)]))]
-                                (when page
-                                  (state/sidebar-add-block!
-                                   (state/get-current-repo)
-                                   (:db/id page)
-                                   :page
-                                   {:page page})))
+                      :file
+                      (route/redirect! {:to :file
+                                        :path-params {:path data}})
 
-                              :block
-                              (let [block-uuid (uuid (:block/uuid data))
-                                    block (db/entity [:block/uuid block-uuid])]
+                      :block
+                      (let [repo (state/get-current-repo)
+                            block-uuid (uuid (:block/uuid data))
+                            collapsed? (db/parents-collapsed? repo block-uuid)
+                            page (:block/page (db/entity [:block/uuid block-uuid]))
+                            long-page? (block-handler/long-page? repo (:db/id page))]
+                        (if page
+                          (if (or collapsed? long-page?)
+                            (route/redirect-to-page! block-uuid)
+                            (route/redirect-to-page! (:block/name page) (str "ls-block-" (:block/uuid data))))
+                          ;; search indice outdated
+                          (println "[Error] Block page missing: "
+                                   {:block-id block-uuid
+                                    :block (db/pull [:block/uuid block-uuid])})))
+                      nil)
+                    (state/close-modal!))
+       :on-shift-chosen (fn [{:keys [type data alias]}]
+                          (search-handler/add-search-to-recent! repo search-q)
+                          (case type
+                            :page
+                            (let [data (or alias data)
+                                  page (when data (db/entity [:block/name (util/page-name-sanity-lc data)]))]
+                              (when page
                                 (state/sidebar-add-block!
                                  (state/get-current-repo)
-                                 (:db/id block)
-                                 :block
-                                 block))
+                                 (:db/id page)
+                                 :page
+                                 {:page page})))
 
-                              :new-page
-                              (page-handler/create! search-q)
+                            :block
+                            (let [block-uuid (uuid (:block/uuid data))
+                                  block (db/entity [:block/uuid block-uuid])]
+                              (state/sidebar-add-block!
+                               (state/get-current-repo)
+                               (:db/id block)
+                               :block
+                               block))
 
-                              :file
-                              (route/redirect! {:to :file
-                                                :path-params {:path data}})
+                            :new-page
+                            (page-handler/create! search-q)
 
-                              nil)
-                            (state/close-modal!))
-         :item-render (fn [{:keys [type data alias]}]
-                        (let [search-mode (state/get-search-mode)
-                              data (if (string? data) (pdf-assets/fix-local-asset-filename data) data)]
-                          [:div {:class "py-2"} (case type
-                                                  :graph-add-filter
-                                                  [:b search-q]
+                            :file
+                            (route/redirect! {:to :file
+                                              :path-params {:path data}})
 
-                                                  :new-page
-                                                  [:div.text.font-bold (str (t :new-page) ": ")
-                                                   [:span.ml-1 (str "\"" search-q "\"")]]
+                            nil)
+                          (state/close-modal!))
+       :item-render (fn [{:keys [type data alias]}]
+                      (let [search-mode (state/get-search-mode)
+                            data (if (string? data) (pdf-assets/fix-local-asset-filename data) data)]
+                        [:div {:class "py-2"} (case type
+                                                :graph-add-filter
+                                                [:b search-q]
+
+                                                :new-page
+                                                [:div.text.font-bold (str (t :new-page) ": ")
+                                                 [:span.ml-1 (str "\"" search-q "\"")]]
 
-                                                  :page
-                                                  [:span {:data-page-ref data}
-                                                   (when alias
-                                                     (let [target-original-name (model/get-page-original-name alias)]
-                                                       [:span.mr-2.text-sm.font-medium.mb-2 (str "Alias -> " target-original-name)]))
-                                                   (search-result-item "Page" (highlight-exact-query data search-q))]
+                                                :page
+                                                [:span {:data-page-ref data}
+                                                 (when alias
+                                                   (let [target-original-name (model/get-page-original-name alias)]
+                                                     [:span.mr-2.text-sm.font-medium.mb-2 (str "Alias -> " target-original-name)]))
+                                                 (search-result-item "Page" (highlight-exact-query data search-q))]
 
-                                                  :file
-                                                  (search-result-item "File" (highlight-exact-query data search-q))
+                                                :file
+                                                (search-result-item "File" (highlight-exact-query data search-q))
 
-                                                  :block
-                                                  (let [{:block/keys [page uuid]} data  ;; content here is normalized
-                                                        page (util/get-page-original-name page)
-                                                        repo (state/sub :git/current-repo)
-                                                        format (db/get-page-format page)
-                                                        block (model/query-block-by-uuid uuid)
-                                                        content (search-db/block->content block)]
-                                                    [:span {:data-block-ref uuid}
-                                                      (search-result-item "Block"
-                                                        (block-search-result-item repo uuid format content search-q search-mode))])
+                                                :block
+                                                (let [{:block/keys [page uuid]} data  ;; content here is normalized
+                                                      page (util/get-page-original-name page)
+                                                      repo (state/sub :git/current-repo)
+                                                      format (db/get-page-format page)
+                                                      block (model/query-block-by-uuid uuid)
+                                                      content (:block/content block)]
+                                                  [:span {:data-block-ref uuid}
+                                                   (search-result-item "Block"
+                                                                       (block-search-result-item repo uuid format content search-q search-mode))])
 
-                                                  nil)]))})
-       (when (and has-more? (util/electron?) (not all?))
-         [:div.px-2.py-4.search-more
-          [:a.text-sm.font-medium {:href (rfe/href :search {:q search-q})
-                                   :on-click (fn []
-                                               (when-not (string/blank? search-q)
-                                                 (search-handler/search (state/get-current-repo) search-q {:limit 1000
-                                                                                                           :more? true})
-                                                 (search-handler/clear-search!)))}
-           (t :more)]])])))
+                                                nil)]))})
+     (when (and has-more? (util/electron?) (not all?))
+       [:div.px-2.py-4.search-more
+        [:a.text-sm.font-medium {:href (rfe/href :search {:q search-q})
+                                 :on-click (fn []
+                                             (when-not (string/blank? search-q)
+                                               (search-handler/search (state/get-current-repo) search-q {:limit 1000
+                                                                                                         :more? true})
+                                               (search-handler/clear-search!)))}
+         (t :more)]])]))
 
 (rum/defc recent-search-and-pages
   [in-page-search?]
@@ -307,67 +309,59 @@
   [state]
   (let [search-result (state/sub :search/result)
         search-q (state/sub :search/q)
-        blocks-count (or (db/blocks-count) 0)
         search-mode (state/sub :search/mode)
-        timeout (cond
-                  (> blocks-count 2000)
-                  400
-
-                  :else
-                  300)
+        timeout 300
         in-page-search? (= search-mode :page)]
-    (rum/with-context [[t] i18n/*tongue-context*]
-      [:div.cp__palette.cp__palette-main
-       (when (mobile-util/is-native-platform?)
-         {:style {:min-height "50vh"}})
+    [:div.cp__palette.cp__palette-main
+     (when (mobile-util/is-native-platform?)
+       {:style {:min-height "50vh"}})
 
-       [:div.input-wrap
-        [:input.cp__palette-input.w-full
-         {:type          "text"
-          :auto-focus    true
-          :placeholder   (case search-mode
-                           :graph
-                           (t :graph-search)
-                           :page
-                           (t :page-search)
-                           (t :search))
-          :auto-complete (if (util/chrome?) "chrome-off" "off") ; off not working here
-          :value         search-q
-          :on-change     (fn [e]
-                           (when @search-timeout
-                             (js/clearTimeout @search-timeout))
-                           (let [value (util/evalue e)
-                                 is-composing? (util/onchange-event-is-composing? e)] ;; #3199
-                             (if (and (string/blank? value) (not is-composing?))
-                               (search-handler/clear-search! false)
-                               (let [search-mode (state/get-search-mode)
-                                     opts (if (= :page search-mode)
-                                            (when-let [current-page (or (state/get-current-page)
-                                                                        (date/today))]
-                                              {:page-db-id (:db/id (db/entity [:block/name (util/page-name-sanity-lc current-page)]))})
-                                            {})]
-                                 (state/set-q! value)
-                                 (reset! search-timeout
-                                         (js/setTimeout
-                                          (fn []
-                                            (if (= :page search-mode)
-                                              (search-handler/search (state/get-current-repo) value opts)
-                                              (search-handler/search (state/get-current-repo) value)))
-                                          timeout))))))}]]
-       [:div.search-results-wrap
-        (if (seq search-result)
-          (search-auto-complete search-result search-q false)
-          (recent-search-and-pages in-page-search?))]])))
+     [:div.input-wrap
+      [:input.cp__palette-input.w-full
+       {:type          "text"
+        :auto-focus    true
+        :placeholder   (case search-mode
+                         :graph
+                         (t :graph-search)
+                         :page
+                         (t :page-search)
+                         (t :search))
+        :auto-complete (if (util/chrome?) "chrome-off" "off") ; off not working here
+        :value         search-q
+        :on-change     (fn [e]
+                         (when @search-timeout
+                           (js/clearTimeout @search-timeout))
+                         (let [value (util/evalue e)
+                               is-composing? (util/onchange-event-is-composing? e)] ;; #3199
+                           (if (and (string/blank? value) (not is-composing?))
+                             (search-handler/clear-search! false)
+                             (let [search-mode (state/get-search-mode)
+                                   opts (if (= :page search-mode)
+                                          (when-let [current-page (or (state/get-current-page)
+                                                                      (date/today))]
+                                            {:page-db-id (:db/id (db/entity [:block/name (util/page-name-sanity-lc current-page)]))})
+                                          {})]
+                               (state/set-q! value)
+                               (reset! search-timeout
+                                       (js/setTimeout
+                                        (fn []
+                                          (if (= :page search-mode)
+                                            (search-handler/search (state/get-current-repo) value opts)
+                                            (search-handler/search (state/get-current-repo) value)))
+                                        timeout))))))}]]
+     [:div.search-results-wrap
+      (if (seq search-result)
+        (search-auto-complete search-result search-q false)
+        (recent-search-and-pages in-page-search?))]]))
 
 (rum/defc more < rum/reactive
   [route]
   (let [search-q (get-in route [:path-params :q])
         search-result (state/sub :search/more-result)]
-    (rum/with-context [[t] i18n/*tongue-context*]
-      [:div#search.flex-1.flex
-       [:div.inner
-        [:h1.title (t :search/result-for) [:i search-q]]
-        [:p.font-medium.tx-sm (str (count (:blocks search-result)) " " (t :search/items))]
-        [:div#search-wrapper.relative.w-full.text-gray-400.focus-within:text-gray-600
-         (when-not (string/blank? search-q)
-           (search-auto-complete search-result search-q true))]]])))
+    [:div#search.flex-1.flex
+     [:div.inner
+      [:h1.title (t :search/result-for) [:i search-q]]
+      [:p.font-medium.tx-sm (str (count (:blocks search-result)) " " (t :search/items))]
+      [:div#search-wrapper.relative.w-full.text-gray-400.focus-within:text-gray-600
+       (when-not (string/blank? search-q)
+         (search-auto-complete search-result search-q true))]]]))

+ 19 - 20
src/main/frontend/components/select.cljs

@@ -4,7 +4,7 @@
   new select-type, set :ui/open-select to the select-type. See
   :graph/open command for an example."
   (:require [frontend.modules.shortcut.core :as shortcut]
-            [frontend.context.i18n :as i18n]
+            [frontend.context.i18n :refer [t]]
             [frontend.search :as search]
             [frontend.state :as state]
             [frontend.ui :as ui]
@@ -35,26 +35,25 @@
           :or {limit 100
                prompt-key :select/default-prompt
                empty-placeholder (fn [_t] [:div])}}]
-  (rum/with-context [[t] i18n/*tongue-context*]
-    (let [input (::input state)]
-      [:div.cp__select.cp__select-main
-       [:div.input-wrap
-        [:input.cp__select-input.w-full
-         {:type        "text"
-          :placeholder (t prompt-key)
-          :auto-focus  true
-          :value       @input
-          :on-change   (fn [e] (reset! input (util/evalue e)))}]]
+  (let [input (::input state)]
+    [:div.cp__select.cp__select-main
+     [:div.input-wrap
+      [:input.cp__select-input.w-full
+       {:type        "text"
+        :placeholder (t prompt-key)
+        :auto-focus  true
+        :value       @input
+        :on-change   (fn [e] (reset! input (util/evalue e)))}]]
 
-       [:div.item-results-wrap
-        (ui/auto-complete
-         (search/fuzzy-search items @input :limit limit :extract-fn :value)
-         {:item-render render-item
-          :class       "cp__select-results"
-          :on-chosen   (fn [x]
-                         (state/close-modal!)
-                         (on-chosen x))
-          :empty-placeholder (empty-placeholder t)})]])))
+     [:div.item-results-wrap
+      (ui/auto-complete
+       (search/fuzzy-search items @input :limit limit :extract-fn :value)
+       {:item-render render-item
+        :class       "cp__select-results"
+        :on-chosen   (fn [x]
+                       (state/close-modal!)
+                       (on-chosen x))
+        :empty-placeholder (empty-placeholder t)})]]))
 
 (defn select-config
   "Config that supports multiple types (uses) of this component. To add a new

+ 159 - 165
src/main/frontend/components/settings.cljs

@@ -2,7 +2,7 @@
   (:require [clojure.string :as string]
             [frontend.components.svg :as svg]
             [frontend.config :as config]
-            [frontend.context.i18n :as i18n]
+            [frontend.context.i18n :refer [t]]
             [frontend.storage :as storage]
             [frontend.date :as date]
             [frontend.dicts :as dicts]
@@ -109,22 +109,21 @@
 
 (rum/defc delete-account-confirm
   [close-fn]
-  (rum/with-context [[t] i18n/*tongue-context*]
-    [:div
-     (ui/admonition
-      :important
-      [:p.text-gray-700 (t :user/delete-account-notice)])
-     [:div.mt-5.sm:mt-4.sm:flex.sm:flex-row-reverse
-      [:span.flex.w-full.rounded-md.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 user-handler/delete-account!}
-        (t :user/delete-account)]]
-      [:span.mt-3.flex.w-full.rounded-md.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}
-        "Cancel"]]]]))
+  [:div
+   (ui/admonition
+    :important
+    [:p.text-gray-700 (t :user/delete-account-notice)])
+   [:div.mt-5.sm:mt-4.sm:flex.sm:flex-row-reverse
+    [:span.flex.w-full.rounded-md.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 user-handler/delete-account!}
+      (t :user/delete-account)]]
+    [:span.mt-3.flex.w-full.rounded-md.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}
+      "Cancel"]]]])
 
 (rum/defc outdenting-hint
   []
@@ -163,22 +162,20 @@
 
 
 (defn edit-config-edn []
-  (rum/with-context [[t] i18n/*tongue-context*]
-    (row-with-button-action
-     {:left-label   (t :settings-page/custom-configuration)
-      :button-label (t :settings-page/edit-config-edn)
-      :href         (rfe/href :file {:path (config/get-config-path)})
-      :on-click     #(js/setTimeout (fn [] (ui-handler/toggle-settings-modal!)))
-      :-for         "config_edn"})))
+  (row-with-button-action
+   {:left-label   (t :settings-page/custom-configuration)
+    :button-label (t :settings-page/edit-config-edn)
+    :href         (rfe/href :file {:path (config/get-config-path)})
+    :on-click     #(js/setTimeout (fn [] (ui-handler/toggle-settings-modal!)))
+    :-for         "config_edn"}))
 
 (defn edit-custom-css []
-  (rum/with-context [[t] i18n/*tongue-context*]
-    (row-with-button-action
-     {:left-label   (t :settings-page/custom-theme)
-      :button-label (t :settings-page/edit-custom-css)
-      :href         (rfe/href :file {:path (config/get-custom-css-path)})
-      :on-click     #(js/setTimeout (fn [] (ui-handler/toggle-settings-modal!)))
-      :-for         "customize_css"})))
+  (row-with-button-action
+   {:left-label   (t :settings-page/custom-theme)
+    :button-label (t :settings-page/edit-custom-css)
+    :href         (rfe/href :file {:path (config/get-custom-css-path)})
+    :on-click     #(js/setTimeout (fn [] (ui-handler/toggle-settings-modal!)))
+    :-for         "customize_css"}))
 
 (defn show-brackets-row [t show-brackets?]
   [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
@@ -554,137 +551,134 @@
         switch-theme (if dark? "white" "dark")
         *active (::active state)]
 
-    (rum/with-context
-      [[t] i18n/*tongue-context*]
-
-      [:div#settings.cp__settings-main
-       [:header
-        [:h1.title (t :settings)]]
-
-       [:div.cp__settings-inner.md:flex
-
-        [:aside.md:w-64 {:style {:min-width "10rem"}}
-         [:ul
-          (for [[label text icon]
-                [[:general (t :settings-page/tab-general) (ui/icon "adjustments" {:style {:font-size 20}})]
-                 [:editor (t :settings-page/tab-editor) (ui/icon "writing" {:style {:font-size 20}})]
-                 (when-not (mobile-util/is-native-platform?)
-                   [:git (t :settings-page/tab-version-control) (ui/icon "history" {:style {:font-size 20}})])
-                 [:advanced (t :settings-page/tab-advanced) (ui/icon "bulb" {:style {:font-size 20}})]]]
-
-            (when label
-              [:li
-               {:key text
-                :class    (util/classnames [{:active (= label @*active)}])
-                :on-click #(reset! *active label)}
-
-               [:a.flex.items-center
-                icon
-                [:strong text]]]))]]
-
-        [:article
-
-         (case @*active
-
-           :general
-           [:div.panel-wrap.is-general
-            (when-not (mobile-util/is-native-platform?)
-              (version-row t version))
-            (language-row t preferred-language)
-            (theme-modes-row t switch-theme system-theme? dark?)
-            (when current-repo (edit-config-edn))
-            (when current-repo (edit-custom-css))
-            (keyboard-shortcuts-row t)]
-
-           :editor
-           [:div.panel-wrap.is-editor
-            (file-format-row t preferred-format)
-            (date-format-row t preferred-date-format)
-            (workflow-row t preferred-workflow)
-            ;; (enable-block-timestamps-row t enable-block-timestamps?)
-            (show-brackets-row t show-brackets?)
-            (when (util/electron?) (switch-spell-check-row t))
-            (outdenting-row t logical-outdenting?)
-            (when-not (or (util/mobile?) (mobile-util/is-native-platform?))
-              (shortcut-tooltip-row t enable-shortcut-tooltip?)
-              (tooltip-row t enable-tooltip?))
-            (timetracking-row t enable-timetracking?)
-            (journal-row t enable-journals?)
-            (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?)]
-
-           :git
-           [:div.panel-wrap
-            [:div.text-sm.my-4
-             [:span.text-sm.opacity-50.my-4
-              "You can view a page's edit history by clicking the three vertical dots "
-              "in the top-right corner and selecting \"Check page's history\". "
-              "Logseq uses "]
-             [:a {:href "https://git-scm.com/" :target "_blank"}
-              "Git"]
-             [:span.text-sm.opacity-50.my-4
-              " for version control."]]
-            [:br]
-            (switch-git-auto-commit-row t)
-            (git-auto-commit-seconds t)
-
-            (ui/admonition
-             :warning
-             [:p (t :settings-page/git-confirm)])]
-
-           :advanced
-           [:div.panel-wrap.is-advanced
-            (when (and util/mac? (util/electron?)) (app-auto-update-row t))
-            (usage-diagnostics-row t instrument-disabled?)
-            (when-not (mobile-util/is-native-platform?) (developer-mode-row t developer-mode?))
-            (when (util/electron?) (plugin-system-switcher-row t))
-            (clear-cache-row t)
-
-            (ui/admonition
-             :warning
-             [:p "Clearing the cache will discard open graphs. You will lose unsaved changes."])
-
-            (when logged?
-              [:div
-               [:div.mt-6.sm:mt-5.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center.sm:pt-5
-                [:label.block.text-sm.font-medium.leading-5.sm:mt-px..opacity-70
-                 {:for "cors"}
-                 (t :settings-page/custom-cors-proxy-server)]
-                [:div.mt-1.sm:mt-0.sm:col-span-2
-                 [:div.max-w-lg.rounded-md.sm:max-w-xs
-                  [:input#pat.form-input.is-small.transition.duration-150.ease-in-out
-                   {:default-value cors-proxy
-                    :on-blur       (fn [event]
-                                     (when-let [server (util/evalue event)]
-                                       (user-handler/set-cors! server)
-                                       (notification/show! "Custom CORS proxy updated successfully!" :success)))
-                    :on-key-press  (fn [event]
-                                     (let [k (gobj/get event "key")]
-                                       (when (= "Enter" k)
-                                         (when-let [server (util/evalue event)]
-                                           (user-handler/set-cors! server)
-                                           (notification/show! "Custom CORS proxy updated successfully!" :success)))))}]]]]
-               (ui/admonition
-                :important
-                [:p (t :settings-page/dont-use-other-peoples-proxy-servers)
-                 [:a {:href   "https://github.com/isomorphic-git/cors-proxy"
-                      :target "_blank"}
-                  "https://github.com/isomorphic-git/cors-proxy"]])])
-
-            (when logged?
-              [:div
-               [:hr]
-               [:div.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center.sm:pt-5
-                [:label.block.text-sm.font-medium.leading-5.opacity-70.text-red-600.dark:text-red-400
-                 {:for "delete account"}
-                 (t :user/delete-account)]
-                [:div.mt-1.sm:mt-0.sm:col-span-2
-                 [:div.max-w-lg.rounded-md.sm:max-w-xs
-                  (ui/button (t :user/delete-your-account)
-                             :on-click (fn []
-                                         (ui-handler/toggle-settings-modal!)
-                                         (js/setTimeout #(state/set-modal! delete-account-confirm))))]]]])]
-
-           nil)]]])))
+    [:div#settings.cp__settings-main
+     [:header
+      [:h1.title (t :settings)]]
+
+     [:div.cp__settings-inner.md:flex
+
+      [:aside.md:w-64 {:style {:min-width "10rem"}}
+       [:ul
+        (for [[label text icon]
+              [[:general (t :settings-page/tab-general) (ui/icon "adjustments" {:style {:font-size 20}})]
+               [:editor (t :settings-page/tab-editor) (ui/icon "writing" {:style {:font-size 20}})]
+               (when-not (mobile-util/is-native-platform?)
+                 [:git (t :settings-page/tab-version-control) (ui/icon "history" {:style {:font-size 20}})])
+               [:advanced (t :settings-page/tab-advanced) (ui/icon "bulb" {:style {:font-size 20}})]]]
+
+          (when label
+            [:li
+             {:key text
+              :class    (util/classnames [{:active (= label @*active)}])
+              :on-click #(reset! *active label)}
+
+             [:a.flex.items-center
+              icon
+              [:strong text]]]))]]
+
+      [:article
+
+       (case @*active
+
+         :general
+         [:div.panel-wrap.is-general
+          (when-not (mobile-util/is-native-platform?)
+            (version-row t version))
+          (language-row t preferred-language)
+          (theme-modes-row t switch-theme system-theme? dark?)
+          (when current-repo (edit-config-edn))
+          (when current-repo (edit-custom-css))
+          (keyboard-shortcuts-row t)]
+
+         :editor
+         [:div.panel-wrap.is-editor
+          (file-format-row t preferred-format)
+          (date-format-row t preferred-date-format)
+          (workflow-row t preferred-workflow)
+          ;; (enable-block-timestamps-row t enable-block-timestamps?)
+          (show-brackets-row t show-brackets?)
+          (when (util/electron?) (switch-spell-check-row t))
+          (outdenting-row t logical-outdenting?)
+          (when-not (or (util/mobile?) (mobile-util/is-native-platform?))
+            (shortcut-tooltip-row t enable-shortcut-tooltip?)
+            (tooltip-row t enable-tooltip?))
+          (timetracking-row t enable-timetracking?)
+          (journal-row t enable-journals?)
+          (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?)]
+
+         :git
+         [:div.panel-wrap
+          [:div.text-sm.my-4
+           [:span.text-sm.opacity-50.my-4
+            "You can view a page's edit history by clicking the three vertical dots "
+            "in the top-right corner and selecting \"Check page's history\". "
+            "Logseq uses "]
+           [:a {:href "https://git-scm.com/" :target "_blank"}
+            "Git"]
+           [:span.text-sm.opacity-50.my-4
+            " for version control."]]
+          [:br]
+          (switch-git-auto-commit-row t)
+          (git-auto-commit-seconds t)
+
+          (ui/admonition
+           :warning
+           [:p (t :settings-page/git-confirm)])]
+
+         :advanced
+         [:div.panel-wrap.is-advanced
+          (when (and util/mac? (util/electron?)) (app-auto-update-row t))
+          (usage-diagnostics-row t instrument-disabled?)
+          (when-not (mobile-util/is-native-platform?) (developer-mode-row t developer-mode?))
+          (when (util/electron?) (plugin-system-switcher-row t))
+          (clear-cache-row t)
+
+          (ui/admonition
+           :warning
+           [:p "Clearing the cache will discard open graphs. You will lose unsaved changes."])
+
+          (when logged?
+            [:div
+             [:div.mt-6.sm:mt-5.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center.sm:pt-5
+              [:label.block.text-sm.font-medium.leading-5.sm:mt-px..opacity-70
+               {:for "cors"}
+               (t :settings-page/custom-cors-proxy-server)]
+              [:div.mt-1.sm:mt-0.sm:col-span-2
+               [:div.max-w-lg.rounded-md.sm:max-w-xs
+                [:input#pat.form-input.is-small.transition.duration-150.ease-in-out
+                 {:default-value cors-proxy
+                  :on-blur       (fn [event]
+                                   (when-let [server (util/evalue event)]
+                                     (user-handler/set-cors! server)
+                                     (notification/show! "Custom CORS proxy updated successfully!" :success)))
+                  :on-key-press  (fn [event]
+                                   (let [k (gobj/get event "key")]
+                                     (when (= "Enter" k)
+                                       (when-let [server (util/evalue event)]
+                                         (user-handler/set-cors! server)
+                                         (notification/show! "Custom CORS proxy updated successfully!" :success)))))}]]]]
+             (ui/admonition
+              :important
+              [:p (t :settings-page/dont-use-other-peoples-proxy-servers)
+               [:a {:href   "https://github.com/isomorphic-git/cors-proxy"
+                    :target "_blank"}
+                "https://github.com/isomorphic-git/cors-proxy"]])])
+
+          (when logged?
+            [:div
+             [:hr]
+             [:div.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center.sm:pt-5
+              [:label.block.text-sm.font-medium.leading-5.opacity-70.text-red-600.dark:text-red-400
+               {:for "delete account"}
+               (t :user/delete-account)]
+              [:div.mt-1.sm:mt-0.sm:col-span-2
+               [:div.max-w-lg.rounded-md.sm:max-w-xs
+                (ui/button (t :user/delete-your-account)
+                  :on-click (fn []
+                              (ui-handler/toggle-settings-modal!)
+                              (js/setTimeout #(state/set-modal! delete-account-confirm))))]]]])]
+
+         nil)]]]))

+ 98 - 102
src/main/frontend/components/shortcut.cljs

@@ -1,6 +1,6 @@
 (ns frontend.components.shortcut
   (:require [clojure.string :as str]
-            [frontend.context.i18n :as i18n]
+            [frontend.context.i18n :refer [t]]
             [frontend.modules.shortcut.core :as shortcut]
             [frontend.modules.shortcut.data-helper :as dh]
             [frontend.state :as state]
@@ -69,117 +69,113 @@
                           state/state
                           [:config (state/get-current-repo) :shortcuts])
          _ (rum/react shortcut-config)]
-     (rum/with-context [[t] i18n/*tongue-context*]
-       [:div
-        [:table
-         [:thead
-          [:tr
-           [:th.text-left [:b (t name)]]
-           [:th.text-right]]]
-         [:tbody
-          (map (fn [[k {:keys [binding]}]]
-                 [:tr {:key k}
-                  [:td.text-left (t (dh/decorate-namespace k))]
-                  (shortcut-col k binding configurable? (t (dh/decorate-namespace k)))])
-               (dh/binding-by-category name))]]]))))
+     [:div
+      [:table
+       [:thead
+        [:tr
+         [:th.text-left [:b (t name)]]
+         [:th.text-right]]]
+       [:tbody
+        (map (fn [[k {:keys [binding]}]]
+               [:tr {:key k}
+                [:td.text-left (t (dh/decorate-namespace k))]
+                (shortcut-col k binding configurable? (t (dh/decorate-namespace k)))])
+          (dh/binding-by-category name))]]])))
 
 (rum/defc trigger-table []
-  (rum/with-context [[t] i18n/*tongue-context*]
-    [:table
-     [:thead
-      [:tr
-       [:th.text-left [:b (t :help/shortcuts-triggers)]]
-       [:th.text-right [:b (t :help/shortcut)]]]]
-     [:tbody
-      [:tr
-       [:td.text-left (t :help/slash-autocomplete)]
-       [:td.text-right [:code "/"]]]
-      [:tr
-       [:td.text-left (t :help/block-content-autocomplete)]
-       [:td.text-right [:code "<"]]]
-      [:tr
-       [:td.text-left (t :help/reference-autocomplete)]
-       [:td.text-right [:code "[[]]"]]]
-      [:tr
-       [:td.text-left (t :help/block-reference)]
-       [:td.text-right [:code "(())"]]]
-      [:tr
-       [:td.text-left (t :command.editor/open-link-in-sidebar)]
-       [:td.text-right (ui/render-keyboard-shortcut ["shift" "click"])]]
-      [:tr
-       [:td.text-left (t :help/context-menu)]
-       [:td.text-right (ui/render-keyboard-shortcut ["right" "click"])]]]]))
+  [:table
+   [:thead
+    [:tr
+     [:th.text-left [:b (t :help/shortcuts-triggers)]]
+     [:th.text-right [:b (t :help/shortcut)]]]]
+   [:tbody
+    [:tr
+     [:td.text-left (t :help/slash-autocomplete)]
+     [:td.text-right [:code "/"]]]
+    [:tr
+     [:td.text-left (t :help/block-content-autocomplete)]
+     [:td.text-right [:code "<"]]]
+    [:tr
+     [:td.text-left (t :help/reference-autocomplete)]
+     [:td.text-right [:code "[[]]"]]]
+    [:tr
+     [:td.text-left (t :help/block-reference)]
+     [:td.text-right [:code "(())"]]]
+    [:tr
+     [:td.text-left (t :command.editor/open-link-in-sidebar)]
+     [:td.text-right (ui/render-keyboard-shortcut ["shift" "click"])]]
+    [:tr
+     [:td.text-left (t :help/context-menu)]
+     [:td.text-right (ui/render-keyboard-shortcut ["right" "click"])]]]])
 
 (defn markdown-and-orgmode-syntax []
-  (rum/with-context [[t] i18n/*tongue-context*]
-    (let [list [:bold :italics :del :mark :latex :code :link :pre :img]
+  (let [list [:bold :italics :del :mark :latex :code :link :pre :img]
 
-          preferred-format (state/get-preferred-format) ; markdown/org
+        preferred-format (state/get-preferred-format) ; markdown/org
 
-          title (case preferred-format
-                  :markdown (t :help/markdown-syntax)
-                  :org (t :help/org-mode-syntax))
+        title (case preferred-format
+                :markdown (t :help/markdown-syntax)
+                :org (t :help/org-mode-syntax))
 
-          learn-more (case preferred-format
-                       :markdown "https://www.markdownguide.org/basic-syntax"
-                       :org "https://orgmode.org/worg/dev/org-syntax.html")
+        learn-more (case preferred-format
+                     :markdown "https://www.markdownguide.org/basic-syntax"
+                     :org "https://orgmode.org/worg/dev/org-syntax.html")
 
-          raw (case preferred-format
-                :markdown {:bold (str "**" (t :bold) "**")
-                           :italics (str "_" (t :italics) "_")
-                           :link "[Link](https://www.example.com)"
-                           :del (str "~~" (t :strikethrough) "~~")
-                           :mark (str "^^" (t :highlight) "^^")
-                           :latex "$$E = mc^2$$"
-                           :code (str "`" (t :code) "`")
-                           :pre "```clojure\n  (println \"Hello world!\")\n```"
-                           :img "![image](https://asset.logseq.com/static/img/logo.png)"}
-                :org {:bold (str "*" (t :bold) "*")
-                      :italic (str "/" (t :italics) "/")
-                      :del (str "+" (t :strikethrough) "+")
-                      :pre [:pre "#+BEGIN_SRC clojure\n  (println \"Hello world!\")\n#+END_SRC"]
-                      :link "[[https://www.example.com][Link]]"
-                      :mark (str "^^" (t :highlight) "^^")
-                      :latex "$$E = mc^2$$"
-                      :code "~Code~"
-                      :img "[[https://asset.logseq.com/static/img/logo.png][image]]"})
+        raw (case preferred-format
+              :markdown {:bold (str "**" (t :bold) "**")
+                         :italics (str "_" (t :italics) "_")
+                         :link "[Link](https://www.example.com)"
+                         :del (str "~~" (t :strikethrough) "~~")
+                         :mark (str "^^" (t :highlight) "^^")
+                         :latex "$$E = mc^2$$"
+                         :code (str "`" (t :code) "`")
+                         :pre "```clojure\n  (println \"Hello world!\")\n```"
+                         :img "![image](https://asset.logseq.com/static/img/logo.png)"}
+              :org {:bold (str "*" (t :bold) "*")
+                    :italic (str "/" (t :italics) "/")
+                    :del (str "+" (t :strikethrough) "+")
+                    :pre [:pre "#+BEGIN_SRC clojure\n  (println \"Hello world!\")\n#+END_SRC"]
+                    :link "[[https://www.example.com][Link]]"
+                    :mark (str "^^" (t :highlight) "^^")
+                    :latex "$$E = mc^2$$"
+                    :code "~Code~"
+                    :img "[[https://asset.logseq.com/static/img/logo.png][image]]"})
 
-          rendered {:italics [:i (t :italics)]
-                    :bold [:b (t :bold)]
-                    :link [:a {:href "https://www.example.com"} "Link"]
-                    :del [:del (t :strikethrough)]
-                    :mark [:mark (t :highlight)]
-                    :latex (latex/latex "help-latex" "E = mc^2" true false)
-                    :code [:code (t :code)]
-                    :pre (highlight/highlight "help-highlight" {:data-lang "clojure"} "(println \"Hello world!\")")
-                    :img [:img {:style {:float "right" :width 32 :height 32}
-                                :src "https://asset.logseq.com/static/img/logo.png"
-                                :alt "image"}]}]
+        rendered {:italics [:i (t :italics)]
+                  :bold [:b (t :bold)]
+                  :link [:a {:href "https://www.example.com"} "Link"]
+                  :del [:del (t :strikethrough)]
+                  :mark [:mark (t :highlight)]
+                  :latex (latex/latex "help-latex" "E = mc^2" true false)
+                  :code [:code (t :code)]
+                  :pre (highlight/highlight "help-highlight" {:data-lang "clojure"} "(println \"Hello world!\")")
+                  :img [:img {:style {:float "right" :width 32 :height 32}
+                              :src "https://asset.logseq.com/static/img/logo.png"
+                              :alt "image"}]}]
 
-      [:table
-       [:thead
-        [:tr
-         [:th.text-left [:b title]]
-         [:th.text-right [:a {:href learn-more} "Learn more →"]]]]
-       [:tbody
-        (map (fn [name]
-               [:tr
-                [:td.text-left [(if (= :pre name) :pre :code) (get raw name)]]
-                [:td.text-right (get rendered name)]])
-             list)]])))
+    [:table
+     [:thead
+      [:tr
+       [:th.text-left [:b title]]
+       [:th.text-right [:a {:href learn-more} "Learn more →"]]]]
+     [:tbody
+      (map (fn [name]
+             [:tr
+              [:td.text-left [(if (= :pre name) :pre :code) (get raw name)]]
+              [:td.text-right (get rendered name)]])
+        list)]]))
 
 (rum/defc shortcut
   []
-  (rum/with-context [[t] i18n/*tongue-context*]
-    [:div
-     [:h1.title (t :help/shortcut-page-title)]
-     (trigger-table)
-     (markdown-and-orgmode-syntax)
-     (shortcut-table :shortcut.category/basics true)
-     (shortcut-table :shortcut.category/navigating true)
-     (shortcut-table :shortcut.category/block-editing true)
-     (shortcut-table :shortcut.category/block-command-editing true)
-     (shortcut-table :shortcut.category/block-selection true)
-     (shortcut-table :shortcut.category/formatting true)
-     (shortcut-table :shortcut.category/toggle true)
-     (shortcut-table :shortcut.category/others true)]))
+  [:div
+   [:h1.title (t :help/shortcut-page-title)]
+   (trigger-table)
+   (markdown-and-orgmode-syntax)
+   (shortcut-table :shortcut.category/basics true)
+   (shortcut-table :shortcut.category/navigating true)
+   (shortcut-table :shortcut.category/block-editing true)
+   (shortcut-table :shortcut.category/block-command-editing true)
+   (shortcut-table :shortcut.category/block-selection true)
+   (shortcut-table :shortcut.category/formatting true)
+   (shortcut-table :shortcut.category/toggle true)
+   (shortcut-table :shortcut.category/others true)])

+ 186 - 194
src/main/frontend/components/sidebar.cljs

@@ -12,7 +12,7 @@
             [frontend.components.plugins :as plugins]
             [frontend.components.select :as select]
             [frontend.config :as config]
-            [frontend.context.i18n :as i18n]
+            [frontend.context.i18n :refer [t]]
             [frontend.db :as db]
             [frontend.db.model :as db-model]
             [frontend.components.svg :as svg]
@@ -168,12 +168,11 @@
                 state)}
   [state]
   (let [num (state/sub :srs/cards-due-count)]
-    (rum/with-context [[t] i18n/*tongue-context*]
-      [:a.item.group.flex.items-center.px-2.py-2.text-sm.font-medium.rounded-md {:on-click #(state/pub-event! [:modal/show-cards])}
-       (ui/icon "infinity mr-3" {:style {:font-size 20}})
-       [:span.flex-1 (t :right-side-bar/flashcards)]
-       (when (and num (not (zero? num)))
-         [:span.ml-3.inline-block.py-0.5.px-3.text-xs.font-medium.rounded-full.fade-in num])])))
+    [:a.item.group.flex.items-center.px-2.py-2.text-sm.font-medium.rounded-md {:on-click #(state/pub-event! [:modal/show-cards])}
+     (ui/icon "infinity mr-3" {:style {:font-size 20}})
+     [:span.flex-1 (t :right-side-bar/flashcards)]
+     (when (and num (not (zero? num)))
+       [:span.ml-3.inline-block.py-0.5.px-3.text-xs.font-medium.rounded-full.fade-in num])]))
 
 (defn get-default-home-if-valid
   []
@@ -202,61 +201,60 @@
 
 (rum/defc sidebar-nav
   [_route-match close-modal-fn]
-  (rum/with-context [[t] i18n/*tongue-context*]
-    (let [default-home (get-default-home-if-valid)]
-
-      [:div.left-sidebar-inner.flex-1.flex.flex-col.min-h-0
-       {:on-click #(when-let [^js target (and (util/sm-breakpoint?) (.-target %))]
-                     (when (some (fn [sel] (boolean (.closest target sel)))
-                                 [".favorites .bd" ".recent .bd" ".dropdown-wrapper" ".nav-header"])
-                       (close-modal-fn)))}
-       [:div.flex.flex-col.pb-4.wrap
-        [:nav.px-2.space-y-1 {:aria-label "Sidebar"}
-         (repo/repos-dropdown)
-
-         [:div.nav-header
-
-          (if (:page default-home)
-            (sidebar-item
-              {:class            "home-nav"
-               :title            (:page default-home)
-               :on-click-handler route-handler/redirect-to-home!
-               :icon             "home"})
-            (sidebar-item
-              {:class            "journals-nav"
-               :title            (t :right-side-bar/journals)
-               :on-click-handler route-handler/go-to-journals!
-               :icon             "calendar"}))
-
-          [:div.flashcards-nav
-           (flashcards)]
+  (let [default-home (get-default-home-if-valid)]
 
-          (sidebar-item
-            {:class "graph-view-nav"
-             :title (t :right-side-bar/graph-view)
-             :href  (rfe/href :graph)
-             :icon  "hierarchy"})
+    [:div.left-sidebar-inner.flex-1.flex.flex-col.min-h-0
+     {:on-click #(when-let [^js target (and (util/sm-breakpoint?) (.-target %))]
+                   (when (some (fn [sel] (boolean (.closest target sel)))
+                               [".favorites .bd" ".recent .bd" ".dropdown-wrapper" ".nav-header"])
+                     (close-modal-fn)))}
+     [:div.flex.flex-col.pb-4.wrap
+      [:nav.px-2.space-y-1 {:aria-label "Sidebar"}
+       (repo/repos-dropdown)
+
+       [:div.nav-header
 
+        (if (:page default-home)
           (sidebar-item
-            {:class "all-pages-nav"
-             :title (t :right-side-bar/all-pages)
-             :href  (rfe/href :all-pages)
-             :icon  "files"})]]
-
-        (favorites t)
-
-        (recent-pages t)
-
-        [:nav.px-2 {:aria-label "Sidebar"
-                    :class      "new-page"}
-         (when-not config/publishing?
-           [:a.item.group.flex.items-center.px-2.py-2.text-sm.font-medium.rounded-md
-            {:on-click (fn []
-                         (and (util/sm-breakpoint?)
-                              (state/toggle-left-sidebar!))
-                         (state/pub-event! [:go/search]))}
-            (ui/icon "circle-plus mr-3" {:style {:font-size 20}})
-            [:span.flex-1 (t :right-side-bar/new-page)]])]]])))
+           {:class            "home-nav"
+            :title            (:page default-home)
+            :on-click-handler route-handler/redirect-to-home!
+            :icon             "home"})
+          (sidebar-item
+           {:class            "journals-nav"
+            :title            (t :right-side-bar/journals)
+            :on-click-handler route-handler/go-to-journals!
+            :icon             "calendar"}))
+
+        [:div.flashcards-nav
+         (flashcards)]
+
+        (sidebar-item
+         {:class "graph-view-nav"
+          :title (t :right-side-bar/graph-view)
+          :href  (rfe/href :graph)
+          :icon  "hierarchy"})
+
+        (sidebar-item
+         {:class "all-pages-nav"
+          :title (t :right-side-bar/all-pages)
+          :href  (rfe/href :all-pages)
+          :icon  "files"})]]
+
+      (favorites t)
+
+      (recent-pages t)
+
+      [:nav.px-2 {:aria-label "Sidebar"
+                  :class      "new-page"}
+       (when-not config/publishing?
+         [:a.item.group.flex.items-center.px-2.py-2.text-sm.font-medium.rounded-md
+          {:on-click (fn []
+                       (and (util/sm-breakpoint?)
+                            (state/toggle-left-sidebar!))
+                       (state/pub-event! [:go/search]))}
+          (ui/icon "circle-plus mr-3" {:style {:font-size 20}})
+          [:span.flex-1 (t :right-side-bar/new-page)]])]]]))
 
 (rum/defc left-sidebar < rum/reactive
   [{:keys [left-sidebar-open? route-match]}]
@@ -282,39 +280,38 @@
   [{:keys [route-match global-graph-pages? route-name indexeddb-support? db-restoring? main-content]}]
 
   (let [left-sidebar-open? (state/sub :ui/left-sidebar-open?)]
-    (rum/with-context [[t] i18n/*tongue-context*]
-      [:div#main-container.cp__sidebar-main-layout.flex-1.flex
-       {:class (util/classnames [{:is-left-sidebar-open left-sidebar-open?}])}
+    [:div#main-container.cp__sidebar-main-layout.flex-1.flex
+     {:class (util/classnames [{:is-left-sidebar-open left-sidebar-open?}])}
 
-       ;; desktop left sidebar layout
-       (left-sidebar {:left-sidebar-open? left-sidebar-open?
-                      :route-match route-match})
+     ;; desktop left sidebar layout
+     (left-sidebar {:left-sidebar-open? left-sidebar-open?
+                    :route-match route-match})
 
-       [:div#main-content-container.scrollbar-spacing.w-full.flex.justify-center
-        [:div.cp__sidebar-main-content
-         {:data-is-global-graph-pages global-graph-pages?
-          :data-is-full-width         (or global-graph-pages?
-                                          (contains? #{:all-files :all-pages :my-publishing} route-name))}
+     [:div#main-content-container.scrollbar-spacing.w-full.flex.justify-center
+      [:div.cp__sidebar-main-content
+       {:data-is-global-graph-pages global-graph-pages?
+        :data-is-full-width         (or global-graph-pages?
+                                        (contains? #{:all-files :all-pages :my-publishing} route-name))}
 
-         (when-not (mobile-util/is-native-platform?)
-           (widgets/demo-graph-alert))
+       (when-not (mobile-util/is-native-platform?)
+         (widgets/demo-graph-alert))
 
-         (widgets/github-integration-soon-deprecated-alert)
+       (widgets/github-integration-soon-deprecated-alert)
 
-         (cond
-           (not indexeddb-support?)
-           nil
+       (cond
+         (not indexeddb-support?)
+         nil
 
-           db-restoring?
-           [:div.mt-20
-            [:div.ls-center
-             (ui/loading (t :loading))]]
+         db-restoring?
+         [:div.mt-20
+          [:div.ls-center
+           (ui/loading (t :loading))]]
 
-           :else
-           [:div {:class (if global-graph-pages? "" (util/hiccup->class "max-w-7xl.mx-auto.pb-24"))
-                  :style {:margin-bottom (if global-graph-pages? 0 120)
-                          :padding-bottom (when (mobile-util/native-iphone?) "7rem")}}
-            main-content])]]])))
+         :else
+         [:div {:class (if global-graph-pages? "" (util/hiccup->class "max-w-7xl.mx-auto.pb-24"))
+                :style {:margin-bottom (if global-graph-pages? 0 120)
+                        :padding-bottom (when (mobile-util/native-iphone?) "7rem")}}
+          main-content])]]]))
 
 (rum/defc footer
   []
@@ -344,7 +341,7 @@
   []
   (let [cloning? (state/sub :repo/cloning?)
         default-home (get-default-home-if-valid)
-        importing-to-db? (state/sub :repo/importing-to-db?)
+        parsing? (state/sub :repo/parsing-files?)
         current-repo (state/sub :git/current-repo)
         loading-files? (when current-repo (state/sub [:repo/loading-files? current-repo]))
         me (state/sub :me)
@@ -352,51 +349,50 @@
         latest-journals (db/get-latest-journals (state/get-current-repo) journals-length)
         preferred-format (state/sub [:me :preferred_format])
         logged? (:name me)]
-    (rum/with-context [[t] i18n/*tongue-context*]
-      [:div
-       (cond
-         (and default-home
-              (= :home (state/get-current-route))
-              (not (state/route-has-p?))
-              (:page default-home))
-         (route-handler/redirect-to-page! (:page default-home))
+    [:div
+     (cond
+       (and default-home
+            (= :home (state/get-current-route))
+            (not (state/route-has-p?))
+            (:page default-home))
+       (route-handler/redirect-to-page! (:page default-home))
 
-         (and config/publishing?
-              (not default-home)
-              (empty? latest-journals))
-         (route-handler/redirect! {:to :all-pages})
+       (and config/publishing?
+            (not default-home)
+            (empty? latest-journals))
+       (route-handler/redirect! {:to :all-pages})
 
-         importing-to-db?
-         (ui/loading (t :parsing-files))
+       parsing?
+       (ui/loading (t :parsing-files))
 
-         loading-files?
-         (ui/loading (t :loading-files))
+       loading-files?
+       (ui/loading (t :loading-files))
 
-         (and (not logged?) (seq latest-journals))
-         (journal/journals latest-journals)
+       (and (not logged?) latest-journals)
+       (journal/journals latest-journals)
 
-         (and logged? (not preferred-format))
-         (widgets/choose-preferred-format)
+       (and logged? (not preferred-format))
+       (widgets/choose-preferred-format)
 
-                         ;; TODO: delay this
-         (and logged? (nil? (:email me)))
-         (settings/set-email)
+       ;; TODO: delay this
+       (and logged? (nil? (:email me)))
+       (settings/set-email)
 
-         cloning?
-         (ui/loading (t :cloning))
+       cloning?
+       (ui/loading (t :cloning))
 
-         (seq latest-journals)
-         (journal/journals latest-journals)
+       (seq latest-journals)
+       (journal/journals latest-journals)
 
-         (or
-          (and (mobile-util/is-native-platform?)
-               (nil? (state/get-current-repo)))
-          (and logged? (empty? (:repos me))))
-         (widgets/add-graph)
+       (or
+        (and (mobile-util/is-native-platform?)
+             (nil? (state/get-current-repo)))
+        (and logged? (empty? (:repos me))))
+       (widgets/add-graph)
 
-                         ;; FIXME: why will this happen?
-         :else
-         [:div])])))
+       ;; FIXME: why will this happen?
+       :else
+       [:div])]))
 
 (rum/defc custom-context-menu < rum/reactive
   []
@@ -406,9 +402,7 @@
        {:class-names "fade"
         :timeout {:enter 500
                   :exit 300}}
-       links
-        ;; (custom-context-menu-content)
-       ))))
+       links))))
 
 (rum/defc new-block-mode < rum/reactive
   []
@@ -431,12 +425,11 @@
   []
   (when-not (state/sub :ui/sidebar-open?)
     ;; TODO: remove with-context usage
-    (rum/with-context [[t] i18n/*tongue-context*]
-      [:div.cp__sidebar-help-btn
-       {:title (t :help-shortcut-title)
-        :on-click (fn []
-                    (state/sidebar-add-block! (state/get-current-repo) "help" :help nil))}
-       "?"])))
+    [:div.cp__sidebar-help-btn
+     {:title (t :help-shortcut-title)
+      :on-click (fn []
+                  (state/sidebar-add-block! (state/get-current-repo) "help" :help nil))}
+     "?"]))
 
 (defn- hide-context-menu-and-clear-selection
   [e]
@@ -480,67 +473,66 @@
         home? (= :home route-name)
         edit? (:editor/editing? @state/state)
         default-home (get-default-home-if-valid)]
-    (rum/with-context [[t] i18n/*tongue-context*]
-      (theme/container
-       {:t             t
-        :theme         theme
-        :route         route-match
-        :current-repo  current-repo
-        :edit?         edit?
-        :nfs-granted?  granted?
-        :db-restoring? db-restoring?
-        :sidebar-open? sidebar-open?
-        :settings-open? settings-open?
-        :sidebar-blocks-len (count right-sidebar-blocks)
-        :system-theme? system-theme?
-        :on-click      (fn [e]
-                         (editor-handler/unhighlight-blocks!)
-                         (util/fix-open-external-with-shift! e))}
-
-       [:div.theme-inner
-        {:class (util/classnames [{:ls-left-sidebar-open left-sidebar-open?
-                                   :ls-right-sidebar-open sidebar-open?}])}
-
-        [:div.#app-container
-         [:div#left-container
-          {:class (if (state/sub :ui/sidebar-open?) "overflow-hidden" "w-full")}
-          (header/header {:open-fn        open-fn
-                          :white?         white?
-                          :current-repo   current-repo
-                          :logged?        logged?
-                          :page?          page?
-                          :route-match    route-match
-                          :me             me
-                          :default-home   default-home
-                          :new-block-mode new-block-mode})
-
-          (main {:route-match         route-match
-                 :global-graph-pages? global-graph-pages?
-                 :logged?             logged?
-                 :home?               home?
-                 :route-name          route-name
-                 :indexeddb-support?  indexeddb-support?
-                 :white?              white?
-                 :db-restoring?       db-restoring?
-                 :main-content        main-content})
-
-          (footer)]
-         (right-sidebar/sidebar)
-
-         [:div#app-single-container]]
-
-        (ui/notification)
-        (ui/modal)
-        (ui/sub-modal)
-        (command-palette/command-palette-modal)
-        (select/select-modal)
-        (custom-context-menu)
-        (plugins/custom-js-installer {:t t
-                                      :current-repo current-repo
-                                      :nfs-granted? granted?
-                                      :db-restoring? db-restoring?})
-        [:a#download.hidden]
-        (when
-         (and (not config/mobile?)
-              (not config/publishing?))
-          (help-button))]))))
+    (theme/container
+     {:t             t
+      :theme         theme
+      :route         route-match
+      :current-repo  current-repo
+      :edit?         edit?
+      :nfs-granted?  granted?
+      :db-restoring? db-restoring?
+      :sidebar-open? sidebar-open?
+      :settings-open? settings-open?
+      :sidebar-blocks-len (count right-sidebar-blocks)
+      :system-theme? system-theme?
+      :on-click      (fn [e]
+                       (editor-handler/unhighlight-blocks!)
+                       (util/fix-open-external-with-shift! e))}
+
+     [:div.theme-inner
+      {:class (util/classnames [{:ls-left-sidebar-open left-sidebar-open?
+                                 :ls-right-sidebar-open sidebar-open?}])}
+
+      [:div.#app-container
+       [:div#left-container
+        {:class (if (state/sub :ui/sidebar-open?) "overflow-hidden" "w-full")}
+        (header/header {:open-fn        open-fn
+                        :white?         white?
+                        :current-repo   current-repo
+                        :logged?        logged?
+                        :page?          page?
+                        :route-match    route-match
+                        :me             me
+                        :default-home   default-home
+                        :new-block-mode new-block-mode})
+
+        (main {:route-match         route-match
+               :global-graph-pages? global-graph-pages?
+               :logged?             logged?
+               :home?               home?
+               :route-name          route-name
+               :indexeddb-support?  indexeddb-support?
+               :white?              white?
+               :db-restoring?       db-restoring?
+               :main-content        main-content})
+
+        (footer)]
+       (right-sidebar/sidebar)
+
+       [:div#app-single-container]]
+
+      (ui/notification)
+      (ui/modal)
+      (ui/sub-modal)
+      (command-palette/command-palette-modal)
+      (select/select-modal)
+      (custom-context-menu)
+      (plugins/custom-js-installer {:t t
+                                    :current-repo current-repo
+                                    :nfs-granted? granted?
+                                    :db-restoring? db-restoring?})
+      [:a#download.hidden]
+      (when
+          (and (not config/mobile?)
+               (not config/publishing?))
+        (help-button))])))

+ 123 - 129
src/main/frontend/components/widgets.cljs

@@ -1,6 +1,6 @@
 (ns frontend.components.widgets
   (:require [clojure.string :as string]
-            [frontend.context.i18n :as i18n]
+            [frontend.context.i18n :refer [t]]
             [frontend.handler.notification :as notification]
             [frontend.handler.page :as page-handler]
             [frontend.handler.repo :as repo-handler]
@@ -16,23 +16,22 @@
 
 (rum/defc choose-preferred-format
   []
-  (rum/with-context [[t] i18n/*tongue-context*]
-    [:div
-     [:h1.title {:style {:margin-bottom "0.25rem"}}
-      (t :format/preferred-mode)]
+  [:div
+   [:h1.title {:style {:margin-bottom "0.25rem"}}
+    (t :format/preferred-mode)]
 
-     [:div.mt-4.ml-1
-      (ui/button
-       "Markdown"
-       :on-click
-       #(user-handler/set-preferred-format! :markdown))
+   [:div.mt-4.ml-1
+    (ui/button
+      "Markdown"
+      :on-click
+      #(user-handler/set-preferred-format! :markdown))
 
-      [:span.ml-2.mr-2 "-OR-"]
+    [:span.ml-2.mr-2 "-OR-"]
 
-      (ui/button
-       "Org Mode"
-       :on-click
-       #(user-handler/set-preferred-format! :org))]]))
+    (ui/button
+      "Org Mode"
+      :on-click
+      #(user-handler/set-preferred-format! :org))]])
 
 (rum/defcs add-github-repo <
   (rum/local "" ::repo)
@@ -40,124 +39,121 @@
   [state]
   (let [repo (get state ::repo)
         branch (get state ::branch)]
-    (rum/with-context [[t] i18n/*tongue-context*]
-      [:div.flex.flex-col
-       [:div.w-full.mx-auto
-        [:div
-         [:div
-          [:h1.title
-           (t :git/add-repo-prompt)]
-          [:div.mt-4.mb-4.relative.rounded-md.shadow-sm.max-w-xs
-           [:input#repo.form-input.block.w-full.sm:text-sm.sm:leading-5
-            {:autoFocus true
-             :placeholder "https://github.com/username/repo"
-             :on-change (fn [e]
-                          (reset! repo (util/evalue e)))}]]
-          [:label.font-medium "Default Branch (make sure it's matched with your setting on GitHub): "]
-          [:div.mt-2.mb-4.relative.rounded-md.shadow-sm.max-w-xs
-           [:input#branch.form-input.block.w-full.sm:text-sm.sm:leading-5
-            {:value @branch
-             :placeholder "e.g. main"
-             :on-change (fn [e]
-                          (reset! branch (util/evalue e)))}]]]]
+    [:div.flex.flex-col
+     [:div.w-full.mx-auto
+      [:div
+       [:div
+        [:h1.title
+         (t :git/add-repo-prompt)]
+        [:div.mt-4.mb-4.relative.rounded-md.shadow-sm.max-w-xs
+         [:input#repo.form-input.block.w-full.sm:text-sm.sm:leading-5
+          {:autoFocus true
+           :placeholder "https://github.com/username/repo"
+           :on-change (fn [e]
+                        (reset! repo (util/evalue e)))}]]
+        [:label.font-medium "Default Branch (make sure it's matched with your setting on GitHub): "]
+        [:div.mt-2.mb-4.relative.rounded-md.shadow-sm.max-w-xs
+         [:input#branch.form-input.block.w-full.sm:text-sm.sm:leading-5
+          {:value @branch
+           :placeholder "e.g. main"
+           :on-change (fn [e]
+                        (reset! branch (util/evalue e)))}]]]]
 
-        (ui/button
-         (t :git/add-repo-prompt-confirm)
-         :on-click
-         (fn []
-           (let [branch (string/trim @branch)]
-             (if (string/blank? branch)
-               (notification/show!
-                [:p.text-gray-700.dark:text-gray-300 "Please input a branch, make sure it's matched with your setting on GitHub."]
-                :error
-                false)
-               (let [repo (util/lowercase-first @repo)]
-                 (if (util/starts-with? repo "https://github.com/")
-                   (let [repo (string/replace repo ".git" "")]
-                     (repo-handler/create-repo! repo branch))
+      (ui/button
+        (t :git/add-repo-prompt-confirm)
+        :on-click
+        (fn []
+          (let [branch (string/trim @branch)]
+            (if (string/blank? branch)
+              (notification/show!
+               [:p.text-gray-700.dark:text-gray-300 "Please input a branch, make sure it's matched with your setting on GitHub."]
+               :error
+               false)
+              (let [repo (util/lowercase-first @repo)]
+                (if (util/starts-with? repo "https://github.com/")
+                  (let [repo (string/replace repo ".git" "")]
+                    (repo-handler/create-repo! repo branch))
 
-                   (notification/show!
-                    [:p.text-gray-700.dark:text-gray-300 "Please input a valid repo url, e.g. https://github.com/username/repo"]
-                    :error
-                    false)))))))]])))
+                  (notification/show!
+                   [:p.text-gray-700.dark:text-gray-300 "Please input a valid repo url, e.g. https://github.com/username/repo"]
+                   :error
+                   false)))))))]]))
 
 (rum/defc add-local-directory
   []
-  (rum/with-context [[t] i18n/*tongue-context*]
-    [:div.flex.flex-col
-     [:h1.title (t :on-boarding/add-graph)]
-     (let [nfs-supported? (or (nfs/supported?) (mobile-util/is-native-platform?))]
-       (if (mobile-util/is-native-platform?)
-         [:div.text-sm
-          (ui/button "Open a local directory"
-            :on-click #(page-handler/ls-dir-files! shortcut/refresh!))
-          [:hr]
-          [:ol
-           [:li
-            [:div.font-bold.mb-2 "How to sync my notes?"]
-            (if (mobile-util/native-android?)
-              [:div
-               [:p "We're developing our built-in paid Logseq Sync, but you can use any third-party sync service to keep your notes sync with other devices."]
-               [:p "If you prefer to use Dropbox to sync your notes, you can use "
-                [:a {:href "https://play.google.com/store/apps/details?id=com.ttxapps.dropsync"
-                     :target "_blank"}
-                 "Dropsync"]
-                ". Or you can use "
-                [:a {:href "https://play.google.com/store/apps/details?id=dk.tacit.android.foldersync.lite"
-                     :target "_blank"}
-                 "FolderSync"]
-                "."]]
-              [:div
-               [:p "You can sync your graphs by using iCloud. Please choose an existing graph or create a new graph in your iCloud Drive's Logseq directory."]
-               [:p "We're developing our built-in paid Logseq Sync. Stay tuned."]])]
+  [:div.flex.flex-col
+   [:h1.title (t :on-boarding/add-graph)]
+   (let [nfs-supported? (or (nfs/supported?) (mobile-util/is-native-platform?))]
+     (if (mobile-util/is-native-platform?)
+       [:div.text-sm
+        (ui/button "Open a local directory"
+          :on-click #(page-handler/ls-dir-files! shortcut/refresh!))
+        [:hr]
+        [:ol
+         [:li
+          [:div.font-bold.mb-2 "How to sync my notes?"]
+          (if (mobile-util/native-android?)
+            [:div
+             [:p "We're developing our built-in paid Logseq Sync, but you can use any third-party sync service to keep your notes sync with other devices."]
+             [:p "If you prefer to use Dropbox to sync your notes, you can use "
+              [:a {:href "https://play.google.com/store/apps/details?id=com.ttxapps.dropsync"
+                   :target "_blank"}
+               "Dropsync"]
+              ". Or you can use "
+              [:a {:href "https://play.google.com/store/apps/details?id=dk.tacit.android.foldersync.lite"
+                   :target "_blank"}
+               "FolderSync"]
+              "."]]
+            [:div
+             [:p "You can sync your graphs by using iCloud. Please choose an existing graph or create a new graph in your iCloud Drive's Logseq directory."]
+             [:p "We're developing our built-in paid Logseq Sync. Stay tuned."]])]
 
-           [:li.mt-8
-            [:div.font-bold.mb-2 "I need some help"]
-            [:p "👋 Join our discord group to chat with the makers and our helpful community members."]
-            (ui/button "Join the community"
-                       :href "https://discord.gg/KpN4eHY"
-                       :target "_blank")]]]
-         [:div.cp__widgets-open-local-directory
-          [:div.select-file-wrap.cursor
-           (when nfs-supported?
-             {:on-click #(page-handler/ls-dir-files! shortcut/refresh!)})
-           [:div
-            [:h1.title (t :on-boarding/open-local-dir)]
-            [:p (t :on-boarding/new-graph-desc-1)]
-            [:p (t :on-boarding/new-graph-desc-2)]
-            [:ul
-             [:li (t :on-boarding/new-graph-desc-3)]
-             [:li (t :on-boarding/new-graph-desc-4)]
-             [:li (t :on-boarding/new-graph-desc-5)]]
-            (when-not nfs-supported?
-              (ui/admonition :warning
-                             [:p "It seems that your browser doesn't support the "
-                              [:a {:href   "https://web.dev/file-system-access/"
-                                   :target "_blank"}
-                               "new native filesystem API"]
-                              [:span ", please use any Chromium 86+ based browser like Chrome, Vivaldi, Edge, etc. Notice that the API doesn't support mobile browsers at the moment."]]))]]]))]))
+         [:li.mt-8
+          [:div.font-bold.mb-2 "I need some help"]
+          [:p "👋 Join our discord group to chat with the makers and our helpful community members."]
+          (ui/button "Join the community"
+            :href "https://discord.gg/KpN4eHY"
+            :target "_blank")]]]
+       [:div.cp__widgets-open-local-directory
+        [:div.select-file-wrap.cursor
+         (when nfs-supported?
+           {:on-click #(page-handler/ls-dir-files! shortcut/refresh!)})
+         [:div
+          [:h1.title (t :on-boarding/open-local-dir)]
+          [:p (t :on-boarding/new-graph-desc-1)]
+          [:p (t :on-boarding/new-graph-desc-2)]
+          [:ul
+           [:li (t :on-boarding/new-graph-desc-3)]
+           [:li (t :on-boarding/new-graph-desc-4)]
+           [:li (t :on-boarding/new-graph-desc-5)]]
+          (when-not nfs-supported?
+            (ui/admonition :warning
+                           [:p "It seems that your browser doesn't support the "
+                            [:a {:href   "https://web.dev/file-system-access/"
+                                 :target "_blank"}
+                             "new native filesystem API"]
+                            [:span ", please use any Chromium 86+ based browser like Chrome, Vivaldi, Edge, etc. Notice that the API doesn't support mobile browsers at the moment."]]))]]]))])
 
 (rum/defc android-permission-alert
   []
   (when (mobile-util/native-android?)
-    (rum/with-context [[_t] i18n/*tongue-context*]
-      [:div.flex.flex-col
-       [:h1.title "Storage access permission"]
-       [:div.text-sm
-        [:div
-         [:p "Logseq needs the permission to access your device storage. Read "
-          [:a {:href "https://developer.android.com/about/versions/11/privacy/storage#all-files-access"
-               :target "_blank"}
-           "more"]
-          "."]
-         [:div
-          (ui/button "Grant Permission"
-                     :on-click #(page-handler/ls-dir-files! shortcut/refresh!))]
-         [:p.mb-1 "Note:"]
-         [:ol
-          [:li "We will never access files outside of your graph folders you choose."]
-          [:li "If you have granted the permission, you don't need to do it again."]]]
-        [:hr]]])))
+    [:div.flex.flex-col
+     [:h1.title "Storage access permission"]
+     [:div.text-sm
+      [:div
+       [:p "Logseq needs the permission to access your device storage. Read "
+        [:a {:href "https://developer.android.com/about/versions/11/privacy/storage#all-files-access"
+             :target "_blank"}
+         "more"]
+        "."]
+       [:div
+        (ui/button "Grant Permission"
+          :on-click #(page-handler/ls-dir-files! shortcut/refresh!))]
+       [:p.mb-1 "Note:"]
+       [:ol
+        [:li "We will never access files outside of your graph folders you choose."]
+        [:li "If you have granted the permission, you don't need to do it again."]]]
+      [:hr]]]))
 
 (rum/defcs add-graph <
   [state & {:keys [graph-types]
@@ -181,17 +177,15 @@
                              (keep generate-f)
                              (vec)
                              (interpose [:b.mt-10.mb-5.opacity-50 "OR"]))]
-    (rum/with-context [[_t] i18n/*tongue-context*]
-      [:div.p-8.flex.flex-col available-graph])))
+    [:div.p-8.flex.flex-col available-graph]))
 
 (rum/defc demo-graph-alert
   []
   (when (and (config/demo-graph?)
              (not config/publishing?))
-    (rum/with-context [[t] i18n/*tongue-context*]
-      (ui/admonition
-        :warning
-        [:p (t :on-boarding/demo-graph)]))))
+    (ui/admonition
+     :warning
+     [:p (t :on-boarding/demo-graph)])))
 
 (rum/defc github-integration-soon-deprecated-alert
   []

+ 0 - 4
src/main/frontend/config.cljs

@@ -253,10 +253,6 @@
   [path]
   (util/starts-with? path default-draw-directory))
 
-(defn journal?
-  [path]
-  (string/includes? path (str (get-journals-directory) "/")))
-
 (defonce local-repo "local")
 
 (defn demo-graph?

+ 11 - 22
src/main/frontend/context/i18n.cljs

@@ -1,7 +1,6 @@
 (ns frontend.context.i18n
   (:require [frontend.dicts :as dicts]
             [frontend.modules.shortcut.dict :as shortcut-dict]
-            [rum.core :as rum]
             [medley.core :refer [deep-merge]]
             [frontend.state :as state]))
 
@@ -15,26 +14,16 @@
 (defn fetch-local-language []
   (.. js/window -navigator -language))
 
-(rum/defcontext *tongue-context*)
+(defonce translate-dicts (atom {}))
 
-;; FIXME: reactive
-(defonce t
+(defn t
+  [& args]
   (let [preferred-language (keyword (state/sub :preferred-language))
-        set-preferred-language state/set-preferred-language!
-        all-dicts (deep-merge dicts/dicts shortcut-dict/dict)
-        t (partial (dicts/translate all-dicts) preferred-language)]
-    (if (nil? preferred-language)
-      (set-preferred-language (fetch-local-language))
-      :ok)
-    t))
-
-(rum/defc tongue-provider [children]
-  (let [preferred-language (keyword (state/sub :preferred-language))
-        set-preferred-language state/set-preferred-language!
-        all-dicts (deep-merge dicts/dicts shortcut-dict/dict)
-        t (partial (dicts/translate all-dicts) preferred-language)]
-    (if (nil? preferred-language)
-      (set-preferred-language (fetch-local-language))
-      :ok)
-    (rum/bind-context [*tongue-context* [t preferred-language set-preferred-language]]
-                      children)))
+        _ (when (nil? preferred-language)
+            (state/set-preferred-language! (fetch-local-language)))
+        dicts (or (get @translate-dicts preferred-language)
+                  (let [result (some-> (deep-merge dicts/dicts shortcut-dict/dict)
+                                       dicts/translate)]
+                    (swap! translate-dicts assoc preferred-language result)
+                    result))]
+    (apply (partial dicts preferred-language) args)))

+ 20 - 19
src/main/frontend/db.cljs

@@ -98,7 +98,7 @@
   (when-let [old-job (get @persistent-jobs repo)]
     (js/clearTimeout old-job)))
 
-(defn- persist-if-idle!
+(defn persist-if-idle!
   [repo]
   (clear-repo-persistent-job! repo)
   (let [job (js/setTimeout
@@ -120,24 +120,25 @@
   [repo conn]
   (d/listen! conn :persistence
              (fn [tx-report]
-               (if (util/electron?)
-                 (when-not (:dbsync? (:tx-meta tx-report))
-                   ;; sync with other windows if needed
-                   (p/let [graph-has-other-window? (ipc/ipc "graphHasOtherWindow" repo)]
-                    (when graph-has-other-window?
-                      (ipc/ipc "dbsync" repo {:data (db->string (:tx-data tx-report))}))))
-                 (do
-                   (state/set-last-transact-time! repo (util/time-ms))
-                   (persist-if-idle! repo)))
-
-               ;; rebuild search indices
-               (let [data (:tx-data tx-report)
-                     datoms (filter
-                             (fn [datom]
-                               (contains? #{:block/name :block/content} (:a datom)))
-                             data)]
-                 (when-let [f @*sync-search-indice-f]
-                   (f datoms))))))
+               (when-not (:new-graph? (:tx-meta tx-report)) ; skip initial txs
+                 (if (util/electron?)
+                   (when-not (:dbsync? (:tx-meta tx-report))
+                     ;; sync with other windows if needed
+                     (p/let [graph-has-other-window? (ipc/ipc "graphHasOtherWindow" repo)]
+                       (when graph-has-other-window?
+                         (ipc/ipc "dbsync" repo {:data (db->string (:tx-data tx-report))}))))
+                   (do
+                     (state/set-last-transact-time! repo (util/time-ms))
+                     (persist-if-idle! repo)))
+
+                 ;; rebuild search indices
+                 (let [data (:tx-data tx-report)
+                       datoms (filter
+                               (fn [datom]
+                                 (contains? #{:block/name :block/content} (:a datom)))
+                               data)]
+                   (when-let [f @*sync-search-indice-f]
+                     (f datoms)))))))
 
 (defn- listen-and-persist!
   [repo]

+ 0 - 18
src/main/frontend/db/model.cljs

@@ -665,18 +665,6 @@
           block-uuid)
         (sort-by-left (db-utils/entity [:block/uuid block-uuid])))))
 
-(defn get-blocks-by-page
-  [repo id-or-lookup-ref]
-  (when-let [conn (conn/get-conn repo)]
-    (->
-     (d/q
-      '[:find (pull ?block [*])
-        :in $ ?page
-        :where
-        [?block :block/page ?page]]
-      conn id-or-lookup-ref)
-     flatten)))
-
 (defn get-block-children
   "Including nested children."
   [repo block-uuid]
@@ -1259,12 +1247,6 @@
          (reset! blocks-count-cache n)
          n)))))
 
-(defn get-all-block-uuids
-  []
-  (when-let [conn (conn/get-conn)]
-    (->> (d/datoms conn :avet :block/uuid)
-         (map :v))))
-
 ;; block/uuid and block/content
 (defn get-all-block-contents
   []

+ 8 - 8
src/main/frontend/db/outliner.cljs

@@ -1,5 +1,6 @@
 (ns frontend.db.outliner
-  (:require [datascript.core :as d]))
+  (:require [datascript.core :as d]
+            [clojure.set :as set]))
 
 (defn get-by-id
   [conn id]
@@ -9,13 +10,12 @@
 
 (defn get-by-parent-&-left
   [conn parent-id left-id]
-  (let [r (d/q '[:find (pull ?a [*])
-                 :in $ ?p ?l
-                 :where
-                 [?a :block/left ?l]
-                 [?a :block/parent ?p]]
-            @conn parent-id left-id)]
-    (ffirst r)))
+  (when (and parent-id left-id)
+    (let [lefts (:block/_left (d/entity @conn left-id))
+          children (:block/_parent (d/entity @conn parent-id))
+          ids (set/intersection lefts children)
+          id (:db/id (first ids))]
+      (when id (d/pull @conn '[*] id)))))
 
 ;; key [:block/children parent-id]
 

+ 28 - 16
src/main/frontend/db/query_dsl.cljs

@@ -141,6 +141,31 @@
      l)
     @vars))
 
+;; TODO: Convert -> fns to rules
+(defn ->property-query
+  ([k v]
+   (->property-query k v '?v))
+  ([k v sym]
+   [['?b :block/properties '?prop]
+    [(list 'missing? '$ '?b :block/name)]
+    [(list 'get '?prop (keyword k)) sym]
+    (list
+     'or
+     [(list '= sym v)]
+     [(list 'contains? sym v)]
+     ;; For integer pages that aren't strings
+     [(list 'contains? sym (str v))])]))
+
+(defn ->page-property-query
+  [k v]
+  [['?p :block/name]
+   ['?p :block/properties '?prop]
+   [(list 'get '?prop (keyword k)) '?v]
+   (list
+    'or
+    [(list '= '?v v)]
+    [(list 'contains? '?v v)])])
+
 (defn build-query
   ([repo e env]
    (build-query repo e (assoc env :vars (atom {})) 0))
@@ -269,13 +294,7 @@
              sym (if (= current-filter 'or)
                    '?v
                    (uniq-symbol counter "?v"))]
-         [['?b :block/properties '?prop]
-          [(list 'missing? '$ '?b :block/name)]
-          [(list 'get '?prop (keyword k)) sym]
-          (list
-           'or
-           [(list '= sym v)]
-           [(list 'contains? sym v)])])
+         (->property-query k v sym))
 
        (and (= 'property fe)
             (= 2 (count e)))
@@ -346,15 +365,8 @@
              k (string/replace (name k) "_" "-")]
          (if-not (nil? v)
            (let [v (text/parse-property k v)
-                 v (if (coll? v) (first v) v)
-                 sym '?v]
-             [['?p :block/name]
-              ['?p :block/properties '?prop]
-              [(list 'get '?prop (keyword k)) sym]
-              (list
-               'or
-               [(list '= sym v)]
-               [(list 'contains? sym v)])])
+                 v (if (coll? v) (first v) v)]
+             (->page-property-query k v))
            [['?p :block/name]
             ['?p :block/properties '?prop]
             [(list 'get '?prop (keyword k)) '?prop-v]

+ 1 - 0
src/main/frontend/db/react.cljs

@@ -296,6 +296,7 @@
            distinct)))
       [[key]])))
 
+;; TODO: incremental or delayed queries (e.g. only run custom queries when idle)
 (defn refresh!
   [repo-url handler-opts]
   (let [related-keys (get-related-keys handler-opts)

+ 5 - 1
src/main/frontend/db/utils.cljs

@@ -91,12 +91,16 @@
   ([tx-data]
    (transact! (state/get-current-repo) tx-data))
   ([repo-url tx-data]
+   (transact! repo-url tx-data nil))
+  ([repo-url tx-data tx-meta]
    (when-not config/publishing?
      (let [tx-data (->> (util/remove-nils tx-data)
                         (remove nil?))]
        (when (seq tx-data)
          (when-let [conn (conn/get-conn repo-url false)]
-           (d/transact! conn (vec tx-data))))))))
+           (if tx-meta
+             (d/transact! conn (vec tx-data) tx-meta)
+             (d/transact! conn (vec tx-data)))))))))
 
 (defn get-key-value
   ([key]

+ 1094 - 1041
src/main/frontend/dicts.cljs

@@ -398,14 +398,14 @@
         :select.graph/add-graph "Yes, add another graph"}
 
    :de {:help/about "Über Logseq"
-        :on-boarding/demo-graph "This is a demo graph, changes will not be saved until you open a local folder."
-        :on-boarding/add-graph "Add a graph"
-        :on-boarding/open-local-dir "Open a local directory"
-        :on-boarding/new-graph-desc-1 "Logseq supports both Markdown and Org-mode. You can open an existing directory or create a new one on your device, a directory is also known simply as a folder. Your data will be stored only on this device."
-        :on-boarding/new-graph-desc-2 "After you have opened your directory, it will create three folders in that directory:"
-        :on-boarding/new-graph-desc-3 "/journals - store your journal pages"
-        :on-boarding/new-graph-desc-4 "/pages - store the other pages"
-        :on-boarding/new-graph-desc-5 "/logseq - store configuration, custom.css, and some metadata."
+        :on-boarding/demo-graph "Dies ist ein Demo-Graph. Änderungen werden nicht gespeichert, solange Sie kein lokales Verzeichnis öffnen."
+        :on-boarding/add-graph "Graph hinzufügen"
+        :on-boarding/open-local-dir "Lokales Verzeichnis öffnen"
+        :on-boarding/new-graph-desc-1 "Logseq unterstützt sowohl Markdown als auch Org-mode. Sie können ein bestehendes Verzeichnis öffnen oder auf Ihrem Rechner ein neues Verzeichnis anlegen. Ein Verzeichnis wird auch als Ordner bezeichnet. Ihre Daten werden nur auf diesem Gerät gespeichert."
+        :on-boarding/new-graph-desc-2 "Nachdem Sie ein Verzeichnis geöffnet haben, werden darin drei Ordner angelegt:"
+        :on-boarding/new-graph-desc-3 "/journals - enthält Ihre Journal-Seiten"
+        :on-boarding/new-graph-desc-4 "/pages - enthält die anderen Seiten"
+        :on-boarding/new-graph-desc-5 "/logseq - enthält die Konfiguration, custom.css und Metadaten."
         :help/bug "Fehlerbericht"
         :help/feature "Feature-Anfrage"
         :help/changelog "Änderungsprotokoll"
@@ -431,10 +431,10 @@
         :help/fold-unfold "Blöcke ein-/ausklappen (wenn nicht im Bearbeitungsmodus)"
         :help/markdown-syntax "Markdown-Syntax"
         :help/org-mode-syntax "Org-Mode-Syntax"
-        :new-graph "Add new graph"
+        :new-graph "Neuen Graphen hinzufügen"
         :bold "Fett"
         :italics "Kursiv"
-        :html-link "Html-Link"
+        :html-link "HTML-Link"
         :highlight "Hervorhebung"
         :strikethrough "Durchstreichung"
         :code "Quelltext"
@@ -445,11 +445,11 @@
         :right-side-bar/recent "Neueste"
         :right-side-bar/contents "Inhalt"
         :right-side-bar/block-ref "Blockreferenz"
-        :right-side-bar/journals "Journals"
-        :right-side-bar/graph-view "Graph view"
-        :right-side-bar/all-pages "All pages"
+        :right-side-bar/journals "Journale"
+        :right-side-bar/graph-view "Graph View"
+        :right-side-bar/all-pages "Alle Seiten"
         :right-side-bar/flashcards "Flashcards"
-        :right-side-bar/new-page "New page"
+        :right-side-bar/new-page "Neue Seite"
         :git/set-access-token "Persönliches GitHub-Zugangs-Token festlegen"
         :git/token-is-encrypted "Das Token wird verschlüsselt und im lokalen Speicher des Browsers gespeichert"
         :git/token-server "Auf dem Server wird es niemals gespeichert"
@@ -463,9 +463,9 @@
         :git/import-notes "Notizen importieren"
         :git/import-notes-helper "Notizen aus einem Repository auf GitHub importieren."
         :git/add-another-repo "Ein weiteres Repository hinzufügen"
-        :git/re-index "Erneut runterladen und Datenbank neu indizieren"
+        :git/re-index "Erneut herunterladen und Datenbank neu indizieren"
         :git/message "Ihre Commit-Nachricht"
-        :git/commit-and-push "Commit und Hochladen!"
+        :git/commit-and-push "Commit und hochladen!"
         :git/use-remote "Entfernte Version nutzen"
         :git/keep-local "Lokale Version beibehalten"
         :git/edit "Bearbeiten"
@@ -550,10 +550,10 @@
         :content/copy-as-json "Als JSON kopieren"
         :content/click-to-edit "Klicken zum bearbeiten"
         :settings-page/edit-config-edn "config.edn bearbeiten (im aktuellen Repository)"
-        :settings-page/git-desc "is used for pages version control, you can click the vertical three dots menu to check the page's history."
-        :settings-page/git-confirm "You need to restart the app after updating the Git settings."
-        :settings-page/git-switcher-label "Enable Git auto commit"
-        :settings-page/git-commit-delay "Git auto commit seconds"
+        :settings-page/git-desc "wird für die Seiten-Versionskontrolle benutzt. Sie können das Menü mit den drei vertikalen Punkten anklicken, um den Änderungsverlauf der Seite zu sehen."
+        :settings-page/git-confirm "Sie müssen die App neu starten, nachdem Sie die Git-Einstellungen angepasst haben."
+        :settings-page/git-switcher-label "Git Auto Commit aktivieren"
+        :settings-page/git-commit-delay "Anzahl Sekunden für Git Auto Commit"
         :settings-page/show-brackets "Klammern anzeigen"
         :settings-page/preferred-file-format "Bevorzugtes Datei-Format"
         :settings-page/preferred-workflow "Bevorzugter Workflow"
@@ -561,7 +561,7 @@
         :settings-page/enable-journals "Journale einschalten"
         :settings-page/home-default-page "Standard-Homepage einrichten"
         :settings-page/enable-block-time "Zeitstempel für Blöcke aktivieren"
-        :settings-page/dont-use-other-peoples-proxy-servers "Verwenden Sie keine fremden Proxyserver. Hierbei können das Token und die Notizen gelesen werden. Logseq ist für kann die Sicherheit nicht garantieren, wenn Sie die Proxy-Server anderer Leute verwenden. Sie können selber einen Proxy-Server einrichten, mehr dazu unter "
+        :settings-page/dont-use-other-peoples-proxy-servers "Verwenden Sie keine fremden Proxyserver. Hierbei können das Token und die Notizen gelesen werden. Logseq kann die Sicherheit nicht garantieren, wenn Sie die Proxy-Server anderer Leute verwenden. Sie können selber einen Proxy-Server einrichten, mehr dazu unter "
         :settings-page/custom-cors-proxy-server "Benutzerdefinierter CORS-Proxy-Server"
         :settings-page/developer-mode "Entwicklermodus"
         :settings-page/enable-developer-mode "Entwicklermodus aktivieren"
@@ -575,7 +575,7 @@
         :yes "Ja"
         :submit "Senden"
         :cancel "Abbrechen"
-        :re-index "Neu-Indizieren"
+        :re-index "Neu indizieren"
         :export-json "Als JSON exportieren"
         :unlink "Verknüpfung aufheben"
         :search (if config/publishing?
@@ -1942,1039 +1942,1092 @@
         :select.graph/empty-placeholder-description "No encontramos un grafo. Queries añadir otro?"
         :select.graph/add-graph "Si, añadame otro grafo"}
 
-:nb-NO {:on-boarding/title "Hei, og velkommen til Logseq!"
-        :on-boarding/sharing "deling"
-        :on-boarding/is-a " er en "
-        :on-boarding/vision "En personvernfokusert, åpen kildekode-plattform for kunnskapshåndtering og samarbeid."
-        :on-boarding/local-first "lokalt først"
-        :on-boarding/non-linear "ikke-lineær"
-        :on-boarding/outliner "outliner"
-        :on-boarding/notebook-for-organizing-and " notisbok for organisering og "
-        :on-boarding/your-personal-knowledge-base " din personlige kunnskapsbase."
-        :on-boarding/notice "Mer at dette prosjektet er i Beta-testing og under rask utvikling. Husk å ta sikkerhetskopier minst x1 per dag."
-        :on-boarding/features-desc "Bruk det til å organisere dine gjøremålslister, skrive dagbok eller til å registerer det som skjer i livet ditt."
-        :on-boarding/privacy "Serveren vil aldri lagre eller analysere dine private notater. Dine data er rene tekstfiler. Vi støtter for øyeblikket både Markdown og Emacs Org mode. Selv om nettsiden er nede eller ikke kan vedlikeholdes er dataene alltid dine."
-        :on-boarding/inspired-by " er enormt inspirert av "
-        :on-boarding/where-are-my-notes-saved "Hvor blir mine notater lagret?"
-        :on-boarding/storage "Dine notater blir lagret i din lokale nettleser ved hjelp av "
-        :on-boarding/how-do-i-use-it "Hvordan bruker jeg det?"
-        :on-boarding/use-1 "1. Synkroniser mellom flere enheter"
-        :on-boarding/use-1-desc "For øyeblikket støtter vi bare synkronsiering via GitHub. Flere muligheter (egen-hostet git, WebDAV, Google Drive etc.) vil komme snart."
-        :on-boarding/use-1-video "Sjekk ut denne fantastiske videoen av "
-        :on-boarding/use-2 "2. Bruk den lokalt (innlogging ikke nødvendig)"
-        :on-boarding/features "Funksjoner"
-        :on-boarding/features-backlinks "Tilbakekoblinger mellom [[Side]]r"
-        :on-boarding/features-block-embed "Innebygging av blokker"
-        :on-boarding/features-page-embed "Innebygging av sider"
-        :on-boarding/features-graph-vis "Diagaram-visualisering"
-        :on-boarding/features-heading-properties "Overskriftsegenskaper"
-        :on-boarding/features-datalog "Datalog spørringer, notat-databasen drives av "
-        :on-boarding/features-custom-view-component "Tilpasset visningskomponent"
-        :on-boarding/integration " integrasjon"
-        :on-boarding/slide-support " lysbilde-støtte"
-        :on-boarding/built-in-supports "Innebygging av:"
-        :on-boarding/supports-code-highlights "Fremheving av kode"
-        :on-boarding/supports-katex-latex "Katex latex"
-        :on-boarding/raw "Rå "
-        :on-boarding/raw-html "Rå html"
-        :on-boarding/learn-more "Lær mer"
-        :on-boarding/discord-desc " hvor samfunnet stiller spørsmål og deler tips"
-        :on-boarding/github-desc " alle oppfordres til å rapportere problemer!"
-        :on-boarding/our-blog "Vår blogg: "
-        :on-boarding/credits-to "Krediteringer"
-        :on-boarding/clojure-desc " - Et dynamisk, funksjonelt og generelt programmeringsspråk"
-        :on-boarding/datascript-desc " - Uforanderlig database og Datalog søkemotor for Clojure, ClojureScript og JS"
-        :on-boarding/angstrom-desc-1 ", dokumentet "
-        :on-boarding/angstrom-desc-2 "parser"
-        :on-boarding/angstrom-desc-3 " er bygd på Angstrom."
-        :on-boarding/cuekeeper-desc " - Nettleser-basert GTD (gjøremålsliste) system."
-        :on-boarding/sci-desc " - Liten Clojure Tolk"
-        :on-boarding/isomorphic-git-desc " - En ren JavaScript impelemtering av git for node og nettlesere!"
-        :on-boarding/demo-graph "This is a demo graph, changes will not be saved until you open a local folder."
-        :on-boarding/add-graph "Add a graph"
-        :on-boarding/open-local-dir "Open a local directory"
-        :on-boarding/new-graph-desc-1 "Logseq supports both Markdown and Org-mode. You can open an existing directory or create a new one on your device, a directory is also known simply as a folder. Your data will be stored only on this device."
-        :on-boarding/new-graph-desc-2 "After you have opened your directory, it will create three folders in that directory:"
-        :on-boarding/new-graph-desc-3 "/journals - store your journal pages"
-        :on-boarding/new-graph-desc-4 "/pages - store the other pages"
-        :on-boarding/new-graph-desc-5 "/logseq - store configuration, custom.css, and some metadata."
-        :help/start "Kom i gang"
-        :help/about "Om Logseq"
-        :help/roadmap "Veikart"
-        :help/bug "Feilrapport"
-        :help/feature "Funksjonsforespørsel"
-        :help/changelog "Endringslogg"
-        :help/blog "Logseq Blogg"
-        :help/docs "Dokumentasjon"
-        :help/privacy "Personvernerklæring"
-        :help/terms "Vilkår"
-        :help/community "Discord samfunn"
-        :help/awesome-logseq "Fantastiske Logseq"
-        :help/shortcuts "Tastatursnarveier"
-        :help/shortcuts-triggers "Utløsere"
-        :help/shortcut "Snarvei"
-        :help/slash-autocomplete "Skråstrek Autofullføring"
-        :help/block-content-autocomplete "Blokkinnhold (Src, Quote, Query, etc) Autofullføring"
-        :help/reference-autocomplete "Sidereferanse Autofullføring"
-        :help/block-reference "Blokkreferanse"
-        :help/key-commands "Nøkkelkommandoer"
-        :help/working-with-lists " (arbeide med lister)"
-        :help/select-nfs-browser " Vennligst bruk en annen nettleser (f.eks. siste chrome) som støtter NFS for å åpne en lokal mappe."
-        :undo "Angre"
-        :redo "Gjør om"
-        :general "Generell"
-        :more "Mer"
-        :search/result-for "Søkeresultat for "
-        :search/items "elementer"
-        :search/page-names "Søk i navn på sider"
-        :help/context-menu "Kontekstmeny"
-        :help/fold-unfold "Brett/brett ut blokker (når du ikke er i redigeringsmodus)"
-        :help/markdown-syntax "Markdown syntaks"
-        :help/org-mode-syntax "Org mode syntaks"
-        :bold "Fet"
-        :italics "Kursiv"
-        :html-link "Html Lenke"
-        :highlight "Markering"
-        :strikethrough "Gjennomstreking"
-        :code "Kode"
-        :right-side-bar/help "Hjelp"
-        :right-side-bar/switch-theme "Tema moduser"
-        :right-side-bar/theme "{1} tema"
-        :right-side-bar/page "Sidegraf"
-        :right-side-bar/recent "Siste"
-        :right-side-bar/contents "Innhold"
-        :right-side-bar/favorites "Favoritter"
-        :right-side-bar/page-graph "Sidegraf"
-        :right-side-bar/block-ref "Blokkreferanse"
-        :right-side-bar/journals "Journals"
-        :right-side-bar/graph-view "Graph view"
-        :right-side-bar/all-pages "All pages"
-        :right-side-bar/flashcards "Flashcards"
-        :right-side-bar/new-page "New page"
-        :left-side-bar/new-page "Ny side"
-        :left-side-bar/nav-favorites "Favoritter"
-        :left-side-bar/nav-shortcuts "Snarveier"
-        :left-side-bar/nav-recent-pages "Nylig"
-        :git/set-access-token "Sett 'GitHub personal access token'"
-        :git/token-is-encrypted "Ditt token blir kryptert og lagret lokalt i nettleseren din"
-        :git/token-server "Serveren lagerer det aldri"
-        :git/create-personal-access-token "Hvordan lage et 'GitHub personal access token?'"
-        :git/push "Push nå"
-        :git/push-failed "Push feilet!"
-        :git/local-changes-synced "Alle lokale endringer er synkronisert!"
-        :git/pull "Pull nå"
-        :git/last-pull "Siste pull kl."
-        :git/version "Versjon"
-        :git/import-notes "Importer dine notater"
-        :git/import-notes-helper "Du kan importere dine notater fra et repo på GitHub."
-        :git/add-another-repo "Legg til et annet repo"
-        :git/re-index "Klon igjen og re-indekser databasen"
-        :git/message "Din commit beskjed"
-        :git/commit-and-push "Commit og push!"
-        :git/use-remote "Bruk remote"
-        :git/keep-local "Behold local"
-        :git/edit "Rediger"
-        :git/title "Diff"
-        :git/no-diffs "Ingen diff"
-        :git/commit-message "Commit beskjed (valgfri)"
-        :git/pushing "Pusher"
-        :git/force-push "Commit og tving push"
-        :git/a-force-push "En tvunget push"
-        :git/add-repo-prompt "Installer Logseq på ditt repo"
-        :git/add-repo-prompt-confirm "Legg til og installer"
-        :format/preferred-mode "Hva er dine foretrukne modus?"
-        :format/markdown "Markdown"
-        :format/org-mode "Org Mode"
-        :reference/linked "Lenket referanse"
-        :reference/unlinked-ref "Ulenket referanse"
-        :project/setup "Sett opp et offentlig prosjekt på Logseq"
-        :project/location "Alle publiserte sider vil bli lokalisert under"
-        :project/sync-settings "Synkroniser prosjektinnstillinger"
-        :page/presentation-mode "Presentasjonsmodus"
-        :page/edit-properties-placeholder "Egenskaper"
-        :page/delete-success "Side {1} ble vellykket slettet!"
-        :page/delete-confirmation "Er du sikker på at du vil slette denne siden og filen dens?"
-        :page/rename-to "Gi nytt navn \"{1}\" til:"
-        :page/priority "Prioritet \"{1}\""
-        :page/copy-to-json "Kopier hele siden som JSON"
-        :page/rename "Gi siden nytt navn"
-        :page/open-in-finder "Åpne i mappe"
-        :page/open-with-default-app "Åpne med forhåndsvalgt app"
-        :page/action-publish "Publiser"
-        :page/make-public "Gjør den offentlig for publisering"
-        :page/version-history "Sjekk sidehistorikk"
-        :page/make-private "Gjør den privat"
-        :page/delete "Slett side"
-        :page/publish "Publiser denne siden på Logseq"
-        :page/cancel-publishing "Avbryt publisering på Logseq"
-        :page/publish-as-slide " Publiser denne sidne som et lysbilde på Logseq"
-        :page/unpublish "Avpubliser denne siden på Logseq"
-        :page/add-to-favorites "Legg til i Favoritter"
-        :page/unfavorite "Fjern side fra Faoritter"
-        :page/show-journals "Vis Dagbøker"
-        :page/show-name "Vis navn på siden"
-        :page/hide-name "Skjul navn på siden"
-        :block/name "Navn på siden"
-        :page/last-modified "Sist endret"
-        :page/new-title "Tittel på den nye siden din?"
-        :page/earlier "Tidligere"
-        :page/no-more-journals "Ikke flere dagbøker"
-        :publishing/pages "Sider"
-        :publishing/page-name "Sidenavn"
-        :publishing/current-project "Nåværende prosjekt"
-        :publishing/delete-from-logseq "Slett fra Logseq server"
-        :publishing/edit "Rediger"
-        :publishing/save "Lagre"
-        :publishing/cancel "Avbryt"
-        :publishing/delete "Slett"
-        :journal/multiple-files-with-different-formats "Det ser ut som du har flere dagbokfiler (med forskjellig format) for den samme måneden. Vennligst ha bare en fil for hver måned."
-        :journal/go-to "Gå til filer"
-        :file/name "Filnavn"
-        :file/file "Fil: "
-        :file/last-modified-at "Sist endret"
-        :file/no-data "Ingen data"
-        :file/format-not-supported "Format .{1} er ikke støttet."
-        :page/created-at "Opprettet"
-        :page/updated-at "Oppdatert"
-        :page/backlinks "Tilbakekoblinger"
-        :editor/block-search "Søk etter en blokk"
-        :editor/image-uploading "Laster opp"
-        :draw/invalid-file "Kunne ikke laste inn den ugyldige excalidraw-filen"
-        :draw/specify-title "Vennligst spesifiser en tittel først!"
-        :draw/rename-success "Filen ble omdøpt!"
-        :draw/rename-failure "Omdøpring av fil feilet, årsak: "
-        :draw/title-placeholder "Uten navn"
-        :draw/save "Lagre"
-        :draw/save-changes "Lagre endringer"
-        :draw/new-file "Ny fil"
-        :draw/list-files "Opplist filer"
-        :draw/delete "Slett"
-        :draw/more-options "Flere valg"
-        :draw/back-to-logseq "Tilbake til logseq"
-        :text/image "Bilde"
-        :asset/confirm-delete "Er du sikker på at du vil slette denne {1}?"
-        :asset/physical-delete "Fjerner også filen (merk at den ikke kan gjenopprettes)"
-        :content/copy "Kopier"
-        :content/cut "Klipp ut"
-        :content/make-todos "Lag {1}s"
-        :content/copy-block-ref "Kopier blokkreferanse"
-        :content/copy-block-emebed "Kopier innebygging av blokk"
-        :content/focus-on-block "Fokuser på blokk"
-        :content/open-in-sidebar "Åpne i sidefeltet"
-        :content/copy-as-json "Kopier som JSON"
-        :content/click-to-edit "Klikk for å redigere"
-        :settings-page/git-desc "is used for pages version control, you can click the vertical three dots menu to check the page's history."
-        :settings-page/git-confirm "You need to restart the app after updating the Git settings."
-        :settings-page/git-switcher-label "Enable Git auto commit"
-        :settings-page/git-commit-delay "Git auto commit seconds"
-        :settings-page/edit-config-edn "Rediger config.edn for nåværende repo"
-        :settings-page/edit-custom-css "Rediger custom.css"
-        :settings-page/custom-configuration "Tilpasset konfigurasjon"
-        :settings-page/custom-theme "Tilpasset tema"
-        :settings-page/show-brackets "Vis klammer"
-        :settings-page/spell-checker "Stavekontroll"
-        :settings-page/auto-updater "Automatisk oppdatering"
-        :settings-page/disable-sentry "Send bruksdata og diagnostikk til Logseq"
-        :settings-page/preferred-outdenting "Skru på logiske innrykk"
-        :settings-page/custom-date-format "Foretrukket datoformat"
-        :settings-page/preferred-file-format "Foretrukket filformat"
-        :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-tooltip "Aktiver verktøytips"
-        :settings-page/enable-journals "Aktiver dagbøker"
-        :settings-page/enable-all-pages-public "Aktiver alle sider som offentlige ved publisering"
-        :settings-page/enable-encryption "Aktiver kryptering"
-        :settings-page/customize-shortcuts "Tastatursnarveier"
-        :settings-page/shortcut-settings "Tilpass snarveier"
-        :settings-page/home-default-page "Angi standard hjemmeside"
-        :settings-page/enable-block-time "Aktiver tidsstempel for blokker"
-        :settings-page/dont-use-other-peoples-proxy-servers "Ikke bruk andres proxy-servere. Det er veldig farlig, og kan føre til at ditt token og dine notater blir stjålet. Logseq er ikke ansvarlig for tap dersom du bruker andres proxy-servere. Du kan utplassere den selv, se "
-        :settings-page/clear-cache "Slett hurtigbuffer"
-        :settings-page/clear "Slett"
-        :settings-page/custom-cors-proxy-server "Egendefinert CORS proxy server"
-        :settings-page/developer-mode "Utviklermodus"
-        :settings-page/enable-developer-mode "Aktiver utviklermodus"
-        :settings-page/disable-developer-mode "Deaktiver utviklermodus"
-        :settings-page/developer-mode-desc "Utviklermodus hjelper bidragsytere og tilleggsutviklere med å teste sine integrasjoner mot Logseq mer effektivt."
-        :settings-page/current-version "Nåværende versjon"
-        :settings-page/current-graph "Nåværende graf"
-        :settings-page/tab-general "Generelt"
-        :settings-page/tab-editor "Editor"
-        :settings-page/tab-shortcuts "Snarveier"
-        :settings-page/tab-version-control "Versjonskontroll"
-        :settings-page/tab-advanced "Avansert"
-        :logseq "Logseq"
-        :on "PÅ"
-        :more-options "Flere valg"
-        :to "til"
-        :yes "Ja"
-        :no "Nei"
-        :submit "Send inn"
-        :cancel "Avbryt"
-        :close "Lukk"
-        :delete "Slett"
-        :re-index "Indekser på nytt"
-        :re-index-detail "Bygg grafen på nytt"
-        :open-new-window "Nytt vindu"
-        :sync-from-local-files "Oppfrisk"
-        :sync-from-local-files-detail "Importer endringer fra lokale filer"
-        :unlink "koble fra"
-        :search (if config/publishing?
-                  "Søk"
-                  "Søk eller Opprett Side")
-        :page-search "Søk i denne siden"
-        :graph-search "Søk graf"
-        :new-page "Ny side"
-        :new-file "Ny fil"
-        :new-graph "Legg til ny graf"
-        :graph "Graf"
-        :graph-view "Vis graf"
-        :cards-view "Vis kort"
-        :publishing "Publisering"
-        :export "Eksport"
-        :export-graph "Eksporter graf"
-        :export-page "Eksporter side"
-        :export-markdown "Eksporter som standard Markdown (ingen blokk-egenskaper)"
-        :export-opml "Eksporter som OPML"
-        :export-public-pages "Eksporter offentlige sider"
-        :export-json "Eksporter som JSON"
-        :export-roam-json "Eksporter som Roam JSON"
-        :export-edn "Eksporter som EDN"
-        :export-datascript-edn "Eksporter datascript EDN"
-        :convert-markdown "Konverter Markdown overskrifter til uordnede lister (# -> -)"
-        :all-graphs "Alle grafer"
-        :all-pages "Alle sider"
-        :all-files "Alle filer"
-        :remove-orphaned-pages "Fjern foreldreløse sider"
-        :all-journals "Alle dagbøker"
-        :my-publishing "Min publisering"
-        :settings "Innstillinger"
-        :plugins "Utvidelser"
-        :themes "Tema"
-        :developer-mode-alert "Du må starte appen på nytt for å skru på plugin-systemet. Vil du starte på nytt nå?"
-        :relaunch-confirm-to-work "Appen må startes på nytt for at dette skal virke. Vil du starte på nytt nå?"
-        :import "Importer"
-        :join-community "Bli med i samfunnet"
-        :sponsor-us "Spons oss"
-        :discord-title "Vår discord gruppe!"
-        :sign-out "Logg ut"
-        :help-shortcut-title "Klikk for å sjekke snarveier og andre tips"
-        :loading "Laster"
-        :cloning "Kloner"
-        :parsing-files "Analyserer filer"
-        :loading-files "Laster filer"
-        :login-github "Logg inn med GitHub"
-        :login "Logg inn"
-        :go-to "Gå til "
-        :or "eller"
-        :download "Last ned"
-        :repo/download-zip "Last ned alle filer som en zip"
-        :language "Språk"
-        :white "Lys"
-        :dark "Mørk"
-        :remove-background "Fjern bakgrunn"
-        :open "Åpne"
-        :open-a-directory "Åpne en lokal mappe"
-        :user/delete-account "Slett konto"
-        :user/delete-your-account "Slett din konto"
-        :user/delete-account-notice "Alle dine publiserte sider på logseq.com vil bli slettet."
+   :nb-NO {:on-boarding/title "Hei, og velkommen til Logseq!"
+           :on-boarding/sharing "deling"
+           :on-boarding/is-a " er en "
+           :on-boarding/vision "En personvernfokusert, åpen kildekode-plattform for kunnskapshåndtering og samarbeid."
+           :on-boarding/local-first "lokalt først"
+           :on-boarding/non-linear "ikke-lineær"
+           :on-boarding/outliner "outliner"
+           :on-boarding/notebook-for-organizing-and " notisbok for organisering og "
+           :on-boarding/your-personal-knowledge-base " din personlige kunnskapsbase."
+           :on-boarding/notice "Mer at dette prosjektet er i Beta-testing og under rask utvikling. Husk å ta sikkerhetskopier minst x1 per dag."
+           :on-boarding/features-desc "Bruk det til å organisere dine gjøremålslister, skrive dagbok eller til å registerer det som skjer i livet ditt."
+           :on-boarding/privacy "Serveren vil aldri lagre eller analysere dine private notater. Dine data er rene tekstfiler. Vi støtter for øyeblikket både Markdown og Emacs Org mode. Selv om nettsiden er nede eller ikke kan vedlikeholdes er dataene alltid dine."
+           :on-boarding/inspired-by " er enormt inspirert av "
+           :on-boarding/where-are-my-notes-saved "Hvor blir mine notater lagret?"
+           :on-boarding/storage "Dine notater blir lagret i din lokale nettleser ved hjelp av "
+           :on-boarding/how-do-i-use-it "Hvordan bruker jeg det?"
+           :on-boarding/use-1 "1. Synkroniser mellom flere enheter"
+           :on-boarding/use-1-desc "For øyeblikket støtter vi bare synkronsiering via GitHub. Flere muligheter (egen-hostet git, WebDAV, Google Drive etc.) vil komme snart."
+           :on-boarding/use-1-video "Sjekk ut denne fantastiske videoen av "
+           :on-boarding/use-2 "2. Bruk den lokalt (innlogging ikke nødvendig)"
+           :on-boarding/features "Funksjoner"
+           :on-boarding/features-backlinks "Tilbakekoblinger mellom [[Side]]r"
+           :on-boarding/features-block-embed "Innebygging av blokker"
+           :on-boarding/features-page-embed "Innebygging av sider"
+           :on-boarding/features-graph-vis "Diagaram-visualisering"
+           :on-boarding/features-heading-properties "Overskriftsegenskaper"
+           :on-boarding/features-datalog "Datalog spørringer, notat-databasen drives av "
+           :on-boarding/features-custom-view-component "Tilpasset visningskomponent"
+           :on-boarding/integration " integrasjon"
+           :on-boarding/slide-support " lysbilde-støtte"
+           :on-boarding/built-in-supports "Innebygging av:"
+           :on-boarding/supports-code-highlights "Fremheving av kode"
+           :on-boarding/supports-katex-latex "Katex latex"
+           :on-boarding/raw "Rå "
+           :on-boarding/raw-html "Rå html"
+           :on-boarding/learn-more "Lær mer"
+           :on-boarding/discord-desc " hvor samfunnet stiller spørsmål og deler tips"
+           :on-boarding/github-desc " alle oppfordres til å rapportere problemer!"
+           :on-boarding/our-blog "Vår blogg: "
+           :on-boarding/credits-to "Krediteringer"
+           :on-boarding/clojure-desc " - Et dynamisk, funksjonelt og generelt programmeringsspråk"
+           :on-boarding/datascript-desc " - Uforanderlig database og Datalog søkemotor for Clojure, ClojureScript og JS"
+           :on-boarding/angstrom-desc-1 ", dokumentet "
+           :on-boarding/angstrom-desc-2 "parser"
+           :on-boarding/angstrom-desc-3 " er bygd på Angstrom."
+           :on-boarding/cuekeeper-desc " - Nettleser-basert GTD (gjøremålsliste) system."
+           :on-boarding/sci-desc " - Liten Clojure Tolk"
+           :on-boarding/isomorphic-git-desc " - En ren JavaScript impelemtering av git for node og nettlesere!"
+           :on-boarding/demo-graph "This is a demo graph, changes will not be saved until you open a local folder."
+           :on-boarding/add-graph "Add a graph"
+           :on-boarding/open-local-dir "Open a local directory"
+           :on-boarding/new-graph-desc-1 "Logseq supports both Markdown and Org-mode. You can open an existing directory or create a new one on your device, a directory is also known simply as a folder. Your data will be stored only on this device."
+           :on-boarding/new-graph-desc-2 "After you have opened your directory, it will create three folders in that directory:"
+           :on-boarding/new-graph-desc-3 "/journals - store your journal pages"
+           :on-boarding/new-graph-desc-4 "/pages - store the other pages"
+           :on-boarding/new-graph-desc-5 "/logseq - store configuration, custom.css, and some metadata."
+           :help/start "Kom i gang"
+           :help/about "Om Logseq"
+           :help/roadmap "Veikart"
+           :help/bug "Feilrapport"
+           :help/feature "Funksjonsforespørsel"
+           :help/changelog "Endringslogg"
+           :help/blog "Logseq Blogg"
+           :help/docs "Dokumentasjon"
+           :help/privacy "Personvernerklæring"
+           :help/terms "Vilkår"
+           :help/community "Discord samfunn"
+           :help/awesome-logseq "Fantastiske Logseq"
+           :help/shortcuts "Tastatursnarveier"
+           :help/shortcuts-triggers "Utløsere"
+           :help/shortcut "Snarvei"
+           :help/slash-autocomplete "Skråstrek Autofullføring"
+           :help/block-content-autocomplete "Blokkinnhold (Src, Quote, Query, etc) Autofullføring"
+           :help/reference-autocomplete "Sidereferanse Autofullføring"
+           :help/block-reference "Blokkreferanse"
+           :help/key-commands "Nøkkelkommandoer"
+           :help/working-with-lists " (arbeide med lister)"
+           :help/select-nfs-browser " Vennligst bruk en annen nettleser (f.eks. siste chrome) som støtter NFS for å åpne en lokal mappe."
+           :undo "Angre"
+           :redo "Gjør om"
+           :general "Generell"
+           :more "Mer"
+           :search/result-for "Søkeresultat for "
+           :search/items "elementer"
+           :search/page-names "Søk i navn på sider"
+           :help/context-menu "Kontekstmeny"
+           :help/fold-unfold "Brett/brett ut blokker (når du ikke er i redigeringsmodus)"
+           :help/markdown-syntax "Markdown syntaks"
+           :help/org-mode-syntax "Org mode syntaks"
+           :bold "Fet"
+           :italics "Kursiv"
+           :html-link "Html Lenke"
+           :highlight "Markering"
+           :strikethrough "Gjennomstreking"
+           :code "Kode"
+           :right-side-bar/help "Hjelp"
+           :right-side-bar/switch-theme "Tema moduser"
+           :right-side-bar/theme "{1} tema"
+           :right-side-bar/page "Sidegraf"
+           :right-side-bar/recent "Siste"
+           :right-side-bar/contents "Innhold"
+           :right-side-bar/favorites "Favoritter"
+           :right-side-bar/page-graph "Sidegraf"
+           :right-side-bar/block-ref "Blokkreferanse"
+           :right-side-bar/journals "Journals"
+           :right-side-bar/graph-view "Graph view"
+           :right-side-bar/all-pages "All pages"
+           :right-side-bar/flashcards "Flashcards"
+           :right-side-bar/new-page "New page"
+           :left-side-bar/new-page "Ny side"
+           :left-side-bar/nav-favorites "Favoritter"
+           :left-side-bar/nav-shortcuts "Snarveier"
+           :left-side-bar/nav-recent-pages "Nylig"
+           :git/set-access-token "Sett 'GitHub personal access token'"
+           :git/token-is-encrypted "Ditt token blir kryptert og lagret lokalt i nettleseren din"
+           :git/token-server "Serveren lagerer det aldri"
+           :git/create-personal-access-token "Hvordan lage et 'GitHub personal access token?'"
+           :git/push "Push nå"
+           :git/push-failed "Push feilet!"
+           :git/local-changes-synced "Alle lokale endringer er synkronisert!"
+           :git/pull "Pull nå"
+           :git/last-pull "Siste pull kl."
+           :git/version "Versjon"
+           :git/import-notes "Importer dine notater"
+           :git/import-notes-helper "Du kan importere dine notater fra et repo på GitHub."
+           :git/add-another-repo "Legg til et annet repo"
+           :git/re-index "Klon igjen og re-indekser databasen"
+           :git/message "Din commit beskjed"
+           :git/commit-and-push "Commit og push!"
+           :git/use-remote "Bruk remote"
+           :git/keep-local "Behold local"
+           :git/edit "Rediger"
+           :git/title "Diff"
+           :git/no-diffs "Ingen diff"
+           :git/commit-message "Commit beskjed (valgfri)"
+           :git/pushing "Pusher"
+           :git/force-push "Commit og tving push"
+           :git/a-force-push "En tvunget push"
+           :git/add-repo-prompt "Installer Logseq på ditt repo"
+           :git/add-repo-prompt-confirm "Legg til og installer"
+           :format/preferred-mode "Hva er dine foretrukne modus?"
+           :format/markdown "Markdown"
+           :format/org-mode "Org Mode"
+           :reference/linked "Lenket referanse"
+           :reference/unlinked-ref "Ulenket referanse"
+           :project/setup "Sett opp et offentlig prosjekt på Logseq"
+           :project/location "Alle publiserte sider vil bli lokalisert under"
+           :project/sync-settings "Synkroniser prosjektinnstillinger"
+           :page/presentation-mode "Presentasjonsmodus"
+           :page/edit-properties-placeholder "Egenskaper"
+           :page/delete-success "Side {1} ble vellykket slettet!"
+           :page/delete-confirmation "Er du sikker på at du vil slette denne siden og filen dens?"
+           :page/rename-to "Gi nytt navn \"{1}\" til:"
+           :page/priority "Prioritet \"{1}\""
+           :page/copy-to-json "Kopier hele siden som JSON"
+           :page/rename "Gi siden nytt navn"
+           :page/open-in-finder "Åpne i mappe"
+           :page/open-with-default-app "Åpne med forhåndsvalgt app"
+           :page/action-publish "Publiser"
+           :page/make-public "Gjør den offentlig for publisering"
+           :page/version-history "Sjekk sidehistorikk"
+           :page/make-private "Gjør den privat"
+           :page/delete "Slett side"
+           :page/publish "Publiser denne siden på Logseq"
+           :page/cancel-publishing "Avbryt publisering på Logseq"
+           :page/publish-as-slide " Publiser denne sidne som et lysbilde på Logseq"
+           :page/unpublish "Avpubliser denne siden på Logseq"
+           :page/add-to-favorites "Legg til i Favoritter"
+           :page/unfavorite "Fjern side fra Faoritter"
+           :page/show-journals "Vis Dagbøker"
+           :page/show-name "Vis navn på siden"
+           :page/hide-name "Skjul navn på siden"
+           :block/name "Navn på siden"
+           :page/last-modified "Sist endret"
+           :page/new-title "Tittel på den nye siden din?"
+           :page/earlier "Tidligere"
+           :page/no-more-journals "Ikke flere dagbøker"
+           :publishing/pages "Sider"
+           :publishing/page-name "Sidenavn"
+           :publishing/current-project "Nåværende prosjekt"
+           :publishing/delete-from-logseq "Slett fra Logseq server"
+           :publishing/edit "Rediger"
+           :publishing/save "Lagre"
+           :publishing/cancel "Avbryt"
+           :publishing/delete "Slett"
+           :journal/multiple-files-with-different-formats "Det ser ut som du har flere dagbokfiler (med forskjellig format) for den samme måneden. Vennligst ha bare en fil for hver måned."
+           :journal/go-to "Gå til filer"
+           :file/name "Filnavn"
+           :file/file "Fil: "
+           :file/last-modified-at "Sist endret"
+           :file/no-data "Ingen data"
+           :file/format-not-supported "Format .{1} er ikke støttet."
+           :page/created-at "Opprettet"
+           :page/updated-at "Oppdatert"
+           :page/backlinks "Tilbakekoblinger"
+           :editor/block-search "Søk etter en blokk"
+           :editor/image-uploading "Laster opp"
+           :draw/invalid-file "Kunne ikke laste inn den ugyldige excalidraw-filen"
+           :draw/specify-title "Vennligst spesifiser en tittel først!"
+           :draw/rename-success "Filen ble omdøpt!"
+           :draw/rename-failure "Omdøpring av fil feilet, årsak: "
+           :draw/title-placeholder "Uten navn"
+           :draw/save "Lagre"
+           :draw/save-changes "Lagre endringer"
+           :draw/new-file "Ny fil"
+           :draw/list-files "Opplist filer"
+           :draw/delete "Slett"
+           :draw/more-options "Flere valg"
+           :draw/back-to-logseq "Tilbake til logseq"
+           :text/image "Bilde"
+           :asset/confirm-delete "Er du sikker på at du vil slette denne {1}?"
+           :asset/physical-delete "Fjerner også filen (merk at den ikke kan gjenopprettes)"
+           :content/copy "Kopier"
+           :content/cut "Klipp ut"
+           :content/make-todos "Lag {1}s"
+           :content/copy-block-ref "Kopier blokkreferanse"
+           :content/copy-block-emebed "Kopier innebygging av blokk"
+           :content/focus-on-block "Fokuser på blokk"
+           :content/open-in-sidebar "Åpne i sidefeltet"
+           :content/copy-as-json "Kopier som JSON"
+           :content/click-to-edit "Klikk for å redigere"
+           :settings-page/git-desc "is used for pages version control, you can click the vertical three dots menu to check the page's history."
+           :settings-page/git-confirm "You need to restart the app after updating the Git settings."
+           :settings-page/git-switcher-label "Enable Git auto commit"
+           :settings-page/git-commit-delay "Git auto commit seconds"
+           :settings-page/edit-config-edn "Rediger config.edn for nåværende repo"
+           :settings-page/edit-custom-css "Rediger custom.css"
+           :settings-page/custom-configuration "Tilpasset konfigurasjon"
+           :settings-page/custom-theme "Tilpasset tema"
+           :settings-page/show-brackets "Vis klammer"
+           :settings-page/spell-checker "Stavekontroll"
+           :settings-page/auto-updater "Automatisk oppdatering"
+           :settings-page/disable-sentry "Send bruksdata og diagnostikk til Logseq"
+           :settings-page/preferred-outdenting "Skru på logiske innrykk"
+           :settings-page/custom-date-format "Foretrukket datoformat"
+           :settings-page/preferred-file-format "Foretrukket filformat"
+           :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-tooltip "Aktiver verktøytips"
+           :settings-page/enable-journals "Aktiver dagbøker"
+           :settings-page/enable-all-pages-public "Aktiver alle sider som offentlige ved publisering"
+           :settings-page/enable-encryption "Aktiver kryptering"
+           :settings-page/customize-shortcuts "Tastatursnarveier"
+           :settings-page/shortcut-settings "Tilpass snarveier"
+           :settings-page/home-default-page "Angi standard hjemmeside"
+           :settings-page/enable-block-time "Aktiver tidsstempel for blokker"
+           :settings-page/dont-use-other-peoples-proxy-servers "Ikke bruk andres proxy-servere. Det er veldig farlig, og kan føre til at ditt token og dine notater blir stjålet. Logseq er ikke ansvarlig for tap dersom du bruker andres proxy-servere. Du kan utplassere den selv, se "
+           :settings-page/clear-cache "Slett hurtigbuffer"
+           :settings-page/clear "Slett"
+           :settings-page/custom-cors-proxy-server "Egendefinert CORS proxy server"
+           :settings-page/developer-mode "Utviklermodus"
+           :settings-page/enable-developer-mode "Aktiver utviklermodus"
+           :settings-page/disable-developer-mode "Deaktiver utviklermodus"
+           :settings-page/developer-mode-desc "Utviklermodus hjelper bidragsytere og tilleggsutviklere med å teste sine integrasjoner mot Logseq mer effektivt."
+           :settings-page/current-version "Nåværende versjon"
+           :settings-page/current-graph "Nåværende graf"
+           :settings-page/tab-general "Generelt"
+           :settings-page/tab-editor "Editor"
+           :settings-page/tab-shortcuts "Snarveier"
+           :settings-page/tab-version-control "Versjonskontroll"
+           :settings-page/tab-advanced "Avansert"
+           :logseq "Logseq"
+           :on "PÅ"
+           :more-options "Flere valg"
+           :to "til"
+           :yes "Ja"
+           :no "Nei"
+           :submit "Send inn"
+           :cancel "Avbryt"
+           :close "Lukk"
+           :delete "Slett"
+           :re-index "Indekser på nytt"
+           :re-index-detail "Bygg grafen på nytt"
+           :open-new-window "Nytt vindu"
+           :sync-from-local-files "Oppfrisk"
+           :sync-from-local-files-detail "Importer endringer fra lokale filer"
+           :unlink "koble fra"
+           :search (if config/publishing?
+                     "Søk"
+                     "Søk eller Opprett Side")
+           :page-search "Søk i denne siden"
+           :graph-search "Søk graf"
+           :new-page "Ny side"
+           :new-file "Ny fil"
+           :new-graph "Legg til ny graf"
+           :graph "Graf"
+           :graph-view "Vis graf"
+           :cards-view "Vis kort"
+           :publishing "Publisering"
+           :export "Eksport"
+           :export-graph "Eksporter graf"
+           :export-page "Eksporter side"
+           :export-markdown "Eksporter som standard Markdown (ingen blokk-egenskaper)"
+           :export-opml "Eksporter som OPML"
+           :export-public-pages "Eksporter offentlige sider"
+           :export-json "Eksporter som JSON"
+           :export-roam-json "Eksporter som Roam JSON"
+           :export-edn "Eksporter som EDN"
+           :export-datascript-edn "Eksporter datascript EDN"
+           :convert-markdown "Konverter Markdown overskrifter til uordnede lister (# -> -)"
+           :all-graphs "Alle grafer"
+           :all-pages "Alle sider"
+           :all-files "Alle filer"
+           :remove-orphaned-pages "Fjern foreldreløse sider"
+           :all-journals "Alle dagbøker"
+           :my-publishing "Min publisering"
+           :settings "Innstillinger"
+           :plugins "Utvidelser"
+           :themes "Tema"
+           :developer-mode-alert "Du må starte appen på nytt for å skru på plugin-systemet. Vil du starte på nytt nå?"
+           :relaunch-confirm-to-work "Appen må startes på nytt for at dette skal virke. Vil du starte på nytt nå?"
+           :import "Importer"
+           :join-community "Bli med i samfunnet"
+           :sponsor-us "Spons oss"
+           :discord-title "Vår discord gruppe!"
+           :sign-out "Logg ut"
+           :help-shortcut-title "Klikk for å sjekke snarveier og andre tips"
+           :loading "Laster"
+           :cloning "Kloner"
+           :parsing-files "Analyserer filer"
+           :loading-files "Laster filer"
+           :login-github "Logg inn med GitHub"
+           :login "Logg inn"
+           :go-to "Gå til "
+           :or "eller"
+           :download "Last ned"
+           :repo/download-zip "Last ned alle filer som en zip"
+           :language "Språk"
+           :white "Lys"
+           :dark "Mørk"
+           :remove-background "Fjern bakgrunn"
+           :open "Åpne"
+           :open-a-directory "Åpne en lokal mappe"
+           :user/delete-account "Slett konto"
+           :user/delete-your-account "Slett din konto"
+           :user/delete-account-notice "Alle dine publiserte sider på logseq.com vil bli slettet."
 
-        :help/shortcut-page-title "Tastatursnarveier"
+           :help/shortcut-page-title "Tastatursnarveier"
 
-        :plugin/installed "Installert"
-        :plugin/installing "Innstallerer"
-        :plugin/install "Innstaller"
-        :plugin/reload "Last på nytt"
-        :plugin/update "Oppdater"
-        :plugin/check-update "Se etter oppdatering"
-        :plugin/check-all-updates "Se etter alle oppdateringer"
-        :plugin/refresh-lists "Oppfrisk lister"
-        :plugin/enabled "Aktivert"
-        :plugin/disabled "Deaktivert"
-        :plugin/update-available "Oppdatering tilgjengelig"
-        :plugin/updating "Oppdaterer"
-        :plugin/uninstall "Avinstaller"
-        :plugin/marketplace "Markedsplass"
-        :plugin/downloads "Nedlastinger"
-        :plugin/stars "Stjerner"
-        :plugin/title "Tittel"
-        :plugin/all "Alle"
-        :plugin/unpacked "Utpakket"
-        :plugin/delete-alert "Vil du avinstallere plugin [{1}]?"
-        :plugin/open-settings "Åpne innstillinger"
-        :plugin/open-package "Åpne pakke"
-        :plugin/load-unpacked "Last inn utpakket utvidelse"
-        :plugin/open-preferences "Åpne innstillingsfil for utvidelsen"
-        :plugin/restart "Start App på nytt"
-        :plugin/unpacked-tips "Velg mappe for utvidelse"
-        :plugin/contribute "✨ Skriv og send inn en ny utvidelse"
-        :plugin/marketplace-tips "Hvis utvidelsen ikke fungerer når du installerer den, prøv å restarte Logseq."
-        :plugin/up-to-date "Den er oppdatert"
-        :plugin/custom-js-alert "Fant custom.js fil, får den lov til å kjøre? (Hvis du ikke forstår innholdet i denne filen er det anbefalt å ikke la den kjøre. Dette kan ha sikkerhetsrisiko.)"
+           :plugin/installed "Installert"
+           :plugin/installing "Innstallerer"
+           :plugin/install "Innstaller"
+           :plugin/reload "Last på nytt"
+           :plugin/update "Oppdater"
+           :plugin/check-update "Se etter oppdatering"
+           :plugin/check-all-updates "Se etter alle oppdateringer"
+           :plugin/refresh-lists "Oppfrisk lister"
+           :plugin/enabled "Aktivert"
+           :plugin/disabled "Deaktivert"
+           :plugin/update-available "Oppdatering tilgjengelig"
+           :plugin/updating "Oppdaterer"
+           :plugin/uninstall "Avinstaller"
+           :plugin/marketplace "Markedsplass"
+           :plugin/downloads "Nedlastinger"
+           :plugin/stars "Stjerner"
+           :plugin/title "Tittel"
+           :plugin/all "Alle"
+           :plugin/unpacked "Utpakket"
+           :plugin/delete-alert "Vil du avinstallere plugin [{1}]?"
+           :plugin/open-settings "Åpne innstillinger"
+           :plugin/open-package "Åpne pakke"
+           :plugin/load-unpacked "Last inn utpakket utvidelse"
+           :plugin/open-preferences "Åpne innstillingsfil for utvidelsen"
+           :plugin/restart "Start App på nytt"
+           :plugin/unpacked-tips "Velg mappe for utvidelse"
+           :plugin/contribute "✨ Skriv og send inn en ny utvidelse"
+           :plugin/marketplace-tips "Hvis utvidelsen ikke fungerer når du installerer den, prøv å restarte Logseq."
+           :plugin/up-to-date "Den er oppdatert"
+           :plugin/custom-js-alert "Fant custom.js fil, får den lov til å kjøre? (Hvis du ikke forstår innholdet i denne filen er det anbefalt å ikke la den kjøre. Dette kan ha sikkerhetsrisiko.)"
 
-        :pdf/copy-ref "Kopier ref"
-        :pdf/copy-text "Kopier tekst"
-        :pdf/linked-ref "Lenkede referanser"
-        :pdf/toggle-dashed "Stiplet stil for utheving av område"
+           :pdf/copy-ref "Kopier ref"
+           :pdf/copy-text "Kopier tekst"
+           :pdf/linked-ref "Lenkede referanser"
+           :pdf/toggle-dashed "Stiplet stil for utheving av område"
 
-        :updater/new-version-install "En ny versjon er lastet ned. Start applikasjonen på nytt for å installere."
-        :updater/quit-and-install "Start på nytt for å installere"
+           :updater/new-version-install "En ny versjon er lastet ned. Start applikasjonen på nytt for å installere."
+           :updater/quit-and-install "Start på nytt for å installere"
 
-        :paginates/pages "Totalt {1} sider"
-        :paginates/prev "Forrige"
-        :paginates/next "Neste"
+           :paginates/pages "Totalt {1} sider"
+           :paginates/prev "Forrige"
+           :paginates/next "Neste"
 
-        :tips/all-done "Alt ferdig"
+           :tips/all-done "Alt ferdig"
 
-        :command-palette/prompt "Skriv en kommando"
-        :select/default-prompt "Velg en"
-        :select.graph/prompt "Velg en graf"
-        :select.graph/empty-placeholder-description "Ingen grafer matcher. Vil du legge til en ny?"
-        :select.graph/add-graph "Ja, legg til en ny graf"}
+           :command-palette/prompt "Skriv en kommando"
+           :select/default-prompt "Velg en"
+           :select.graph/prompt "Velg en graf"
+           :select.graph/empty-placeholder-description "Ingen grafer matcher. Vil du legge til en ny?"
+           :select.graph/add-graph "Ja, legg til en ny graf"}
+
+   :pt-BR {:on-boarding/title "Olá, bem-vindo ao Logseq!"
+           :on-boarding/sharing "compartilhar"
+           :on-boarding/is-a " é um bloco de notas "
+           :on-boarding/vision "Uma plataforma de código-aberto focada na privacidade para gestão de conhecimento e colaboração."
+           :on-boarding/local-first "local"
+           :on-boarding/non-linear "não-linear"
+           :on-boarding/outliner "estruturador"
+           :on-boarding/notebook-for-organizing-and " para organização e "
+           :on-boarding/your-personal-knowledge-base " a sua base de conhecimento pessoal."
+           :on-boarding/notice "Note que este projeto está em fase de testes Beta e sob rápido desenvolvimento, lembre-se de fazer uma cópia de segurança regular pelo menos 1x/dia."
+           :on-boarding/features-desc "Use-o para organizar a sua lista de tarefas, para escrever os seus diários, ou para registrar o seu cotidiano."
+           :on-boarding/privacy "O servidor nunca irá armazenar ou analisar as suas notas privadas. Os seus dados são arquivos de texto simples, e suportamos neste momento Markdown e o modo Emacs Org. Mesmo que o servidor na web esteja fora do ar ou seja permanentemente desativado, os seus dados serão sempre seus."
+           :on-boarding/inspired-by " é fortemente inspirado por "
+           :on-boarding/where-are-my-notes-saved "Onde são guardadas as minhas notas?"
+           :on-boarding/storage "As suas notas serão salvas no armazenamento local do navegador usando "
+           :on-boarding/how-do-i-use-it "Como usar?"
+           :on-boarding/use-1 "1. Sincronizar entre vários dispositivos"
+           :on-boarding/use-1-desc "Atualmente, apenas suportamos sincronização através do GitHub, outras opções (git auto-hospedado, WebDAV, Google Drive, etc.) serão acrescentadas em breve."
+           :on-boarding/use-1-video "Confira este excelente vídeo de "
+           :on-boarding/use-2 "2. Usar localmente (sem precisar de iniciar sessão)"
+           :on-boarding/features "Funcionalidades"
+           :on-boarding/features-backlinks "Backlinks entre [[Página]]s"
+           :on-boarding/features-block-embed "Incorporar bloco"
+           :on-boarding/features-page-embed "Incorporar página"
+           :on-boarding/features-graph-vis "Visualização de grafo"
+           :on-boarding/features-heading-properties "Propriedades de cabeçalho"
+           :on-boarding/features-datalog "Consultas de Datalog, a base de dados de notas usa "
+           :on-boarding/features-custom-view-component "Vista personalizada de componente"
+           :on-boarding/integration " integração"
+           :on-boarding/slide-support " suporte para diapositivos"
+           :on-boarding/built-in-supports "Suporte integrado para:"
+           :on-boarding/supports-code-highlights "Destaque de código"
+           :on-boarding/supports-katex-latex "Katex latex"
+           :on-boarding/raw "Raw "
+           :on-boarding/raw-html "Html raw"
+           :on-boarding/learn-more "Saber mais"
+           :on-boarding/discord-desc " onde a comunidade faz perguntas e compartilha dicas"
+           :on-boarding/github-desc " todos são encorajados a relatar problemas!"
+           :on-boarding/our-blog "O nosso blog: "
+           :on-boarding/credits-to "Créditos a"
+           :on-boarding/clojure-desc " - Uma linguagem de programação dinâmica, funcional, e genérica"
+           :on-boarding/datascript-desc " - Base de dados imutável e motor de consulta de Datalog para Clojure, ClojureScript e JS"
+           :on-boarding/angstrom-desc-1 ", o documento "
+           :on-boarding/angstrom-desc-2 "analisador sintático (parser)"
+           :on-boarding/angstrom-desc-3 " é construído em Angstrom."
+           :on-boarding/cuekeeper-desc " - Sistema de GTD (lista de tarefas) baseado no navegador."
+           :on-boarding/sci-desc " - Interpretador Compacto de Clojure"
+           :on-boarding/isomorphic-git-desc " - Uma implementação de git em JavaScript puro para node e navegadores!"
+           :on-boarding/demo-graph "Esse é um grafo de demonstração, mudanças não serão salvas enquanto uma pasta local não for aberta."
+           :on-boarding/add-graph "Adicionar grafo"
+           :on-boarding/open-local-dir "Abrir pasta local"
+           :on-boarding/new-graph-desc-1 "Logseq funciona com Markdown e Org-mode. Você pode abrir uma pasta existente ou criar uma nova em seu dispositivo. Seus dados serão armazenados apenas neste dispositivo."
+           :on-boarding/new-graph-desc-2 "Após abrir sua pasta, três pastas serão criadas nela:"
+           :on-boarding/new-graph-desc-3 "/journals - armazena suas páginas diárias"
+           :on-boarding/new-graph-desc-4 "/pages - armazena as outras páginas"
+           :on-boarding/new-graph-desc-5 "/logseq - armazena configuração, custom.css e metadados."
+           :help/start "Começar a usar"
+           :help/about "Sobre o Logseq"
+           :help/roadmap "Plano de implementação"
+           :help/bug "Relato de erros"
+           :help/feature "Pedido de funcionalidades"
+           :help/changelog "Registo de alterações"
+           :help/blog "Blog do Logseq"
+           :help/docs "Documentação"
+           :help/privacy "Política de privacidade"
+           :help/terms "Termos"
+           :help/community "Comunidade de Discord"
+           :help/awesome-logseq "Awesome Logseq"
+           :help/shortcuts "Atalhos de Teclado"
+           :help/shortcuts-triggers "Gatilhos"
+           :help/shortcut "Atalho"
+           :help/slash-autocomplete "Auto-completar com a barra"
+           :help/block-content-autocomplete "Auto-completar conteúdo de bloco (Src, Quote, Query, etc)"
+           :help/reference-autocomplete "Auto-completar referência de páginas"
+           :help/block-reference "Referência de bloco"
+           :help/key-commands "Comandos chave"
+           :help/working-with-lists " (trabalhando com listas)"
+           :help/select-nfs-browser "Por favor use outro navegador (como o Chrome mais recente) com suporte para funcionalidades NFS para abrir pastas locais."
+           :undo "Desfazer"
+           :redo "Refazer"
+           :general "Geral"
+           :more "Mais"
+           :search/result-for "Resultado da pesquisa para "
+           :search/items "itens"
+           :help/context-menu "Menu contextual"
+           :help/fold-unfold "Ocultar/Expandir blocos (fora do modo de edição)"
+           :help/markdown-syntax "Sintaxe de Markdown"
+           :help/org-mode-syntax "Sintaxe de modo Org"
+           :bold "Negrito"
+           :italics "Itálico"
+           :html-link "Link Html"
+           :highlight "Realçado"
+           :strikethrough "Rasurado"
+           :code "Código"
+           :right-side-bar/help "Ajuda"
+           :right-side-bar/switch-theme "Temas"
+           :right-side-bar/theme "Tema {1}"
+           :right-side-bar/page "Grafo da página"
+           :right-side-bar/recent "Recente"
+           :right-side-bar/contents "Conteúdo"
+           :right-side-bar/favorites "Favoritos"
+           :right-side-bar/page-graph "Grafo da página"
+           :right-side-bar/block-ref "Referência de bloco"
+           :right-side-bar/journals "Diários"
+           :right-side-bar/flashcards "Flashcards"
+           :right-side-bar/new-page "Nova página"
+           :git/set-access-token "Definir token de acesso pessoal do GitHub"
+           :git/token-is-encrypted "O token será criptografado e guardado no armazenamento local do navegador"
+           :git/token-server "Nunca será armazenado pelo servidor"
+           :git/create-personal-access-token "Como criar um token de acesso pessoal do GitHub?"
+           :git/push "Enviar agora"
+           :git/push-failed "Envio falhou!"
+           :git/local-changes-synced "Todas as alterações locais foram sincronizadas!"
+           :git/pull "Puxar agora"
+           :git/last-pull "Última atualização em"
+           :git/version "Versão"
+           :git/import-notes "Importar as suas notas"
+           :git/import-notes-helper "Pode importar as suas notas de um repositório no GitHub."
+           :git/add-another-repo "Adicionar outro repositório"
+           :git/re-index "Clonar novamente e re-indexar a base de dados"
+           :git/message "Mensagem de consolidação"
+           :git/commit-and-push "Consolidar e enviar!"
+           :git/use-remote "Usar remotamente"
+           :git/keep-local "Manter localmente"
+           :git/edit "Editar"
+           :git/title "Diferenças"
+           :git/no-diffs "Sem diferenças"
+           :git/commit-message "Mensagem de consolidação (opcional)"
+           :git/pushing "Enviando"
+           :git/force-push "Consolidar e forçar envio"
+           :git/a-force-push "Envio forçado"
+           :git/add-repo-prompt "Instalar o Logseq no seu repositório"
+           :git/add-repo-prompt-confirm "Adicionar e instalar"
+           :format/preferred-mode "Qual o seu modo preferido?"
+           :format/markdown "Markdown"
+           :format/org-mode "Modo Org"
+           :reference/linked "Referência ligada"
+           :reference/unlinked-ref "Referências não ligadas"
+           :project/setup "Configurar um projeto público em Logseq"
+           :project/location "Todas as páginas publicadas ficarão localizadas em"
+           :project/sync-settings "Definições de sincronização do projeto"
+           :page/presentation-mode "Modo de apresentação"
+           :page/edit-properties-placeholder "Propriedades"
+           :page/delete-success "Página {1} apagada com sucesso!"
+           :page/delete-confirmation "Tem certeza que quer apagar esta página e o arquivo associado?"
+           :page/rename-to "Renomear \"{1}\" como:"
+           :page/priority "Prioridade \"{1}\""
+           :page/copy-to-json "Copiar a página inteira como JSON"
+           :page/rename "Renomear página"
+           :page/open-in-finder "Abrir em pasta"
+           :page/open-with-default-app "Abrir com a aplicação por omissão"
+           :page/action-publish "Publicar"
+           :page/make-public "Tornar pública para publicação"
+           :page/version-history "Ver histórico da página"
+           :page/make-private "Tornar privada"
+           :page/delete "Apagar página"
+           :page/publish "Publicar esta página em Logseq"
+           :page/cancel-publishing "Cancelar publicação em Logseq"
+           :page/publish-as-slide "Publicar esta página como um slide em Logseq"
+           :page/unpublish "Remover publicação desta página em Logseq"
+           :page/add-to-favorites "Adicionar aos Favoritos"
+           :page/show-journals "Mostrar Diários"
+           :page/show-name "Mostrar nome da página"
+           :page/hide-name "Esconder nome da página"
+           :block/name "Nome da página"
+           :page/last-modified "Última modificação em"
+           :page/new-title "Qual o novo título da nova página?"
+           :page/earlier "Antes"
+           :page/no-more-journals "Não há mais diários"
+           :publishing/pages "Páginas"
+           :publishing/page-name "Nome da página"
+           :publishing/current-project "Projeto Atual"
+           :publishing/delete-from-logseq "Apagar do servidor Logseq"
+           :publishing/edit "Editar"
+           :publishing/save "Guardar"
+           :publishing/cancel "Cancelar"
+           :publishing/delete "Apagar"
+           :journal/multiple-files-with-different-formats "Parece que tem vários arquivos (em vários formatos) de diário para o mesmo mês, por favor salve apenas um arquivo de diário para cada mês."
+           :journal/go-to "Ir para arquivos"
+           :file/name "Nome do arquivo"
+           :file/file "Arquivo: "
+           :file/last-modified-at "Última modificação em"
+           :file/no-data "Sem dados"
+           :file/format-not-supported "Formato .{1} não suportado."
+           :page/created-at "Criado em"
+           :page/updated-at "Atualizado em"
+           :page/backlinks "Back Links"
+           :editor/block-search "Pesquisar por um bloco"
+           :editor/image-uploading "Enviando"
+           :draw/invalid-file "Não foi possível carregar este arquivo excalidraw inválido"
+           :draw/specify-title "Por favor indique um título primeiro!"
+           :draw/rename-success "Arquivo foi renomeado com sucesso!"
+           :draw/rename-failure "Renomear arquivo falhou, motivo: "
+           :draw/title-placeholder "Sem título"
+           :draw/save "Guardar"
+           :draw/save-changes "Guardar alterações"
+           :draw/new-file "Novo arquivo"
+           :draw/list-files "Listar arquivos"
+           :draw/delete "Apagar"
+           :draw/more-options "Mais opções"
+           :draw/back-to-logseq "Voltar a logseq"
+           :text/image "Imagem"
+           :asset/confirm-delete "Tem certeza que quer apagar este {1}?"
+           :asset/physical-delete "Remover também o arquivo (não poderá ser restaurado)"
+           :content/copy "Copiar"
+           :content/cut "Cortar"
+           :content/make-todos "Fazer {1}s"
+           :content/copy-block-ref "Copiar referência do bloco"
+           :content/focus-on-block "Focar no bloco"
+           :content/open-in-sidebar "Abrir na barra lateral"
+           :content/copy-as-json "Copiar como JSON"
+           :content/click-to-edit "Clicar para editar"
+           :settings-page/edit-config-edn "Editar config.edn para o repositório atual"
+           :settings-page/git-desc "é usado para controle de versão das páginas, você pode clicar no menu de três pontos para verificar o histórico da página."
+           :settings-page/git-confirm "É necessário reiniciar a aplicação após atualizar as configurações do Git."
+           :settings-page/git-switcher-label "Habilitar auto-consolidação no Git"
+           :settings-page/git-commit-delay "Intervalo em segundos para auto-consolidação"
+           :settings-page/show-brackets "Mostrar parênteses retos"
+           :settings-page/spell-checker "Verificador ortográfico"
+           :settings-page/disable-sentry "Enviar dados de utilização e diagnósticos para Logseq"
+           :settings-page/preferred-outdenting "Ativar dedentação lógica"
+           :settings-page/custom-date-format "Formato de data preferido"
+           :settings-page/preferred-file-format "Formato de Arquivo preferido"
+           :settings-page/preferred-workflow "Fluxo de trabalho preferido"
+           :settings-page/enable-timetracking "Ativar controle de tempo"
+           :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"
+           :settings-page/enable-block-time "Ativar carimbos temporais nos blocos"
+           :settings-page/dont-use-other-peoples-proxy-servers "Não use servidores proxy de outras pessoas. É muito perigoso, o que pode levar a que o seu token e notas sejam roubadas. O Logseq não será responsável por estas perdas se usar servidores proxy de outras pessoas. Pode criar o seu, veja "
+           :settings-page/clear-cache "Limpar cache"
+           :settings-page/clear "Limpar"
+           :settings-page/custom-cors-proxy-server "Servidor proxy CORS personalizado"
+           :settings-page/developer-mode "Modo de desenvolvimento"
+           :settings-page/enable-developer-mode "Ativar modo de desenvolvimento"
+           :settings-page/disable-developer-mode "Desativar modo de desenvolvimento"
+           :settings-page/developer-mode-desc "O modo de desenvolvimento ajuda os contribuidores e programadores de extensões a testar as suas integrações com o Logseq de forma eficiente."
+           :settings-page/current-version "Versão atual"
+           :settings-page/current-graph "Grafo atual"
+           :settings-page/tab-general "Geral"
+           :settings-page/tab-editor "Editor"
+           :settings-page/tab-shortcuts "Atalhos"
+           :settings-page/tab-advanced "Avançado"
+           :settings-page/tab-version-control "Controle de Versões"
+           :logseq "Logseq"
+           :on "ON"
+           :more-options "Mais opções"
+           :to "para"
+           :yes "Sim"
+           :no "Não"
+           :submit "Submeter"
+           :cancel "Cancelar"
+           :close "Fechar"
+           :delete "Apagar"
+           :re-index "Re-indexar"
+           :unlink "remover ligação"
+           :search (if config/publishing?
+                     "Pesquisar"
+                     "Pesquisar ou Criar Página")
+           :page-search "Pesquisar na página atual"
+           :graph-search "Pesquisar grafo"
+           :new-page "Nova página"
+           :new-file "Novo arquivo"
+           :new-graph "Adicionar novo grafo"
+           :graph "Grafo"
+           :graph-view "Ver Grafo"
+           :cards-view "Ver Cartões"
+           :publishing "Publicar"
+           :export "Exportar"
+           :export-graph "Exportar Grafo"
+           :export-markdown "Exportar como Markdown padrão (sem propriedades de bloco)"
+           :export-opml "Exportar como OPML"
+           :export-page "Exportar página"
+           :export-public-pages "Exportar páginas públicas"
+           :export-json "Exportar como JSON"
+           :export-roam-json "Exportar como Roam JSON"
+           :export-edn "Exportar como EDN"
+           :export-datascript-edn "Exportar datascript EDN"
+           :convert-markdown "Converter cabeçalhos Markdown para listas não-ordenadas (# -> -)"
+           :all-graphs "Todos os grafos"
+           :all-pages "Todas as páginas"
+           :all-files "Todos os arquivos"
+           :all-journals "Todos os diários"
+           :my-publishing "Minhas publicações"
+           :settings "Definições"
+           :plugins "Plugins"
+           :themes "Temas"
+           :developer-mode-alert "É necessário reiniciar a aplicação para ativar o sistema de plugins. Quer reiniciar agora?"
+           :relaunch-confirm-to-work "Deve reiniciar a aplicação para fazê-lo funcionar. Quer reiniciar agora?"
+           :import "Importar"
+           :join-community "Junte-se à comunidade"
+           :sponsor-us "Seja um patrocinador"
+           :discord-title "O nosso grupo de discord!"
+           :sign-out "Sair da sessão"
+           :help-shortcut-title "Clique para ver atalhos e outras dicas"
+           :loading "Carregando"
+           :cloning "Clonando"
+           :parsing-files "Analisando arquivos"
+           :loading-files "Carregando arquivos"
+           :login-github "Iniciar sessão com GitHub"
+           :login "Iniciar sessão"
+           :go-to "Ir para "
+           :or "ou"
+           :download "Baixar"
+           :repo/download-zip "Baixar um zip com todos os arquivos"
+           :language "Linguagem"
+           :white "Claro"
+           :dark "Escuro"
+           :remove-background "Remover fundo"
+           :re-index-detail "Re-indexar grafo"
+           :open "Abrir"
+           :open-a-directory "Abrir uma pasta local"
+           :open-new-window "Nova janela"
+           :user/delete-account "Apagar conta"
+           :user/delete-your-account "Apague a sua conta"
+           :user/delete-account-notice "Todas as suas páginas publicadas em logseq.com serão apagadas."
 
-   :pt-BR
-   {:on-boarding/title "Olá, bem-vindo ao Logseq!"
-    :on-boarding/sharing "compartilhar"
-    :on-boarding/is-a " é um bloco de notas "
-    :on-boarding/vision "Uma plataforma de código-aberto focada na privacidade para gestão de conhecimento e colaboração."
-    :on-boarding/local-first "local"
-    :on-boarding/non-linear "não-linear"
-    :on-boarding/outliner "estruturador"
-    :on-boarding/notebook-for-organizing-and " para organização e "
-    :on-boarding/your-personal-knowledge-base " a sua base de conhecimento pessoal."
-    :on-boarding/notice "Note que este projeto está em fase de testes Beta e sob rápido desenvolvimento, lembre-se de fazer uma cópia de segurança regular pelo menos 1x/dia."
-    :on-boarding/features-desc "Use-o para organizar a sua lista de tarefas, para escrever os seus diários, ou para registrar o seu cotidiano."
-    :on-boarding/privacy "O servidor nunca irá armazenar ou analisar as suas notas privadas. Os seus dados são arquivos de texto simples, e suportamos neste momento Markdown e o modo Emacs Org. Mesmo que o servidor na web esteja fora do ar ou seja permanentemente desativado, os seus dados serão sempre seus."
-    :on-boarding/inspired-by " é fortemente inspirado por "
-    :on-boarding/where-are-my-notes-saved "Onde são guardadas as minhas notas?"
-    :on-boarding/storage "As suas notas serão salvas no armazenamento local do navegador usando "
-    :on-boarding/how-do-i-use-it "Como usar?"
-    :on-boarding/use-1 "1. Sincronizar entre vários dispositivos"
-    :on-boarding/use-1-desc "Atualmente, apenas suportamos sincronização através do GitHub, outras opções (git auto-hospedado, WebDAV, Google Drive, etc.) serão acrescentadas em breve."
-    :on-boarding/use-1-video "Confira este excelente vídeo de "
-    :on-boarding/use-2 "2. Usar localmente (sem precisar de iniciar sessão)"
-    :on-boarding/features "Funcionalidades"
-    :on-boarding/features-backlinks "Backlinks entre [[Página]]s"
-    :on-boarding/features-block-embed "Incorporar bloco"
-    :on-boarding/features-page-embed "Incorporar página"
-    :on-boarding/features-graph-vis "Visualização de grafo"
-    :on-boarding/features-heading-properties "Propriedades de cabeçalho"
-    :on-boarding/features-datalog "Consultas de Datalog, a base de dados de notas usa "
-    :on-boarding/features-custom-view-component "Vista personalizada de componente"
-    :on-boarding/integration " integração"
-    :on-boarding/slide-support " suporte para diapositivos"
-    :on-boarding/built-in-supports "Suporte integrado para:"
-    :on-boarding/supports-code-highlights "Destaque de código"
-    :on-boarding/supports-katex-latex "Katex latex"
-    :on-boarding/raw "Raw "
-    :on-boarding/raw-html "Html raw"
-    :on-boarding/learn-more "Saber mais"
-    :on-boarding/discord-desc " onde a comunidade faz perguntas e compartilha dicas"
-    :on-boarding/github-desc " todos são encorajados a relatar problemas!"
-    :on-boarding/our-blog "O nosso blog: "
-    :on-boarding/credits-to "Créditos a"
-    :on-boarding/clojure-desc " - Uma linguagem de programação dinâmica, funcional, e genérica"
-    :on-boarding/datascript-desc " - Base de dados imutável e motor de consulta de Datalog para Clojure, ClojureScript e JS"
-    :on-boarding/angstrom-desc-1 ", o documento "
-    :on-boarding/angstrom-desc-2 "analisador sintático (parser)"
-    :on-boarding/angstrom-desc-3 " é construído em Angstrom."
-    :on-boarding/cuekeeper-desc " - Sistema de GTD (lista de tarefas) baseado no navegador."
-    :on-boarding/sci-desc " - Interpretador Compacto de Clojure"
-    :on-boarding/isomorphic-git-desc " - Uma implementação de git em JavaScript puro para node e navegadores!"
-    :on-boarding/demo-graph "Esse é um grafo de demonstração, mudanças não serão salvas enquanto uma pasta local não for aberta."
-    :on-boarding/add-graph "Adicionar grafo"
-    :on-boarding/open-local-dir "Abrir pasta local"
-    :on-boarding/new-graph-desc-1 "Logseq funciona com Markdown e Org-mode. Você pode abrir uma pasta existente ou criar uma nova em seu dispositivo. Seus dados serão armazenados apenas neste dispositivo."
-    :on-boarding/new-graph-desc-2 "Após abrir sua pasta, três pastas serão criadas nela:"
-    :on-boarding/new-graph-desc-3 "/journals - armazena suas páginas diárias"
-    :on-boarding/new-graph-desc-4 "/pages - armazena as outras páginas"
-    :on-boarding/new-graph-desc-5 "/logseq - armazena configuração, custom.css e metadados."
-    :help/start "Começar a usar"
-    :help/about "Sobre o Logseq"
-    :help/roadmap "Plano de implementação"
-    :help/bug "Relato de erros"
-    :help/feature "Pedido de funcionalidades"
-    :help/changelog "Registo de alterações"
-    :help/blog "Blog do Logseq"
-    :help/docs "Documentação"
-    :help/privacy "Política de privacidade"
-    :help/terms "Termos"
-    :help/community "Comunidade de Discord"
-    :help/awesome-logseq "Awesome Logseq"
-    :help/shortcuts "Atalhos de Teclado"
-    :help/shortcuts-triggers "Gatilhos"
-    :help/shortcut "Atalho"
-    :help/slash-autocomplete "Auto-completar com a barra"
-    :help/block-content-autocomplete "Auto-completar conteúdo de bloco (Src, Quote, Query, etc)"
-    :help/reference-autocomplete "Auto-completar referência de páginas"
-    :help/block-reference "Referência de bloco"
-    :help/key-commands "Comandos chave"
-    :help/working-with-lists " (trabalhando com listas)"
-    :help/select-nfs-browser "Por favor use outro navegador (como o Chrome mais recente) com suporte para funcionalidades NFS para abrir pastas locais."
-    :undo "Desfazer"
-    :redo "Refazer"
-    :general "Geral"
-    :more "Mais"
-    :search/result-for "Resultado da pesquisa para "
-    :search/items "itens"
-    :help/context-menu "Menu contextual"
-    :help/fold-unfold "Ocultar/Expandir blocos (fora do modo de edição)"
-    :help/markdown-syntax "Sintaxe de Markdown"
-    :help/org-mode-syntax "Sintaxe de modo Org"
-    :bold "Negrito"
-    :italics "Itálico"
-    :html-link "Link Html"
-    :highlight "Realçado"
-    :strikethrough "Rasurado"
-    :code "Código"
-    :right-side-bar/help "Ajuda"
-    :right-side-bar/switch-theme "Temas"
-    :right-side-bar/theme "Tema {1}"
-    :right-side-bar/page "Grafo da página"
-    :right-side-bar/recent "Recente"
-    :right-side-bar/contents "Conteúdo"
-    :right-side-bar/favorites "Favoritos"
-    :right-side-bar/page-graph "Grafo da página"
-    :right-side-bar/block-ref "Referência de bloco"
-    :right-side-bar/journals "Diários"
-    :right-side-bar/flashcards "Flashcards"
-    :right-side-bar/new-page "Nova página"
-    :git/set-access-token "Definir token de acesso pessoal do GitHub"
-    :git/token-is-encrypted "O token será criptografado e guardado no armazenamento local do navegador"
-    :git/token-server "Nunca será armazenado pelo servidor"
-    :git/create-personal-access-token "Como criar um token de acesso pessoal do GitHub?"
-    :git/push "Enviar agora"
-    :git/push-failed "Envio falhou!"
-    :git/local-changes-synced "Todas as alterações locais foram sincronizadas!"
-    :git/pull "Puxar agora"
-    :git/last-pull "Última atualização em"
-    :git/version "Versão"
-    :git/import-notes "Importar as suas notas"
-    :git/import-notes-helper "Pode importar as suas notas de um repositório no GitHub."
-    :git/add-another-repo "Adicionar outro repositório"
-    :git/re-index "Clonar novamente e re-indexar a base de dados"
-    :git/message "Mensagem de consolidação"
-    :git/commit-and-push "Consolidar e enviar!"
-    :git/use-remote "Usar remotamente"
-    :git/keep-local "Manter localmente"
-    :git/edit "Editar"
-    :git/title "Diferenças"
-    :git/no-diffs "Sem diferenças"
-    :git/commit-message "Mensagem de consolidação (opcional)"
-    :git/pushing "Enviando"
-    :git/force-push "Consolidar e forçar envio"
-    :git/a-force-push "Envio forçado"
-    :git/add-repo-prompt "Instalar o Logseq no seu repositório"
-    :git/add-repo-prompt-confirm "Adicionar e instalar"
-    :format/preferred-mode "Qual o seu modo preferido?"
-    :format/markdown "Markdown"
-    :format/org-mode "Modo Org"
-    :reference/linked "Referência ligada"
-    :reference/unlinked-ref "Referências não ligadas"
-    :project/setup "Configurar um projeto público em Logseq"
-    :project/location "Todas as páginas publicadas ficarão localizadas em"
-    :project/sync-settings "Definições de sincronização do projeto"
-    :page/presentation-mode "Modo de apresentação"
-    :page/edit-properties-placeholder "Propriedades"
-    :page/delete-success "Página {1} apagada com sucesso!"
-    :page/delete-confirmation "Tem certeza que quer apagar esta página e o arquivo associado?"
-    :page/rename-to "Renomear \"{1}\" como:"
-    :page/priority "Prioridade \"{1}\""
-    :page/copy-to-json "Copiar a página inteira como JSON"
-    :page/rename "Renomear página"
-    :page/open-in-finder "Abrir em pasta"
-    :page/open-with-default-app "Abrir com a aplicação por omissão"
-    :page/action-publish "Publicar"
-    :page/make-public "Tornar pública para publicação"
-    :page/version-history "Ver histórico da página"
-    :page/make-private "Tornar privada"
-    :page/delete "Apagar página"
-    :page/publish "Publicar esta página em Logseq"
-    :page/cancel-publishing "Cancelar publicação em Logseq"
-    :page/publish-as-slide "Publicar esta página como um slide em Logseq"
-    :page/unpublish "Remover publicação desta página em Logseq"
-    :page/add-to-favorites "Adicionar aos Favoritos"
-    :page/show-journals "Mostrar Diários"
-    :page/show-name "Mostrar nome da página"
-    :page/hide-name "Esconder nome da página"
-    :block/name "Nome da página"
-    :page/last-modified "Última modificação em"
-    :page/new-title "Qual o novo título da nova página?"
-    :page/earlier "Antes"
-    :page/no-more-journals "Não há mais diários"
-    :publishing/pages "Páginas"
-    :publishing/page-name "Nome da página"
-    :publishing/current-project "Projeto Atual"
-    :publishing/delete-from-logseq "Apagar do servidor Logseq"
-    :publishing/edit "Editar"
-    :publishing/save "Guardar"
-    :publishing/cancel "Cancelar"
-    :publishing/delete "Apagar"
-    :journal/multiple-files-with-different-formats "Parece que tem vários arquivos (em vários formatos) de diário para o mesmo mês, por favor salve apenas um arquivo de diário para cada mês."
-    :journal/go-to "Ir para arquivos"
-    :file/name "Nome do arquivo"
-    :file/file "Arquivo: "
-    :file/last-modified-at "Última modificação em"
-    :file/no-data "Sem dados"
-    :file/format-not-supported "Formato .{1} não suportado."
-    :page/created-at "Criado em"
-    :page/updated-at "Atualizado em"
-    :page/backlinks "Back Links"
-    :editor/block-search "Pesquisar por um bloco"
-    :editor/image-uploading "Enviando"
-    :draw/invalid-file "Não foi possível carregar este arquivo excalidraw inválido"
-    :draw/specify-title "Por favor indique um título primeiro!"
-    :draw/rename-success "Arquivo foi renomeado com sucesso!"
-    :draw/rename-failure "Renomear arquivo falhou, motivo: "
-    :draw/title-placeholder "Sem título"
-    :draw/save "Guardar"
-    :draw/save-changes "Guardar alterações"
-    :draw/new-file "Novo arquivo"
-    :draw/list-files "Listar arquivos"
-    :draw/delete "Apagar"
-    :draw/more-options "Mais opções"
-    :draw/back-to-logseq "Voltar a logseq"
-    :text/image "Imagem"
-    :asset/confirm-delete "Tem certeza que quer apagar este {1}?"
-    :asset/physical-delete "Remover também o arquivo (não poderá ser restaurado)"
-    :content/copy "Copiar"
-    :content/cut "Cortar"
-    :content/make-todos "Fazer {1}s"
-    :content/copy-block-ref "Copiar referência do bloco"
-    :content/focus-on-block "Focar no bloco"
-    :content/open-in-sidebar "Abrir na barra lateral"
-    :content/copy-as-json "Copiar como JSON"
-    :content/click-to-edit "Clicar para editar"
-    :settings-page/edit-config-edn "Editar config.edn para o repositório atual"
-    :settings-page/git-desc "é usado para controle de versão das páginas, você pode clicar no menu de três pontos para verificar o histórico da página."
-    :settings-page/git-confirm "É necessário reiniciar a aplicação após atualizar as configurações do Git."
-    :settings-page/git-switcher-label "Habilitar auto-consolidação no Git"
-    :settings-page/git-commit-delay "Intervalo em segundos para auto-consolidação"
-    :settings-page/show-brackets "Mostrar parênteses retos"
-    :settings-page/spell-checker "Verificador ortográfico"
-    :settings-page/disable-sentry "Enviar dados de utilização e diagnósticos para Logseq"
-    :settings-page/preferred-outdenting "Ativar dedentação lógica"
-    :settings-page/custom-date-format "Formato de data preferido"
-    :settings-page/preferred-file-format "Formato de Arquivo preferido"
-    :settings-page/preferred-workflow "Fluxo de trabalho preferido"
-    :settings-page/enable-timetracking "Ativar controle de tempo"
-    :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"
-    :settings-page/enable-block-time "Ativar carimbos temporais nos blocos"
-    :settings-page/dont-use-other-peoples-proxy-servers "Não use servidores proxy de outras pessoas. É muito perigoso, o que pode levar a que o seu token e notas sejam roubadas. O Logseq não será responsável por estas perdas se usar servidores proxy de outras pessoas. Pode criar o seu, veja "
-    :settings-page/clear-cache "Limpar cache"
-    :settings-page/clear "Limpar"
-    :settings-page/custom-cors-proxy-server "Servidor proxy CORS personalizado"
-    :settings-page/developer-mode "Modo de desenvolvimento"
-    :settings-page/enable-developer-mode "Ativar modo de desenvolvimento"
-    :settings-page/disable-developer-mode "Desativar modo de desenvolvimento"
-    :settings-page/developer-mode-desc "O modo de desenvolvimento ajuda os contribuidores e programadores de extensões a testar as suas integrações com o Logseq de forma eficiente."
-    :settings-page/current-version "Versão atual"
-    :settings-page/current-graph "Grafo atual"
-    :settings-page/tab-general "Geral"
-    :settings-page/tab-editor "Editor"
-    :settings-page/tab-shortcuts "Atalhos"
-    :settings-page/tab-advanced "Avançado"
-    :settings-page/tab-version-control "Controle de Versões"
-    :logseq "Logseq"
-    :on "ON"
-    :more-options "Mais opções"
-    :to "para"
-    :yes "Sim"
-    :no "Não"
-    :submit "Submeter"
-    :cancel "Cancelar"
-    :close "Fechar"
-    :delete "Apagar"
-    :re-index "Re-indexar"
-    :unlink "remover ligação"
-    :search (if config/publishing?
-              "Pesquisar"
-              "Pesquisar ou Criar Página")
-    :page-search "Pesquisar na página atual"
-    :graph-search "Pesquisar grafo"
-    :new-page "Nova página"
-    :new-file "Novo arquivo"
-    :new-graph "Adicionar novo grafo"
-    :graph "Grafo"
-    :graph-view "Ver Grafo"
-    :cards-view "Ver Cartões"
-    :publishing "Publicar"
-    :export "Exportar"
-    :export-markdown "Exportar como Markdown padrão (sem propriedades de bloco)"
-    :export-opml "Exportar como OPML"
-    :export-public-pages "Exportar páginas públicas"
-    :export-json "Exportar como JSON"
-    :export-roam-json "Exportar como Roam JSON"
-    :export-edn "Exportar como EDN"
-    :export-datascript-edn "Exportar datascript EDN"
-    :convert-markdown "Converter cabeçalhos Markdown para listas não-ordenadas (# -> -)"
-    :all-graphs "Todos os grafos"
-    :all-pages "Todas as páginas"
-    :all-files "Todos os arquivos"
-    :all-journals "Todos os diários"
-    :my-publishing "Minhas publicações"
-    :settings "Definições"
-    :plugins "Plugins"
-    :themes "Temas"
-    :developer-mode-alert "É necessário reiniciar a aplicação para ativar o sistema de plugins. Quer reiniciar agora?"
-    :relaunch-confirm-to-work "Deve reiniciar a aplicação para fazê-lo funcionar. Quer reiniciar agora?"
-    :import "Importar"
-    :join-community "Junte-se à comunidade"
-    :sponsor-us "Seja um patrocinador"
-    :discord-title "O nosso grupo de discord!"
-    :sign-out "Sair da sessão"
-    :help-shortcut-title "Clique para ver atalhos e outras dicas"
-    :loading "Carregando"
-    :cloning "Clonando"
-    :parsing-files "Analisando arquivos"
-    :loading-files "Carregando arquivos"
-    :login-github "Iniciar sessão com GitHub"
-    :login "Iniciar sessão"
-    :go-to "Ir para "
-    :or "ou"
-    :download "Baixar"
-    :repo/download-zip "Baixar um zip com todos os arquivos"
-    :language "Linguagem"
-    :white "Claro"
-    :dark "Escuro"
-    :remove-background "Remover fundo"
-    :open "Abrir"
-    :open-a-directory "Abrir uma pasta local"
-    :user/delete-account "Apagar conta"
-    :user/delete-your-account "Apague a sua conta"
-    :user/delete-account-notice "Todas as suas páginas publicadas em logseq.com serão apagadas."
+           :help/shortcut-page-title "Atalhos de teclado"
 
-    :help/shortcut-page-title "Atalhos de teclado"
+           :pdf/copy-ref "Copiar referência"
+           :pdf/copy-text "Copiar texto"
+           :pdf/linked-ref "Referências ligadas"
+           :command-palette/prompt "Digite um comando"
+           :remove-orphaned-pages "Remover páginas órfãs"
+           :sync-from-local-files "Recarregar arquivos"
+           :sync-from-local-files-detail "Importar modificações de arquivos"
+           :content/copy-block-emebed "Copiar bloco para incorporar"
+           :left-side-bar/nav-favorites "Favoritos"
+           :left-side-bar/nav-recent-pages "Recentes"
+           :left-side-bar/nav-shortcuts "Atalhos"
+           :left-side-bar/new-page "Nova página"
+           :page/unfavorite "Tirar página dos favoritos"
+           :paginates/next "Próximo"
+           :paginates/prev "Anterior"
+           :pdf/toggle-dashed "Destaque estilo pontilhado"
+           :plugin/all "Todos"
+           :plugin/check-all-updates "Verificar atualizações"
+           :plugin/check-update "Verificar atualização"
+           :plugin/contribute "✨ Crie e envie um novo plugin"
+           :plugin/custom-js-alert "Arquivo custom.js encontrado, deseja habilitar sua execução? (A execução deste arquivo pode trazer riscos, se você não conhece o conteúdo do arquivo é recomendado que você não ative esta opção.)"
 
-    :pdf/copy-ref "Copiar referência"
-    :pdf/copy-text "Copiar texto"
-    :pdf/linked-ref "Referências ligadas"
-    :command-palette/prompt "Digite um comando"}
+           :plugin/delete-alert "Certeza que deseja excluir o plugin? [{1}]?"
+           :plugin/disabled "Desabilitado"
+           :plugin/downloads "Downloads"
+           :plugin/enabled "Habilitado"
+           :plugin/install "Instalar"
+           :plugin/installed "Instalado"
+           :plugin/installing "Instalando"
+           :plugin/load-unpacked "Carregar plugin descompactado"
+           :plugin/marketplace "Loja"
+           :plugin/marketplace-tips "Se o plugin não funcionar direito depois de instalado, tente reiniciar o Logseq."
+           :plugin/open-preferences "Abrir o arquivo de preferências do plguin"
+           :plugin/open-settings "Abrir configurações"
+           :plugin/refresh-lists "Recarregar lista"
+           :plugin/restart "Reiniciar o App"
+           :plugin/title "Título"
+           :plugin/uninstall "Desinstalar"
+           :plugin/unpacked-tips "Selecione a pasta do plugin"
+           :plugin/up-to-date "Está atualizado"
+           :plugin/update "Atualizar"
+           :plugin/update-available "Atualização disponível"
+           :plugin/updating "Atualizando"
+           :right-side-bar/all-pages "Todas as páginas"
+           :right-side-bar/graph-view "Ver grafo"
+           :search/page-names "Procurar nome da página"
+           :plugin/stars "Estrelas"
+           :select/default-prompt "Selecione um"
+           :select.graph/prompt "Selecione um grafo"
+           :select.graph/add-graph "Sim, adicionar outro grafo"
+           :select.graph/empty-placeholder-description "Nenhum grafo encontrado. Deseja adicionar um novo?"
+           :settings-page/enable-shortcut-tooltip "Habilitar dicas de atalho"
+           :tips/all-done "Tudo certo"
+           :updater/new-version-install "Uma nova versão foi baixada"
+           :updater/quit-and-install "Reinicie para instalar"}
 
-   :pt-PT
-   {:on-boarding/title "Olá, bem-vindo ao Logseq!"
-    :on-boarding/sharing "partilhar"
-    :on-boarding/is-a " é um bloco de notas "
-    :on-boarding/vision "Uma plataforma de código-aberto focada na privacidade para gestão de conhecimento e colaboração."
-    :on-boarding/local-first "local"
-    :on-boarding/non-linear "não-linear"
-    :on-boarding/outliner "delineador"
-    :on-boarding/notebook-for-organizing-and " para organização e "
-    :on-boarding/your-personal-knowledge-base " a sua base de conhecimento pessoal."
-    :on-boarding/notice "Note que este projeto está em fase de testes Beta e sob rápido desenvolvimento, lembre-se de fazer uma cópia de segurança regular pelo menos 1x/dia."
-    :on-boarding/features-desc "Use-o para organizar a sua lista de tarefas, para escrever os seus diários, ou para registar o seu quotidiano."
-    :on-boarding/privacy "O servidor nunca irá armazenar ou analisar as suas notas privadas. Os seus dados são ficheiros de texto simples, e suportamos neste momento Markdown e o modo Emacs Org. Mesmo que o sítio web esteja em baixo ou não possa ser mantido, os seus dados são sempre seus."
-    :on-boarding/inspired-by " é fortemente inspirado por "
-    :on-boarding/where-are-my-notes-saved "Onde são guardadas as minhas notas?"
-    :on-boarding/storage "A suas notas serão guardadas no armazenamento local do navegador usando "
-    :on-boarding/how-do-i-use-it "Como usar?"
-    :on-boarding/use-1 "1. Sincronizar entre vários dispositivos"
-    :on-boarding/use-1-desc "Atualmente, apenas suportamos sincronização através do GitHub, mais opções (git auto-hospedado, WebDAV, Google Drive, etc.) serão acrescentadas em breve."
-    :on-boarding/use-1-video "Confira este excelente vídeo de "
-    :on-boarding/use-2 "2. Usar localmente (sem precisar de iniciar sessão)"
-    :on-boarding/features "Funcionalidades"
-    :on-boarding/features-backlinks "Backlinks entre [[Página]]s"
-    :on-boarding/features-block-embed "Incorporar bloco"
-    :on-boarding/features-page-embed "Incorporar página"
-    :on-boarding/features-graph-vis "Visualização de grafo"
-    :on-boarding/features-heading-properties "Propriedades de cabeçalho"
-    :on-boarding/features-datalog "Consultas de Datalog, a base de dados de notas usa "
-    :on-boarding/features-custom-view-component "Vista personalizada de componente"
-    :on-boarding/integration " integração"
-    :on-boarding/slide-support " suporte para diapositivos"
-    :on-boarding/built-in-supports "Suporte integrado para:"
-    :on-boarding/supports-code-highlights "Destaque de código"
-    :on-boarding/supports-katex-latex "Katex latex"
-    :on-boarding/raw "Raw "
-    :on-boarding/raw-html "Html raw"
-    :on-boarding/learn-more "Saber mais"
-    :on-boarding/discord-desc " onde a comunidade faz perguntas e partilha dicas"
-    :on-boarding/github-desc " toda a gente é encorajada a reportar problemas!"
-    :on-boarding/our-blog "O nosso blogue: "
-    :on-boarding/credits-to "Créditos a"
-    :on-boarding/clojure-desc " - Uma linguagem de programação dinâmica, funcional, e generalista"
-    :on-boarding/datascript-desc " - Base de dados imutável e motor de consulta de Datalog para Clojure, ClojureScript e JS"
-    :on-boarding/angstrom-desc-1 ", o documento "
-    :on-boarding/angstrom-desc-2 "analisador sintático (parser)"
-    :on-boarding/angstrom-desc-3 " é construído em Angstrom."
-    :on-boarding/cuekeeper-desc " - Sistema de GTD (lista de tarefas) baseado no navegador."
-    :on-boarding/sci-desc " - Interpretador Compacto de Clojure"
-    :on-boarding/isomorphic-git-desc " - Uma implementação de git em JavaScript puro para node e navegadores!"
-    :on-boarding/add-graph "Adicionar grafo"
-    :help/start "Começar a usar"
-    :help/about "Sobre o Logseq"
-    :help/roadmap "Plano de implementação"
-    :help/bug "Reporte de erros"
-    :help/feature "Pedido de funcionalidades"
-    :help/changelog "Registo de alterações"
-    :help/blog "Blogue de Logseq"
-    :help/docs "Documentação"
-    :help/privacy "Política de privacidade"
-    :help/terms "Termos"
-    :help/community "Comunidade de Discord"
-    :help/awesome-logseq "Awesome Logseq"
-    :help/shortcuts "Atalhos de Teclado"
-    :help/shortcuts-triggers "Iniciadores"
-    :help/shortcut "Atalho"
-    :help/slash-autocomplete "Auto-completar com Slash"
-    :help/block-content-autocomplete "Auto-completar conteúdo de bloco (Src, Quote, Query, etc)"
-    :help/reference-autocomplete "Auto-completar referência de páginas"
-    :help/block-reference "Referência de bloco"
-    :help/key-commands "Comandos chave"
-    :help/working-with-lists " (trabalhar com listas)"
-    :help/select-nfs-browser "Por favor use outro navegador (como o Chrome mais recente) com suporte para funcionalidades NFS para abrir pastas locais."
-    :undo "Desfazer"
-    :redo "Refazer"
-    :general "Geral"
-    :more "Mais"
-    :search/result-for "Resultado da pesquisa para "
-    :search/items "itens"
-    :help/context-menu "Menu contextual"
-    :help/fold-unfold "Ocultar/Expandir blocos (fora do modo de edição)"
-    :help/markdown-syntax "Sintaxe de Markdown"
-    :help/org-mode-syntax "Sintaxa de modo Org"
-    :bold "Negrito"
-    :italics "Itálico"
-    :html-link "Link Html"
-    :highlight "Realçado"
-    :strikethrough "Rasurado"
-    :code "Código"
-    :right-side-bar/help "Ajuda"
-    :right-side-bar/switch-theme "Temas"
-    :right-side-bar/theme "Tema {1}"
-    :right-side-bar/page "Grafo da página"
-    :right-side-bar/recent "Recente"
-    :right-side-bar/contents "Conteúdo"
-    :right-side-bar/favorites "Favoritos"
-    :right-side-bar/page-graph "Grafo da página"
-    :right-side-bar/block-ref "Referência de bloco"
-    :right-side-bar/journals "Journals"
-    :right-side-bar/graph-view "Graph view"
-    :right-side-bar/all-pages "All pages"
-    :right-side-bar/flashcards "Flashcards"
-    :right-side-bar/new-page "New page"
-    :git/set-access-token "Definir token de acesso pessoal do GitHub"
-    :git/token-is-encrypted "O token será encriptado e guardado no armazenamento local do navegador"
-    :git/token-server "O servidor nunca o irá guardar"
-    :git/create-personal-access-token "Como criar um token de acesso pessoal do GitHub?"
-    :git/push "Fazer push agora"
-    :git/push-failed "Push falhou!"
-    :git/local-changes-synced "Todas as alterações locais foram sincronizadas!"
-    :git/pull "Fazer pull agora"
-    :git/last-pull "Último pull em"
-    :git/version "Versão"
-    :git/import-notes "Importar as suas notas"
-    :git/import-notes-helper "Pode importar as suas notas de um repositório no GitHub."
-    :git/add-another-repo "Adicionar outro repositório"
-    :git/re-index "Clonar novamente e re-indexar a base de dados"
-    :git/message "A sua mensagem de commit"
-    :git/commit-and-push "Fazer commit e push!"
-    :git/use-remote "Usar remotamente"
-    :git/keep-local "Manter localmente"
-    :git/edit "Editar"
-    :git/title "Diff"
-    :git/no-diffs "Sem diffs"
-    :git/commit-message "Mensagem de commit (opcional)"
-    :git/pushing "A fazer push"
-    :git/force-push "Fazer commit e forçar push"
-    :git/a-force-push "Push forçado"
-    :git/add-repo-prompt "Instalar o Logseq no seu repositório"
-    :git/add-repo-prompt-confirm "Adicionar e instalar"
-    :format/preferred-mode "Qual o seu modo preferido?"
-    :format/markdown "Markdown"
-    :format/org-mode "Modo Org"
-    :reference/linked "Referência ligada"
-    :reference/unlinked-ref "Referências não ligadas"
-    :project/setup "Configurar um projeto público em Logseq"
-    :project/location "Todas as páginas publicadas ficarão localizadas em"
-    :project/sync-settings "Definições de sincronização do projeto"
-    :page/presentation-mode "Modo de apresentação"
-    :page/edit-properties-placeholder "Propriedades"
-    :page/delete-success "Página {1} apagada com sucesso!"
-    :page/delete-confirmation "Tem a certeza de que quer apagar esta página e o respetivo ficheiro?"
-    :page/rename-to "Renomear \"{1}\" como:"
-    :page/priority "Prioridade \"{1}\""
-    :page/copy-to-json "Copiar a página inteira como JSON"
-    :page/rename "Renomear página"
-    :page/open-in-finder "Abrir em pasta"
-    :page/open-with-default-app "Abrir com a aplicação por omissão"
-    :page/action-publish "Publicar"
-    :page/make-public "Tornar pública para publicação"
-    :page/version-history "Ver histórico da página"
-    :page/make-private "Tornar privada"
-    :page/delete "Apagar página"
-    :page/publish "Publicar esta página em Logseq"
-    :page/cancel-publishing "Cancelar publicação em Logseq"
-    :page/publish-as-slide "Publicar esta página como um diapositivo em Logseq"
-    :page/unpublish "Remover publicação desta página em Logseq"
-    :page/add-to-favorites "Adicionar aos Favoritos"
-    :page/show-journals "Mostrar Diários"
-    :page/show-name "Mostrar nome da página"
-    :page/hide-name "Esconder nome da página"
-    :block/name "Nome da página"
-    :page/last-modified "Última modificação em"
-    :page/new-title "Qual o novo título da nova página?"
-    :page/earlier "Antes"
-    :page/no-more-journals "Não há mais diários"
-    :publishing/pages "Páginas"
-    :publishing/page-name "Nome da página"
-    :publishing/current-project "Projeto Atual"
-    :publishing/delete-from-logseq "Apagar do servidor Logseq"
-    :publishing/edit "Editar"
-    :publishing/save "Guardar"
-    :publishing/cancel "Cancelar"
-    :publishing/delete "Apagar"
-    :journal/multiple-files-with-different-formats "Parece que tem vários ficheiros de diário (com diferentes formatos) para o mesmo mês, por favor apenas guarde um ficheiro de diário para cada mês."
-    :journal/go-to "Ir para ficheiros"
-    :file/name "Nome do ficheiro"
-    :file/file "Ficheiro: "
-    :file/last-modified-at "Última modificação em"
-    :file/no-data "Sem dados"
-    :file/format-not-supported "Formato .{1} não suportado."
-    :page/created-at "Criado Em"
-    :page/updated-at "Atualizado Em"
-    :page/backlinks "Back Links"
-    :editor/block-search "Pesquisar por um bloco"
-    :editor/image-uploading "A enviar"
-    :draw/invalid-file "Não foi possível carregar este ficheiro excalidraw inválido"
-    :draw/specify-title "Por favor indique um título primeiro!"
-    :draw/rename-success "Ficheiro foi renomeado com sucesso!"
-    :draw/rename-failure "Renomear ficheiro falhou, razão: "
-    :draw/title-placeholder "Sem título"
-    :draw/save "Guardar"
-    :draw/save-changes "Guardar alterações"
-    :draw/new-file "Novo ficheiro"
-    :draw/list-files "Listar ficheiros"
-    :draw/delete "Apagar"
-    :draw/more-options "Mais opções"
-    :draw/back-to-logseq "Voltar a logseq"
-    :text/image "Imagem"
-    :asset/confirm-delete "Tem a certeza que quer apagar este {1}?"
-    :asset/physical-delete "Remover também o ficheiro (note que não pode ser restaurado)"
-    :content/copy "Copiar"
-    :content/cut "Cortar"
-    :content/make-todos "Fazer {1}s"
-    :content/copy-block-ref "Copiar referência do bloco"
-    :content/focus-on-block "Focar no bloco"
-    :content/open-in-sidebar "Abrir na barra lateral"
-    :content/copy-as-json "Copiar como JSON"
-    :content/click-to-edit "Clicar para editar"
-    :settings-page/edit-config-edn "Editar config.edn para o repositório atual"
-    :settings-page/show-brackets "Mostrar parênteses rectos"
-    :settings-page/spell-checker "Verificador ortográfico"
-    :settings-page/disable-sentry "Enviar dados de utilização e diagnósticos para Logseq"
-    :settings-page/preferred-outdenting "Ativar outdenting lógico"
-    :settings-page/custom-date-format "Formato de data preferido"
-    :settings-page/preferred-file-format "Formato de ficheiro preferido"
-    :settings-page/preferred-workflow "Workflow preferido"
-    :settings-page/enable-timetracking "Ativar controlo de tempo"
-    :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 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 por omissão"
-    :settings-page/enable-block-time "Ativar carimbos temporais nos blocos"
-    :settings-page/dont-use-other-peoples-proxy-servers "Não use os servidores proxy de outras pessoas. É muito perigoso, o que pode levar a que o seu token e notas sejam roubadas. O Logseq não será responsável por estas perdas se usar servidores proxy de outras pessoas. Pode implementá-lo por si, veja "
-    :settings-page/clear-cache "Limpar cache"
-    :settings-page/clear "Limpar"
-    :settings-page/custom-cors-proxy-server "Servidor proxy CORS personalizado"
-    :settings-page/developer-mode "Modo de desenvolvimento"
-    :settings-page/enable-developer-mode "Ativar modo de desenvolvimento"
-    :settings-page/disable-developer-mode "Desativar modo de desenvolvimento"
-    :settings-page/developer-mode-desc "O modo de desenvolvimento ajuda os contribuidores e programadores de extensões a testar as suas integrações com o Logseq mais eficientemente."
-    :settings-page/current-version "Versão atual"
-    :settings-page/current-graph "Grafo atual"
-    :settings-page/tab-general "Geral"
-    :settings-page/tab-editor "Editor"
-    :settings-page/tab-shortcuts "Atalhos"
-    :settings-page/tab-advanced "Avançado"
-    :settings-page/tab-version-control "Controlo de Versões"
-    :logseq "Logseq"
-    :on "ON"
-    :more-options "Mais opções"
-    :to "para"
-    :yes "Sim"
-    :no "Não"
-    :submit "Submeter"
-    :cancel "Cancelar"
-    :close "Fechar"
-    :delete "Apagar"
-    :re-index "Re-indexar"
-    :unlink "remover ligação"
-    :search (if config/publishing?
-              "Pesquisar"
-              "Pesquisar ou Criar Página")
-    :page-search "Pesquisar na página atual"
-    :graph-search "Pesquisar grafo"
-    :new-page "Nova página"
-    :new-file "Novo ficheiro"
-    :new-graph "Adicionar novo grafo"
-    :graph "Grafo"
-    :graph-view "Ver Grafo"
-    :cards-view "Ver Cartões"
-    :publishing "Publicar"
-    :export "Exportar"
-    :export-markdown "Exportar como Markdown padrão (sem propriedades de bloco)"
-    :export-opml "Exportar como OPML"
-    :export-public-pages "Export páginas públicas"
-    :export-json "Exportar como JSON"
-    :export-roam-json "Exportar como Roam JSON"
-    :export-edn "Exportar como EDN"
-    :export-datascript-edn "Exportar datascript EDN"
-    :convert-markdown "Converter cabeçalhos Markdown para listas não-ordenadas (# -> -)"
-    :all-graphs "Todos os grafos"
-    :all-pages "Todas as páginas"
-    :all-files "Todos os ficheiros"
-    :all-journals "Todos os diários"
-    :my-publishing "As minhas publicações"
-    :settings "Definições"
-    :plugins "Plugins"
-    :themes "Temas"
-    :developer-mode-alert "É necessário reiniciar a aplicação para ativar o sistema de plugins. Quer reiniciar agora?"
-    :relaunch-confirm-to-work "Deve reiniciar a aplicação para fazê-lo funcionar. Quer reiniciar agora?"
-    :import "Importar"
-    :join-community "Junte-se à comunidade"
-    :sponsor-us "Seja um patrocinador"
-    :discord-title "O nosso grupo de discord!"
-    :sign-out "Sair da sessão"
-    :help-shortcut-title "Clique para ver atalhos e outras dicas"
-    :loading "A carregar"
-    :cloning "A clonar"
-    :parsing-files "A analisar ficheiros"
-    :loading-files "A carregar ficheiros"
-    :login-github "Iniciar sessão com GitHub"
-    :login "Iniciar sessão"
-    :go-to "Ir para "
-    :or "ou"
-    :download "Descarregar"
-    :repo/download-zip "Descarregar todos os ficheiros como zip"
-    :language "Linguagem"
-    :white "Claro"
-    :dark "Escuro"
-    :remove-background "Remover fundo"
-    :open "Abrir"
-    :open-a-directory "Abrir uma pasta local"
-    :user/delete-account "Apagar conta"
-    :user/delete-your-account "Apague a sua conta"
-    :user/delete-account-notice "Todas as suas páginas publicadas em logseq.com serão apagadas."
+   :pt-PT {:on-boarding/title "Olá, bem-vindo ao Logseq!"
+           :on-boarding/sharing "partilhar"
+           :on-boarding/is-a " é um bloco de notas "
+           :on-boarding/vision "Uma plataforma de código-aberto focada na privacidade para gestão de conhecimento e colaboração."
+           :on-boarding/local-first "local"
+           :on-boarding/non-linear "não-linear"
+           :on-boarding/outliner "delineador"
+           :on-boarding/notebook-for-organizing-and " para organização e "
+           :on-boarding/your-personal-knowledge-base " a sua base de conhecimento pessoal."
+           :on-boarding/notice "Note que este projeto está em fase de testes Beta e sob rápido desenvolvimento, lembre-se de fazer uma cópia de segurança regular pelo menos 1x/dia."
+           :on-boarding/features-desc "Use-o para organizar a sua lista de tarefas, para escrever os seus diários, ou para registar o seu quotidiano."
+           :on-boarding/privacy "O servidor nunca irá armazenar ou analisar as suas notas privadas. Os seus dados são ficheiros de texto simples, e suportamos neste momento Markdown e o modo Emacs Org. Mesmo que o sítio web esteja em baixo ou não possa ser mantido, os seus dados são sempre seus."
+           :on-boarding/inspired-by " é fortemente inspirado por "
+           :on-boarding/where-are-my-notes-saved "Onde são guardadas as minhas notas?"
+           :on-boarding/storage "A suas notas serão guardadas no armazenamento local do navegador usando "
+           :on-boarding/how-do-i-use-it "Como usar?"
+           :on-boarding/use-1 "1. Sincronizar entre vários dispositivos"
+           :on-boarding/use-1-desc "Atualmente, apenas suportamos sincronização através do GitHub, mais opções (git auto-hospedado, WebDAV, Google Drive, etc.) serão acrescentadas em breve."
+           :on-boarding/use-1-video "Confira este excelente vídeo de "
+           :on-boarding/use-2 "2. Usar localmente (sem precisar de iniciar sessão)"
+           :on-boarding/features "Funcionalidades"
+           :on-boarding/features-backlinks "Backlinks entre [[Página]]s"
+           :on-boarding/features-block-embed "Incorporar bloco"
+           :on-boarding/features-page-embed "Incorporar página"
+           :on-boarding/features-graph-vis "Visualização de grafo"
+           :on-boarding/features-heading-properties "Propriedades de cabeçalho"
+           :on-boarding/features-datalog "Consultas de Datalog, a base de dados de notas usa "
+           :on-boarding/features-custom-view-component "Vista personalizada de componente"
+           :on-boarding/integration " integração"
+           :on-boarding/slide-support " suporte para diapositivos"
+           :on-boarding/built-in-supports "Suporte integrado para:"
+           :on-boarding/supports-code-highlights "Destaque de código"
+           :on-boarding/supports-katex-latex "Katex latex"
+           :on-boarding/raw "Raw "
+           :on-boarding/raw-html "Html raw"
+           :on-boarding/learn-more "Saber mais"
+           :on-boarding/discord-desc " onde a comunidade faz perguntas e partilha dicas"
+           :on-boarding/github-desc " toda a gente é encorajada a reportar problemas!"
+           :on-boarding/our-blog "O nosso blogue: "
+           :on-boarding/credits-to "Créditos a"
+           :on-boarding/clojure-desc " - Uma linguagem de programação dinâmica, funcional, e generalista"
+           :on-boarding/datascript-desc " - Base de dados imutável e motor de consulta de Datalog para Clojure, ClojureScript e JS"
+           :on-boarding/angstrom-desc-1 ", o documento "
+           :on-boarding/angstrom-desc-2 "analisador sintático (parser)"
+           :on-boarding/angstrom-desc-3 " é construído em Angstrom."
+           :on-boarding/cuekeeper-desc " - Sistema de GTD (lista de tarefas) baseado no navegador."
+           :on-boarding/sci-desc " - Interpretador Compacto de Clojure"
+           :on-boarding/isomorphic-git-desc " - Uma implementação de git em JavaScript puro para node e navegadores!"
+           :on-boarding/add-graph "Adicionar grafo"
+           :help/start "Começar a usar"
+           :help/about "Sobre o Logseq"
+           :help/roadmap "Plano de implementação"
+           :help/bug "Reporte de erros"
+           :help/feature "Pedido de funcionalidades"
+           :help/changelog "Registo de alterações"
+           :help/blog "Blogue de Logseq"
+           :help/docs "Documentação"
+           :help/privacy "Política de privacidade"
+           :help/terms "Termos"
+           :help/community "Comunidade de Discord"
+           :help/awesome-logseq "Awesome Logseq"
+           :help/shortcuts "Atalhos de Teclado"
+           :help/shortcuts-triggers "Iniciadores"
+           :help/shortcut "Atalho"
+           :help/slash-autocomplete "Auto-completar com Slash"
+           :help/block-content-autocomplete "Auto-completar conteúdo de bloco (Src, Quote, Query, etc)"
+           :help/reference-autocomplete "Auto-completar referência de páginas"
+           :help/block-reference "Referência de bloco"
+           :help/key-commands "Comandos chave"
+           :help/working-with-lists " (trabalhar com listas)"
+           :help/select-nfs-browser "Por favor use outro navegador (como o Chrome mais recente) com suporte para funcionalidades NFS para abrir pastas locais."
+           :undo "Desfazer"
+           :redo "Refazer"
+           :general "Geral"
+           :more "Mais"
+           :search/result-for "Resultado da pesquisa para "
+           :search/items "itens"
+           :help/context-menu "Menu contextual"
+           :help/fold-unfold "Ocultar/Expandir blocos (fora do modo de edição)"
+           :help/markdown-syntax "Sintaxe de Markdown"
+           :help/org-mode-syntax "Sintaxa de modo Org"
+           :bold "Negrito"
+           :italics "Itálico"
+           :html-link "Link Html"
+           :highlight "Realçado"
+           :strikethrough "Rasurado"
+           :code "Código"
+           :right-side-bar/help "Ajuda"
+           :right-side-bar/switch-theme "Temas"
+           :right-side-bar/theme "Tema {1}"
+           :right-side-bar/page "Grafo da página"
+           :right-side-bar/recent "Recente"
+           :right-side-bar/contents "Conteúdo"
+           :right-side-bar/favorites "Favoritos"
+           :right-side-bar/page-graph "Grafo da página"
+           :right-side-bar/block-ref "Referência de bloco"
+           :right-side-bar/journals "Journals"
+           :right-side-bar/graph-view "Graph view"
+           :right-side-bar/all-pages "All pages"
+           :right-side-bar/flashcards "Flashcards"
+           :right-side-bar/new-page "New page"
+           :git/set-access-token "Definir token de acesso pessoal do GitHub"
+           :git/token-is-encrypted "O token será encriptado e guardado no armazenamento local do navegador"
+           :git/token-server "O servidor nunca o irá guardar"
+           :git/create-personal-access-token "Como criar um token de acesso pessoal do GitHub?"
+           :git/push "Fazer push agora"
+           :git/push-failed "Push falhou!"
+           :git/local-changes-synced "Todas as alterações locais foram sincronizadas!"
+           :git/pull "Fazer pull agora"
+           :git/last-pull "Último pull em"
+           :git/version "Versão"
+           :git/import-notes "Importar as suas notas"
+           :git/import-notes-helper "Pode importar as suas notas de um repositório no GitHub."
+           :git/add-another-repo "Adicionar outro repositório"
+           :git/re-index "Clonar novamente e re-indexar a base de dados"
+           :git/message "A sua mensagem de commit"
+           :git/commit-and-push "Fazer commit e push!"
+           :git/use-remote "Usar remotamente"
+           :git/keep-local "Manter localmente"
+           :git/edit "Editar"
+           :git/title "Diff"
+           :git/no-diffs "Sem diffs"
+           :git/commit-message "Mensagem de commit (opcional)"
+           :git/pushing "A fazer push"
+           :git/force-push "Fazer commit e forçar push"
+           :git/a-force-push "Push forçado"
+           :git/add-repo-prompt "Instalar o Logseq no seu repositório"
+           :git/add-repo-prompt-confirm "Adicionar e instalar"
+           :format/preferred-mode "Qual o seu modo preferido?"
+           :format/markdown "Markdown"
+           :format/org-mode "Modo Org"
+           :reference/linked "Referência ligada"
+           :reference/unlinked-ref "Referências não ligadas"
+           :project/setup "Configurar um projeto público em Logseq"
+           :project/location "Todas as páginas publicadas ficarão localizadas em"
+           :project/sync-settings "Definições de sincronização do projeto"
+           :page/presentation-mode "Modo de apresentação"
+           :page/edit-properties-placeholder "Propriedades"
+           :page/delete-success "Página {1} apagada com sucesso!"
+           :page/delete-confirmation "Tem a certeza de que quer apagar esta página e o respetivo ficheiro?"
+           :page/rename-to "Renomear \"{1}\" como:"
+           :page/priority "Prioridade \"{1}\""
+           :page/copy-to-json "Copiar a página inteira como JSON"
+           :page/rename "Renomear página"
+           :page/open-in-finder "Abrir em pasta"
+           :page/open-with-default-app "Abrir com a aplicação por omissão"
+           :page/action-publish "Publicar"
+           :page/make-public "Tornar pública para publicação"
+           :page/version-history "Ver histórico da página"
+           :page/make-private "Tornar privada"
+           :page/delete "Apagar página"
+           :page/publish "Publicar esta página em Logseq"
+           :page/cancel-publishing "Cancelar publicação em Logseq"
+           :page/publish-as-slide "Publicar esta página como um diapositivo em Logseq"
+           :page/unpublish "Remover publicação desta página em Logseq"
+           :page/add-to-favorites "Adicionar aos Favoritos"
+           :page/show-journals "Mostrar Diários"
+           :page/show-name "Mostrar nome da página"
+           :page/hide-name "Esconder nome da página"
+           :block/name "Nome da página"
+           :page/last-modified "Última modificação em"
+           :page/new-title "Qual o novo título da nova página?"
+           :page/earlier "Antes"
+           :page/no-more-journals "Não há mais diários"
+           :publishing/pages "Páginas"
+           :publishing/page-name "Nome da página"
+           :publishing/current-project "Projeto Atual"
+           :publishing/delete-from-logseq "Apagar do servidor Logseq"
+           :publishing/edit "Editar"
+           :publishing/save "Guardar"
+           :publishing/cancel "Cancelar"
+           :publishing/delete "Apagar"
+           :journal/multiple-files-with-different-formats "Parece que tem vários ficheiros de diário (com diferentes formatos) para o mesmo mês, por favor apenas guarde um ficheiro de diário para cada mês."
+           :journal/go-to "Ir para ficheiros"
+           :file/name "Nome do ficheiro"
+           :file/file "Ficheiro: "
+           :file/last-modified-at "Última modificação em"
+           :file/no-data "Sem dados"
+           :file/format-not-supported "Formato .{1} não suportado."
+           :page/created-at "Criado Em"
+           :page/updated-at "Atualizado Em"
+           :page/backlinks "Back Links"
+           :editor/block-search "Pesquisar por um bloco"
+           :editor/image-uploading "A enviar"
+           :draw/invalid-file "Não foi possível carregar este ficheiro excalidraw inválido"
+           :draw/specify-title "Por favor indique um título primeiro!"
+           :draw/rename-success "Ficheiro foi renomeado com sucesso!"
+           :draw/rename-failure "Renomear ficheiro falhou, razão: "
+           :draw/title-placeholder "Sem título"
+           :draw/save "Guardar"
+           :draw/save-changes "Guardar alterações"
+           :draw/new-file "Novo ficheiro"
+           :draw/list-files "Listar ficheiros"
+           :draw/delete "Apagar"
+           :draw/more-options "Mais opções"
+           :draw/back-to-logseq "Voltar a logseq"
+           :text/image "Imagem"
+           :asset/confirm-delete "Tem a certeza que quer apagar este {1}?"
+           :asset/physical-delete "Remover também o ficheiro (note que não pode ser restaurado)"
+           :content/copy "Copiar"
+           :content/cut "Cortar"
+           :content/make-todos "Fazer {1}s"
+           :content/copy-block-ref "Copiar referência do bloco"
+           :content/focus-on-block "Focar no bloco"
+           :content/open-in-sidebar "Abrir na barra lateral"
+           :content/copy-as-json "Copiar como JSON"
+           :content/click-to-edit "Clicar para editar"
+           :settings-page/edit-config-edn "Editar config.edn para o repositório atual"
+           :settings-page/show-brackets "Mostrar parênteses rectos"
+           :settings-page/spell-checker "Verificador ortográfico"
+           :settings-page/disable-sentry "Enviar dados de utilização e diagnósticos para Logseq"
+           :settings-page/preferred-outdenting "Ativar outdenting lógico"
+           :settings-page/custom-date-format "Formato de data preferido"
+           :settings-page/preferred-file-format "Formato de ficheiro preferido"
+           :settings-page/preferred-workflow "Workflow preferido"
+           :settings-page/enable-timetracking "Ativar controlo de tempo"
+           :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 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 por omissão"
+           :settings-page/enable-block-time "Ativar carimbos temporais nos blocos"
+           :settings-page/dont-use-other-peoples-proxy-servers "Não use os servidores proxy de outras pessoas. É muito perigoso, o que pode levar a que o seu token e notas sejam roubadas. O Logseq não será responsável por estas perdas se usar servidores proxy de outras pessoas. Pode implementá-lo por si, veja "
+           :settings-page/clear-cache "Limpar cache"
+           :settings-page/clear "Limpar"
+           :settings-page/custom-cors-proxy-server "Servidor proxy CORS personalizado"
+           :settings-page/developer-mode "Modo de desenvolvimento"
+           :settings-page/enable-developer-mode "Ativar modo de desenvolvimento"
+           :settings-page/disable-developer-mode "Desativar modo de desenvolvimento"
+           :settings-page/developer-mode-desc "O modo de desenvolvimento ajuda os contribuidores e programadores de extensões a testar as suas integrações com o Logseq mais eficientemente."
+           :settings-page/current-version "Versão atual"
+           :settings-page/current-graph "Grafo atual"
+           :settings-page/tab-general "Geral"
+           :settings-page/tab-editor "Editor"
+           :settings-page/tab-shortcuts "Atalhos"
+           :settings-page/tab-advanced "Avançado"
+           :settings-page/tab-version-control "Controlo de Versões"
+           :logseq "Logseq"
+           :on "ON"
+           :more-options "Mais opções"
+           :to "para"
+           :yes "Sim"
+           :no "Não"
+           :submit "Submeter"
+           :cancel "Cancelar"
+           :close "Fechar"
+           :delete "Apagar"
+           :re-index "Re-indexar"
+           :unlink "remover ligação"
+           :search (if config/publishing?
+                     "Pesquisar"
+                     "Pesquisar ou Criar Página")
+           :page-search "Pesquisar na página atual"
+           :graph-search "Pesquisar grafo"
+           :new-page "Nova página"
+           :new-file "Novo ficheiro"
+           :new-graph "Adicionar novo grafo"
+           :graph "Grafo"
+           :graph-view "Ver Grafo"
+           :cards-view "Ver Cartões"
+           :publishing "Publicar"
+           :export "Exportar"
+           :export-markdown "Exportar como Markdown padrão (sem propriedades de bloco)"
+           :export-opml "Exportar como OPML"
+           :export-public-pages "Export páginas públicas"
+           :export-json "Exportar como JSON"
+           :export-roam-json "Exportar como Roam JSON"
+           :export-edn "Exportar como EDN"
+           :export-datascript-edn "Exportar datascript EDN"
+           :convert-markdown "Converter cabeçalhos Markdown para listas não-ordenadas (# -> -)"
+           :all-graphs "Todos os grafos"
+           :all-pages "Todas as páginas"
+           :all-files "Todos os ficheiros"
+           :all-journals "Todos os diários"
+           :my-publishing "As minhas publicações"
+           :settings "Definições"
+           :plugins "Plugins"
+           :themes "Temas"
+           :developer-mode-alert "É necessário reiniciar a aplicação para ativar o sistema de plugins. Quer reiniciar agora?"
+           :relaunch-confirm-to-work "Deve reiniciar a aplicação para fazê-lo funcionar. Quer reiniciar agora?"
+           :import "Importar"
+           :join-community "Junte-se à comunidade"
+           :sponsor-us "Seja um patrocinador"
+           :discord-title "O nosso grupo de discord!"
+           :sign-out "Sair da sessão"
+           :help-shortcut-title "Clique para ver atalhos e outras dicas"
+           :loading "A carregar"
+           :cloning "A clonar"
+           :parsing-files "A analisar ficheiros"
+           :loading-files "A carregar ficheiros"
+           :login-github "Iniciar sessão com GitHub"
+           :login "Iniciar sessão"
+           :go-to "Ir para "
+           :or "ou"
+           :download "Descarregar"
+           :repo/download-zip "Descarregar todos os ficheiros como zip"
+           :language "Linguagem"
+           :white "Claro"
+           :dark "Escuro"
+           :remove-background "Remover fundo"
+           :open "Abrir"
+           :open-a-directory "Abrir uma pasta local"
+           :user/delete-account "Apagar conta"
+           :user/delete-your-account "Apague a sua conta"
+           :user/delete-account-notice "Todas as suas páginas publicadas em logseq.com serão apagadas."
 
-    :help/shortcut-page-title "Atalhos de teclado"
+           :help/shortcut-page-title "Atalhos de teclado"
 
-    :pdf/copy-ref "Copiar referência"
-    :pdf/copy-text "Copiar texto"
-    :pdf/linked-ref "Referências ligadas"
-    :command-palette/prompt "Introduza um comando"}
+           :pdf/copy-ref "Copiar referência"
+           :pdf/copy-text "Copiar texto"
+           :pdf/linked-ref "Referências ligadas"
+           :command-palette/prompt "Introduza um comando"}
 
-    :ru {:on-boarding/title "Привет, добро пожаловать в Logseq!"
+   :ru {:on-boarding/title "Привет, добро пожаловать в Logseq!"
         :on-boarding/sharing "распространения"
         :on-boarding/is-a " "
         :on-boarding/vision "Безопасная, открытая платформа для уравления базой знаний и совместной работы."

+ 123 - 129
src/main/frontend/extensions/pdf/highlights.cljs

@@ -2,7 +2,7 @@
   (:require [cljs-bean.core :as bean]
             [clojure.string :as string]
             [frontend.components.svg :as svg]
-            [frontend.context.i18n :as i18n]
+            [frontend.context.i18n :refer [t]]
             [frontend.extensions.pdf.assets :as pdf-assets]
             [frontend.extensions.pdf.utils :as pdf-utils]
             [frontend.handler.notification :as notification]
@@ -117,62 +117,59 @@
         #())
       [])
 
-    (rum/with-context
-      [[t] i18n/*tongue-context*]
+    [:ul.extensions__pdf-hls-ctx-menu
+     {:ref      *el
+      :style    {:top top :left left}
+      :on-click (fn [^js/MouseEvent e]
+                  (when-let [action (.. e -target -dataset -action)]
+                    (case action
+                      "ref"
+                      (pdf-assets/copy-hl-ref! highlight)
 
-      [:ul.extensions__pdf-hls-ctx-menu
-       {:ref      *el
-        :style    {:top top :left left}
-        :on-click (fn [^js/MouseEvent e]
-                    (when-let [action (.. e -target -dataset -action)]
-                      (case action
-                        "ref"
-                        (pdf-assets/copy-hl-ref! highlight)
-
-                        "copy"
-                        (do
-                          (front-utils/copy-to-clipboard!
-                            (or (:text content) (.toString range)))
-                          (pdf-utils/clear-all-selection))
+                      "copy"
+                      (do
+                        (front-utils/copy-to-clipboard!
+                         (or (:text content) (.toString range)))
+                        (pdf-utils/clear-all-selection))
 
-                        "link"
-                        (pdf-assets/goto-block-ref! highlight)
+                      "link"
+                      (pdf-assets/goto-block-ref! highlight)
 
-                        "del"
-                        (do
-                          (del-hl! highlight)
-                          (pdf-assets/del-ref-block! highlight)
-                          (pdf-assets/unlink-hl-area-image$ viewer (:pdf/current @state/state) highlight))
+                      "del"
+                      (do
+                        (del-hl! highlight)
+                        (pdf-assets/del-ref-block! highlight)
+                        (pdf-assets/unlink-hl-area-image$ viewer (:pdf/current @state/state) highlight))
 
-                        ;; colors
-                        (let [properties {:color action}]
-                          (if-not id
-                            ;; add highlight
-                            (let [highlight (merge (if (fn? highlight) (highlight) highlight)
-                                                   {:id         (pdf-utils/gen-uuid)
-                                                    :properties properties})]
-                              (add-hl! highlight)
-                              (pdf-utils/clear-all-selection)
-                              (pdf-assets/copy-hl-ref! highlight))
+                      ;; colors
+                      (let [properties {:color action}]
+                        (if-not id
+                          ;; add highlight
+                          (let [highlight (merge (if (fn? highlight) (highlight) highlight)
+                                                 {:id         (pdf-utils/gen-uuid)
+                                                  :properties properties})]
+                            (add-hl! highlight)
+                            (pdf-utils/clear-all-selection)
+                            (pdf-assets/copy-hl-ref! highlight))
 
-                            ;; update highlight
-                            (upd-hl! (assoc highlight :properties properties))))))
+                          ;; update highlight
+                          (upd-hl! (assoc highlight :properties properties))))))
 
-                    (clear-ctx-tip!))}
+                  (clear-ctx-tip!))}
 
-       [:li.item-colors
-        (for [it ["yellow", "blue", "green", "red", "purple"]]
-          [:a {:key it :data-color it :data-action it} it])]
+     [:li.item-colors
+      (for [it ["yellow", "blue", "green", "red", "purple"]]
+        [:a {:key it :data-color it :data-action it} it])]
 
 
-       (and id [:li.item {:data-action "ref"} (t :pdf/copy-ref)])
+     (and id [:li.item {:data-action "ref"} (t :pdf/copy-ref)])
 
-       (and (not (:image content)) [:li.item {:data-action "copy"} (t :pdf/copy-text)])
+     (and (not (:image content)) [:li.item {:data-action "copy"} (t :pdf/copy-text)])
 
-       (and id [:li.item {:data-action "link"} (t :pdf/linked-ref)])
+     (and id [:li.item {:data-action "link"} (t :pdf/linked-ref)])
 
-       (and id [:li.item {:data-action "del"} (t :delete)])
-       ])))
+     (and id [:li.item {:data-action "del"} (t :delete)])
+     ]))
 
 (rum/defc pdf-highlights-text-region
   [^js viewer vw-hl hl
@@ -827,90 +824,87 @@
             #(.off bus "pagechanging" page-fn))))
       [viewer])
 
-    (rum/with-context
-      [[t] i18n/*tongue-context*]
-
-      [:div.extensions__pdf-toolbar
-       [:div.inner
-        [:div.r.flex.buttons
-
-         ;; appearance
-         [:a.button
-          {:title    "More settings"
-           :on-click #(set-settings-visible! (not settings-visible?))}
-          (svg/adjustments 18)]
-
-         ;; selection
-         [:a.button
-          {:title    (str "Area highlight (" (if front-utils/mac? "⌘" "Shift") ")")
-           :class    (when area-mode? "is-active")
-           :on-click #(set-area-mode! (not area-mode?))}
-          (svg/icon-area 18)]
-
-         ;; zoom
-         [:a.button
-          {:title    "Zoom out"
-           :on-click (partial pdf-utils/zoom-out-viewer viewer)}
-          (svg/zoom-out 18)]
-
-         [:a.button
-          {:title    "Zoom in"
-           :on-click (partial pdf-utils/zoom-in-viewer viewer)}
-          (svg/zoom-in 18)]
-
-         [:a.button
-          {:title    "Outline"
-           :on-click #(set-outline-visible! (not outline-visible?))}
-          (svg/view-list 16)]
-
-         ;; metadata
-         [:a.button.is-info
-          {:title    "Document info"
-           :on-click #(p/let [ret (pdf-utils/get-meta-data$ viewer)]
-                        (state/set-modal! (make-docinfo-in-modal ret)))}
-          (svg/icon-info)]
-
-         ;; annotations
-         [:a.button
-          {:title    "Annotations page"
-           :on-click #(pdf-assets/goto-annotations-page! (:pdf/current @state/state))}
-          (svg/annotations 16)]
-
-         ;; pager
-         [:div.pager.flex.items-center.ml-1
-
-          [:span.nu.flex.items-center.opacity-70
-           [:input {:ref            *page-ref
-                    :type           "number"
-                    :default-value  current-page-num
-                    :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))]
-                                        (when (and (= (.-keyCode e) 13) value (> value 0))
-                                          (set! (. viewer -currentPageNumber)
-                                                (if (> value total-page-num) total-page-num value)))))}]
-           [:small "/ " total-page-num]]
-
-          [:span.ct.flex.items-center
-           [:a.button {:on-click #(. viewer previousPage)} (svg/up-narrow)]
-           [:a.button {:on-click #(. viewer nextPage)} (svg/down-narrow)]]]
-
-         [:a.button
-          {:on-click #(state/set-state! :pdf/current nil)}
-          (t :close)]]]
-
-       ;; contents outline
-       (pdf-outline viewer outline-visible? set-outline-visible!)
-
-       ;; settings
-       (and settings-visible?
-            (pdf-settings
-              viewer
-              viewer-theme
-              {:t t
-               :hide-settings! #(set-settings-visible! false)
-               :select-theme!  #(set-viewer-theme! %)}))])))
+    [:div.extensions__pdf-toolbar
+     [:div.inner
+      [:div.r.flex.buttons
+
+       ;; appearance
+       [:a.button
+        {:title    "More settings"
+         :on-click #(set-settings-visible! (not settings-visible?))}
+        (svg/adjustments 18)]
+
+       ;; selection
+       [:a.button
+        {:title    (str "Area highlight (" (if front-utils/mac? "⌘" "Shift") ")")
+         :class    (when area-mode? "is-active")
+         :on-click #(set-area-mode! (not area-mode?))}
+        (svg/icon-area 18)]
+
+       ;; zoom
+       [:a.button
+        {:title    "Zoom out"
+         :on-click (partial pdf-utils/zoom-out-viewer viewer)}
+        (svg/zoom-out 18)]
+
+       [:a.button
+        {:title    "Zoom in"
+         :on-click (partial pdf-utils/zoom-in-viewer viewer)}
+        (svg/zoom-in 18)]
+
+       [:a.button
+        {:title    "Outline"
+         :on-click #(set-outline-visible! (not outline-visible?))}
+        (svg/view-list 16)]
+
+       ;; metadata
+       [:a.button.is-info
+        {:title    "Document info"
+         :on-click #(p/let [ret (pdf-utils/get-meta-data$ viewer)]
+                      (state/set-modal! (make-docinfo-in-modal ret)))}
+        (svg/icon-info)]
+
+       ;; annotations
+       [:a.button
+        {:title    "Annotations page"
+         :on-click #(pdf-assets/goto-annotations-page! (:pdf/current @state/state))}
+        (svg/annotations 16)]
+
+       ;; pager
+       [:div.pager.flex.items-center.ml-1
+
+        [:span.nu.flex.items-center.opacity-70
+         [:input {:ref            *page-ref
+                  :type           "number"
+                  :default-value  current-page-num
+                  :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))]
+                                      (when (and (= (.-keyCode e) 13) value (> value 0))
+                                        (set! (. viewer -currentPageNumber)
+                                              (if (> value total-page-num) total-page-num value)))))}]
+         [:small "/ " total-page-num]]
+
+        [:span.ct.flex.items-center
+         [:a.button {:on-click #(. viewer previousPage)} (svg/up-narrow)]
+         [:a.button {:on-click #(. viewer nextPage)} (svg/down-narrow)]]]
+
+       [:a.button
+        {:on-click #(state/set-state! :pdf/current nil)}
+        (t :close)]]]
+
+     ;; contents outline
+     (pdf-outline viewer outline-visible? set-outline-visible!)
+
+     ;; settings
+     (and settings-visible?
+          (pdf-settings
+           viewer
+           viewer-theme
+           {:t t
+            :hide-settings! #(set-settings-visible! false)
+            :select-theme!  #(set-viewer-theme! %)}))]))
 
 (rum/defc pdf-viewer
   [url initial-hls ^js pdf-document ops]

+ 9 - 44
src/main/frontend/format/block.cljs

@@ -418,26 +418,6 @@
       content
       (property/->new-properties content))))
 
-(defn- remove-indentations
-  [format level element]
-  (if (= format :org)
-    element
-    (case (first element)
-      "Paragraph"
-      ["Paragraph"
-       (let [level (if (= (ffirst (second element)) "Plain")
-                     (count (re-find #"^[\s\t]+" (second (first (second element)))))
-                     level)]
-         (->> (partition-by #(contains? #{["Break_Line"] ["Hard_Break_Line"]} %) (second element))
-             (map (fn [c]
-                    (if (and (= (ffirst c) "Plain")
-                             (>= (count (re-find #"^[\s\t]+" (second (first c)))) level))
-                      (cons ["Plain" (subs (second (first c)) level)] (rest c))
-                      c)))
-             (apply concat)))]
-
-      element)))
-
 (defn get-custom-id-or-new-id
   [properties]
   (or (when-let [custom-id (or (get-in properties [:properties :custom-id])
@@ -471,11 +451,11 @@
           (update :refs (fn [col] (remove nil? col)))))
 
 (defn extract-blocks*
-  [blocks pre-block-body pre-block-properties encoded-content]
+  [blocks pre-block-properties encoded-content]
   (let [first-block (first blocks)
         first-block-start-pos (get-in first-block [:block/meta :start-pos])
-        blocks (if (or (seq @pre-block-body)
-                       (seq @pre-block-properties))
+        blocks (if (or (> first-block-start-pos 0)
+                       (empty? blocks))
                  (cons
                   (merge
                    (let [content (utf8/substring encoded-content 0 first-block-start-pos)
@@ -486,7 +466,6 @@
                                 :meta {:start-pos 0
                                        :end-pos (or first-block-start-pos
                                                     (utf8/length encoded-content))}
-                                :body @pre-block-body
                                 :properties @pre-block-properties
                                 :properties-order (keys @pre-block-properties)
                                 :refs (->> (get-page-refs-from-properties @pre-block-properties)
@@ -506,11 +485,9 @@
   (try
     (let [encoded-content (utf8/encode content)
           last-pos (utf8/length encoded-content)
-          pre-block-body (atom nil)
           pre-block-properties (atom nil)
           blocks
           (loop [headings []
-                 block-body []
                  blocks (reverse blocks)
                  timestamps {}
                  properties {}
@@ -526,18 +503,12 @@
                 (cond
                   (paragraph-timestamp-block? block)
                   (let [timestamps (extract-timestamps block)
-                        timestamps' (merge timestamps timestamps)
-                        [timestamps others] (split-with #(= "Timestamp" (first %)) (second block))
-                        other-body (->>
-                                    (concat
-                                     timestamps
-                                     (drop-while #(contains? #{"Hard_Break_Line" "Break_Line"} (first %)) others))
-                                    (remove nil?))]
-                    (recur headings (conj block-body ["Paragraph" other-body]) (rest blocks) timestamps' properties last-pos last-level children (conj block-all-content block-content)))
+                        timestamps' (merge timestamps timestamps)]
+                    (recur headings (rest blocks) timestamps' properties last-pos last-level children (conj block-all-content block-content)))
 
                   (property/properties-ast? block)
                   (let [properties (extract-properties (second block))]
-                    (recur headings block-body (rest blocks) timestamps properties last-pos last-level children (conj block-all-content block-content)))
+                    (recur headings (rest blocks) timestamps properties last-pos last-level children (conj block-all-content block-content)))
 
                   (heading-block? block)
                   (let [id (get-custom-id-or-new-id properties)
@@ -568,9 +539,6 @@
                         block (cond->
                                 (assoc block
                                       :uuid id
-                                      :body (vec
-                                             (->> (reverse block-body)
-                                                  (map #(remove-indentations format (:level block) %))))
                                       :refs ref-pages-in-properties
                                       :children (or current-block-children [])
                                       :format format)
@@ -600,20 +568,17 @@
 
                                 (and updated-at (integer? updated-at))
                                 (assoc :block/updated-at updated-at))]
-                    (recur (conj headings block) [] (rest blocks) {} {} last-pos' (:level block) children []))
+                    (recur (conj headings block) (rest blocks) {} {} last-pos' (:level block) children []))
 
                   :else
-                  (let [block-body' (conj block-body block)]
-                    (recur headings block-body' (rest blocks) timestamps properties last-pos last-level children (conj block-all-content block-content)))))
+                  (recur headings (rest blocks) timestamps properties last-pos last-level children (conj block-all-content block-content))))
               (do
-                (when (seq block-body)
-                  (reset! pre-block-body (reverse block-body)))
                 (when (seq properties)
                   (let [properties (:properties properties)]
                     (reset! pre-block-properties properties)))
                 (-> (reverse headings)
                     safe-blocks))))]
-      (extract-blocks* blocks pre-block-body pre-block-properties encoded-content))
+      (extract-blocks* blocks pre-block-properties encoded-content))
     (catch js/Error e
       (js/console.error "extract-blocks-failed")
       (log/error :exception e))))

+ 3 - 27
src/main/frontend/format/mldoc.cljs

@@ -9,9 +9,7 @@
             [lambdaisland.glogi :as log]
             [medley.core :as medley]
             ["mldoc" :as mldoc :refer [Mldoc]]
-            [linked.core :as linked]
-            [promesa.core :as p]
-            [frontend.util.pool :as pool]))
+            [linked.core :as linked]))
 
 (defonce parseJson (gobj/get Mldoc "parseJson"))
 (defonce parseInlineJson (gobj/get Mldoc "parseInlineJson"))
@@ -36,9 +34,10 @@
 (defn default-config
   ([format]
    (default-config format {:export-heading-to-list? false}))
-  ([format {:keys [export-heading-to-list? export-keep-properties? export-md-indent-style export-md-remove-options]}]
+  ([format {:keys [export-heading-to-list? export-keep-properties? export-md-indent-style export-md-remove-options parse_outline_only?]}]
    (let [format (string/capitalize (name (or format :markdown)))]
      (->> {:toc false
+           :parse_outline_only (or parse_outline_only? false)
            :heading_number false
            :keep_line_break true
            :format format
@@ -198,29 +197,6 @@
         []))
     (log/error :edn/wrong-content-type content)))
 
-(defn ->edn-async
-  ([content config]
-   (->edn-async nil content config))
-  ([file content config]
-   (if util/node-test?
-     (p/resolved (->edn content config))
-     (try
-       (if (string/blank? content)
-         (p/resolved [])
-         (p/let [v (pool/add-parse-job! content config)]
-           (try
-             (-> v
-                 (util/json->clj)
-                 (update-src-full-content content)
-                 (collect-page-properties))
-             (catch js/Error e
-               (println :parser/failed file)
-               (js/console.error e)
-               (p/resolved [])))))
-       (catch js/Error e
-         (log/error :parser/failed e)
-         (p/resolved []))))))
-
 (defn opml->edn
   [content]
   (try

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

@@ -158,14 +158,19 @@
                       (error-handler error)
                       (log/error :write-file-failed error)))))))))
 
-(defn- get-file-path [dir path]
-  (let [[dir path'] (map #(-> (when %
-                                (string/replace % "///" "/"))
-                              js/decodeURI)
+(defn get-file-path [dir path]
+  (let [[dir' path'] (map #(some-> %
+                                  (string/replace "///" "/")
+                                  js/decodeURI)
                          [dir path])
-        path (if (string/starts-with? path' dir)
-               path
-               (-> (str dir path)
+        path (cond (nil? path)
+               dir
+
+               (string/starts-with? path' dir')
+               path'
+
+               :else
+               (-> (str dir' path)
                    (string/replace "//" "/")))]
     (if (mobile-util/native-ios?)
       (js/encodeURI (js/decodeURI path))

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

@@ -26,7 +26,6 @@
             [frontend.state :as state]
             [frontend.storage :as storage]
             [frontend.util :as util]
-            [frontend.util.pool :as pool]
             [cljs.reader :refer [read-string]]
             [goog.object :as gobj]
             [lambdaisland.glogi :as log]
@@ -237,7 +236,6 @@
     (reset! db/*sync-search-indice-f search/sync-search-indice!)
     (db/run-batch-txs!)
     (file-handler/run-writes-chan!)
-    (pool/init-parser-pool!)
     (when config/dev?
       (enable-datalog-console))
     (when (util/electron?)

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

@@ -3120,8 +3120,7 @@
   [block id search-timeout]
   (fn [e]
     (if (state/sub :editor/show-block-search?)
-      (let [blocks-count (or (db/blocks-count) 0)
-            timeout (if (> blocks-count 2000) 300 100)]
+      (let [timeout 300]
         (when @search-timeout
           (js/clearTimeout @search-timeout))
         (reset! search-timeout

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

@@ -20,6 +20,7 @@
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.notification :as notification]
             [frontend.handler.page :as page-handler]
+            [frontend.handler.search :as search-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.route :as route-handler]
@@ -74,12 +75,8 @@
     close-fn)))
 
 (defmethod handle :graph/added [[_ repo]]
-  ;; TODO: add ast/version to db
-  (let [_conn (conn/get-conn repo false)
-        ; ast-version (d/datoms @conn :aevt :ast/version)
-        ]
-    (db/set-key-value repo :ast/version db-schema/ast-version)
-    (srs/update-cards-due-count!)))
+  (db/set-key-value repo :ast/version db-schema/ast-version)
+  (search-handler/rebuild-indices!))
 
 (defn- graph-switch [graph]
   (repo-handler/push-if-auto-enabled! (state/get-current-repo))

+ 47 - 89
src/main/frontend/handler/extract.cljs

@@ -10,12 +10,9 @@
             [frontend.format.mldoc :as mldoc]
             [frontend.state :as state]
             [frontend.text :as text]
-            [frontend.utf8 :as utf8]
             [frontend.util :as util]
             [frontend.util.property :as property]
-            [lambdaisland.glogi :as log]
-            [promesa.core :as p]
-            [frontend.mobile.util :as mobile]))
+            [lambdaisland.glogi :as log]))
 
 (defn get-page-name
   [file ast]
@@ -45,7 +42,7 @@
 ;; TODO: performance improvement
 (defn- extract-pages-and-blocks
   #_:clj-kondo/ignore
-  [repo-url format ast properties file content _utf8-content _journal?]
+  [repo-url format ast properties file content]
   (try
     (let [page (get-page-name file ast)
           [_original-page-name page-name _journal-day] (block/convert-page-if-journal page)
@@ -138,39 +135,31 @@
       (log/error :exception e))))
 
 (defn extract-blocks-pages
-  ([repo-url file content]
-   (extract-blocks-pages repo-url file content (utf8/encode content)))
-  ([repo-url file content utf8-content]
-   (if (string/blank? content)
-     (p/resolved [])
-     (p/let [format (format/get-format file)
-             _ (println "Parsing start : " file)
-             parse-f (if (and (mobile/is-native-platform?) config/dev?)
-                       mldoc/->edn
-                       (fn [content config]
-                         (mldoc/->edn-async file content config)))
-             ast (parse-f content (mldoc/default-config format))]
-       _ (println "Parsing finished : " file)
-       (let [journal? (config/journal? file)
-             first-block (ffirst ast)
-             properties (let [properties (and (property/properties-ast? first-block)
-                                              (->> (last first-block)
-                                                   (map (fn [[x y]]
-                                                          [x (if (string? y)
-                                                               (text/parse-property x y)
-                                                               y)]))
-                                                   (into {})
-                                                   (walk/keywordize-keys)))]
-                          (when (and properties (seq properties))
-                            (if (:filters properties)
-                              (update properties :filters
-                                      (fn [v]
-                                        (string/replace (or v "") "\\" "")))
-                              properties)))]
-         (extract-pages-and-blocks
-          repo-url
-          format ast properties
-          file content utf8-content journal?))))))
+  [repo-url file content]
+  (if (string/blank? content)
+    []
+    (let [format (format/get-format file)
+          ast (mldoc/->edn content (mldoc/default-config format {:parse_outline_only? true}))]
+      (println "Parsing finished : " file)
+      (let [first-block (ffirst ast)
+            properties (let [properties (and (property/properties-ast? first-block)
+                                             (->> (last first-block)
+                                                  (map (fn [[x y]]
+                                                         [x (if (string? y)
+                                                              (text/parse-property x y)
+                                                              y)]))
+                                                  (into {})
+                                                  (walk/keywordize-keys)))]
+                         (when (and properties (seq properties))
+                           (if (:filters properties)
+                             (update properties :filters
+                                     (fn [v]
+                                       (string/replace (or v "") "\\" "")))
+                             properties)))]
+        (extract-pages-and-blocks
+         repo-url
+         format ast properties
+         file content)))))
 
 (defn with-block-uuid
   [pages]
@@ -190,57 +179,26 @@
          (map (partial apply merge))
          (with-block-uuid))))
 
-(defn remove-illegal-refs
-  [block block-ids-set refresh?]
-  (let [aux-fn (fn [refs]
-                 (let [block-refs (if refresh? (set refs)
-                                      (set/intersection (set refs) block-ids-set))]
-                   (set/union
-                    (set (filter :block/name refs))
-                    block-refs)))]
-    (-> block
-        (update :block/refs aux-fn)
-        (update :block/path-refs aux-fn))))
-
-;; TODO: refactor with reset-file!
-(defn extract-all-blocks-pages
-  [repo-url files metadata refresh?]
-  (when (seq files)
-    (-> (p/all (map
-                 (fn [{:file/keys [path content]}]
-                   (when content
-                     (let [org? (= "org" (string/lower-case (util/get-file-ext path)))
-                           content (if org?
-                                     content
-                                     (text/scheduled-deadline-dash->star content))
-                           utf8-content (utf8/encode content)]
-                       (extract-blocks-pages repo-url path content utf8-content))))
-                 files))
-        (p/then (fn [result]
-                  (let [result (remove empty? result)]
-                    (when (seq result)
-                      (let [result (util/distinct-by (fn [[pages _blocks]]
-                                                       (let [page (first pages)]
-                                                         (:block/name page))) result)
-                            [pages blocks] (apply map concat result)
-                            block-ids (->> (map :block/uuid blocks)
-                                           (remove nil?))
-                            pages (with-ref-pages pages blocks)
-                            blocks (map (fn [block]
-                                          (let [id (:block/uuid block)
-                                                properties (merge (get-in metadata [:block/properties id])
-                                                                  (:block/properties block))]
-                                            (if (seq properties)
-                                              (assoc block :block/properties properties)
-                                              (dissoc block :block/properties))))
-                                     blocks)
-                            ;; To prevent "unique constraint" on datascript
-                            pages-index (map #(select-keys % [:block/name]) pages)
-                            block-ids-set (set (map (fn [uuid] [:block/uuid uuid]) block-ids))
-                            blocks (map #(remove-illegal-refs % block-ids-set refresh?) blocks)
-                            block-ids (map (fn [uuid] {:block/uuid uuid}) block-ids)]
-                        (apply concat [pages-index pages block-ids blocks])))))))))
-
 (defn extract-all-block-refs
   [content]
   (map second (re-seq #"\(\(([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})\)\)" content)))
+
+(comment
+  (def page (frontend.db/entity [:block/name (string/lower-case "Scripture (NASB 1995)")]))
+  (def page (frontend.db/entity [:block/name (string/lower-case "test")]))
+  (def file (:block/file page))
+  (def content (:file/content (db/pull (:db/id file))))
+
+  (do
+    (prn "Full parse: ")
+    (dotimes [_ 5] (time (do (mldoc/->edn content (mldoc/default-config :markdown)) nil)))
+    (prn "Parse outline-only")
+    (dotimes [_ 5] (time (do (mldoc/->edn content (mldoc/default-config :markdown {:parse_outline_only? true})) nil))))
+  (simple-benchmark []
+                    (mldoc/->edn content (mldoc/default-config :markdown))
+                    5)
+  (simple-benchmark []
+                    (mldoc/->edn content (mldoc/default-config :markdown {:parse_outline_only? true}))
+                    5)
+
+  )

+ 80 - 99
src/main/frontend/handler/file.cljs

@@ -18,12 +18,12 @@
             [frontend.handler.extract :as extract-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.state :as state]
-            [frontend.utf8 :as utf8]
             [frontend.util :as util]
             [lambdaisland.glogi :as log]
             [promesa.core :as p]
             [frontend.debug :as debug]
-            [frontend.mobile.util :as mobile]))
+            [frontend.mobile.util :as mobile]
+            [clojure.set :as set]))
 
 ;; TODO: extract all git ops using a channel
 
@@ -104,24 +104,6 @@
                    (log/error :nfs/load-files-error repo-url)
                    (log/error :exception error))))))
 
-(defn- remove-non-exists-refs!
-  [data]
-  (let [block-ids (->> (map :block/uuid data)
-                       (remove nil?)
-                       (set))
-        keep-block-ref-f (fn [refs]
-                           (filter (fn [ref]
-                                     (cond
-                                       (and (vector? ref) (= :block/uuid (first ref)))
-                                       (let [id (second ref)]
-                                         (or (contains? block-ids id)
-                                             (db/entity [:block/uuid id])))
-                                       :else
-                                       true)) refs))]
-    (map (fn [item]
-           (update item :block/refs keep-block-ref-f))
-      data)))
-
 (defn- page-exists-in-another-file
   "Conflict of files towards same page"
   [repo-url page file]
@@ -131,74 +113,73 @@
        current-file))))
 
 (defn reset-file!
-  [repo-url file content]
-  (let [electron-local-repo? (and (util/electron?)
-                                  (config/local-db? repo-url))
-        file (cond
-               (and electron-local-repo?
-                    util/win32?
-                    (utils/win32 file))
-               file
+  ([repo-url file content]
+   (reset-file! repo-url file content false))
+  ([repo-url file content new-graph?]
+   (let [electron-local-repo? (and (util/electron?)
+                                   (config/local-db? repo-url))
+         file (cond
+                (and electron-local-repo?
+                     util/win32?
+                     (utils/win32 file))
+                file
 
-               (and electron-local-repo? (or
-                                          util/win32?
-                                          (not= "/" (first file))))
-               (str (config/get-repo-dir repo-url) "/" file)
+                (and electron-local-repo? (or
+                                           util/win32?
+                                           (not= "/" (first file))))
+                (str (config/get-repo-dir repo-url) "/" file)
 
-               (and (mobile/native-android?) (not= "/" (first file)))
-               (str (config/get-repo-dir repo-url) "/" file)
+                (and (mobile/native-android?) (not= "/" (first file)))
+                (str (config/get-repo-dir repo-url) "/" file)
 
-               (and (mobile/native-ios?) (not= "/" (first file)))
-               file
+                (and (mobile/native-ios?) (not= "/" (first file)))
+                file
 
-               :else
-               file)
-        file (util/path-normalize file)
-        new? (nil? (db/entity [:file/path file]))]
-    ;; TODO: refactor with reset-file!
-    ;; TODO: missing text/scheduled-deadline-dash->star
-    (db/set-file-content! repo-url file content)
-    (let [format (format/get-format file)
-          utf8-content (utf8/encode content)
-          file-content [{:file/path file}]]
-      (p/let [tx (if (contains? config/mldoc-support-formats format)
-                   ;; TODO: compare the logic with extract-all-blocks-pages
-                   (p/let [[pages blocks] (extract-handler/extract-blocks-pages repo-url file content utf8-content)
-                           first-page (first pages)
-                           delete-blocks (->
-                                          (concat
-                                           (db/delete-file-blocks! repo-url file)
-                                           (when first-page (db/delete-page-blocks repo-url (:block/name first-page))))
-                                          (distinct))
-                           _ (when-let [current-file (page-exists-in-another-file repo-url first-page file)]
-                               (when (not= file current-file)
-                                 (let [error (str "Page already exists with another file: " current-file ", current file: " file)]
-                                   (state/pub-event! [:notification/show
-                                                      {:content error
-                                                       :status :error
-                                                       :clear? false}]))))
-                           ;; TODO: merge with the validation logic in extract-all-blocks-pages
-                           blocks (remove-non-exists-refs! blocks)
-                           block-ids (map (fn [block] {:block/uuid (:block/uuid block)}) blocks)
-                           pages (extract-handler/with-ref-pages pages blocks)
-                           ;; To prevent "unique constraint" on datascript
-                           pages-index (map #(select-keys % [:block/name]) pages)
-                           block-ids-set (set (map (fn [uuid] [:block/uuid uuid]) block-ids))
-                           blocks (map #(extract-handler/remove-illegal-refs % block-ids-set true) blocks) ;; refresh should be true or false?
-                           block-ids (map (fn [uuid] {:block/uuid uuid}) block-ids)]
-                     ;; does order matter?
-                     (concat file-content pages-index delete-blocks pages block-ids blocks))
-                   file-content)]
-        (let [tx (concat tx [(let [t (tc/to-long (t/now))] ;; TODO: use file system timestamp?
-                               (cond->
-                                 {:file/path file}
-                                 new?
-                                 (assoc :file/created-at t)))])]
-          (db/transact! repo-url tx))))))
+                :else
+                file)
+         file (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)
+           file-content [{:file/path file}]
+           tx (if (contains? config/mldoc-support-formats format)
+                (let [[pages blocks] (extract-handler/extract-blocks-pages repo-url file content)
+                      first-page (first pages)
+                      delete-blocks (->
+                                     (concat
+                                      (db/delete-file-blocks! repo-url file)
+                                      (when first-page (db/delete-page-blocks repo-url (:block/name first-page))))
+                                     (distinct))
+                      _ (when-let [current-file (page-exists-in-another-file repo-url first-page file)]
+                          (when (not= file current-file)
+                            (let [error (str "Page already exists with another file: " current-file ", current file: " file)]
+                              (state/pub-event! [:notification/show
+                                                 {:content error
+                                                  :status :error
+                                                  :clear? false}]))))
+                      block-ids (map (fn [block] {:block/uuid (:block/uuid block)}) blocks)
+                      block-refs-ids (->> (mapcat :block/refs blocks)
+                                          (filter (fn [ref] (and (vector? ref)
+                                                                 (= :block/uuid (first ref)))))
+                                          (map (fn [ref] {:block/uuid (second ref)}))
+                                          (seq))
+                      ;; To prevent "unique constraint" on datascript
+                      block-ids (set/union (set block-ids) (set block-refs-ids))
+                      pages (extract-handler/with-ref-pages pages blocks)
+                      pages-index (map #(select-keys % [:block/name]) pages)]
+                  ;; does order matter?
+                  (concat file-content pages-index delete-blocks pages block-ids blocks))
+                file-content)
+           tx (concat tx [(let [t (tc/to-long (t/now))] ;; TODO: use file system timestamp?
+                            (cond->
+                              {:file/path file}
+                              new?
+                              (assoc :file/created-at t)))])]
+       (db/transact! repo-url tx (when new-graph? {:new-graph? true}))))))
 
 ;; TODO: Remove this function in favor of `alter-files`
 (defn alter-file
-  [repo path content {:keys [reset? re-render-root? from-disk? skip-compare?]
+  [repo path content {:keys [reset? re-render-root? from-disk? skip-compare? new-graph?]
                       :or {reset? true
                            re-render-root? false
                            from-disk? false
@@ -209,24 +190,24 @@
                       #(fs/write-file! repo (config/get-repo-dir repo) path content
                                        (assoc (when original-content {:old-content original-content})
                                               :skip-compare? skip-compare?)))]
-    (p/let [_ (if reset?
-                (do
-                  (when-let [page-id (db/get-file-page-id path)]
-                    (db/transact! repo
-                      [[:db/retract page-id :block/alias]
-                       [:db/retract page-id :block/tags]]))
-                  (reset-file! repo path content))
-                (db/set-file-content! repo path content))]
-      (util/p-handle (write-file!)
-                     (fn [_]
-                       (when (= path (config/get-config-path repo))
-                         (restore-config! repo true))
-                       (when (= path (config/get-custom-css-path repo))
-                         (ui-handler/add-style-if-exists!))
-                       (when re-render-root? (ui-handler/re-render-root!)))
-                     (fn [error]
-                       (println "Write file failed, path: " path ", content: " content)
-                       (log/error :write/failed error))))))
+    (if reset?
+      (do
+        (when-let [page-id (db/get-file-page-id path)]
+          (db/transact! repo
+            [[:db/retract page-id :block/alias]
+             [:db/retract page-id :block/tags]]))
+        (reset-file! repo path content new-graph?))
+      (db/set-file-content! repo path content))
+    (util/p-handle (write-file!)
+                   (fn [_]
+                     (when (= path (config/get-config-path repo))
+                       (restore-config! repo true))
+                     (when (= path (config/get-custom-css-path repo))
+                       (ui-handler/add-style-if-exists!))
+                     (when re-render-root? (ui-handler/re-render-root!)))
+                   (fn [error]
+                     (println "Write file failed, path: " path ", content: " content)
+                     (log/error :write/failed error)))))
 
 (defn set-file-content!
   [repo path new-content]

+ 20 - 10
src/main/frontend/handler/link.cljs

@@ -1,12 +1,14 @@
 (ns frontend.handler.link
   (:require [frontend.util :as util]))
 
-(def plain-link "(\bhttps?://)?[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]")
-(def org-link-re-1 (re-pattern (util/format "\\[\\[(%s)\\]\\[(.+)\\]\\]" plain-link)))
-(def org-link-re-2 (re-pattern (util/format "\\[\\[(%s)\\]\\]" plain-link)))
-(def markdown-link-re (re-pattern (util/format "^\\[(.+)\\]\\((%s)\\)" plain-link)))
+(def plain-link-re-string
+  "(?:http://www\\.|https://www\\.|http://|https://){1}[a-z0-9]+(?:[\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(?:.*)*")
+;; Based on https://orgmode.org/manual/Link-Format.html#Link-Format
+(def org-link-re-1 (re-pattern (util/format "\\[\\[(%s)\\]\\[(.+)\\]\\]" plain-link-re-string)))
+(def org-link-re-2 (re-pattern (util/format "\\[\\[(%s)\\]\\]" plain-link-re-string)))
+(def markdown-link-re (re-pattern (util/format "^\\[(.+)\\]\\((%s)\\)" plain-link-re-string)))
 
-(defn- org-link?
+(defn- org-link
   [link]
   (when-let [matches (or (re-matches org-link-re-1 link)
                          (re-matches org-link-re-2 link))]
@@ -14,15 +16,23 @@
      :url (second matches)
      :label (nth matches 2 nil)}))
 
-(defn- markdown-link?
+(defn- markdown-link
   [link]
   (when-let [matches (re-matches markdown-link-re link)]
     {:type "markdown-link"
      :url (nth matches 2)
      :label (second matches)}))
 
-(defn link?
+(defn- plain-link
   [link]
-  (or (util/url? link)
-      (org-link? link)
-      (markdown-link? link)))
+  (when (util/url? link)
+    {:type "plain-link"
+     :url link}))
+
+(defn link
+  "If the given string is an org, markdown or plain url, return a map indicating
+  the url type, url itself and the optional label."
+  [link]
+  (or (plain-link link)
+      (org-link link)
+      (markdown-link link)))

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

@@ -5,7 +5,6 @@
             [frontend.config :as config]
             [frontend.date :as date]
             [frontend.db :as db]
-            [frontend.db.model :as db-model]
             [frontend.dicts :as dicts]
             [frontend.encrypt :as encrypt]
             [frontend.format :as format]
@@ -13,7 +12,6 @@
             [frontend.fs.nfs :as nfs]
             [frontend.git :as git]
             [frontend.handler.common :as common-handler]
-            [frontend.handler.extract :as extract-handler]
             [frontend.handler.file :as file-handler]
             [frontend.handler.git :as git-handler]
             [frontend.handler.notification :as notification]
@@ -28,7 +26,6 @@
             [lambdaisland.glogi :as log]
             [promesa.core :as p]
             [shadow.resource :as rc]
-            [clojure.set :as set]
             [frontend.mobile.util :as mobile-util]
             [frontend.db.persist :as db-persist]
             [electron.ipc :as ipc]))
@@ -146,35 +143,6 @@
              _ (create-custom-theme repo-url)]
        (state/pub-event! [:page/create-today-journal repo-url])))))
 
-(defn- remove-non-exists-refs!
-  [data all-block-ids]
-  (let [block-ids (->> (->> (map :block/uuid data)
-                        (remove nil?)
-                        (set))
-                       (set/union (set all-block-ids)))
-        keep-block-ref-f (fn [refs]
-                           (filter (fn [ref]
-                                     (if (and (vector? ref)
-                                              (= :block/uuid (first ref)))
-                                       (contains? block-ids (second ref))
-                                       ref)) refs))]
-    (map (fn [item]
-           (if (and (map? item)
-                    (:block/uuid item))
-             (update item :block/refs keep-block-ref-f)
-             item)) data)))
-
-(defn- reset-contents-and-blocks!
-  [repo-url files blocks-pages delete-files delete-blocks refresh?]
-  (db/transact-files-db! repo-url files)
-  (let [files (map #(select-keys % [:file/path :file/last-modified-at]) files)
-        all-data (-> (concat delete-files delete-blocks files blocks-pages)
-                     (util/remove-nils))
-        all-data (if refresh?
-                   (remove-non-exists-refs! all-data (db-model/get-all-block-uuids))
-                   (remove-non-exists-refs! all-data nil))]
-    (db/transact! repo-url all-data)))
-
 (defn- load-pages-metadata!
   [repo file-paths files]
   (try
@@ -202,21 +170,24 @@
 
 (defn- parse-files-and-create-default-files-inner!
   [repo-url files delete-files delete-blocks file-paths first-clone? db-encrypted? re-render? re-render-opts metadata opts]
-  (p/let [refresh? (:refresh? opts)
-          parsed-files (filter
-                        (fn [file]
-                          (let [format (format/get-format (:file/path file))]
-                            (contains? config/mldoc-support-formats format)))
-                        files)
-          blocks-pages (if (seq parsed-files)
-                         (extract-handler/extract-all-blocks-pages repo-url parsed-files metadata refresh?)
-                         [])]
-    (let [config-file (config/get-config-path)]
-      (when (contains? (set file-paths) config-file)
-        (when-let [content (some #(when (= (:file/path %) config-file)
-                                    (:file/content %)) files)]
-          (file-handler/restore-config! repo-url content true))))
-    (reset-contents-and-blocks! repo-url files blocks-pages delete-files delete-blocks refresh?)
+  (let [parsed-files (filter
+                      (fn [file]
+                        (let [format (format/get-format (:file/path file))]
+                          (contains? config/mldoc-support-formats format)))
+                      files)
+        parsed-files (sort-by :file/path parsed-files)
+        new-graph? (:new-graph? opts)
+        delete-data (->> (concat delete-files delete-blocks)
+                         (remove nil?))]
+    (when (seq delete-data) (db/transact! repo-url delete-data))
+    (doseq [file parsed-files]
+      (file-handler/alter-file repo-url
+                               (:file/path file)
+                               (:file/content file)
+                               {:new-graph? new-graph?
+                                :re-render-root? false
+                                :from-disk? true
+                                :metadata metadata}))
     (load-pages-metadata! repo-url file-paths files)
     (when first-clone?
       (if (and (not db-encrypted?) (state/enable-encryption? repo-url))
@@ -225,7 +196,7 @@
         (create-default-files! repo-url db-encrypted?)))
     (when re-render?
       (ui-handler/re-render-root! re-render-opts))
-    (state/set-importing-to-db! false)
+    (state/set-parsing-files! false)
     (state/pub-event! [:graph/added repo-url])))
 
 (defn- parse-files-and-create-default-files!
@@ -239,32 +210,42 @@
       (parse-files-and-create-default-files-inner! repo-url files delete-files delete-blocks file-paths first-clone? db-encrypted? re-render? re-render-opts metadata opts))
     (parse-files-and-create-default-files-inner! repo-url files delete-files delete-blocks file-paths first-clone? db-encrypted? re-render? re-render-opts metadata opts)))
 
+(defn- update-parsing-state!
+  [repo-url refresh?]
+  (state/set-loading-files! repo-url false)
+  (when-not refresh? (state/set-parsing-files! true)))
+
 (defn parse-files-and-load-to-db!
   [repo-url files {:keys [first-clone? delete-files delete-blocks re-render? re-render-opts refresh?] :as opts
                    :or {re-render? true}}]
-  (state/set-loading-files! repo-url false)
-  (when-not refresh? (state/set-importing-to-db! true))
-  (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 first-clone? db-encrypted? re-render? re-render-opts metadata 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 first-clone? db-encrypted? re-render? re-render-opts metadata opts))))
+  (update-parsing-state! repo-url refresh?)
+
+  (let [f (fn []
+            (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 first-clone? db-encrypted? re-render? re-render-opts metadata 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 first-clone? db-encrypted? re-render? re-render-opts metadata opts))))]
+    (if util/node-test?
+      (f)
+      (js/setTimeout f 100))))
 
 (defn load-repo-to-db!
-  [repo-url {:keys [first-clone? diffs nfs-files refresh?]}]
+  [repo-url {:keys [first-clone? diffs nfs-files refresh? new-graph?]}]
   (spec/validate :repos/url repo-url)
   (when (= :repos (state/get-current-route))
     (route-handler/redirect-to-home!))
+
   (let [config (or (state/get-config repo-url)
                    (when-let [content (some-> (first (filter #(= (config/get-config-path repo-url) (:file/path %)) nfs-files))
                                         :file/content)]
@@ -282,7 +263,8 @@
                            (parse-files-and-load-to-db! repo-url files-contents (assoc option :refresh? refresh?)))))]
     (cond
       (and (not (seq diffs)) nfs-files)
-      (parse-files-and-load-to-db! repo-url nfs-files {:first-clone? true})
+      (parse-files-and-load-to-db! repo-url nfs-files {:first-clone? true
+                                                       :new-graph? new-graph?})
 
       (and first-clone? (not nfs-files))
       (->

+ 24 - 8
src/main/frontend/handler/search.cljs

@@ -5,7 +5,10 @@
             [frontend.search :as search]
             [frontend.state :as state]
             [frontend.util :as util]
-            [promesa.core :as p]))
+            [promesa.core :as p]
+            [frontend.text :as text]
+            [frontend.util.drawer :as drawer]
+            [frontend.util.property :as property]))
 
 (defn add-search-to-recent!
   [repo q]
@@ -15,6 +18,13 @@
           new-items (take 10 (distinct (cons q items)))]
       (db/set-key-value repo :recent/search new-items))))
 
+(defn sanity-search-content
+  "Convert a block to the display contents for searching"
+  [format content]
+  (->> (text/remove-level-spaces content format)
+       (drawer/remove-logbook)
+       (property/remove-built-in-properties format)))
+
 (defn search
   ([repo q]
    (search repo q {:limit 20}))
@@ -27,7 +37,10 @@
                         (:db/id (db/entity repo [:block/name (util/page-name-sanity-lc page-db-id)]))
                         page-db-id)
            opts (if page-db-id (assoc opts :page (str page-db-id)) opts)]
-       (p/let [blocks (search/block-search repo q opts)]
+       (p/let [blocks (search/block-search repo q opts)
+               blocks (map (fn [b]
+                             (let [format (:block/format (db/entity [:block/uuid (:block/uuid b)]))]
+                               (update b :block/content (partial sanity-search-content format)))) blocks)]
          (let [result (merge
                        {:blocks blocks
                         :has-more? (= limit (count blocks))}
@@ -48,9 +61,12 @@
      (state/set-search-mode! :global))))
 
 (defn rebuild-indices!
-  []
-  (println "Starting to rebuild search indices!")
-  (p/let [_ (search/rebuild-indices!)]
-    (notification-handler/show!
-     "Search indices rebuilt successfully!"
-     :success)))
+  ([]
+   (rebuild-indices! false))
+  ([notice?]
+   (println "Starting to rebuild search indices!")
+   (p/let [_ (search/rebuild-indices!)]
+     (when notice?
+       (notification-handler/show!
+        "Search indices rebuilt successfully!"
+        :success)))))

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

@@ -174,14 +174,14 @@
                            (repo-handler/start-repo-db-if-not-exists! repo {:db-type :local-native-fs})
                            (p/let [_ (repo-handler/load-repo-to-db! repo
                                                                     {:first-clone? true
+                                                                     :new-graph?   true
                                                                      :nfs-files    files})]
                              (state/add-repo! {:url repo :nfs? true})
                              (state/set-loading-files! repo false)
-                             (and ok-handler (ok-handler))
+                             (when ok-handler (ok-handler))
                              (when (util/electron?)
                                (fs/watch-dir! dir-name))
-                             (state/pub-event! [:graph/added repo])
-                             (db/persist! repo)))))
+                             (db/persist-if-idle! repo)))))
                (p/catch (fn [error]
                           (log/error :nfs/load-files-error repo)
                           (log/error :exception error)))))))

+ 3 - 40
src/main/frontend/modules/outliner/core.cljs

@@ -7,7 +7,6 @@
             [frontend.db.conn :as conn]
             [frontend.db.outliner :as db-outliner]
             [frontend.modules.outliner.datascript :as ds]
-            [frontend.modules.outliner.state :as outliner-state]
             [frontend.modules.outliner.tree :as tree]
             [frontend.modules.outliner.utils :as outliner-u]
             [frontend.state :as state]
@@ -39,27 +38,6 @@
       [:block/uuid left-id])
     (block)))
 
-(defn- index-blocks-by-left-id
-  [blocks]
-  (reduce
-    (fn [acc block]
-      (assert (tree/satisfied-inode? block) "Block should match satisfied-inode?.")
-      (let [left-id (tree/-get-left-id block)]
-        (when (get acc left-id)
-          (prn "acc: " acc)
-          (prn "block: " (:data block))
-          (throw (js/Error. "There are two blocks have the same left-id")))
-        (assoc acc left-id block)))
-    {}
-    blocks))
-
-(defn get-children
-  [id]
-  (let [repo (state/get-current-repo)]
-   (some->>
-     (outliner-state/get-by-parent-id repo [:block/uuid id])
-     (mapv block))))
-
 (defn- block-with-timestamps
   [block]
   (let [updated-at (util/time-ms)
@@ -209,24 +187,9 @@
       block-id))
 
   (-get-children [this]
-    (let [children (get-children (tree/-get-id this))]
-      (when (seq children)
-        (let [left-id->block (index-blocks-by-left-id children)]
-          (loop [sorted-children []
-                 current-node this]
-            (let [id (tree/-get-id current-node)]
-              (if-let [right (get left-id->block id)]
-                (recur (conj sorted-children right) right)
-                (do
-                  (let [should-equal
-                        (=
-                          (count children)
-                          (count sorted-children))]
-                    (when-not should-equal
-                      (prn "children: " (mapv #(get-in % [:data :block/uuid]) children))
-                      (prn "sorted-children: " (mapv #(get-in % [:data :block/uuid]) sorted-children))
-                      (throw (js/Error. "Number of children and sorted-children are not equal."))))
-                  sorted-children)))))))))
+    (let [parent-id (tree/-get-id this)
+          children (db-model/get-block-immediate-children (state/get-current-repo) parent-id)]
+      (map block children))))
 
 (defn set-block-collapsed! [txs-state id collapsed?]
   (swap! txs-state concat [{:db/id id

+ 1 - 1
src/main/frontend/modules/outliner/file.cljs

@@ -25,7 +25,7 @@
   [repo page-db-id]
   (let [page-block (db/pull repo '[*] page-db-id)
         page-db-id (:db/id page-block)
-        blocks (model/get-blocks-by-page repo page-db-id)]
+        blocks (model/get-page-blocks-no-cache repo (:block/name page-block))]
     (when-not (and (= 1 (count blocks))
                    (string/blank? (:block/content (first blocks)))
                    (nil? (:block/file page-block)))

+ 0 - 16
src/main/frontend/modules/outliner/state.cljs

@@ -1,16 +0,0 @@
-(ns frontend.modules.outliner.state
-  (:require [frontend.db.outliner :as db-outliner]
-            [frontend.db.react :as react]
-            [frontend.modules.outliner.utils :as u-outliner]
-            [frontend.util :as util]))
-
-(defn get-by-parent-id
-  [repo id]
-  (->
-    (react/q repo [:block/children (str (u-outliner/->block-id id))]
-      {:use-cache? false}
-      db-outliner/get-by-parent-id
-      (u-outliner/->block-lookup-ref id))
-    (util/react)
-    (flatten)
-    (seq)))

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

@@ -295,7 +295,7 @@
 
    :search/re-index                {:desc    "Rebuild search index"
                                     :binding "mod+c mod+s"
-                                    :fn      search-handler/rebuild-indices!}
+                                    :fn      (fn [_] (search-handler/rebuild-indices! true))}
 
    :sidebar/open-today-page        {:desc    "Open today's page in the right sidebar"
                                     :binding (if mac? "mod+shift+j" "alt+shift+j")

+ 483 - 484
src/main/frontend/modules/shortcut/dict.cljs

@@ -4,487 +4,486 @@
 
 (def dict
   (shortcut-dict
-   (dh/desc-helper)
-   (dh/category-helper)
-   {:zh-CN
-    {:shortcut.category/formatting            "格式化"
-     :shortcut.category/basics                "基础操作"
-     :shortcut.category/navigating            "移动"
-     :shortcut.category/block-editing         "块编辑基本"
-     :shortcut.category/block-command-editing "块编辑文本操作"
-     :shortcut.category/block-selection       "块选择操作"
-     :shortcut.category/toggle                "切换"
-     :shortcut.category/others                "其他"
-     :command.editor/indent                  "缩进块标签"
-     :command.editor/outdent                 "取消缩进块"
-     :command.editor/move-block-up           "向上移动块"
-     :command.editor/move-block-down         "向下移动块"
-     :command.editor/new-block               "创建块"
-     :command.editor/new-line                "块中新建行"
-     :command.editor/zoom-in                 "聚焦"
-     :command.editor/zoom-out                "退出聚焦"
-     :command.editor/follow-link             "跟随光标下的链接"
-     :command.editor/open-link-in-sidebar    "在侧边栏打开"
-     :command.editor/expand-block-children   "展开"
-     :command.editor/collapse-block-children "折叠"
-     :command.editor/select-block-up         "选择上方的块"
-     :command.editor/select-block-down       "选择下方的块"
-     :command.editor/select-all-blocks       "选择所有块"
-     :command.ui/toggle-help                 "显示/关闭帮助"
-     :command.git/commit                     "提交消息"
-     :command.go/search                      "全文搜索"
-     :command.go/backward                    "回退"
-     :command.go/forward                     "前进"
-     :command.go/search-in-page              "在当前页面搜索"
-     :command.ui/toggle-document-mode        "切换文档模式"
-     :command.ui/toggle-contents             "打开/关闭目录"
-     :command.ui/toggle-theme                "在暗色/亮色主题之间切换"
-     :command.ui/toggle-right-sidebar        "启用/关闭右侧栏"
-     :command.ui/toggle-settings             "显示/关闭设置"
-     :command.go/journals                    "跳转到日记"
-     :command.ui/toggle-wide-mode            "切换宽屏模式"
-     :command.ui/toggle-brackets             "切换是否显示括号"
-     :command.search/re-index                "重新建立搜索索引"
-     :command.editor/bold                    "粗体"
-     :command.editor/italics                 "斜体"
-     :command.editor/insert-link             "Html 链接"
-     :command.editor/highlight               "高亮"
-     :command.editor/undo                    "撤销"
-     :command.editor/redo                    "重做"
-     :command.editor/copy                    "复制"
-     :command.editor/cut                     "剪切"
-     :command.editor/up                      "向上移动光标 / 向上选择"
-     :command.editor/down                    "向下移动光标 / 向下选择"
-     :command.editor/left                    "向左移动光标 / 向左选择"
-     :command.editor/right                   "向右移动光标 / 向右选择"
-     :command.editor/backspace               "向左删除"
-     :command.editor/delete                  "向右删除"
-     :command.editor/cycle-todo              "切换TODO状态"
-     :command.editor/clear-block             "清除块内容"
-     :command.editor/kill-line-before        "删除光标右侧行"
-     :command.editor/kill-line-after         "删除光标左侧行"
-     :command.editor/beginning-of-block      "移动光标到块开始位置"
-     :command.editor/end-of-block            "移动光标到块末尾"
-     :command.editor/forward-word            "光标向后移动一个单词"
-     :command.editor/backward-word           "光标向前移动一个单词"
-     :command.editor/forward-kill-word       "向后删除一个单词"
-     :command.editor/backward-kill-word      "向前删除一个单词"
-     :command.editor/open-edit               "编辑选中块"
-     :command.editor/delete-selection        "删除选中块"
-     :command.editor/toggle-open-blocks      "切换折叠/展开所有块(非编辑状态)"}
-    :zh-Hant
-    {:command.editor/indent                  "縮進塊標簽"
-     :command.editor/outdent                 "取消縮進塊"
-     :command.editor/move-block-up           "向上移動塊"
-     :command.editor/move-block-down         "向下移動塊"
-     :command.editor/new-block               "創建塊"
-     :command.editor/new-line                "塊中新建行"
-     :command.editor/zoom-in                 "聚焦"
-     :command.editor/zoom-out                "推出聚焦"
-     :command.editor/follow-link             "跟隨光標下的鏈接"
-     :command.editor/open-link-in-sidebar    "在側邊欄打開"
-     :command.editor/expand-block-children   "展開"
-     :command.editor/collapse-block-children "折疊"
-     :command.editor/select-block-up         "選擇上方的塊"
-     :command.editor/select-block-down       "選擇下方的塊"
-     :command.editor/select-all-blocks       "選擇所有塊"
-     :command.ui/toggle-help                 "顯示/關閉幫助"
-     :command.git/commit                     "提交消息"
-     :command.go/search                      "全文搜索"
-     :command.ui/toggle-document-mode        "切換文檔模式"
-     :command.ui/toggle-theme                "“在暗色/亮色主題之間切換”"
-     :command.ui/toggle-right-sidebar        "啟用/關閉右側欄"
-     :command.go/journals                    "跳轉到日記"}
-    :de
-    {:shortcut.category/formatting            "Formatierung"
-     :command.editor/indent                  "Block einrücken"
-     :command.editor/outdent                 "Block ausrücken"
-     :command.editor/move-block-up           "Block nach oben verschieben"
-     :command.editor/move-block-down         "Block nach unten verschieben"
-     :command.editor/new-block               "Neuen Block erstellen"
-     :command.editor/new-line                "Neue Zeile innerhalb des Blocks erstellen"
-     :command.editor/zoom-in                 "Heranzoomen"
-     :command.editor/zoom-out                "Herauszoomen"
-     :command.editor/follow-link             "Link unter dem Cursor folgen"
-     :command.editor/open-link-in-sidebar    "Link in Seitenleiste öffnen"
-     :command.editor/expand-block-children   "Erweitern"
-     :command.editor/collapse-block-children "Zusammenklappen"
-     :command.editor/select-block-up         "Block oberhalb auswählen"
-     :command.ui/toggle-help                 "Hilfe aktivieren"
-     :command.go/search                      "Volltextsuche"
-     :command.ui/toggle-document-mode        "Dokumentenmodus umschalten"
-     :command.ui/toggle-theme                "Umschalten zwischen dunklem/hellem Thema"
-     :command.ui/toggle-right-sidebar        "Rechte Seitenleiste umschalten"
-     :command.go/journals                    "Zu Journalen springen"
-     :command.git/commit                     "Git Commit-Nachricht"
-     :command.editor/select-block-down       "Block unterhalb auswählen"
-     :command.editor/select-all-blocks       "Alle Blöcke auswählen"}
-    :fr
-    {:shortcut.category/formatting            "Formats"
-     :command.editor/indent                  "Indenter un Bloc vers la droite"
-     :command.editor/outdent                 "Indenter un Bloc vers la gauche"
-     :command.editor/move-block-up           "Déplacer un bloc au dessus"
-     :command.editor/move-block-down         "Déplacer un bloc en dessous"
-     :command.editor/new-block               "Créer un nouveau bloc"
-     :command.editor/new-line                "Aller à la ligne dans un bloc"
-     :command.editor/zoom-in                 "Zoomer"
-     :command.editor/zoom-out                "Dézoomer"
-     :command.editor/follow-link             "Suivre le lien sous le curseur"
-     :command.editor/open-link-in-sidebar    "Ouvrir le lien dans la barre latérale"
-     :command.editor/expand-block-children   "Etendre"
-     :command.editor/collapse-block-children "Réduire"
-     :command.editor/select-block-up         "Sélectionner le bloc au dessus"
-     :command.editor/select-block-down       "Sélectionner le bloc en dessous"
-     :command.editor/select-all-blocks       "Sélectionner tous les blocs"
-     :command.ui/toggle-help                 "Afficher l'aide"
-     :command.git/commit                     "Message de commit Git"
-     :command.go/search                      "Recherche globale dans le texte"
-     :command.ui/toggle-document-mode        "Intervertir le mode document"
-     :command.ui/toggle-theme                "Intervertir le thème foncé/clair"
-     :command.ui/toggle-right-sidebar        "Afficher/cacher la barre latérale"
-     :command.go/journals                    "Aller au Journal"}
-    :af
-    {:shortcut.category/formatting            "Formatering"
-     :command.editor/indent                  "Ingekeepte blok oortjie"
-     :command.editor/outdent                 "Oningekeepte blok"
-     :command.editor/move-block-up           "Skuif Blok Boontoe"
-     :command.editor/move-block-down         "Skuif Blok Ondertoe"
-     :command.editor/new-block               "Skep 'n nuwe blok"
-     :command.editor/new-line                "Nuwe lyn in blok"
-     :command.editor/zoom-in                 "Zoem in"
-     :command.editor/zoom-out                "Zoem uit"
-     :command.editor/follow-link             "Volg die skakel onder die wyser"
-     :command.editor/open-link-in-sidebar    "Maak skakel in kantlys oop"
-     :command.editor/expand-block-children   "Brei uit"
-     :command.editor/collapse-block-children "Vou in"
-     :command.editor/select-block-up         "Kies blok bo"
-     :command.editor/select-block-down       "Kies blok onder"
-     :command.editor/select-all-blocks       "Kies alle blokke"
-     :command.ui/toggle-help                 "Wissel help"
-     :command.git/commit                     "Jou git stoor boodskap"
-     :command.go/search                      "Volteks soek"
-     :command.ui/toggle-document-mode        "Wissel dokument modus"
-     :command.go/journals                    "Spring na joernale"
-     :command.ui/toggle-theme                "Wissel tussen donker/lig temas"
-     :command.ui/toggle-right-sidebar        "Wissel regter sybalk"}
-    :es
-    {:shortcut.category/formatting            "Formato"
-     :shortcut.category/basics                "Básico"
-     :shortcut.category/navigating            "Navegación"
-     :shortcut.category/block-editing         "Edición de bloque general"
-     :shortcut.category/block-command-editing "Comandos edición de bloque"
-     :shortcut.category/block-selection       "Selección de bloques (pulsar Esc para salir)"
-     :shortcut.category/toggle                "Alternar"
-     :shortcut.category/others                "Otros"
-     :command.editor/indent                  "Aumentar sangría"
-     :command.editor/outdent                 "Disminuir sangría"
-     :command.editor/move-block-up           "Mover bloque arriba"
-     :command.editor/move-block-down         "Mover bloque abajo"
-     :command.editor/new-block               "Crear bloque nuevo"
-     :command.editor/new-line                "Nueva linea en bloque"
-     :command.editor/zoom-in                 "Acercar / Adelante"
-     :command.editor/zoom-out                "Alejar / Atrás"
-     :command.editor/follow-link             "Seguir enlace bajo el cursor"
-     :command.editor/open-link-in-sidebar    "Abrir enlace en barra lateral"
-     :command.editor/expand-block-children   "Expandir"
-     :command.editor/collapse-block-children "Colapsar"
-     :command.editor/select-block-up         "Seleccionar bloque de arriba"
-     :command.editor/select-block-down       "Seleccionar bloque de abajo"
-     :command.editor/select-all-blocks       "Seleccionar todos los bloques"
-     :command.ui/toggle-help                 "Alternar ayuda"
-     :command.git/commit                     "Confirmar"
-     :command.go/search                      "Buscar en el grafo"
-     :command.go/search-in-page              "Buscar en la página actual"
-     :command.ui/toggle-document-mode        "Alternar modo documento"
-     :command.ui/toggle-contents             "Alternar Contenido en barra lateral"
-     :command.ui/toggle-theme                "Alternar entre tema claro/oscuro"
-     :command.ui/toggle-right-sidebar        "Alternar barra lateral"
-     :command.ui/toggle-settings             "Alternar Opciones"
-     :command.go/journals                    "Ir a los diarios"
-     :command.ui/toggle-wide-mode            "Alternar modo ancho"
-     :command.ui/toggle-brackets             "Alternar corchetes"
-     :command.search/re-index                "Reconstruir índice de búsqueda"
-     :command.editor/bold                    "Negrita"
-     :command.editor/italics                 "Cursiva"
-     :command.editor/insert-link             "Enlace html"
-     :command.editor/highlight               "Resaltado"
-     :command.editor/undo                    "Deshacer"
-     :command.editor/redo                    "Rehacer"
-     :command.editor/copy                    "Copiar"
-     :command.editor/cut                     "Pegar"
-     :command.editor/up                      "Mover cursor arriba / Seleccionar arriba"
-     :command.editor/down                    "Mover cursor abajo / Seleccionar abajo"
-     :command.editor/left                    "Mover cursor a la izquierda / Abrir bloque seleccionado al inicio"
-     :command.editor/right                   "Mover cursor a la derecha / Abrir bloque seleccionado al final"
-     :command.editor/backspace               "Retroceso / Eliminar hacia atrás"
-     :command.editor/delete                  "Suprimir / Eliminar hacia delante"
-     :command.editor/cycle-todo              "Rotar estado TODO del elemento"
-     :command.editor/clear-block             "Borrar contenido del bloque"
-     :command.editor/kill-line-before        "Borrar linea antes del cursor"
-     :command.editor/kill-line-after         "Borrar linea despues del cursor"
-     :command.editor/beginning-of-block      "Mover cursor al inicio del bloque"
-     :command.editor/end-of-block            "Mover cursor al final del bloque"
-     :command.editor/forward-word            "Mover cursor una palabra adelante"
-     :command.editor/backward-word           "Mover cursor una palabra atrás"
-     :command.editor/forward-kill-word       "Borrar palabra posterior"
-     :command.editor/backward-kill-word      "Borrar palabra anterior"
-     :command.editor/open-edit               "Editar bloque seleccionado"
-     :command.editor/delete-selection        "Eliminar bloques seleccionados"
-     :command.editor/toggle-open-blocks      "Alternar bloques abieros, (colapsar o expandir todos)"}
-    :ru
-    {:shortcut.category/formatting            "Форматирование"
-     :shortcut.category/basics                "Базовые"
-     :shortcut.category/navigating            "Навигация"
-     :shortcut.category/block-editing         "Общее редактирование блока"
-     :shortcut.category/block-command-editing "Команды редактирования блока"
-     :shortcut.category/block-selection       "Выделение блоков (нажмите Esc для снятия выделения)"
-     :shortcut.category/toggle                "Переключатели"
-     :shortcut.category/others                "Разное"
-     :command.editor/indent                   "Увеличить отступ"
-     :command.editor/outdent                  "Уменьшить отступ"
-     :command.editor/move-block-up            "Передвинуть блок выше"
-     :command.editor/move-block-down          "Передвинуть блок ниже"
-     :command.editor/new-block                "Создать новый блок"
-     :command.editor/new-line                 "Новая строка в блоке"
-     :command.editor/zoom-in                  "Увеличить / Вперед"
-     :command.editor/zoom-out                 "Уменьшить / Назад"
-     :command.editor/follow-link              "Перейти по ссылке под курсором"
-     :command.editor/open-link-in-sidebar     "Открыть ссылку в боковой панели"
-     :command.editor/expand-block-children    "Раскрыть"
-     :command.editor/collapse-block-children  "Свернуть"
-     :command.editor/select-block-up          "Выбрать блок выше"
-     :command.editor/select-block-down        "Выбрать блок ниже"
-     :command.editor/select-all-blocks        "Выбрать все блоки"
-     :command.ui/toggle-help                  "Переключить помощь"
-     :command.git/commit                      "Подтвердить"
-     :command.go/search                       "Искать на графе"
-     :command.go/search-in-page               "Искать на текущей странице"
-     :command.ui/toggle-document-mode         "Переключить режи документа"
-     :command.ui/toggle-contents              "Переключить контент на боковой панели"
-     :command.ui/toggle-theme                 "Переключение между светлой / темной темой"
-     :command.ui/toggle-right-sidebar         "Переключить боковую панель"
-     :command.ui/toggle-settings              "Переключить параметры"
-     :command.go/journals                     "Перейти в Дневники"
-     :command.ui/toggle-wide-mode             "Переключить широкоформатный режим"
-     :command.ui/toggle-brackets              "Переключить скобки"
-     :command.search/re-index                 "Восстановить индекс поиска"
-     :command.editor/bold                     "Жирный"
-     :command.editor/italics                  "Курсив"
-     :command.editor/insert-link              "HTML ссылка"
-     :command.editor/highlight                "Выделение"
-     :command.editor/undo                     "Отменить"
-     :command.editor/redo                     "Вернуть"
-     :command.editor/copy                     "Копировать"
-     :command.editor/cut                      "Вырезать"
-     :command.editor/up                       "Переместить курсор вверх / Выбрать вверх"
-     :command.editor/down                     "Переместить курсор вниз / Выбрать вниз"
-     :command.editor/left                     "Переместить курсор влево / Открыть выбранный блок в начале"
-     :command.editor/right                    "Переместить курсор вправо / Открыть выбранный блок в конце"
-     :command.editor/backspace                "Удалить перед курсором"
-     :command.editor/delete                   "Удалить после курсора"
-     :command.editor/cycle-todo               "Переключить состояние элемента TODO"
-     :command.editor/clear-block              "Удалить содержимое блока"
-     :command.editor/kill-line-before         "Удалить строку до курсора"
-     :command.editor/kill-line-after          "Удалить строку после курсора"
-     :command.editor/beginning-of-block       "Переместите курсор в начало блока"
-     :command.editor/end-of-block             "Переместите курсор в конец блока"
-     :command.editor/forward-word             "Переместить курсор на одно слово вперед"
-     :command.editor/backward-word            "Переместить курсор на одно слово назад"
-     :command.editor/forward-kill-word        "Удалить следующее слово"
-     :command.editor/backward-kill-word       "Удалить предыдущее слово"
-     :command.editor/open-edit                "Редактировать выбранный блок"
-     :command.editor/delete-selection         "Удалить выбранные блоки"
-     :command.editor/toggle-open-blocks       "Переключить открытые блоки (свернуть или развернуть все)"}
-    :nb-NO
-    {:shortcut.category/formatting            "Formatering"
-     :shortcut.category/basics                "Basis"
-     :shortcut.category/navigating            "Navigasjon"
-     :shortcut.category/block-editing         "Blokkredigering generelt"
-     :shortcut.category/block-command-editing "Blokkredigering kommandoer"
-     :shortcut.category/block-selection       "Blokkseleksjon (trykk ESC for å avslutte)"
-     :shortcut.category/toggle                "Veksling"
-     :shortcut.category/others                "Annet"
-     :command.editor/indent                  "Innrykk inn"
-     :command.editor/outdent                 "Innrykk ut"
-     :command.editor/move-block-up           "Flytt blokk opp"
-     :command.editor/move-block-down         "Flytt blokk ned"
-     :command.editor/new-block               "Opprett ny blokk"
-     :command.editor/new-line                "Ny linje i nåværende blokk"
-     :command.editor/zoom-in                 "Zoom inn / Fremover"
-     :command.editor/zoom-out                "Zoom ut / Tilbake"
-     :command.editor/follow-link             "Følg lenke under markøren"
-     :command.editor/open-link-in-sidebar    "Åpne lenke i sidestolpe"
-     :command.editor/expand-block-children   "Utvid"
-     :command.editor/collapse-block-children "Slå sammen"
-     :command.editor/select-block-up         "Velg blokk over"
-     :command.editor/select-block-down       "Velg blokk under"
-     :command.editor/select-all-blocks       "Velg alle blokker"
-     :command.ui/toggle-help                 "Vis hjelp"
-     :command.git/commit                     "Git commit beskjed"
-     :command.go/search                      "Fulltekst søk"
-     :command.go/search-in-page              "Søk på nåværende side"
-     :command.ui/toggle-document-mode        "Aktiver dokumentmodus"
-     :command.ui/toggle-contents             "Åpne favoritter i sidestolpen"
-     :command.ui/toggle-theme                "Veksle mellom lyst og mørkt tema"
-     :command.ui/toggle-left-sidebar         "Aktiver venstre sidestolpe"
-     :command.ui/toggle-right-sidebar        "Aktiver høyre sidestolpe"
-     :command.ui/toggle-settings             "Åpne innstillinger"
-     :command.go/journals                    "Gå til dagbok"
-     :command.ui/toggle-wide-mode            "Aktiver vid-modus"
-     :command.ui/toggle-brackets             "Aktiver vising av klammer"
-     :command.search/re-index                "Gjenoppbygg søkeindeks"
-     :command.editor/bold                    "Fet"
-     :command.editor/italics                 "Kursiv"
-     :command.editor/insert-link             "HTML lenke"
-     :command.editor/highlight               "Markering"
-     :command.editor/undo                    "Angre"
-     :command.editor/redo                    "Gjør om"
-     :command.editor/copy                    "Kopier"
-     :command.editor/cut                     "Klipp ut"
-     :command.editor/up                      "Flytt markøren opp / Velg opp"
-     :command.editor/down                    "Flytt markøren ned / Velg ned"
-     :command.editor/left                    "Flytt markøren til venstre / Åpne valgt blokk på begynnelsen"
-     :command.editor/right                   "Flytt markøren til høyre / Åpne valgt blokk på slutten"
-     :command.editor/backspace               "Backspace / Slett bakover"
-     :command.editor/delete                  "Delete / Slett forover"
-     :command.editor/cycle-todo              "Veksle TODO status for valg linje"
-     :command.editor/clear-block             "Slett alt innhold i blokken"
-     :command.editor/kill-line-before        "Slett linje foran markøren"
-     :command.editor/kill-line-after         "Slett linsje etter markøren"
-     :command.editor/beginning-of-block      "Flytt markøren til begynnelsen av blokken"
-     :command.editor/end-of-block            "Flytt markøren til slutten av blokken"
-     :command.editor/forward-word            "Flytt markøren frem ett ord"
-     :command.editor/backward-word           "Flytt markøren bakover ett ord"
-     :command.editor/forward-kill-word       "Slett ett ord forover"
-     :command.editor/backward-kill-word      "Slett ett ord bakover"
-     :command.editor/open-edit               "Rediger valgt blokk"
-     :command.editor/delete-selection        "Slett valgte blokker"
-     :command.editor/toggle-open-blocks      "Veksle åpne blokker (slå sammen eller utvid alle blokker)"}
-     :pt-PT
-    {:shortcut.category/formatting            "Formatação"
-     :shortcut.category/basics                "Básico"
-     :shortcut.category/navigating            "Navegação"
-     :shortcut.category/block-editing         "Edição geral de blocos"
-     :shortcut.category/block-command-editing "Comandos de edição de blocos"
-     :shortcut.category/block-selection       "Seleção de blocos (premir Esc para sair)"
-     :shortcut.category/toggle                "Alternar"
-     :shortcut.category/others                "Outros"
-     :command.editor/indent                  "Aumentar avanço de parágrafo"
-     :command.editor/outdent                 "Diminuir avanço de parágrafo"
-     :command.editor/move-block-up           "Mover bloco para cima"
-     :command.editor/move-block-down         "Mover bloco para baixo"
-     :command.editor/new-block               "Criar novo bloco"
-     :command.editor/new-line                "Nova linha no bloco actual"
-     :command.editor/zoom-in                 "Aproximar / Para a frente"
-     :command.editor/zoom-out                "Afastar / Para trás"
-     :command.editor/follow-link             "Seguir ligação sob o cursor"
-     :command.editor/open-link-in-sidebar    "Abrir ligação na barra lateral"
-     :command.editor/expand-block-children   "Expandir"
-     :command.editor/collapse-block-children "Colapsar"
-     :command.editor/select-block-up         "Selecionar bloco acima"
-     :command.editor/select-block-down       "Selecionar bloco abaixo"
-     :command.editor/select-all-blocks       "Selecionar todos os blocos"
-     :command.ui/toggle-help                 "Alternar ajuda"
-     :command.git/commit                     "Confirmar"
-     :command.go/search                      "Pesquisar no grafo"
-     :command.go/search-in-page              "Pesquisar na página atual"
-     :command.ui/toggle-document-mode        "Alternar modo de documento"
-     :command.ui/toggle-contents             "Alternar Conteúdo na barra lateral"
-     :command.ui/toggle-theme                "Alternar entre tema claro/escuro"
-     :command.ui/toggle-right-sidebar        "Alternar barra lateral"
-     :command.ui/toggle-settings             "Alternar Opções"
-     :command.go/journals                    "Ir para diários"
-     :command.ui/toggle-wide-mode            "Alternar modo de ecrã amplo"
-     :command.ui/toggle-brackets             "Alternar parênteses rectos"
-     :command.search/re-index                "Reconstruir índice de pesquisa"
-     :command.editor/bold                    "Negrito"
-     :command.editor/italics                 "Itálico"
-     :command.editor/insert-link             "Inserir ligação html"
-     :command.editor/highlight               "Realçado"
-     :command.editor/undo                    "Desfazer"
-     :command.editor/redo                    "Refazer"
-     :command.editor/copy                    "Copiar"
-     :command.editor/cut                     "Cortar"
-     :command.editor/up                      "Mover cursor para cima / Selecionar para cima"
-     :command.editor/down                    "Mover cursor para baixo / Selecionar para baixo"
-     :command.editor/left                    "Mover cursor para a esquerda / Abrir bloco selecionado no início"
-     :command.editor/right                   "Mover cursor para a direita / Abrir bloco selecionado no final"
-     :command.editor/backspace               "Retroceder / Eliminar para atrás"
-     :command.editor/delete                  "Apagar / Eliminar para a frente"
-     :command.editor/cycle-todo              "Alternar estado TODO do elemento"
-     :command.editor/clear-block             "Apagar conteúdo do bloco"
-     :command.editor/kill-line-before        "Apagar linha antes do cursor"
-     :command.editor/kill-line-after         "Apagar linha depois do cursor"
-     :command.editor/beginning-of-block      "Mover o cursor para o início do bloco"
-     :command.editor/end-of-block            "Mover o cursor para o fim do bloco"
-     :command.editor/forward-word            "Mover o cursor para a próxima palavra"
-     :command.editor/backward-word           "Mover o cursor para a palavra anterior"
-     :command.editor/forward-kill-word       "Apagar a próxima palavra"
-     :command.editor/backward-kill-word      "Apagar a palavra anterior"
-     :command.editor/open-edit               "Editar bloco selecionado"
-     :command.editor/delete-selection        "Eliminar blocos selecionados"
-     :command.editor/toggle-open-blocks      "Alternar blocos abertos (colapsar ou expandir todos)"}
-    :pt-BR
-    {:shortcut.category/formatting            "Formatação"
-     :shortcut.category/basics                "Básico"
-     :shortcut.category/navigating            "Navegação"
-     :shortcut.category/block-editing         "Edição geral de blocos"
-     :shortcut.category/block-command-editing "Comandos de edição de blocos"
-     :shortcut.category/block-selection       "Seleção de blocos (aperte Esc para sair)"
-     :shortcut.category/toggle                "Alternar"
-     :shortcut.category/others                "Outros"
-     :command.editor/indent                  "Aumentar avanço de parágrafo"
-     :command.editor/outdent                 "Diminuir avanço de parágrafo"
-     :command.editor/move-block-up           "Mover bloco para cima"
-     :command.editor/move-block-down         "Mover bloco para baixo"
-     :command.editor/new-block               "Criar novo bloco"
-     :command.editor/new-line                "Nova linha no bloco actual"
-     :command.editor/zoom-in                 "Aproximar / Para a frente"
-     :command.editor/zoom-out                "Afastar / Para trás"
-     :command.editor/follow-link             "Seguir ligação sob o cursor"
-     :command.editor/open-link-in-sidebar    "Abrir ligação na barra lateral"
-     :command.editor/expand-block-children   "Expandir"
-     :command.editor/collapse-block-children "Recolher"
-     :command.editor/select-block-up         "Selecionar bloco acima"
-     :command.editor/select-block-down       "Selecionar bloco abaixo"
-     :command.editor/select-all-blocks       "Selecionar todos os blocos"
-     :command.ui/toggle-help                 "Alternar ajuda"
-     :command.git/commit                     "Confirmar"
-     :command.go/search                      "Pesquisar no grafo"
-     :command.go/search-in-page              "Pesquisar na página atual"
-     :command.ui/toggle-document-mode        "Alternar modo de documento"
-     :command.ui/toggle-contents             "Alternar Conteúdo na barra lateral"
-     :command.ui/toggle-theme                "Alternar entre tema claro/escuro"
-     :command.ui/toggle-right-sidebar        "Alternar barra lateral"
-     :command.ui/toggle-settings             "Alternar Opções"
-     :command.go/journals                    "Ir para diários"
-     :command.ui/toggle-wide-mode            "Alternar largura extendida"
-     :command.ui/toggle-brackets             "Alternar colchetes"
-     :command.search/re-index                "Reconstruir índice de pesquisa"
-     :command.editor/bold                    "Negrito"
-     :command.editor/italics                 "Itálico"
-     :command.editor/insert-link             "Inserir vínculo"
-     :command.editor/highlight               "Realçado"
-     :command.editor/undo                    "Desfazer"
-     :command.editor/redo                    "Refazer"
-     :command.editor/copy                    "Copiar"
-     :command.editor/cut                     "Cortar"
-     :command.editor/up                      "Mover cursor para cima / Selecionar para cima"
-     :command.editor/down                    "Mover cursor para baixo / Selecionar para baixo"
-     :command.editor/left                    "Mover cursor para a esquerda / Abrir bloco selecionado no início"
-     :command.editor/right                   "Mover cursor para a direita / Abrir bloco selecionado no final"
-     :command.editor/backspace               "Retroceder / Eliminar para trás"
-     :command.editor/delete                  "Deletar / Eliminar para frente"
-     :command.editor/cycle-todo              "Alternar estado A FAZER do elemento"
-     :command.editor/clear-block             "Apagar conteúdo do bloco"
-     :command.editor/kill-line-before        "Apagar linha antes do cursor"
-     :command.editor/kill-line-after         "Apagar linha depois do cursor"
-     :command.editor/beginning-of-block      "Mover o cursor para o início do bloco"
-     :command.editor/end-of-block            "Mover o cursor para o fim do bloco"
-     :command.editor/forward-word            "Mover o cursor para a próxima palavra"
-     :command.editor/backward-word           "Mover o cursor para a palavra anterior"
-     :command.editor/forward-kill-word       "Apagar a próxima palavra"
-     :command.editor/backward-kill-word      "Apagar a palavra anterior"
-     :command.editor/open-edit               "Editar bloco selecionado"
-     :command.editor/delete-selection        "Eliminar blocos selecionados"
-     :command.editor/toggle-open-blocks      "Alternar blocos abertos (recolher ou expandir todos)"}}))
+    (dh/desc-helper)
+    (dh/category-helper)
+    {:zh-CN   {:shortcut.category/formatting            "格式化"
+               :shortcut.category/basics                "基础操作"
+               :shortcut.category/navigating            "移动"
+               :shortcut.category/block-editing         "块编辑基本"
+               :shortcut.category/block-command-editing "块编辑文本操作"
+               :shortcut.category/block-selection       "块选择操作"
+               :shortcut.category/toggle                "切换"
+               :shortcut.category/others                "其他"
+               :command.editor/indent                   "缩进块标签"
+               :command.editor/outdent                  "取消缩进块"
+               :command.editor/move-block-up            "向上移动块"
+               :command.editor/move-block-down          "向下移动块"
+               :command.editor/new-block                "创建块"
+               :command.editor/new-line                 "块中新建行"
+               :command.editor/zoom-in                  "聚焦"
+               :command.editor/zoom-out                 "退出聚焦"
+               :command.editor/follow-link              "跟随光标下的链接"
+               :command.editor/open-link-in-sidebar     "在侧边栏打开"
+               :command.editor/expand-block-children    "展开"
+               :command.editor/collapse-block-children  "折叠"
+               :command.editor/select-block-up          "选择上方的块"
+               :command.editor/select-block-down        "选择下方的块"
+               :command.editor/select-all-blocks        "选择所有块"
+               :command.ui/toggle-help                  "显示/关闭帮助"
+               :command.git/commit                      "提交消息"
+               :command.go/search                       "全文搜索"
+               :command.go/backward                     "回退"
+               :command.go/forward                      "前进"
+               :command.go/search-in-page               "在当前页面搜索"
+               :command.ui/toggle-document-mode         "切换文档模式"
+               :command.ui/toggle-contents              "打开/关闭目录"
+               :command.ui/toggle-theme                 "在暗色/亮色主题之间切换"
+               :command.ui/toggle-right-sidebar         "启用/关闭右侧栏"
+               :command.ui/toggle-settings              "显示/关闭设置"
+               :command.go/journals                     "跳转到日记"
+               :command.ui/toggle-wide-mode             "切换宽屏模式"
+               :command.ui/toggle-brackets              "切换是否显示括号"
+               :command.search/re-index                 "重新建立搜索索引"
+               :command.editor/bold                     "粗体"
+               :command.editor/italics                  "斜体"
+               :command.editor/insert-link              "Html 链接"
+               :command.editor/highlight                "高亮"
+               :command.editor/undo                     "撤销"
+               :command.editor/redo                     "重做"
+               :command.editor/copy                     "复制"
+               :command.editor/cut                      "剪切"
+               :command.editor/up                       "向上移动光标 / 向上选择"
+               :command.editor/down                     "向下移动光标 / 向下选择"
+               :command.editor/left                     "向左移动光标 / 向左选择"
+               :command.editor/right                    "向右移动光标 / 向右选择"
+               :command.editor/backspace                "向左删除"
+               :command.editor/delete                   "向右删除"
+               :command.editor/cycle-todo               "切换TODO状态"
+               :command.editor/clear-block              "清除块内容"
+               :command.editor/kill-line-before         "删除光标右侧行"
+               :command.editor/kill-line-after          "删除光标左侧行"
+               :command.editor/beginning-of-block       "移动光标到块开始位置"
+               :command.editor/end-of-block             "移动光标到块末尾"
+               :command.editor/forward-word             "光标向后移动一个单词"
+               :command.editor/backward-word            "光标向前移动一个单词"
+               :command.editor/forward-kill-word        "向后删除一个单词"
+               :command.editor/backward-kill-word       "向前删除一个单词"
+               :command.editor/open-edit                "编辑选中块"
+               :command.editor/delete-selection         "删除选中块"
+               :command.editor/toggle-open-blocks       "切换折叠/展开所有块(非编辑状态)"}
+
+     :zh-Hant {:command.editor/indent                  "縮進塊標簽"
+               :command.editor/outdent                 "取消縮進塊"
+               :command.editor/move-block-up           "向上移動塊"
+               :command.editor/move-block-down         "向下移動塊"
+               :command.editor/new-block               "創建塊"
+               :command.editor/new-line                "塊中新建行"
+               :command.editor/zoom-in                 "聚焦"
+               :command.editor/zoom-out                "推出聚焦"
+               :command.editor/follow-link             "跟隨光標下的鏈接"
+               :command.editor/open-link-in-sidebar    "在側邊欄打開"
+               :command.editor/expand-block-children   "展開"
+               :command.editor/collapse-block-children "折疊"
+               :command.editor/select-block-up         "選擇上方的塊"
+               :command.editor/select-block-down       "選擇下方的塊"
+               :command.editor/select-all-blocks       "選擇所有塊"
+               :command.ui/toggle-help                 "顯示/關閉幫助"
+               :command.git/commit                     "提交消息"
+               :command.go/search                      "全文搜索"
+               :command.ui/toggle-document-mode        "切換文檔模式"
+               :command.ui/toggle-theme                "“在暗色/亮色主題之間切換”"
+               :command.ui/toggle-right-sidebar        "啟用/關閉右側欄"
+               :command.go/journals                    "跳轉到日記"}
+
+     :de      {:shortcut.category/formatting           "Formatierung"
+               :command.editor/indent                  "Block einrücken"
+               :command.editor/outdent                 "Block ausrücken"
+               :command.editor/move-block-up           "Block nach oben verschieben"
+               :command.editor/move-block-down         "Block nach unten verschieben"
+               :command.editor/new-block               "Neuen Block erstellen"
+               :command.editor/new-line                "Neue Zeile innerhalb des Blocks erstellen"
+               :command.editor/zoom-in                 "Heranzoomen"
+               :command.editor/zoom-out                "Herauszoomen"
+               :command.editor/follow-link             "Link unter dem Cursor folgen"
+               :command.editor/open-link-in-sidebar    "Link in Seitenleiste öffnen"
+               :command.editor/expand-block-children   "Erweitern"
+               :command.editor/collapse-block-children "Zusammenklappen"
+               :command.editor/select-block-up         "Block oberhalb auswählen"
+               :command.ui/toggle-help                 "Hilfe aktivieren"
+               :command.go/search                      "Volltextsuche"
+               :command.ui/toggle-document-mode        "Dokumentenmodus umschalten"
+               :command.ui/toggle-theme                "Umschalten zwischen dunklem/hellem Thema"
+               :command.ui/toggle-right-sidebar        "Rechte Seitenleiste umschalten"
+               :command.go/journals                    "Zu Journalen springen"
+               :command.git/commit                     "Git Commit-Nachricht"
+               :command.editor/select-block-down       "Block unterhalb auswählen"
+               :command.editor/select-all-blocks       "Alle Blöcke auswählen"}
+
+     :fr      {:shortcut.category/formatting           "Formats"
+               :command.editor/indent                  "Indenter un Bloc vers la droite"
+               :command.editor/outdent                 "Indenter un Bloc vers la gauche"
+               :command.editor/move-block-up           "Déplacer un bloc au dessus"
+               :command.editor/move-block-down         "Déplacer un bloc en dessous"
+               :command.editor/new-block               "Créer un nouveau bloc"
+               :command.editor/new-line                "Aller à la ligne dans un bloc"
+               :command.editor/zoom-in                 "Zoomer"
+               :command.editor/zoom-out                "Dézoomer"
+               :command.editor/follow-link             "Suivre le lien sous le curseur"
+               :command.editor/open-link-in-sidebar    "Ouvrir le lien dans la barre latérale"
+               :command.editor/expand-block-children   "Etendre"
+               :command.editor/collapse-block-children "Réduire"
+               :command.editor/select-block-up         "Sélectionner le bloc au dessus"
+               :command.editor/select-block-down       "Sélectionner le bloc en dessous"
+               :command.editor/select-all-blocks       "Sélectionner tous les blocs"
+               :command.ui/toggle-help                 "Afficher l'aide"
+               :command.git/commit                     "Message de commit Git"
+               :command.go/search                      "Recherche globale dans le texte"
+               :command.ui/toggle-document-mode        "Intervertir le mode document"
+               :command.ui/toggle-theme                "Intervertir le thème foncé/clair"
+               :command.ui/toggle-right-sidebar        "Afficher/cacher la barre latérale"
+               :command.go/journals                    "Aller au Journal"}
+
+     :af      {:shortcut.category/formatting           "Formatering"
+               :command.editor/indent                  "Ingekeepte blok oortjie"
+               :command.editor/outdent                 "Oningekeepte blok"
+               :command.editor/move-block-up           "Skuif Blok Boontoe"
+               :command.editor/move-block-down         "Skuif Blok Ondertoe"
+               :command.editor/new-block               "Skep 'n nuwe blok"
+               :command.editor/new-line                "Nuwe lyn in blok"
+               :command.editor/zoom-in                 "Zoem in"
+               :command.editor/zoom-out                "Zoem uit"
+               :command.editor/follow-link             "Volg die skakel onder die wyser"
+               :command.editor/open-link-in-sidebar    "Maak skakel in kantlys oop"
+               :command.editor/expand-block-children   "Brei uit"
+               :command.editor/collapse-block-children "Vou in"
+               :command.editor/select-block-up         "Kies blok bo"
+               :command.editor/select-block-down       "Kies blok onder"
+               :command.editor/select-all-blocks       "Kies alle blokke"
+               :command.ui/toggle-help                 "Wissel help"
+               :command.git/commit                     "Jou git stoor boodskap"
+               :command.go/search                      "Volteks soek"
+               :command.ui/toggle-document-mode        "Wissel dokument modus"
+               :command.go/journals                    "Spring na joernale"
+               :command.ui/toggle-theme                "Wissel tussen donker/lig temas"
+               :command.ui/toggle-right-sidebar        "Wissel regter sybalk"}
+
+     :es      {:shortcut.category/formatting            "Formato"
+               :shortcut.category/basics                "Básico"
+               :shortcut.category/navigating            "Navegación"
+               :shortcut.category/block-editing         "Edición de bloque general"
+               :shortcut.category/block-command-editing "Comandos edición de bloque"
+               :shortcut.category/block-selection       "Selección de bloques (pulsar Esc para salir)"
+               :shortcut.category/toggle                "Alternar"
+               :shortcut.category/others                "Otros"
+               :command.editor/indent                   "Aumentar sangría"
+               :command.editor/outdent                  "Disminuir sangría"
+               :command.editor/move-block-up            "Mover bloque arriba"
+               :command.editor/move-block-down          "Mover bloque abajo"
+               :command.editor/new-block                "Crear bloque nuevo"
+               :command.editor/new-line                 "Nueva linea en bloque"
+               :command.editor/zoom-in                  "Acercar / Adelante"
+               :command.editor/zoom-out                 "Alejar / Atrás"
+               :command.editor/follow-link              "Seguir enlace bajo el cursor"
+               :command.editor/open-link-in-sidebar     "Abrir enlace en barra lateral"
+               :command.editor/expand-block-children    "Expandir"
+               :command.editor/collapse-block-children  "Colapsar"
+               :command.editor/select-block-up          "Seleccionar bloque de arriba"
+               :command.editor/select-block-down        "Seleccionar bloque de abajo"
+               :command.editor/select-all-blocks        "Seleccionar todos los bloques"
+               :command.ui/toggle-help                  "Alternar ayuda"
+               :command.git/commit                      "Confirmar"
+               :command.go/search                       "Buscar en el grafo"
+               :command.go/search-in-page               "Buscar en la página actual"
+               :command.ui/toggle-document-mode         "Alternar modo documento"
+               :command.ui/toggle-contents              "Alternar Contenido en barra lateral"
+               :command.ui/toggle-theme                 "Alternar entre tema claro/oscuro"
+               :command.ui/toggle-right-sidebar         "Alternar barra lateral"
+               :command.ui/toggle-settings              "Alternar Opciones"
+               :command.go/journals                     "Ir a los diarios"
+               :command.ui/toggle-wide-mode             "Alternar modo ancho"
+               :command.ui/toggle-brackets              "Alternar corchetes"
+               :command.search/re-index                 "Reconstruir índice de búsqueda"
+               :command.editor/bold                     "Negrita"
+               :command.editor/italics                  "Cursiva"
+               :command.editor/insert-link              "Enlace html"
+               :command.editor/highlight                "Resaltado"
+               :command.editor/undo                     "Deshacer"
+               :command.editor/redo                     "Rehacer"
+               :command.editor/copy                     "Copiar"
+               :command.editor/cut                      "Pegar"
+               :command.editor/up                       "Mover cursor arriba / Seleccionar arriba"
+               :command.editor/down                     "Mover cursor abajo / Seleccionar abajo"
+               :command.editor/left                     "Mover cursor a la izquierda / Abrir bloque seleccionado al inicio"
+               :command.editor/right                    "Mover cursor a la derecha / Abrir bloque seleccionado al final"
+               :command.editor/backspace                "Retroceso / Eliminar hacia atrás"
+               :command.editor/delete                   "Suprimir / Eliminar hacia delante"
+               :command.editor/cycle-todo               "Rotar estado TODO del elemento"
+               :command.editor/clear-block              "Borrar contenido del bloque"
+               :command.editor/kill-line-before         "Borrar linea antes del cursor"
+               :command.editor/kill-line-after          "Borrar linea despues del cursor"
+               :command.editor/beginning-of-block       "Mover cursor al inicio del bloque"
+               :command.editor/end-of-block             "Mover cursor al final del bloque"
+               :command.editor/forward-word             "Mover cursor una palabra adelante"
+               :command.editor/backward-word            "Mover cursor una palabra atrás"
+               :command.editor/forward-kill-word        "Borrar palabra posterior"
+               :command.editor/backward-kill-word       "Borrar palabra anterior"
+               :command.editor/open-edit                "Editar bloque seleccionado"
+               :command.editor/delete-selection         "Eliminar bloques seleccionados"
+               :command.editor/toggle-open-blocks       "Alternar bloques abieros, (colapsar o expandir todos)"}
+
+     :ru      {:shortcut.category/formatting            "Форматирование"
+               :shortcut.category/basics                "Базовые"
+               :shortcut.category/navigating            "Навигация"
+               :shortcut.category/block-editing         "Общее редактирование блока"
+               :shortcut.category/block-command-editing "Команды редактирования блока"
+               :shortcut.category/block-selection       "Выделение блоков (нажмите Esc для снятия выделения)"
+               :shortcut.category/toggle                "Переключатели"
+               :shortcut.category/others                "Разное"
+               :command.editor/indent                   "Увеличить отступ"
+               :command.editor/outdent                  "Уменьшить отступ"
+               :command.editor/move-block-up            "Передвинуть блок выше"
+               :command.editor/move-block-down          "Передвинуть блок ниже"
+               :command.editor/new-block                "Создать новый блок"
+               :command.editor/new-line                 "Новая строка в блоке"
+               :command.editor/zoom-in                  "Увеличить / Вперед"
+               :command.editor/zoom-out                 "Уменьшить / Назад"
+               :command.editor/follow-link              "Перейти по ссылке под курсором"
+               :command.editor/open-link-in-sidebar     "Открыть ссылку в боковой панели"
+               :command.editor/expand-block-children    "Раскрыть"
+               :command.editor/collapse-block-children  "Свернуть"
+               :command.editor/select-block-up          "Выбрать блок выше"
+               :command.editor/select-block-down        "Выбрать блок ниже"
+               :command.editor/select-all-blocks        "Выбрать все блоки"
+               :command.ui/toggle-help                  "Переключить помощь"
+               :command.git/commit                      "Подтвердить"
+               :command.go/search                       "Искать на графе"
+               :command.go/search-in-page               "Искать на текущей странице"
+               :command.ui/toggle-document-mode         "Переключить режи документа"
+               :command.ui/toggle-contents              "Переключить контент на боковой панели"
+               :command.ui/toggle-theme                 "Переключение между светлой / темной темой"
+               :command.ui/toggle-right-sidebar         "Переключить боковую панель"
+               :command.ui/toggle-settings              "Переключить параметры"
+               :command.go/journals                     "Перейти в Дневники"
+               :command.ui/toggle-wide-mode             "Переключить широкоформатный режим"
+               :command.ui/toggle-brackets              "Переключить скобки"
+               :command.search/re-index                 "Восстановить индекс поиска"
+               :command.editor/bold                     "Жирный"
+               :command.editor/italics                  "Курсив"
+               :command.editor/insert-link              "HTML ссылка"
+               :command.editor/highlight                "Выделение"
+               :command.editor/undo                     "Отменить"
+               :command.editor/redo                     "Вернуть"
+               :command.editor/copy                     "Копировать"
+               :command.editor/cut                      "Вырезать"
+               :command.editor/up                       "Переместить курсор вверх / Выбрать вверх"
+               :command.editor/down                     "Переместить курсор вниз / Выбрать вниз"
+               :command.editor/left                     "Переместить курсор влево / Открыть выбранный блок в начале"
+               :command.editor/right                    "Переместить курсор вправо / Открыть выбранный блок в конце"
+               :command.editor/backspace                "Удалить перед курсором"
+               :command.editor/delete                   "Удалить после курсора"
+               :command.editor/cycle-todo               "Переключить состояние элемента TODO"
+               :command.editor/clear-block              "Удалить содержимое блока"
+               :command.editor/kill-line-before         "Удалить строку до курсора"
+               :command.editor/kill-line-after          "Удалить строку после курсора"
+               :command.editor/beginning-of-block       "Переместите курсор в начало блока"
+               :command.editor/end-of-block             "Переместите курсор в конец блока"
+               :command.editor/forward-word             "Переместить курсор на одно слово вперед"
+               :command.editor/backward-word            "Переместить курсор на одно слово назад"
+               :command.editor/forward-kill-word        "Удалить следующее слово"
+               :command.editor/backward-kill-word       "Удалить предыдущее слово"
+               :command.editor/open-edit                "Редактировать выбранный блок"
+               :command.editor/delete-selection         "Удалить выбранные блоки"
+               :command.editor/toggle-open-blocks       "Переключить открытые блоки (свернуть или развернуть все)"}
+
+     :nb-NO   {:shortcut.category/formatting            "Formatering"
+               :shortcut.category/basics                "Basis"
+               :shortcut.category/navigating            "Navigasjon"
+               :shortcut.category/block-editing         "Blokkredigering generelt"
+               :shortcut.category/block-command-editing "Blokkredigering kommandoer"
+               :shortcut.category/block-selection       "Blokkseleksjon (trykk ESC for å avslutte)"
+               :shortcut.category/toggle                "Veksling"
+               :shortcut.category/others                "Annet"
+               :command.editor/indent                   "Innrykk inn"
+               :command.editor/outdent                  "Innrykk ut"
+               :command.editor/move-block-up            "Flytt blokk opp"
+               :command.editor/move-block-down          "Flytt blokk ned"
+               :command.editor/new-block                "Opprett ny blokk"
+               :command.editor/new-line                 "Ny linje i nåværende blokk"
+               :command.editor/zoom-in                  "Zoom inn / Fremover"
+               :command.editor/zoom-out                 "Zoom ut / Tilbake"
+               :command.editor/follow-link              "Følg lenke under markøren"
+               :command.editor/open-link-in-sidebar     "Åpne lenke i sidestolpe"
+               :command.editor/expand-block-children    "Utvid"
+               :command.editor/collapse-block-children  "Slå sammen"
+               :command.editor/select-block-up          "Velg blokk over"
+               :command.editor/select-block-down        "Velg blokk under"
+               :command.editor/select-all-blocks        "Velg alle blokker"
+               :command.ui/toggle-help                  "Vis hjelp"
+               :command.git/commit                      "Git commit beskjed"
+               :command.go/search                       "Fulltekst søk"
+               :command.go/search-in-page               "Søk på nåværende side"
+               :command.ui/toggle-document-mode         "Aktiver dokumentmodus"
+               :command.ui/toggle-contents              "Åpne favoritter i sidestolpen"
+               :command.ui/toggle-theme                 "Veksle mellom lyst og mørkt tema"
+               :command.ui/toggle-left-sidebar          "Aktiver venstre sidestolpe"
+               :command.ui/toggle-right-sidebar         "Aktiver høyre sidestolpe"
+               :command.ui/toggle-settings              "Åpne innstillinger"
+               :command.go/journals                     "Gå til dagbok"
+               :command.ui/toggle-wide-mode             "Aktiver vid-modus"
+               :command.ui/toggle-brackets              "Aktiver vising av klammer"
+               :command.search/re-index                 "Gjenoppbygg søkeindeks"
+               :command.editor/bold                     "Fet"
+               :command.editor/italics                  "Kursiv"
+               :command.editor/insert-link              "HTML lenke"
+               :command.editor/highlight                "Markering"
+               :command.editor/undo                     "Angre"
+               :command.editor/redo                     "Gjør om"
+               :command.editor/copy                     "Kopier"
+               :command.editor/cut                      "Klipp ut"
+               :command.editor/up                       "Flytt markøren opp / Velg opp"
+               :command.editor/down                     "Flytt markøren ned / Velg ned"
+               :command.editor/left                     "Flytt markøren til venstre / Åpne valgt blokk på begynnelsen"
+               :command.editor/right                    "Flytt markøren til høyre / Åpne valgt blokk på slutten"
+               :command.editor/backspace                "Backspace / Slett bakover"
+               :command.editor/delete                   "Delete / Slett forover"
+               :command.editor/cycle-todo               "Veksle TODO status for valg linje"
+               :command.editor/clear-block              "Slett alt innhold i blokken"
+               :command.editor/kill-line-before         "Slett linje foran markøren"
+               :command.editor/kill-line-after          "Slett linsje etter markøren"
+               :command.editor/beginning-of-block       "Flytt markøren til begynnelsen av blokken"
+               :command.editor/end-of-block             "Flytt markøren til slutten av blokken"
+               :command.editor/forward-word             "Flytt markøren frem ett ord"
+               :command.editor/backward-word            "Flytt markøren bakover ett ord"
+               :command.editor/forward-kill-word        "Slett ett ord forover"
+               :command.editor/backward-kill-word       "Slett ett ord bakover"
+               :command.editor/open-edit                "Rediger valgt blokk"
+               :command.editor/delete-selection         "Slett valgte blokker"
+               :command.editor/toggle-open-blocks       "Veksle åpne blokker (slå sammen eller utvid alle blokker)"}
+
+     :pt-PT   {:shortcut.category/formatting            "Formatação"
+               :shortcut.category/basics                "Básico"
+               :shortcut.category/navigating            "Navegação"
+               :shortcut.category/block-editing         "Edição geral de blocos"
+               :shortcut.category/block-command-editing "Comandos de edição de blocos"
+               :shortcut.category/block-selection       "Seleção de blocos (premir Esc para sair)"
+               :shortcut.category/toggle                "Alternar"
+               :shortcut.category/others                "Outros"
+               :command.editor/indent                   "Aumentar avanço de parágrafo"
+               :command.editor/outdent                  "Diminuir avanço de parágrafo"
+               :command.editor/move-block-up            "Mover bloco para cima"
+               :command.editor/move-block-down          "Mover bloco para baixo"
+               :command.editor/new-block                "Criar novo bloco"
+               :command.editor/new-line                 "Nova linha no bloco actual"
+               :command.editor/zoom-in                  "Aproximar / Para a frente"
+               :command.editor/zoom-out                 "Afastar / Para trás"
+               :command.editor/follow-link              "Seguir ligação sob o cursor"
+               :command.editor/open-link-in-sidebar     "Abrir ligação na barra lateral"
+               :command.editor/expand-block-children    "Expandir"
+               :command.editor/collapse-block-children  "Colapsar"
+               :command.editor/select-block-up          "Selecionar bloco acima"
+               :command.editor/select-block-down        "Selecionar bloco abaixo"
+               :command.editor/select-all-blocks        "Selecionar todos os blocos"
+               :command.ui/toggle-help                  "Alternar ajuda"
+               :command.git/commit                      "Confirmar"
+               :command.go/search                       "Pesquisar no grafo"
+               :command.go/search-in-page               "Pesquisar na página atual"
+               :command.ui/toggle-document-mode         "Alternar modo de documento"
+               :command.ui/toggle-contents              "Alternar Conteúdo na barra lateral"
+               :command.ui/toggle-theme                 "Alternar entre tema claro/escuro"
+               :command.ui/toggle-right-sidebar         "Alternar barra lateral"
+               :command.ui/toggle-settings              "Alternar Opções"
+               :command.go/journals                     "Ir para diários"
+               :command.ui/toggle-wide-mode             "Alternar modo de ecrã amplo"
+               :command.ui/toggle-brackets              "Alternar parênteses rectos"
+               :command.search/re-index                 "Reconstruir índice de pesquisa"
+               :command.editor/bold                     "Negrito"
+               :command.editor/italics                  "Itálico"
+               :command.editor/insert-link              "Inserir ligação html"
+               :command.editor/highlight                "Realçado"
+               :command.editor/undo                     "Desfazer"
+               :command.editor/redo                     "Refazer"
+               :command.editor/copy                     "Copiar"
+               :command.editor/cut                      "Cortar"
+               :command.editor/up                       "Mover cursor para cima / Selecionar para cima"
+               :command.editor/down                     "Mover cursor para baixo / Selecionar para baixo"
+               :command.editor/left                     "Mover cursor para a esquerda / Abrir bloco selecionado no início"
+               :command.editor/right                    "Mover cursor para a direita / Abrir bloco selecionado no final"
+               :command.editor/backspace                "Retroceder / Eliminar para atrás"
+               :command.editor/delete                   "Apagar / Eliminar para a frente"
+               :command.editor/cycle-todo               "Alternar estado TODO do elemento"
+               :command.editor/clear-block              "Apagar conteúdo do bloco"
+               :command.editor/kill-line-before         "Apagar linha antes do cursor"
+               :command.editor/kill-line-after          "Apagar linha depois do cursor"
+               :command.editor/beginning-of-block       "Mover o cursor para o início do bloco"
+               :command.editor/end-of-block             "Mover o cursor para o fim do bloco"
+               :command.editor/forward-word             "Mover o cursor para a próxima palavra"
+               :command.editor/backward-word            "Mover o cursor para a palavra anterior"
+               :command.editor/forward-kill-word        "Apagar a próxima palavra"
+               :command.editor/backward-kill-word       "Apagar a palavra anterior"
+               :command.editor/open-edit                "Editar bloco selecionado"
+               :command.editor/delete-selection         "Eliminar blocos selecionados"
+               :command.editor/toggle-open-blocks       "Alternar blocos abertos (colapsar ou expandir todos)"}
+
+     :pt-BR   {:shortcut.category/formatting            "Formatação"
+               :shortcut.category/basics                "Básico"
+               :shortcut.category/navigating            "Navegação"
+               :shortcut.category/block-editing         "Edição geral de blocos"
+               :shortcut.category/block-command-editing "Comandos de edição de blocos"
+               :shortcut.category/block-selection       "Seleção de blocos (aperte Esc para sair)"
+               :shortcut.category/toggle                "Alternar"
+               :shortcut.category/others                "Outros"
+               :command.editor/indent                   "Aumentar avanço de parágrafo"
+               :command.editor/outdent                  "Diminuir avanço de parágrafo"
+               :command.editor/move-block-up            "Mover bloco para cima"
+               :command.editor/move-block-down          "Mover bloco para baixo"
+               :command.editor/new-block                "Criar novo bloco"
+               :command.editor/new-line                 "Nova linha no bloco actual"
+               :command.editor/zoom-in                  "Aproximar / Para a frente"
+               :command.editor/zoom-out                 "Afastar / Para trás"
+               :command.editor/follow-link              "Seguir ligação sob o cursor"
+               :command.editor/open-link-in-sidebar     "Abrir ligação na barra lateral"
+               :command.editor/expand-block-children    "Expandir"
+               :command.editor/collapse-block-children  "Recolher"
+               :command.editor/select-block-up          "Selecionar bloco acima"
+               :command.editor/select-block-down        "Selecionar bloco abaixo"
+               :command.editor/select-all-blocks        "Selecionar todos os blocos"
+               :command.ui/toggle-help                  "Alternar ajuda"
+               :command.git/commit                      "Confirmar"
+               :command.go/search                       "Pesquisar no grafo"
+               :command.go/search-in-page               "Pesquisar na página atual"
+               :command.ui/toggle-document-mode         "Alternar modo de documento"
+               :command.ui/toggle-contents              "Alternar Conteúdo na barra lateral"
+               :command.ui/toggle-theme                 "Alternar entre tema claro/escuro"
+               :command.ui/toggle-right-sidebar         "Alternar barra lateral"
+               :command.ui/toggle-settings              "Alternar Opções"
+               :command.go/journals                     "Ir para diários"
+               :command.ui/toggle-wide-mode             "Alternar largura extendida"
+               :command.ui/toggle-brackets              "Alternar colchetes"
+               :command.search/re-index                 "Reconstruir índice de pesquisa"
+               :command.editor/bold                     "Negrito"
+               :command.editor/italics                  "Itálico"
+               :command.editor/insert-link              "Inserir vínculo"
+               :command.editor/highlight                "Realçado"
+               :command.editor/undo                     "Desfazer"
+               :command.editor/redo                     "Refazer"
+               :command.editor/copy                     "Copiar"
+               :command.editor/cut                      "Cortar"
+               :command.editor/up                       "Mover cursor para cima / Selecionar para cima"
+               :command.editor/down                     "Mover cursor para baixo / Selecionar para baixo"
+               :command.editor/left                     "Mover cursor para a esquerda / Abrir bloco selecionado no início"
+               :command.editor/right                    "Mover cursor para a direita / Abrir bloco selecionado no final"
+               :command.editor/backspace                "Retroceder / Eliminar para trás"
+               :command.editor/delete                   "Deletar / Eliminar para frente"
+               :command.editor/cycle-todo               "Alternar estado A FAZER do elemento"
+               :command.editor/clear-block              "Apagar conteúdo do bloco"
+               :command.editor/kill-line-before         "Apagar linha antes do cursor"
+               :command.editor/kill-line-after          "Apagar linha depois do cursor"
+               :command.editor/beginning-of-block       "Mover o cursor para o início do bloco"
+               :command.editor/end-of-block             "Mover o cursor para o fim do bloco"
+               :command.editor/forward-word             "Mover o cursor para a próxima palavra"
+               :command.editor/backward-word            "Mover o cursor para a palavra anterior"
+               :command.editor/forward-kill-word        "Apagar a próxima palavra"
+               :command.editor/backward-kill-word       "Apagar a palavra anterior"
+               :command.editor/open-edit                "Editar bloco selecionado"
+               :command.editor/delete-selection         "Eliminar blocos selecionados"
+               :command.editor/toggle-open-blocks       "Alternar blocos abertos (recolher ou expandir todos)"}}))

+ 8 - 10
src/main/frontend/page.cljs

@@ -3,8 +3,7 @@
             [frontend.state :as state]
             [frontend.ui :as ui]
             [frontend.components.sidebar :as sidebar]
-            [frontend.handler.plugin :as plugin-handler]
-            [frontend.context.i18n :as i18n]))
+            [frontend.handler.plugin :as plugin-handler]))
 
 (rum/defc route-view
   [view route-match]
@@ -32,14 +31,13 @@
                      (teardown)))}
   []
   (when-let [route-match (state/sub :route-match)]
-    (i18n/tongue-provider
-     (let [route-name (get-in route-match [:data :name])]
-       (when-let [view (:view (:data route-match))]
-         (if (= :draw route-name)
-           (view route-match)
-           (sidebar/sidebar
-            route-match
-            (view route-match))))))))
+    (let [route-name (get-in route-match [:data :name])]
+      (when-let [view (:view (:data route-match))]
+        (if (= :draw route-name)
+          (view route-match)
+          (sidebar/sidebar
+           route-match
+           (view route-match)))))))
 
         ;; FIXME: disable for now
         ;; (let [route-name (get-in route-match [:data :name])

+ 3 - 15
src/main/frontend/search/db.cljs

@@ -1,33 +1,21 @@
 (ns frontend.search.db
-  (:refer-clojure :exclude [empty?])
   (:require [cljs-bean.core :as bean]
             [clojure.string :as string]
             [frontend.db :as db]
             [frontend.state :as state]
-            [frontend.text :as text]
             [frontend.util :as util]
-            [frontend.util.drawer :as drawer]
-            [frontend.util.property :as property]
             ["fuse.js" :as fuse]))
 
 (defonce indices (atom nil))
 
-(defn block->content
-  "Convert a block to the display contents for searching"
-  [{:block/keys [content format]}]
-  (->> (text/remove-level-spaces content format)
-       (drawer/remove-logbook)
-       (property/remove-built-in-properties format)))
-
 (defn block->index
   "Convert a block to the index for searching"
-  [{:block/keys [uuid page] :as block}]
-  (when-let [result (->> (block->content block)
-                         (util/search-normalize))]
+  [{:block/keys [uuid page content] :as block}]
+  (when-let [content (util/search-normalize content)]
     {:id (:db/id block)
      :uuid (str uuid)
      :page page
-     :content result}))
+     :content content}))
 
 (defn build-blocks-indice
   ;; TODO: Remove repo effects fns further up the call stack. db fns need standardization on taking connection

+ 4 - 3
src/main/frontend/state.cljs

@@ -31,8 +31,9 @@
      :notification/show?                    false
      :notification/content                  nil
      :repo/cloning?                         false
+     ;; :repo/loading-files? is only for github repos
      :repo/loading-files?                   {}
-     :repo/importing-to-db?                 nil
+     :repo/parsing-files?                   nil
      :repo/changed-files                    nil
      :nfs/user-granted?                     {}
      :nfs/refreshing?                       nil
@@ -1282,9 +1283,9 @@
   [repo]
   (get-in @state [:repo/loading-files? repo]))
 
-(defn set-importing-to-db!
+(defn set-parsing-files!
   [value]
-  (set-state! :repo/importing-to-db? value))
+  (set-state! :repo/parsing-files? value))
 
 (defn set-editor-last-input-time!
   [repo time]

+ 1 - 9
src/main/frontend/text.cljs

@@ -231,14 +231,6 @@
   [img-formats s]
   (some (fn [fmt] (util/safe-re-find (re-pattern (str "(?i)\\." fmt "(?:\\?([^#]*))?(?:#(.*))?$")) s)) img-formats))
 
-(defn scheduled-deadline-dash->star
-  [content]
-  (-> content
-      (string/replace "- TODO -> DONE [" "* TODO -> DONE [")
-      (string/replace "- DOING -> DONE [" "* DOING -> DONE [")
-      (string/replace "- LATER -> DONE [" "* LATER -> DONE [")
-      (string/replace "- NOW -> DONE [" "* NOW -> DONE [")))
-
 (defn remove-indentation-spaces
   [s level remove-first-line?]
   (let [lines (string/split-lines s)
@@ -379,7 +371,7 @@
       (contains? @non-parsing-properties (string/lower-case k))
       v
 
-      (link/link? v)
+      (link/link v)
       v
 
       :else

+ 51 - 53
src/main/frontend/ui.cljs

@@ -1,7 +1,7 @@
 (ns frontend.ui
   (:require [clojure.string :as string]
             [frontend.components.svg :as svg]
-            [frontend.context.i18n :as i18n]
+            [frontend.context.i18n :refer [t]]
             [frontend.handler.notification :as notification-handler]
             [frontend.mixins :as mixins]
             [frontend.modules.shortcut.core :as shortcut]
@@ -156,7 +156,7 @@
      (merge
       {:type  "button"
        :class (str (util/hiccup->class klass) " " class)}
-      (dissoc option :background :class :small?)
+      (dissoc option :background :class :small? :large?)
       (when href
         {:on-click (fn []
                      (util/open-url href)
@@ -392,15 +392,14 @@
   "Render an infinite list."
   [state _list-element-id body {:keys [on-load has-more more more-class]
                                :or {more-class "text-sm"}}]
-  (rum/with-context [[t] i18n/*tongue-context*]
-    [:div
-     body
-     (when has-more
-       [:div.w-full.p-4
-        [:a.fade-link.text-link.font-bold
-         {:on-click on-load
-          :class more-class}
-         (or more (t :page/earlier))]])]))
+  [:div
+   body
+   (when has-more
+     [:div.w-full.p-4
+      [:a.fade-link.text-link.font-bold
+       {:on-click on-load
+        :class more-class}
+       (or more (t :page/earlier))]])])
 
 (rum/defcs auto-complete <
   (rum/local 0 ::current-idx)
@@ -568,48 +567,47 @@
   [{:keys [tag title sub-title sub-checkbox? on-cancel on-confirm]
     :or {on-cancel #()}}]
   (fn [close-fn]
-    (rum/with-context [[t] i18n/*tongue-context*]
-      (let [*sub-checkbox-selected (and sub-checkbox? (atom []))]
-        [:div.ui__confirm-modal
-         {:class (str "is-" tag)}
-         [:div.sm:flex.sm:items-start
-          [:div.mx-auto.flex-shrink-0.flex.items-center.justify-center.h-12.w-12.rounded-full.bg-red-100.sm:mx-0.sm:h-10.sm:w-10
-           [:svg.h-6.w-6.text-red-600
-            {:stroke "currentColor", :view-box "0 0 24 24", :fill "none"}
-            [:path
-             {:d
-              "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
-              :stroke-width    "2"
-              :stroke-linejoin "round"
-              :stroke-linecap  "round"}]]]
-          [:div.mt-3.text-center.sm:mt-0.sm:ml-4.sm:text-left
-           [:h2.headline.text-lg.leading-6.font-medium
-            (if (keyword? title) (t title) title)]
-           [:label.sublabel
-            (when sub-checkbox?
-              (checkbox
-               {:default-value false
-                :on-change     (fn [e]
-                                 (let [checked (.. e -target -checked)]
-                                   (reset! *sub-checkbox-selected [checked])))}))
-            [:h3.subline.text-gray-400
-             (if (keyword? sub-title)
-               (t sub-title)
-               sub-title)]]]]
-
-         [: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 #(and (fn? on-confirm)
-                             (on-confirm % {:close-fn close-fn
-                                            :sub-selected (and *sub-checkbox-selected @*sub-checkbox-selected)}))}
-            (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 (comp on-cancel close-fn)}
-            (t :cancel)]]]]))))
+    (let [*sub-checkbox-selected (and sub-checkbox? (atom []))]
+      [:div.ui__confirm-modal
+       {:class (str "is-" tag)}
+       [:div.sm:flex.sm:items-start
+        [:div.mx-auto.flex-shrink-0.flex.items-center.justify-center.h-12.w-12.rounded-full.bg-red-100.sm:mx-0.sm:h-10.sm:w-10
+         [:svg.h-6.w-6.text-red-600
+          {:stroke "currentColor", :view-box "0 0 24 24", :fill "none"}
+          [:path
+           {:d
+            "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
+            :stroke-width    "2"
+            :stroke-linejoin "round"
+            :stroke-linecap  "round"}]]]
+        [:div.mt-3.text-center.sm:mt-0.sm:ml-4.sm:text-left
+         [:h2.headline.text-lg.leading-6.font-medium
+          (if (keyword? title) (t title) title)]
+         [:label.sublabel
+          (when sub-checkbox?
+            (checkbox
+             {:default-value false
+              :on-change     (fn [e]
+                               (let [checked (.. e -target -checked)]
+                                 (reset! *sub-checkbox-selected [checked])))}))
+          [:h3.subline.text-gray-400
+           (if (keyword? sub-title)
+             (t sub-title)
+             sub-title)]]]]
+
+       [: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 #(and (fn? on-confirm)
+                           (on-confirm % {:close-fn close-fn
+                                          :sub-selected (and *sub-checkbox-selected @*sub-checkbox-selected)}))}
+          (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 (comp on-cancel close-fn)}
+          (t :cancel)]]]])))
 
 (rum/defc sub-modal < rum/reactive
   []

+ 0 - 78
src/main/frontend/util/pool.cljs

@@ -1,78 +0,0 @@
-(ns frontend.util.pool
-  (:require [electron.ipc :as ipc]
-            [frontend.config :as config]
-            [frontend.util :as util]
-            [promesa.core :as p]
-            [clojure.string :as string]
-            ["threads" :refer [Pool Worker spawn]]
-            [frontend.mobile.util :as mobile-util]))
-
-(defonce parser-pool (atom nil))
-
-(defn- absolute-path-for-worker
-  "Returns the absolute path to the worker file, on Windows.
-
-   NOTE: This is a bug in threads.js.
-   See-also: https://github.com/andywer/threads.js/blob/8f94053f028b0d4e4fb1fdec535867f6d0e23946/src/master/implementation.browser.ts#L10"
-  [path]
-  (if util/win32?
-    (-> path
-        (p/then #(str "//./" (string/replace % "\\" "/"))))
-    path))
-
-(defn create-parser-pool!
-  ([]
-   (create-parser-pool! 8))
-  ([num]
-   (p/let [static-path (if (and (util/electron?)
-                                (= "file:" (.-protocol js/location)))
-                         (absolute-path-for-worker (ipc/ipc :getDirname))
-                         "/static")
-           path (str static-path "/js/parser-worker.js")
-           path (if (or (util/electron?)
-                        (mobile-util/is-native-platform?))
-                  path
-                  (config/asset-uri path))]
-     (Pool.
-      (fn []
-        (spawn (Worker. path) num))))))
-
-;; (defn finish-pool!
-;;   [{:keys [pool tasks]} ok-handler]
-;;   (-> (p/all @tasks)
-;;       (p/then (fn [result]
-;;                 (ok-handler result)
-;;                 (.completed pool)
-;;                 (.terminate pool)
-;;                 (reset! tasks nil)))))
-
-(defn terminate-pool!
-  [^js pool]
-  (p/let [_ (.completed pool)]
-    (.terminate pool)))
-
-(defn add-parse-job!
-  [content config]
-  (when-let [pool @parser-pool]
-    (.queue ^js pool
-            (fn [parser]
-              (try
-                (parser.parse content config)
-                (catch js/Error e
-                  (js/console.error e)
-                  nil)))))
-  ;; (let [task (.queue ^js pool
-  ;;                    (fn [parser]
-  ;;                      (parser.parse content config)))]
-  ;;   (swap! (:tasks m) conj task)
-  ;;   task)
-  )
-
-(defn init-parser-pool!
-  []
-  (p/let [pool (create-parser-pool!)]
-    (reset! parser-pool pool)))
-
-(comment
-  (add-parse-job! "- hello" (frontend.format.mldoc/default-config :markdown))
-  (add-parse-job! "*world*" (frontend.format.mldoc/default-config :markdown)))

+ 3 - 2
src/main/frontend/util/property.cljs

@@ -36,8 +36,9 @@
 
 (defn contains-properties?
   [content]
-  (and (string/includes? content properties-start)
-       (util/safe-re-find properties-end-pattern content)))
+  (when content
+    (and (string/includes? content properties-start)
+         (util/safe-re-find properties-end-pattern content))))
 
 (defn remove-empty-properties
   [content]

+ 0 - 14
src/main/frontend/worker/parser.cljs

@@ -1,14 +0,0 @@
-(ns frontend.worker.parser
-  (:require ["mldoc" :refer [Mldoc]]
-            ["threads/worker" :refer [expose]]))
-
-(def parse-json (.-parseJson Mldoc))
-
-(expose (clj->js {:parse parse-json}))
-
-(defn init
-  []
-  (println "Parser worker initialized!")
-  (js/self.addEventListener "message"
-                            (fn [^js e]
-                              (js/postMessage (.. e -data)))))

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

@@ -507,7 +507,7 @@
                         ;; attached shallow children
                         (assoc block :block/children
                           (map #(list :uuid (get-in % [:data :block/uuid]))
-                            (outliner/get-children uuid))))]
+                            (db/get-block-immediate-children repo uuid))))]
             (bean/->js (normalize-keyword-for-json block))))))))
 
 (def ^:export get_current_block

+ 98 - 83
src/test/frontend/db/query_dsl_test.cljs

@@ -17,13 +17,16 @@
                 :file/content "---
 title: Dec 26th, 2020
 tags: [[page-tag-1]], page-tag-2
-parent: [[child page 1]]
+parent: [[child page 1]], [[child-no-space]]
 ---
 - DONE 26-b1 [[page 1]]
 created-at:: 1608968448113
 last-modified-at:: 1608968448113
 prop-a:: val-a
 prop-c:: [[page a]], [[page b]], [[page c]]
+prop-num:: 2000
+prop-linked-num:: [[3000]]
+prop-d:: [[no-space-link]]
 - LATER 26-b2-modified-later [[page 2]] #tag1
 created-at:: 1608968448114
 last-modified-at:: 1608968448120
@@ -94,6 +97,97 @@ last-modified-at:: 1609084800002"}]]
 
 (defonce empty-result {:query nil :result nil})
 
+(deftest block-property-queries
+  (are [x y] (= (q-count x) y)
+       "(property prop-a val-a)"
+       {:query (dsl/->property-query "prop-a" "val-a")
+        :count 2}
+
+       "(property prop-b val-b)"
+       {:query (dsl/->property-query "prop-b" "val-b")
+        :count 1}
+
+       "(and (property prop-b val-b))"
+       {:query '([?b :block/properties ?prop]
+                 [(missing? $ ?b :block/name)]
+                 [(get ?prop :prop-b) ?v]
+                 (or [(= ?v "val-b")] [(contains? ?v "val-b")] [(contains? ?v "val-b")]))
+        :count 1}
+
+       "(and (property prop-c \"page c\"))"
+       {:query (dsl/->property-query "prop-c" "page c")
+        :count 1}
+
+       ;; TODO: optimize
+       "(and (property prop-c \"page c\") (property prop-c \"page b\"))"
+       {:query '[[?b :block/properties ?prop]
+                 [(missing? $ ?b :block/name)]
+                 [(get ?prop :prop-c) ?v]
+                 (or [(= ?v "page c")] [(contains? ?v "page c")] [(contains? ?v "page c")])
+                 [(get ?prop :prop-c) ?v1]
+                 (or [(= ?v1 "page b")] [(contains? ?v1 "page b")] [(contains? ?v1 "page b")])]
+        :count 1}
+
+       "(or (property prop-c \"page c\") (property prop-b val-b))"
+       {:query '[[?b :block/content ?content]
+                 (or
+                  (and [?b :block/properties ?prop] [(missing? $ ?b :block/name)] [(get ?prop :prop-c) ?v] (or [(= ?v "page c")] [(contains? ?v "page c")] [(contains? ?v "page c")]))
+                  (and [?b :block/properties ?prop] [(missing? $ ?b :block/name)] [(get ?prop :prop-b) ?v] (or [(= ?v "val-b")] [(contains? ?v "val-b")] [(contains? ?v "val-b")])))]
+        :count 2}
+
+       "(property prop-num 2000)"
+       {:query (dsl/->property-query "prop-num" 2000)
+        :count 1}
+
+       "(property prop-linked-num 3000)"
+       {:query (dsl/->property-query "prop-linked-num" 3000)
+        :count 1}
+
+       "(property prop-d no-space-link)"
+       {:query (dsl/->property-query "prop-d" "no-space-link")
+        :count 1}))
+
+(deftest page-property-queries
+  (are [x y] (= (q-count x) y)
+       "(page-property parent)"
+       {:query '[[?p :block/name]
+                 [?p :block/properties ?prop]
+                 [(get ?prop :parent) ?prop-v]
+                 [true]], :count 3}
+
+       "(page-property parent [[child page 1]])"
+       {:query (dsl/->page-property-query "parent" "child page 1")
+        :count 2}
+
+       "(page-property parent [[child-no-space]])"
+       {:query (dsl/->page-property-query "parent" "child-no-space")
+        :count 1}
+
+       "(page-property parent \"child page 1\")"
+       {:query (dsl/->page-property-query "parent" "child page 1")
+        :count 2}
+
+       "(and (page-property parent [[child page 1]]) (page-property parent [[child page 2]]))"
+       {:query '([?p :block/name]
+                 [?p :block/properties ?prop]
+                 [(get ?prop :parent) ?v]
+                 (or [(= ?v "child page 1")] [(contains? ?v "child page 1")])
+                 (or [(= ?v "child page 2")] [(contains? ?v "child page 2")]))
+        :count 1}
+
+       "(or (page-property parent [[child page 1]]) (page-property parent [[child page 2]]))"
+       {:query '(or (and
+                     [?p :block/name]
+                     [?p :block/properties ?prop]
+                     [(get ?prop :parent) ?v]
+                     (or [(= ?v "child page 1")] [(contains? ?v "child page 1")]))
+                    (and
+                     [?p :block/name]
+                     [?p :block/properties ?prop]
+                     [(get ?prop :parent) ?v]
+                     (or [(= ?v "child page 2")] [(contains? ?v "child page 2")])))
+        :count 3}))
+
 (deftest test-parse
   []
   (testing "nil or blank strings should be ignored"
@@ -117,43 +211,7 @@ last-modified-at:: 1609084800002"}]]
       {:query '[[?b :block/path-refs [:block/name "page 2"]]]
        :count 4}))
 
-  (testing "Block properties query"
-    (are [x y] (= (q-count x) y)
-      "(property prop-a val-a)"
-      {:query '[[?b :block/properties ?prop] [(missing? $ ?b :block/name)] [(get ?prop :prop-a) ?v] (or [(= ?v "val-a")] [(contains? ?v "val-a")])]
-       :count 2}
-
-      "(property prop-b val-b)"
-      {:query '[[?b :block/properties ?prop] [(missing? $ ?b :block/name)] [(get ?prop :prop-b) ?v] (or [(= ?v "val-b")] [(contains? ?v "val-b")])]
-       :count 1}
-
-      "(and (property prop-b val-b))"
-      {:query '([?b :block/properties ?prop]
-                [(missing? $ ?b :block/name)]
-                [(get ?prop :prop-b) ?v]
-                (or [(= ?v "val-b")] [(contains? ?v "val-b")]))
-       :count 1}
-
-      "(and (property prop-c \"page c\"))"
-      {:query '[[?b :block/properties ?prop] [(missing? $ ?b :block/name)] [(get ?prop :prop-c) ?v] (or [(= ?v "page c")] [(contains? ?v "page c")])]
-       :count 1}
-
-      ;; TODO: optimize
-      "(and (property prop-c \"page c\") (property prop-c \"page b\"))"
-      {:query '[[?b :block/properties ?prop]
-                [(missing? $ ?b :block/name)]
-                [(get ?prop :prop-c) ?v]
-                (or [(= ?v "page c")] [(contains? ?v "page c")])
-                [(get ?prop :prop-c) ?v1]
-                (or [(= ?v1 "page b")] [(contains? ?v1 "page b")])]
-       :count 1}
 
-      "(or (property prop-c \"page c\") (property prop-b val-b))"
-      {:query '[[?b :block/content ?content]
-                (or
-                 (and [?b :block/properties ?prop] [(missing? $ ?b :block/name)] [(get ?prop :prop-c) ?v] (or [(= ?v "page c")] [(contains? ?v "page c")]))
-                 (and [?b :block/properties ?prop] [(missing? $ ?b :block/name)] [(get ?prop :prop-b) ?v] (or [(= ?v "val-b")] [(contains? ?v "val-b")])))]
-       :count 2}))
 
   (testing "task queries"
     (are [x y] (= (q-count x) y)
@@ -247,50 +305,7 @@ last-modified-at:: 1609084800002"}]]
                 [(contains? #{"page-tag-1" "page-tag-2"} ?tag1)]]
        :count 2}))
 
-  (testing "page-property queries"
-    (are [x y] (= (q-count x) y)
-      "(page-property parent)"
-      {:query '[[?p :block/name]
-                [?p :block/properties ?prop]
-                [(get ?prop :parent) ?prop-v]
-                [true]], :count 3}
-
-      "(page-property parent [[child page 1]])"
-      {:query '[[?p :block/name]
-                [?p :block/properties ?prop]
-                [(get ?prop :parent) ?v]
-                (or [(= ?v "child page 1")] [(contains? ?v "child page 1")])]
-       :count 2}
 
-      "(page-property parent \"child page 1\")"
-      {:query '[[?p :block/name]
-                [?p :block/properties ?prop]
-                [(get ?prop :parent) ?v]
-                (or
-                 [(= ?v "child page 1")]
-                 [(contains? ?v "child page 1")])]
-       :count 2}
-
-      "(and (page-property parent [[child page 1]]) (page-property parent [[child page 2]]))"
-      {:query '([?p :block/name]
-                [?p :block/properties ?prop]
-                [(get ?prop :parent) ?v]
-                (or [(= ?v "child page 1")] [(contains? ?v "child page 1")])
-                (or [(= ?v "child page 2")] [(contains? ?v "child page 2")]))
-       :count 1}
-
-      "(or (page-property parent [[child page 1]]) (page-property parent [[child page 2]]))"
-      {:query '(or (and
-                    [?p :block/name]
-                    [?p :block/properties ?prop]
-                    [(get ?prop :parent) ?v]
-                    (or [(= ?v "child page 1")] [(contains? ?v "child page 1")]))
-                   (and
-                    [?p :block/name]
-                    [?p :block/properties ?prop]
-                    [(get ?prop :parent) ?v]
-                    (or [(= ?v "child page 2")] [(contains? ?v "child page 2")])))
-       :count 3}))
 
   ;; boolean queries
   (testing "AND queries"
@@ -319,7 +334,7 @@ last-modified-at:: 1609084800002"}]]
       "(not [[page 1]])"
       {:query '([?b :block/uuid]
                 (not [?b :block/path-refs [:block/name "page 1"]]))
-       :count 36}))
+       :count 39}))
 
   (testing "Between query"
     (are [x y] (= (count-only x) y)
@@ -367,7 +382,7 @@ last-modified-at:: 1609084800002"}]]
                   (and [?b :block/path-refs [:block/name "page 1"]])
                   (and [?b :block/path-refs [:block/name "page 2"]])
                   [?b])))
-       :count 39})
+       :count 42})
 
     ;; FIXME: not working
     ;; (are [x y] (= (q-count x) y)
@@ -461,7 +476,7 @@ last-modified-at:: 1609084800002"}]]
                       (done))))
    :after config/destroy-test-db!})
 
-#_(run-tests)
+#_(cljs.test/run-tests)
 
 (comment
   (require '[clojure.pprint :as pprint])

+ 1 - 1
src/test/frontend/format/block_test.cljs

@@ -32,4 +32,4 @@
     [["foo" "#bar, #baz"]] ["bar" "baz"]
     [["foo" "[[nested [[page]]]], test"]] ["nested [[page]]" "test"]))
 
-#_(run-tests)
+#_(cljs.test/run-tests)

+ 24 - 0
src/test/frontend/fs/capacitor_fs_test.cljs

@@ -0,0 +1,24 @@
+(ns frontend.fs.capacitor-fs-test
+  (:require [frontend.fs.capacitor-fs :as capacitor-fs]
+            [clojure.test :refer [deftest is]]))
+
+(deftest get-file-path
+  (let [dir "file:///private/var/mobile/Library/Mobile%20Documents/iCloud~com~logseq~logseq/Documents/"
+        url-decoded-dir "file:/private/var/mobile/Library/Mobile Documents/iCloud~com~logseq~logseq/Documents/"]
+    (is (= (str url-decoded-dir "pages/pages-metadata.edn")
+           (capacitor-fs/get-file-path
+            dir
+            "file:///private/var/mobile/Library/Mobile Documents/iCloud~com~logseq~logseq/Documents/pages/pages-metadata.edn"))
+        "full path returns as url decoded full path")
+
+    (is (= (str url-decoded-dir "journals/2002_01_28.md")
+           (capacitor-fs/get-file-path
+            dir
+            "/journals/2002_01_28.md"))
+        "relative path returns as url decoded full path")
+
+    (is (= dir
+           (capacitor-fs/get-file-path
+            dir
+            nil))
+        "nil path returns url encoded dir")))

+ 1 - 1
src/test/frontend/handler/extract_test.cljs

@@ -73,4 +73,4 @@
      - line4"
    [[1 "line1"] [2 "line2"] [3 "line3"] [3 "line4"]]))
 
-#_(run-tests)
+#_(cljs.test/run-tests)

+ 10 - 12
src/test/frontend/handler/test_link.cljs → src/test/frontend/handler/link_test.cljs

@@ -1,25 +1,25 @@
-(ns frontend.handler.test-link
+(ns frontend.handler.link-test
   (:require [cljs.test :refer [are deftest testing]]
             [frontend.handler.link :as link]))
 
-(deftest test-link?
+(deftest test-link
   (testing "non-link"
-    (are [x y] (= (link/link? x) y)
+    (are [x y] (= (link/link x) y)
       "google.com" nil
       "[[google.com][google]]" nil
       "[[google.com]]" nil
       "[google](google.com)" nil))
-  
+
   (testing "plain links"
-    (are [x y] (= (link/link? x) y)
+    (are [x y] (= (link/link x) y)
       "http://www.google.com"
       {:type "plain-link" :url "http://www.google.com"}
 
       "http://google.com"
       {:type "plain-link" :url "http://google.com"}))
 
-  (testing "org links"
-    (are [x y] (= (link/link? x) y)
+  (testing "org links with labels"
+    (are [x y] (= (link/link x) y)
       "[[http://www.google.com][google]]"
       {:type "org-link" :url "http://www.google.com" :label "google"}
 
@@ -32,8 +32,8 @@
       "[[https://google.com][google]]"
       {:type "org-link" :url "https://google.com" :label "google"}))
 
-  (testing "org links"
-    (are [x y] (= (link/link? x) y)
+  (testing "org links without labels"
+    (are [x y] (= (link/link x) y)
       "[[http://www.google.com]]"
       {:type "org-link" :url "http://www.google.com" :label nil}
 
@@ -41,11 +41,9 @@
       {:type "org-link" :url "https://www.google.com" :label nil}))
 
   (testing "markdown links"
-    (are [x y] (= (link/link? x) y)
+    (are [x y] (= (link/link x) y)
       "[google](http://www.google.com)"
       {:type "markdown-link" :url "http://www.google.com" :label "google"}
 
       "[google](https://www.google.com)"
       {:type "markdown-link" :url "https://www.google.com" :label "google"})))
-
-#_(cljs.test/test-ns 'frontend.test-link)

+ 2 - 2
src/test/frontend/modules/outliner/core_test.cljs

@@ -330,7 +330,7 @@
         (is (= [19 20] children-of-18))))))
 
 (comment
-  (run-test test-insert-nodes))
+  (cljs.test/run-tests test-insert-nodes))
 
 (comment
-  (run-tests))
+  (cljs.test/run-tests))

+ 1 - 1
src/test/frontend/text_test.cljs

@@ -143,7 +143,7 @@
       :tags "foo" "foo"
       :tags "foo, bar" #{"foo" "bar"}
       :tags "foo,bar" #{"foo" "bar"}
-      :tags "[[foo]]" "[[foo]]"
+      :tags "[[foo]]" #{"foo"}
       :tags "[[foo]] [[bar]]" #{"foo" "bar"}
       :tags "[[foo]], [[bar]]" #{"foo" "bar"}
       :tags "[[foo]], [[bar]], #baz" #{"foo" "bar" "baz"}

+ 104 - 12
yarn.lock

@@ -2054,6 +2054,14 @@ clean-stack@^2.0.0:
   resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
   integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
 
+cli-truncate@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7"
+  integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==
+  dependencies:
+    slice-ansi "^3.0.0"
+    string-width "^4.2.0"
+
 cliui@^3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
@@ -2875,6 +2883,29 @@ earcut@^2.2.2:
   resolved "https://registry.yarnpkg.com/earcut/-/earcut-2.2.3.tgz#d44ced2ff5a18859568e327dd9c7d46b16f55cf4"
   integrity sha512-iRDI1QeCQIhMCZk48DRDMVgQSSBDmbzzNhnxIo+pwx3swkfjMh6vh0nWLq1NdvGHLKH6wIrAM3vQWeTj6qeoug==
 
[email protected]:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/electron-context-menu/-/electron-context-menu-3.1.1.tgz#109884e79df293f7e85effcbdbbe45d362987d94"
+  integrity sha512-LJhwaKf6XHwk2LQ5SdwoGNODoA8lRwks9bbEeAqqMf4e3hsrT7pZtX6MaHKYNFZKxF14JjI/VR+VRjGvxmaQoA==
+  dependencies:
+    cli-truncate "^2.1.0"
+    electron-dl "^3.2.1"
+    electron-is-dev "^2.0.0"
+
+electron-dl@^3.2.1:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/electron-dl/-/electron-dl-3.3.0.tgz#4e422e276c627373ba61fcf3f92ffa088988db1a"
+  integrity sha512-Zwaz/OMGPIfBLV2SQH4sTsdDOs/U4y5AOHfremMBXEpjIxX+SiTx845DZAvJJwgb5hfowyWOBLiJhd/emBNLLQ==
+  dependencies:
+    ext-name "^5.0.0"
+    pupa "^2.0.1"
+    unused-filename "^2.1.0"
+
+electron-is-dev@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-2.0.0.tgz#833487a069b8dad21425c67a19847d9064ab19bd"
+  integrity sha512-3X99K852Yoqu9AcW50qz3ibYBWY79/pBhlMCab8ToEWS48R0T9tyxRiQhwylE7zQdXrMnx2JKqUJyMPmt5FBqA==
+
 electron-to-chromium@^1.4.17:
   version "1.4.38"
   resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.38.tgz#10ea58d73d36b13e78d5024f3b74a352d3958d01"
@@ -3043,6 +3074,11 @@ escalade@^3.1.1:
   resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
   integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
 
+escape-goat@^2.0.0:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675"
+  integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==
+
 escape-string-regexp@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
@@ -3138,6 +3174,21 @@ expect@=27.2.5:
     jest-message-util "^27.2.5"
     jest-regex-util "^27.0.6"
 
+ext-list@^2.0.0:
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37"
+  integrity sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==
+  dependencies:
+    mime-db "^1.28.0"
+
+ext-name@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/ext-name/-/ext-name-5.0.0.tgz#70781981d183ee15d13993c8822045c506c8f0a6"
+  integrity sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==
+  dependencies:
+    ext-list "^2.0.0"
+    sort-keys-length "^1.0.0"
+
 ext@^1.1.2:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/ext/-/ext-1.6.0.tgz#3871d50641e874cc172e2b53f919842d19db4c52"
@@ -4389,7 +4440,7 @@ is-path-inside@^3.0.2:
   resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
   integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
 
-is-plain-obj@^1.1.0:
+is-plain-obj@^1.0.0, is-plain-obj@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
   integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4=
@@ -5110,6 +5161,11 @@ miller-rabin@^4.0.0:
     bn.js "^4.0.0"
     brorand "^1.0.1"
 
+mime-db@^1.28.0:
+  version "1.51.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c"
+  integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==
+
 mime@^2.4.6:
   version "2.6.0"
   resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
@@ -5201,10 +5257,10 @@ mkdirp@^1.0.3:
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
   integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
 
-mldoc@1.2.7:
-  version "1.2.7"
-  resolved "https://registry.yarnpkg.com/mldoc/-/mldoc-1.2.7.tgz#3eb8b07cf33cc975e5306c1e6f81bfd72f74ee5c"
-  integrity sha512-AUB8cCNN+wHT5ktUDfR0Jqt+nw/rNybmkhKP/BEmI/kw8e6jNRHg7HIQSJEv94lrcCbkivEDbluzU6/YtKI6AA==
+mldoc@1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/mldoc/-/mldoc-1.3.0.tgz#e72a157cc1204442ea5e90facd60837343a66338"
+  integrity sha512-qDxtIXzOheFJHq0d/4bDvMkM/VrhbDLMKw9MZurWbNPW6PxZQduC0BhXIGZyP4qT+pecZpUEMbUyMbNGn8ztsw==
   dependencies:
     yargs "^12.0.2"
 
@@ -5213,6 +5269,11 @@ modern-normalize@^1.1.0:
   resolved "https://registry.yarnpkg.com/modern-normalize/-/modern-normalize-1.1.0.tgz#da8e80140d9221426bd4f725c6e11283d34f90b7"
   integrity sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA==
 
+modify-filename@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/modify-filename/-/modify-filename-1.1.0.tgz#9a2dec83806fbb2d975f22beec859ca26b393aa1"
+  integrity sha1-mi3sg4Bvuy2XXyK+7IWcoms5OqE=
+
 [email protected]:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@@ -6601,6 +6662,13 @@ punycode@^2.1.0:
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
   integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
 
+pupa@^2.0.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62"
+  integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==
+  dependencies:
+    escape-goat "^2.0.0"
+
 [email protected]:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-4.0.2.tgz#7281f233c08b4f41e1c5d85c66a2d064e6d7e1b3"
@@ -6702,17 +6770,10 @@ [email protected]:
     react-draggable "3.x"
     react-resizable "1.x"
 
[email protected]:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/react-icon-base/-/react-icon-base-2.1.0.tgz#a196e33fdf1e7aaa1fda3aefbb68bdad9e82a79d"
-  integrity sha1-oZbjP98eeqof2jrvu2i9rZ6Cp50=
-
 [email protected]:
   version "2.2.7"
   resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-2.2.7.tgz#d7860826b258557510dac10680abea5ca23cf650"
   integrity sha512-0n4lcGqzJFcIQLoQytLdJCE0DKSA9dkwEZRYoGrIDJZFvIT6Hbajx5mv9geqhqFiNjUgtxg8kPyDfjlhymbGFg==
-  dependencies:
-    react-icon-base "2.1.0"
 
 react-is@^16.13.1, react-is@^16.3.1:
   version "16.13.1"
@@ -7327,6 +7388,15 @@ slash@^3.0.0:
   resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
   integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
 
+slice-ansi@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787"
+  integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==
+  dependencies:
+    ansi-styles "^4.0.0"
+    astral-regex "^2.0.0"
+    is-fullwidth-code-point "^3.0.0"
+
 slice-ansi@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
@@ -7388,6 +7458,20 @@ socks@^2.6.1:
     ip "^1.1.5"
     smart-buffer "^4.1.0"
 
+sort-keys-length@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/sort-keys-length/-/sort-keys-length-1.0.1.tgz#9cb6f4f4e9e48155a6aa0671edd336ff1479a188"
+  integrity sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg=
+  dependencies:
+    sort-keys "^1.0.0"
+
+sort-keys@^1.0.0:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad"
+  integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0=
+  dependencies:
+    is-plain-obj "^1.0.0"
+
 source-map-js@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf"
@@ -8271,6 +8355,14 @@ untildify@^4.0.0:
   resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b"
   integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==
 
+unused-filename@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/unused-filename/-/unused-filename-2.1.0.tgz#33719c4e8d9644f32d2dec1bc8525c6aaeb4ba51"
+  integrity sha512-BMiNwJbuWmqCpAM1FqxCTD7lXF97AvfQC8Kr/DIeA6VtvhJaMDupZ82+inbjl5yVP44PcxOuCSxye1QMS0wZyg==
+  dependencies:
+    modify-filename "^1.1.0"
+    path-exists "^4.0.0"
+
 upath@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894"