Browse Source

Merge branch 'master' into feat/slash-command-for-code-block

Gabriel Horner 2 năm trước cách đây
mục cha
commit
028db2e7f2
100 tập tin đã thay đổi với 1779 bổ sung863 xóa
  1. 3 0
      .clj-kondo/config.edn
  2. 4 0
      .github/ISSUE_TEMPLATE/bug_report.yaml
  3. 1 1
      .github/workflows/build-desktop-release.yml
  4. 1 0
      CONTRIBUTING.md
  5. 2 2
      android/app/build.gradle
  6. 2 5
      bb.edn
  7. 1 1
      deps.edn
  8. 3 3
      deps/common/bb.edn
  9. 1 1
      deps/common/deps.edn
  10. 2 2
      deps/db/bb.edn
  11. 1 1
      deps/db/deps.edn
  12. 2 2
      deps/graph-parser/bb.edn
  13. 1 1
      deps/graph-parser/deps.edn
  14. 14 1
      deps/graph-parser/src/logseq/graph_parser/mldoc.cljc
  15. 18 0
      deps/graph-parser/test/logseq/graph_parser/mldoc_test.cljs
  16. 2 2
      deps/publishing/bb.edn
  17. 1 1
      deps/publishing/deps.edn
  18. 18 1
      deps/publishing/src/logseq/publishing/db.cljs
  19. 3 1
      deps/publishing/test/logseq/publishing/db_test.cljs
  20. 14 18
      docs/contributing-to-translations.md
  21. 4 1
      docs/dev-practices.md
  22. 3 1
      e2e-tests/code-editing.spec.ts
  23. 2 1
      e2e-tests/fixtures.ts
  24. 1 0
      e2e-tests/history.spec.ts
  25. 2 1
      e2e-tests/hotkey.spec.ts
  26. 13 1
      e2e-tests/page-rename.spec.ts
  27. 0 1
      e2e-tests/util/page.ts
  28. 32 29
      e2e-tests/util/search-modal.ts
  29. 64 2
      e2e-tests/whiteboards.spec.ts
  30. 4 4
      ios/App/App.xcodeproj/project.pbxproj
  31. 13 5
      ios/App/App/FsWatcher.swift
  32. 18 2
      playwright.config.ts
  33. 0 0
      resources/css/katex.min.css
  34. 0 0
      resources/js/katex.min.js
  35. 2 2
      resources/package.json
  36. 44 20
      scripts/src/logseq/tasks/lang.clj
  37. 37 14
      src/electron/electron/plugin.cljs
  38. 3 10
      src/main/electron/listener.cljs
  39. 6 6
      src/main/frontend/components/assets.cljs
  40. 38 86
      src/main/frontend/components/block.cljs
  41. 1 1
      src/main/frontend/components/block.css
  42. 71 0
      src/main/frontend/components/block/macros.cljs
  43. 1 1
      src/main/frontend/components/container.cljs
  44. 2 1
      src/main/frontend/components/container.css
  45. 1 1
      src/main/frontend/components/conversion.cljs
  46. 6 4
      src/main/frontend/components/editor.cljs
  47. 1 1
      src/main/frontend/components/file.cljs
  48. 4 4
      src/main/frontend/components/file_sync.cljs
  49. 7 7
      src/main/frontend/components/header.cljs
  50. 5 5
      src/main/frontend/components/onboarding.cljs
  51. 18 20
      src/main/frontend/components/page.cljs
  52. 100 27
      src/main/frontend/components/plugins.cljs
  53. 65 16
      src/main/frontend/components/plugins.css
  54. 0 1
      src/main/frontend/components/query.cljs
  55. 2 0
      src/main/frontend/components/query/result.cljs
  56. 2 2
      src/main/frontend/components/repo.cljs
  57. 4 4
      src/main/frontend/components/right_sidebar.cljs
  58. 6 6
      src/main/frontend/components/search.cljs
  59. 233 31
      src/main/frontend/components/settings.cljs
  60. 57 40
      src/main/frontend/components/settings.css
  61. 90 44
      src/main/frontend/components/shortcut.cljs
  62. 28 0
      src/main/frontend/components/shortcut.css
  63. 5 1
      src/main/frontend/config.cljs
  64. 17 5
      src/main/frontend/date.cljs
  65. 2 2
      src/main/frontend/db/react.cljs
  66. 2 4
      src/main/frontend/extensions/excalidraw.cljs
  67. 2 3
      src/main/frontend/extensions/latex.cljs
  68. 1 0
      src/main/frontend/extensions/pdf/toolbar.cljs
  69. 4 1
      src/main/frontend/handler/code.cljs
  70. 6 8
      src/main/frontend/handler/editor.cljs
  71. 22 15
      src/main/frontend/handler/events.cljs
  72. 1 10
      src/main/frontend/handler/page.cljs
  73. 76 69
      src/main/frontend/handler/paste.cljs
  74. 15 7
      src/main/frontend/handler/plugin.cljs
  75. 0 49
      src/main/frontend/handler/query.cljs
  76. 10 0
      src/main/frontend/handler/user.cljs
  77. 1 0
      src/main/frontend/handler/whiteboard.cljs
  78. 10 7
      src/main/frontend/modules/outliner/tree.cljs
  79. 16 9
      src/main/frontend/modules/shortcut/config.cljs
  80. 20 17
      src/main/frontend/modules/shortcut/core.cljs
  81. 20 25
      src/main/frontend/modules/shortcut/data_helper.cljs
  82. 2 0
      src/main/frontend/quick_capture.cljs
  83. 1 1
      src/main/frontend/routes.cljs
  84. 7 3
      src/main/frontend/search.cljs
  85. 23 5
      src/main/frontend/state.cljs
  86. 29 6
      src/main/frontend/ui.cljs
  87. 10 3
      src/main/frontend/ui.css
  88. 12 0
      src/main/frontend/util.cljc
  89. 49 0
      src/main/frontend/util/property.cljs
  90. 1 1
      src/main/frontend/version.cljs
  91. 3 3
      src/main/logseq/api.cljs
  92. 1 22
      src/resources/dicts/af.edn
  93. 0 22
      src/resources/dicts/de.edn
  94. 58 16
      src/resources/dicts/en.edn
  95. 130 16
      src/resources/dicts/es.edn
  96. 2 18
      src/resources/dicts/fr.edn
  97. 1 16
      src/resources/dicts/it.edn
  98. 0 13
      src/resources/dicts/ja.edn
  99. 1 14
      src/resources/dicts/ko.edn
  100. 135 27
      src/resources/dicts/nb-no.edn

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

@@ -15,6 +15,8 @@
   :aliased-namespace-symbol {:level :warning}
   ;; Disable until it doesn't trigger false positives on rum/defcontext
   :earmuffed-var-not-dynamic {:level :off}
+  ;; Disable until we decide to use conj! as recommended in docs
+  :unused-value {:level :off}
   :unresolved-symbol {:exclude [goog.DEBUG
                                 goog.string.unescapeEntities
                                 ;; TODO:lint: Fix when fixing all type hints
@@ -39,6 +41,7 @@
              electron.utils utils
              "/electron/utils" js-utils
              frontend.commands commands
+             frontend.components.block.macros block-macros
              frontend.components.query query
              frontend.components.query.result query-result
              frontend.config config

+ 4 - 0
.github/ISSUE_TEMPLATE/bug_report.yaml

@@ -7,6 +7,10 @@ body:
         Thank you very much for opening a bug report with Logseq.
 
         If you have a feature idea or need help, please go to [our Forum](https://discuss.logseq.com/) or [our Discord](https://discord.com/invite/KpN4eHY).
+
+        Please make sure to provide a descriptive, deterministic, and reproducible report. It saves time for both the developers and users who are looking for solutions. Providing as much information as possible, including screenshots and logs, is highly appreciated. This will help us to better understand the issue and respond more effectively.
+
+        Please DO NOT use this template to ask questions. There are other appropriate channels to ask questions. This template is strictly for reporting bugs.
   - type: checkboxes
     id: confirm-search
     attributes:

+ 1 - 1
.github/workflows/build-desktop-release.yml

@@ -122,7 +122,7 @@ jobs:
         run: |
           sed -i 's/defonce version ".*"/defonce version "${{ steps.ref.outputs.version }}"/g' src/main/frontend/version.cljs
 
-      - name: Set Build Environment Variables (only when workflow_dispath)
+      - name: Set Build Environment Variables (only when workflow_dispatch)
         if: ${{ github.event_name == 'workflow_dispatch' }}
         # if scheduled, use default settings
         run: |

+ 1 - 0
CONTRIBUTING.md

@@ -133,6 +133,7 @@ When submitting a Pull Request (PR) or expecting a subsequent review, please fol
    * Unrelated refactoring or heavy refactoring
    * Code or doc formatting changes including whitespace changes
    * Dependency updates e.g. in package.json
+   * Changes that contain multiple unverified resources. This is risky for our users and is a lot of work to verify. A change with one resource that can be verified is acceptable.
 
 ### PR Additional Links
 

+ 2 - 2
android/app/build.gradle

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

+ 2 - 5
bb.edn

@@ -5,13 +5,13 @@
   logseq/bb-tasks
   #_{:local/root "../bb-tasks"}
   {:git/url "https://github.com/logseq/bb-tasks"
-   :git/sha "4295d5df0458cc06a09c5d506510ee49b785407d"}
+   :git/sha "70d3edeb287f5cec7192e642549a401f7d6d4263"}
   logseq/graph-parser
   {:local/root "deps/graph-parser"}
   org.clj-commons/digest
   {:mvn/version "1.4.100"}}
  :pods
- {clj-kondo/clj-kondo {:version "2022.10.05"}
+ {clj-kondo/clj-kondo {:version "2023.05.26"}
   org.babashka/fswatcher {:version "0.0.3"}}
  :tasks
  {dev:desktop-watch
@@ -109,9 +109,6 @@
   lang:missing
   logseq.tasks.lang/list-missing
 
-  lang:duplicates
-  logseq.tasks.lang/list-duplicates
-
   lang:validate-translations
   logseq.tasks.lang/validate-translations
 

+ 1 - 1
deps.edn

@@ -54,5 +54,5 @@
                    :main-opts ["-m" "cljs-test-runner.main" "-d" "src/bench" "-n" "frontend.benchmark-test-runner"]}
 
            ;; Use :replace-deps for tools. See https://github.com/clj-kondo/clj-kondo/issues/1536#issuecomment-1013006889
-           :clj-kondo {:replace-deps {clj-kondo/clj-kondo {:mvn/version "2022.12.08"}}
+           :clj-kondo {:replace-deps {clj-kondo/clj-kondo {:mvn/version "2023.05.26"}}
                        :main-opts  ["-m" "clj-kondo.main"]}}}

+ 3 - 3
deps/common/bb.edn

@@ -3,10 +3,10 @@
  {logseq/bb-tasks
   #_{:local/root "../../../bb-tasks"}
   {:git/url "https://github.com/logseq/bb-tasks"
-   :git/sha "0d49051909bfa0c6b414e86606d82b4ea54f382c"}}
+   :git/sha "70d3edeb287f5cec7192e642549a401f7d6d4263"}}
 
  :pods
- {clj-kondo/clj-kondo {:version "2023.03.17"}}
+ {clj-kondo/clj-kondo {:version "2023.05.26"}}
 
  :tasks
  {test:load-all-namespaces-with-nbb
@@ -23,4 +23,4 @@
 
  :tasks/config
  {:large-vars
-  {:max-lines-count 45}}}
+  {:max-lines-count 45}}}

+ 1 - 1
deps/common/deps.edn

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

+ 2 - 2
deps/db/bb.edn

@@ -4,10 +4,10 @@
  {logseq/bb-tasks
   #_{:local/root "../../../bb-tasks"}
   {:git/url "https://github.com/logseq/bb-tasks"
-   :git/sha "1815db538241082a01e95601e23e4290dd64d0c0"}}
+   :git/sha "70d3edeb287f5cec7192e642549a401f7d6d4263"}}
 
  :pods
- {clj-kondo/clj-kondo {:version "2022.10.05"}}
+ {clj-kondo/clj-kondo {:version "2023.05.26"}}
 
  :tasks
  {test:load-all-namespaces-with-nbb

+ 1 - 1
deps/db/deps.edn

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

+ 2 - 2
deps/graph-parser/bb.edn

@@ -3,10 +3,10 @@
  {logseq/bb-tasks
   #_{:local/root "../../../bb-tasks"}
   {:git/url "https://github.com/logseq/bb-tasks"
-   :git/sha "1815db538241082a01e95601e23e4290dd64d0c0"}}
+   :git/sha "70d3edeb287f5cec7192e642549a401f7d6d4263"}}
  
  :pods
- {clj-kondo/clj-kondo {:version "2022.10.05"}}
+ {clj-kondo/clj-kondo {:version "2023.05.26"}}
 
  :tasks
  {test:load-all-namespaces-with-nbb

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

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

+ 14 - 1
deps/graph-parser/src/logseq/graph_parser/mldoc.cljc

@@ -75,13 +75,26 @@
           js/JSON.stringify))))
 
 (defn remove-indentation-spaces
+  "Remove the indentation spaces from the content. Only for markdown.
+   level - ast level + 1 (2 for the first level, 3 for the second level, etc., as the non-first line of multi-line block has 2 more space
+           Ex.
+              - level 1 multiline block first line
+                level 1 multiline block second line
+              \t- level 2 multiline block first line
+              \t  level 2 multiline block second line
+   remove-first-line? - apply the indentation removal to the first line or not"
   [s level remove-first-line?]
   (let [lines (string/split-lines s)
         [f & r] lines
         body (map (fn [line]
+                    ;; Check if the indentation area only contains white spaces
+                    ;; Level = ast level + 1, 1-based indentation level
+                    ;; For markdown in Logseq, the indentation area for the non-first line of multi-line block is (ast level - 1) * "\t" + 2 * "(space)"
                     (if (string/blank? (gp-util/safe-subs line 0 level))
+                      ;; If valid, then remove the indentation area spaces. Keep the rest of the line (might contain leading spaces)
                       (gp-util/safe-subs line level)
-                      line))
+                      ;; Otherwise, trim these invalid spaces
+                      (string/triml line)))
                (if remove-first-line? lines r))
         content (if remove-first-line? body (cons f body))]
     (string/join "\n" content)))

+ 18 - 0
deps/graph-parser/test/logseq/graph_parser/mldoc_test.cljs

@@ -119,6 +119,24 @@ body"
       (is ["@tag" "tag1" "tag2"] (sort (:filetags props)))
       (is ["@tag" "tag1" "tag2" "tag3"] (sort (:tags props))))))
 
+(deftest remove-indentation-spaces
+  (testing "Remove indentations for every line"
+    (is (=  "block 1.1\n  line 1\n    line 2\nline 3\nline 4"
+            (let [s "block 1.1
+    line 1
+      line 2
+ line 3
+line 4"]
+              (gp-mldoc/remove-indentation-spaces s 2 false)))) 
+    (is (=  "\t- block 1.1\n  line 1\n    line 2\nline 3\nline 4"
+            (let [s "\t- block 1.1
+\t    line 1
+\t      line 2
+\t line 3
+\tline 4"]
+              (gp-mldoc/remove-indentation-spaces s 3 false))))))
+    
+
 (deftest ^:integration test->edn
   (let [graph-dir "test/docs-0.9.2"
         _ (docs-graph-helper/clone-docs-repo-if-not-exists graph-dir "v0.9.2")

+ 2 - 2
deps/publishing/bb.edn

@@ -3,10 +3,10 @@
  {logseq/bb-tasks
   #_{:local/root "../../../bb-tasks"}
   {:git/url "https://github.com/logseq/bb-tasks"
-   :git/sha "0d49051909bfa0c6b414e86606d82b4ea54f382c"}}
+   :git/sha "70d3edeb287f5cec7192e642549a401f7d6d4263"}}
 
  :pods
- {clj-kondo/clj-kondo {:version "2023.03.17"}}
+ {clj-kondo/clj-kondo {:version "2023.05.26"}}
 
  :tasks
  {test:load-all-namespaces-with-nbb

+ 1 - 1
deps/publishing/deps.edn

@@ -3,5 +3,5 @@
  {logseq/db {:local/root "../db"}}
 
  :aliases
- {:clj-kondo {:replace-deps {clj-kondo/clj-kondo {:mvn/version "2023.03.17"}}
+ {:clj-kondo {:replace-deps {clj-kondo/clj-kondo {:mvn/version "2023.05.26"}}
               :main-opts    ["-m" "clj-kondo.main"]}}}

+ 18 - 1
deps/publishing/src/logseq/publishing/db.cljs

@@ -2,6 +2,8 @@
   "Provides db fns and associated util fns for publishing"
   (:require [datascript.core :as d]
             [logseq.db.schema :as db-schema]
+            [logseq.db.rules :as rules]
+            [clojure.set :as set]
             [clojure.string :as string]))
 
 (defn ^:api get-area-block-asset-url
@@ -92,6 +94,20 @@
      flatten
      distinct)))
 
+(defn- get-aliases-for-page-ids
+  [db page-ids]
+  (->> (d/q '[:find ?e
+              :in $ ?pages %
+              :where
+              [?page :block/name]
+              [(contains? ?pages ?page)]
+              (alias ?page ?e)]
+            db
+            (set page-ids)
+            (:alias rules/rules))
+       (map first)
+       set))
+
 (defn clean-export!
   "Prepares a database assuming all pages are public unless a page has a 'public:: false'"
   [db]
@@ -113,7 +129,8 @@
   "Prepares a database assuming all pages are private unless a page has a 'public:: true'"
   [db]
   (when-let [public-pages* (seq (get-public-pages db))]
-    (let [public-pages (set public-pages*)
+    (let [public-pages (set/union (set public-pages*)
+                                  (get-aliases-for-page-ids db public-pages*))
           exported-namespace? #(contains? #{"block" "recent"} %)
           filtered-db (d/filter db
                                 (fn [db datom]

+ 3 - 1
deps/publishing/test/logseq/publishing/db_test.cljs

@@ -36,7 +36,7 @@
 (deftest filter-only-public-pages-and-blocks
   (let [conn (ldb/start-conn)
         _ (graph-parser/parse-file conn "page1.md" "- b11\n- b12\n- ![awesome.png](../assets/awesome_1648822509908_0.png)")
-        _ (graph-parser/parse-file conn "page2.md" "public:: true\n- b21\n- ![thumb-on-fire.PNG](../assets/thumb-on-fire_1648822523866_0.PNG)")
+        _ (graph-parser/parse-file conn "page2.md" "alias:: page2-alias\npublic:: true\n- b21\n- ![thumb-on-fire.PNG](../assets/thumb-on-fire_1648822523866_0.PNG)")
         _ (graph-parser/parse-file conn "page3.md" "public:: true\n- b31")
         [filtered-db assets] (publish-db/filter-only-public-pages-and-blocks @conn)
         exported-pages (->> (d/q '[:find (pull ?b [*])
@@ -56,6 +56,8 @@
         "Contains all pages that have been marked public")
     (is (not (contains? exported-pages "page1"))
         "Doesn't contain private page")
+    (is (seq (d/entity filtered-db [:block/name "page2-alias"]))
+          "Alias of public page is exported")
     (is (= #{"page2" "page3"} exported-block-pages)
         "Only exports blocks from public pages")
     (is (= ["thumb-on-fire_1648822523866_0.PNG"] assets)

+ 14 - 18
docs/contributing-to-translations.md

@@ -80,27 +80,23 @@ $ bb lang:missing es --copy
 
 Almost all translations are small. The only exceptions to this are the keys `:tutorial/text` and `:tutorial/dummy-notes`. These translations are files that are part of the onboarding tutorial and can be found under [src/resources/tutorials/](https://github.com/logseq/logseq/blob/master/src/resources/tutorials/).
 
-## Fix Untranslated
-
-There is a lot to translate and sometimes we forget to translate a string. To see what translation keys are still left for your language use :
-
-```shell
-$ bb lang:duplicates LOCALE
-
-Keys with duplicate values found:
-
-|                  :translation-key | :duplicate-value |
-|-----------------------------------+------------------|
-|                          :general |          General |
-|                           :logseq |           Logseq |
-|                               :no |               No |
-```
+### Editing Tips
 
+* Some translations may include punctuation like `:` or `!`. When translating them, please use the punctuation that makes the most sense for your language as you don't have to follow the English ones.
+* Some translations may include arguments/interpolations e.g. `{1}`. If you see them in a translation, be sure to include them. These arguments are substituted in the string and are usually used something the app needs to calculate e.g. a number. See [these docs](https://github.com/tonsky/tongue#interpolation) for more examples.
 ## Fix Mistakes
 
-Sometimes, we typo a translation key or forget to use it. If this happens,
-the github CI step of `bb lang:validate-translations` will detect these errors
-and tell you what's wrong.
+There is a lint command to catch common translation mistakes - `bb
+lang:validate-translations`. This runs for all contribution pull requests so
+you'll need to ensure it doesn't fail. Mistakes that it catches:
+
+* Adding translation entries for nonexistent entries in English.
+    * Most common mistake is mistyping an entry name
+* Adding English entries for translations that don't exist in the UI.
+* Adding translation entries that are just duplicates of the English entry.
+    * This catches contributors copying entries from English and then forgetting to translate. Sometimes you do want to have the translation be the same. For this case, add an entry to `allowed-duplicates` in
+[lang.clj](https://github.com/logseq/logseq/blob/master/scripts/src/logseq/tasks/lang.clj) for your language
+with a list of duplicated entries e.g. `:nb-NO #{:port ...}`.
 
 ## Add a Language
 

+ 4 - 1
docs/dev-practices.md

@@ -72,11 +72,14 @@ queries and rules. Our queries are linted through clj-kondo and
 [datalog-parser](https://github.com/lambdaforge/datalog-parser). clj-kondo will
 error if it detects an invalid query.
 
-### Invalid translations
+### Translations
 
 Our translations can be configured incorrectly. We can catch some of these
 mistakes [as noted here](./contributing-to-translations.md#fix-mistakes).
 
+Punctuation and delimiting characters (e.g. `:`, `:`, `?`) should be part of the translatable string.
+Those characters and their position may vary depending on the language.
+
 ### Spell Checker
 
 We use [typos](https://github.com/crate-ci/typos) to spell check our source code.

+ 3 - 1
e2e-tests/code-editing.spec.ts

@@ -14,7 +14,9 @@ import {
  * For more information, see: https://codemirror.net/doc/manual.html
  */
 
-test('switch code editing mode', async ({ page }) => {
+// TODO: Fix test that started intermittently failing some time around
+// https://github.com/logseq/logseq/pull/9540
+test.skip('switch code editing mode', async ({ page }) => {
   await createRandomPage(page)
 
   // NOTE: ` will trigger auto-pairing in Logseq

+ 2 - 1
e2e-tests/fixtures.ts

@@ -27,12 +27,13 @@ const consoleLogWatcher = (msg: ConsoleMessage) => {
 
   // List of error messages to ignore
   const ignoreErrors = [
-    /net::ERR_CONNECTION_REFUSED/,
+    /net/,
     /^Error with Permissions-Policy header:/
   ];
 
   // If the text matches any of the ignoreErrors, return early
   if (ignoreErrors.some(error => text.match(error))) {
+    console.log(`WARN:: ${text}\n`)
     return;
   }
 

+ 1 - 0
e2e-tests/history.spec.ts

@@ -43,6 +43,7 @@ test('undo/redo of a renamed page should be preserved', async ({ page, block })
   await page.waitForTimeout(500) // Wait for 500ms autosave period to expire
 
   await renamePage(page, randomString(10))
+  await page.click('.ui__confirm-modal button')
 
   await page.keyboard.press(modKey + '+z')
   await page.waitForTimeout(100)

+ 2 - 1
e2e-tests/hotkey.spec.ts

@@ -1,9 +1,10 @@
 import { expect } from '@playwright/test'
 import { test } from './fixtures'
-import { createRandomPage, enterNextBlock, lastBlock, modKey, IsLinux } from './utils'
+import { createRandomPage, enterNextBlock, lastBlock, modKey, IsLinux, closeSearchBox } from './utils'
 
 test('open search dialog', async ({ page }) => {
   await page.waitForTimeout(200)
+  await closeSearchBox(page)
   await page.keyboard.press(modKey + '+k')
 
   await page.waitForSelector('[placeholder="Search or create page"]')

+ 13 - 1
e2e-tests/page-rename.spec.ts

@@ -1,6 +1,6 @@
 import { expect, Page } from '@playwright/test'
 import { test } from './fixtures'
-import { createPage, randomLowerString, randomString, renamePage } from './utils'
+import { closeSearchBox, createPage, randomLowerString, randomString, renamePage, searchPage } from './utils'
 
 /***
  * Test rename feature
@@ -15,6 +15,7 @@ async function page_rename_test(page: Page, original_page_name: string, new_page
 
   // Rename page in UI
   await renamePage(page, new_name)
+  await page.click('.ui__confirm-modal button')
 
   expect(await page.innerText('.page-title .title')).toBe(new_name)
 
@@ -45,6 +46,7 @@ async function homepage_rename_test(page: Page, original_page_name: string, new_
   expect(await page.locator('.home-nav span.flex-1').innerText()).toBe(original_name);
 
   await renamePage(page, new_name)
+  await page.click('.ui__confirm-modal button')
 
   expect(await page.locator('.home-nav span.flex-1').innerText()).toBe(new_name);
 
@@ -64,6 +66,16 @@ test('page rename test', async ({ page }) => {
   await page_rename_test(page, "abcd", "a.b.c.d")
   await page_rename_test(page, "abcd", "a/b/c/d")
 
+  // The page name in page search are not updated after changing the capitalization of the page name #9577
+  // https://github.com/logseq/logseq/issues/9577
+  // Expect the page name to be updated in the search results
+  await page_rename_test(page, "DcBA_", "dCBA_")
+  const results = await searchPage(page, "DcBA_")
+  // search result 0 is the new page & 1 is the new whiteboard
+  const thirdResultRow = await results[2].innerText()
+  expect(thirdResultRow).toContain("dCBA_");
+  expect(thirdResultRow).not.toContain("DcBA_");
+  await closeSearchBox(page)
 })
 
 // TODO introduce more samples when #4722 is fixed

+ 0 - 1
e2e-tests/util/page.ts

@@ -11,5 +11,4 @@ export async function renamePage(page: Page, new_name: string) {
   await page.fill('input[type="text"]', '')
   await page.type('.title input', new_name)
   await page.keyboard.press('Enter')
-  await page.click('.ui__confirm-modal button')
 }

+ 32 - 29
e2e-tests/util/search-modal.ts

@@ -1,63 +1,66 @@
 import { Page, Locator, ElementHandle } from '@playwright/test'
 import { randomString } from './basic'
 
+export async function closeSearchBox(page: Page): Promise<void> {
+    await page.keyboard.press("Escape") // escape (potential) search box typing
+    await page.waitForTimeout(500)
+    await page.keyboard.press("Escape") // escape modal
+}
+
 export async function createRandomPage(page: Page) {
     const randomTitle = randomString(20)
-  
+    await closeSearchBox(page)
     // Click #search-button
     await page.click('#search-button')
     // Fill [placeholder="Search or create page"]
     await page.fill('[placeholder="Search or create page"]', randomTitle)
     // Click text=/.*New page: "new page".*/
-    await page.click('text=/.*New page: ".*/')
+    await page.click('text=/.*New page:".*/')
     // Wait for h1 to be from our new page
     await page.waitForSelector(`h1 >> text="${randomTitle}"`, { state: 'visible' })
     // wait for textarea of first block
     await page.waitForSelector('textarea >> nth=0', { state: 'visible' })
-  
+
     return randomTitle;
-  }
-  
-  export async function createPage(page: Page, page_name: string) {// Click #search-button
+}
+
+export async function createPage(page: Page, page_name: string) {// Click #search-button
+    await closeSearchBox(page)
     await page.click('#search-button')
     // Fill [placeholder="Search or create page"]
     await page.fill('[placeholder="Search or create page"]', page_name)
     // Click text=/.*New page: "new page".*/
-    await page.click('text=/.*New page: ".*/')
+    await page.click('text=/.*New page:".*/')
     // wait for textarea of first block
     await page.waitForSelector('textarea >> nth=0', { state: 'visible' })
-  
+
     return page_name;
   }
-  
-  export async function searchAndJumpToPage(page: Page, pageTitle: string) {
+
+export async function searchAndJumpToPage(page: Page, pageTitle: string) {
+    await closeSearchBox(page)
     await page.click('#search-button')
     await page.type('[placeholder="Search or create page"]', pageTitle)
     await page.waitForSelector(`[data-page-ref="${pageTitle}"]`, { state: 'visible' })
     page.click(`[data-page-ref="${pageTitle}"]`)
     await page.waitForNavigation()
     return pageTitle;
-  }
-  
-  /**
-   * type a search query into the search box
-   * stop at the point where search box shows up
-   * 
-   * @param page the pw page object
-   * @param query the search query to type into the search box
-   * @returns the HTML element for the search results ui
-   */
-  export async function searchPage(page: Page, query: string): Promise<ElementHandle<SVGElement | HTMLElement>[]>{
+}
+
+/**
+ * type a search query into the search box
+ * stop at the point where search box shows up
+ *
+ * @param page the pw page object
+ * @param query the search query to type into the search box
+ * @returns the HTML element for the search results ui
+ */
+export async function searchPage(page: Page, query: string): Promise<ElementHandle<SVGElement | HTMLElement>[]>{
+    await closeSearchBox(page)
     await page.click('#search-button')
     await page.waitForSelector('[placeholder="Search or create page"]')
     await page.type('[placeholder="Search or create page"]', query, { delay: 10 })
     await page.waitForTimeout(2000) // wait longer for search contents to render
-  
+
     return page.$$('#ui__ac-inner>div');
-  }
-  
-  export async function closeSearchBox(page: Page): Promise<void> {
-    await page.keyboard.press("Escape") // escape (potential) search box typing
-    await page.waitForTimeout(500)
-    await page.keyboard.press("Escape") // escape modal
-  }
+}

+ 64 - 2
e2e-tests/whiteboards.spec.ts

@@ -1,6 +1,6 @@
 import { expect } from '@playwright/test'
 import { test } from './fixtures'
-import { modKey } from './utils'
+import { modKey, renamePage } from './utils'
 
 test('enable whiteboards', async ({ page }) => {
   if (await page.$('.nav-header .whiteboard') === null) {
@@ -293,7 +293,7 @@ test('create a block', async ({ page }) => {
 
 test('expand the block', async ({ page }) => {
   await page.keyboard.press('Escape')
-  await page.click('.logseq-tldraw .tl-context-bar .tie-object-expanded ')
+  await page.keyboard.press(modKey + '+ArrowDown')
   await page.waitForTimeout(100)
 
   await expect(page.locator('.logseq-tldraw .tl-logseq-portal-container .tl-logseq-portal-header')).toHaveCount(1)
@@ -437,3 +437,65 @@ test('go to another board and check reference', async ({ page }) => {
   const pageRefCount$ = page.locator('.whiteboard-page-refs-count')
   await expect(pageRefCount$.locator('.open-page-ref-link')).toContainText('1')
 })
+
+test('Create an embedded whiteboard', async ({ page }) => {
+  const canvas = await page.waitForSelector('.logseq-tldraw')
+  await canvas.dblclick({
+    position: {
+      x: 150,
+      y: 150,
+    },
+  })
+
+  const quickAdd$ = page.locator('.tl-quick-search')
+  await expect(quickAdd$).toBeVisible()
+
+  await page.fill('.tl-quick-search input', 'My embedded whiteboard')
+  await quickAdd$
+    .locator('div[data-index="2"] .tl-quick-search-option')
+    .first()
+    .click()
+
+  await expect(quickAdd$).toBeHidden()
+  await expect(page.locator('.tl-logseq-portal-header a')).toContainText('My embedded whiteboard')
+})
+
+test('New whiteboard should have the correct name', async ({ page }) => {
+  page.locator('.tl-logseq-portal-header a').click()
+
+  await expect(page.locator('.whiteboard-page-title')).toContainText('My embedded whiteboard')
+})
+
+test('Create an embedded page', async ({ page }) => {
+  const canvas = await page.waitForSelector('.logseq-tldraw')
+  await canvas.dblclick({
+    position: {
+      x: 150,
+      y: 150,
+    },
+  })
+
+  const quickAdd$ = page.locator('.tl-quick-search')
+  await expect(quickAdd$).toBeVisible()
+
+  await page.fill('.tl-quick-search input', 'My page')
+  await quickAdd$
+    .locator('div[data-index="1"] .tl-quick-search-option')
+    .first()
+    .click()
+
+  await expect(quickAdd$).toBeHidden()
+  await expect(page.locator('.tl-logseq-portal-header a')).toContainText('My page')
+})
+
+test('New page should have the correct name', async ({ page }) => {
+  page.locator('.tl-logseq-portal-header a').click()
+
+  await expect(page.locator('.ls-page-title')).toContainText('My page')
+})
+
+test('Renaming a page to an existing whiteboard name should be prohibited', async ({ page }) => {
+  await renamePage(page, "My embedded whiteboard")
+
+  await expect(page.locator('.page-title input')).toHaveValue('My page')
+})

+ 4 - 4
ios/App/App.xcodeproj/project.pbxproj

@@ -519,7 +519,7 @@
 				INFOPLIST_FILE = App/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				MARKETING_VERSION = 0.9.8;
+				MARKETING_VERSION = 0.9.9;
 				OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -546,7 +546,7 @@
 				INFOPLIST_FILE = App/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				MARKETING_VERSION = 0.9.8;
+				MARKETING_VERSION = 0.9.9;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
@@ -571,7 +571,7 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
-				MARKETING_VERSION = 0.9.8;
+				MARKETING_VERSION = 0.9.9;
 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 				MTL_FAST_MATH = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
@@ -598,7 +598,7 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
-				MARKETING_VERSION = 0.9.8;
+				MARKETING_VERSION = 0.9.9;
 				MTL_FAST_MATH = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
 				PRODUCT_NAME = "$(TARGET_NAME)";

+ 13 - 5
ios/App/App/FsWatcher.swift

@@ -56,7 +56,7 @@ public class FsWatcher: CAPPlugin, PollingWatcherDelegate {
             DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                 self.notifyListeners("watcher", data: ["event": "unlink",
                                                        "dir": baseUrl.description as Any,
-                                                       "path": url.description
+                                                       "path": url.relativePath(from: baseUrl)?.precomposedStringWithCanonicalMapping as Any
                 ])
             }
         case .Add, .Change:
@@ -173,11 +173,11 @@ public class PollingWatcher {
     public init?(at: URL) {
         url = at
     }
-    
+
     public func start() {
-        
+
         self.tick(notify: false)
-        
+
         let queue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".timer")
         timer = DispatchSource.makeTimerSource(queue: queue)
         timer!.setEventHandler(qos: .background, flags: []) { [weak self] in
@@ -278,10 +278,18 @@ extension URL {
             return nil
         }
 
+        // NOTE: standardizedFileURL will remove `/private` prefix
+        // If the file is not exist, it won't remove the prefix.
+
         // Remove/replace "." and "..", make paths absolute:
-        let destComponents = self.standardizedFileURL.pathComponents
+        var destComponents = self.standardizedFileURL.pathComponents
         let baseComponents = base.standardizedFileURL.pathComponents
 
+        // replace "private" when needed
+        if destComponents.count > 1 && destComponents[1] == "private" && baseComponents.count > 1 && baseComponents[1] != "private" {
+            destComponents.remove(at: 1)
+        }
+
         // Find number of common path components:
         var i = 0
         while i < destComponents.count && i < baseComponents.count

+ 18 - 2
playwright.config.ts

@@ -1,12 +1,28 @@
 import { PlaywrightTestConfig } from '@playwright/test'
 
 const config: PlaywrightTestConfig = {
+  // The directory where the tests are located
+  // The order of the tests is determined by the file names alphabetically.
   testDir: './e2e-tests',
+
+  // The number of retries before marking a test as failed.
   maxFailures: 1,
-  workers: 1, // NOTE: must be 1 for now, otherwise tests will fail.
+
+  // The number of Logseq instances to run in parallel.
+  // NOTE: must be 1 for now, otherwise tests will fail.
+  workers: 1,
+
+  // 'github' for GitHub Actions CI to generate annotations, plus a concise 'dot'.
+  // default 'list' when running locally.
+  reporter: process.env.CI ? 'github' : 'list',
+
+  // Fail the build on CI if test.only is present.
+  forbidOnly: !!process.env.CI,
+
   use: {
+    // SCapture screenshot after each test failure.
     screenshot: 'only-on-failure',
-  }
+  },
 }
 
 export default config

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
resources/css/katex.min.css


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
resources/js/katex.min.js


+ 2 - 2
resources/package.json

@@ -1,7 +1,7 @@
 {
   "name": "Logseq",
   "productName": "Logseq",
-  "version": "0.9.8",
+  "version": "0.9.9",
   "main": "electron.js",
   "author": "Logseq",
   "license": "AGPL-3.0",
@@ -22,7 +22,7 @@
   "dependencies": {
     "better-sqlite3": "8.0.1",
     "chokidar": "^3.5.1",
-    "dugite": "1.108.0",
+    "dugite": "2.5.0",
     "electron-dl": "3.3.0",
     "electron-log": "4.3.1",
     "electron-squirrel-startup": "1.0.0",

+ 44 - 20
scripts/src/logseq/tasks/lang.clj

@@ -154,31 +154,55 @@
           (task-util/print-table (map #(hash-map :invalid-key %) expected-only)))
         (System/exit 1)))))
 
-(defn validate-translations
-  "Runs multiple translation validations that fail fast if one of them is invalid"
-  []
-  (validate-non-default-languages)
-  (validate-ui-translations-are-used))
+(def allowed-duplicates
+  "Allows certain keys in a language to have the same translation
+   as English. Happens more in romance languages but pretty rare otherwise"
+  {:fr #{:port :type :help/docs :search-item/page :shortcut.category/navigating :text/image
+         :settings-of-plugins}
+   :de #{:graph :host :plugins :port :right-side-bar/whiteboards :search-item/block
+         :settings-of-plugins :search-item/whiteboard :shortcut.category/navigating
+         :settings-page/enable-tooltip :settings-page/enable-whiteboards :settings-page/plugin-system}
+   :es #{:settings-page/tab-general :settings-page/tab-editor :whiteboard/color}
+   :it #{:plugins}
+   :nl #{:plugins :type :left-side-bar/nav-recent-pages :plugin/update}
+   :pl #{:port}
+   :pt-BR #{:plugins :right-side-bar/flashcards :settings-page/enable-flashcards :page/backlinks
+            :host :settings-page/tab-editor :shortcut.category/plugins :whiteboard/link}
+   :pt-PT #{:plugins :settings-of-plugins :plugin/downloads :right-side-bar/flashcards
+            :settings-page/enable-flashcards :settings-page/plugin-system}
+   :nb-NO #{:port :type :whiteboard :right-side-bar/flashcards :right-side-bar/whiteboards 
+            :search-item/whiteboard :settings-page/enable-flashcards :settings-page/enable-whiteboards 
+            :settings-page/tab-editor :shortcut.category/whiteboard :whiteboard/medium 
+            :whiteboard/twitter-url :whiteboard/youtube-url}
+   :tr #{:help/awesome-logseq}
+   })
 
-(defn list-duplicates
-  "Lists translations that are the same as the one in English"
-  [& args]
+(defn- validate-languages-dont-have-duplicates
+  "Looks up duplicates for all languages"
+  []
   (let [dicts (get-dicts)
         en-dicts (dicts :en)
-        lang (or (keyword (first args))
-                 (task-util/print-usage "LOCALE"))
-        lang-dicts (dicts lang)
         invalid-dicts
-        (sort-by
-         :translation-key
-         (keep
-          #(when (= (en-dicts %) (lang-dicts %))
-             {:translation-key %
-              :duplicate-value (shorten (lang-dicts %) 70)})
-          (keys lang-dicts)))]
+        (->> (dissoc dicts :en)
+             (mapcat
+              (fn [[lang lang-dicts]]
+                (keep
+                 #(when (= (en-dicts %) (lang-dicts %))
+                    {:translation-key %
+                     :lang lang
+                     :duplicate-value (shorten (lang-dicts %) 70)})
+                 (keys (apply dissoc lang-dicts (allowed-duplicates lang))))))
+             (sort-by (juxt :lang :translation-key)))]
     (if (empty? invalid-dicts)
-      (println "No duplicated keys found!")
+      (println "All languages have no duplicate English values!")
       (do
-        (println "Keys with duplicate values found:")
+        (println "These translations keys are invalid because they are just copying the English value:")
         (task-util/print-table invalid-dicts)
         (System/exit 1)))))
+
+(defn validate-translations
+  "Runs multiple translation validations that fail fast if one of them is invalid"
+  []
+  (validate-non-default-languages)
+  (validate-ui-translations-are-used)
+  (validate-languages-dont-have-duplicates))

+ 37 - 14
src/electron/electron/plugin.cljs

@@ -19,6 +19,23 @@
               (.. win -webContents
                   (send (name type) (bean/->js payload))))))
 
+(defonce github-api-0 "https://api.github.com")
+(defonce github-api-1 "https://plugins.logseq.io/github/api")
+(defonce *github-api (atom github-api-0))
+(defonce *last-valid-github-api (atom nil))
+
+(defn valid-github-api!
+  []
+  (when (or (nil? @*last-valid-github-api)
+            (> (- (js/Date.now) @*last-valid-github-api) (* 1000 60)))
+    (let [target github-api-1]
+      (-> (fetch (str target "/rate_limit") {:timeout 2000})
+          (p/then #(when (not= (.-status %) 200) (throw (js/Error. (.-statusText %)))))
+          (p/then #(do (reset! *github-api target) (debug "INFO: use github api - " target)))
+          (p/catch #(do (reset! *github-api github-api-0) (debug "ERR: valid github api - " %)))
+          (p/finally #(reset! *last-valid-github-api (js/Date.now)))))))
+
+
 (defn dotdir-file?
   [file]
   (and file (string/starts-with? (node-path/normalize file) cfgs/dot-root)))
@@ -34,13 +51,14 @@
 (defn- fetch-release-asset
   [{:keys [repo theme]} url-suffix {:keys [response-transform]
                                     :or   {response-transform identity}}]
-  (-> (p/let [repo         (some-> repo (string/trim) (string/replace #"^/+(.+?)/+$" "$1"))
-              api          #(str "https://api.github.com/repos/" repo "/" %)
+  (-> (p/let [_            (valid-github-api!)
+              repo         (some-> repo (string/trim) (string/replace #"^/+(.+?)/+$" "$1"))
+              api          #(str @*github-api "/repos/" repo "/" %)
               endpoint     (api url-suffix)
-              ^js res      (fetch endpoint)
+              ^js res      (fetch endpoint {:timeout (* 1000 5)})
               illegal-text (when-not (= 200 (.-status res)) (.text res))
               _            (when-not (string/blank? illegal-text) (throw (js/Error. (str "Github API Failed(" (.-status res) ") " illegal-text))))
-              _            (debug "[Release URL] " endpoint "[Status/Text]" (.-status res))
+              _            (debug "Release latest:" endpoint ":status" (.-status res))
               res          (response-transform res)
               res          (.json res)
               res          (bean/->clj res)
@@ -83,7 +101,7 @@
                             ;; cases. Previous logseq versions did not store the
                             ;; plugin's git tag required to correctly install it
                             (let [repo' (some-> repo (string/trim) (string/replace #"^/+(.+?)/+$" "$1"))
-                                  api   #(str "https://api.github.com/repos/" repo' "/" %)]
+                                  api   #(str @*github-api "/repos/" repo' "/" %)]
                               (fetch (api "releases/latest")))
                             res))}))
 
@@ -174,10 +192,10 @@
           updating?       (and version (. semver valid coerced-version)
                                (not= action :install))]
 
-      (debug (if updating? "Updating:" "Installing:") repo)
+      (debug "===" (if updating? "Updating:" "Installing:") repo "===")
 
       (-> (p/create
-            (fn [resolve _reject]
+            (fn [resolve reject]
               ;;(reset! *installing-or-updating item)
               ;; get releases
               (-> (p/let [[asset latest-version notes]
@@ -185,18 +203,19 @@
                             (fetch-specific-release-asset item)
                             (fetch-latest-release-asset item))
 
-                          _      (debug "[Release Asset] #" latest-version " =>" (:url asset))
+                          _      (debug "Release latest:" latest-version " from" (:url asset))
 
                           ;; compare latest version
                           _      (when-let [coerced-latest-version
                                             (and updating? latest-version
                                                  (. semver coerce latest-version))]
 
-                                   (debug "[Updating Latest?] " version " > " latest-version)
+                                   (debug "Release compare:" version "(current) > " latest-version "(latest)")
 
                                    (if (. semver lt coerced-version coerced-latest-version)
-                                     (debug "[Updating Latest] " latest-version)
-                                     (throw (js/Error. :no-new-version))))
+                                     (debug "Updating latest:" latest-version)
+                                     (do (debug "Update skip: no new version")
+                                         (throw (js/Error. :no-new-version)))))
 
                           dl-url (if-not (string? asset)
                                    (:browser_download_url asset) asset)
@@ -207,7 +226,7 @@
 
                           dest   (.join node-path cfgs/dot-root "plugins" (:id item))
                           _      (when-not only-check (download-asset-zip item dl-url latest-version dest))
-                          _      (debug (str "[" (if only-check "Checked" "Updated") "DONE]") latest-version)]
+                          _      (debug (str "[" (if only-check "Checked" "Updated") " DONE]") latest-version)]
 
                     (emit :lsp-updates
                           {:status     :completed
@@ -224,8 +243,12 @@
                             {:status     :error
                              :only-check only-check
                              :payload    (assoc item :error-code (.-message e))})
-                      (debug e))
-                    (resolve nil)))))
+                      (reject e))))))
+
+          (p/catch
+            (fn [^js e]
+              (when-not (contains? #{":no-new-version"} (.-message e))
+                (debug e))))
 
           (p/finally
             (fn []))))

+ 3 - 10
src/main/electron/listener.cljs

@@ -5,7 +5,6 @@
             [datascript.core :as d]
             [dommy.core :as dom]
             [electron.ipc :as ipc]
-            [frontend.context.i18n :refer [t]]
             [frontend.db :as db]
             [frontend.db.model :as db-model]
             [frontend.fs.sync :as sync]
@@ -35,9 +34,7 @@
   []
   ;; only persist current db!
   ;; TODO rename the function and event to persist-db
-  (repo-handler/persist-db! {:before     #(notification/show!
-                                           (ui/loading (t :graph/persist))
-                                           :warning)
+  (repo-handler/persist-db! {:before     #(ui/notify-graph-persist!)
                              :on-success #(ipc/ipc "persistent-dbs-saved")
                              :on-error   #(ipc/ipc "persistent-dbs-error")}))
 
@@ -139,15 +136,11 @@
                  ;; fire back "broadcastPersistGraphDone" on done
                  (fn [data]
                    (let [repo (bean/->clj data)
-                         before-f #(notification/show!
-                                    (ui/loading (t :graph/persist))
-                                    :warning)
+                         before-f #(ui/notify-graph-persist!)
                          after-f #(ipc/ipc "broadcastPersistGraphDone")
                          error-f (fn []
                                    (after-f)
-                                   (notification/show!
-                                    (t :graph/persist-error)
-                                    :error))
+                                   (ui/notify-graph-persist-error!))
                          handlers {:before     before-f
                                    :on-success after-f
                                    :on-error   error-f}]

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

@@ -88,11 +88,11 @@
        :disabled (string/blank? val)
        :on-click on-submit)]]))
 
-(rum/defc restart-button [active?]
-  (when active?
-    (ui/button (t :plugin/restart)
-               :on-click #(js/logseq.api.relaunch)
-               :small? true :intent "logseq")))
+(rum/defc restart-button
+  []
+  (ui/button (t :plugin/restart)
+             :on-click #(js/logseq.api.relaunch)
+             :small? true :intent "logseq"))
 
 (rum/defcs ^:large-vars/data-var alias-directories
   < rum/reactive
@@ -215,7 +215,7 @@
              #(state/set-assets-alias-enabled! (not alias-enabled?))
              true)]
       [:span
-       (restart-button alias-enabled-changed?)]]
+       (when alias-enabled-changed? (restart-button))]]
 
      (when alias-enabled?
        [:div.pt-4

+ 38 - 86
src/main/frontend/components/block.cljs

@@ -6,12 +6,12 @@
             [cljs-bean.core :as bean]
             [cljs.core.match :refer [match]]
             [cljs.reader :as reader]
-            [clojure.set :as set]
             [clojure.string :as string]
             [clojure.walk :as walk]
             [datascript.core :as d]
             [dommy.core :as dom]
             [frontend.commands :as commands]
+            [frontend.components.block.macros :as block-macros]
             [frontend.components.datetime :as datetime-comp]
             [frontend.components.lazy-editor :as lazy-editor]
             [frontend.components.macro :as macro]
@@ -37,13 +37,11 @@
             [frontend.fs :as fs]
             [frontend.handler.assets :as assets-handler]
             [frontend.handler.block :as block-handler]
-            [frontend.handler.common :as common-handler]
             [frontend.handler.dnd :as dnd]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.file-sync :as file-sync]
             [frontend.handler.notification :as notification]
             [frontend.handler.plugin :as plugin-handler]
-            [frontend.handler.query :as query-handler]
             [frontend.handler.repeated :as repeated]
             [frontend.handler.route :as route-handler]
             [frontend.handler.ui :as ui-handler]
@@ -68,7 +66,6 @@
             [logseq.graph-parser.block :as gp-block]
             [logseq.graph-parser.config :as gp-config]
             [logseq.graph-parser.mldoc :as gp-mldoc]
-            [logseq.graph-parser.property :as gp-property]
             [logseq.graph-parser.text :as text]
             [logseq.graph-parser.util :as gp-util]
             [logseq.graph-parser.util.block-ref :as block-ref]
@@ -552,7 +549,7 @@
                untitled? (str " opacity-50"))
       :data-ref page-name
       :draggable true
-      :on-drag-start (fn [e] (editor-handler/block->data-transfer! page-name e))
+      :on-drag-start (fn [e] (editor-handler/block->data-transfer! page-name-in-block e))
       :on-mouse-down (fn [_e] (reset! *mouse-down? true))
       :on-mouse-up (fn [e]
                      (when @*mouse-down?
@@ -1243,17 +1240,7 @@
 (defn- macro-function-cp
   [config arguments]
   (or
-   (when (:query-result config)
-     (when-let [query-result (rum/react (:query-result config))]
-       (let [fn-string (-> (util/format "(fn [result] %s)" (first arguments))
-                           (common-handler/safe-read-string "failed to parse function")
-                           (query-handler/normalize-query-function query-result)
-                           (str))
-             f (sci/eval-string fn-string)]
-         (when (fn? f)
-           (try (f query-result)
-                (catch :default e
-                  (js/console.error e)))))))
+   (some-> (:query-result config) rum/react (block-macros/function-macro arguments))
    [:span.warning
     (util/format "{{function %s}}" (first arguments))]))
 
@@ -1686,7 +1673,7 @@
        :block)
       (util/stop e))
 
-    (whiteboard-handler/inside-portal? (.-target e))
+    (and (util/meta-key? e) (whiteboard-handler/inside-portal? (.-target e)))
     (do (whiteboard-handler/add-new-block-portal-shape!
          uuid
          (whiteboard-handler/closest-shape (.-target e)))
@@ -1957,11 +1944,12 @@
                  (not= "nil" marker))
         {:class (str (string/lower-case marker))})
       (when bg-color
-        {:style {:background-color (if (some #{bg-color} ui/block-background-colors)
-                                     (str "var(--ls-highlight-color-" bg-color ")")
-                                     bg-color)
-                 :color (when-not (some #{bg-color} ui/block-background-colors) "white")}
-         :class "px-1 with-bg-color"}))
+        (let [built-in-color? (ui/built-in-color? bg-color)]
+          {:style {:background-color (if built-in-color?
+                                       (str "var(--ls-highlight-color-" bg-color ")")
+                                       bg-color)
+                   :color (when-not built-in-color? "white")}
+           :class "px-1 with-bg-color"})))
 
      ;; children
      (let [area?  (= :area (keyword (:hl-type properties)))
@@ -2057,66 +2045,25 @@
         :else
         (inline-text config (:block/format block) (str v)))]]))
 
-(def hidden-editable-page-properties
-  "Properties that are hidden in the pre-block (page property)"
-  #{:title :filters :icon})
-
-(assert (set/subset? hidden-editable-page-properties (gp-property/editable-built-in-properties))
-        "Hidden editable page properties must be valid editable properties")
-
-(def hidden-editable-block-properties
-  "Properties that are hidden in a block (block property)"
-  (into #{:logseq.query/nlp-date}
-        gp-property/editable-view-and-table-properties))
-
-(assert (set/subset? hidden-editable-block-properties (gp-property/editable-built-in-properties))
-        "Hidden editable page properties must be valid editable properties")
-
-(defn- add-aliases-to-properties
-  [properties block]
-  (let [repo (state/get-current-repo)
-        aliases (db/get-page-alias-names repo
-                                         (:block/name (db/pull (:db/id (:block/page block)))))]
-    (if (seq aliases)
-      (if (:alias properties)
-        (update properties :alias (fn [c]
-                                    (util/distinct-by string/lower-case (concat c aliases))))
-        (assoc properties :alias aliases))
-      properties)))
-
 (rum/defc properties-cp
   [config {:block/keys [pre-block?] :as block}]
-  (let [dissoc-keys (fn [m keys] (apply dissoc m keys))
-        properties (cond-> (update-keys (:block/properties block) keyword)
-                           true
-                           (dissoc-keys (property/hidden-properties))
-                           pre-block?
-                           (dissoc-keys hidden-editable-page-properties)
-                           (not pre-block?)
-                           (dissoc-keys hidden-editable-block-properties)
-                           pre-block?
-                           (add-aliases-to-properties block))]
+  (let [ordered-properties
+        (property/get-visible-ordered-properties (:block/properties block)
+                                                 (:block/properties-order block)
+                                                 {:pre-block? pre-block?
+                                                  :page-id (:db/id (:block/page block))})]
     (cond
-      (seq properties)
-      (let [properties-order (cond->> (:block/properties-order block)
-                                      true
-                                      (remove (property/hidden-properties))
-                                      pre-block?
-                                      (remove hidden-editable-page-properties))
-            properties-order (distinct properties-order)
-            ordered-properties (if (seq properties-order)
-                                 (map (fn [k] [k (get properties k)]) properties-order)
-                                 properties)]
-        [:div.block-properties
-         {:class (when pre-block? "page-properties")
-          :title (if pre-block?
-                   "Click to edit this page's properties"
-                   "Click to edit this block's properties")}
-         (for [[k v] ordered-properties]
-           (rum/with-key (property-cp config block k v)
-             (str (:block/uuid block) "-" k)))])
-
-      (and pre-block? properties)
+      (seq ordered-properties)
+      [:div.block-properties
+       {:class (when pre-block? "page-properties")
+        :title (if pre-block?
+                 "Click to edit this page's properties"
+                 "Click to edit this block's properties")}
+       (for [[k v] ordered-properties]
+         (rum/with-key (property-cp config block k v)
+           (str (:block/uuid block) "-" k)))]
+
+      (and pre-block? ordered-properties)
       [:span.opacity-50 "Properties"]
 
       :else
@@ -3126,14 +3073,15 @@
 
         :else
         (let [language (if (contains? #{"edn" "clj" "cljc" "cljs"} language) "clojure" language)]
-          [:div {:ref (fn [el]
-                        (set-inside-portal? (and el (whiteboard-handler/inside-portal? el))))}
+          [:div.ui-fenced-code-editor
+           {:ref (fn [el]
+                   (set-inside-portal? (and el (whiteboard-handler/inside-portal? el))))}
            (cond
              (nil? inside-portal?) nil
 
              (or (:slide? config) inside-portal?)
              (highlight/highlight (str (random-uuid))
-                                  {:class (str "language-" language)
+                                  {:class     (str "language-" language)
                                    :data-lang language}
                                   code)
 
@@ -3302,10 +3250,14 @@
             [:sup.fn (str name "↩︎")]])]])
 
       ["Src" options]
-      [:div.cp__fenced-code-block
-       (if-let [opts (plugin-handler/hook-fenced-code-by-type (util/safe-lower-case (:language options)))]
-         (plugins/hook-ui-fenced-code (string/join "" (:lines options)) opts)
-         (src-cp config options html-export?))]
+      (let [lang (util/safe-lower-case (:language options))]
+        [:div.cp__fenced-code-block
+         {:data-lang lang}
+         (if-let [opts (plugin-handler/hook-fenced-code-by-type lang)]
+           [:div.ui-fenced-code-wrap
+            (src-cp config options html-export?)
+            (plugins/hook-ui-fenced-code (:block config) (string/join "" (:lines options)) opts)]
+           (src-cp config options html-export?))])
 
       :else
       "")

+ 1 - 1
src/main/frontend/components/block.css

@@ -1,7 +1,7 @@
 .block-content-wrapper {
   /* 38px is the width of block-control */
   width: calc(100% - 22px);
-
+  user-select: text;
   @screen sm {
     width: calc(100% - 33px);
     overflow-x: visible;

+ 71 - 0
src/main/frontend/components/block/macros.cljs

@@ -0,0 +1,71 @@
+(ns frontend.components.block.macros
+  "Logseq macros that render and evaluate in blocks"
+  (:require [clojure.walk :as walk]
+            [frontend.extensions.sci :as sci]
+            [frontend.handler.common :as common-handler]
+            [goog.string :as gstring]
+            [goog.string.format]))
+
+(defn- normalize-query-function
+  [ast result]
+  (let [ast (walk/prewalk
+             (fn [f]
+               (if (and (list? f)
+                        (keyword? (second f))
+                        (contains? #{'sum 'average 'count 'min 'max} (first f)))
+                 (if (contains? #{'min 'max} (first f))
+                   (list
+                    'apply
+                    (first f)
+                    (list 'map (second f) 'result))
+                   (list
+                    (first f)
+                    (list 'map (second f) 'result)))
+                 f))
+             ast)]
+    (walk/postwalk
+     (fn [f]
+       (cond
+         (keyword? f)
+         ;; These keyword aliases should be the same as those used in the query-table for sorting
+         (case f
+           :block
+           :block/content
+
+           :page
+           :block/name
+
+           :created-at
+           :block/created-at
+
+           :updated-at
+           :block/updated-at
+
+           (let [vals (map #(get-in % [:block/properties f]) result)
+                 int? (some integer? vals)]
+             `(~'fn [~'b]
+                    (~'let [~'result-str (~'get-in ~'b [:block/properties ~f])
+                            ~'result-num (~'parseFloat ~'result-str)
+                            ~'result (if (~'isNaN ~'result-num) ~'result-str ~'result-num)]
+                           (~'or ~'result (~'when ~int? 0))))))
+
+         :else
+         f))
+     ast)))
+
+(defn function-macro
+  "Provides functionality for {{function}}"
+  [query-result* arguments]
+  (let [query-result (if (map? query-result*)
+                       ;; Ungroup results grouped by page in page view
+                       (mapcat val query-result*)
+                       query-result*)
+        fn-string (-> (gstring/format "(fn [result] %s)" (first arguments))
+                      (common-handler/safe-read-string "failed to parse function")
+                      (normalize-query-function query-result)
+                      (str))
+        f (sci/eval-string fn-string)]
+    (when (fn? f)
+      (try (f query-result)
+           (catch :default e
+             (js/console.error e))))))

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

@@ -541,7 +541,7 @@
          db-restoring?
          [:div.mt-20
           [:div.ls-center
-           (ui/loading (t :loading))]]
+           (ui/loading)]]
 
          :else
          [:div

+ 2 - 1
src/main/frontend/components/container.css

@@ -488,7 +488,8 @@ html[data-theme='dark'] {
 }
 
 .settings-modal {
-  margin: -15px;
+  @apply -m-8 rounded-lg;
+  /* box-shadow: inset 0 0 0 1px var(--ls-border-color); */
 }
 
 .cp__sidebar-main-layout {

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

@@ -149,7 +149,7 @@
              [:p (t :file-rn/need-action)])
            [:p
             (ui/button
-             (str (t :file-rn/all-action) " (" (count rename-items) ")")
+             (t :file-rn/all-action (count rename-items))
              :on-click <rename-all
              :class "text-md p-2 mr-1")
             (t :file-rn/or-select-actions)

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

@@ -132,7 +132,7 @@
                                 nil
 
                                 (empty? matched-pages)
-                                (cons (str (t :new-page) ": " q) matched-pages)
+                                (cons q matched-pages)
 
                                ;; reorder, shortest and starts-with first.
                                 :else
@@ -143,8 +143,8 @@
                                                      matched-pages)]
                                   (if (gstring/caseInsensitiveStartsWith (first matched-pages) q)
                                     (cons (first matched-pages)
-                                          (cons  (str (t :new-page) ": " q) (rest matched-pages)))
-                                    (cons (str (t :new-page) ": " q) matched-pages))))]
+                                          (cons q (rest matched-pages)))
+                                    (cons q matched-pages))))]
             (ui/auto-complete
              matched-pages
              {:on-chosen   (page-handler/on-chosen-handler input id q pos format)
@@ -155,7 +155,9 @@
                                {:children
                                 [:div.flex
                                  (when (db-model/whiteboard-page? page-name) [:span.mr-1 (ui/icon "whiteboard" {:extension? true})])
-                                 (search/highlight-exact-query page-name q)]
+                                 [:div.flex.space-x-1
+                                  [:div (when-not (db/page-exists? page-name) (t :new-page))]
+                                  (search/highlight-exact-query page-name q)]]
                                 :open?           chosen?
                                 :manual?         true
                                 :fixed-position? true

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

@@ -152,7 +152,7 @@
        ;; wait for content load
        (and format
             (contains? (gp-config/text-formats) format))
-       (ui/loading "Loading ...")
+       (ui/loading)
 
        :else
        [:div (t :file/format-not-supported (name format))])]))

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

@@ -598,7 +598,7 @@
 
     [:div.version-list
      (if loading?
-       [:div.p-4 (ui/loading "Loading...")]
+       [:div.p-4 (ui/loading)]
        (for [version version-files]
          (let [version-uuid (get-version-key version)
                local?       (some? (:relative-path version))]
@@ -704,7 +704,7 @@
 
      ;; ready loading
      [:div.flex.items-center.h-full.justify-center.w-full.absolute.ready-loading
-      (ui/loading "Loading...")]]))
+      (ui/loading)]]))
 
 (defn pick-page-histories-panel [graph-uuid page-name]
   (fn []
@@ -789,7 +789,7 @@
    [:div.cloud-tip.rounded-md.mt-6.py-4
     [:div.items-center.opacity-90.flex.justify-center
      [:span.pr-2.flex (ui/icon "bell-ringing" {:class "font-semibold"})]
-     [:strong "Logseq Sync is still in Beta and we're working on a Pro plan!"]]
+     [:strong "Logseq Sync is still in Beta and we're working on a Pro plan!"]]]
 
     ;; [:ul.flex.py-6.px-4
     ;;  [:li.it
@@ -802,7 +802,7 @@
     ;;  [:li.it
     ;;   [:h1.dark:text-white "50G"]
     ;;   [:h2 "Total Storage"]]]
-    ]
+    
 
    [:div.pt-6.flex.justify-end.space-x-2
     (ui/button "Done" :on-click close-fn)]])

+ 7 - 7
src/main/frontend/components/header.cljs

@@ -58,7 +58,7 @@
   [{:keys [on-click]}]
   (ui/with-shortcut :ui/toggle-left-sidebar "bottom"
     [:button.#left-menu.cp__header-left-menu.button.icon
-     {:title "Toggle left menu"
+     {:title (t :header/toggle-left-sidebar)
       :on-click on-click}
      (ui/icon "menu-2" {:size ui/icon-size})]))
 
@@ -86,7 +86,7 @@
      (fn [{:keys [toggle-fn]}]
        [:button.button.icon.toolbar-dots-btn
         {:on-click toggle-fn
-         :title "More"}
+         :title (t :header/more)}
         (ui/icon "dots" {:size ui/icon-size})])
      (->>
       [(when (state/enable-editing?)
@@ -134,7 +134,7 @@
           :icon (ui/icon "bulb")})
 
        (when (and (state/sub :auth/id-token) (user-handler/logged-in?))
-         {:title (str (t :logout) " (" (user-handler/email) ")")
+         {:title (t :logout-user (user-handler/email))
           :options {:on-click #(user-handler/logout)}
           :icon  (ui/icon "logout")})]
       (concat page-menu-and-hr)
@@ -148,12 +148,12 @@
 
    (ui/with-shortcut :go/backward "bottom"
      [:button.it.navigation.nav-left.button.icon
-      {:title "Go back" :on-click #(js/window.history.back)}
+      {:title (t :header/go-back) :on-click #(js/window.history.back)}
       (ui/icon "arrow-left" {:size ui/icon-size})])
 
    (ui/with-shortcut :go/forward "bottom"
      [:button.it.navigation.nav-right.button.icon
-      {:title "Go forward" :on-click #(js/window.history.forward)}
+      {:title (t :header/go-forward) :on-click #(js/window.history.forward)}
       (ui/icon "arrow-right" {:size ui/icon-size})])])
 
 (rum/defc updater-tips-new-version
@@ -218,13 +218,13 @@
          (when-not (or (state/home?) custom-home-page? (state/whiteboard-dashboard?))
            (ui/with-shortcut :go/backward "bottom"
              [:button.it.navigation.nav-left.button.icon.opacity-70
-              {:title "Go back" :on-click #(js/window.history.back)}
+              {:title (t :header/go-back) :on-click #(js/window.history.back)}
               (ui/icon "chevron-left" {:size 26})]))
          ;; search button for non-mobile
          (when current-repo
            (ui/with-shortcut :go/search "right"
              [:button.button.icon#search-button
-              {:title "Search"
+              {:title (t :header/search)
                :on-click #(do (when (or (mobile-util/native-android?)
                                         (mobile-util/native-iphone?))
                                 (state/set-left-sidebar-open! false))

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

@@ -16,7 +16,7 @@
                               [:span.mr-1 (t :help/forum-community)]
                               (ui/icon "message-circle" {:style {:font-size 20}})]
          list
-         [{:title "Usage"
+         [{:title (t :help/title-usage)
            :children [[[:a
                         {:on-click (fn [] (state/sidebar-add-block! (state/get-current-repo) "shortcut-settings" :shortcut-settings))}
                         [:div.flex-row.inline-flex.items-center
@@ -26,21 +26,21 @@
                       [(t :help/start) "https://docs.logseq.com/#/page/tutorial"]
                       ["FAQ" "https://docs.logseq.com/#/page/faq"]]}
 
-          {:title "Community"
+          {:title (t :help/title-community)
            :children [[(t :help/awesome-logseq) "https://github.com/logseq/awesome-logseq"]
                       [(t :help/blog) "https://blog.logseq.com"]
                       [discourse-with-icon "https://discuss.logseq.com"]]}
 
-          {:title "Development"
+          {:title (t :help/title-development)
            :children [[(t :help/roadmap) "https://trello.com/b/8txSM12G/roadmap"]
                       [(t :help/bug) "https://github.com/logseq/logseq/issues/new?labels=from:in-app&template=bug_report.yaml"]
                       [(t :help/feature) "https://discuss.logseq.com/c/feature-requests/"]
                       [(t :help/changelog) "https://docs.logseq.com/#/page/changelog"]]}
 
-          {:title "About"
+          {:title (t :help/title-about)
            :children [[(t :help/about) "https://blog.logseq.com/about/"]]}
 
-          {:title "Terms"
+          {:title (t :help/title-terms)
            :children [[(t :help/privacy) "https://blog.logseq.com/privacy-policy/"]
                       [(t :help/terms) "https://blog.logseq.com/terms/"]]}]]
 

+ 18 - 20
src/main/frontend/components/page.cljs

@@ -208,8 +208,7 @@
          [:ul.mt-2
           (for [[original-name name] (sort-by last pages)]
             [:li {:key (str "tagged-page-" name)}
-             [:a {:href (rfe/href :page {:name name})}
-              original-name]])]
+             (component-block/page-cp {} {:block/name name :block/original-name original-name})])]
          {:default-collapsed? false})]])))
 
 (rum/defc page-title-editor < rum/reactive
@@ -219,6 +218,11 @@
                              (util/page-name-sanity-lc @*title-value))
                        (db/page-exists? page-name)
                        (db/page-exists? @*title-value))
+        rollback-fn #(do
+                       (reset! *title-value old-name)
+                       (gobj/set (rum/deref input-ref) "value" old-name)
+                       (reset! *edit? true)
+                       (.focus (rum/deref input-ref)))
         confirm-fn (fn []
                      (let [new-page-name (string/trim @*title-value)]
                        (ui/make-confirm-modal
@@ -229,16 +233,7 @@
                                           (close-fn)
                                           (page-handler/rename! (or title page-name) @*title-value)
                                           (reset! *edit? false))
-                         :on-cancel     (fn []
-                                          (reset! *title-value old-name)
-                                          (gobj/set (rum/deref input-ref) "value" old-name)
-                                          (reset! *edit? true)
-                                          (.focus (rum/deref input-ref)))})))
-        rollback-fn #(do
-                       (reset! *title-value old-name)
-                       (gobj/set (rum/deref input-ref) "value" old-name)
-                       (reset! *edit? false)
-                       (when-not untitled? (notification/show! "Illegal page name, can not rename!" :warning)))
+                         :on-cancel     rollback-fn})))
         blur-fn (fn [e]
                   (when (gp-util/wrapped-by-quotes? @*title-value)
                     (swap! *title-value gp-util/unquote-string)
@@ -248,13 +243,16 @@
                     (reset! *edit? false)
 
                     (string/blank? @*title-value)
-                    (rollback-fn)
+                    (do (when-not untitled? (notification/show! (t :page/illegal-page-name) :warning))
+                        (rollback-fn))
 
-                    (and (collide?) whiteboard-page?)
-                    (notification/show! (str "Page “" @*title-value "” already exists!") :error)
+                    (and (collide?) (or whiteboard-page? (model/whiteboard-page? @*title-value)))
+                    (do (notification/show! (t :page/page-already-exists @*title-value) :error)
+                        (rollback-fn))
 
                     (and (date/valid-journal-title? @*title-value) whiteboard-page?)
-                    (notification/show! (str "Whiteboard page cannot be renamed with journal titles!") :error)
+                    (do (notification/show! (t :page/whiteboard-to-journal-error) :error)
+                        (rollback-fn))
 
                     untitled?
                     (page-handler/rename! (or title page-name) @*title-value)
@@ -821,7 +819,7 @@
       [: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 :remove-orphaned-pages)
           (t :page/delete-confirmation))]]]
 
      [:table.table-auto.cp__all_pages_table.mt-4
@@ -857,7 +855,7 @@
                     (close-fn)
                     (doseq [page-name (map :block/name pages)]
                       (page-handler/delete! page-name #()))
-                    (notification/show! (str (t :tips/all-done) "!") :success)
+                    (notification/show! (t :tips/all-done) :success)
                     (js/setTimeout #(refresh-fn) 200)))]]))
 
 (rum/defc pagination
@@ -1042,7 +1040,7 @@
          [:div.r.flex.items-center.justify-between
           [:div
            (ui/tippy
-            {:html  [:small (str (t :page/show-whiteboards) " ?")]
+            {:html  [:small (t :page/show-whiteboards)]
              :arrow true}
             [:a.button.whiteboard
              {:class    (util/classnames [{:active (boolean @*whiteboard?)}])
@@ -1050,7 +1048,7 @@
              (ui/icon "whiteboard" {:extension? true :style {:fontSize ui/icon-size}})])]
           [:div
            (ui/tippy
-            {:html  [:small (str (t :page/show-journals) " ?")]
+            {:html  [:small (t :page/show-journals)]
              :arrow true}
             [:a.button.journal
              {:class    (util/classnames [{:active (boolean @*journal?)}])

+ 100 - 27
src/main/frontend/components/plugins.cljs

@@ -5,6 +5,7 @@
             [frontend.context.i18n :refer [t]]
             [frontend.ui :as ui]
             [frontend.handler.ui :as ui-handler]
+            [frontend.handler.editor :as editor-handler]
             [frontend.handler.plugin-config :as plugin-config-handler]
             [frontend.handler.common.plugin :as plugin-common-handler]
             [frontend.search :as search]
@@ -191,10 +192,7 @@
   (ui/admonition
     :warning
     [:p.text-md
-     "Plugins can access your graph and your local files, issue network requests.
-       They can also cause data corruption or loss. We're working on proper access rules for your graphs.
-       Meanwhile, make sure you have regular backups of your graphs and only install the plugins when you can read and
-       understand the source code."]))
+     (t :plugin/security-warning)]))
 
 (rum/defc card-ctls-of-market < rum/static
   [item stat installed? installing-or-updating?]
@@ -273,7 +271,7 @@
         (if installing-or-updating?
           (t :plugin/updating)
           (if new-version
-            (str (t :plugin/update) " 👉 " new-version)
+            [:span (t :plugin/update) " 👉 " new-version]
             (t :plugin/check-update)))]])
 
     (ui/toggle (not disabled?)
@@ -365,7 +363,7 @@
                     (.focus target))}
       (ui/icon "x")])
    [:input.form-input.is-small
-    {:placeholder "Search plugins"
+    {:placeholder (t :plugin/search-plugin)
      :ref         *search-ref
      :auto-focus  true
      :on-key-down (fn [^js e]
@@ -459,6 +457,22 @@
                                 (state/set-state! [:electron/user-cfgs :settings/agent] opts)
                                 (state/close-sub-modal! :https-proxy-panel))))]]]))
 
+(rum/defc auto-check-for-updates-control
+  []
+  (let [[enabled, set-enabled!] (rum/use-state (plugin-handler/get-enabled-auto-check-for-updates?))
+        text (t :plugin/auto-check-for-updates)]
+
+    [:div.flex.items-center.justify-between.px-4.py-2
+     {:on-click (fn []
+                  (let [t (not enabled)]
+                    (set-enabled! t)
+                    (plugin-handler/set-enabled-auto-check-for-updates t)
+                    (notification/show!
+                      [:span text [:strong.pl-1 (if t "ON" "OFF")] "!"]
+                      (if t :success :info))))}
+     [:span.pr-3.opacity-80 text]
+     (ui/toggle enabled #() true)]))
+
 (rum/defc ^:large-vars/cleanup-todo panel-control-tabs < rum/static
   [search-key *search-key category *category
    sort-by *sort-by filter-by *filter-by total-nums
@@ -557,7 +571,7 @@
               :options {:on-click #(reset! *sort-by :stars)}
               :icon    (ui/icon (aim-icon :stars))}
 
-             {:title   (str (t :plugin/title) " (A - Z)")
+             {:title   (t :plugin/title "A - Z")
               :options {:on-click #(reset! *sort-by :letters)}
               :icon    (ui/icon (aim-icon :letters))}])
           {}))
@@ -585,14 +599,18 @@
 
                 (when (state/developer-mode?)
                   [{:hr true}
-                   {:title   [:span.flex.items-center (ui/icon "file-code") "Open Preferences"]
+                   {:title   [:span.flex.items-center (ui/icon "file-code") (t :plugin/open-preferences)]
                     :options {:on-click
                               #(p/let [root (plugin-handler/get-ls-dotdir-root)]
                                  (js/apis.openPath (str root "/preferences.json")))}}
-                   {:title   [:span.flex.items-center (ui/icon "bug") "Open\u00A0" [:code "~/.logseq"]]
+                   {:title   [:span.flex.items-center.whitespace-nowrap.space-x-1 (ui/icon "bug") (t :plugin/open-logseq-dir) [:code "~/.logseq"]]
                     :options {:on-click
                               #(p/let [root (plugin-handler/get-ls-dotdir-root)]
-                                 (js/apis.openPath root))}}]))
+                                 (js/apis.openPath root))}}])
+
+                [{:hr true :key "dropdown-more"}
+                 {:title   (auto-check-for-updates-control)
+                  :options {:no-padding? true}}])
         {})
 
       ;; developer
@@ -730,7 +748,7 @@
        [:p.flex.justify-center.py-20 svg/loading]
 
        @*error
-       [:p.flex.justify-center.pt-20.opacity-50 "Remote error: " (.-message @*error)]
+       [:p.flex.justify-center.pt-20.opacity-50 (t :plugin/remote-error) (.-message @*error)]
 
        :else
        [:div.cp__plugins-marketplace-cnt
@@ -895,7 +913,7 @@
                 [:span.opacity-30.hover:opacity-80 (ui/icon "info-circle")]))]])]
 
        ;; all done
-       [:div.py-4 [:strong.text-4xl "\uD83C\uDF89 All updated!"]])
+       [:div.py-4 [:strong.text-4xl (str "\uD83C\uDF89 " (t :plugin/all-updated))]])
 
      ;; actions
      (when (seq updates)
@@ -1047,7 +1065,11 @@
                       [:span (t :plugin/found-updates)] (ui/point "bg-red-600" 5 {:style {:margin-top 2}})]
             :options {:on-click #(open-waiting-updates-modal!)
                       :class    "extra-item"}
-            :icon    (ui/icon "download")})])
+            :icon    (ui/icon "download")})]
+
+        [{:hr true :key "dropdown-more"}
+         {:title (auto-check-for-updates-control)
+          :options {:no-padding? true}}])
       {:trigger-class "toolbar-plugins-manager-trigger"})))
 
 (rum/defc header-ui-items-list-wrap
@@ -1116,14 +1138,61 @@
             (let [updates-coming (state/sub :plugin/updates-coming)]
               (toolbar-plugins-manager-list updates-coming items)))]]))))
 
-(rum/defcs hook-ui-fenced-code < rum/reactive
-  [_state content {:keys [render edit] :as _opts}]
+(rum/defc hook-ui-fenced-code
+  [block content {:keys [render edit] :as _opts}]
 
-  [:div
-   {:on-mouse-down (fn [e] (when (false? edit) (util/stop e)))
-    :class         (util/classnames [{:not-edit (false? edit)}])}
-   (when (fn? render)
-     (js/React.createElement render #js {:content content}))])
+  (let [[content1 set-content1!] (rum/use-state content)
+        [editor-active? set-editor-active!] (rum/use-state (string/blank? content))
+        *cm (rum/use-ref nil)
+        *el (rum/use-ref nil)]
+
+    (rum/use-effect!
+      #(set-content1! content)
+      [content])
+
+    (rum/use-effect!
+      (fn []
+        (some-> (rum/deref *el)
+                (.closest ".ui-fenced-code-wrap")
+                (.-classList)
+                (#(if editor-active?
+                    (.add % "is-active")
+                    (.remove % "is-active"))))
+        (when-let [cm (rum/deref *cm)]
+          (.refresh cm)
+          (.focus cm)
+          (.setCursor cm (.lineCount cm) (count (.getLine cm (.lastLine cm))))))
+      [editor-active?])
+
+    (rum/use-effect!
+      (fn []
+        (let [t (js/setTimeout
+                  #(when-let [^js cm (some-> (rum/deref *el)
+                                             (.closest ".ui-fenced-code-wrap")
+                                             (.querySelector ".CodeMirror")
+                                             (.-CodeMirror))]
+                     (rum/set-ref! *cm cm)
+                     (doto cm
+                       (.on "change" (fn []
+                                       (some-> cm (.getDoc) (.getValue) (set-content1!))))))
+                  ;; wait for the cm loaded
+                  1000)]
+          #(js/clearTimeout t)))
+      [])
+
+    [:div.ui-fenced-code-result
+     {:on-mouse-down (fn [e] (when (false? edit) (util/stop e)))
+      :class         (util/classnames [{:not-edit (false? edit)}])
+      :ref           *el}
+     [:<>
+      [:span.actions
+       {:on-mouse-down #(util/stop %)}
+       (ui/button (ui/icon "square-toggle-horizontal" {:size 14})
+                  :on-click #(set-editor-active! (not editor-active?)))
+       (ui/button (ui/icon "source-code" {:size 14})
+                  :on-click #(editor-handler/edit-block! block (count content1) (:block/uuid block)))]
+      (when (fn? render)
+        (js/React.createElement render #js {:content content1}))]]))
 
 (rum/defc plugins-page
   []
@@ -1183,7 +1252,7 @@
         [sub-content, _set-sub-content!] (rum-utils/use-atom *updates-sub-content)
         notify! (fn [content status]
                   (if auto-checking?
-                    (println "Plugin Updates: " content)
+                    (println (t :plugin/list-of-updates) content)
                     (let [cb #(plugin-handler/cancel-user-checking!)]
                       (try
                         (set-uid (notification/show! content status false uid nil cb))
@@ -1195,7 +1264,7 @@
         (if check-pending?
           (notify!
             [:div
-             [:div (str "Checking for plugin updates ...")]
+             [:div (t :plugin/checking-for-updates)]
              (when sub-content [:p.opacity-60 sub-content])]
             (ui/loading ""))
           (when uid (notification/clear! uid))))
@@ -1206,9 +1275,11 @@
       (fn []
         (when online?
           (let [last-updates (storage/get :lsp-last-auto-updates)]
-            (when (or (not (number? last-updates))
-                      ;; interval 12 hours
-                      (> (- (js/Date.now) last-updates) (* 60 60 12 1000)))
+            (when (and (not (false? last-updates))
+                       (or (true? last-updates)
+                           (not (number? last-updates))
+                           ;; interval 12 hours
+                           (> (- (js/Date.now) last-updates) (* 60 60 12 1000))))
               (js/setTimeout
                 (fn []
                   (plugin-handler/auto-check-enabled-for-updates!)
@@ -1237,7 +1308,8 @@
 
     [:div.cp__plugins-settings.cp__settings-main
      [:header
-      [:h1.title (ui/icon "puzzle") (str " " (or title (t :settings-of-plugins)))]]
+      [:h1.title (ui/icon "puzzle" {:size 22})
+       [:strong (or title (t :settings-of-plugins))]]]
 
      [:div.cp__settings-inner.md:flex
       {:class (util/classnames [{:no-aside (not nav?)}])}
@@ -1247,7 +1319,7 @@
            [:ul.settings-plugin-list
             (for [{:keys [id name title icon]} plugins]
               [:li
-               {:class (util/classnames [{:active (= id focused)}])}
+               {:key id :class (util/classnames [{:active (= id focused)}])}
                [:a.flex.items-center.settings-plugin-item
                 {:data-id  id
                  :on-click #(do (state/set-state! :plugin/focused-settings id))}
@@ -1332,4 +1404,5 @@
       [:div.settings-modal.of-plugins
        (focused-settings-content title)])
     {:center? false
+     :label   "plugin-settings-modal"
      :id      "ls-focused-settings-modal"}))

+ 65 - 16
src/main/frontend/components/plugins.css

@@ -482,6 +482,15 @@
   }
 
   &-settings {
+    > header {
+      padding: 8px 12px;
+      border-bottom: 1px solid var(--ls-quaternary-background-color);
+
+      h1 {
+        @apply flex items-center text-[22px] m-0 space-x-1;
+      }
+    }
+
     &-inner {
       position: relative;
       padding: 10px 0 20px;
@@ -598,25 +607,23 @@
       }
     }
 
-    aside {
-      max-height: 70vh;
-      overflow: auto;
-      margin-bottom: -17px;
+    .cp__settings-inner {
+      aside {
+        @apply max-h-[70vh] overflow-auto mb-[-17px] p-3;
 
-      ul {
-        img.icon {
-          height: 24px;
-          width: 24px;
-        }
+        ul {
+          @apply list-none p-0 m-0;
 
-        li {
-          strong {
-            font-weight: 400;
-            overflow: hidden;
-            height: 22px;
+          img.icon {
+            @apply w-[24px] h-[24px];
+          }
 
-            text-overflow: ellipsis;
-            white-space: nowrap;
+          li {
+            @apply p-1.5 rounded;
+
+            strong {
+              @apply overflow-hidden text-ellipsis whitespace-nowrap;
+            }
           }
         }
       }
@@ -898,6 +905,42 @@
   }
 }
 
+.ui-fenced-code {
+  &-wrap {
+    &:not(.is-active) {
+      .ui-fenced-code-editor {
+        display: none !important;
+      }
+    }
+
+    &.is-active {
+      .ui-fenced-code-result {
+        @apply pt-2;
+      }
+    }
+  }
+
+  &-result {
+    @apply relative;
+
+    > .actions {
+      @apply absolute top-2 right-1 z-10 opacity-0 transition-opacity;
+
+      .ui__button {
+        font-size: 13px;
+        padding: 4px;
+        margin-left: 6px;
+      }
+    }
+
+    &:hover {
+      > .actions {
+        @apply opacity-100;
+      }
+    }
+  }
+}
+
 .lsp-frame-readme {
   margin: -2rem;
   min-height: 75vh;
@@ -939,6 +982,12 @@ html[data-theme='dark'] {
   }
 }
 
+.ui__modal[label=plugin-settings-modal] {
+  .ui__modal-close-wrap {
+    padding-top: 14px;
+  }
+}
+
 .ui__modal[label=plugins-dashboard] {
   .panel-content {
     overflow-y: auto;

+ 0 - 1
src/main/frontend/components/query.cljs

@@ -59,7 +59,6 @@
                                      (:block/name (ffirst result))
                                      (:block/uuid (first (second (first result))))
                                      true)]
-    (println "this should be a function" inline)
     (if @*query-error
       (do
         (log/error :exception @*query-error)

+ 2 - 0
src/main/frontend/components/query/result.cljs

@@ -80,5 +80,7 @@
                          (dissoc result nil)
                          result))
                      transformed-query-result)]
+        (when-let [query-result (:query-result config)]
+          (reset! query-result result))
         (when query-atom
           (util/safe-with-meta result (meta @query-atom))))))

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

@@ -107,7 +107,7 @@
        [:div.pl-1.content.mt-3
 
         [:div
-         [:h2.text-lg.font-medium.my-4 (str (t :graph/local-graphs) ":")]
+         [:h2.text-lg.font-medium.my-4 (t :graph/local-graphs)]
          (when (seq local-graphs)
            (repos-inner local-graphs))
 
@@ -123,7 +123,7 @@
           [:div
            [:hr]
            [:div.flex.align-items.justify-between
-            [:h2.text-lg.font-medium.my-4 (str (t :graph/remote-graphs) ":")]
+            [:h2.text-lg.font-medium.my-4 (t :graph/remote-graphs)]
             [:div
              (ui/button
               [:span.flex.items-center "Refresh"

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

@@ -29,7 +29,7 @@
   (when-not (util/sm-breakpoint?)
     (ui/with-shortcut :ui/toggle-right-sidebar "left"
       [:button.button.icon.toggle-right-sidebar
-       {:title "Toggle right sidebar"
+       {:title (t :right-side-bar/toggle-right-sidebar)
         :on-click ui-handler/toggle-right-sidebar!}
        (ui/icon "layout-sidebar-right" {:size 20})])))
 
@@ -56,7 +56,7 @@
 (rum/defc shortcut-settings
   []
   [:div.contents.flex-col.flex.ml-3
-   (shortcut/shortcut {:show-title? false})])
+   (shortcut/shortcut-page {:show-title? false})])
 
 (defn- block-with-breadcrumb
   [repo block idx sidebar-key ref?]
@@ -123,11 +123,11 @@
     [(t :right-side-bar/help) (onboarding/help)]
 
     :page-graph
-    [(str (t :right-side-bar/page-graph))
+    [(t :right-side-bar/page-graph)
      (page/page-graph)]
 
     :history
-    [(str (t :right-side-bar/history))
+    [(t :right-side-bar/history)
      (history)]
 
     :block-ref

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

@@ -257,8 +257,8 @@
    {:name icon
     :class "highlight"
     :extension? true}
-   [:div.text.font-bold (str label ": ")
-    [:span.ml-1 name]]))
+   [:div.text.font-bold label
+    [:span.ml-2 name]]))
 
 (defn- search-item-render
   [search-q {:keys [type data alias]}]
@@ -312,7 +312,7 @@
 
                                 :else
                                 (do (log/error "search result with non-existing uuid: " data)
-                                    (str "Cache is outdated. Please click the 'Re-index' button in the graph's dropdown menu."))))])
+                                    (t :search/cache-outdated))))])
 
        :page-content
        (let [{:block/keys [snippet uuid]} data  ;; content here is normalized
@@ -327,7 +327,7 @@
                                 (if page
                                   (page-content-search-result-item repo uuid format snippet search-q search-mode)
                                   (do (log/error "search result with non-existing uuid: " data)
-                                      (str "Cache is outdated. Please click the 'Re-index' button in the graph's dropdown menu."))))]))
+                                      (t :search/cache-outdated))))]))
 
        nil)]))
 
@@ -396,11 +396,11 @@
   [in-page-search?]
   [:div.recent-search
    [:div.wrap.px-4.pb-2.text-sm.opacity-70.flex.flex-row.justify-between.align-items.mx-1.sm:mx-0
-    [:div "Recent search:"]
+    [:div (t :search/recent)]
     [:div.hidden.md:flex
      (ui/with-shortcut :go/search-in-page "bottom"
        [:div.flex-row.flex.align-items
-        [:div.mr-3.flex "Search blocks in page:"]
+        [:div.mr-3.flex (t :search/blocks-in-page)]
         [:div.flex.items-center
          (ui/toggle in-page-search?
                     (fn [_value]

+ 233 - 31
src/main/frontend/components/settings.cljs

@@ -1,37 +1,39 @@
 (ns frontend.components.settings
   (:require [clojure.string :as string]
-            [frontend.components.svg :as svg]
-            [frontend.components.plugins :as plugins]
+            [electron.ipc :as ipc]
             [frontend.components.assets :as assets]
+            [frontend.components.conversion :as conversion-component]
+            [frontend.components.file-sync :as fs]
+            [frontend.components.plugins :as plugins]
+            [frontend.components.svg :as svg]
             [frontend.config :as config]
             [frontend.context.i18n :refer [t]]
-            [frontend.storage :as storage]
-            [frontend.spec.storage :as storage-spec]
             [frontend.date :as date]
+            [frontend.db :as db]
             [frontend.dicts :as dicts]
             [frontend.handler :as handler]
             [frontend.handler.config :as config-handler]
+            [frontend.handler.file-sync :as file-sync-handler]
+            [frontend.handler.global-config :as global-config-handler]
             [frontend.handler.notification :as notification]
+            [frontend.handler.plugin :as plugin-handler]
             [frontend.handler.route :as route-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.user :as user-handler]
-            [frontend.handler.plugin :as plugin-handler]
-            [frontend.handler.file-sync :as file-sync-handler]
-            [frontend.handler.global-config :as global-config-handler]
+            [frontend.mobile.util :as mobile-util]
             [frontend.modules.instrumentation.core :as instrument]
             [frontend.modules.shortcut.data-helper :as shortcut-helper]
+            [frontend.spec.storage :as storage-spec]
             [frontend.state :as state]
+            [frontend.storage :as storage]
             [frontend.ui :as ui]
-            [electron.ipc :as ipc]
-            [promesa.core :as p]
             [frontend.util :refer [classnames web-platform?] :as util]
             [frontend.version :refer [version]]
             [goog.object :as gobj]
+            [goog.string :as gstring]
+            [promesa.core :as p]
             [reitit.frontend.easy :as rfe]
-            [rum.core :as rum]
-            [frontend.mobile.util :as mobile-util]
-            [frontend.db :as db]
-            [frontend.components.conversion :as conversion-component]))
+            [rum.core :as rum]))
 
 (defn toggle
   [label-for name state on-toggle & [detail-text]]
@@ -57,19 +59,19 @@
        [:div (cond
                (mobile-util/native-android?)
                (ui/button
-                "Check for updates"
+                (t :settings-page/check-for-updates)
                 :class "text-sm p-1 mr-1"
-                :href "https://github.com/logseq/logseq/releases" )
+                :href "https://github.com/logseq/logseq/releases")
 
                (mobile-util/native-ios?)
                (ui/button
-                "Check for updates"
+                (t :settings-page/check-for-updates)
                 :class "text-sm p-1 mr-1"
-                :href "https://apps.apple.com/app/logseq/id1601013908" )
+                :href "https://apps.apple.com/app/logseq/id1601013908")
 
                (util/electron?)
                (ui/button
-                (if update-pending? "Checking ..." "Check for updates")
+                (if update-pending? (t :settings-page/checking) (t :settings-page/check-for-updates))
                 :class "text-sm p-1 mr-1"
                 :disabled update-pending?
                 :on-click #(js/window.apis.checkForUpdates false))
@@ -78,7 +80,7 @@
                nil)]
 
        [:div.text-sm.cursor
-        {:title (str "Revision: " config/revision)
+        {:title (str (t :settings-page/revision) config/revision)
          :on-click (fn []
                      (notification/show! [:div "Current Revision: "
                                           [:a {:target "_blank"
@@ -91,14 +93,14 @@
        [:a.text-sm.fade-link.underline.inline
         {:target "_blank"
          :href "https://docs.logseq.com/#/page/changelog"}
-        "What's new?"]]]
+        (t :settings-page/changelog)]]]
 
      (when-not (or update-pending?
                    (string/blank? type))
        [:div.update-state.text-sm
         (case type
           "update-not-available"
-          [:p "Your app is up-to-date 🎉"]
+          [:p (t :settings-page/app-updated)]
 
           "update-available"
           (let [{:keys [name url]} payload]
@@ -125,10 +127,10 @@
    {:style {:box-shadow "0 4px 20px 4px rgba(0, 20, 60, .1), 0 4px 80px -8px rgba(0, 20, 60, .2)"}}
    [:div {:style {:margin "12px" :max-width "500px"}}
     [:p.text-sm
-     "The left side shows outdenting with the default setting, and the right shows outdenting with logical outdenting enabled. "
+     (t :settings-page/preferred-outdenting-tip)
      [:a.text-sm
       {:target "_blank" :href "https://discuss.logseq.com/t/whats-your-preferred-outdent-behavior-the-direct-one-or-the-logical-one/978"}
-      "→ Learn more"]]
+      (t :settings-page/preferred-outdenting-tip-more)]]
     [:img {:src    "https://discuss.logseq.com/uploads/default/original/1X/e8ea82f63a5e01f6d21b5da827927f538f3277b9.gif"
            :width  500
            :height 500}]]])
@@ -139,7 +141,7 @@
    {:style {:box-shadow "0 4px 20px 4px rgba(0, 20, 60, .1), 0 4px 80px -8px rgba(0, 20, 60, .2)"}}
    [:div {:style {:margin "12px" :max-width "500px"}}
     [:p.text-sm
-     "This option controls whether to expand the block references automatically when zoom-in."]
+     (t :settings-page/auto-expand-block-refs-tip)]
     [:img {:src    "https://user-images.githubusercontent.com/28241963/225818326-118deda9-9d1e-477d-b0ce-771ca0bcd976.gif"
            :width  500
            :height 500}]]])
@@ -750,6 +752,196 @@
    {:left-label (t :settings-page/enable-whiteboards)
     :action (whiteboards-enabled-switcher enabled?)}))
 
+(rum/defc settings-account-usage-description [pro-account? graph-usage]
+  (let [count-usage (count graph-usage)
+        count-limit (if pro-account? 10 1)
+        count-percent (js/Math.round (/ count-usage count-limit 0.01))
+        storage-usage (->> (map :used-gbs graph-usage)
+                           (reduce + 0)) 
+        storage-usage-formatted (cond 
+                                  (zero? storage-usage) "0.0"
+                                  (< storage-usage 0.01) "Less than 0.01"
+                                  :else (gstring/format "%.2f" storage-usage))
+        ;; TODO: check logic on this. What are the rules around storage limits?  
+        ;; do we, and should we be able to, give individual users more storage?
+        ;; should that be on a per graph or per user basis?
+        default-storage-limit (if pro-account? 10 0.05)
+        storage-limit (->> (range 0 count-limit)
+                           (map #(get-in graph-usage [% :limit-gbs] default-storage-limit))
+                           (reduce + 0))
+        storage-percent (/ storage-usage storage-limit 0.01)
+        storage-percent-formatted (gstring/format "%.1f" storage-percent)]
+    [:div.text-sm
+     (when pro-account?
+       [:<>
+        (gstring/format "%s of %s synced graphs " count-usage count-limit)
+        [:strong.text-white (gstring/format "(%s%%)" count-percent)]
+        ", "]) 
+     (gstring/format "%sGB of %sGB total storage " storage-usage-formatted storage-limit)
+     [:strong.text-white (gstring/format "(%s%%)" storage-percent-formatted)]]))
+     ; storage-usage-formatted "GB of " storage-limit "GB total storage"
+     ; [:strong.text-white " (" storage-percent-formatted "%)"]]))
+
+
+(rum/defc settings-account-usage-graphs [_pro-account? graph-usage]
+  (when (< 0 (count graph-usage))
+   [:div.grid.gap-3 {:style {:grid-template-columns (str "repeat(" (count graph-usage) ", 1fr)")}}
+    (for [{:keys [name used-percent]} graph-usage
+          :let [color (if (<= 100 used-percent) "bg-red-500" "bg-blue-500")]]
+     [:div.rounded-full.w-full.h-2 {:class "bg-black/50" 
+                                    :tooltip name}
+      [:div.rounded-full.h-2 {:class color
+                              :style {:width (str used-percent "%") 
+                                      :min-width "0.5rem" 
+                                      :max-width "100%"}}]])]))
+  
+(rum/defc ^:large-vars/cleanup-todo settings-account < rum/reactive
+  []
+  (let [current-graph-uuid (state/sub-current-file-sync-graph-uuid)
+        graph-usage (state/get-remote-graph-usage)
+        current-graph-is-remote? ((set (map :uuid graph-usage)) current-graph-uuid)
+        logged-in? (user-handler/logged-in?)
+        user-info (state/get-user-info)
+        paid-user? (#{"active" "on_trial" "cancelled"} (:LemonStatus user-info))
+        gift-user? (some #{"pro"} (:UserGroups user-info))
+        pro-account? (or paid-user? gift-user?)
+        expiration-date (some-> user-info :LemonEndsAt date/parse-iso)
+        renewal-date (some-> user-info :LemonRenewsAt date/parse-iso)
+        has-subscribed? (some? (:LemonStatus user-info))]
+    [:div.panel-wrap.is-features.mb-8
+     [:div.mt-1.sm:mt-0.sm:col-span-2
+      (cond
+        logged-in?
+        [:div.grid.grid-cols-3.gap-8.pt-2
+         [:div "Current plan"]
+         [:div.col-span-2 
+          [:div {:class "w-full bg-gray-500/10 rounded-lg p-4 flex flex-col gap-4"}
+           [:div.flex.gap-4.items-center
+            (if pro-account?
+              [:div.flex-1 "Pro"]
+              [:div.flex-1 "Free"])
+            (cond 
+              has-subscribed?
+              (ui/button "Manage plan" {:class "p-1 h-8 justify-center"
+                                        :disabled true
+                                        :icon "upload"})
+                                         ; :on-click user-handler/upgrade})
+              (not pro-account?)
+              (ui/button "Upgrade plan" {:class "p-1 h-8 justify-center"
+                                         :icon "upload"
+                                         :on-click user-handler/upgrade})
+              :else nil)]
+           (settings-account-usage-graphs pro-account? graph-usage)
+           (settings-account-usage-description pro-account? graph-usage)
+           (if current-graph-is-remote?
+             (ui/button "Deactivate syncing" {:class "p-1 h-8 justify-center"
+                                              :disabled true
+                                              :background "gray"
+                                              :icon "cloud-off"})
+             (ui/button "Activate syncing" {:class "p-1 h-8 justify-center"
+                                            :background "blue"
+                                            :icon "cloud"
+                                            :on-click #(fs/maybe-onboarding-show :sync-initiate)}))]]
+         (when has-subscribed?
+          [:<>
+           [:div "Billing"]
+           [:div.col-span-2.flex.flex-col.gap-4
+            (cond 
+              ;; If there is no expiration date, print the renewal date
+              (and renewal-date (nil? expiration-date)) 
+              [:div 
+               [:strong.font-semibold "Next billing date: " 
+                (date/get-locale-string renewal-date)]]
+              ;; If the expiration date is in the future, word it as such
+              (< (js/Date.) expiration-date) 
+              [:div
+               [:strong.font-semibold "Pro plan expires on: " 
+                (date/get-locale-string expiration-date)]]
+              ;; Otherwise, ind
+              :else 
+              [:div 
+               [:strong.font-semibold "Pro plan expired on: " 
+                (date/get-locale-string expiration-date)]])
+                               
+            [:div (ui/button "Open invoices" {:class "w-full h-8 p-1 justify-center" 
+                                              :disabled true 
+                                              :background "gray" 
+                                              :icon "receipt"})]]])
+         [:div "Profile"]
+         [:div.col-span-2.grid.grid-cols-2.gap-4
+          [:div.flex.flex-col.gap-2.box-border {:class "basis-1/2"}
+           [:label.text-sm.font-semibold "First name"]
+           [:input.rounded.border.px-2.py-1.box-border {:class "border-blue-500 bg-black/25 w-full"}]]
+          [:div.flex.flex-col.gap-2 {:class "basis-1/2"}
+           [:label.text-sm.font-semibold "Last name"]
+           [:input.rounded.border.px-2.py-1.box-border {:class "border-blue-500 bg-black/25 w-full"}]]
+          [:div.flex-1.flex.flex-col.gap-2.col-span-2
+           [:label.text-sm.font-semibold "Username"]
+           [:input.rounded.border.px-2.py-1.box-border {:class "border-blue-500 bg-black/25" 
+                                                        :value (user-handler/email)}]]]
+         [:div "Authentication"]
+         [:div.col-span-2
+          [:div.grid.grid-cols-2.gap-4
+           [:div (ui/button (t :logout) {:class "p-1 h-8 justify-center w-full"
+                                         :background "gray"
+                                         :icon "logout"
+                                         :on-click user-handler/logout})]
+           [:div (ui/button "Reset password" {:class "p-1 h-8 justify-center w-full"
+                                              :disabled true
+                                              :background "gray"
+                                              :icon "key"
+                                              :on-click user-handler/logout})]
+           [:div.col-span-2 (ui/button "Delete Account" {:class "p-1 h-8 justify-center w-full" 
+                                                         :disabled true
+                                                         :background "red"})]]]] 
+                                            
+        (not logged-in?)
+        [:div.grid.grid-cols-3.gap-8.pt-2
+         [:div "Authentication"]
+         [:div.col-span-2.flex.flex-wrap.gap-4
+          [:div.w-full.text-white "With a Logseq account, you can access cloud-based services like Logseq Sync and alpha/beta features."]
+          [:div.flex-1 (ui/button "Sign up" {:class "h-8 w-full text-center justify-center"
+                                             :on-click (fn []
+                                                         (state/close-settings!)
+                                                         (state/pub-event! [:user/login]))})]
+          [:div.flex-1 (ui/button (t :login) {:icon "login" 
+                                              :class "h-8 w-full text-center justify-center" 
+                                              :background "gray"
+                                              :on-click (fn []
+                                                          (state/close-settings!)
+                                                          (state/pub-event! [:user/login]))})]]
+         [:div.col-span-3.flex.flex-col.gap-4 {:class "bg-black/20 p-4 rounded-lg"}
+          [:div.flex.w-full.items-center
+           [:div {:class "w-1/2 text-lg"} 
+            "Discover the power of " 
+            [:strong {:class "text-white/80"} "Logseq Sync"]]
+           [:div {:class "w-1/2 bg-gradient-to-r from-white/10 to-transparent p-3 rounded-lg flex items-center gap-2 px-5 ml-5"} 
+            [:div.w-3.h-3.rounded-full.bg-green-500]
+            "Synced"]]
+          [:div.flex.w-full.gap-4
+           [:div {:class "w-1/2 bg-black/50 rounded-lg p-4 pt-10 relative flex flex-col gap-4"}
+            [:div.absolute.top-0.left-4.bg-gray-700.uppercase.px-2.py-1.rounded-b-lg.font-bold.text-xs "Free"]
+            [:div
+             [:strong.text-white.text-xl.font-normal "$0"]] 
+            [:div.text-white.font-bold {:class "h-[2.5rem] "} "Get started with basic syncing"]
+            [:ul.text-xs.list-none.m-0.flex.flex-col.gap-0.5
+             [:li "Unlimited unsynced graphs"]
+             [:li "1 synced graph (up to 50MB, notes only)"]
+             [:li "No asset syncing"]
+             [:li "Access to core Logseq features"]]]
+           [:div {:class "w-1/2 bg-black/50 rounded-lg p-4 pt-10 relative flex flex-col gap-4"}
+            [:div.absolute.top-0.left-4.bg-blue-700.uppercase.px-2.py-1.rounded-b-lg.font-bold.text-xs "Pro"]
+            [:div
+             [:strong.text-white.text-xl.font-normal "$10"] 
+             [:span.text-xs.font-base {:class "ml-0.5"} "/ month"]]
+            [:div.text-white.font-bold {:class "h-[2.5rem]"} "Unlock advanced syncing and more"]
+            [:ul.text-xs.list-none.m-0.flex.flex-col.gap-0.5
+             [:li "Unlimited unsynced graphs"]
+             [:li "10 synced graphs (up to 5GB each)"]
+             [:li "Sync assets up to 100MB per file"]
+             [:li "Early access to alpha/beta features"]
+             [:li "Upcoming cloud-based features, including Logseq Publish"]]]]]])]]))
+
 (rum/defc settings-features < rum/reactive
   []
   (let [current-repo (state/get-current-repo)
@@ -809,7 +1001,7 @@
           [:a.mx-1 {:href "https://blog.logseq.com/how-to-setup-and-use-logseq-sync/"
                     :target "_blank"}
            "here"]
-          "for instructions on how to set up and use Sync."]]])
+          "for instructions on how to set up and use Sync."]]])]))
 
      ;; (when-not web-platform?
      ;;   [:<>
@@ -820,10 +1012,12 @@
      ;;     {:class (when-not user-handler/alpha-user? "opacity-50 pointer-events-none cursor-not-allowed")}
      ;;     ;; features
      ;;     ]])
-     ]))
+     
+
+(def DEFAULT-ACTIVE-TAB-STATE (if config/ENABLE-SETTINGS-ACCOUNT-TAB [:account :account] [:general :general]))
 
 (rum/defcs settings
-  < (rum/local [:general :general] ::active)
+  < (rum/local DEFAULT-ACTIVE-TAB-STATE ::active)
     {:will-mount
      (fn [state]
        (state/load-app-user-cfgs)
@@ -841,15 +1035,18 @@
         *active (::active state)]
 
     [:div#settings.cp__settings-main
-     [:header
-      [:h1.title (t :settings)]]
 
      [:div.cp__settings-inner
 
       [:aside.md:w-64 {:style {:min-width "10rem"}}
+       [:header.cp__settings-header
+        (ui/icon "settings")
+        [:h1.cp__settings-modal-title (t :settings)]]
        [:ul.settings-menu
         (for [[label id text icon]
-              [[:general "general" (t :settings-page/tab-general) (ui/icon "adjustments")]
+              [(when config/ENABLE-SETTINGS-ACCOUNT-TAB
+                [:account "account" (t :settings-page/tab-account) (ui/icon "user-circle")])
+               [:general "general" (t :settings-page/tab-general) (ui/icon "adjustments")]
                [:editor "editor" (t :settings-page/tab-editor) (ui/icon "writing")]
 
                (when (util/electron?)
@@ -871,11 +1068,13 @@
               :on-click #(reset! *active [label (first @*active)])}
 
              [:a.flex.items-center.settings-menu-link
-             {:data-id id}
+              {:data-id id}
               icon
               [:strong text]]]))]]
 
       [:article
+       [:header.cp__settings-header
+        [:h1.cp__settings-category-title (name (first @*active))]]
 
        (case (first @*active)
 
@@ -885,6 +1084,9 @@
            (reset! *active [label label])
            nil)
 
+         :account 
+         (settings-account)
+
          :general
          (settings-general current-repo)
 

+ 57 - 40
src/main/frontend/components/settings.css

@@ -1,44 +1,76 @@
 .cp__settings {
-
   &-main {
-    > header {
-      padding: 10px;
-      padding-top: 0;
-      border-bottom: 1px solid var(--ls-quaternary-background-color);
+    aside {
+      @apply bg-gray-400/5 p-4;
+    }
 
-      h1 {
-        font-size: 22px;
-        margin: 0;
+    article {
+      @apply p-4 flex-1 min-h-[12rem] w-auto overflow-y-auto;
+      @apply md:max-h-[70vh] md:w-[40rem];
+      /* margin-right: -17px; */
+      /* margin-bottom: -17px; */
+
+      @screen md {
+        /* max-height: 70vh; */
+        /* width: 680px; */
       }
     }
+
+    aside > .cp__settings-header,
+    article > .cp__settings-header {
+      @apply h-10 py-2 flex flex-row items-center justify-start gap-2;
+    }
+
+    aside > .cp__settings-header {
+      @apply px-2;
+    }
+
+    aside > .cp__settings-header > .ui__icon {
+      @apply h-8 w-8 bg-gray-700/10 rounded grid place-items-center;
+    }
+
+    aside > .cp__settings-header > .ui__icon > svg {
+      @apply h-6 w-6;
+    }
+
+    h1.cp__settings-modal-title {
+      @apply text-2xl font-semibold lowercase;
+    }
+
+    h1.cp__settings-category-title {
+      @apply text-xl lowercase;
+    }
+
+    h1.cp__settings-modal-title:first-letter, 
+    h1.cp__settings-category-title:first-letter {
+      @apply uppercase;
+    }
+
+    .settings-menu {
+      @apply p-0 m-0 mt-4 pr-3; 
+    }
+
+    .settings-menu-item {
+      @apply list-none p-0 my-1.5 rounded;
+      @apply hover:bg-black/10;
+    }
+
+    .settings-menu-link {
+      @apply px-2 py-1.5 select-none; 
+      color: var(--ls-primary-text-color);
+    }
   }
 
   &-inner {
     @apply flex flex-col md:flex-row;
 
     > aside {
-      border-right: 0 solid var(--ls-quaternary-background-color);
-      border-bottom: 1px solid var(--ls-quaternary-background-color);
-
-      @screen md {
-        border-right: 1px solid var(--ls-quaternary-background-color);
-        border-bottom: 0 solid var(--ls-quaternary-background-color);
-      }
 
       ul {
-        padding: 12px 12px 12px 5px;
-        margin: 0;
 
         > li {
-          list-style: none;
-          padding: 0;
-          margin: 5px 0;
-          border-radius: 4px;
 
           > a {
-            padding: 10px;
-            user-select: none;
-            color: var(--ls-primary-text-color);
 
             > i {
               overflow: hidden;
@@ -65,21 +97,6 @@
       }
     }
 
-    > article {
-      flex: 1;
-      padding: 0 12px 12px;
-      min-height: 380px;
-      width: auto;
-      overflow: auto;
-      margin-right: -17px;
-      margin-bottom: -17px;
-
-      @screen md {
-        max-height: 70vh;
-        width: 680px;
-      }
-    }
-
     &.no-aside {
       > article {
         padding-left: 0;
@@ -87,7 +104,7 @@
     }
 
     .panel-wrap {
-      padding: 12px;
+      @apply p-1;
 
       @screen sm {
         width: 600px;

+ 90 - 44
src/main/frontend/components/shortcut.cljs

@@ -13,12 +13,15 @@
 
 (rum/defcs customize-shortcut-dialog-inner <
   (rum/local "")
+  (rum/local nil :rum/action)
   (shortcut/record!)
   [state k action-name current-binding]
-  (let [keypress (:rum/local state)
-        keyboard-shortcut (if (= "" @keypress) current-binding @keypress)]
-    [:div
-     [:div
+  (let [*keypress         (:rum/local state)
+        *action           (:rum/action state)
+        keypressed?       (not= "" @*keypress)
+        keyboard-shortcut (if-not keypressed? current-binding @*keypress)]
+    [:<>
+     [:div.sm:w-lsm
       [:p.mb-4 "Press any sequence of keys to set the shortcut for the " [:b action-name] " action."]
       [:p.mb-4.mt-4
        (ui/render-keyboard-shortcut (-> keyboard-shortcut
@@ -26,26 +29,30 @@
                                         (str/lower-case)
                                         (str/split  #" |\+")))
        " "
-       [:a.text-sm
-        {:style {:margin-left "12px"}
-         :on-click (fn []
-                     (dh/remove-shortcut k)
-                     (shortcut/refresh!)
-                     (swap! keypress (fn [] "")) ;; Clear local state
-                     )}
-        "Reset"]]]
+       (when keypressed?
+         [:a.text-sm
+          {:style    {:margin-left "12px"}
+           :on-click (fn []
+                       (dh/remove-shortcut k)
+                       (shortcut/refresh!)
+                       (swap! *keypress (constantly ""))          ;; Clear local state
+                       )}
+          "Reset"])]]
      [:div.cancel-save-buttons.text-right.mt-4
-      (ui/button "Save" :on-click state/close-modal!)
+      (ui/button "Save" :on-click (fn []
+                                    (reset! *action :save)
+                                    (state/close-modal!)))
       [:a.ml-4
        {:on-click (fn []
-                    (reset! keypress (dh/binding-for-storage current-binding))
+                    (reset! *keypress (dh/binding-for-storage current-binding))
+                    (reset! *action :cancel)
                     (state/close-modal!))} "Cancel"]]]))
 
 (defn customize-shortcut-dialog [k action-name displayed-binding]
   (fn [_]
     (customize-shortcut-dialog-inner k action-name displayed-binding)))
 
-(rum/defc shortcut-col [k binding configurable? action-name]
+(rum/defc shortcut-col [_category k binding configurable? action-name]
   (let [conflict?         (dh/potential-conflict? k)
         displayed-binding (dh/binding-for-display k binding)
         disabled?         (str/includes? displayed-binding "system default")]
@@ -61,28 +68,48 @@
                  (if disabled? "Cannot override system default" "Click to modify"))
         :background (if conflict? "pink" (when disabled? "gray"))
         :on-click (when-not disabled?
-                    #(state/set-modal! (customize-shortcut-dialog k action-name displayed-binding))))])))
-
-(rum/defc shortcut-table < rum/reactive
-  ([name]
-   (shortcut-table name false))
-  ([name configurable?]
-   (let [shortcut-config (rum/cursor-in
-                          state/state
-                          [:config (state/get-current-repo) :shortcuts])
-         _ (rum/react shortcut-config)]
-     [:div
-      [:table
-       [:thead
-        [:tr
-         [:th.text-left [:b (t name)]]
-         [:th.text-right]]]
-       [:tbody
-        (map (fn [[k {:keys [binding]}]]
-               [:tr {:key (str k)}
-                [:td.text-left (t (dh/decorate-namespace k))]
-                (shortcut-col k binding configurable? (t (dh/decorate-namespace k)))])
-          (dh/binding-by-category name))]]])))
+                    #(state/set-sub-modal!
+                       (customize-shortcut-dialog k action-name displayed-binding)
+                       {:center? true})))])))
+
+(rum/defcs shortcut-table
+  < rum/reactive
+    (rum/local true ::folded?)
+    {:will-mount (fn [state]
+                   (let [name (first (:rum/args state))]
+                     (cond-> state
+                             (contains? #{:shortcut.category/basics}
+                                        name)
+                             (-> ::folded? (reset! false) (do state)))))}
+  [state category configurable?]
+  (let [*folded? (::folded? state)
+        plugin?  (= category :shortcut.category/plugins)
+        _        (state/sub [:config (state/get-current-repo) :shortcuts])]
+    [:div.cp__shortcut-table-wrap
+     [:a.fold
+      {:on-click #(reset! *folded? (not @*folded?))}
+      (ui/icon (if @*folded? "chevron-left" "chevron-down"))]
+     [:table
+      [:thead
+       [:tr
+        [:th.text-left [:b (t category)]]
+        [:th.text-right]]]
+      (when-not @*folded?
+        [:tbody
+         (map (fn [[k {:keys [binding]}]]
+                (let [cmd   (dh/shortcut-cmd k)
+                      label (cond
+                              (string? (:desc cmd))
+                              [:<>
+                               [:code.text-xs (namespace k)]
+                               [:small.pl-1 (:desc cmd)]]
+
+                              (not plugin?) (-> k (dh/decorate-namespace) (t))
+                              :else (str k))]
+                  [:tr {:key (str k)}
+                   [:td.text-left.flex.items-center label]
+                   (shortcut-col category k binding configurable? label)]))
+              (dh/binding-by-category category))])]]))
 
 (rum/defc trigger-table []
   [:table
@@ -167,13 +194,9 @@
               [:td.text-right (get rendered name)]])
         list)]]))
 
-(rum/defc shortcut
-  [{:keys [show-title?]
-    :or {show-title? true}}]
-  [:div
-   (when show-title? [:h1.title (t :help/shortcut-page-title)])
-   (trigger-table)
-   (markdown-and-orgmode-syntax)
+(rum/defc keymap-tables
+  []
+  [:div.cp__keymap-tables
    (shortcut-table :shortcut.category/basics true)
    (shortcut-table :shortcut.category/navigating true)
    (shortcut-table :shortcut.category/block-editing true)
@@ -182,4 +205,27 @@
    (shortcut-table :shortcut.category/formatting true)
    (shortcut-table :shortcut.category/toggle true)
    (when (state/enable-whiteboards?) (shortcut-table :shortcut.category/whiteboard true))
+   (shortcut-table :shortcut.category/plugins true)
    (shortcut-table :shortcut.category/others true)])
+
+(rum/defc keymap-pane
+  []
+  (let [[ready?, set-ready!] (rum/use-state false)]
+    (rum/use-effect!
+      (fn [] (js/setTimeout #(set-ready! true) 32))
+      [])
+
+    [:div.cp__keymap-pane
+     [:h1.pb-2.text-3xl.pt-2 "Keymap"]
+     (if ready?
+       (keymap-tables)
+       [:p.flex.justify-center.py-20 (ui/loading "")])]))
+
+(rum/defc shortcut-page
+  [{:keys [show-title?]
+    :or {show-title? true}}]
+  [:div.cp__shortcut-page
+   (when show-title? [:h1.title (t :help/shortcut-page-title)])
+   (trigger-table)
+   (markdown-and-orgmode-syntax)
+   (keymap-tables)])

+ 28 - 0
src/main/frontend/components/shortcut.css

@@ -0,0 +1,28 @@
+.ui__modal {
+  &[label="keymap-manager"] {
+    .panel-content {
+      @apply m-[-16px];
+    }
+
+    @screen lg {
+      .panel-content {
+        width: 980px;
+      }
+    }
+  }
+}
+
+.cp__shortcut {
+  &-table-wrap {
+    @apply relative;
+
+    a.fold {
+      @apply absolute right-0 top-0 w-full pt-3 pr-3
+      pb-3 flex items-center justify-end select-none;
+
+      &:active {
+        @apply bg-white/50 opacity-60;
+      }
+    }
+  }
+}

+ 5 - 1
src/main/frontend/config.cljs

@@ -29,6 +29,10 @@
 
 (goog-define ENABLE-FILE-SYNC-PRODUCTION false)
 
+;; this is a feature flag to enable the account tab 
+;; when it launches (when pro plan launches) it should be removed
+(def ENABLE-SETTINGS-ACCOUNT-TAB false)
+
 (if ENABLE-FILE-SYNC-PRODUCTION
   (do (def FILE-SYNC-PROD? true)
       (def LOGIN-URL
@@ -472,7 +476,7 @@
 (defn get-current-repo-assets-root
   []
   (when-let [repo-dir (and (local-db? (state/get-current-repo))
-                            (get-repo-dir (state/get-current-repo)))]
+                           (get-repo-dir (state/get-current-repo)))]
     (path/path-join repo-dir "assets")))
 
 (defn get-custom-js-path

+ 17 - 5
src/main/frontend/date.cljs

@@ -62,11 +62,13 @@
    (tf/unparse custom-formatter date-time)))
 
 (defn get-locale-string
-  [s]
+  "Accepts a :date-time-no-ms string representation, or a cljs-time date object"
+  [input]
   (try
-    (->> (tf/parse (tf/formatters :date-time-no-ms) s)
-        (t/to-default-time-zone)
-        (tf/unparse (tf/formatter "MMM do, yyyy")))
+    (->> (cond->> input
+          (string? input) (tf/parse (tf/formatters :date-time-no-ms)))
+         (t/to-default-time-zone)
+         (tf/unparse (tf/formatter "MMM do, yyyy")))
     (catch :default _e
       nil)))
 
@@ -209,12 +211,22 @@
    (tf/formatter "yyyy-MM-dd HH:mm")
    (t/to-default-time-zone (tc/from-long n))))
 
+(def iso-parser (tf/formatter "yyyy-MM-dd'T'HH:mm:ss.SSSS'Z'"))
+(defn parse-iso [string]
+  (tf/parse iso-parser string))
+
 (comment
   (def default-formatter (tf/formatter "MMM do, yyyy"))
   (def zh-formatter (tf/formatter "YYYY年MM月dd日"))
 
-  (tf/show-formatters))
+  (tf/show-formatters)
 
   ;; :date 2020-05-31
   ;; :rfc822 Sun, 31 May 2020 03:00:57 Z
 
+  (let [info {:ExpireTime 1680781356,
+              :UserGroups [],
+              :LemonRenewsAt "2024-04-11T07:28:00.000000Z",
+              :LemonEndsAt nil,
+              :LemonStatus "active"}]
+    (->> info :LemonRenewsAt (tf/parse iso-parser) (< (js/Date.))))) 

+ 2 - 2
src/main/frontend/db/react.cljs

@@ -52,10 +52,10 @@
 (defonce query-state (atom {}))
 
 ;; Current dynamic component
-(def ^:dynamic *query-component*)
+(def ^:dynamic *query-component* nil)
 
 ;; Which reactive queries are triggered by the current component
-(def ^:dynamic *reactive-queries*)
+(def ^:dynamic *reactive-queries* nil)
 
 ;; component -> query-key
 (defonce query-components (atom {}))

+ 2 - 4
src/main/frontend/extensions/excalidraw.cljs

@@ -17,8 +17,7 @@
             [goog.object :as gobj]
             [goog.functions :refer [debounce]]
             [rum.core :as rum]
-            [frontend.mobile.util :as mobile-util]
-            [frontend.context.i18n :refer [t]]))
+            [frontend.mobile.util :as mobile-util]))
 
 (def excalidraw (r/adapt-class Excalidraw))
 
@@ -148,8 +147,7 @@
     (when (:file option)
       (cond
         db-restoring?
-        [:div.ls-center
-         (ui/loading (t :loading))]
+        [:div.ls-center (ui/loading)]
 
         (false? loading?)
         (draw-inner data option)

+ 2 - 3
src/main/frontend/extensions/latex.cljs

@@ -6,8 +6,7 @@
             [frontend.util :as util]
             [frontend.handler.plugin :refer [hook-extensions-enhancer-by-type] :as plugin-handler]
             [promesa.core :as p]
-            [goog.dom :as gdom]
-            [frontend.context.i18n :refer [t]]))
+            [goog.dom :as gdom]))
 
 ;; TODO: extracted to a rum mixin
 (defn loaded? []
@@ -62,7 +61,7 @@
   [id s block? _display?]
   (let [loading? (rum/react *loading?)]
     (if loading?
-      (ui/loading (t :loading))
+      (ui/loading)
       (let [element (if block?
                       :div.latex
                       :span.latex-inline)]

+ 1 - 0
src/main/frontend/extensions/pdf/toolbar.cljs

@@ -21,6 +21,7 @@
 (def *area-dashed? (atom ((fnil identity false) (storage/get (str "ls-pdf-area-is-dashed")))))
 (def *area-mode? (atom false))
 (def *highlight-mode? (atom false))
+#_:clj-kondo/ignore
 (rum/defcontext *highlights-ctx*)
 
 (rum/defc pdf-settings

+ 4 - 1
src/main/frontend/handler/code.cljs

@@ -18,9 +18,12 @@
     (when editor
       (.save editor)
       (let [textarea (.getTextArea editor)
+            ds (.-dataset textarea)
             value (gobj/get textarea "value")
-            default-value (gobj/get textarea "defaultValue")]
+            default-value (or (.-v ds) (gobj/get textarea "defaultValue"))]
         (when (not= value default-value)
+          ;; update default value for the editor initial state
+          (set! ds -v value)
           (cond
             ;; save block content
             (:block/uuid config)

+ 6 - 8
src/main/frontend/handler/editor.cljs

@@ -168,7 +168,6 @@
   ([text]
    (when-let [m (get-selection-and-format)]
      (let [{:keys [selection-start selection-end format selection value edit-id input]} m
-           cur-pos (cursor/pos input)
            empty-selection? (= selection-start selection-end)
            selection-link? (and selection (gp-mldoc/mldoc-link? format selection))
            [content forward-pos] (cond
@@ -190,7 +189,7 @@
                       (subs value 0 selection-start)
                       content
                       (subs value selection-end))
-           cur-pos (or selection-start cur-pos)]
+           cur-pos (or selection-start (cursor/pos input))]
        (state/set-edit-content! edit-id new-value)
        (cursor/move-cursor-to input (+ cur-pos forward-pos))))))
 
@@ -1874,10 +1873,9 @@
     (cond
       (and (= content "1. ") (= last-input-char " ") input-id edit-block
            (not (own-order-number-list? edit-block)))
-      (do
-        (state/set-edit-content! input-id "")
-        (-> (p/delay 10)
-            (p/then #(state/pub-event! [:editor/toggle-own-number-list edit-block]))))
+      (p/do!
+       (state/pub-event! [:editor/toggle-own-number-list edit-block])
+       (state/set-edit-content! input-id ""))
 
       (and (= last-input-char commands/command-trigger)
            (or (re-find #"(?m)^/" (str (.-value input))) (start-of-new-word? input pos)))
@@ -2005,7 +2003,7 @@
                   sibling?
                   keep-uuid?
                   cut-paste?
-                  revert-cut-txs]
+                  revert-cut-tx]
            :or {exclude-properties []}}]
   (let [editing-block (when-let [editing-block (state/get-edit-block)]
                         (some-> (db/pull [:block/uuid (:block/uuid editing-block)])
@@ -2047,7 +2045,7 @@
 
     (outliner-tx/transact!
       {:outliner-op :insert-blocks
-       :additional-tx revert-cut-txs}
+       :additional-tx revert-cut-tx}
       (when target-block'
         (let [format (or (:block/format target-block') (state/get-preferred-format))
               blocks' (map (fn [block]

+ 22 - 15
src/main/frontend/handler/events.cljs

@@ -23,6 +23,7 @@
             [frontend.components.shell :as shell]
             [frontend.components.whiteboard :as whiteboard]
             [frontend.components.user.login :as login]
+            [frontend.components.shortcut :as shortcut]
             [frontend.config :as config]
             [frontend.context.i18n :refer [t]]
             [frontend.db :as db]
@@ -165,12 +166,8 @@
 
 ;; Parameters for the `persist-db` function, to show the notification messages
 (def persist-db-noti-m
-  {:before     #(notification/show!
-                 (ui/loading (t :graph/persist))
-                 :warning)
-   :on-error   #(notification/show!
-                 (t :graph/persist-error)
-                 :error)})
+  {:before     #(ui/notify-graph-persist!)
+   :on-error   #(ui/notify-graph-persist-error!)})
 
 (defn- graph-switch-on-persisted
   "Logic for keeping db sync when switching graphs
@@ -940,6 +937,11 @@
 (defmethod handle :editor/quick-capture [[_ args]]
   (quick-capture/quick-capture args))
 
+(defmethod handle :modal/keymap-manager [[_]]
+  (state/set-modal!
+    #(shortcut/keymap-pane)
+    {:label "keymap-manager"}))
+
 (defmethod handle :editor/toggle-own-number-list [[_ blocks]]
   (let [batch? (sequential? blocks)
         blocks (cond->> blocks
@@ -964,15 +966,20 @@
   []
   (let [chan (state/get-events-chan)]
     (async/go-loop []
-      (let [payload (async/<! chan)]
-        (try
-          (handle payload)
-          (catch :default error
-            (let [type :handle-system-events/failed]
-              (js/console.error (str type) (clj->js payload) "\n" error)
-              (state/pub-event! [:capture-error {:error error
-                                                 :payload {:type type
-                                                           :payload payload}}])))))
+      (let [[payload d] (async/<! chan)]
+        (->
+         (try
+           (p/resolved (handle payload))
+           (catch :default error
+             (p/rejected error)))
+         (p/then (fn [result]
+                   (p/resolve! d result)))
+         (p/catch (fn [error]
+                    (let [type :handle-system-events/failed]
+                      (state/pub-event! [:capture-error {:error error
+                                                         :payload {:type type
+                                                                   :payload payload}}])
+                      (p/reject! d error))))))
       (recur))
     chan))
 

+ 1 - 10
src/main/frontend/handler/page.cljs

@@ -6,7 +6,6 @@
             [datascript.core :as d]
             [frontend.commands :as commands]
             [frontend.config :as config]
-            [frontend.context.i18n :refer [t]]
             [frontend.date :as date]
             [frontend.db :as db]
             [frontend.db.conn :as conn]
@@ -792,10 +791,6 @@
       (fn [chosen _click?]
         (state/clear-editor-action!)
         (let [wrapped? (= page-ref/left-brackets (gp-util/safe-subs edit-content (- pos 2) pos))
-              prefix (str (t :new-page) ": ")
-              chosen (if (string/starts-with? chosen prefix) ;; FIXME: What if a page named "New page: XXX"?
-                       (string/replace-first chosen prefix "")
-                       chosen)
               chosen (if (and (util/safe-re-find #"\s+" chosen) (not wrapped?))
                        (page-ref/->page-ref chosen)
                        chosen)
@@ -814,11 +809,7 @@
                                            :command :page-ref})))
       (fn [chosen _click?]
         (state/clear-editor-action!)
-        (let [prefix (str (t :new-page) ": ")
-              chosen (if (string/starts-with? chosen prefix)
-                       (string/replace-first chosen prefix "")
-                       chosen)
-              page-ref-text (get-page-ref-text chosen)]
+        (let [page-ref-text (get-page-ref-text chosen)]
           (editor-handler/insert-command! id
                                           page-ref-text
                                           format

+ 76 - 69
src/main/frontend/handler/paste.cljs

@@ -114,77 +114,87 @@
     (when (= (set (map :block/uuid blocks)) recent-cut-block-ids)
       (seq revert-tx))))
 
-(defn- paste-copied-blocks-or-text
-  ;; todo: logseq/whiteboard-shapes is now text/html
-  [text e html]
-  (util/stop e)
-  (->
-   (p/let [copied-blocks (get-copied-blocks)]
-     (let [input (state/get-input)
-           input-id (state/get-edit-input-id)
-           text (string/replace text "\r\n" "\n") ;; Fix for Windows platform
-           replace-text-f (fn [text]
-                            (let [input-id (state/get-edit-input-id)]
-                              (commands/delete-selection! input-id)
-                              (commands/simple-insert! input-id text nil)))
-           internal-paste? (seq copied-blocks)]
-       (if internal-paste?
-         (let [revert-cut-tx (get-revert-cut-tx copied-blocks)
-               cut-paste? (boolean (seq revert-cut-tx))
-               keep-uuid? cut-paste?]
-           (editor-handler/paste-blocks copied-blocks {:revert-cut-tx revert-cut-tx
-                                                       :cut-paste? cut-paste?
-                                                       :keep-uuid? keep-uuid?}))
-         (let [shape-refs-text (when (and (not (string/blank? html))
-                                          (get-whiteboard-tldr-from-text html))
-                                 ;; text should always be prepared block-ref generated in tldr
-                                 text)
-               {:keys [value selection] :as selection-and-format} (editor-handler/get-selection-and-format)
-               text-url? (gp-util/url? text)
-               selection-url? (gp-util/url? selection)]
-           (cond
-             (not (string/blank? shape-refs-text))
-             (commands/simple-insert! input-id shape-refs-text nil)
+(defn- paste-copied-text
+  [input *text html]
+  (let [replace-text-f (fn [text]
+                         (let [input-id (state/get-edit-input-id)]
+                           (commands/delete-selection! input-id)
+                           (commands/simple-insert! input-id text nil)))
+        text (string/replace *text "\r\n" "\n") ;; Fix for Windows platform
+        input-id (state/get-edit-input-id)
+        shape-refs-text (when (and (not (string/blank? html))
+                                   (get-whiteboard-tldr-from-text html))
+                          ;; text should always be prepared block-ref generated in tldr
+                          text)
+        {:keys [value selection] :as selection-and-format} (editor-handler/get-selection-and-format)
+        text-url? (gp-util/url? text)
+        selection-url? (gp-util/url? selection)]
+    (cond
+      (not (string/blank? shape-refs-text))
+      (commands/simple-insert! input-id shape-refs-text nil)
+
+      ;; When a url is selected in a formatted link, replaces it with pasted text
+      (or (and (or text-url? selection-url?)
+               (selection-within-link? selection-and-format))
+          (and text-url? selection-url?))
+      (replace-text-f text)
 
-             (or (and (or text-url? selection-url?)
-                      (selection-within-link? selection-and-format))
-                 (and text-url? selection-url?))
-             (replace-text-f text)
+      ;; Pastes a formatted link over selected text
+      (and (or text-url?
+               (and value (gp-util/url? (string/trim value))))
+           (not (string/blank? (util/get-selected-text))))
+      (editor-handler/html-link-format! text)
 
-             (and (or text-url?
-                      (and value (gp-util/url? (string/trim value))))
-                  (not (string/blank? (util/get-selected-text))))
-             (editor-handler/html-link-format! text)
+      ;; Pastes only block id when inside of '(())'
+      (and (block-ref/block-ref? text)
+           (editor-handler/wrapped-by? input block-ref/left-parens block-ref/right-parens))
+      (commands/simple-insert! input-id (block-ref/get-block-ref-id text) nil)
 
-             (and (block-ref/block-ref? text)
-                  (editor-handler/wrapped-by? input block-ref/left-parens block-ref/right-parens))
-             (commands/simple-insert! input-id (block-ref/get-block-ref-id text) nil)
+      :else
+      ;; from external
+      (let [format (or (db/get-page-format (state/get-current-page)) :markdown)
+            html-text (let [result (when-not (string/blank? html)
+                                     (try
+                                       (html-parser/convert format html)
+                                       (catch :default e
+                                         (log/error :exception e)
+                                         nil)))]
+                        (if (string/blank? result) nil result))
+            text-blocks? (if (= format :markdown) markdown-blocks? org-blocks?)
+            blocks? (text-blocks? text)
+            text' (or html-text
+                      (when (gp-util/url? text)
+                        (wrap-macro-url text))
+                      text)]
+        (cond
+          blocks?
+          (paste-text-parseable format text)
 
-             :else
-             ;; from external
-             (let [format (or (db/get-page-format (state/get-current-page)) :markdown)
-                   html-text (let [result (when-not (string/blank? html)
-                                            (try
-                                              (html-parser/convert format html)
-                                              (catch :default e
-                                                (log/error :exception e)
-                                                nil)))]
-                               (if (string/blank? result) nil result))
-                   text-blocks? (if (= format :markdown) markdown-blocks? org-blocks?)
-                   blocks? (text-blocks? text)
-                   text' (or html-text text)]
-               (cond
-                 blocks?
-                 (paste-text-parseable format text)
+          (util/safe-re-find #"(?:\r?\n){2,}" text')
+          (paste-segmented-text format text')
 
-                 (util/safe-re-find #"(?:\r?\n){2,}" text')
-                 (paste-segmented-text format text')
+          :else
+          (replace-text-f text'))))))
 
-                 :else
-                 (replace-text-f text'))))))))
+(defn- paste-copied-blocks-or-text
+  ;; todo: logseq/whiteboard-shapes is now text/html
+  [input text e html]
+  (util/stop e)
+  (->
+   (p/let [copied-blocks (get-copied-blocks)]
+     (if (seq copied-blocks)
+       ;; Handle internal paste
+       (let [revert-cut-tx (get-revert-cut-tx copied-blocks)
+             cut-paste? (boolean (seq revert-cut-tx))
+             keep-uuid? cut-paste?]
+         (editor-handler/paste-blocks copied-blocks {:revert-cut-tx revert-cut-tx
+                                                     :cut-paste? cut-paste?
+                                                     :keep-uuid? keep-uuid?}))
+       (paste-copied-text input text html)))
    (p/catch (fn [error]
-              (prn "Paste failed: ")
-              (log/error :exception error)))))
+              (log/error :msg "Paste failed" :exception error)
+              (state/pub-event! [:capture-error {:error error
+                                                 :payload {:type ::paste-copied-blocks-or-text}}])))))
 
 (defn paste-text-in-one-block-at-point
   []
@@ -206,7 +216,7 @@
     (when-not (mobile-util/native-ios?)
       (util/stop e)
       (paste-text-in-one-block-at-point))
-    (paste-copied-blocks-or-text text e html)))
+    (paste-copied-blocks-or-text input text e html)))
 
 (defn- paste-file-if-exists [id e]
   (when id
@@ -248,10 +258,7 @@
         (paste-file-if-exists id e)
 
         :else
-        (let [text' (or (when (gp-util/url? text)
-                          (wrap-macro-url text))
-                        text)]
-          (paste-text-or-blocks-aux (state/get-input) e text' html))))))
+        (paste-text-or-blocks-aux (state/get-input) e text html)))))
 
 (defn editor-on-paste-raw!
   "Raw pastes without _any_ formatting. Can also replace selected text with a paste"

+ 15 - 7
src/main/frontend/handler/plugin.cljs

@@ -7,6 +7,7 @@
             [logseq.graph-parser.mldoc :as gp-mldoc]
             [frontend.handler.notification :as notification]
             [frontend.handler.common.plugin :as plugin-common-handler]
+            [frontend.storage :as storage]
             [camel-snake-kebab.core :as csk]
             [frontend.state :as state]
             [medley.core :as medley]
@@ -186,8 +187,6 @@
   []
   (let [channel  (name :lsp-updates)
         listener (fn [_ ^js e]
-                   (js/console.debug (str :lsp-updates) e)
-
                    (when-let [{:keys [status payload only-check]} (bean/->clj e)]
                      (case (keyword status)
 
@@ -203,7 +202,7 @@
                                  #(do
                                     ;;(if theme (select-a-plugin-theme id))
                                     (notification/show!
-                                      (str (t :plugin/update) (t :plugins) ": " name " - " (.-version (.-options pl))) :success)
+                                      (t :plugin/update-plugin name (.-version (.-options pl))) :success)
                                     (state/consume-updates-from-coming-plugin! payload true))))
 
                              (do                            ;; register new
@@ -211,7 +210,7 @@
                                  (js/LSPluginCore.register (bean/->js {:key id :url dst}))
                                  (fn [] (when theme (js/setTimeout #(select-a-plugin-theme id) 300))))
                                (notification/show!
-                                 (str (t :plugin/installed) (t :plugins) ": " name) :success)))))
+                                 (t :plugin/installed-plugin name) :success)))))
 
                        :error
                        (let [error-code  (keyword (string/replace (:error-code payload) #"^[\s\:\[]+" ""))
@@ -219,7 +218,7 @@
                              [msg type] (case error-code
 
                                           :no-new-version
-                                          [(str (t :plugin/up-to-date) " :)") :success]
+                                          [(t :plugin/up-to-date ":)") :success]
 
                                           [error-code :error])
                              pending?    (seq (:plugin/updates-pending @state/state))]
@@ -297,9 +296,10 @@
   [pid key keybinding]
   (let [id      (keyword (str "plugin." pid "/" key))
         binding (:binding keybinding)
+        binding (some->> (if (string? binding) [binding] (seq binding))
+                         (map util/normalize-user-keyname))
         binding (if util/mac?
-                  (or (:mac keybinding) binding)
-                  binding)
+                  (or (:mac keybinding) binding) binding)
         mode    (or (:mode keybinding) :global)
         mode    (get keybinding-mode-handler-map (keyword mode))]
     [mode id {:binding binding}]))
@@ -630,6 +630,14 @@
       (state/pub-event! [:plugin/consume-updates])
       (set-auto-checking! true))))
 
+(defn get-enabled-auto-check-for-updates?
+  []
+  (not (false? (storage/get :lsp-last-auto-updates))))
+
+(defn set-enabled-auto-check-for-updates
+  [v?]
+  (storage/set :lsp-last-auto-updates (boolean v?)))
+
 (defn call-plugin
   [^js pl type payload]
   (when pl

+ 0 - 49
src/main/frontend/handler/query.cljs

@@ -1,49 +0,0 @@
-(ns frontend.handler.query
-  "Provides util handler fns for query"
-  (:require [clojure.walk :as walk]))
-
-(defn normalize-query-function
-  [ast result]
-  (let [ast (walk/prewalk
-             (fn [f]
-               (if (and (list? f)
-                        (keyword? (second f))
-                        (contains? #{'sum 'average 'count 'min 'max} (first f)))
-                 (if (contains? #{'min 'max} (first f))
-                   (list
-                    'apply
-                    (first f)
-                    (list 'map (second f) 'result))
-                   (list
-                    (first f)
-                    (list 'map (second f) 'result)))
-                 f))
-             ast)]
-    (walk/postwalk
-     (fn [f]
-       (cond
-         (keyword? f)
-         (case f
-           :block
-           :block/content
-
-           :page
-           :block/name
-
-           :created-at
-           :block/created-at
-
-           :updated-at
-           :block/updated-at
-
-           (let [vals (map #(get-in % [:block/properties f]) result)
-                 int? (some integer? vals)]
-             `(~'fn [~'b]
-               (~'let [~'result-str (~'get-in ~'b [:block/properties ~f])
-                       ~'result-num (~'parseFloat ~'result-str)
-                       ~'result (if (~'isNaN ~'result-num) ~'result-str ~'result-num)]
-                (~'or ~'result (~'when ~int? 0))))))
-
-         :else
-         f))
-     ast)))

+ 10 - 0
src/main/frontend/handler/user.cljs

@@ -199,6 +199,16 @@
   (state/clear-user-info!)
   (state/pub-event! [:user/logout]))
 
+(defn upgrade [] 
+  (let [base-upgrade-url "https://logseqdemo.lemonsqueezy.com/checkout/buy/13e194b5-c927-41a8-af58-ed1a36d6000d"
+        user-uuid (user-uuid)
+        url (cond-> base-upgrade-url
+              user-uuid (str "?checkout[custom][user_uuid]=" (name user-uuid)))]
+    (println " ~~~ LEMON: " url " ~~~ ")
+    (js/window.open url)))
+  ; (js/window.open 
+  ;   "https://logseqdemo.lemonsqueezy.com/checkout/buy/13e194b5-c927-41a8-af58-ed1a36d6000d"))
+
 (defn <ensure-id&access-token
   []
   (go

+ 1 - 0
src/main/frontend/handler/whiteboard.cljs

@@ -187,6 +187,7 @@
 (defn get-default-new-whiteboard-tx
   [page-name id]
   [#:block{:name (util/page-name-sanity-lc page-name),
+           :original-name page-name
            :type "whiteboard",
            :properties
            {:ls-type :whiteboard-page,

+ 10 - 7
src/main/frontend/modules/outliner/tree.cljs

@@ -88,13 +88,16 @@
 
 (defn block-entity->map
   [e]
-  {:db/id (:db/id e)
-   :block/uuid (:block/uuid e)
-   :block/parent {:db/id (:db/id (:block/parent e))}
-   :block/left {:db/id (:db/id (:block/left e))}
-   :block/page (:block/page e)
-   :block/refs (:block/refs e)
-   :block/children (:block/children e)})
+  (cond-> {:db/id (:db/id e)
+           :block/uuid (:block/uuid e)
+           :block/parent {:db/id (:db/id (:block/parent e))}
+           :block/page (:block/page e)}
+    (:db/id (:block/left e))
+    (assoc :block/left {:db/id (:db/id (:block/left e))})
+    (:block/refs e)
+    (assoc :block/refs (:block/refs e))
+    (:block/children e)
+    (assoc :block/children (:block/children e))))
 
 (defn filter-top-level-blocks
   [blocks]

+ 16 - 9
src/main/frontend/modules/shortcut/config.cljs

@@ -113,19 +113,19 @@
    :whiteboard/zoom-out          {:binding "shift+dash"
                                   :fn      #(.zoomOut (.-api ^js (state/active-tldraw-app)) false)}
 
-   :whiteboard/zoom-in           {:binding "shift+="
+   :whiteboard/zoom-in           {:binding "shift+equals"
                                   :fn      #(.zoomIn (.-api ^js (state/active-tldraw-app)) false)}
 
-   :whiteboard/send-backward     {:binding "["
+   :whiteboard/send-backward     {:binding "open-square-bracket"
                                   :fn      #(.sendBackward ^js (state/active-tldraw-app))}
 
-   :whiteboard/send-to-back      {:binding "shift+["
+   :whiteboard/send-to-back      {:binding "shift+open-square-bracket"
                                   :fn      #(.sendToBack ^js (state/active-tldraw-app))}
 
-   :whiteboard/bring-forward     {:binding "]"
+   :whiteboard/bring-forward     {:binding "close-square-bracket"
                                   :fn      #(.bringForward ^js (state/active-tldraw-app))}
 
-   :whiteboard/bring-to-front    {:binding "shift+]"
+   :whiteboard/bring-to-front    {:binding "shift+close-square-bracket"
                                   :fn      #(.bringToFront ^js (state/active-tldraw-app))}
 
    :whiteboard/lock              {:binding "mod+l"
@@ -439,7 +439,7 @@
                                      :fn      route-handler/redirect-to-whiteboard-dashboard!}
 
    :go/keyboard-shortcuts          {:binding "g s"
-                                    :fn      #(route-handler/redirect! {:to :shortcut-setting})}
+                                    :fn      #(state/pub-event! [:modal/keymap-manager])}
 
    :go/tomorrow                    {:binding "g t"
                                     :fn      journal-handler/go-to-tomorrow!}
@@ -927,7 +927,10 @@
     :dev/show-block-ast
     :dev/show-page-data
     :dev/show-page-ast
-    :ui/clear-all-notifications]})
+    :ui/clear-all-notifications]
+
+   :shortcut.category/plugins
+   []})
 
 (let [category-maps {::category (set (keys category*))
                      ::dicts/category dicts/categories}]
@@ -942,10 +945,14 @@
    (fn [v]
      (vec (remove #(:inactive (get all-default-keyboard-shortcuts %)) v)))))
 
+(def *shortcut-cmds (atom {}))
+
 (defn add-shortcut!
   [handler-id id shortcut-map]
-  (swap! config assoc-in [handler-id id] shortcut-map))
+  (swap! config assoc-in [handler-id id] shortcut-map)
+  (swap! *shortcut-cmds assoc id (:cmd shortcut-map)))
 
 (defn remove-shortcut!
   [handler-id id]
-  (swap! config medley/dissoc-in [handler-id id]))
+  (swap! config medley/dissoc-in [handler-id id])
+  (swap! *shortcut-cmds dissoc id))

+ 20 - 17
src/main/frontend/modules/shortcut/core.cljs

@@ -66,7 +66,7 @@
          (doseq [k (dh/shortcut-binding id)]
            (try
              (log/debug :shortcut/register-shortcut {:id id :binding k})
-             (.registerShortcut handler (util/keyname id) (dh/normalize-user-keyname k))
+             (.registerShortcut handler (util/keyname id) (util/normalize-user-keyname k))
              (catch :default e
                (log/error :shortcut/register-shortcut {:id      id
                                                        :binding k
@@ -81,24 +81,24 @@
   (when-let [handler (get-handler-by-id handler-id)]
     (when-let [ks (dh/shortcut-binding shortcut-id)]
       (doseq [k ks]
-        (.unregisterShortcut ^js handler (dh/normalize-user-keyname k))))
+        (.unregisterShortcut ^js handler (util/normalize-user-keyname k))))
     (shortcut-config/remove-shortcut! handler-id shortcut-id)))
 
-(defn uninstall-shortcut!
+(defn uninstall-shortcut-handler!
   [install-id]
   (when-let [handler (-> (get @*installed install-id)
                          :handler)]
     (.dispose ^js handler)
     (swap! *installed dissoc install-id)))
 
-(defn install-shortcut!
+(defn install-shortcut-handler!
   [handler-id {:keys [set-global-keys?
                       prevent-default?
                       state]
                :or   {set-global-keys? true
                       prevent-default? false}}]
   (when-let [install-id (get-handler-by-id handler-id)]
-    (uninstall-shortcut! install-id))
+    (uninstall-shortcut-handler! install-id))
 
   (let [shortcut-map (dh/shortcut-map handler-id state)
         handler      (new KeyboardShortcutHandler js/window)]
@@ -136,23 +136,23 @@
         :shortcut.handler/editor-global
         :shortcut.handler/global-non-editing-only
         :shortcut.handler/global-prevent-default]
-       (map #(install-shortcut! % {}))
+       (map #(install-shortcut-handler! % {}))
        doall))
 
 (defn mixin [handler-id]
   {:did-mount
    (fn [state]
-     (let [install-id (install-shortcut! handler-id {:state state})]
+     (let [install-id (install-shortcut-handler! handler-id {:state state})]
        (assoc state ::install-id install-id)))
 
    :will-remount (fn [old-state new-state]
-                  (uninstall-shortcut! (::install-id old-state))
-                  (when-let [install-id (install-shortcut! handler-id {:state new-state})]
+                  (uninstall-shortcut-handler! (::install-id old-state))
+                  (when-let [install-id (install-shortcut-handler! handler-id {:state new-state})]
                     (assoc new-state ::install-id install-id)))
    :will-unmount
    (fn [state]
      (when-let [install-id (::install-id state)]
-       (uninstall-shortcut! install-id))
+       (uninstall-shortcut-handler! install-id))
      state)})
 
 (defn unlisten-all []
@@ -183,7 +183,7 @@
     (state/set-state! :ui/shortcut-handler-refreshing? true)
 
     (doseq [id (keys @*installed)]
-      (uninstall-shortcut! id))
+      (uninstall-shortcut-handler! id))
     (install-shortcuts!)
     (state/pub-event! [:shortcut-handler-refreshed])
     (state/set-state! :ui/shortcut-handler-refreshing? false)))
@@ -216,7 +216,7 @@
            keystroke (:rum/local state)]
 
        (doseq [id (keys @*installed)]
-         (uninstall-shortcut! id))
+         (uninstall-shortcut-handler! id))
 
        (events/listen handler "key"
                       (fn [e]
@@ -226,13 +226,16 @@
        (assoc state ::key-record-handler handler)))
 
    :will-unmount
-   (fn [{:rum/keys [args local] :as state}]
+   (fn [{:rum/keys [args local action] :as state}]
      (let [k (first args)
            keystroke (str/trim @local)]
-       (when-not (empty? keystroke)
-         (config-handler/set-config! :shortcuts (merge
-                                                 (:shortcuts (state/get-config))
-                                                 {k keystroke}))))
+       (when (and (= @action :save)
+                  (seq keystroke))
+         (config-handler/set-config!
+           :shortcuts
+           (merge
+             (:shortcuts (state/get-config))
+             {k keystroke}))))
 
      (when-let [^js handler (::key-record-handler state)]
        (.dispose handler))

+ 20 - 25
src/main/frontend/modules/shortcut/data_helper.cljs

@@ -52,27 +52,21 @@
          shortcut)
        (mapv mod-key)))))
 
-(defn normalize-user-keyname
-  [k]
-  (let [keynames {";" "semicolon"
-                  "=" "equals"
-                  "-" "dash"
-                  "[" "open-square-bracket"
-                  "]" "close-square-bracket"
-                  "'" "single-quote"}]
-    (some-> k
-            (util/safe-lower-case)
-            (str/replace #"[;=-\[\]']" (fn [s]
-                                         (get keynames s))))))
+(defn shortcut-cmd
+  [id]
+  (get @shortcut-config/*shortcut-cmds id))
 
 ;; returns a vector to preserve order
 (defn binding-by-category [name]
-  (let [dict (->> (vals @shortcut-config/config)
-                  (apply merge)
-                  (map (fn [[k _]]
-                         {k {:binding (shortcut-binding k)}}))
-                  (into {}))]
-    (->> (shortcut-config/category name)
+  (let [dict    (->> (vals @shortcut-config/config)
+                     (apply merge)
+                     (map (fn [[k _]]
+                            {k {:binding (shortcut-binding k)}}))
+                     (into {}))
+        plugin? (= name :shortcut.category/plugins)]
+    (->> (if plugin?
+           (->> (keys dict) (filter #(str/starts-with? (str %) ":plugin.")))
+           (shortcut-config/category name))
          (mapv (fn [k] [k (k dict)])))))
 
 (defn shortcut-map
@@ -148,13 +142,14 @@
 (defn remove-shortcut [k]
   (let [repo (state/get-current-repo)
         path (config/get-repo-config-path)]
-    (when-let [content (db/get-file path)]
-      (let [result (config-handler/parse-repo-config content)
-            new-result (rewrite/update
-                        result
-                        :shortcuts
-                        #(dissoc (rewrite/sexpr %) k))
-            new-content (str new-result)]
+    (when-let [result (some-> (db/get-file path)
+                              (config-handler/parse-repo-config))]
+      (when-let [new-content (and (:shortcuts result)
+                                  (-> (rewrite/update
+                                        result
+                                        :shortcuts
+                                        #(dissoc (rewrite/sexpr %) k))
+                                      (str)))]
         (repo-config-handler/set-repo-config-state! repo new-content)
         (file/set-file-content! repo path new-content)))))
 

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

@@ -18,6 +18,8 @@
 
 (defn quick-capture [args]
   (let [{:keys [url title content page append]} (bean/->clj args)
+        title (or title "")
+        url (or url "")
         insert-today? (get-in (state/get-config)
                               [:quick-capture-options :insert-today?]
                               false)

+ 1 - 1
src/main/frontend/routes.cljs

@@ -71,7 +71,7 @@
 
    ["/settings/shortcut"
     {:name :shortcut-setting
-     :view shortcut/shortcut}]
+     :view shortcut/shortcut-page}]
 
    ["/settings/zotero"
     {:name :zotero-setting

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

@@ -227,7 +227,8 @@
                           (remove string/blank?)
                           (map search-db/original-page-name->index))
         pages-to-remove-set (->> (remove :added pages)
-                                 (map :v))
+                                 (map :v)
+                                 set)
         pages-to-remove-id-set (->> (remove :added pages)
                                     (map :e)
                                     set)]
@@ -260,12 +261,15 @@
   (let [data (:tx-data tx-report)
         datoms (filter
                 (fn [datom]
-                  (contains? #{:block/name :block/content} (:a datom)))
+                  ;; Capture any direct change on page display title, page ref or block content
+                  (contains? #{:block/name :block/original-name :block/content} (:a datom)))
                 data)]
     (when (seq datoms)
       (let [datoms (group-by :a datoms)
             blocks (:block/content datoms)
-            pages (:block/name datoms)]
+            pages (concat ;; Duplicated eids are handled in `get-pages-from-datoms-impl`
+                   (:block/name datoms)
+                   (:block/original-name datoms))]
         (merge (get-blocks-from-datoms-impl blocks)
                (get-pages-from-datoms-impl pages))))))
 

+ 23 - 5
src/main/frontend/state.cljs

@@ -760,6 +760,18 @@ Similar to re-frame subscriptions"
   (when-let [graphs (seq (get-in @state [:file-sync/remote-graphs :graphs]))]
     (some #(when (= (:GraphUUID %) (str uuid)) %) graphs)))
 
+(defn get-remote-graph-usage
+  []
+  (when-let [graphs (seq (get-in @state [:file-sync/remote-graphs :graphs]))]
+    (->> graphs
+         (map #(hash-map :uuid (:GraphUUID %)
+                         :name (:GraphName %)
+                         :used-gbs (/ (:GraphStorageUsage %) 1024 1024 1024)
+                         :limit-gbs (/ (:GraphStorageLimit %) 1024 1024 1024)
+                         :used-percent (/ (:GraphStorageUsage %) (:GraphStorageLimit %) 0.01)))
+         (map #(assoc % :free-gbs (- (:limit-gbs %) (:used-gbs %))))
+         (vec))))
+
 (defn delete-remote-graph!
   [repo]
   (swap! state update-in [:file-sync/remote-graphs :graphs]
@@ -1709,8 +1721,10 @@ Similar to re-frame subscriptions"
 (defn pub-event!
   {:malli/schema [:=> [:cat vector?] :any]}
   [payload]
-  (let [chan (get-events-chan)]
-    (async/put! chan payload)))
+  (let [d (p/deferred)
+        chan (get-events-chan)]
+    (async/put! chan [payload d])
+    d))
 
 (defn get-export-block-text-indent-style []
   (:copy/export-block-text-indent-style @state))
@@ -2013,9 +2027,10 @@ Similar to re-frame subscriptions"
                    (fn [old-value] (merge old-value m)))))
 
 (defn http-proxy-enabled-or-val? []
-  (when-let [agent-opts (sub [:electron/user-cfgs :settings/agent])]
-    (when (every? not-empty (vals agent-opts))
-      (str (:protocol agent-opts) "://" (:host agent-opts) ":" (:port agent-opts)))))
+  (when-let [{:keys [type protocol host port] :as agent-opts} (sub [:electron/user-cfgs :settings/agent])]
+    (when (and  (not (contains? #{"system"} type))
+                (every? not-empty (vals agent-opts)))
+      (str protocol "://" host ":" port))))
 
 (defn set-mobile-app-state-change
   [is-active?]
@@ -2098,6 +2113,9 @@ Similar to re-frame subscriptions"
       (when (seq groups)
         (storage/set :user-groups groups)))))
 
+(defn get-user-info []
+  (sub :user/info))
+
 (defn clear-user-info!
   []
   (storage/remove :user-groups))

+ 29 - 6
src/main/frontend/ui.cljs

@@ -55,7 +55,7 @@
 
 (defonce icon-size (if (mobile-util/native-platform?) 26 20))
 
-(def block-background-colors
+(def built-in-colors
   ["yellow"
    "red"
    "pink"
@@ -64,11 +64,15 @@
    "purple"
    "gray"])
 
+(defn built-in-color?
+  [color]
+  (some #{color} built-in-colors))
+
 (rum/defc menu-background-color
   [add-bgcolor-fn rm-bgcolor-fn]
   [:div.flex.flex-row.justify-between.py-1.px-2.items-center
    [:div.flex.flex-row.justify-between.flex-1.mx-2.mt-2
-    (for [color block-background-colors]
+    (for [color built-in-colors]
       [:a
        {:title (t (keyword "color" color))
         :on-click #(add-bgcolor-fn color)}
@@ -171,12 +175,18 @@
                   sequence)]))
 
 (rum/defc menu-link
-  [options child shortcut]
-  (if (:only-child? options)
+  [{:keys [only-child? no-padding? class] :as options} child shortcut]
+  (if only-child?
     [:div.menu-link
      (dissoc options :only-child?) child]
     [:a.flex.justify-between.px-4.py-2.text-sm.transition.ease-in-out.duration-150.cursor.menu-link
-     options
+     (cond-> options
+             (true? no-padding?)
+             (assoc :class (str class " no-padding"))
+
+             true
+             (dissoc :no-padding?))
+
      [:span.flex-1 child]
      (when shortcut
        [:span.ml-1 (render-keyboard-shortcut shortcut)])]))
@@ -210,7 +220,7 @@
                                   [:div.title-wrap {:style {:margin-right "8px"
                                                             :margin-left  "4px"}} title]]))]
                  (if hr
-                   [:hr.menu-separator {:key "dropdown-hr"}]
+                   [:hr.menu-separator {:key (or key "dropdown-hr")}]
                    (rum/with-key
                     (menu-link new-options child nil)
                     title)))))
@@ -716,6 +726,7 @@
             (modal-panel show? modal-panel-content state close-fn false close-btn?)))]))))
 
 (defn loading
+  ([] (loading (t :loading)))
   ([content] (loading content nil))
   ([content opts]
    [:div.flex.flex-row.items-center.inline
@@ -723,6 +734,18 @@
      (when-not (string/blank? content)
        [:span.text.pl-2 content])]]))
 
+(defn notify-graph-persist!
+  []
+  (notification/show!
+   (loading (t :graph/persist))
+   :warning))
+
+(defn notify-graph-persist-error!
+  []
+  (notification/show!
+   (t :graph/persist-error)
+   :error))
+
 (rum/defc rotating-arrow
   [collapsed?]
   [:span

+ 10 - 3
src/main/frontend/ui.css

@@ -41,6 +41,12 @@
   }
 }
 
+.menu-link {
+  &.no-padding {
+    padding: 0 !important;
+  }
+}
+
 .ui__ac-group-name {
   @apply p-2 text-xs;
   color: var(--ls-block-ref-link-text-color);
@@ -100,11 +106,12 @@
   }
 
   &-overlay div {
-    background: var(--ls-tertiary-background-color);
+    background-image: linear-gradient(to bottom, var(--ls-primary-background-color), var(--ls-quaternary-background-color));
   }
 
   &-panel {
-    @apply relative rounded-md shadow-xl;
+    @apply relative rounded-md shadow-xl border;
+    border-color: var(--ls-border-color);
 
     overflow: hidden;
     background: var(--ls-secondary-background-color);
@@ -150,7 +157,7 @@
     hover:opacity-100;
 
     &-wrap {
-      @apply absolute top-0 right-0 pt-2 pr-2;
+      @apply z-10 absolute top-0 right-0 pt-2 pr-2;
     }
   }
 

+ 12 - 0
src/main/frontend/util.cljc

@@ -67,6 +67,18 @@
   [parts]
   (string/join "/" parts))
 
+(defn normalize-user-keyname
+  [k]
+  (let [keynames {";" "semicolon"
+                  "=" "equals"
+                  "-" "dash"
+                  "[" "open-square-bracket"
+                  "]" "close-square-bracket"
+                  "'" "single-quote"}]
+    (some-> (str k)
+            (string/lower-case)
+            (string/replace #"[;=-\[\]']" (fn [s]
+                                            (get keynames s))))))
 
 #?(:cljs
    (defn safe-re-find

+ 49 - 0
src/main/frontend/util/property.cljs

@@ -10,6 +10,8 @@
             [logseq.graph-parser.util.page-ref :as page-ref]
             [frontend.format.mldoc :as mldoc]
             [logseq.graph-parser.text :as text]
+            [frontend.db :as db]
+            [frontend.state :as state]
             [frontend.util.cursor :as cursor]))
 
 (defn hidden-properties
@@ -414,3 +416,50 @@
     (util/format
      (config/properties-wrapper-pattern page-format)
      (string/join "\n" lines))))
+
+(def hidden-editable-page-properties
+  "Properties that are hidden in the pre-block (page property)"
+  #{:title :filters :icon})
+
+(assert (set/subset? hidden-editable-page-properties (gp-property/editable-built-in-properties))
+        "Hidden editable page properties must be valid editable properties")
+
+(def hidden-editable-block-properties
+  "Properties that are hidden in a block (block property)"
+  (into #{:logseq.query/nlp-date}
+        gp-property/editable-view-and-table-properties))
+
+(assert (set/subset? hidden-editable-block-properties (gp-property/editable-built-in-properties))
+        "Hidden editable page properties must be valid editable properties")
+
+(defn- add-aliases-to-properties
+  "Adds aliases to a page when a page has aliases and is also an alias of other pages"
+  [properties page-id]
+  (let [repo (state/get-current-repo)
+        aliases (db/get-page-alias-names repo
+                                         (:block/name (db/pull page-id)))]
+    (if (seq aliases)
+      (if (:alias properties)
+        (update properties :alias (fn [c]
+                                    (util/distinct-by string/lower-case (concat c aliases))))
+        (assoc properties :alias aliases))
+      properties)))
+
+(defn get-visible-ordered-properties
+  "Given a block's properties, order of properties and any display context,
+  returns a tuple of property pairs that are visible when not being edited"
+  [properties* properties-order {:keys [pre-block? page-id]}]
+  (let [dissoc-keys (fn [m keys] (apply dissoc m keys))
+        properties (cond-> (update-keys properties* keyword)
+                     true
+                     (dissoc-keys (hidden-properties))
+                     pre-block?
+                     (dissoc-keys hidden-editable-page-properties)
+                     (not pre-block?)
+                     (dissoc-keys hidden-editable-block-properties)
+                     pre-block?
+                     (add-aliases-to-properties page-id))]
+    (if (seq properties-order)
+      (keep (fn [k] (when (contains? properties k) [k (get properties k)]))
+            (distinct properties-order))
+      properties*)))

+ 1 - 1
src/main/frontend/version.cljs

@@ -1,3 +1,3 @@
 (ns ^:no-doc frontend.version)
 
-(defonce version "0.9.8")
+(defonce version "0.9.9")

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

@@ -367,8 +367,8 @@
                                (if palette?
                                  (palette-handler/invoke-command palette-cmd)
                                  (action')))
-                [handler-id id shortcut-map] (update shortcut-args 2 assoc :fn dispatch-cmd)]
-            (js/console.debug :shortcut/register-shortcut [handler-id id shortcut-map])
+                [handler-id id shortcut-map] (update shortcut-args 2 assoc :fn dispatch-cmd :cmd palette-cmd)]
+            (println :shortcut/register-shortcut [handler-id id shortcut-map])
             (st/register-shortcut! handler-id id shortcut-map)))))))
 
 (defn ^:export unregister_plugin_simple_command
@@ -384,7 +384,7 @@
         (palette-handler/unregister (:id cmd))
         ;; remove keybinding commands
         (when (seq (:shortcut cmd))
-          (js/console.debug :shortcut/unregister-shortcut cmd)
+          (println :shortcut/unregister-shortcut cmd)
           (st/unregister-shortcut! (:handler-id cmd) (:id cmd)))))))
 
 (defn ^:export register_search_service

+ 1 - 22
src/resources/dicts/af.edn

@@ -1,16 +1,7 @@
-{: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/about "Oor Logseq"
+{:help/about "Oor Logseq"
  :help/bug "Fout verslag"
  :help/feature "Funksie versoek"
  :help/changelog "Verandering-dagboek"
- :help/blog "Logseq blog"
  :help/privacy "Privaatheidsbeleid"
  :help/terms "Terme"
  :help/shortcuts "Sleutelbord kortpaaie"
@@ -32,16 +23,9 @@
  :right-side-bar/switch-theme "Skakel oor na die {1} tema"
  :right-side-bar/contents "Inhoud"
  :right-side-bar/block-ref "Blok verwysing"
- :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/journals "Journals"
  :page/presentation-mode "Aanbiedings modus"
  :page/delete-confirmation "Is jy seker jy wil die bladsy uitvee?"
- :page/delete "Delete page (will delete the file too)"
  :page/show-journals "Wys joernale"
- :block/name "Page name"
  :file/name "Lêer naam"
  :file/last-modified-at "Laas verander op"
  :file/no-data "Geen data"
@@ -53,9 +37,6 @@
  :content/open-in-sidebar "Maak in kantlys oop"
  :content/click-to-edit "Kliek om te wysig"
  :settings-page/edit-config-edn "Wysig config.edn (vir huidige stoor)"
- :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/preferred-file-format "Voorkeur lêer formaat"
  :settings-page/preferred-workflow "Voorkeur werkstroom"
  :settings-page/developer-mode "Ontwikkelaar modus"
@@ -66,7 +47,6 @@
  :re-index "Herindekseer"
  :export-json "Uitvoer as JSON"
  :search "Soek"
- :new-page "Nuwe bladsy"
  :graph "Grafiek"
  :all-pages "Alle blaaie"
  :all-files "Alle lêers"
@@ -74,7 +54,6 @@
  :import "Invoer"
  :join-community "Sluit by die gemeenskap aan"
  :help-shortcut-title "Kliek op die kortpad en ander wenke"
- :loading "Laai tans"
  :parsing-files "Lêer ontleding"
  :loading-files "Laai lêers"
  :download "Laai af"

+ 0 - 22
src/resources/dicts/de.edn

@@ -30,14 +30,11 @@
  :italics "Kursiv"
  :join-community "Treten Sie der Community bei"
  :language "Sprache"
- :loading "Laden"
  :loading-files "Dateien laden"
  :login "Einloggen"
  :logout "Ausloggen"
  :more "Mehr"
  :new-graph "Neuen Graphen hinzufügen"
- :new-page "Neue Seite"
- :new-whiteboard "Neues Whiteboard"
  :open-a-directory "Öffne ein lokales Verzeichnis"
  :open-new-window "Neues Fenster"
  :page-search "In der aktuellen Seite suchen"
@@ -51,7 +48,6 @@
  :relaunch-confirm-to-work "Sie sollten die App neu starten, damit sie funktioniert. Möchten Sie sie jetzt neu starten?"
  :remove-background "Hintergrund entfernen"
  :remove-heading "Überschrift entfernen"
- :remove-orphaned-pages "Verweiste Seiten entfernen"
  :save "Speichern"
  :search "Suchen oder Seite erstellen"
  :settings "Einstellungen"
@@ -106,7 +102,6 @@
  :file/no-data "Keine Daten"
  :file/validate-existing-file-error "Seite existiert bereits mit einer anderen Datei: {1}, aktuelle Datei: {2}. Bitte behalten Sie nur eine davon und indizieren Sie Ihren Graphen neu."
 
- :file-rn/all-action "Alle Aktionen anwenden!"
  :file-rn/apply-rename "Anwenden des Vorgangs zur Datei-Umbenennung"
  :file-rn/close-panel "Das Panel schließen"
  :file-rn/confirm-proceed "Format aktualisieren!"
@@ -133,31 +128,23 @@
  :file-rn/unreachable-title "Warnung! Der Seitenname wird unter dem aktuellen Dateinamenformat zu {1}, es sei denn, die Eigenschaft `title::` wird manuell gesetzt"
 
  :graph/all-graphs "Alle Graphen"
- :graph/local-graphs "Lokale Graphen"
  :graph/persist "Logseq synchronisiert gerade den internen Status, bitte warten Sie einige Sekunden."
  :graph/persist-error "Synchronisation des internen Status fehlgeschlagen."
- :graph/remote-graphs "Remote Graphen"
  :graph/save "Speichern..."
  :graph/save-error "Speichern fehlgeschlagen"
  :graph/save-success "Erfolgreich gespeichert"
 
  :help-shortcut-title "Hier klicken, um Tastenkombinationen und weitere Tipps zu sehen"
  :help/about "Über Logseq"
- :help/awesome-logseq "Awesome Logseq"
  :help/block-content-autocomplete "Blockinhalt (Quelltext, Zitate, Abfragen, etc.) Autovervollständigung"
  :help/block-reference "Blockverweis"
- :help/blog "Logseq Blog"
  :help/bug "Fehlerbericht"
  :help/changelog "Änderungsprotokoll"
  :help/context-menu "Kontextmenü"
  :help/docs "Dokumentation"
  :help/feature "Feature-Anfrage"
- :help/forum-community "Forum-Community"
- :help/markdown-syntax "Markdown-Syntax"
- :help/org-mode-syntax "Org-Mode-Syntax"
  :help/privacy "Datenschutzrichtlinie"
  :help/reference-autocomplete "Seitenverweis Autovervollständigung"
- :help/roadmap "Roadmap"
  :help/shortcut "Tastaturkürzel"
  :help/shortcut-page-title "Tastaturbefehl"
  :help/shortcuts "Tastaturkürzel"
@@ -196,7 +183,6 @@
  :on-boarding/welcome-whiteboard-modal-start "Whiteboarding beginnen"
 
  :page/add-to-favorites "Zu Favoriten hinzufügen"
- :page/backlinks "Backlinks"
  :page/copy-page-url "Seiten-URL kopieren"
  :page/created-at "Erstellt am"
  :page/delete "Seite löschen"
@@ -232,7 +218,6 @@
  :plugin/custom-js-alert "Datei custom.js gefunden. Darf sie ausgeführt werden? (Wenn Sie den Inhalt dieser Datei nicht verstehen, wird empfohlen, die Ausführung nicht zuzulassen, da dies gewisse Sicherheitsrisiken birgt)."
  :plugin/delete-alert "Wollen Sie wirklich das Plugin [{1}] deinstallieren?"
  :plugin/disabled "Deaktiviert"
- :plugin/downloads "Downloads"
  :plugin/enabled "Aktiviert"
  :plugin/install "Installieren"
  :plugin/installed "Installiert"
@@ -246,14 +231,12 @@
  :plugin/reload "Neu laden"
  :plugin/restart "App neu starten"
  :plugin/stars "Sterne"
- :plugin/title "Titel"
  :plugin/uninstall "Deinstallieren"
  :plugin/unpacked "Entpackt"
  :plugin/unpacked-tips "Plugin-Verzeichnis auswählen"
  :plugin/update "Aktualisierung"
  :plugin/update-available "Aktualisierung verfügbar"
  :plugin/updating "Aktualisiere"
- :plugin/up-to-date "Es ist auf dem neuesten Stand"
 
  :plugin.install-from-file/menu-title "Aus plugins.edn installieren"
  :plugin.install-from-file/title "Plugins aus plugins.edn installieren"
@@ -340,14 +323,11 @@
  :settings-page/tab-advanced "Erweitert"
  :settings-page/tab-assets "Anlagen"
  :settings-page/tab-features "Funktionen"
- :settings-page/tab-editor "Editor"
  :settings-page/tab-general "Allgemein"
  :settings-page/tab-version-control "Versionskontrolle"
 
  :text/image "Bild"
 
- :tips/all-done "Alles erledigt"
-
  :updater/new-version-install "Eine neue Version wurde heruntergeladen."
  :updater/quit-and-install "Neu starten, um zu installieren"
 
@@ -432,7 +412,6 @@
  :command.editor/forward-word            "Cursor ein Wort vorwärts bewegen"
  :command.editor/highlight               "Hervorheben"
  :command.editor/indent                  "Block einrücken"
- :command.editor/insert-link             "HTML Link"
  :command.editor/insert-youtube-timestamp "YouTube-Zeitstempel einfügen"
  :command.editor/italics                 "Kursiv"
  :command.editor/kill-line-after         "Zeile nach der Cursorposition löschen"
@@ -490,7 +469,6 @@
  :command.graph/re-index                 "Aktuellen Graph neu indizieren"
  :command.graph/remove                   "Einen Graph entfernen"
  :command.graph/save                     "Aktuellen Graph auf Festplatte speichern"
- :command.misc/copy                      "mod+c"
  :command.pdf/close                      "PDF: Aktuelles PDF-Dokument schließen"
  :command.pdf/find                       "PDF: Text des aktuellen PDF-Dokuments durchsuchen"
  :command.pdf/next-page                  "PDF: Nächste Seite des aktuellen PDF-Dokuments"

+ 58 - 16
src/resources/dicts/en.edn

@@ -17,6 +17,11 @@
  :on-boarding/tour-whiteboard-home-description "Whiteboards have their own section in the app where you can see them at a glance, create new ones or delete them easily."
  :on-boarding/tour-whiteboard-new "{1} Create new whiteboard"
  :on-boarding/tour-whiteboard-new-description "There are multiple ways of creating a new whiteboard. One of them is always right here in the dashboard."
+ :help/title-usage "Usage"
+ :help/title-community "Community"
+ :help/title-development "Development"
+ :help/title-about "About"
+ :help/title-terms "Terms"
  :help/start "Getting started"
  :help/about "About Logseq"
  :help/roadmap "Roadmap"
@@ -41,6 +46,9 @@
  :search/result-for "Search result for "
  :search/items "items"
  :search/page-names "Search page names"
+ :search/recent "Recent search:"
+ :search/blocks-in-page "Search blocks in page:"
+ :search/cache-outdated "Cache is outdated. Please click the 'Re-index' button in the graph's dropdown menu."
  :search-item/whiteboard "Whiteboard"
  :search-item/page "Page"
  :search-item/file "File"
@@ -67,6 +75,7 @@
  :right-side-bar/new-page "New page"
  :right-side-bar/show-journals "Show Journals"
  :right-side-bar/separator "Right sidebar resize handler"
+ :right-side-bar/toggle-right-sidebar "Toggle right sidebar"
  :left-side-bar/journals "Journals"
  :left-side-bar/create "Create"
  :left-side-bar/new-page "New page"
@@ -93,6 +102,9 @@
  :block/name "Page name"
  :page/earlier "Earlier"
  :page/copy-page-url "Copy page URL"
+ :page/illegal-page-name "Illegal page name!"
+ :page/page-already-exists "Page “{1}” already exists!"
+ :page/whiteboard-to-journal-error "Whiteboard pages cannot be renamed to journal titles!"
  :file/name "File name"
  :file/last-modified-at "Last modified at"
  :file/no-data "No data"
@@ -104,7 +116,7 @@
  :file-rn/or-select-actions-2 ". These actions are not available once you close this panel."
  :file-rn/legend "🟢 Optional rename actions; 🟡 Rename action required to avoid title change; 🔴 Breaking change."
  :file-rn/close-panel "Close the Panel"
- :file-rn/all-action "Apply all Actions!"
+ :file-rn/all-action "Apply all Actions! ({1})"
  :file-rn/select-format "(Developer Mode Option, Dangerous!) Select filename format"
  :file-rn/rename "rename file \"{1}\" to \"{2}\""
  :file-rn/apply-rename "Apply the file rename operation"
@@ -125,7 +137,7 @@
  :file-rn/instruct-3 "2. Follow the instructions below to rename the files to the new format:"
  :page/created-at "Created At"
  :page/updated-at "Updated At"
- :page/backlinks "Back Links"
+ :page/backlinks "Backlinks"
  :linked-references/filter-search "Search in linked pages"
  :editor/block-search "Search for a block"
  :text/image "Image"
@@ -188,8 +200,11 @@
  :settings-page/disable-sentry "Send usage data and diagnostics to Logseq"
  :settings-page/disable-sentry-desc "Logseq will never collect your local graph database or sell your data."
  :settings-page/preferred-outdenting "Logical outdenting"
+ :settings-page/preferred-outdenting-tip "The left side shows outdenting with the default setting, and the right shows outdenting with logical outdenting enabled "
+ :settings-page/preferred-outdenting-tip-more "→ Learn more"
  :settings-page/show-full-blocks "Show all lines of a block reference"
  :settings-page/auto-expand-block-refs "Expand block references automatically when zoom-in"
+ :settings-page/auto-expand-block-refs-tip "This option controls whether to expand the block references automatically when zoom-in."
  :settings-page/custom-date-format "Preferred date format"
  :settings-page/custom-date-format-warning "Re-index required! Existing journal references would be broken!"
  :settings-page/preferred-pasting-file-hint "When enabled, pasting an image from the internet will download and insert the image. When disabled, it will paste the link to the image."
@@ -214,6 +229,7 @@
  :settings-page/tab-general "General"
  :settings-page/tab-editor "Editor"
  :settings-page/tab-version-control "Version control"
+ :settings-page/tab-account "Account"
  :settings-page/tab-advanced "Advanced"
  :settings-page/tab-assets "Assets"
  :settings-page/tab-features "Features"
@@ -228,6 +244,11 @@
  :settings-page/enable-whiteboards "Whiteboards"
  :settings-page/native-titlebar "Native title bar"
  :settings-page/native-titlebar-desc "Enables the native window title bar on Windows and Linux."
+ :settings-page/check-for-updates "Check for updates"
+ :settings-page/checking "Checking ..."
+ :settings-page/revision "Revision: "
+ :settings-page/changelog "What's new?"
+ :settings-page/app-updated "Your app is up-to-date 🎉"
  :yes "Yes"
 
  :submit "Submit"
@@ -336,10 +357,10 @@
  :page-search "Search in the current page"
  :graph-search "Search graph"
  :home "Home"
- :new-page "New page"
+ :new-page "New page:"
  :whiteboard "Whiteboard"
  :whiteboards "Whiteboards"
- :new-whiteboard "New whiteboard"
+ :new-whiteboard "New whiteboard:"
  :new-graph "Add new graph"
  :graph "Graph"
  :graph/persist "Logseq is syncing internal status, please wait for several seconds."
@@ -348,8 +369,8 @@
  :graph/save-success "Saved successfully"
  :graph/save-error "Save failed"
  :graph/all-graphs "All graphs"
- :graph/local-graphs "Local graphs"
- :graph/remote-graphs "Remote graphs"
+ :graph/local-graphs "Local graphs:"
+ :graph/remote-graphs "Remote graphs:"
  :export "Export"
  :export-graph "Export graph"
  :export-page "Export page"
@@ -363,7 +384,7 @@
  :all-pages "All pages"
  :all-whiteboards "All whiteboards"
  :all-files "All files"
- :remove-orphaned-pages "Remove orphaned pages"
+ :remove-orphaned-pages "Remove orphaned pages?"
  :all-journals "All journals"
  :settings "Settings"
  :settings-of-plugins "Plugins"
@@ -375,11 +396,12 @@
  :join-community "Join the community"
  :discourse-title "Our forum!"
  :help-shortcut-title "Click to check shortcuts and other tips"
- :loading "Loading"
+ :loading "Loading..."
  :parsing-files "Parsing files"
  :loading-files "Loading files"
  :login "Login"
  :logout "Logout"
+ :logout-user "Logout ({1})"
  :download "Download"
  :language "Language"
  :remove-background "Remove background"
@@ -392,16 +414,19 @@
  :help/shortcut-page-title "Keyboard shortcuts"
 
  :plugin/installed "Installed"
+ :plugin/installed-plugin "Installed plugin: {1}"
  :plugin/not-installed "Not installed"
  :plugin/installing "Installing"
  :plugin/install "Install"
  :plugin/reload "Reload"
  :plugin/update "Update"
+ :plugin/update-plugin "Update plugin: {1} - {2}"
  :plugin/check-update "Check update"
  :plugin/check-all-updates "Check all updates"
  :plugin/found-updates "New updates"
  :plugin/found-n-updates "Found {1} updates"
  :plugin/update-all-selected "Update all of selected"
+ :plugin/all-updated "All updated!"
  :plugin/updates-downloading "Downloading updates"
  :plugin/refresh-lists "Refresh lists"
  :plugin/enabled "Enabled"
@@ -412,7 +437,7 @@
  :plugin/marketplace "Marketplace"
  :plugin/downloads "Downloads"
  :plugin/stars "Stars"
- :plugin/title "Title"
+ :plugin/title "Title ({1})"
  :plugin/all "All"
  :plugin/unpacked "Unpacked"
  :plugin/delete-alert "Are you sure you want to uninstall the plugin [{1}]?"
@@ -422,8 +447,19 @@
  :plugin/restart "Restart App"
  :plugin/unpacked-tips "Select the plugin directory"
  :plugin/contribute "✨ Write and submit new plugin"
- :plugin/up-to-date "It's up to date"
+ :plugin/up-to-date "It's up to date {1}"
  :plugin/custom-js-alert "Found the custom.js file, is it allowed to execute? (If you don't understand the content of this file, it is recommended not to allow execution, which has certain security risks.)"
+ :plugin/security-warning "Plugins can access your graph and your local files, issue network requests.
+       They can also cause data corruption or loss. We're working on proper access rules for your graphs.
+       Meanwhile, make sure you have regular backups of your graphs and only install the plugins when you can read and
+       understand the source code."
+ :plugin/search-plugin "Search plugins"
+ :plugin/open-preferences "Open Preferences"
+ :plugin/open-logseq-dir "Open"
+ :plugin/remote-error "Remote error: "
+ :plugin/checking-for-updates "Checking for plugin updates ..."
+ :plugin/list-of-updates "Plugin Updates: "
+ :plugin/auto-check-for-updates "Auto check for updates"
  :plugin.install-from-file/menu-title "Install from plugins.edn"
  :plugin.install-from-file/title "Install plugins from plugins.edn"
  :plugin.install-from-file/notice "The following plugins will replace your plugins:"
@@ -443,7 +479,7 @@
  :paginates/prev "Prev"
  :paginates/next "Next"
 
- :tips/all-done "All Done"
+ :tips/all-done "All Done!"
 
  :command-palette/prompt "Type a command"
  :select/default-prompt "Select one"
@@ -467,14 +503,21 @@
  :shortcut.category/toggle "Toggle"
  :shortcut.category/whiteboard "Whiteboard"
  :shortcut.category/others "Others"
+ :shortcut.category/plugins "Plugins"
  :window/minimize "Minimize"
  :window/maximize "Maximize"
  :window/restore "Restore"
  :window/close "Close"
  :window/exit-fullscreen "Exit full screen"
 
-   ;; Commands are nested for now to stay in sync with the shortcuts system.
-   ;; Other languages should not nest keys under :commands
+ :header/toggle-left-sidebar         "Toggle left sidebar"
+ :header/search                      "Search"
+ :header/more                        "More"
+ :header/go-back                     "Go back"
+ :header/go-forward                  "Go forward"
+
+ ;; Commands are nested for now to stay in sync with the shortcuts system.
+ ;; Other languages should not nest keys under :commands
  :commands
  {:date-picker/complete         "Date picker: Choose selected day"
   :date-picker/prev-day         "Date picker: Select previous day"
@@ -586,7 +629,7 @@
   :sidebar/open-today-page        "Open today's page in the right sidebar"
   :sidebar/close-top              "Closes the top item in the right sidebar"
   :sidebar/clear                  "Clear all in the right sidebar"
-  :misc/copy                      "mod+c"
+  :misc/copy                      "Copy"
   :command-palette/toggle         "Toggle command palette"
   :graph/export-as-html           "Export public graph pages as html"
   :graph/open                     "Select graph to open"
@@ -612,7 +655,6 @@
   :ui/toggle-help                 "Toggle help"
   :ui/toggle-theme                "Toggle between dark/light theme"
   :ui/toggle-contents             "Toggle Contents in sidebar"
-   ;;  :ui/open-new-window             "Open another window"
   :command/toggle-favorite        "Add to/remove from favorites"
   :editor/open-file-in-default-app "Open file in default app"
   :editor/open-file-in-directory   "Open file in parent directory"
@@ -629,4 +671,4 @@
   :dev/show-block-data             "(Dev) Show block data"
   :dev/show-block-ast              "(Dev) Show block AST"
   :dev/show-page-data              "(Dev) Show page data"
-  :dev/show-page-ast               "(Dev) Show page AST"}}
+  :dev/show-page-ast               "(Dev) Show page AST"}}

+ 130 - 16
src/resources/dicts/es.edn

@@ -19,11 +19,10 @@
  :help/privacy "Política de privacidad"
  :help/terms "Términos"
  :help/forum-community "Foro de la comunidad"
- :help/awesome-logseq "Awesome Logseq"
  :help/shortcuts "Atajos de teclado"
  :help/shortcuts-triggers "Iniciadores"
  :help/shortcut "Atajo"
- :help/slash-autocomplete "Autocompletado Slash"
+ :help/slash-autocomplete "Autocompletado de barra '/'"
  :help/block-content-autocomplete "Autocompletado bloque de contenido (Src, Quote, Query, etc)"
  :help/reference-autocomplete "Referencia de página"
  :help/block-reference "Referencia de bloque"
@@ -78,7 +77,6 @@
  :file-rn/or-select-actions-2 ". Estas acciones no estarán disponibles una vez cierres este panel."
  :file-rn/legend "🟢 Acciones de cambio de nombre opcionales; 🟡 Cambio de nombre obligatorio para evitar el cambio de título; 🔴 Cambio destructor."
  :file-rn/close-panel "Cerrar el panel"
- :file-rn/all-action "¡Aplicar todas las acciones!"
  :file-rn/select-format "(Opción modo desarrollador, ¡peligroso!) Seccione el formato de nombre de archivo"
  :file-rn/rename "Renombrar \"{1}\" a \"{2}\""
  :file-rn/apply-rename "Aplicar la operación de cambio de nombre de archivo"
@@ -172,7 +170,6 @@
  :page-search "Buscar en la página actual"
  :graph-search "Buscar grafo"
  :home "Inicio"
- :new-page "Nueva página"
  :whiteboard "Pizarra"
  :whiteboards "Pizarras"
  :new-graph "Añadir nuevo grafo"
@@ -194,7 +191,6 @@
  :all-graphs "Lista de grafos"
  :all-pages "Lista de páginas"
  :all-files "Lista de archivos"
- :remove-orphaned-pages "Eliminar páginas huérfanas"
  :all-journals "Lista de diarios"
  :settings "Opciones"
  :settings-of-plugins "Opciones de Extensiones"
@@ -203,16 +199,15 @@
  :relaunch-confirm-to-work "Debe relanzar la aplicación para hacer que funcione. ¿Desea reiniciarla ahora?"
  :import "Importar"
  :join-community "Unirse a la comunidad"
- :discourse-title "Nuestro foro!"
+ :discourse-title "¡Nuestro foro!"
  :help-shortcut-title "Clic para ver atajos y otras sugerencias"
- :loading "Cargando"
  :parsing-files "Analizando archivos"
  :loading-files "Cargando archivos"
  :login "Iniciar sesión"
  :logout "Cerrar sesión"
  :download "Descargar"
  :language "Idioma"
- :remove-background "Remover el fondo"
+ :remove-background "Eliminar el fondo"
  :open-a-directory "Abrir un directorio local"
  :help/shortcut-page-title "Atajos de teclado"
  :plugin/installed "Instalado"
@@ -232,7 +227,6 @@
  :plugin/marketplace "Tienda"
  :plugin/downloads "Descargas"
  :plugin/stars "Estrellas"
- :plugin/title "Título"
  :plugin/all "Todo"
  :plugin/unpacked "Desempaquetado"
  :plugin/delete-alert "¿Está seguro de desinstalar la extensión [{1}]?"
@@ -242,7 +236,6 @@
  :plugin/restart "Reiniciar la aplicación"
  :plugin/unpacked-tips "Seleccionar el directorio de la extensión"
  :plugin/contribute "✨ Escribir y publicar nueva extensión"
- :plugin/up-to-date "Está actualizada"
  :plugin/custom-js-alert "Se encontró el archivo custom.js, desea permitir que se ejecute? (Si no comprende el contenido de este archivo se recomienda no permitir su ejecución dado que representa ciertos riesgos de seguridad)."
  :pdf/copy-ref "Copiar referencia"
  :pdf/copy-text "Copiar texto"
@@ -253,7 +246,6 @@
  :paginates/pages "Total {1} páginas"
  :paginates/prev "Anterior"
  :paginates/next "Siguiente"
- :tips/all-done "Todo Hecho"
  :command-palette/prompt "Escriba un comando"
  :select/default-prompt "Seleccione uno"
  :select.graph/prompt "Seleccione un grafo"
@@ -265,7 +257,6 @@
  :auto-heading "Encabezados automáticos"
  :heading "Encabezado {1}"
  :importing "Importando"
- :new-whiteboard "Nueva pizarra"
  :remove-heading "Eliminar encabezado"
  :accessibility/skip-to-main-content "Saltar a contenido principal"
  :asset/copy "Copiar imagen"
@@ -302,8 +293,6 @@
  :editor/expand-block-children "Expandir todo"
  :file/validate-existing-file-error "La página existe en otro archivo: {1}, actual... "
  :graph/all-graphs "Todos los grafos"
- :graph/local-graphs "Grafos locales"
- :graph/remote-graphs "Grafos remotos"
  :left-side-bar/create "Crear"
  :left-side-bar/new-whiteboard "Nueva pizarra"
  :notification/clear-all "Limpiar todo"
@@ -480,7 +469,6 @@
  :command.graph/re-index                            "Reindexar grafo actual"
  :command.graph/remove                              "Eliminar un grafo"
  :command.graph/save                                "Guardar grafo actual al disco"
- :command.misc/copy                                 "mod + c"
  :command.pdf/close                                 "Pdf: Cerrar documento pdf actual"
  :command.pdf/find                                  "Pdf: Buscar texto del documento pdf actual"
  :command.pdf/next-page                             "Pdf: Página siguiente del documento pdf actual"
@@ -529,4 +517,130 @@
  :window/exit-fullscreen                            "Salir de pantalla completa"
  :window/maximize                                   "Maximizar"
  :window/minimize                                   "Minimizar"
- :window/restore                                    "Restaurar"}
+ :window/restore                                    "Restaurar"
+ :loading                                           "Cargando..."
+ :logout-user                                       "Cerrar sesión ({1})"
+ :new-page                                          "Nueva página:"
+ :new-whiteboard                                    "Nueva pizarra:"
+ :remove-orphaned-pages                             "¿Eliminar páginas huérfanas?"
+ :command.misc/copy                                 "Copiar"
+ :file-rn/all-action                                "¡Aplicar todas las acciones! ({1})"
+ :graph/local-graphs                                "Grafos locales:"
+ :graph/remote-graphs                               "Grafos remotos:"
+ :header/go-back                                    "Ir hacia atrás"
+ :header/go-forward                                 "Ir haca adelante"
+ :header/more                                       "Más"
+ :header/search                                     "Buscar"
+ :header/toggle-left-sidebar                        "Alternar barra lateral izquierda"
+ :help/awesome-logseq                               "Increíble Logseq"
+ :help/title-about                                  "Acerca de"
+ :help/title-community                              "Comunidad"
+ :help/title-development                            "Desarrollo"
+ :help/title-terms                                  "Términos"
+ :help/title-usage                                  "Uso"
+ :page/illegal-page-name                            "¡Nombre de página ilegal!"
+ :page/page-already-exists                          "¡La página “{1}” ya existe!"
+ :page/whiteboard-to-journal-error                  "Las páginas de pizarra no pueden ser renombradas con título de diario..."
+ :plugin/all-updated                                "¡Todo está actualizado!"
+ :plugin/checking-for-updates                       "Comprobando por actualizaciones de extensiones ..."
+ :plugin/installed-plugin                           "Extensión instalada: {1}"
+ :plugin/list-of-updates                            "Actualizaciones de extensiones: "
+ :plugin/open-logseq-dir                            "Abrir"
+ :plugin/open-preferences                           "Abrir preferencias"
+ :plugin/remote-error                               "Error remoto: "
+ :plugin/search-plugin                              "Buscar extensiones"
+ :plugin/security-warning                           "Las extensiones pueden acceder a tu grafo y tus archivos locales..."
+ :plugin/title                                      "Título ({1})"
+ :plugin/up-to-date                                 "Está actualizado {1}"
+ :plugin/update-plugin                              "Actualizar extensión: {1} - {2}"
+ :right-side-bar/toggle-right-sidebar               "Alternar barra lateral derecha"
+ :search/blocks-in-page                             "Buscar bloques en página:"
+ :search/cache-outdated                             "La cache está desactualizada. Por favor dé clic en el botón 'Reindexar'..."
+ :search/recent                                     "Búsqueda reciente:"
+ :settings-page/preferred-pasting-file-hint         "Cuando está habilitado, al pegar una imagen del internet ..."
+ :settings-page/tab-account                         "Cuenta"
+ :shortcut.category/plugins                         "Extensiones"
+ :tips/all-done                                     "¡Todo hecho!"
+ :whiteboard/add-block-or-page                      "Añadir bloque o página"
+ :whiteboard/align-bottom                           "Alinear abajo"
+ :whiteboard/align-center-horizontally              "Alinear al centro horizontalmente"
+ :whiteboard/align-center-vertically                "Alinear al centro verticalmente"
+ :whiteboard/align-left                             "Alinear a la izquierda"
+ :whiteboard/align-right                            "Alinear a la derecha"
+ :whiteboard/align-top                              "Alinear arriba"
+ :whiteboard/arrow-head                             "Cabeza de flecha"
+ :whiteboard/auto-resize                            "Cambiar tamaño automaticamente"
+ :whiteboard/bold                                   "Negrita"
+ :whiteboard/cache-outdated                         "La cache está desactualizada. Por favor dé clic en el botón 'Reindexar'..."
+ :whiteboard/circle                                 "Círculo"
+ :whiteboard/collapse                               "Colapsar"
+ :whiteboard/color                                  "Color"
+ :whiteboard/connector                              "Conector"
+ :whiteboard/copy                                   "Copiar"
+ :whiteboard/cut                                    "Cortar"
+ :whiteboard/delete                                 "Eliminar"
+ :whiteboard/deselect-all                           "Deseleccionar todo"
+ :whiteboard/dev-print-shape-props                  "(Dev) Imprimir propiedades de figura"
+ :whiteboard/distribute-horizontally                "Distribuir horizontalmente"
+ :whiteboard/distribute-vertically                  "Distribuir verticalmente"
+ :whiteboard/draw                                   "Dibujar"
+ :whiteboard/eraser                                 "Borrar"
+ :whiteboard/expand                                 "Expandir"
+ :whiteboard/export                                 "Exportar"
+ :whiteboard/extra-large                            "Extra grande"
+ :whiteboard/extra-small                            "Extra pequeño"
+ :whiteboard/fill                                   "Rellenar"
+ :whiteboard/flip-horizontally                      "Voltear horizontalmente"
+ :whiteboard/flip-vertically                        "Voltear verticalmente"
+ :whiteboard/group                                  "Agrupar"
+ :whiteboard/highlight                              "Resaltar"
+ :whiteboard/huge                                   "Enorme"
+ :whiteboard/italic                                 "Itálica"
+ :whiteboard/large                                  "Grande"
+ :whiteboard/link                                   "Enlace"
+ :whiteboard/link-to-any-page-or-block              "Enlace a cualquier página o bloque"
+ :whiteboard/lock                                   "Bloquear"
+ :whiteboard/medium                                 "Medio"
+ :whiteboard/move-to-back                           "Mover al fondo"
+ :whiteboard/move-to-front                          "Mover al frente"
+ :whiteboard/new-block                              "Nuevo bloque:"
+ :whiteboard/new-block-no-colon                     "Nuevo bloque"
+ :whiteboard/new-page                               "Nueva página:"
+ :whiteboard/new-whiteboard                         "Nueva pizarra"
+ :whiteboard/opacity                                "Opacidad"
+ :whiteboard/open-page                              "Abrir página"
+ :whiteboard/open-page-in-sidebar                   "Abrir página en barra lateral"
+ :whiteboard/open-twitter-url                       "Abrir url de Twitter"
+ :whiteboard/open-website-url                       "Abrir url de sitio web"
+ :whiteboard/open-youtube-url                       "Abrir url de YouTube"
+ :whiteboard/pack-into-rectangle                    "Empacar en un rectángulo"
+ :whiteboard/pan                                    "Mover"
+ :whiteboard/paste                                  "Pegar"
+ :whiteboard/paste-as-link                          "Pegar como enlace"
+ :whiteboard/rectangle                              "Recargar"
+ :whiteboard/redo                                   "Rehacer"
+ :whiteboard/references                             "Referencias"
+ :whiteboard/reload                                 "Recargar"
+ :whiteboard/remove-link                            "Eliminar enlace"
+ :whiteboard/scale-level                            "Escalar nivel"
+ :whiteboard/search-only-blocks                     "Buscar solo bloques"
+ :whiteboard/search-only-pages                      "Buscar solo páginas"
+ :whiteboard/select                                 "Seleccionar"
+ :whiteboard/select-all                             "Seleccionar todo"
+ :whiteboard/select-custom-color                    "Seleccionar color personalizado"
+ :whiteboard/shape                                  "Figura"
+ :whiteboard/shape-quick-links                      "Enlaces rápidos de figura"
+ :whiteboard/small                                  "Pequeño"
+ :whiteboard/start-typing-to-search                 "Comienza a escribir para buscar..."
+ :whiteboard/stroke-type                            "Tipo de línea"
+ :whiteboard/text                                   "Texto"
+ :whiteboard/triangle                               "Triángulo"
+ :whiteboard/twitter-url                            "url de Twitter"
+ :whiteboard/undo                                   "Deshacer"
+ :whiteboard/ungroup                                "Desagrupar"
+ :whiteboard/unlock                                 "Desbloquear"
+ :whiteboard/website-url                            "url de sitio web"
+ :whiteboard/youtube-url                            "url de YouTube"
+ :whiteboard/zoom-in                                "Acercar"
+ :whiteboard/zoom-out                               "Alejar"
+ :whiteboard/zoom-to-fit                            "Zoom para ajustar"}

+ 2 - 18
src/resources/dicts/fr.edn

@@ -24,7 +24,6 @@
  :italics "Italique"
  :highlight "Surligner"
  :strikethrough "Barré"
- :code "Code"
  :right-side-bar/help "Aide"
  :right-side-bar/switch-theme "Basculer sur le thème {1}"
  :right-side-bar/contents "Contenus"
@@ -66,7 +65,6 @@
  :export-json "Exporter au format JSON"
  :search/publishing "Rechercher"
  :search "Rechercher ou Créer la Page"
- :new-page "Nouvelle page"
  :graph "Graphe"
  :all-pages "Toutes les pages"
  :all-files "Tous les fichiers"
@@ -74,7 +72,6 @@
  :settings "Préférences"
  :import "Importer"
  :join-community "Rejoindre la communauté"
- :loading "Chargement en cours"
  :parsing-files "Analyse des fichiers"
  :loading-files "Chargement des fichiers"
  :download "Télécharger"
@@ -105,7 +102,6 @@
  :logout "Déconnexion"
  :more "Plus"
  :new-graph "Ajouter un nouveau graphe"
- :new-whiteboard "Nouveau tableau blanc"
  :open-a-directory "Ouvrir un dossier local"
  :open-new-window "Nouvelle fenêtre"
  :page-search "Chercher dans la page en cours"
@@ -116,15 +112,14 @@
  :re-index-multiple-windows-warning "Vous devez d'abord fermer les autres fenêtres avant de réindexer"
  :relaunch-confirm-to-work "Il est nécessaire de relancer l'application pour que ça fonctionne. Voulez-vous redémarrer ?"
  :remove-heading "Retirer les entêtes"
- :remove-orphaned-pages "Supprimer les pages orphelines"
  :save "Sauver"
  :settings-of-plugins "Extensions"
  :sync-from-local-changes-detected "Le rafraîchissement va mettre à jour le contenu de Logseq en fonction des modifications réalisées sur les fichiers locaux. Voulez-vous continuer ?"
  :sync-from-local-files "Rafraîchir"
  :sync-from-local-files-detail "Importer les changements depuis les fichiers locaux"
  :themes "Thèmes"
- :type "Type"
  :untitled "Sans titre"
+ :type "Type"
  :accessibility/skip-to-main-content "Aller au contenu principal"
  :asset/confirm-delete "Êtes-vous sûr de vouloir supprimer {1} ?"
  :asset/copy "Copier l'image"
@@ -142,7 +137,6 @@
  :color/yellow "Jaune"
  :command-palette/prompt "Tapez un commande"
  :content/copy-block-emebed "Copier l'intégration du bloc"
- :file-rn/all-action "Appliquer toutes les actions !"
  :file-rn/apply-rename "Appliquer le renommage"
  :file-rn/close-panel "Fermer le panneau"
  :file-rn/confirm-proceed "Mettre à jour le format !"
@@ -168,16 +162,12 @@
  :file-rn/suggest-rename "Action requise : "
  :file-rn/unreachable-title "Attention ! la page deviendra {1} sous le format actuel, à moins que vous n'ayez modifié la propriété `title::`"
  :graph/all-graphs "Tous les graphes"
- :graph/local-graphs "Locaux graphes"
  :graph/persist "Logseq synchronise son statut local, veuillez patienter quelques secondes."
  :graph/persist-error "La synchronisation interne a échoué."
- :graph/remote-graphs "Graphes distants"
  :graph/save "Enregistrement ..."
  :graph/save-error "Enregistrement échoué"
  :graph/save-success "Enregistrement réussi"
- :help/awesome-logseq "Awesome Logseq"
  :help/forum-community "Forum communautaire"
- :help/roadmap "Roadmap"
  :help/shortcut-page-title "Raccourcis clavier"
  :help/start "Démarrage"
  :left-side-bar/create "Créer"
@@ -207,7 +197,6 @@
  :page/updated-at "Mis à jour le"
  :page/version-history "Consulter l'historique de la page"
  :paginates/next "Suiv."
- :paginates/pages "Total {1} pages"
  :paginates/prev "Préc."
  :pdf/copy-ref "Copier la référence"
  :pdf/copy-text "Copier le texte"
@@ -236,11 +225,9 @@
  :plugin/reload "Recharger"
  :plugin/restart "Redémarrer l'application"
  :plugin/stars "Étoiles"
- :plugin/title "Titre"
  :plugin/uninstall "Désinstaller"
  :plugin/unpacked "Décompressée"
  :plugin/unpacked-tips "Sélectionnez le dossier de l'extension"
- :plugin/up-to-date "C'est à jour"
  :plugin/update "Mettre à jour"
  :plugin/update-available "Mise à jour disponible"
  :plugin/updating "Mise à jour en cours"
@@ -282,7 +269,6 @@
  :settings-page/enable-all-pages-public "Toutes les pages publiques lors de la publication"
  :settings-page/enable-block-time "Horodatage de bloc"
  :settings-page/enable-flashcards "Cartes-mémoire"
- :settings-page/enable-journals "Journals"
  :settings-page/enable-shortcut-tooltip "Activer les astuces sur les raccourcis"
  :settings-page/enable-tooltip "Astuces"
  :settings-page/enable-whiteboards "Tableaux blancs"
@@ -307,7 +293,6 @@
  :settings-page/tab-general "Général"
  :settings-page/tab-version-control "Contrôle de version"
  :text/image "Image"
- :tips/all-done "Tout terminé"
  :updater/new-version-install "Une nouvelle version a été téléchargée."
  :updater/quit-and-install "Relancez pour installer"
  :whiteboard/link-whiteboard-or-block "Lier un tablau blanc/page/bloc"
@@ -325,7 +310,6 @@
  :settings-page/clear-cache-warning "Vider le cache supprimera les graphiques ouverts. Vous perdrez les modifications non enregistrées."
  :settings-page/disable-sentry-desc "Logseq ne collectera jamais votre base de données de graphes locale ni ne vendra vos données."
 
- :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"
@@ -428,7 +412,6 @@
  :command.graph/re-index  "Réindexer le graph actuel"
  :command.graph/remove  "Retirer un graphe"
  :command.graph/save  "Enregistrer le graphe sur le disque"
- :command.misc/copy  "mod+c"
  :command.pdf/close  "PDF: Fermer le document PDF"
  :command.pdf/find  "PDF: Rechercher du texte dans le document"
  :command.pdf/next-page  "PDF: Page suivante du document en cours"
@@ -449,6 +432,7 @@
  :shortcut.category/block-command-editing  "Édition de commande de bloc"
  :shortcut.category/block-editing  "Édition de bloc en générale"
  :shortcut.category/block-selection  "Sélection de bloc (appuyer sur Esc pour quitter la sélection)"
+ :shortcut.category/formatting "Formats"
  :shortcut.category/navigating  "Navigation"
  :shortcut.category/others  "Autres"
  :shortcut.category/toggle  "Basculer"

+ 1 - 16
src/resources/dicts/it.edn

@@ -10,7 +10,6 @@
  :on-boarding/new-graph-desc-5 "/logseq - contiene i dati di configurazione, custom.css, e alcuni metadati."
  :help/start "Per iniziare"
  :help/about "Informazioni su Logseq"
- :help/roadmap "Roadmap"
  :help/bug "Segnala un problema"
  :help/feature "Richiedi una funzionalità"
  :help/changelog "Registro delle modifiche"
@@ -18,7 +17,6 @@
  :help/docs "Documentazione"
  :help/privacy "Politica sulla riservatezza"
  :help/terms "Termini di Servizio"
- :help/awesome-logseq "Awesome Logseq"
  :help/shortcuts "Scorciatoie da tastiera"
  :help/shortcuts-triggers "Attivazione delle scorciatoie"
  :help/shortcut "Scorciatoia"
@@ -47,7 +45,6 @@
  :right-side-bar/block-ref "Riferimento di Blocco"
  :right-side-bar/graph-view "Vista del grafico"
  :right-side-bar/all-pages "Tutte le pagine"
- :right-side-bar/flashcards "Flashcard"
  :right-side-bar/new-page "Nuova pagina"
  :right-side-bar/show-journals "Mostra diari"
  :left-side-bar/journals "Diario"
@@ -116,7 +113,6 @@
  :settings-page/developer-mode-desc "La modalità sviluppatore aiuta i contributori e gli sviluppatori di estensioni a testare le loro integrazioni con Logseq in modo più efficiente."
  :settings-page/current-version "Versione attuale"
  :settings-page/tab-general "Generale"
- :settings-page/tab-editor "Editor"
  :settings-page/tab-version-control "Controllo di versione"
  :settings-page/tab-advanced "Avanzate"
  :settings-page/plugin-system "Sistema di plugin"
@@ -128,7 +124,6 @@
  :delete "Elimina"
  :save "Salva"
  :type "Tipo"
- :host "Host"
  :port "Porta"
  :re-index "Re-indicizza"
  :re-index-detail "Ricostruisci il grafo"
@@ -143,7 +138,6 @@
  :search "Cerca o crea una pagina"
  :page-search "Cerca nella pagina corrente"
  :graph-search "Cerca nel grafo"
- :new-page "Nuova pagina"
  :new-graph "Aggiungi nuovo grafo"
  :graph "Grafo"
  :graph/persist "Logseq sta sincronizzando lo stato interno, per favore attendi alcuni secondi."
@@ -163,17 +157,15 @@
  :all-graphs "Tutti i grafi"
  :all-pages "Tutte le pagine"
  :all-files "Tutti i file"
- :remove-orphaned-pages "Rimuovi pagine orfane"
  :all-journals "Tutte le pagine di diario"
  :settings "Impostazioni"
  :settings-of-plugins "Impostazioni plugin"
- :plugins "Plugin"
+ :plugins "Plugins"
  :themes "Temi"
  :relaunch-confirm-to-work "È necessario riavviare l'app per farla funzionare. Vuoi riavviarla ora?"
  :import "Importa"
  :join-community "Unisciti alla comunità"
  :help-shortcut-title "Clicca per conoscere le scorciatoie e altri suggerimenti"
- :loading "Caricamento"
  :parsing-files "Analisi dei file"
  :loading-files "Caricamento dei file"
  :login "Accedi"
@@ -202,7 +194,6 @@
  :plugin/marketplace "Libreria"
  :plugin/downloads "Numero di scaricamenti"
  :plugin/stars "Stelle"
- :plugin/title "Titolo"
  :plugin/all "Tutti"
  :plugin/unpacked "Non pacchettizzati"
  :plugin/delete-alert "Sei sicuro di voler disinstallare [{1}]?"
@@ -212,7 +203,6 @@
  :plugin/restart "Riavvia app"
  :plugin/unpacked-tips "Seleziona la cartella del plugin"
  :plugin/contribute "✨ Sviluppa e sottoponici un nuovo plugin"
- :plugin/up-to-date "È aggiornato"
  :plugin/custom-js-alert "Trovato il file custom.js, è consentito eseguirlo? (Se non si comprende il contenuto di questo file, si consiglia di non consentire l'esecuzione, che presenta alcuni rischi per la sicurezza.)"
 
  :pdf/copy-ref "Copia riferimenti"
@@ -227,8 +217,6 @@
  :paginates/prev "Precedente"
  :paginates/next "Successivo"
 
- :tips/all-done "Completato"
-
  :command-palette/prompt "Digita un comando"
  :select/default-prompt "Selezionane uno"
  :select.graph/prompt "Seleziona un grafo"
@@ -302,7 +290,6 @@
  :command.editor/cut                     "Taglia"
  :command.editor/undo                    "Annulla"
  :command.editor/redo                    "Rifai"
- :command.editor/insert-link             "Link HTML"
  :command.editor/select-all-blocks       "Seleziona tutti i blocchi"
  :command.editor/zoom-in                 "Ingrandisci blocco di modifica / Avanti altrimenti"
  :command.editor/zoom-out                "Rimpicciolisci il blocco di modifica / Indietro altrimenti"
@@ -315,7 +302,6 @@
  :command.search/re-index                "Ricostruisci indice di ricerca"
  :command.sidebar/open-today-page        "Apri la pagina di oggi nella barra laterale destra"
  :command.sidebar/clear                  "Pulisci tutto nella barra laterale destra"
- :command.misc/copy                      "mod+c"
  :command.command-palette/toggle         "Attiva/disattiva tavolozza comandi"
  :command.graph/open                     "Seleziona il diagramma da aprire"
  :command.graph/remove                   "Rimuovi un diagramma"
@@ -337,7 +323,6 @@
  :command.ui/toggle-help                 "Attiva/disattiva aiuto"
  :command.ui/toggle-theme                "Passa dal tema scuro a quello chiaro"
  :command.ui/toggle-contents             "Attiva/disattiva i contenuti nella barra laterale"
-             ;;  :command.ui/open-new-window             "Apri un'altra finestra"
  :command.command/toggle-favorite        "Aggiungi a/rimuovi dai preferiti"
  :command.editor/open-file-in-default-app "Apri file nell'app predefinita"
  :command.editor/open-file-in-directory   "Apri file nella directory principale"

+ 0 - 13
src/resources/dicts/ja.edn

@@ -18,7 +18,6 @@
  :help/docs "ドキュメント"
  :help/privacy "プライバシーポリシー"
  :help/terms "利用規約"
- :help/awesome-logseq "Awesome Logseq"
  :help/shortcuts "キーボードショートカット"
  :help/shortcuts-triggers "トリガー"
  :help/shortcut "ショートカット"
@@ -28,8 +27,6 @@
  :help/block-reference "ブロック参照"
  :help/open-link-in-sidebar "サイドバーでリンクを開く"
  :more "詳細"
- :search/result-for "Search result for "
- :search/items "items"
  :search/page-names "ページ名で検索"
  :help/context-menu "ブロックのコンテキストメニュー"
  :help/markdown-syntax "マークダウン文法"
@@ -147,7 +144,6 @@
  :search "検索/新規ページ名"
  :page-search "現在のページを検索"
  :graph-search "グラフを検索"
- :new-page "新規ページ"
  :new-graph "新規グラフを追加"
  :graph "グラフ"
  :graph/persist "Logseq の内部状態を同期中です。少々お待ちください。"
@@ -156,8 +152,6 @@
  :graph/save-success "保存に成功しました"
  :graph/save-error "保存に失敗しました"
  :graph/all-graphs "全グラフ"
- :graph/local-graphs "ローカルグラフ"
- :graph/remote-graphs "リモートグラフ"
  :export "エクスポート"
  :export-graph "グラフをエクスポート"
  :export-page "ページをエクスポート"
@@ -170,7 +164,6 @@
  :all-graphs "全グラフ"
  :all-pages "全ページ"
  :all-files "全ファイル"
- :remove-orphaned-pages "孤立ページを削除"
  :all-journals "全日誌"
  :settings "設定"
  :settings-of-plugins "プラグイン設定"
@@ -180,7 +173,6 @@
  :import "インポート"
  :join-community "コミュニティへ参加"
  :help-shortcut-title "クリックしてショートカットと他のTipsを確認"
- :loading "ロード中"
  :parsing-files "ファイル解析中"
  :loading-files "ファイルロード中"
  :login "ログイン"
@@ -212,7 +204,6 @@
  :plugin/marketplace "マーケットプレース"
  :plugin/downloads "ダウンロード"
  :plugin/stars "スター"
- :plugin/title "タイトル"
  :plugin/all "全て"
  :plugin/unpacked "展開済"
  :plugin/delete-alert "プラグイン [{1}] をアンインストールしてもよいですか?"
@@ -222,7 +213,6 @@
  :plugin/restart "アプリを再起動"
  :plugin/unpacked-tips "プラグインディレクトリを選択"
  :plugin/contribute "✨ 新規プラグインの作成とサブミット"
- :plugin/up-to-date "最新の状態です"
  :plugin/custom-js-alert "custom.js ファイルを見つけました。実行を許可しますか? (もしあなたがこのファイルの内容を理解していない場合、セキュリティのリスクがあるため許可しないことをお勧めします。)"
 
  :pdf/copy-ref "参照をコピー"
@@ -237,8 +227,6 @@
  :paginates/prev "前"
  :paginates/next "次"
 
- :tips/all-done "All Done"
-
  :command-palette/prompt "コマンドを入力"
  :select/default-prompt "選択してください"
  :select.graph/prompt "グラフを選んでください"
@@ -289,7 +277,6 @@
  :command.go/next-journal                 "次の日誌へ移動"
  :command.go/prev-journal                 "前の日誌へ移動"
  :command.go/keyboard-shortcuts           "キーボードショートカットへ移動"
-             ;;  :command.ui/open-new-window              "別のウィンドウを開く"
  :command.go/search-in-page               "ページ内を検索"
  :command.go/electron-find-in-page        "ページ内で文字列検索"
  :command.go/electron-jump-to-the-next   "文字列検索で次を検索"

+ 1 - 14
src/resources/dicts/ko.edn

@@ -18,7 +18,6 @@
  :help/docs "안내 문서"
  :help/privacy "개인정보 보호정책"
  :help/terms "약관"
- :help/awesome-logseq "Awesome Logseq"
  :help/shortcuts "키보드 단축키"
  :help/shortcuts-triggers "트리거"
  :help/shortcut "단축키"
@@ -143,7 +142,6 @@
  :search "페이지를 검색하거나 생성"
  :page-search "현재 페이지에서 검색"
  :graph-search "그래프 검색"
- :new-page "새 페이지"
  :new-graph "새 그래프"
  :graph "그래프"
  :graph/persist "Logseq가 내부 상태를 동기화 중입니다. 잠시만 기다려주십시오."
@@ -163,7 +161,6 @@
  :all-graphs "모든 그래프"
  :all-pages "모든 페이지"
  :all-files "모든 파일"
- :remove-orphaned-pages "고립된 페이지 삭제"
  :all-journals "모든 일지"
  :settings "설정"
  :settings-of-plugins "플러그인 설정"
@@ -173,7 +170,6 @@
  :import "불러오기"
  :join-community "커뮤니티에 참여"
  :help-shortcut-title "다른 단축키와 도움말 확인하기"
- :loading "로딩 중"
  :parsing-files "파일 파싱 중"
  :loading-files "파일 로딩 중"
  :login "로그인"
@@ -202,7 +198,6 @@
  :plugin/marketplace "마켓플레이스"
  :plugin/downloads "다운로드"
  :plugin/stars "스타"
- :plugin/title "제목"
  :plugin/all "전체"
  :plugin/unpacked "압축 해제됨"
  :plugin/delete-alert "다음 [{1}] 플러그인을 삭제하시겠습니까?"
@@ -212,7 +207,6 @@
  :plugin/restart "앱 다시 시작"
  :plugin/unpacked-tips "플러그인 디렉토리 열기"
  :plugin/contribute "✨ 새 플러그인을 만들고 기여하기"
- :plugin/up-to-date "최신 상태입니다."
  :plugin/custom-js-alert "custom.js 파일을 감지했습니다. 실행을 허용하겠습니까? (파일의 내용을 이해하지 못한다면 보안과 안전 상의 이유로 실행하지 않는 것이 권장됩니다."
 
  :pdf/copy-ref "레퍼런스 복사하기"
@@ -227,8 +221,6 @@
  :paginates/prev "이전"
  :paginates/next "다음"
 
- :tips/all-done "모두 완료"
-
  :command-palette/prompt "커맨드를 입력하십시오"
  :select/default-prompt "하나를 선택하십시오"
  :select.graph/prompt "그래프를 선택하십시오."
@@ -243,7 +235,6 @@
  :discourse-title "포럼!"
  :heading "헤딩 {1}"
  :importing "불러오는 중"
- :new-whiteboard "새 화이트보드"
  :remove-heading "헤딩 제거"
  :untitled "제목 없음"
  :accessibility/skip-to-main-content "메인 컨텐츠로 건너뛰기"
@@ -268,7 +259,6 @@
  :on-boarding/welcome-whiteboard-modal-start "화이트보드 시작하기"
  :on-boarding/welcome-whiteboard-modal-title "새 캔버스"
  :file/validate-existing-file-error "페이지가 이미 존재합니다: {1}, 현재 파일: {2}. 하나만 남겨놓은 뒤 그래프의 인덱스를 재생성하십시오."
- :file-rn/all-action "모든 작업 적용!"
  :file-rn/apply-rename "파일명 변경"
  :file-rn/close-panel "패널 닫기"
  :file-rn/confirm-proceed "포맷 업데이트!"
@@ -292,8 +282,6 @@
  :file-rn/suggest-rename "작업 필요: "
  :file-rn/unreachable-title "경고! 이 페이지는 `title::` 속성이 설정되어 있지 않는 한, 현재 파일명 포맷에 따라 {1}(으)로 변경될 것입니다."
  :graph/all-graphs "모든 그래프"
- :graph/local-graphs "로컬 그래프"
- :graph/remote-graphs "원격 그래프"
  :help/forum-community "포럼 커뮤니티"
  :left-side-bar/create "생성"
  :left-side-bar/new-whiteboard "새 화이트보드"
@@ -372,7 +360,6 @@
  :command.go/next-journal                 "다음 일지로 이동"
  :command.go/prev-journal                 "이전 일지로 이동"
  :command.go/keyboard-shortcuts           "키보드 단축키로 이동"
-             ;;  :command.ui/open-new-window              "새 창 열기"
  :command.go/search-in-page               "페이지 안에서 검색"
  :command.ui/toggle-document-mode         "문서 모드 토글"
  :command.ui/toggle-contents              "목차 토글"
@@ -386,7 +373,7 @@
  :command.editor/bold                     "볼드체"
  :command.editor/italics                  "이탤릭체"
  :command.editor/insert-link              "링크 삽입"
- :command.editor/highlight                "하이라이트"
+ :command.editor/highlight                "하이라"
  :command.editor/undo                     "실행 취소"
  :command.editor/redo                     "다시 실행"
  :command.editor/copy                     "복사"

+ 135 - 27
src/resources/dicts/nb-no.edn

@@ -46,7 +46,6 @@
  :right-side-bar/block-ref "Blokkreferanse"
  :right-side-bar/graph-view "Grafvisning"
  :right-side-bar/all-pages "Alle sider"
- :right-side-bar/flashcards "Flashcards"
  :right-side-bar/new-page "Ny side"
  :right-side-bar/separator "Høyre sidestolpe størrelsesendring"
  :right-side-bar/show-journals "Vis dagbøker"
@@ -114,12 +113,10 @@
  :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/tab-general "Generelt"
- :settings-page/tab-editor "Editor"
  :settings-page/tab-version-control "Versjonskontroll"
  :settings-page/tab-advanced "Avansert"
  :settings-page/custom-global-configuration "Egendefinert global konfigurasjon"
  :settings-page/edit-global-config-edn "Rediger global config.edn"
- :settings-page/enable-flashcards "Flashcards"
  :settings-page/export-theme "Eksporter tema"
  :settings-page/sync "Synkronisering"
  :settings-page/tab-features "Funksjoner"
@@ -137,7 +134,6 @@
  :search "Søk eller Opprett Side"
  :page-search "Søk i denne siden"
  :graph-search "Søk graf"
- :new-page "Ny side"
  :new-graph "Legg til ny graf"
  :graph "Graf"
  :export "Eksport"
@@ -152,7 +148,6 @@
  :all-graphs "Alle grafer"
  :all-pages "Alle sider"
  :all-files "Alle filer"
- :remove-orphaned-pages "Fjern foreldreløse sider"
  :all-journals "Alle dagbøker"
  :settings "Innstillinger"
  :plugins "Utvidelser"
@@ -161,7 +156,6 @@
  :import "Importer"
  :join-community "Bli med i samfunnet"
  :help-shortcut-title "Klikk for å sjekke snarveier og andre tips"
- :loading "Laster"
  :parsing-files "Analyserer filer"
  :loading-files "Laster filer"
  :login "Logg inn"
@@ -189,7 +183,6 @@
  :plugin/marketplace "Markedsplass"
  :plugin/downloads "Nedlastinger"
  :plugin/stars "Stjerner"
- :plugin/title "Tittel"
  :plugin/all "Alle"
  :plugin/unpacked "Utpakket"
  :plugin/delete-alert "Vil du avinstallere utvidelse [{1}]?"
@@ -199,7 +192,6 @@
  :plugin/restart "Start App på nytt"
  :plugin/unpacked-tips "Velg mappe for utvidelse"
  :plugin/contribute "✨ Skriv og send inn en ny utvidelse"
- :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"
@@ -214,8 +206,6 @@
  :paginates/prev "Forrige"
  :paginates/next "Neste"
 
- :tips/all-done "Alt ferdig"
-
  :command-palette/prompt "Skriv en kommando"
  :select/default-prompt "Velg en"
  :select.graph/prompt "Velg en graf"
@@ -225,21 +215,17 @@
  :file-sync/other-user-graph "Nåværende lokal graf er bundet til annen brukers fjerngraf. Kan ikke begynne å synkronisere."
  :file-sync/graph-deleted "Nåværende fjerngraf er slettet"
  :host "Vert"
- :port "Port"
  :re-index-discard-unsaved-changes-warning "Reindeksering vil forkaste nåværende graf, og deretter prosessere alle filene på nytt slik de er på disk akkurat nå. Du vil miste ulagrede endringer, og det kan ta litt tid. Forsette?"
  :re-index-multiple-windows-warning "Du må lukke de andre vinduene før du kan reindeksere denne grafen"
  :save "Lagrer..."
  :settings-of-plugins "Innstillinger for utvidelser"
  :sync-from-local-changes-detected "Oppfrisk oppdager og prosesserer filer på disk som er modifiserte og avviker fra sideinnholdet som vises i Logseq. Fortsett?"
- :type "Type"
  :graph/persist "Logeq synkroniserer intern status, vennligst vent i flere sekunder."
  :graph/persist-error "Intern status synk feilet"
  :graph/save "Lagrer..."
  :graph/save-error "Lagring feilet"
  :graph/save-success "Lagring vellykket"
  :graph/all-graphs "Alle grafer"
- :graph/local-graphs "Lokale grafer"
- :graph/remote-graphs "Fjerngrafer"
  :page/copy-page-url "Kopier side URL"
  :page/open-backup-directory "Åpne mappe med sidens sikkerhetskopier"
  :plugin/not-installed "Ikke installert"
@@ -247,7 +233,6 @@
  :settings-page/network-proxy "Nettverksproxy"
  :settings-page/plugin-system "System for utvidelser"
  :discourse-title "Vårt forum!"
- :importing "Import"
  :asset/copy "Kopier bilde"
  :asset/delete "Slett bilde"
  :asset/maximize "Maksimer bilde"
@@ -257,7 +242,6 @@
  :all-whiteboards "Alle whiteboard"
  :auto-heading "Automatisk overskrift"
  :heading "Overskrift {1}"
- :new-whiteboard "Nytt whiteboard"
  :remove-heading "Fjern overskrift"
  :untitled "Uten navn"
  :accessibility/skip-to-main-content "Hopp til hovedinnhold"
@@ -290,7 +274,6 @@
  :editor/delete-selection "Slett valgte blokker"
  :editor/expand-block-children "Utvid alle"
  :file/validate-existing-file-error "Siden eksisterer allerede i en annen fil: {1}, nåværen..."
- :file-rn/all-action "Utfør alle Handlinger!"
  :file-rn/apply-rename "Utfør omdøping av filen"
  :file-rn/close-panel "Lukk Panel"
  :file-rn/confirm-proceed "Oppdater format!"
@@ -338,16 +321,14 @@
  :plugin/update-all-selected "Oppdater alle valgte"
  :plugin/updates-downloading "Laster ned oppdateringer"
  :plugin.install-from-file/menu-title "Installer fra plugins.edn"
- :plugin.install-from-file/notice "Følgende plugins vil erstatte dine plugins:"
- :plugin.install-from-file/success "Alle plugins er installert!"
- :plugin.install-from-file/title "Installer plugins fra plugins.edn"
+ :plugin.install-from-file/notice "Følgende utvidelser vil erstatte dine utvidelser:"
+ :plugin.install-from-file/success "Alle utvidelser er installert!"
+ :plugin.install-from-file/title "Installer utvidelser fra plugins.edn"
  :right-side-bar/history "(Dev) Angre/Gjør om logg"
- :right-side-bar/whiteboards "Whiteboards"
  :search/items "elementer"
  :search-item/block "Blokk"
  :search-item/file "Fil"
  :search-item/page "Side"
- :search-item/whiteboard "Whiteboard"
  :select/default-select-multiple "Velg en eller flere"
  :settings-page/alpha-features "Alpha funksjoner"
  :settings-page/auto-expand-block-refs "Utvid blokkreferanser automatisk når zoomet inn..."
@@ -356,7 +337,6 @@
  :settings-page/custom-date-format-warning "Re-indeksering kreves! Eksisterernde dagbokreferanse vi..."
  :settings-page/disable-sentry-desc "Logseq vil aldri samle inn dine lokale graf sin databas..."
  :settings-page/edit-setting "Rediger"
- :settings-page/enable-whiteboards "Whiteboards"
  :settings-page/filename-format "Filnavn format"
  :settings-page/login-prompt "For å få tilgang til nye funksjoner før alle andre må du..."
  :settings-page/preferred-pasting-file "Foretrekk innliming av fil"
@@ -477,14 +457,12 @@
  :command.graph/remove "Fjern en graf"
  :command.graph/save "Lagre nåværende graf til disk"
  :command.graph/re-index "Reindekser nåværende graf"
- :command.misc/copy "mod+c"
  :command.pdf/close "Lukk nåværende pdf leser"
  :command.pdf/next-page "Neste side i nåværende pdf dok"
  :command.pdf/previous-page "Forrige side i nåværende pdf dok"
  :command.sidebar/clear "Fjern alt i høyre sidestolpe"
  :command.sidebar/open-today-page "Åpne dagens side i høyre sidestolpe"
  :command.ui/goto-plugins "Gå til dashbord for utvidelser"
-             ;;  :command.ui/open-new-window "Åpne et nytt vindu"
  :command.ui/select-theme-color "Velg tilgjengelige temafarger"
  :command.ui/toggle-cards "Veksle kort"
  :command.dev/show-block-ast "(Dev) Vis blokk AST"
@@ -501,7 +479,7 @@
  :command.pdf/find "Pdf: Søk tekst i nåværende pdf doc"
  :command.sidebar/close-top "Lukker øverste objekt i høyre sidestolpe"
  :command.ui/clear-all-notifications "Fjern alle varsler"
- :command.ui/install-plugins-from-file "Installer plugins fra plugins.edn"
+ :command.ui/install-plugins-from-file "Installer utvidelser fra plugins.edn"
  :command.whiteboard/bring-forward "Flytt fremover"
  :command.whiteboard/bring-to-front "Flytt fremst"
  :command.whiteboard/connector "Koblingsverktøy"
@@ -526,4 +504,134 @@
  :command.whiteboard/zoom-out "Zoom ut"
  :command.whiteboard/zoom-to-fit "Zoom til tegning"
  :command.whiteboard/zoom-to-selection "Zoom for å passe seleksjonen"
- :shortcut.category/whiteboard "Whiteboard"}
+ 
+ :home "Hjem"
+ :importing "Importerer"
+ :port "Port"
+ :toggle-theme "Bytt tema"
+ :type "Type"
+ :whiteboard "Whiteboard"
+ :whiteboards "Whiteboard"
+ :command.misc/copy "Kopier"
+ :file-sync/rsapi-cannot-upload-err "Kunne ikke starte synkronisering, vennligst sjekk om..."
+ :header/go-back "Gå tilbake"
+ :header/go-forward "Gå fremover"
+ :header/more "Mer"
+ :header/search "Søk"
+ :header/toggle-left-sidebar "Veksle venstre sidestolpe"
+ :help/title-about "Om"
+ :help/title-community "Samfunn"
+ :help/title-development "Utvikling"
+ :help/title-terms "Betingelser"
+ :help/title-usage "Bruk"
+ :plugin/all-updated "Alt er oppdatert!"
+ :plugin/checking-for-updates "Ser etter oppdateringer for utvidelser"
+ :plugin/list-of-updates "Oppdateringer for utvidelser: "
+ :plugin/open-logseq-dir "Åpne"
+ :plugin/open-preferences "Åpne Innstillinger"
+ :plugin/remote-error "Ekstern feil: "
+ :plugin/search-plugin "Søk utvidelser"
+ :plugin/security-warning "Utvidelser kan få tilgang til din graf og dine lokale filer..."
+ :right-side-bar/flashcards "Flashcards"
+ :right-side-bar/toggle-right-sidebar "Veksle høyre sidestolpe"
+ :right-side-bar/whiteboards "Whiteboards"
+ :search/blocks-in-page "Søk blokker på side:"
+ :search/cache-outdated "Cache er utdatert. Vennligst klikk 'Re-indekser' knappen"
+ :search/recent "Nylige søk:"
+ :search-item/whiteboard "Whiteboard"
+ :settings-page/enable-flashcards "Flashcards"
+ :settings-page/enable-whiteboards "Whiteboards"
+ :settings-page/native-titlebar "Native tittellinje"
+ :settings-page/native-titlebar-desc "Skrur på Windows native tittellinje på vinduet..."
+ :settings-page/preferred-pasting-file-hint "Når skrudd på gir dette et hint når du limer inn et bile fra internett..."
+ :settings-page/tab-editor "Editor"
+ :shortcut.category/plugins "Utvidelser"
+ :shortcut.category/whiteboard "Whiteboard"
+ :whiteboard/add-block-or-page "Legg til blokk eller side"
+ :whiteboard/align-bottom "Bunnjuster"
+ :whiteboard/align-center-horizontally "Sentrer horisontalt"
+ :whiteboard/align-center-vertically "Sentrer vertikalt"
+ :whiteboard/align-left "Venstrejuster"
+ :whiteboard/align-right "Høyrejuster"
+ :whiteboard/align-top "Toppjuster"
+ :whiteboard/arrow-head "Pilhode"
+ :whiteboard/auto-resize "Automatisk endre størrelse"
+ :whiteboard/bold "Fet"
+ :whiteboard/cache-outdated "Cache er utdatert. Vennligst klikk 'Re-indekser' knappen"
+ :whiteboard/circle "Sirkel"
+ :whiteboard/collapse "Trekk sammen"
+ :whiteboard/color "Farge"
+ :whiteboard/connector "Kobling"
+ :whiteboard/copy "Kopier"
+ :whiteboard/cut "Klipp ut"
+ :whiteboard/delete "Slett"
+ :whiteboard/deselect-all "Fjern alle valg"
+ :whiteboard/dev-print-shape-props "(Dev) Print form-innstillinger"
+ :whiteboard/distribute-horizontally "Distribuer horisontalt"
+ :whiteboard/distribute-vertically "Distribuer vertikalt"
+ :whiteboard/draw "Tegn"
+ :whiteboard/eraser "Viskelær"
+ :whiteboard/expand "Utvid"
+ :whiteboard/export "Eksporter"
+ :whiteboard/extra-large "Ekstra Stor"
+ :whiteboard/extra-small "Ekstra Liten"
+ :whiteboard/fill "Fyll"
+ :whiteboard/flip-horizontally "Vend horisontalt"
+ :whiteboard/flip-vertically "Vend vertikalt"
+ :whiteboard/group "Gruppe"
+ :whiteboard/highlight "Markering"
+ :whiteboard/huge "Enorm"
+ :whiteboard/italic "Kursiv"
+ :whiteboard/large "Stor"
+ :whiteboard/link "Lenke"
+ :whiteboard/link-to-any-page-or-block "Lenke til hvilkensomhelst side eller blokk"
+ :whiteboard/lock "Lås"
+ :whiteboard/medium "Medium"
+ :whiteboard/move-to-back "Flytt bakover"
+ :whiteboard/move-to-front "Flytt fremover"
+ :whiteboard/new-block "Ny blokk:"
+ :whiteboard/new-block-no-colon "Ny blokk"
+ :whiteboard/new-page "Ny side:"
+ :whiteboard/new-whiteboard "Nytt whiteboard"
+ :whiteboard/opacity "Ugjennomsiktighet"
+ :whiteboard/open-page "Åpne side"
+ :whiteboard/open-page-in-sidebar "Åpne siden i sidestolpen"
+ :whiteboard/open-twitter-url "Åpne Twitter url"
+ :whiteboard/open-website-url "Åpne nettside url"
+ :whiteboard/open-youtube-url "Åpne YouTube url"
+ :whiteboard/pack-into-rectangle "Pakk inn i firkant"
+ :whiteboard/pan "Panorer"
+ :whiteboard/paste "Lim inn"
+ :whiteboard/paste-as-link "Lim inn som lenke"
+ :whiteboard/rectangle "Rektangel"
+ :whiteboard/redo "Gjør om"
+ :whiteboard/references "Referanser"
+ :whiteboard/reload "Last på nytt"
+ :whiteboard/remove-link "Fjern lenke"
+ :whiteboard/scale-level "Skaleringsnivå"
+ :whiteboard/search-only-blocks "Søk kun blokker"
+ :whiteboard/search-only-pages "Søk kun sider"
+ :whiteboard/select "Velg"
+ :whiteboard/select-all "Velg alle"
+ :whiteboard/select-custom-color "Velg egendefinert farge"
+ :whiteboard/shape "Form"
+ :whiteboard/shape-quick-links "Form Hurtiglenker"
+ :whiteboard/small "Liten"
+ :whiteboard/start-typing-to-search "Begynn å skrive for å søke..."
+ :whiteboard/stroke-type "Type strøk"
+ :whiteboard/text "Tekst"
+ :whiteboard/triangle "Triangel"
+ :whiteboard/twitter-url "Twitter url"
+ :whiteboard/undo "Angre"
+ :whiteboard/ungroup "Del opp gruppe"
+ :whiteboard/unlock "Lås opp"
+ :whiteboard/website-url "Nettside url"
+ :whiteboard/youtube-url "YouTube url"
+ :whiteboard/zoom-in "Zoom inn"
+ :whiteboard/zoom-out "Zoom ut"
+ :whiteboard/zoom-to-fit "Tilpass zoom"
+ :window/close "Lukk"
+ :window/exit-fullscreen "Gå ut av fullskjerm"
+ :window/maximize "Maksimer"
+ :window/minimize "Minimer"
+ :window/restore "Gjenopprett"}

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác