浏览代码

Merge branch 'feat/db' into feat/datascript-storage

Tienson Qin 1 年之前
父节点
当前提交
ea3f5973b7
共有 100 个文件被更改,包括 2280 次插入1303 次删除
  1. 2 0
      .clj-kondo/config.edn
  2. 2 2
      android/app/build.gradle
  3. 2 2
      deps.edn
  4. 26 2
      deps/common/src/logseq/common/path.cljs
  5. 9 1
      deps/common/test/logseq/common/path_test.cljs
  6. 2 0
      deps/db/.carve/config.edn
  7. 1 1
      deps/db/script/query.cljs
  8. 33 15
      deps/db/src/logseq/db/frontend/malli_schema.cljs
  9. 10 0
      deps/db/src/logseq/db/frontend/property.cljs
  10. 43 12
      deps/db/src/logseq/db/frontend/property/type.cljs
  11. 82 0
      deps/db/src/logseq/db/frontend/property/util.cljs
  12. 13 2
      deps/db/src/logseq/db/frontend/schema.cljs
  13. 36 0
      deps/db/src/logseq/db/sqlite/create_graph.cljs
  14. 2 6
      deps/db/src/logseq/db/sqlite/db.cljs
  15. 4 29
      deps/db/src/logseq/db/sqlite/util.cljs
  16. 2 4
      deps/graph-parser/src/logseq/graph_parser/block.cljs
  17. 1 1
      deps/graph-parser/src/logseq/graph_parser/test/docs_graph_helper.cljs
  18. 3 2
      deps/publishing/src/logseq/publishing/html.cljs
  19. 13 10
      deps/shui/src/logseq/shui/button/v2.cljs
  20. 8 9
      deps/shui/src/logseq/shui/list_item/v1.cljs
  21. 67 79
      deps/shui/src/logseq/shui/shortcut/v1.cljs
  22. 6 0
      e2e-tests/editor.spec.ts
  23. 1 1
      e2e-tests/page-search.spec.ts
  24. 4 4
      ios/App/App.xcodeproj/project.pbxproj
  25. 6 3
      resources/css/shui.css
  26. 1 1
      resources/forge.config.js
  27. 0 8
      resources/js/preload.js
  28. 1 1
      resources/package.json
  29. 34 18
      scripts/src/logseq/tasks/db_graph/create_graph.cljs
  30. 46 5
      scripts/src/logseq/tasks/db_graph/create_graph_with_properties.cljs
  31. 3 1
      scripts/src/logseq/tasks/dev/db_and_file_graphs.clj
  32. 1 1
      scripts/src/logseq/tasks/dev/publishing.cljs
  33. 2 1
      scripts/src/logseq/tasks/lang.clj
  34. 13 1
      src/electron/electron/core.cljs
  35. 17 9
      src/electron/electron/handler.cljs
  36. 18 4
      src/electron/electron/utils.cljs
  37. 3 2
      src/main/frontend/colors.cljs
  38. 4 0
      src/main/frontend/common.css
  39. 7 8
      src/main/frontend/components/block.cljs
  40. 17 0
      src/main/frontend/components/block.css
  41. 74 49
      src/main/frontend/components/cmdk.cljs
  42. 6 2
      src/main/frontend/components/container.css
  43. 189 0
      src/main/frontend/components/db_based/page.cljs
  44. 19 37
      src/main/frontend/components/editor.cljs
  45. 12 190
      src/main/frontend/components/page.cljs
  46. 1 1
      src/main/frontend/components/page_menu.cljs
  47. 51 32
      src/main/frontend/components/property.cljs
  48. 42 30
      src/main/frontend/components/property/closed_value.cljs
  49. 9 9
      src/main/frontend/components/property/value.cljs
  50. 14 16
      src/main/frontend/components/repo.cljs
  51. 1 1
      src/main/frontend/components/shortcut.cljs
  52. 1 4
      src/main/frontend/config.cljs
  53. 23 13
      src/main/frontend/db/conn.cljs
  54. 3 2
      src/main/frontend/db/datascript/entity_plus.cljs
  55. 7 5
      src/main/frontend/db/fix.cljs
  56. 56 57
      src/main/frontend/db/model.cljs
  57. 2 7
      src/main/frontend/db/outliner.cljs
  58. 6 9
      src/main/frontend/db/react.cljs
  59. 37 71
      src/main/frontend/db/restore.cljs
  60. 87 41
      src/main/frontend/db/rtc/core.cljs
  61. 1 2
      src/main/frontend/db/rtc/full_upload_download_graph.cljs
  62. 1 1
      src/main/frontend/extensions/pdf/assets.cljs
  63. 1 1
      src/main/frontend/extensions/slide.cljs
  64. 32 6
      src/main/frontend/fs/memory_fs.cljs
  65. 1 1
      src/main/frontend/fs/sync.cljs
  66. 5 11
      src/main/frontend/handler.cljs
  67. 3 2
      src/main/frontend/handler/assets.cljs
  68. 2 2
      src/main/frontend/handler/common/page.cljs
  69. 38 29
      src/main/frontend/handler/db_based/page.cljs
  70. 56 53
      src/main/frontend/handler/db_based/property.cljs
  71. 1 2
      src/main/frontend/handler/db_based/recent.cljs
  72. 13 12
      src/main/frontend/handler/editor.cljs
  73. 4 4
      src/main/frontend/handler/events.cljs
  74. 1 1
      src/main/frontend/handler/file.cljs
  75. 16 7
      src/main/frontend/handler/page.cljs
  76. 3 3
      src/main/frontend/handler/property.cljs
  77. 7 5
      src/main/frontend/handler/property/util.cljs
  78. 10 5
      src/main/frontend/handler/recent.cljs
  79. 3 3
      src/main/frontend/handler/repo.cljs
  80. 4 1
      src/main/frontend/handler/ui.cljs
  81. 30 10
      src/main/frontend/handler/user.cljs
  82. 0 6
      src/main/frontend/handler/whiteboard.cljs
  83. 1 0
      src/main/frontend/mixins.cljs
  84. 44 37
      src/main/frontend/modules/outliner/core.cljs
  85. 16 16
      src/main/frontend/modules/outliner/file.cljs
  86. 1 2
      src/main/frontend/modules/shortcut/core.cljs
  87. 4 4
      src/main/frontend/modules/shortcut/data_helper.cljs
  88. 0 6
      src/main/frontend/persist_db.cljs
  89. 0 43
      src/main/frontend/persist_db/browser.cljs
  90. 1 1
      src/main/frontend/search.cljs
  91. 10 4
      src/main/frontend/search/db.cljs
  92. 8 5
      src/main/frontend/state.cljs
  93. 2 1
      src/main/frontend/ui.cljs
  94. 5 20
      src/main/frontend/util.cljc
  95. 1 1
      src/main/frontend/version.cljs
  96. 604 140
      src/resources/dicts/it.edn
  97. 42 8
      src/resources/dicts/ja.edn
  98. 2 2
      src/resources/tutorials/tutorial-it.md
  99. 122 0
      src/test/frontend/db/db_based_model_test.cljs
  100. 0 25
      src/test/frontend/db/outliner_test.cljs

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

@@ -49,6 +49,7 @@
              frontend.components.query query
              frontend.components.query query
              frontend.components.query.result query-result
              frontend.components.query.result query-result
              frontend.components.class class-component
              frontend.components.class class-component
+             frontend.components.property property-component
              frontend.config config
              frontend.config config
              frontend.date date
              frontend.date date
              frontend.db db
              frontend.db db
@@ -123,6 +124,7 @@
              logseq.common.config common-config
              logseq.common.config common-config
              logseq.db.frontend.property db-property
              logseq.db.frontend.property db-property
              logseq.db.frontend.property.type db-property-type
              logseq.db.frontend.property.type db-property-type
+             logseq.db.frontend.property.util db-property-util
              logseq.db.frontend.rules rules
              logseq.db.frontend.rules rules
              logseq.db.frontend.schema db-schema
              logseq.db.frontend.schema db-schema
              logseq.db.sqlite.db sqlite-db
              logseq.db.sqlite.db sqlite-db

+ 2 - 2
android/app/build.gradle

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

+ 2 - 2
deps.edn

@@ -42,7 +42,7 @@
  :aliases {:cljs {:extra-paths ["src/dev-cljs/" "src/test/" "src/electron/"]
  :aliases {:cljs {:extra-paths ["src/dev-cljs/" "src/test/" "src/electron/"]
                   :extra-deps  {org.clojure/clojurescript        {:mvn/version "1.11.54"}
                   :extra-deps  {org.clojure/clojurescript        {:mvn/version "1.11.54"}
                                 org.clojure/tools.namespace      {:mvn/version "0.2.11"}
                                 org.clojure/tools.namespace      {:mvn/version "0.2.11"}
-                                cider/cider-nrepl                {:mvn/version "0.30.0"}
+                                cider/cider-nrepl                {:mvn/version "0.44.0"}
                                 org.clojars.knubie/cljs-run-test {:mvn/version "1.0.1"}
                                 org.clojars.knubie/cljs-run-test {:mvn/version "1.0.1"}
                                 tortue/spy                       {:mvn/version "2.14.0"}}
                                 tortue/spy                       {:mvn/version "2.14.0"}}
                   :main-opts   ["-m" "shadow.cljs.devtools.cli"]}
                   :main-opts   ["-m" "shadow.cljs.devtools.cli"]}
@@ -53,7 +53,7 @@
                                 pjstadig/humane-test-output      {:mvn/version "0.11.0"}
                                 pjstadig/humane-test-output      {:mvn/version "0.11.0"}
                                 org.clojars.knubie/cljs-run-test {:mvn/version "1.0.1"}
                                 org.clojars.knubie/cljs-run-test {:mvn/version "1.0.1"}
                                 tortue/spy                       {:mvn/version "2.14.0"}
                                 tortue/spy                       {:mvn/version "2.14.0"}
-                                cider/cider-nrepl                {:mvn/version "0.30.0"}}
+                                cider/cider-nrepl                {:mvn/version "0.44.0"}}
                   :main-opts   ["-m" "shadow.cljs.devtools.cli"]}
                   :main-opts   ["-m" "shadow.cljs.devtools.cli"]}
 
 
            :bench {:extra-paths ["src/bench/"]
            :bench {:extra-paths ["src/bench/"]

+ 26 - 2
deps/common/src/logseq/common/path.cljs

@@ -168,7 +168,28 @@
 
 
   (if (is-file-url? base)
   (if (is-file-url? base)
     (apply url-join base segments)
     (apply url-join base segments)
-    (apply path-join-internal base segments)))
+    (let [rejoined-path (apply path-join-internal base segments)]
+      (if (and (not-empty base)
+               (string/starts-with? base "//")) ;; Win path fix
+        (str "/" rejoined-path)
+        rejoined-path))))
+
+(defn prepend-protocol
+  "Prepend protocol to path. Handle UNC path. aka. path-to-url
+
+   protocol is one of file: http: https: assets:"
+  [protocol path]
+  (cond
+    (string/starts-with? path protocol)
+    (do
+      (js/console.error "BUG: should not prepend protocol to path with protocol" protocol path)
+      path)
+
+    (string/starts-with? path "//") ;; Windows UNC path
+    (str protocol path)
+
+    :else
+    (path-join (str protocol "//") path)))
 
 
 
 
 (defn- path-normalize-internal
 (defn- path-normalize-internal
@@ -204,13 +225,16 @@
     ;; Check file:// and assets://, pathname behavior is different
     ;; Check file:// and assets://, pathname behavior is different
     (let [^js url (js/URL. (string/replace original-url "assets://" "file://"))
     (let [^js url (js/URL. (string/replace original-url "assets://" "file://"))
           path (safe-decode-uri-component (.-pathname url))
           path (safe-decode-uri-component (.-pathname url))
+          host (.-host url)
           path (if (string/starts-with? path "///")
           path (if (string/starts-with? path "///")
                  (subs path 2)
                  (subs path 2)
                  path)
                  path)
           path (if (re-find #"(?i)^/[a-zA-Z]:" path) ;; Win path fix
           path (if (re-find #"(?i)^/[a-zA-Z]:" path) ;; Win path fix
                  (subs path 1)
                  (subs path 1)
                  path)]
                  path)]
-      path)
+      (if (string/blank? host)
+        path
+        (str "//" host path)))
     original-url))
     original-url))
 
 
 (defn trim-dir-prefix
 (defn trim-dir-prefix

+ 9 - 1
deps/common/test/logseq/common/path_test.cljs

@@ -35,7 +35,15 @@
         "global dir")
         "global dir")
     (is (= "/foo/bar/baz/asdf" (path/path-join "/foo/bar//baz/asdf/quux/..")))
     (is (= "/foo/bar/baz/asdf" (path/path-join "/foo/bar//baz/asdf/quux/..")))
     (is (= "assets:///foo.bar/baz" (path/path-join "assets:///foo.bar" "baz")))
     (is (= "assets:///foo.bar/baz" (path/path-join "assets:///foo.bar" "baz")))
-    (is (= "assets:///foo.bar/baz" (path/path-join "assets:///foo.bar/" "baz")))))
+    (is (= "assets:///foo.bar/baz" (path/path-join "assets:///foo.bar/" "baz")))
+    (is (= "//NAS/MyGraph/logseq/config.edn" (path/path-join "//NAS/MyGraph" "logseq/config.edn")))))
+
+(deftest prepend-protocol
+  (testing "prepend-protocol"
+    (is (= "file:///home/logseq/graph" (path/prepend-protocol "file:" "/home/logseq/graph")))
+    (is (= "file:///C%3A/Graph/pages" (path/prepend-protocol "file:" "C:/Graph/pages")))
+    (is (= "file://NAS/MyGraph" (path/prepend-protocol "file:" "//NAS/MyGraph"))
+        "Windows UNC URL")))
 
 
 (deftest path-absolute
 (deftest path-absolute
   (testing "absolute"
   (testing "absolute"

+ 2 - 0
deps/db/.carve/config.edn

@@ -5,6 +5,8 @@
                   logseq.db.sqlite.util
                   logseq.db.sqlite.util
                   logseq.db.sqlite.cli
                   logseq.db.sqlite.cli
                   logseq.db.frontend.property
                   logseq.db.frontend.property
+                  logseq.db.frontend.property.util
+                  logseq.db.sqlite.create-graph
                   logseq.db.frontend.malli-schema
                   logseq.db.frontend.malli-schema
                   ;; Some fns are used by frontend but not worth moving over yet
                   ;; Some fns are used by frontend but not worth moving over yet
                   logseq.db.frontend.schema]
                   logseq.db.frontend.schema]

+ 1 - 1
deps/db/script/query.cljs

@@ -26,7 +26,7 @@
         conn (read-graph graph-name)
         conn (read-graph graph-name)
         query (into (edn/read-string query*) [:in '$ '%]) ;; assumes no :in are in queries
         query (into (edn/read-string query*) [:in '$ '%]) ;; assumes no :in are in queries
         results (mapv first (d/q query @conn (rules/extract-rules rules/db-query-dsl-rules)))]
         results (mapv first (d/q query @conn (rules/extract-rules rules/db-query-dsl-rules)))]
-    (println "DB contains" (count (d/datoms @conn :eavt)) "datoms")
+    #_(println "DB contains" (count (d/datoms @conn :eavt)) "datoms")
     (prn results)))
     (prn results)))
 
 
 (when (= nbb/*file* (:file (meta #'-main)))
 (when (= nbb/*file* (:file (meta #'-main)))

+ 33 - 15
deps/db/src/logseq/db/frontend/malli_schema.cljs

@@ -65,7 +65,7 @@
                                          [:block/schema :type]))}]
                                          [:block/schema :type]))}]
    (map (fn [[prop-type value-schema]]
    (map (fn [[prop-type value-schema]]
           ^:property-value [prop-type (if (vector? value-schema) (last value-schema) value-schema)])
           ^:property-value [prop-type (if (vector? value-schema) (last value-schema) value-schema)])
-        db-property-type/builtin-schema-types)))
+        db-property-type/built-in-validation-schemas)))
 
 
 (def block-properties
 (def block-properties
   "Validates a slightly modified version of :block/properties. Properties are
   "Validates a slightly modified version of :block/properties. Properties are
@@ -118,10 +118,9 @@
     page-attrs
     page-attrs
     page-or-block-attrs)))
     page-or-block-attrs)))
 
 
-(def property-schema-attrs
-  [[:hide? {:optional true} :boolean]
-   [:description {:optional true} :string]
-   ;; For any types except for :checkbox :default :template
+(def property-type-schema-attrs
+  "Property :schema attributes that vary by :type"
+  [;; For any types except for :checkbox :default :template
    [:cardinality {:optional true} [:enum :one :many]]
    [:cardinality {:optional true} [:enum :one :many]]
    ;; For closed values
    ;; For closed values
    [:values {:optional true}  [:vector :uuid]]
    [:values {:optional true}  [:vector :uuid]]
@@ -130,28 +129,47 @@
    ;; For :page and :template
    ;; For :page and :template
    [:classes {:optional true} [:set [:or :uuid :keyword]]]])
    [:classes {:optional true} [:set [:or :uuid :keyword]]]])
 
 
+(def property-common-schema-attrs
+  "Property :schema attributes common to all properties"
+  [[:hide? {:optional true} :boolean]
+   [:description {:optional true} :string]])
+
 (def internal-property
 (def internal-property
   (vec
   (vec
    (concat
    (concat
     [:map
     [:map
      [:block/schema
      [:block/schema
-      (into [:map
-             [:type (apply vector :enum (into db-property-type/internal-builtin-schema-types
-                                              db-property-type/user-builtin-schema-types))]]
-            property-schema-attrs)]]
+      (vec
+       (concat
+        [:map
+         [:type (apply vector :enum (into db-property-type/internal-built-in-property-types
+                                          db-property-type/user-built-in-property-types))]]
+        property-common-schema-attrs
+        property-type-schema-attrs))]]
     page-attrs
     page-attrs
     page-or-block-attrs)))
     page-or-block-attrs)))
 
 
+(def user-property-schema
+  (into
+   [:multi {:dispatch :type}]
+   (map
+    (fn [prop-type]
+      [prop-type
+       (vec
+        (concat
+         [:map
+          ;; Once a schema is defined it must have :type as this is an irreversible decision
+          [:type :keyword]]
+         property-common-schema-attrs
+         (remove #(not (db-property-type/property-type-allows-schema-attribute? prop-type (first %)))
+                 property-type-schema-attrs)))])
+    db-property-type/user-built-in-property-types)))
+
 (def user-property
 (def user-property
   (vec
   (vec
    (concat
    (concat
     [:map
     [:map
-     [:block/schema
-      {:optional true}
-      (into [:map
-             ;; Once a schema is defined it must have :type as this is an irreversible decision
-             [:type (apply vector :enum db-property-type/user-builtin-schema-types)]]
-            property-schema-attrs)]]
+     [:block/schema {:optional true} user-property-schema]]
     page-attrs
     page-attrs
     page-or-block-attrs)))
     page-or-block-attrs)))
 
 

+ 10 - 0
deps/db/src/logseq/db/frontend/property.cljs

@@ -46,6 +46,10 @@
    ;; color props
    ;; color props
    :logseq.color {:schema
    :logseq.color {:schema
                   {:type :default :hide? true}
                   {:type :default :hide? true}
+                  :closed-values
+                  (mapv #(hash-map :value % :uuid (random-uuid))
+                        ;; Stringified version of frontend.colors/COLORS. Too basic to couple
+                        ["tomato" "red" "crimson" "pink" "plum" "purple" "violet" "indigo" "blue" "cyan" "teal" "green" "grass" "orange" "brown"])
                   :visible true}
                   :visible true}
    ;; table-v2 props
    ;; table-v2 props
    :logseq.table.version {:schema {:type :number :hide? true}
    :logseq.table.version {:schema {:type :number :hide? true}
@@ -54,9 +58,15 @@
                           :visible true}
                           :visible true}
    :logseq.table.headers {:schema
    :logseq.table.headers {:schema
                           {:type :default :hide? true}
                           {:type :default :hide? true}
+                          :closed-values
+                          (mapv #(hash-map :value % :uuid (random-uuid))
+                                ["uppercase" "capitalize" "capitalize-first" "lowercase"])
                           :visible true}
                           :visible true}
    :logseq.table.hover {:schema
    :logseq.table.hover {:schema
                         {:type :default :hide? true}
                         {:type :default :hide? true}
+                        :closed-values
+                        (mapv #(hash-map :value % :uuid (random-uuid))
+                              ["row" "col" "both" "none"])
                         :visible true}
                         :visible true}
    :logseq.table.borders {:schema {:type :checkbox :hide? true}
    :logseq.table.borders {:schema {:type :checkbox :hide? true}
                           :visible true}
                           :visible true}

+ 43 - 12
deps/db/src/logseq/db/frontend/property/type.cljs

@@ -1,23 +1,45 @@
 (ns logseq.db.frontend.property.type
 (ns logseq.db.frontend.property.type
-  "Provides property types including fns to validate them"
+  "Provides property types and related helper fns e.g. property value validation
+  fns and their allowed schema attributes"
   (:require [datascript.core :as d]
   (:require [datascript.core :as d]
             [clojure.set :as set]))
             [clojure.set :as set]))
 
 
-(def internal-builtin-schema-types
-  "Valid schema :type only to be used by built-in-properties"
+;; Config vars
+;; ===========
+;; These vars enumerate all known property types and their associated behaviors
+;; except for validation which is in its own section
+
+(def internal-built-in-property-types
+  "Valid property types only for use by internal built-in-properties"
   #{:keyword :map :coll :any})
   #{:keyword :map :coll :any})
 
 
-(def user-builtin-schema-types
-  "Valid schema :type for users in order they appear in the UI"
+(def user-built-in-property-types
+  "Valid property types for users in order they appear in the UI"
   [:default :number :date :checkbox :url :page :template])
   [:default :number :date :checkbox :url :page :template])
 
 
-(def closed-values-schema-types
+(def closed-value-property-types
   "Valid schema :type for closed values"
   "Valid schema :type for closed values"
   #{:default :number :date :url :page})
   #{:default :number :date :url :page})
 
 
-(assert (set/subset? closed-values-schema-types (set user-builtin-schema-types))
+(assert (set/subset? closed-value-property-types (set user-built-in-property-types))
         "All closed value types are valid property types")
         "All closed value types are valid property types")
 
 
+(def ^:private user-built-in-allowed-schema-attributes
+  "Map of types to their set of allowed :schema attributes"
+  (merge-with into
+              (zipmap closed-value-property-types (repeat #{:values :position}))
+              {:number #{:cardinality}
+               :date #{:cardinality}
+               :url #{:cardinality}
+               :page #{:cardinality :classes}
+               :template #{:classes}
+               :checkbox #{}}))
+
+(assert (= (set user-built-in-property-types) (set (keys user-built-in-allowed-schema-attributes)))
+        "Each user built in type should have an allowed schema attribute")
+
+;; Property value validation
+;; =========================
 ;; TODO:
 ;; TODO:
 ;; Validate && list fixes for non-validated values when updating property schema
 ;; Validate && list fixes for non-validated values when updating property schema
 
 
@@ -68,7 +90,8 @@
       (existing-closed-value-valid? db property type-validate-fn value)
       (existing-closed-value-valid? db property type-validate-fn value)
       (type-validate-fn value))))
       (type-validate-fn value))))
 
 
-(def builtin-schema-types
+(def built-in-validation-schemas
+  "Map of types to malli validation schemas that validate a property value for that type"
   {:default  [:fn
   {:default  [:fn
               {:error/message "should be a text"}
               {:error/message "should be a text"}
               ;; uuid check needed for property block values
               ;; uuid check needed for property block values
@@ -97,11 +120,19 @@
    :coll     coll?
    :coll     coll?
    :any      some?})
    :any      some?})
 
 
+(assert (= (set (keys built-in-validation-schemas))
+           (into internal-built-in-property-types
+                 user-built-in-property-types))
+        "Built-in property types must be equal")
+
 (def property-types-with-db
 (def property-types-with-db
   "Property types whose validation fn requires a datascript db"
   "Property types whose validation fn requires a datascript db"
   #{:date :page :template})
   #{:date :page :template})
 
 
-(assert (= (set (keys builtin-schema-types))
-           (into internal-builtin-schema-types
-                 user-builtin-schema-types))
-        "Built-in schema types must be equal")
+;; Helper fns
+;; ==========
+(defn property-type-allows-schema-attribute?
+  "Returns boolean to indicate if property type allows the given :schema attribute"
+  [property-type schema-attribute]
+  (contains? (get user-built-in-allowed-schema-attributes property-type)
+             schema-attribute))

+ 82 - 0
deps/db/src/logseq/db/frontend/property/util.cljs

@@ -0,0 +1,82 @@
+(ns logseq.db.frontend.property.util
+  "Util fns for building core property concepts"
+  (:require [logseq.db.sqlite.util :as sqlite-util]
+            [datascript.core :as d]))
+
+(defonce hidden-page-name-prefix "$$$")
+
+(defn- closed-value-new-block
+  [page-id block-id value property]
+  {:block/type #{"closed value"}
+   :block/format :markdown
+   :block/uuid block-id
+   :block/page page-id
+   :block/metadata {:created-from-property (:block/uuid property)}
+   :block/schema {:value value}
+   :block/parent page-id})
+
+(defn build-closed-value-block
+  "Builds a closed value block to be transacted"
+  [block-uuid block-value page-id property {:keys [icon-id icon description]}]
+  (cond->
+   (closed-value-new-block page-id (or block-uuid (d/squuid)) block-value property)
+    icon
+    (assoc :block/properties {icon-id icon})
+
+    description
+    (update :block/schema assoc :description description)
+
+    true
+    sqlite-util/block-with-timestamps))
+
+(defn- build-new-page
+  "Builds a basic page to be transacted. A minimal version of gp-block/page-name->map"
+  [page-name]
+  (sqlite-util/block-with-timestamps
+   {:block/name (sqlite-util/sanitize-page-name page-name)
+    :block/original-name page-name
+    :block/journal? false
+    :block/uuid (d/squuid)}))
+
+(defn build-property-hidden-page
+  "Builds a hidden property page for closed values to be transacted"
+  [property]
+  (let [page-name (str hidden-page-name-prefix (:block/uuid property))]
+    (-> (build-new-page page-name)
+        (assoc :block/type #{"hidden"}
+               :block/format :markdown))))
+
+(defn new-property-tx
+  "Provide attributes for a new built-in property given name, schema and uuid.
+   TODO: Merge this with sqlite-util/build-new-property once gp-util/page-name-sanity-lc
+   is available to deps/db"
+  [prop-name prop-schema prop-uuid]
+  {:block/uuid prop-uuid
+   :block/schema (merge {:type :default} prop-schema)
+   :block/original-name (name prop-name)
+   :block/name (sqlite-util/sanitize-page-name (name prop-name))})
+
+(defn build-closed-values
+  "Builds all the tx needed for property with closed values including
+   the hidden page and closed value blocks as needed"
+  [prop-name property {:keys [icon-id translate-closed-page-value-fn property-attributes]
+                       :or {translate-closed-page-value-fn identity}}]
+  (let [page-tx (build-property-hidden-page property)
+        page-id [:block/uuid (:block/uuid page-tx)]
+        closed-value-page-uuids? (contains? #{:page :date} (get-in property [:block/schema :type]))
+        closed-value-blocks-tx
+        (if closed-value-page-uuids?
+          (map translate-closed-page-value-fn (:closed-values property))
+          (map (fn [{:keys [value icon description uuid]}]
+                 (build-closed-value-block
+                  uuid value page-id property {:icon-id icon-id
+                                               :icon icon
+                                               :description description}))
+               (:closed-values property)))
+        property-schema (assoc (:block/schema property)
+                               :values (mapv :block/uuid closed-value-blocks-tx))
+        property-tx (merge (sqlite-util/build-new-property
+                            (new-property-tx prop-name property-schema (:block/uuid property)))
+                           property-attributes)]
+    (into [property-tx page-tx]
+          (when-not closed-value-page-uuids? closed-value-blocks-tx))))

+ 13 - 2
deps/db/src/logseq/db/frontend/schema.cljs

@@ -126,7 +126,6 @@
            :block/properties-order)
            :block/properties-order)
    {:file/last-modified-at {}}))
    {:file/last-modified-at {}}))
 
 
-;; TODO: some attributes shouldn't be retracted for the db version
 (def retract-attributes
 (def retract-attributes
   #{
   #{
     :block/refs
     :block/refs
@@ -147,6 +146,18 @@
     }
     }
   )
   )
 
 
+;; If only block/content changes
+(def db-version-retract-attributes
+  #{:block/tags
+    :block/refs
+    :block/marker
+    :block/priority
+    :block/scheduled
+    :block/deadline
+    :block/repeated?
+    :block/macros
+    :block/warning})
+
 
 
 ;;; use `(map [:db.fn/retractAttribute <id> <attr>] retract-page-attributes)`
 ;;; use `(map [:db.fn/retractAttribute <id> <attr>] retract-page-attributes)`
 ;;; to remove attrs to make the page as it's just created and no file attached to it
 ;;; to remove attrs to make the page as it's just created and no file attached to it
@@ -189,4 +200,4 @@
        (keep (fn [[k v]]
        (keep (fn [[k v]]
                (when (not (:db/valueType v))
                (when (not (:db/valueType v))
                  k)))
                  k)))
-       set))
+       set))

+ 36 - 0
deps/db/src/logseq/db/sqlite/create_graph.cljs

@@ -0,0 +1,36 @@
+(ns logseq.db.sqlite.create-graph
+  "Helper fns for creating a DB graph"
+  (:require [logseq.db.sqlite.util :as sqlite-util]
+            [logseq.db.frontend.property :as db-property]
+            [logseq.db.frontend.property.util :as db-property-util]
+            [datascript.core :as d]))
+
+(defn build-db-initial-data
+  [config-content]
+  (let [initial-files [{:block/uuid (d/squuid)
+                        :file/path (str "logseq/" "config.edn")
+                        :file/content config-content
+                        :file/last-modified-at (js/Date.)}
+                       {:block/uuid (d/squuid)
+                        :file/path (str "logseq/" "custom.css")
+                        :file/content ""
+                        :file/last-modified-at (js/Date.)}
+                       {:block/uuid (d/squuid)
+                        :file/path (str "logseq/" "custom.js")
+                        :file/content ""
+                        :file/last-modified-at (js/Date.)}]
+        default-properties (mapcat
+                            (fn [[k-keyword {:keys [schema original-name closed-values]}]]
+                              (let [k-name (name k-keyword)]
+                                (if closed-values
+                                  (db-property-util/build-closed-values
+                                   (or original-name k-name)
+                                   {:block/schema schema :block/uuid (d/squuid) :closed-values closed-values}
+                                   {})
+                                  [(sqlite-util/build-new-property
+                                    {:block/schema schema
+                                     :block/original-name (or original-name k-name)
+                                     :block/name (sqlite-util/sanitize-page-name k-name)
+                                     :block/uuid (d/squuid)})])))
+                            db-property/built-in-properties)]
+    (concat initial-files default-properties)))

+ 2 - 6
deps/db/src/logseq/db/sqlite/db.cljs

@@ -3,18 +3,14 @@
   (:require ["path" :as node-path]
   (:require ["path" :as node-path]
             ["better-sqlite3" :as sqlite3]
             ["better-sqlite3" :as sqlite3]
             [clojure.string :as string]
             [clojure.string :as string]
-            [cljs-bean.core :as bean]
+            [logseq.db.sqlite.util :as sqlite-util]
             [datascript.storage :refer [IStorage]]
             [datascript.storage :refer [IStorage]]
-            [cognitect.transit :as t]
             [cljs.cache :as cache]
             [cljs.cache :as cache]
             [datascript.core :as d]
             [datascript.core :as d]
             [goog.object :as gobj]
             [goog.object :as gobj]
             [logseq.db.frontend.schema :as db-schema]
             [logseq.db.frontend.schema :as db-schema]
-            [datascript.transit :as dt]
             [clojure.edn :as edn]))
             [clojure.edn :as edn]))
 
 
-;; Notice: this works only on Node.js environment, it doesn't support browser yet.
-
 ;; use built-in blocks to represent db schema, config, custom css, custom js, etc.
 ;; use built-in blocks to represent db schema, config, custom css, custom js, etc.
 
 
 ;; sqlite databases
 ;; sqlite databases
@@ -35,7 +31,7 @@
 (defn sanitize-db-name
 (defn sanitize-db-name
   [db-name]
   [db-name]
   (-> db-name
   (-> db-name
-      (string/replace "logseq_db_" "")
+      (string/replace sqlite-util/db-version-prefix "")
       (string/replace "/" "_")
       (string/replace "/" "_")
       (string/replace "\\" "_")
       (string/replace "\\" "_")
       (string/replace ":" "_"))) ;; windows
       (string/replace ":" "_"))) ;; windows

+ 4 - 29
deps/db/src/logseq/db/sqlite/util.cljs

@@ -4,9 +4,7 @@
             [cljs-time.core :as t]
             [cljs-time.core :as t]
             [clojure.string :as string]
             [clojure.string :as string]
             [cognitect.transit :as transit]
             [cognitect.transit :as transit]
-            [datascript.core :as d]
-            [logseq.db.frontend.schema :as db-schema]
-            [logseq.db.frontend.property :as db-property]))
+            [logseq.db.frontend.schema :as db-schema]))
 
 
 (defn- type-of-block
 (defn- type-of-block
   "
   "
@@ -31,6 +29,8 @@
     (contains? (set (:block/type block)) "macro") 7
     (contains? (set (:block/type block)) "macro") 7
     :else 5))
     :else 5))
 
 
+(defonce db-version-prefix "logseq_db_")
+
 (defn time-ms
 (defn time-ms
   "Copy of util/time-ms. Too basic to couple this to main app"
   "Copy of util/time-ms. Too basic to couple this to main app"
   []
   []
@@ -94,29 +94,4 @@
    (merge {:block/type "property"
    (merge {:block/type "property"
            :block/journal? false
            :block/journal? false
            :block/format :markdown}
            :block/format :markdown}
-          block)))
-
-(defn build-db-initial-data
-  [config-content]
-  (let [initial-files [{:block/uuid (d/squuid)
-                        :file/path (str "logseq/" "config.edn")
-                        :file/content config-content
-                        :file/last-modified-at (js/Date.)}
-                       {:block/uuid (d/squuid)
-                        :file/path (str "logseq/" "custom.css")
-                        :file/content ""
-                        :file/last-modified-at (js/Date.)}
-                       {:block/uuid (d/squuid)
-                        :file/path (str "logseq/" "custom.js")
-                        :file/content ""
-                        :file/last-modified-at (js/Date.)}]
-        default-properties (map
-                            (fn [[k-keyword {:keys [schema original-name]}]]
-                              (let [k-name (name k-keyword)]
-                                (build-new-property
-                                 {:block/schema schema
-                                  :block/original-name (or original-name k-name)
-                                  :block/name (sanitize-page-name k-name)
-                                  :block/uuid (d/squuid)})))
-                            db-property/built-in-properties)]
-    (concat initial-files default-properties)))
+          block)))

+ 2 - 4
deps/graph-parser/src/logseq/graph_parser/block.cljs

@@ -349,7 +349,7 @@
          form))
          form))
      (concat title body))
      (concat title body))
     (swap! *refs #(remove string/blank? %))
     (swap! *refs #(remove string/blank? %))
-    (let [ref->map-fn (fn [*col tag?]
+    (let [ref->map-fn (fn [*col _tag?]
                         (let [col (remove string/blank? @*col)
                         (let [col (remove string/blank? @*col)
                               children-pages (->> (mapcat (fn [p]
                               children-pages (->> (mapcat (fn [p]
                                                             (let [p (if (map? p)
                                                             (let [p (if (map? p)
@@ -369,9 +369,7 @@
                              (let [macro? (and (map? item)
                              (let [macro? (and (map? item)
                                                (= "macro" (:type item)))]
                                                (= "macro" (:type item)))]
                                (when-not macro?
                                (when-not macro?
-                                 (cond-> (page-name->map item with-id? db true date-formatter)
-                                   tag?
-                                   (assoc :block/type "class"))))) col)))]
+                                 (page-name->map item with-id? db true date-formatter)))) col)))]
       (assoc block
       (assoc block
              :refs (ref->map-fn *refs false)
              :refs (ref->map-fn *refs false)
              :tags (ref->map-fn *structured-tags true)))))
              :tags (ref->map-fn *structured-tags true)))))

+ 1 - 1
deps/graph-parser/src/logseq/graph_parser/test/docs_graph_helper.cljs

@@ -161,7 +161,7 @@
   ;; only increase over time as the docs graph rarely has deletions
   ;; only increase over time as the docs graph rarely has deletions
   (testing "Counts"
   (testing "Counts"
     (is (= 303 (count files)) "Correct file count")
     (is (= 303 (count files)) "Correct file count")
-    (is (= 64392 (count (d/datoms db :eavt))) "Correct datoms count")
+    (is (= 64375 (count (d/datoms db :eavt))) "Correct datoms count")
 
 
     (is (= 5866
     (is (= 5866
            (ffirst
            (ffirst

+ 3 - 2
deps/publishing/src/logseq/publishing/html.cljs

@@ -5,7 +5,8 @@ necessary db filtering"
             [goog.string :as gstring]
             [goog.string :as gstring]
             [goog.string.format]
             [goog.string.format]
             [datascript.transit :as dt]
             [datascript.transit :as dt]
-            [logseq.publishing.db :as db]))
+            [logseq.publishing.db :as db]
+            [logseq.db.sqlite.util :as sqlite-util]))
 
 
 ;; Copied from hiccup but tweaked for publish usage
 ;; Copied from hiccup but tweaked for publish usage
 ;; Any changes here should also be made in frontend.publishing/unescape-html
 ;; Any changes here should also be made in frontend.publishing/unescape-html
@@ -142,7 +143,7 @@ generated index.html string and assets used by the html"
         asset-filenames (remove nil? asset-filenames')
         asset-filenames (remove nil? asset-filenames')
 
 
         db-str (dt/write-transit-str db)
         db-str (dt/write-transit-str db)
-        repo-name (if db-graph? "logseq_db_local" "local")
+        repo-name (if db-graph? (str sqlite-util/db-version-prefix "local") "local")
         ;; The repo-name is used by the client and thus determines whether
         ;; The repo-name is used by the client and thus determines whether
         ;; it's a db graph or not
         ;; it's a db graph or not
         state (assoc app-state
         state (assoc app-state

+ 13 - 10
deps/shui/src/logseq/shui/button/v2.cljs

@@ -3,11 +3,12 @@
     [clojure.string :as str]
     [clojure.string :as str]
     [rum.core :as rum]
     [rum.core :as rum]
     [logseq.shui.icon.v2 :as icon]
     [logseq.shui.icon.v2 :as icon]
-    [clojure.string :as string]))
+    [clojure.string :as string]
+    [goog.userAgent]))
 
 
 (rum/defcs root < rum/reactive
 (rum/defcs root < rum/reactive
   (rum/local nil ::hover-theme)
   (rum/local nil ::hover-theme)
-  [state {:keys [theme hover-theme color text depth size icon interactive shortcut tiled on-click muted disabled? class href button-props icon-props]
+  [state {:keys [theme hover-theme color text depth size icon interactive shortcut tiled tiles on-click muted disabled? class href button-props icon-props]
           :or {theme :color depth 1 size :md interactive true muted false class ""}} context]
           :or {theme :color depth 1 size :md interactive true muted false class ""}} context]
   (let [*hover-theme (::hover-theme state)
   (let [*hover-theme (::hover-theme state)
         color-string (or (some-> color name) (some-> context :state rum/react :ui/radix-color name) "custom")
         color-string (or (some-> color name) (some-> context :state rum/react :ui/radix-color name) "custom")
@@ -31,12 +32,14 @@
         :on-mouse-out #(reset! *hover-theme nil)}
         :on-mouse-out #(reset! *hover-theme nil)}
         on-click
         on-click
         (assoc :on-click on-click)))
         (assoc :on-click on-click)))
-     (if-not tiled text
-             (for [[index tile] (map-indexed vector (rest (string/split text #"")))]
-               [:<>
-                (when (< 0 index)
-                  [:div.ui__button__tile-separator])
-                [:div.ui__button__tile tile]]))
+     (if (and tiled (or text tiles))
+       (for [[index tile] (map-indexed vector
+                                       (or tiles (and text (rest (string/split text #"")))))]
+         [:<>
+          (when (< 0 index)
+            [:div.ui__button__tile-separator])
+          [:div.ui__button__tile tile]])
+       text)
 
 
      (when icon
      (when icon
        (icon/root icon icon-props))
        (icon/root icon icon-props))
@@ -44,9 +47,9 @@
        (for [key shortcut]
        (for [key shortcut]
          [:div.ui__button-shortcut-key
          [:div.ui__button-shortcut-key
           (case key
           (case key
-            "cmd" [:div "⌘"]
+            "cmd" [:div (if goog.userAgent/MAC "⌘" "Ctrl")]
             "shift" [:div "⇧"]
             "shift" [:div "⇧"]
-            "return" [:div ""]
+            "return" [:div ""]
             "esc" [:div.tracking-tightest {:style {:transform "scaleX(0.8) scaleY(1.2) "
             "esc" [:div.tracking-tightest {:style {:transform "scaleX(0.8) scaleY(1.2) "
                                                    :font-size "0.5rem"
                                                    :font-size "0.5rem"
                                                    :font-weight "500"}} "ESC"]
                                                    :font-weight "500"}} "ESC"]

+ 8 - 9
deps/shui/src/logseq/shui/list_item/v1.cljs

@@ -77,12 +77,12 @@
 
 
 (rum/defc root [{:keys [icon icon-theme query text info shortcut value-label value
 (rum/defc root [{:keys [icon icon-theme query text info shortcut value-label value
                         title highlighted on-highlight on-highlight-dep header on-click
                         title highlighted on-highlight on-highlight-dep header on-click
-                        hoverable compact rounded on-mouse-enter component-opts
-                        display-shortcut-on-highlight?] :as _props
+                        hoverable compact rounded on-mouse-enter component-opts] :as _props
                  :or {hoverable true rounded true}}
                  :or {hoverable true rounded true}}
                 {:keys [app-config] :as context}]
                 {:keys [app-config] :as context}]
   (let [ref (rum/create-ref)
   (let [ref (rum/create-ref)
-        highlight-query (partial highlight-query* app-config query)]
+        highlight-query (partial highlight-query* app-config query)
+        [hover? set-hover?] (rum/use-state false)]
     (rum/use-effect!
     (rum/use-effect!
      (fn []
      (fn []
        (when (and highlighted on-highlight)
        (when (and highlighted on-highlight)
@@ -99,6 +99,8 @@
                      (not highlighted) (str " "))
                      (not highlighted) (str " "))
             :ref ref
             :ref ref
             :on-click (when on-click on-click)
             :on-click (when on-click on-click)
+            :on-mouse-over #(set-hover? true)
+            :on-mouse-out #(set-hover? false)
             :on-mouse-enter (when on-mouse-enter on-mouse-enter)}
             :on-mouse-enter (when on-mouse-enter on-mouse-enter)}
            component-opts)
            component-opts)
      ;; header
      ;; header
@@ -133,10 +135,7 @@
            [:span.text-gray-11 (str (to-string value-label))])
            [:span.text-gray-11 (str (to-string value-label))])
          (when value
          (when value
            [:span.text-gray-11 (to-string value)])])
            [:span.text-gray-11 (to-string value)])])
-      (when (and shortcut
-                 (or (and display-shortcut-on-highlight? highlighted)
-                     (not display-shortcut-on-highlight?)))
-        [:div {:class (str "flex gap-1"
-                           (when display-shortcut-on-highlight? " fade-in"))
-               :style {:opacity (if highlighted 1 0.5)}}
+      (when shortcut
+        [:div {:class "flex gap-1"
+               :style {:opacity (if (or highlighted hover?) 1 0.5)}}
          (shortcut/root shortcut context)])]]))
          (shortcut/root shortcut context)])]]))

+ 67 - 79
deps/shui/src/logseq/shui/shortcut/v1.cljs

@@ -6,35 +6,42 @@
 
 
 (def mac? goog.userAgent/MAC)
 (def mac? goog.userAgent/MAC)
 (defn print-shortcut-key [key]
 (defn print-shortcut-key [key]
-  (case key
-    ("cmd" "command" "mod" "⌘" "meta") "⌘"
-    ("return" "enter" "⏎") "⏎"
-    ("shift" "⇧") "⇧"
-    ("alt" "option" "opt" "⌥") "⌥"
-    ("ctrl" "control" "⌃") "⌃"
-    ("space" " ") " "
-    ("up" "↑") "↑"
-    ("down" "↓") "↓"
-    ("left" "←") "←"
-    ("right" "→") "→"
-    ("tab") "⇥"
-    ("open-square-bracket") "["
-    ("close-square-bracket") "]"
-    ("dash") "-"
-    ("semicolon") ";"
-    ("equals") "="
-    ("single-quote") "'"
-    ("backslash") "\\"
-    ("comma") ","
-    ("period") "."
-    ("slash") "/"
-    ("grave-accent") "`"
-    ("page-up") ""
-    ("page-down") ""
-    (nil) ""
-    (name key)))
+  (let [result (if (coll? key)
+                 (string/join "+" key)
+                 (case (if (string? key)
+                         (string/lower-case key)
+                         key)
+                   ("cmd" "command" "mod" "⌘") (if mac? "⌘" "Ctrl")
+                   ("meta") (if mac? "⌘" "⊞")
+                   ("return" "enter" "⏎") "⏎"
+                   ("shift" "⇧") "⇧"
+                   ("alt" "option" "opt" "⌥") (if mac? "Opt" "Alt")
+                   ("ctrl" "control" "⌃") "Ctrl"
+                   ("space" " ") "Space"
+                   ("up" "↑") "↑"
+                   ("down" "↓") "↓"
+                   ("left" "←") "←"
+                   ("right" "→") "→"
+                   ("tab") "Tab"
+                   ("open-square-bracket") "["
+                   ("close-square-bracket") "]"
+                   ("dash") "-"
+                   ("semicolon") ";"
+                   ("equals") "="
+                   ("single-quote") "'"
+                   ("backslash") "\\"
+                   ("comma") ","
+                   ("period") "."
+                   ("slash") "/"
+                   ("grave-accent") "`"
+                   ("page-up") ""
+                   ("page-down") ""
+                   (nil) ""
+                   (name key)))]
+    (if (= (count result) 1)
+      result
+      (string/capitalize result))))
 
 
-;; TODO: shortcut component shouldn't worry about this
 (defn to-string [input]
 (defn to-string [input]
   (cond
   (cond
     (string? input) input
     (string? input) input
@@ -45,57 +52,38 @@
     (nil? input) ""
     (nil? input) ""
     :else (pr-str input)))
     :else (pr-str input)))
 
 
+(defn- parse-shortcuts
+  [s]
+  (->> (string/split s #" \| ")
+       (map (fn [x]
+              (->> (string/split x #" ")
+                   (map #(if (string/includes? % "+")
+                           (string/split % #"\+")
+                           %)))))))
+
+(rum/defc part
+  [context theme ks size]
+  (button/root {:theme theme
+                :interactive false
+                :tiled true
+                :tiles (map print-shortcut-key ks)
+                :size size
+                :mused true}
+               context))
+
 (rum/defc root
 (rum/defc root
-  [shortcut context & {:keys [tiled size theme]
-                       :or {tiled true
-                            size :sm
+  [shortcut context & {:keys [size theme]
+                       :or {size :sm
                             theme :gray}}]
                             theme :gray}}]
   (when (seq shortcut)
   (when (seq shortcut)
-    (if (coll? shortcut)
-      (let [texts (map print-shortcut-key shortcut)
-            tiled? (every? #(= (count %) 1) texts)]
-        (if tiled?
-          [:div.flex.flex-row
-           (for [text texts]
-             (button/root {:theme theme
-                           :interactive false
-                           :text (to-string text)
-                           :tiled tiled?
-                           :size size
-                           :mused true}
-                          context))]
-          (let [text' (string/join " " texts)]
-            (button/root {:theme theme
-                          :interactive false
-                          :text text'
-                          :tiled false
-                          :size size
-                          :mused true}
-                         context))))
-      [:<>
-       (for [[index option] (map-indexed vector (string/split shortcut #" \| "))]
-         [:<>
-          (when (< 0 index)
-            [:div.text-gray-11.text-sm "|"])
-          (let [[system-default option] (if (.startsWith option "system default: ")
-                                          [true (subs option 16)]
-                                          [false option])]
-            [:<>
-             (when system-default
-               [:div.mr-1.text-xs "System default: "])
-             (for [sequence (string/split option #" ")
-                   :let [text (->> (string/split sequence #"\+")
-                                   (map print-shortcut-key)
-                                   (apply str))]]
-               (let [tiled? (if (contains?
-                                 #{"backspace" "delete" "home" "end" "insert"}
-                                 (string/lower-case text))
-                              false
-                              tiled)]
-                 (button/root {:theme theme
-                               :interactive false
-                               :text (to-string text)
-                               :tiled tiled?
-                               :size size
-                               :mused true}
-                              context)))])])])))
+    (let [shortcuts (if (coll? shortcut)
+                      [shortcut]
+                      (parse-shortcuts shortcut))]
+      (for [[index binding] (map-indexed vector shortcuts)]
+        [:<>
+         (when (< 0 index)
+           [:div.text-gray-11.text-sm "|"])
+         (if (coll? (first binding))   ; + included
+           (for [ks binding]
+             (part context theme ks size))
+           (part context theme binding size))]))))

+ 6 - 0
e2e-tests/editor.spec.ts

@@ -14,6 +14,12 @@ import { dispatch_kb_events } from './util/keyboard-events'
 import * as kb_events from './util/keyboard-events'
 import * as kb_events from './util/keyboard-events'
 
 
 test('hashtag and quare brackets in same line #4178', async ({ page }) => {
 test('hashtag and quare brackets in same line #4178', async ({ page }) => {
+  try {
+    await page.waitForSelector('.notification-clear', { timeout: 10 })
+    page.click('.notification-clear')
+  } catch (error) {
+  }
+
   await createRandomPage(page)
   await createRandomPage(page)
 
 
   await page.type('textarea >> nth=0', '#foo bar')
   await page.type('textarea >> nth=0', '#foo bar')

+ 1 - 1
e2e-tests/page-search.spec.ts

@@ -40,7 +40,7 @@ test('Search page and blocks (diacritics)', async ({ page, block }) => {
 
 
   // check if diacritics are indexed
   // check if diacritics are indexed
   const results = await searchPage(page, 'Einführung in die Allgemeine Sprachwissenschaft' + rand)
   const results = await searchPage(page, 'Einführung in die Allgemeine Sprachwissenschaft' + rand)
-  await expect(results.length).toEqual(5) // 1 page + 2 block + 2 page content
+  await expect(results.length).toEqual(6) // 1 page + 2 block + 2 page content + 1 current page
   await closeSearchBox(page)
   await closeSearchBox(page)
 })
 })
 
 

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

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

+ 6 - 3
resources/css/shui.css

@@ -23,19 +23,22 @@
 }
 }
 
 
 .ui__button-tiled .ui__button__tile {
 .ui__button-tiled .ui__button__tile {
-  @apply flex items-center justify-center text-center;
+  @apply flex items-center justify-center text-center px-1;
 }
 }
 
 
 .ui__button-tiled.ui__button-size-md .ui__button__tile {
 .ui__button-tiled.ui__button-size-md .ui__button__tile {
-  @apply h-6 w-6;
+  @apply h-6;
+  min-width: 1.5rem;
 }
 }
 
 
 .ui__button-tiled.ui__button-size-sm .ui__button__tile {
 .ui__button-tiled.ui__button-size-sm .ui__button__tile {
-    @apply h-4 w-4;
+  @apply h-4;
+  min-width: 1rem;
 }
 }
 
 
 .ui__button__tile-separator {
 .ui__button__tile-separator {
   @apply w-px h-full bg-gray-08-alpha;
   @apply w-px h-full bg-gray-08-alpha;
+  min-height: 14px;
 }
 }
 
 
 .ui__button-theme-text {
 .ui__button-theme-text {

+ 1 - 1
resources/forge.config.js

@@ -4,7 +4,7 @@ module.exports = {
   packagerConfig: {
   packagerConfig: {
     name: 'Logseq',
     name: 'Logseq',
     icon: './icons/logseq_big_sur.icns',
     icon: './icons/logseq_big_sur.icns',
-    buildVersion: 73,
+    buildVersion: 74,
     protocols: [
     protocols: [
       {
       {
         "protocol": "logseq",
         "protocol": "logseq",

+ 0 - 8
resources/js/preload.js

@@ -90,14 +90,6 @@ contextBridge.exposeInMainWorld('apis', {
     await shell.openPath(path)
     await shell.openPath(path)
   },
   },
 
 
-  showItemInFolder (fullpath) {
-    if (IS_WIN32) {
-      shell.openPath(path.dirname(fullpath).replaceAll("/", "\\"))
-    } else {
-      shell.showItemInFolder(fullpath)
-    }
-  },
-
   /**
   /**
    * save all publish assets to disk
    * save all publish assets to disk
    *
    *

+ 1 - 1
resources/package.json

@@ -1,7 +1,7 @@
 {
 {
   "name": "Logseq",
   "name": "Logseq",
   "productName": "Logseq",
   "productName": "Logseq",
-  "version": "0.9.20",
+  "version": "0.10.0",
   "main": "electron.js",
   "main": "electron.js",
   "author": "Logseq",
   "author": "Logseq",
   "license": "AGPL-3.0",
   "license": "AGPL-3.0",

+ 34 - 18
scripts/src/logseq/tasks/db_graph/create_graph.cljs

@@ -5,6 +5,8 @@
   graph and current limitations"
   graph and current limitations"
   (:require [logseq.db.sqlite.db :as sqlite-db]
   (:require [logseq.db.sqlite.db :as sqlite-db]
             [logseq.db.sqlite.util :as sqlite-util]
             [logseq.db.sqlite.util :as sqlite-util]
+            [logseq.db.sqlite.create-graph :as sqlite-create-graph]
+            [logseq.db.frontend.property.util :as db-property-util]
             [logseq.outliner.cli.persist-graph :as persist-graph]
             [logseq.outliner.cli.persist-graph :as persist-graph]
             [logseq.db :as ldb]
             [logseq.db :as ldb]
             [clojure.string :as string]
             [clojure.string :as string]
@@ -27,7 +29,7 @@
   (let [config-content (or (some-> (find-on-classpath "templates/config.edn") fs/readFileSync str)
   (let [config-content (or (some-> (find-on-classpath "templates/config.edn") fs/readFileSync str)
                            (do (println "Setting graph's config to empty since no templates/config.edn was found.")
                            (do (println "Setting graph's config to empty since no templates/config.edn was found.")
                                "{}"))]
                                "{}"))]
-    (d/transact! conn (sqlite-util/build-db-initial-data config-content))))
+    (d/transact! conn (sqlite-create-graph/build-db-initial-data config-content))))
 
 
 (defn init-conn
 (defn init-conn
   "Create sqlite DB, initialize datascript connection and sync listener and then
   "Create sqlite DB, initialize datascript connection and sync listener and then
@@ -138,15 +140,20 @@
      * :blocks - This is a vec of datascript attribute maps e.g. `{:block/content \"bar\"}`.
      * :blocks - This is a vec of datascript attribute maps e.g. `{:block/content \"bar\"}`.
        :block/content is required and :properties can be passed to define block properties
        :block/content is required and :properties can be passed to define block properties
    * :properties - This is a map to configure properties where the keys are property names
    * :properties - This is a map to configure properties where the keys are property names
-     and the values are maps of datascript attributes e.g. `{:block/schema {:type :checkbox}}`
+     and the values are maps of datascript attributes e.g. `{:block/schema {:type :checkbox}}`.
+     An additional key `:closed-values` is available to define closed values. The key takes
+     a vec of maps containing keys :uuid, :value and :icon.
 
 
    The :properties for :pages-and-blocks is a map of property names to property
    The :properties for :pages-and-blocks is a map of property names to property
    values.  Multiple property values for a many cardinality property are defined
    values.  Multiple property values for a many cardinality property are defined
    as a set. The following property types are supported: :default, :url,
    as a set. The following property types are supported: :default, :url,
-   :checkbox, :number and :page. :checkbox and :number values are written
+   :checkbox, :number, :page and :date. :checkbox and :number values are written
    as booleans and integers. :page and :block are references that are written as
    as booleans and integers. :page and :block are references that are written as
-   vectors e.g. `[:page \"PAGE NAME\"]` and `[:block \"block content\"]`"
-  [{:keys [pages-and-blocks properties]}]
+   vectors e.g. `[:page \"PAGE NAME\"]` and `[:block \"block content\"]`
+   
+   This fn also takes an optional map arg which supports these keys:
+   * :property-uuids - A map of property keyword names to uuids to provide ids for built-in properties"
+  [{:keys [pages-and-blocks properties]} & {:as options}]
   (let [;; add uuids before tx for refs in :properties
   (let [;; add uuids before tx for refs in :properties
         pages-and-blocks' (mapv (fn [{:keys [page blocks]}]
         pages-and-blocks' (mapv (fn [{:keys [page blocks]}]
                                   (cond-> {:page (merge {:block/uuid (random-uuid)} page)}
                                   (cond-> {:page (merge {:block/uuid (random-uuid)} page)}
@@ -157,19 +164,28 @@
         property-db-ids (->> property-uuids
         property-db-ids (->> property-uuids
                              (map #(vector (name (first %)) (new-db-id)))
                              (map #(vector (name (first %)) (new-db-id)))
                              (into {}))
                              (into {}))
-        new-properties-tx (mapv (fn [[prop-name uuid]]
-                                  (sqlite-util/build-new-property
-                                   (merge {:db/id (or (property-db-ids (name prop-name))
-                                                      (throw (ex-info "No :db/id for property" {:property prop-name})))
-                                           :block/uuid uuid
-                                           :block/schema (merge {:type :default}
-                                                                (get-in properties [prop-name :block/schema]))
-                                           :block/original-name (name prop-name)
-                                           :block/name (sqlite-util/sanitize-page-name (name prop-name))}
-                                          (when-let [props (not-empty (get-in properties [prop-name :properties]))]
-                                            {:block/properties (->block-properties-tx props uuid-maps)
-                                             :block/refs (build-property-refs props property-db-ids)}))))
-                                property-uuids)
+        new-properties-tx (vec
+                           (mapcat
+                            (fn [[prop-name uuid]]
+                              (if (get-in properties [prop-name :closed-values])
+                                (db-property-util/build-closed-values
+                                 prop-name
+                                 (assoc (get properties prop-name) :block/uuid uuid)
+                                 {:icon-id
+                                  (get-in options [:property-uuids :icon])
+                                  :translate-closed-page-value-fn
+                                  #(hash-map :block/uuid (translate-property-value (:value %) uuid-maps))
+                                  :property-attributes
+                                  {:db/id (or (property-db-ids (name prop-name))
+                                              (throw (ex-info "No :db/id for property" {:property prop-name})))}})
+                                [(sqlite-util/build-new-property
+                                  (merge (db-property-util/new-property-tx prop-name (get-in properties [prop-name :block/schema]) uuid)
+                                         {:db/id (or (property-db-ids (name prop-name))
+                                                     (throw (ex-info "No :db/id for property" {:property prop-name})))}
+                                         (when-let [props (not-empty (get-in properties [prop-name :properties]))]
+                                           {:block/properties (->block-properties-tx props uuid-maps)
+                                            :block/refs (build-property-refs props property-db-ids)})))]))
+                            property-uuids))
         pages-and-blocks-tx
         pages-and-blocks-tx
         (vec
         (vec
          (mapcat
          (mapcat

+ 46 - 5
scripts/src/logseq/tasks/db_graph/create_graph_with_properties.cljs

@@ -4,6 +4,7 @@
    NOTE: This script is also used in CI to confirm graph creation works"
    NOTE: This script is also used in CI to confirm graph creation works"
   (:require [logseq.tasks.db-graph.create-graph :as create-graph]
   (:require [logseq.tasks.db-graph.create-graph :as create-graph]
             [logseq.db.sqlite.util :as sqlite-util]
             [logseq.db.sqlite.util :as sqlite-util]
+            [logseq.db.frontend.property.type :as db-property-type]
             [clojure.string :as string]
             [clojure.string :as string]
             [datascript.core :as d]
             [datascript.core :as d]
             ["path" :as node-path]
             ["path" :as node-path]
@@ -25,10 +26,36 @@
   [date days]
   [date days]
   (new js/Date (- (.getTime date) (* days 24 60 60 1000))))
   (new js/Date (- (.getTime date) (* days 24 60 60 1000))))
 
 
+(defn- build-closed-values-config
+  [{:keys [dates]}]
+  {:default-closed
+   (mapv #(hash-map :value %
+                    :uuid (random-uuid)
+                    :icon {:id % :name % :type :emoji})
+         ["joy" "sob" "upside_down_face"])
+   :url-closed
+   (mapv #(hash-map :value %
+                    :uuid (random-uuid))
+         ["https://logseq.com" "https://docs.logseq.com" "https://github.com/logseq/logseq"])
+   :number-closed
+   (mapv #(hash-map :value %
+                    :uuid (random-uuid))
+         [10 42 (rand 100)])
+   :page-closed
+   (mapv #(hash-map :value [:page %])
+         ["page 1" "page 2" "page 3"])
+   :date-closed
+   (mapv #(hash-map :value [:page (date-journal-title %)])
+         dates)})
+
 (defn- create-init-data
 (defn- create-init-data
   []
   []
   (let [today (new js/Date)
   (let [today (new js/Date)
-        yesterday (subtract-days today 1)]
+        yesterday (subtract-days today 1)
+        two-days-ago (subtract-days today 2)
+        closed-values-config (build-closed-values-config {:dates [today yesterday two-days-ago]})
+        random-closed-value #(-> closed-values-config % rand-nth :uuid)
+        random-page-closed-value #(-> closed-values-config % rand-nth :value)]
     {:pages-and-blocks
     {:pages-and-blocks
      [{:page
      [{:page
        {:block/name (date-journal-title today) :block/journal? true :block/journal-day (date-journal-day today)}
        {:block/name (date-journal-title today) :block/journal? true :block/journal-day (date-journal-day today)}
@@ -37,19 +64,26 @@
         {:block/content "[[Queries]]"}]}
         {:block/content "[[Queries]]"}]}
       {:page
       {:page
        {:block/name (date-journal-title yesterday) :block/journal? true :block/journal-day (date-journal-day yesterday)}}
        {:block/name (date-journal-title yesterday) :block/journal? true :block/journal-day (date-journal-day yesterday)}}
+      {:page
+       {:block/name (date-journal-title two-days-ago) :block/journal? true :block/journal-day (date-journal-day two-days-ago)}}
       {:page {:block/name "properties"}
       {:page {:block/name "properties"}
        :blocks
        :blocks
        [{:block/content "default property block" :properties {:default "haha"}}
        [{:block/content "default property block" :properties {:default "haha"}}
+        {:block/content "default-closed property block" :properties {:default-closed (random-closed-value :default-closed)}}
         {:block/content "url property block" :properties {:url "https://logseq.com"}}
         {:block/content "url property block" :properties {:url "https://logseq.com"}}
         {:block/content "url-many property block" :properties {:url-many #{"https://logseq.com" "https://docs.logseq.com"}}}
         {:block/content "url-many property block" :properties {:url-many #{"https://logseq.com" "https://docs.logseq.com"}}}
+        {:block/content "url-closed property block" :properties {:url-closed (random-closed-value :url-closed)}}
         {:block/content "checkbox property block" :properties {:checkbox true}}
         {:block/content "checkbox property block" :properties {:checkbox true}}
         {:block/content "number property block" :properties {:number 5}}
         {:block/content "number property block" :properties {:number 5}}
         {:block/content "number-many property block" :properties {:number-many #{5 10}}}
         {:block/content "number-many property block" :properties {:number-many #{5 10}}}
+        {:block/content "number-closed property block" :properties {:number-closed (random-closed-value :number-closed)}}
         {:block/content "page property block" :properties {:page [:page "page 1"]}}
         {:block/content "page property block" :properties {:page [:page "page 1"]}}
         {:block/content "page-many property block" :properties {:page-many #{[:page "page 1"] [:page "page 2"]}}}
         {:block/content "page-many property block" :properties {:page-many #{[:page "page 1"] [:page "page 2"]}}}
+        {:block/content "page-closed property block" :properties {:page-closed (random-page-closed-value :page-closed)}}
         {:block/content "date property block" :properties {:date [:page (date-journal-title today)]}}
         {:block/content "date property block" :properties {:date [:page (date-journal-title today)]}}
         {:block/content "date-many property block" :properties {:date-many #{[:page (date-journal-title today)]
         {:block/content "date-many property block" :properties {:date-many #{[:page (date-journal-title today)]
-                                                                             [:page (date-journal-title yesterday)]}}}]}
+                                                                             [:page (date-journal-title yesterday)]}}}
+        {:block/content "date-closed property block" :properties {:date-closed (random-page-closed-value :date-closed)}}]}
       {:page {:block/name "queries"}
       {:page {:block/name "queries"}
        :blocks
        :blocks
        [{:block/content "{{query (property :default \"haha\")}}"}
        [{:block/content "{{query (property :default \"haha\")}}"}
@@ -66,12 +100,17 @@
        :blocks
        :blocks
        [{:block/content "yee"}
        [{:block/content "yee"}
         {:block/content "haw"}]}
         {:block/content "haw"}]}
-      {:page {:block/name "page 2"}}]
+      {:page {:block/name "page 2"}}
+      {:page {:block/name "page 3"}}]
      :properties
      :properties
      (->> [:default :url :checkbox :number :page :date]
      (->> [:default :url :checkbox :number :page :date]
           (mapcat #(cond-> [[% {:block/schema {:type %}}]]
           (mapcat #(cond-> [[% {:block/schema {:type %}}]]
-                     (not (#{:checkbox :default} %))
+                     (db-property-type/property-type-allows-schema-attribute? % :cardinality)
                      (conj [(keyword (str (name %) "-many")) {:block/schema {:type % :cardinality :many}}])))
                      (conj [(keyword (str (name %) "-many")) {:block/schema {:type % :cardinality :many}}])))
+          (into (mapv #(vector (keyword (str (name %) "-closed"))
+                               {:closed-values (closed-values-config (keyword (str (name %) "-closed")))
+                                :block/schema {:type %}})
+                      [:default :url :number :page :date]))
           (into {}))}))
           (into {}))}))
 
 
 (defn -main [args]
 (defn -main [args]
@@ -83,7 +122,9 @@
                         ((juxt node-path/dirname node-path/basename) graph-dir)
                         ((juxt node-path/dirname node-path/basename) graph-dir)
                         [(node-path/join (os/homedir) "logseq" "graphs") graph-dir])
                         [(node-path/join (os/homedir) "logseq" "graphs") graph-dir])
         conn (create-graph/init-conn dir db-name)
         conn (create-graph/init-conn dir db-name)
-        blocks-tx (create-graph/create-blocks-tx (create-init-data))]
+        blocks-tx (create-graph/create-blocks-tx
+                   (create-init-data)
+                   {:property-uuids {:icon (:block/uuid (d/entity @conn [:block/name "icon"]))}})]
     (println "Generating" (count (filter :block/name blocks-tx)) "pages and"
     (println "Generating" (count (filter :block/name blocks-tx)) "pages and"
              (count (filter :block/content blocks-tx)) "blocks ...")
              (count (filter :block/content blocks-tx)) "blocks ...")
     (d/transact! conn blocks-tx)
     (d/transact! conn blocks-tx)

+ 3 - 1
scripts/src/logseq/tasks/dev/db_and_file_graphs.clj

@@ -15,7 +15,7 @@
         ["logseq.db.sqlite." "logseq.db.frontend.property" "logseq.db.frontend.malli-schema"
         ["logseq.db.sqlite." "logseq.db.frontend.property" "logseq.db.frontend.malli-schema"
          "electron.db"
          "electron.db"
          "frontend.handler.db-based."
          "frontend.handler.db-based."
-         "frontend.components.property" "frontend.components.class"]))
+         "frontend.components.property" "frontend.components.class" "frontend.components.db-based"]))
 
 
 (def file-graph-ns
 (def file-graph-ns
   "Namespaces or parent namespaces _only_ for file graphs"
   "Namespaces or parent namespaces _only_ for file graphs"
@@ -32,6 +32,8 @@
   ["src/main/frontend/handler/db_based"
   ["src/main/frontend/handler/db_based"
    "src/main/frontend/components/class.cljs"
    "src/main/frontend/components/class.cljs"
    "src/main/frontend/components/property.cljs"
    "src/main/frontend/components/property.cljs"
+   "src/main/frontend/components/property"
+   "src/main/frontend/components/db_based"
    "src/electron/electron/db.cljs"])
    "src/electron/electron/db.cljs"])
 
 
 (def file-graph-paths
 (def file-graph-paths

+ 1 - 1
scripts/src/logseq/tasks/dev/publishing.cljs

@@ -19,7 +19,7 @@
                        static-dir
                        static-dir
                        graph-dir
                        graph-dir
                        output-path
                        output-path
-                       {:repo-config repo-config})))
+                       {:repo-config repo-config :ui/theme "dark" :ui/radix-color :purple})))
 
 
 (defn- publish-db-graph [static-dir graph-dir output-path]
 (defn- publish-db-graph [static-dir graph-dir output-path]
   (let [db-name (node-path/basename graph-dir)
   (let [db-name (node-path/basename graph-dir)

+ 2 - 1
scripts/src/logseq/tasks/lang.clj

@@ -165,7 +165,8 @@
          :settings-of-plugins :search-item/whiteboard :shortcut.category/navigating
          :settings-of-plugins :search-item/whiteboard :shortcut.category/navigating
          :settings-page/enable-tooltip :settings-page/enable-whiteboards :settings-page/plugin-system}
          :settings-page/enable-tooltip :settings-page/enable-whiteboards :settings-page/plugin-system}
    :es #{:settings-page/tab-general :settings-page/tab-editor :whiteboard/color :right-side-bar/history-global}
    :es #{:settings-page/tab-general :settings-page/tab-editor :whiteboard/color :right-side-bar/history-global}
-   :it #{:plugins}
+   :it #{:home :handbook/home :host :help/awesome-logseq :on-boarding/section-computer
+         :settings-page/tab-account :settings-page/tab-editor :whiteboard/link}
    :nl #{:plugins :type :left-side-bar/nav-recent-pages :plugin/update}
    :nl #{:plugins :type :left-side-bar/nav-recent-pages :plugin/update}
    :pl #{:port}
    :pl #{:port}
    :pt-BR #{:plugins :right-side-bar/flashcards :settings-page/enable-flashcards :page/backlinks
    :pt-BR #{:plugins :right-side-bar/flashcards :settings-page/enable-flashcards :page/backlinks

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

@@ -70,7 +70,19 @@
            url (decode-protected-assets-schema-path url)
            url (decode-protected-assets-schema-path url)
            path (string/replace url "assets://" "")
            path (string/replace url "assets://" "")
            path (js/decodeURIComponent path)]
            path (js/decodeURIComponent path)]
-       (callback #js {:path path}))))
+       (cond (or (string/starts-with? path "/")
+                 (re-find #"(?i)^/[a-zA-Z]:" path))
+             (callback #js {:path path})
+
+             ;; assume winwdows unc path
+             utils/win32?
+             (do (logger/debug :resolve-assets-url url)
+                 (callback #js {:path (str "//" path)}))
+
+             :else
+             (do
+               (logger/warn ::resolve-assets-url "Unknown assets url" url)
+               (callback #js {:path path}))))))
 
 
   (.registerFileProtocol
   (.registerFileProtocol
    protocol FILE_LSP_SCHEME
    protocol FILE_LSP_SCHEME

+ 17 - 9
src/electron/electron/handler.cljs

@@ -86,8 +86,14 @@
 
 
 (defmethod handle :openFileBackupDir [_window [_ repo path]]
 (defmethod handle :openFileBackupDir [_window [_ repo path]]
   (when (string? path)
   (when (string? path)
-    (let [dir (backup-file/get-backup-dir repo path)]
-      (.openPath shell dir))))
+    (let [dir (backup-file/get-backup-dir repo path)
+          full-path (utils/to-native-win-path! dir)]
+      (.openPath shell full-path))))
+
+(defmethod handle :openFileInFolder [_window [_ full-path]]
+  (when-let [full-path (utils/to-native-win-path! full-path)]
+    (logger/info ::open-file-in-folder full-path)
+    (.showItemInFolder shell full-path)))
 
 
 (defmethod handle :readFile [_window [_ path]]
 (defmethod handle :readFile [_window [_ path]]
   (utils/read-file path))
   (utils/read-file path))
@@ -246,7 +252,7 @@
     (->> (common-graph/read-directories dir)
     (->> (common-graph/read-directories dir)
          (remove (fn [s] (= s db/unlinked-graphs-dir)))
          (remove (fn [s] (= s db/unlinked-graphs-dir)))
          (map graph-name->path)
          (map graph-name->path)
-         (map (fn [s] (str "logseq_db_" s))))))
+         (map (fn [s] (str sqlite-util/db-version-prefix s))))))
 
 
 (defn- get-graphs
 (defn- get-graphs
   []
   []
@@ -256,13 +262,15 @@
 
 
 ;; TODO support alias mechanism
 ;; TODO support alias mechanism
 (defn get-graph-name
 (defn get-graph-name
-  "Given a graph's name of string, returns the graph's fullname.
-   E.g., given `cat`, returns `logseq_local_<path_to_directory>/cat`
-   Returns `nil` if no such graph exists."
+  "Given a graph's name of string, returns the graph's fullname. For example, given
+  `cat`, returns `logseq_local_<path_to_directory>/cat` for a file graph and
+  `logseq_db_cat` for a db graph.  Returns `nil` if no such graph exists."
   [graph-identifier]
   [graph-identifier]
   (->> (get-graphs)
   (->> (get-graphs)
-       (some #(when (string/ends-with? (utils/normalize-lc %)
-                                       (str "/" (utils/normalize-lc graph-identifier)))
+       (some #(when (or
+                     (= (utils/normalize-lc %) (utils/normalize-lc (str sqlite-util/db-version-prefix graph-identifier)))
+                     (string/ends-with? (utils/normalize-lc %)
+                                        (str "/" (utils/normalize-lc graph-identifier))))
                 %))))
                 %))))
 
 
 (defmethod handle :getGraphs [_window [_]]
 (defmethod handle :getGraphs [_window [_]]
@@ -385,7 +393,7 @@
   (db/open-db! repo)
   (db/open-db! repo)
   (dt/write-transit-str (sqlite-db/get-initial-data repo)))
   (dt/write-transit-str (sqlite-db/get-initial-data repo)))
 
 
-(defmethod handle :get-other-data [_window [_ repo journal-block-uuids _opts]]
+(defmethod handle :get-other-data [_window [_ _repo _journal-block-uuids _opts]]
   nil)
   nil)
 
 
 ;; DB related IPCs End
 ;; DB related IPCs End

+ 18 - 4
src/electron/electron/utils.cljs

@@ -6,7 +6,9 @@
             [clojure.string :as string]
             [clojure.string :as string]
             [electron.configs :as cfgs]
             [electron.configs :as cfgs]
             [electron.logger :as logger]
             [electron.logger :as logger]
+            [logseq.db.sqlite.util :as sqlite-util]
             [cljs-bean.core :as bean]
             [cljs-bean.core :as bean]
+            [electron.db :as db]
             [promesa.core :as p]))
             [promesa.core :as p]))
 
 
 (defonce *win (atom nil)) ;; The main window
 (defonce *win (atom nil)) ;; The main window
@@ -43,6 +45,14 @@
       (string/replace path "\\" "/")
       (string/replace path "\\" "/")
       path)))
       path)))
 
 
+(defn to-native-win-path!
+  "Convert path to native win path"
+  [path]
+  (when (not-empty path)
+    (if win32?
+      (string/replace path "/" "\\")
+      path)))
+
 (defn get-ls-dotdir-root
 (defn get-ls-dotdir-root
   []
   []
   (let [lg-dir (node-path/join (.getPath app "home") ".logseq")]
   (let [lg-dir (node-path/join (.getPath app "home") ".logseq")]
@@ -250,13 +260,17 @@
 (defn get-graph-dir
 (defn get-graph-dir
   "required by all internal state in the electron section"
   "required by all internal state in the electron section"
   [graph-name]
   [graph-name]
-  (when (string/includes? graph-name "logseq_local_")
-    (string/replace-first graph-name "logseq_local_" "")))
+  (cond (string/starts-with? graph-name sqlite-util/db-version-prefix)
+        (node-path/join (db/get-graphs-dir) (string/replace-first graph-name sqlite-util/db-version-prefix ""))
+        (string/includes? graph-name "logseq_local_")
+        (string/replace-first graph-name "logseq_local_" "")))
 
 
 (defn get-graph-name
 (defn get-graph-name
-  "reversing `get-graph-dir`"
+  "Reverse `get-graph-dir`"
   [graph-dir]
   [graph-dir]
-  (str "logseq_local_" graph-dir))
+  (if (= (db/get-graphs-dir) (node-path/dirname graph-dir))
+    (str sqlite-util/db-version-prefix (node-path/basename graph-dir))
+    (str "logseq_local_" graph-dir)))
 
 
 (defn decode-protected-assets-schema-path
 (defn decode-protected-assets-schema-path
   [schema-path]
   [schema-path]

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

@@ -63,7 +63,7 @@
 
 
 
 
 (defn linear-gradient [color-name color-stop gradient-level]
 (defn linear-gradient [color-name color-stop gradient-level]
-  (let [color-index (.indexOf color-list color-name)
+  (let [color-index (.indexOf color-list (keyword color-name))
         step (fn [dist]
         step (fn [dist]
                (str "var(--rx-"
                (str "var(--rx-"
                  (name (nth color-list (mod (+ color-index dist) (count color-list))))
                  (name (nth color-list (mod (+ color-index dist) (count color-list))))
@@ -85,5 +85,6 @@
                        (str/replace "hsl(" "")
                        (str/replace "hsl(" "")
                        (str/replace ")" "")
                        (str/replace ")" "")
                        (str/split ","))]
                        (str/split ","))]
-    (let [hsl-color (map js/parseFloat hsl-color)]
+    (when-let [hsl-color (and (not (str/blank? (first hsl-color)))
+                           (map js/parseFloat hsl-color))]
       (apply util/hsl2hex hsl-color))))
       (apply util/hsl2hex hsl-color))))

+ 4 - 0
src/main/frontend/common.css

@@ -923,6 +923,10 @@ html.is-mobile {
   #journals .journal-item:first-child {
   #journals .journal-item:first-child {
     margin-top: 5px;
     margin-top: 5px;
   }
   }
+
+  main.theme-inner {
+    @apply overflow-x-hidden;
+  }
 }
 }
 
 
 @layer base {
 @layer base {

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

@@ -83,7 +83,8 @@
             [reitit.frontend.easy :as rfe]
             [reitit.frontend.easy :as rfe]
             [rum.core :as rum]
             [rum.core :as rum]
             [shadow.loader :as loader]
             [shadow.loader :as loader]
-            [logseq.common.path :as path]))
+            [logseq.common.path :as path]
+            [electron.ipc :as ipc]))
 
 
 ;; local state
 ;; local state
 (defonce *dragging?
 (defonce *dragging?
@@ -316,7 +317,7 @@
                  :on-click      (fn [e]
                  :on-click      (fn [e]
                                   (util/stop e)
                                   (util/stop e)
                                   (if local?
                                   (if local?
-                                    (js/window.apis.showItemInFolder image-src)
+                                    (ipc/ipc "openFileInFolder" image-src)
                                     (js/window.apis.openExternal image-src)))}
                                     (js/window.apis.openExternal image-src)))}
                 image-src])
                 image-src])
              [:.flex
              [:.flex
@@ -2319,7 +2320,7 @@
       [:div.closed-values-properties.flex.flex-row.items-center.gap-1.select-none.h-full
       [:div.closed-values-properties.flex.flex-row.items-center.gap-1.select-none.h-full
        (for [pid closed-values-properties]
        (for [pid closed-values-properties]
          (when-let [property (db/entity [:block/uuid pid])]
          (when-let [property (db/entity [:block/uuid pid])]
-           (pv/property-value block property (get (:block/properties block) pid) {:icon? true})))])))
+           (pv/property-value block property (get (:block/properties block) pid) {:icon? true :page-cp page-cp})))])))
 
 
 (rum/defc ^:large-vars/cleanup-todo block-content < rum/reactive
 (rum/defc ^:large-vars/cleanup-todo block-content < rum/reactive
   [config {:block/keys [uuid content properties scheduled deadline format pre-block?] :as block} edit-input-id block-id slide? selected? *ref]
   [config {:block/keys [uuid content properties scheduled deadline format pre-block?] :as block} edit-input-id block-id slide? selected? *ref]
@@ -3603,10 +3604,8 @@
                          (:db/id parent)))))
                          (:db/id parent)))))
                  {:debug-id page})])))))]
                  {:debug-id page})])))))]
 
 
-     (and (:ref? config)
-          (:group-by-page? config)
-          (vector? (first blocks)))
-     [:div.flex.flex-col
+     (and (:ref? config) (:group-by-page? config) (vector? (first blocks)))
+     [:div.flex.flex-col.references-blocks-wrap
       (let [blocks (sort-by (comp :block/journal-day first) > blocks)]
       (let [blocks (sort-by (comp :block/journal-day first) > blocks)]
         (for [[page page-blocks] blocks]
         (for [[page page-blocks] blocks]
           (ui/lazy-visible
           (ui/lazy-visible
@@ -3616,7 +3615,7 @@
                    page (db/entity (:db/id page))
                    page (db/entity (:db/id page))
                    ;; FIXME: parents need to be sorted
                    ;; FIXME: parents need to be sorted
                    parent-blocks (group-by :block/parent page-blocks)]
                    parent-blocks (group-by :block/parent page-blocks)]
-               [:div.my-2 {:key (str "page-" (:db/id page))}
+               [:div.my-2.references-blocks-item {:key (str "page-" (:db/id page))}
                 (ui/foldable
                 (ui/foldable
                  [:div
                  [:div
                   (page-cp config page)
                   (page-cp config page)

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

@@ -479,6 +479,7 @@
 
 
 .color-level {
 .color-level {
   background-color: or(--ls-right-sidebar-content-background, --lx-gray-02, --color-level-1);
   background-color: or(--ls-right-sidebar-content-background, --lx-gray-02, --color-level-1);
+
   .dark & {
   .dark & {
     background-color: or(--ls-right-sidebar-content-background, --lx-gray-01, --color-level-1);
     background-color: or(--ls-right-sidebar-content-background, --lx-gray-01, --color-level-1);
   }
   }
@@ -740,3 +741,19 @@ html.is-mac {
     }
     }
   }
   }
 }
 }
+
+.references-blocks {
+  &-wrap {
+    .foldable-title {
+      @apply ml-3;
+
+      .block-control {
+        @apply relative right-[-5px] top-[1px];
+      }
+    }
+  }
+
+  &-item {
+    @apply bg-gray-03 rounded p-4;
+  }
+}

+ 74 - 49
src/main/frontend/components/cmdk.cljs

@@ -202,18 +202,26 @@
 ;; The pages search action uses an existing handler
 ;; The pages search action uses an existing handler
 (defmethod load-results :pages [group state]
 (defmethod load-results :pages [group state]
   (let [!input (::input state)
   (let [!input (::input state)
-        !results (::results state)]
+        !results (::results state)
+        repo (state/get-current-repo)]
     (swap! !results assoc-in [group :status] :loading)
     (swap! !results assoc-in [group :status] :loading)
     (p/let [pages (search/page-search @!input)
     (p/let [pages (search/page-search @!input)
-            items (map
-                   (fn [page]
-                     (let [entity (db/entity [:block/name (util/page-name-sanity-lc page)])
-                           whiteboard? (= (:block/type entity) "whiteboard")]
-                       (hash-map :icon (if whiteboard? "whiteboard" "page")
-                                 :icon-theme :gray
-                                 :text page
-                                 :source-page page)))
-                   pages)]
+            items (->> pages
+                       (remove nil?)
+                       (map
+                        (fn [page]
+                          (let [entity (db/entity [:block/name (util/page-name-sanity-lc page)])
+                                whiteboard? (= (:block/type entity) "whiteboard")
+                                source-page (model/get-alias-source-page repo page)]
+                            (hash-map :icon (if whiteboard? "whiteboard" "page")
+                                      :icon-theme :gray
+                                      :text (if source-page
+                                              [:div.flex.flex-row.items-center.gap-2
+                                               page
+                                               [:div.opacity-50.font-normal "alias of"]
+                                               (:block/original-name source-page)]
+                                              page)
+                                      :source-page page)))))]
       (swap! !results update group        merge {:status :success :items items}))))
       (swap! !results update group        merge {:status :success :items items}))))
 
 
 ;; The blocks search action uses an existing handler
 ;; The blocks search action uses an existing handler
@@ -256,6 +264,27 @@
                    files)]
                    files)]
       (swap! !results update group        merge {:status :success :items items}))))
       (swap! !results update group        merge {:status :success :items items}))))
 
 
+;; FIXME: recent search
+;; (defmethod load-results :recents [group state]
+;;   (let [!input (::input state)
+;;         !results (::results state)
+;;         recent-searches (mapv (fn [q] {:type :search :data q}) (db/get-key-value :recent/search))
+;;         recent-pages (->> (filter string? (db/get-key-value :recent/pages))
+;;                           (keep (fn [page]
+;;                                   (when-let [page-entity (db/entity [:block/name (util/page-name-sanity-lc page)])]
+;;                                     {:type :page :data (:block/original-name page-entity)})))
+;;                           vec)]
+;;     (swap! !results assoc-in [group :status] :loading)
+;;     (let [items (->> (concat recent-searches recent-pages)
+;;                      (filter #(string/includes? (lower-case-str (:data %)) (lower-case-str @!input)))
+;;                      (map #(hash-map :icon (if (= :page (:type %)) "page" "history")
+;;                                      :icon-theme :gray
+;;                                      :text (:data %)
+;;                                      :source-recent %
+;;                                      :source-page (when (= :page (:type %)) (:data %))
+;;                                      :source-search (when (= :search (:type %)) (:data %)))))]
+;;       (swap! !results update group merge {:status :success :items items}))))
+
 (defn- get-filter-q
 (defn- get-filter-q
   [input]
   [input]
   (or (when (string/starts-with? input "/")
   (or (when (string/starts-with? input "/")
@@ -284,24 +313,22 @@
       (load-results :filters state)
       (load-results :filters state)
       (load-results :files state))))
       (load-results :files state))))
 
 
-(defn close-unless-alt! [state]
-  (when-not (some-> state ::alt? deref)
-    (state/close-modal!)))
-
 (defn- copy-block-ref [state]
 (defn- copy-block-ref [state]
   (when-let [block-uuid (some-> state state->highlighted-item :source-block :block/uuid uuid)]
   (when-let [block-uuid (some-> state state->highlighted-item :source-block :block/uuid uuid)]
     (editor-handler/copy-block-ref! block-uuid block-ref/->block-ref)
     (editor-handler/copy-block-ref! block-uuid block-ref/->block-ref)
-    (close-unless-alt! state)))
+    (state/close-modal!)))
 
 
 (defmulti handle-action (fn [action _state _event] action))
 (defmulti handle-action (fn [action _state _event] action))
 
 
 (defmethod handle-action :open-page [_ state _event]
 (defmethod handle-action :open-page [_ state _event]
   (when-let [page-name (some-> state state->highlighted-item :source-page)]
   (when-let [page-name (some-> state state->highlighted-item :source-page)]
-    (let [page (db/entity [:block/name (util/page-name-sanity-lc page-name)])]
+    (let [redirect-page-name (model/get-redirect-page-name page-name)
+          page (db/entity [:block/name (util/page-name-sanity-lc redirect-page-name)])
+          original-name (:block/original-name page)]
       (if (= (:block/type page) "whiteboard")
       (if (= (:block/type page) "whiteboard")
-        (route-handler/redirect-to-whiteboard! page-name)
-        (route-handler/redirect-to-page! page-name)))
-    (close-unless-alt! state)))
+        (route-handler/redirect-to-whiteboard! original-name)
+        (route-handler/redirect-to-page! original-name)))
+    (state/close-modal!)))
 
 
 (defmethod handle-action :open-block [_ state _event]
 (defmethod handle-action :open-block [_ state _event]
   (let [block-id (some-> state state->highlighted-item :source-block :block/uuid uuid)
   (let [block-id (some-> state state->highlighted-item :source-block :block/uuid uuid)
@@ -311,18 +338,20 @@
         (if (= (:block/type page) "whiteboard")
         (if (= (:block/type page) "whiteboard")
           (route-handler/redirect-to-whiteboard! page-name {:block-id block-id})
           (route-handler/redirect-to-whiteboard! page-name {:block-id block-id})
           (route-handler/redirect-to-page! page-name {:anchor (str "ls-block-" block-id)})))
           (route-handler/redirect-to-page! page-name {:anchor (str "ls-block-" block-id)})))
-      (close-unless-alt! state))))
+      (state/close-modal!))))
 
 
 (defmethod handle-action :open-page-right [_ state _event]
 (defmethod handle-action :open-page-right [_ state _event]
   (when-let [page-name (some-> state state->highlighted-item :source-page)]
   (when-let [page-name (some-> state state->highlighted-item :source-page)]
-    (when-let [page (db/entity [:block/name (util/page-name-sanity-lc page-name)])]
-      (editor-handler/open-block-in-sidebar! (:block/uuid page)))
-    (close-unless-alt! state)))
+    (let [redirect-page-name (model/get-redirect-page-name page-name)
+          page (db/entity [:block/name (util/page-name-sanity-lc redirect-page-name)])]
+      (when page
+        (editor-handler/open-block-in-sidebar! (:block/uuid page))))
+    (state/close-modal!)))
 
 
 (defmethod handle-action :open-block-right [_ state _event]
 (defmethod handle-action :open-block-right [_ state _event]
   (when-let [block-uuid (some-> state state->highlighted-item :source-block :block/uuid uuid)]
   (when-let [block-uuid (some-> state state->highlighted-item :source-block :block/uuid uuid)]
     (editor-handler/open-block-in-sidebar! block-uuid)
     (editor-handler/open-block-in-sidebar! block-uuid)
-    (close-unless-alt! state)))
+    (state/close-modal!)))
 
 
 (defmethod handle-action :open [_ state event]
 (defmethod handle-action :open [_ state event]
   (when-let [item (some-> state state->highlighted-item)]
   (when-let [item (some-> state state->highlighted-item)]
@@ -355,7 +384,7 @@
     (when-let [action (:action command)]
     (when-let [action (:action command)]
       (action)
       (action)
       (when-not (contains? #{:graph/open :graph/remove :ui/toggle-settings :go/flashcards} (:id command))
       (when-not (contains? #{:graph/open :graph/remove :ui/toggle-settings :go/flashcards} (:id command))
-        (close-unless-alt! state)))))
+        (state/close-modal!)))))
 
 
 (defmethod handle-action :create [_ state _event]
 (defmethod handle-action :create [_ state _event]
   (let [item (state->highlighted-item state)
   (let [item (state->highlighted-item state)
@@ -363,20 +392,17 @@
         create-class? (string/starts-with? @!input "#")
         create-class? (string/starts-with? @!input "#")
         create-whiteboard? (= :whiteboard (:source-create item))
         create-whiteboard? (= :whiteboard (:source-create item))
         create-page? (= :page (:source-create item))
         create-page? (= :page (:source-create item))
-        alt? (some-> state ::alt deref)
         class (when create-class? (get-class-from-input @!input))]
         class (when create-class? (get-class-from-input @!input))]
     (cond
     (cond
       create-class? (page-handler/create! class
       create-class? (page-handler/create! class
                                           {:redirect? false
                                           {:redirect? false
                                            :create-first-block? false
                                            :create-first-block? false
                                            :class? true})
                                            :class? true})
-      (and create-whiteboard? alt?) (whiteboard-handler/create-new-whiteboard-page! @!input)
-      (and create-whiteboard? (not alt?)) (whiteboard-handler/create-new-whiteboard-and-redirect! @!input)
-      (and create-page? alt?) (page-handler/create! @!input {:redirect? false})
-      (and create-page? (not alt?)) (page-handler/create! @!input {:redirect? true}))
+      create-whiteboard? (whiteboard-handler/create-new-whiteboard-and-redirect! @!input)
+      create-page? (page-handler/create! @!input {:redirect? true}))
     (if create-class?
     (if create-class?
       (state/pub-event! [:class/configure (db/entity [:block/name (util/page-name-sanity-lc class)])])
       (state/pub-event! [:class/configure (db/entity [:block/name (util/page-name-sanity-lc class)])])
-      (close-unless-alt! state))))
+      (state/close-modal!))))
 
 
 (defn- get-filter-user-input
 (defn- get-filter-user-input
   [input]
   [input]
@@ -441,7 +467,12 @@
      [:div {:class         "border-b border-gray-06 pb-1 last:border-b-0"
      [:div {:class         "border-b border-gray-06 pb-1 last:border-b-0"
             :on-mouse-move #(reset! *mouse-active? true)}
             :on-mouse-move #(reset! *mouse-active? true)}
       [:div {:class "text-xs py-1.5 px-3 flex justify-between items-center gap-2 text-gray-11 bg-gray-02"}
       [:div {:class "text-xs py-1.5 px-3 flex justify-between items-center gap-2 text-gray-11 bg-gray-02"}
-       [:div {:class "font-bold text-gray-11 pl-0.5"} title]
+       [:div {:class "font-bold text-gray-11 pl-0.5 cursor-pointer select-none"
+              :on-click (fn [_e]
+                          ;; change :less to :more or :more to :less
+                          (swap! (::results state) update-in [group :show] {:more :less
+                                                                            :less :more}))}
+        title]
        (when (not= group :create)
        (when (not= group :create)
          [:div {:class "pl-1.5 text-gray-12 rounded-full"
          [:div {:class "pl-1.5 text-gray-12 rounded-full"
                 :style {:font-size "0.7rem"}}
                 :style {:font-size "0.7rem"}}
@@ -474,7 +505,6 @@
                                       :rounded false
                                       :rounded false
                                       :hoverable @*mouse-active?
                                       :hoverable @*mouse-active?
                                       :highlighted highlighted?
                                       :highlighted highlighted?
-                                      :display-shortcut-on-highlight? true
                                       ;; for some reason, the highlight effect does not always trigger on a
                                       ;; for some reason, the highlight effect does not always trigger on a
                                       ;; boolean value change so manually pass in the dep
                                       ;; boolean value change so manually pass in the dep
                                       :on-highlight-dep highlighted-item
                                       :on-highlight-dep highlighted-item
@@ -529,12 +559,12 @@
 (defn- keydown-handler
 (defn- keydown-handler
   [state e]
   [state e]
   (let [shift? (.-shiftKey e)
   (let [shift? (.-shiftKey e)
-        meta? (.-metaKey e)
-        alt? (.-altKey e)
+        meta? (util/meta-key? e)
         ctrl? (.-ctrlKey e)
         ctrl? (.-ctrlKey e)
         keyname (.-key e)
         keyname (.-key e)
         enter? (= keyname "Enter")
         enter? (= keyname "Enter")
         esc? (= keyname "Escape")
         esc? (= keyname "Escape")
+        composing? (util/event-is-composing? e)
         highlighted-group @(::highlighted-group state)
         highlighted-group @(::highlighted-group state)
         show-less (fn [] (swap! (::results state) assoc-in [highlighted-group :show] :less))
         show-less (fn [] (swap! (::results state) assoc-in [highlighted-group :show] :less))
         show-more (fn [] (swap! (::results state) assoc-in [highlighted-group :show] :more))
         show-more (fn [] (swap! (::results state) assoc-in [highlighted-group :show] :more))
@@ -543,7 +573,6 @@
         as-keyup? (or (= keyname "ArrowUp") (and ctrl? (= keyname "p")))]
         as-keyup? (or (= keyname "ArrowUp") (and ctrl? (= keyname "p")))]
     (reset! (::shift? state) shift?)
     (reset! (::shift? state) shift?)
     (reset! (::meta? state) meta?)
     (reset! (::meta? state) meta?)
-    (reset! (::alt? state) alt?)
     (when (or as-keydown? as-keyup?)
     (when (or as-keydown? as-keyup?)
       (.preventDefault e))
       (.preventDefault e))
 
 
@@ -559,9 +588,9 @@
       as-keyup? (if meta?
       as-keyup? (if meta?
                   (show-less)
                   (show-less)
                   (move-highlight state -1))
                   (move-highlight state -1))
-      enter? (do
-               (handle-action :default state e)
-               (util/stop-propagation e))
+      (and enter? (not composing?)) (do
+                                      (handle-action :default state e)
+                                      (util/stop-propagation e))
       esc? (let [filter @(::filter state)]
       esc? (let [filter @(::filter state)]
              (when (or (and filter @(::input-changed? state))
              (when (or (and filter @(::input-changed? state))
                        (not (string/blank? input)))
                        (not (string/blank? input)))
@@ -573,13 +602,11 @@
                                     (util/stop-propagation e))
                                     (util/stop-propagation e))
       :else nil)))
       :else nil)))
 
 
-(defn keyup-handler
+(defn- keyup-handler
   [state e]
   [state e]
   (let [shift? (.-shiftKey e)
   (let [shift? (.-shiftKey e)
-        meta? (.-metaKey e)
-        alt? (.-altKey e)]
+        meta? (util/meta-key? e)]
     (reset! (::shift? state) shift?)
     (reset! (::shift? state) shift?)
-    (reset! (::alt? state) alt?)
     (reset! (::meta? state) meta?)))
     (reset! (::meta? state) meta?)))
 
 
 (defn- input-placeholder
 (defn- input-placeholder
@@ -640,8 +667,8 @@
      (shui/shortcut "/" context)
      (shui/shortcut "/" context)
      [:div "to filter search results"]]
      [:div "to filter search results"]]
     [:div.flex.flex-row.gap-1.items-center.opacity-50.hover:opacity-100
     [:div.flex.flex-row.gap-1.items-center.opacity-50.hover:opacity-100
-     (shui/shortcut "mod enter" context)
-     [:div "to open search in the sidebar"]]]))
+     (shui/shortcut ["mod" "enter"] context)
+     [:div "to open search in the sidebar"]]])  )
 
 
 (rum/defcs tip <
 (rum/defcs tip <
   {:init (fn [state]
   {:init (fn [state]
@@ -751,12 +778,10 @@
        (mixins/on-key-down state {}
        (mixins/on-key-down state {}
                            {:target ref
                            {:target ref
                             :all-handler (fn [e _key] (keydown-handler state e))})
                             :all-handler (fn [e _key] (keydown-handler state e))})
-       (mixins/on-key-up state {}
-                         {:target ref
-                          :all-handler (fn [e _key] (keyup-handler state e))}))))
+       (mixins/on-key-up state {} (fn [e _key]
+                                    (keyup-handler state e))))))
   (rum/local false ::shift?)
   (rum/local false ::shift?)
   (rum/local false ::meta?)
   (rum/local false ::meta?)
-  (rum/local false ::alt?)
   (rum/local nil ::highlighted-group)
   (rum/local nil ::highlighted-group)
   (rum/local nil ::highlighted-item)
   (rum/local nil ::highlighted-item)
   (rum/local default-results ::results)
   (rum/local default-results ::results)

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

@@ -80,7 +80,7 @@
 }
 }
 
 
 .dark .left-sidebar-inner {
 .dark .left-sidebar-inner {
-  background-color: or(--ls-left-sidebar-background-color, --lx-gray-01, --ls-primary-background);
+  background-color: or(--ls-left-sidebar-background-color, --lx-gray-01, --ls-primary-background-color);
 }
 }
 
 
 .left-sidebar-inner {
 .left-sidebar-inner {
@@ -683,7 +683,7 @@
   }
   }
 
 
   .references {
   .references {
-    margin-left: 12px;
+    @apply mx-[28px];
   }
   }
 
 
   .sidebar-drop-indicator {
   .sidebar-drop-indicator {
@@ -757,6 +757,10 @@
       }
       }
     }
     }
   }
   }
+
+  .page-hierarchy {
+    @apply pl-[28px];
+  }
 }
 }
 
 
 .cp__sidebar-main-content[data-is-full-width='true'] {
 .cp__sidebar-main-content[data-is-full-width='true'] {

+ 189 - 0
src/main/frontend/components/db_based/page.cljs

@@ -0,0 +1,189 @@
+(ns frontend.components.db-based.page
+  "Page components only for DB graphs"
+  (:require [frontend.components.block :as component-block]
+            [frontend.components.editor :as editor]
+            [frontend.components.class :as class-component]
+            [frontend.components.property :as property-component]
+            [frontend.components.property.value :as pv]
+            [frontend.config :as config]
+            [frontend.db :as db]
+            [frontend.handler.db-based.property :as db-property-handler]
+            [frontend.ui :as ui]
+            [frontend.util :as util]
+            [rum.core :as rum]))
+
+(rum/defc page-properties < rum/reactive
+  [page {:keys [configure? show-page-properties?]}]
+  (let [types (:block/type page)
+        class? (contains? types "class")
+        edit-input-id-prefix (str "edit-block-" (:block/uuid page))
+        configure-opts {:selected? false
+                        :page-configure? true}
+        has-viewable-properties? (db-property-handler/block-has-viewable-properties? page)
+        has-class-properties? (seq (:properties (:block/schema page)))]
+    (when (or configure? has-viewable-properties? has-class-properties?)
+      [:div.ls-page-properties.mb-4 {:style {:padding 2}}
+       (if configure?
+         (cond
+           (and class? (not show-page-properties?) (not has-class-properties?))
+           [:div
+            [:div.mb-1 "Class properties:"]
+            (component-block/db-properties-cp {:editor-box editor/box}
+                                              page
+                                              (str edit-input-id-prefix "-schema")
+                                              (assoc configure-opts :class-schema? true))]
+
+           (not (db-property-handler/block-has-viewable-properties? page))
+           [:div
+            [:div.mb-1 "Page properties:"]
+            (component-block/db-properties-cp {:editor-box editor/box}
+                                              page
+                                              (str edit-input-id-prefix "-page")
+                                              (assoc configure-opts :class-schema? false))])
+         (if config/publishing?
+           [:div.flex.flex-col.gap-4
+            (when has-viewable-properties?
+              [:div
+               (when has-class-properties?
+                 [:div.mb-1.opacity-70.font-medium.text-sm "Page properties:"])
+               (component-block/db-properties-cp {:editor-box editor/box}
+                                                 page
+                                                 (str edit-input-id-prefix "-page")
+                                                 {:selected? false
+                                                  :page-configure? false
+                                                  :class-schema? false})])
+            (when has-class-properties?
+              [:div
+               (when has-viewable-properties?
+                 [:div.mb-1.opacity-70.font-medium.text-sm "Class properties:"])
+               (component-block/db-properties-cp {:editor-box editor/box}
+                                                 page
+                                                 (str edit-input-id-prefix "-schema")
+                                                 (assoc configure-opts :class-schema? true))])]
+
+           [:div.flex.flex-col.gap-4
+            (when has-class-properties?
+              [:div
+               (when has-viewable-properties?
+                 [:div.mb-1.opacity-70.font-medium.text-sm "Class properties:"])
+               (component-block/db-properties-cp {:editor-box editor/box}
+                                                 page
+                                                 (str edit-input-id-prefix "-schema")
+                                                 (assoc configure-opts :class-schema? true))])
+
+            (when has-viewable-properties?
+              [:div
+               (when has-class-properties?
+                 [:div.mb-1.opacity-70.font-medium.text-sm "Page properties:"])
+               (component-block/db-properties-cp {:editor-box editor/box}
+                                                 page
+                                                 (str edit-input-id-prefix "-page")
+                                                 {:selected? false
+                                                  :page-configure? false
+                                                  :class-schema? false})])]))])))
+
+(rum/defcs page-configure-inner <
+  (rum/local false ::show-page-properties?)
+  {:will-unmount (fn [state]
+                   (let [on-unmount (nth (:rum/args state) 1)]
+                     (on-unmount)))}
+  [state page _on-unmount opts]
+  (let [*show-page-properties? (::show-page-properties? state)
+        types (:block/type page)
+        class? (contains? types "class")
+        property? (contains? types "property")
+        class-or-property? (or class? property?)
+        page-opts {:configure? true
+                   :show-page-properties? @*show-page-properties?}]
+    [:div.flex.flex-col.justify-between.p-4 {:style {:min-width 700
+                                                     :min-height 400}}
+     [:div.flex.flex-col.gap-2
+      (cond
+        (not class-or-property?)
+        (when (and (not class?)
+                   (not property?)
+                   (not (db-property-handler/block-has-viewable-properties? page)))
+          (page-properties page page-opts))
+
+        @*show-page-properties?
+        (page-properties page page-opts)
+
+        :else
+        [:<>
+         (when class?
+           (class-component/configure page))
+         (when class?
+           (page-properties page page-opts))
+         (when (and property? (not class?))
+           [:h2.title "Configure property"])
+         (when property?
+           (property-component/property-config page page (assoc opts
+                                                                :inline-text component-block/inline-text)))])]
+
+     (when (and class-or-property?
+                (not (db-property-handler/block-has-viewable-properties? page))
+                (not config/publishing?)
+                (empty? (:properties (:block/schema page))))
+       [:a.fade-link.flex.flex-row.items-center.gap-1.text-sm
+        {:on-click #(swap! *show-page-properties? not)}
+        (ui/icon (if @*show-page-properties?
+                   "arrow-narrow-left"
+                   "arrow-narrow-right"))
+        (if @*show-page-properties?
+          "Back"
+          "Edit page properties")])]))
+
+(rum/defc page-configure
+  [page *hover? *configuring?]
+  (when (or @*hover? (and config/publishing? (some #{"class" "property"} (:block/type page))))
+    (let [toggle-fn' (fn [toggle-fn]
+                       (fn []
+                         (toggle-fn)
+                         (reset! *configuring? true)))]
+      (ui/dropdown
+       (fn [{:keys [toggle-fn]}]
+         [:a.fade-link.flex.flex-row.items-center
+          {:on-click (toggle-fn' toggle-fn)}
+          [:div.mr-1.text-sm (if-let [block-type (and config/publishing?
+                                                      (some #{"class" "property"} (:block/type page)))]
+                               (str "More info on this " block-type)
+                               "Configure")]])
+       (fn [{:keys [toggle-fn]}]
+         (page-configure-inner
+          page
+          (fn []
+            (reset! *configuring? false)
+            (reset! *hover? false))
+          {:toggle-fn toggle-fn}))
+
+       {:modal-class (util/hiccup->class
+                      "origin-top-right.absolute.left-0.mt-2.rounded-md.shadow-lg")}))))
+
+(rum/defc page-properties-react < rum/reactive
+  [page* page-opts]
+  (let [page (db/sub-block (:db/id page*))]
+    (when (or (db-property-handler/block-has-viewable-properties? page)
+              ;; Allow class and property pages to add new property
+              (some #{"class" "property"} (:block/type page)))
+      (page-properties page page-opts))))
+
+(rum/defc page-tags <
+  [page tags-property *hover? *configuring?]
+  (let [toggle-fn' (fn [toggle-fn]
+                     (fn []
+                       (toggle-fn)
+                       (swap! *configuring? not)))]
+    (ui/dropdown
+     (fn [{:keys [toggle-fn]}]
+       [:a.fade-link.flex.flex-row.items-center
+        {:on-click (toggle-fn' toggle-fn)}
+        [:div.ml-1.text-sm "Set tags"]])
+     (fn [{:keys [toggle-fn]}]
+       (pv/property-value page tags-property nil {:on-chosen (toggle-fn' toggle-fn)
+                                                  :dropdown? false}))
+     {:modal-class (util/hiccup->class
+                    "origin-top-right.absolute.left-0.mt-2.rounded-md.shadow-lg")
+      :on-toggle (fn [value]
+                   (when (false? value)
+                     (reset! *configuring? false)
+                     (reset! *hover? false)))})))

+ 19 - 37
src/main/frontend/components/editor.cljs

@@ -2,7 +2,6 @@
   (:require [clojure.string :as string]
   (:require [clojure.string :as string]
             [frontend.commands :as commands
             [frontend.commands :as commands
              :refer [*first-command-group *matched-block-commands *matched-commands]]
              :refer [*first-command-group *matched-block-commands *matched-commands]]
-            [frontend.components.block :as block]
             [frontend.components.datetime :as datetime-comp]
             [frontend.components.datetime :as datetime-comp]
             [frontend.components.svg :as svg]
             [frontend.components.svg :as svg]
             [frontend.components.search :as search]
             [frontend.components.search :as search]
@@ -101,9 +100,6 @@
                                                       :command :block-commands}))
                                                       :command :block-commands}))
         :class     "black"}))))
         :class     "black"}))))
 
 
-(defn- in-sidebar? [el]
-  (not (.contains (.getElementById js/document "left-container") el)))
-
 (defn- page-on-chosen-handler
 (defn- page-on-chosen-handler
   [embed? input id q pos format]
   [embed? input id q pos format]
   (if embed?
   (if embed?
@@ -143,7 +139,6 @@
         (when input
         (when input
           (let [current-pos (cursor/pos input)
           (let [current-pos (cursor/pos input)
                 edit-content (state/sub-edit-content)
                 edit-content (state/sub-edit-content)
-                sidebar? (in-sidebar? input)
                 q (or
                 q (or
                    (editor-handler/get-selected-text)
                    (editor-handler/get-selected-text)
                    (when (= action :page-search-hashtag)
                    (when (= action :page-search-hashtag)
@@ -151,11 +146,9 @@
                    (when (> (count edit-content) current-pos)
                    (when (> (count edit-content) current-pos)
                      (gp-util/safe-subs edit-content pos current-pos))
                      (gp-util/safe-subs edit-content pos current-pos))
                    "")
                    "")
-                matched-pages (if db-tag?
-                                (editor-handler/get-matched-classes q)
-                                ;; FIXME: display refed pages recentedly or frequencyly used
-                                (when-not (string/blank? q)
-                                  (editor-handler/get-matched-pages q)))
+                ;; FIXME: display refed pages recentedly or frequencyly used
+                matched-pages (when-not (string/blank? q)
+                                (editor-handler/get-matched-pages q))
                 matched-pages (cond
                 matched-pages (cond
                                 (contains? (set (map util/page-name-sanity-lc matched-pages))
                                 (contains? (set (map util/page-name-sanity-lc matched-pages))
                                            (util/page-name-sanity-lc (string/trim q)))  ;; if there's a page name fully matched
                                            (util/page-name-sanity-lc (string/trim q)))  ;; if there's a page name fully matched
@@ -163,16 +156,18 @@
                                            [(count m) m])
                                            [(count m) m])
                                          matched-pages)
                                          matched-pages)
 
 
-                                (and (string/blank? q) (not db-tag?))
+                                (string/blank? q)
                                 nil
                                 nil
 
 
-                                (and (string/blank? q) db-tag?)
-                                matched-pages
-
                                 (empty? matched-pages)
                                 (empty? matched-pages)
-                                (cons q matched-pages)
-
-                               ;; reorder, shortest and starts-with first.
+                                (when-not (db/page-exists? q)
+                                  (if db-tag?
+                                    (concat [(str (t :new-page) " " q)
+                                             (str (t :new-class) " " q)]
+                                            matched-pages)
+                                    (cons (str (t :new-page) " " q) matched-pages)))
+
+                                ;; reorder, shortest and starts-with first.
                                 :else
                                 :else
                                 (let [matched-pages (remove nil? matched-pages)
                                 (let [matched-pages (remove nil? matched-pages)
                                       matched-pages (sort-by
                                       matched-pages (sort-by
@@ -196,27 +191,14 @@
              (ui/auto-complete
              (ui/auto-complete
               matched-pages
               matched-pages
               {:on-chosen   (page-on-chosen-handler embed? input id q pos format)
               {:on-chosen   (page-on-chosen-handler embed? input id q pos format)
-               :on-enter    #(page-handler/page-not-exists-handler input id q current-pos)
-               :item-render (fn [page-name chosen?]
-                              [:div.preview-trigger-wrapper
-                               (block/page-preview-trigger
-                                {:children
-                                 [:div.flex
-                                  (when (db-model/whiteboard-page? page-name) [:span.mr-1 (ui/icon "whiteboard" {:extension? true})])
-                                  [:div.flex.space-x-1
-                                   [:div (when-not (db/page-exists? page-name)
-                                           (if db-tag?
-                                             (t :new-class)
-                                             (t :new-page)))]
-                                   (search-handler/highlight-exact-query page-name q)]]
-                                 :open?           chosen?
-                                 :manual?         true
-                                 :fixed-position? true
-                                 :tippy-distance  24
-                                 :tippy-position  (if sidebar? "left" "right")}
-                                page-name)])
+               :on-enter    (fn []
+                              (page-handler/page-not-exists-handler input id q current-pos))
+               :item-render (fn [page-name _chosen?]
+                              [:div.flex
+                               (when (db-model/whiteboard-page? page-name) [:span.mr-1 (ui/icon "whiteboard" {:extension? true})])
+                               (search-handler/highlight-exact-query page-name q)])
                :empty-placeholder [:div.text-gray-500.text-sm.px-4.py-2 (if db-tag?
                :empty-placeholder [:div.text-gray-500.text-sm.px-4.py-2 (if db-tag?
-                                                                          "Search for a class"
+                                                                          "Search for a page or a class"
                                                                           "Search for a page")]
                                                                           "Search for a page")]
                :class       "black"})]))))))
                :class       "black"})]))))))
 
 

+ 12 - 190
src/main/frontend/components/page.cljs

@@ -9,9 +9,9 @@
             [frontend.components.query :as query]
             [frontend.components.query :as query]
             [frontend.components.reference :as reference]
             [frontend.components.reference :as reference]
             [frontend.components.scheduled-deadlines :as scheduled]
             [frontend.components.scheduled-deadlines :as scheduled]
-            [frontend.components.property :as property]
+            [frontend.components.property :as property-component]
             [frontend.components.property.value :as pv]
             [frontend.components.property.value :as pv]
-            [frontend.components.class :as class-component]
+            [frontend.components.db-based.page :as db-page]
             [frontend.handler.property.util :as pu]
             [frontend.handler.property.util :as pu]
             [frontend.handler.db-based.property :as db-property-handler]
             [frontend.handler.db-based.property :as db-property-handler]
             [frontend.handler.db-based.property.util :as db-pu]
             [frontend.handler.db-based.property.util :as db-pu]
@@ -316,106 +316,6 @@
       :on-focus (fn []
       :on-focus (fn []
                   (when untitled? (reset! *title-value "")))}]))
                   (when untitled? (reset! *title-value "")))}]))
 
 
-(rum/defc page-tags <
-  [page tags-property *hover? *configuring?]
-  (let [toggle-fn' (fn [toggle-fn]
-                     (fn []
-                       (toggle-fn)
-                       (swap! *configuring? not)))]
-    (ui/dropdown
-     (fn [{:keys [toggle-fn]}]
-       [:a.fade-link.flex.flex-row.items-center
-        {:on-click (toggle-fn' toggle-fn)}
-        [:div.ml-1.text-sm "Set tags"]])
-     (fn [{:keys [toggle-fn]}]
-       (pv/property-value page tags-property nil {:on-chosen (toggle-fn' toggle-fn)
-                                                  :dropdown? false}))
-     {:modal-class (util/hiccup->class
-                    "origin-top-right.absolute.left-0.mt-2.rounded-md.shadow-lg")
-      :on-toggle (fn [value]
-                   (when (false? value)
-                     (reset! *configuring? false)
-                     (reset! *hover? false)))})))
-
-(declare page-properties)
-
-(rum/defcs page-configure-inner <
-  (rum/local false ::show-page-properties?)
-  {:will-unmount (fn [state]
-                   (let [on-unmount (nth (:rum/args state) 1)]
-                     (on-unmount)))}
-  [state page _on-unmount opts]
-  (let [*show-page-properties? (::show-page-properties? state)
-        types (:block/type page)
-        class? (contains? types "class")
-        property? (contains? types "property")
-        class-or-property? (or class? property?)
-        page-opts {:configure? true
-                   :show-page-properties? @*show-page-properties?}]
-    [:div.flex.flex-col.justify-between.p-4 {:style {:min-width 700
-                                                     :min-height 400}}
-     [:div.flex.flex-col.gap-2
-      (cond
-        (not class-or-property?)
-        (when (and (not class?)
-                   (not property?)
-                   (not (db-property-handler/block-has-viewable-properties? page)))
-          (page-properties page page-opts))
-
-        @*show-page-properties?
-        (page-properties page page-opts)
-
-        :else
-        [:<>
-         (when class?
-           (class-component/configure page))
-         (when class?
-           (page-properties page page-opts))
-         (when (and property? (not class?))
-           [:h2.title "Configure property"])
-         (when property?
-           (property/property-config page page (assoc opts
-                                                      :inline-text component-block/inline-text)))])]
-
-     (when (and class-or-property?
-                (not (db-property-handler/block-has-viewable-properties? page))
-                (not config/publishing?)
-                (empty? (:properties (:block/schema page))))
-       [:a.fade-link.flex.flex-row.items-center.gap-1.text-sm
-        {:on-click #(swap! *show-page-properties? not)}
-        (ui/icon (if @*show-page-properties?
-                   "arrow-narrow-left"
-                   "arrow-narrow-right"))
-        (if @*show-page-properties?
-          "Back"
-          "Edit page properties")])]))
-
-(rum/defc page-configure
-  [page *hover? *configuring?]
-  (when (or @*hover? (and config/publishing? (some #{"class" "property"} (:block/type page))))
-    (let [toggle-fn' (fn [toggle-fn]
-                       (fn []
-                         (toggle-fn)
-                         (reset! *configuring? true)))]
-      (ui/dropdown
-       (fn [{:keys [toggle-fn]}]
-         [:a.fade-link.flex.flex-row.items-center
-          {:on-click (toggle-fn' toggle-fn)}
-          [:div.mr-1.text-sm (if-let [block-type (and config/publishing?
-                                                      (some #{"class" "property"} (:block/type page)))]
-                               (str "More info on this " block-type)
-                               "Configure")]])
-       (fn [{:keys [toggle-fn]}]
-         (page-configure-inner
-          page
-          (fn []
-            (reset! *configuring? false)
-            (reset! *hover? false))
-          {:toggle-fn toggle-fn}))
-
-       {:modal-class (util/hiccup->class
-                      "origin-top-right.absolute.left-0.mt-2.rounded-md.shadow-lg")}))))
-
 (rum/defcs ^:large-vars/cleanup-todo page-title < rum/reactive
 (rum/defcs ^:large-vars/cleanup-todo page-title < rum/reactive
   (rum/local false ::edit?)
   (rum/local false ::edit?)
   (rum/local "" ::input-value)
   (rum/local "" ::input-value)
@@ -456,12 +356,12 @@
        (when icon
        (when icon
          [:div.page-icon {:on-mouse-down util/stop-propagation}
          [:div.page-icon {:on-mouse-down util/stop-propagation}
           (if (and (map? icon) db-based?)
           (if (and (map? icon) db-based?)
-            (property/icon icon {:on-chosen (fn [_e icon]
-                                              (let [icon-property-id (db-pu/get-built-in-property-uuid :icon)]
-                                                (db-property-handler/update-property!
-                                                 repo
-                                                 (:block/uuid page)
-                                                 {:properties {icon-property-id icon}})))})
+            (property-component/icon icon {:on-chosen (fn [_e icon]
+                                                        (let [icon-property-id (db-pu/get-built-in-property-uuid :icon)]
+                                                          (db-property-handler/update-property!
+                                                           repo
+                                                           (:block/uuid page)
+                                                           {:properties {icon-property-id icon}})))})
             icon)])
             icon)])
 
 
        [:div.flex.flex-1.flex-row.flex-wrap.items-center.gap-4
        [:div.flex.flex-1.flex-row.flex-wrap.items-center.gap-4
@@ -519,11 +419,11 @@
          [:div.absolute.bottom-2.left-0
          [:div.absolute.bottom-2.left-0
           [:div.page-add-tags.flex.flex-row.items-center.flex-wrap.gap-2.ml-2
           [:div.page-add-tags.flex.flex-row.items-center.flex-wrap.gap-2.ml-2
            (when (and (empty? (:block/tags page)) @*hover? (not config/publishing?))
            (when (and (empty? (:block/tags page)) @*hover? (not config/publishing?))
-             (page-tags page tags-property *hover? *configuring?))
+             (db-page/page-tags page tags-property *hover? *configuring?))
 
 
            (when (or (some #(contains? #{"class" "property"} %) (:block/type page))
            (when (or (some #(contains? #{"class" "property"} %) (:block/type page))
                      (not (db-property-handler/block-has-viewable-properties? page)))
                      (not (db-property-handler/block-has-viewable-properties? page)))
-             (page-configure page *hover? *configuring?))]])])))
+             (db-page/page-configure page *hover? *configuring?))]])])))
 
 
 (defn- page-mouse-over
 (defn- page-mouse-over
   [e *control-show? *all-collapsed?]
   [e *control-show? *all-collapsed?]
@@ -554,84 +454,6 @@
                          "control-show cursor-pointer" "control-hide")}
                          "control-show cursor-pointer" "control-hide")}
     (ui/rotating-arrow @*all-collapsed?)]])
     (ui/rotating-arrow @*all-collapsed?)]])
 
 
-(rum/defc page-properties < rum/reactive
-  [page {:keys [configure? show-page-properties?]}]
-  (let [types (:block/type page)
-        class? (contains? types "class")
-        edit-input-id-prefix (str "edit-block-" (:block/uuid page))
-        configure-opts {:selected? false
-                        :page-configure? true}
-        has-viewable-properties? (db-property-handler/block-has-viewable-properties? page)
-        has-class-properties? (seq (:properties (:block/schema page)))]
-    (when (or configure? has-viewable-properties? has-class-properties?)
-      [:div.ls-page-properties.mb-4 {:style {:padding 2}}
-       (if configure?
-         (cond
-           (and class? (not show-page-properties?) (not has-class-properties?))
-           [:div
-            [:div.mb-1 "Class properties:"]
-            (component-block/db-properties-cp {:editor-box editor/box}
-                                              page
-                                              (str edit-input-id-prefix "-schema")
-                                              (assoc configure-opts :class-schema? true))]
-
-           (not (db-property-handler/block-has-viewable-properties? page))
-           [:div
-            [:div.mb-1 "Page properties:"]
-            (component-block/db-properties-cp {:editor-box editor/box}
-                                              page
-                                              (str edit-input-id-prefix "-page")
-                                              (assoc configure-opts :class-schema? false))])
-         (if config/publishing?
-           [:div.flex.flex-col.gap-4
-            (when has-viewable-properties?
-              [:div
-               (when has-class-properties?
-                 [:div.mb-1.opacity-70.font-medium.text-sm "Page properties:"])
-               (component-block/db-properties-cp {:editor-box editor/box}
-                                                 page
-                                                 (str edit-input-id-prefix "-page")
-                                                 {:selected? false
-                                                  :page-configure? false
-                                                  :class-schema? false})])
-            (when has-class-properties?
-              [:div
-               (when has-viewable-properties?
-                 [:div.mb-1.opacity-70.font-medium.text-sm "Class properties:"])
-               (component-block/db-properties-cp {:editor-box editor/box}
-                                                 page
-                                                 (str edit-input-id-prefix "-schema")
-                                                 (assoc configure-opts :class-schema? true))])]
-
-           [:div.flex.flex-col.gap-4
-            (when has-class-properties?
-              [:div
-               (when has-viewable-properties?
-                 [:div.mb-1.opacity-70.font-medium.text-sm "Class properties:"])
-               (component-block/db-properties-cp {:editor-box editor/box}
-                                                 page
-                                                 (str edit-input-id-prefix "-schema")
-                                                 (assoc configure-opts :class-schema? true))])
-
-            (when has-viewable-properties?
-              [:div
-               (when has-class-properties?
-                 [:div.mb-1.opacity-70.font-medium.text-sm "Page properties:"])
-               (component-block/db-properties-cp {:editor-box editor/box}
-                                                 page
-                                                 (str edit-input-id-prefix "-page")
-                                                 {:selected? false
-                                                  :page-configure? false
-                                                  :class-schema? false})])]))])))
-
-(rum/defc page-properties-react < rum/reactive
-  [page* page-opts]
-  (let [page (db/sub-block (:db/id page*))]
-    (when (or (db-property-handler/block-has-viewable-properties? page)
-              ;; Allow class and property pages to add new property
-              (some #{"class" "property"} (:block/type page)))
-      (page-properties page page-opts))))
-
 (defn- get-path-page-name
 (defn- get-path-page-name
   [state page-name]
   [state page-name]
   (or page-name
   (or page-name
@@ -724,8 +546,8 @@
                [:div.mb-4
                [:div.mb-4
                 (component-block/breadcrumb config repo block-id {:level-limit 3})]))
                 (component-block/breadcrumb config repo block-id {:level-limit 3})]))
 
 
-           (when (and db-based? (not block?) (not preview?) (not sidebar?))
-             (page-properties-react page {:configure? false}))
+           (when (and db-based? (not block?) (not preview?))
+             (db-page/page-properties-react page {:configure? false}))
 
 
            ;; blocks
            ;; blocks
            (let [_ (and block? page (reset! *current-block-page (:block/name (:block/page page))))
            (let [_ (and block? page (reset! *current-block-page (:block/name (:block/page page))))

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

@@ -138,7 +138,7 @@
             (let [repo-dir (config/get-repo-dir repo)
             (let [repo-dir (config/get-repo-dir repo)
                   file-fpath (path/path-join repo-dir file-rpath)]
                   file-fpath (path/path-join repo-dir file-rpath)]
               [{:title   (t :page/open-in-finder)
               [{:title   (t :page/open-in-finder)
-                :options {:on-click #(js/window.apis.showItemInFolder file-fpath)}}
+                :options {:on-click #(ipc/ipc "openFileInFolder" file-fpath)}}
                {:title   (t :page/open-with-default-app)
                {:title   (t :page/open-with-default-app)
                 :options {:on-click #(js/window.apis.openPath file-fpath)}}]))
                 :options {:on-click #(js/window.apis.openPath file-fpath)}}]))
 
 

+ 51 - 32
src/main/frontend/components/property.cljs

@@ -109,7 +109,10 @@
     "Text"
     "Text"
     ((comp string/capitalize name) property-type)))
     ((comp string/capitalize name) property-type)))
 
 
-(rum/defcs ^:large-vars/cleanup-todo property-config <
+(rum/defcs ^:large-vars/cleanup-todo property-config
+  "All changes to a property must update the db and the *property-schema. Failure to do
+   so can result in data loss"
+  <
   shortcut/disable-all-shortcuts
   shortcut/disable-all-shortcuts
   rum/reactive
   rum/reactive
   db-mixins/query
   db-mixins/query
@@ -137,7 +140,7 @@
         class? (contains? (:block/type block) "class")
         class? (contains? (:block/type block) "class")
         property-type (get-in property [:block/schema :type])
         property-type (get-in property [:block/schema :type])
         save-property-fn (fn [] (components-pu/update-property! property @*property-name @*property-schema))
         save-property-fn (fn [] (components-pu/update-property! property @*property-name @*property-schema))
-        enable-closed-values? (contains? db-property-type/closed-values-schema-types (or property-type :default))]
+        enable-closed-values? (contains? db-property-type/closed-value-property-types (or property-type :default))]
     [:div.property-configure.flex.flex-1.flex-col
     [:div.property-configure.flex.flex-1.flex-col
      {:on-mouse-down #(state/set-state! :editor/mouse-down-from-property-configure? true)
      {:on-mouse-down #(state/set-state! :editor/mouse-down-from-property-configure? true)
       :on-mouse-up #(state/set-state! :editor/mouse-down-from-property-configure? nil)}
       :on-mouse-up #(state/set-state! :editor/mouse-down-from-property-configure? nil)}
@@ -168,9 +171,9 @@
 
 
       [:div.grid.grid-cols-4.gap-1.items-center.leading-8
       [:div.grid.grid-cols-4.gap-1.items-center.leading-8
        [:label.col-span-1 "Schema type:"]
        [:label.col-span-1 "Schema type:"]
-       (let [schema-types (->> (concat db-property-type/user-builtin-schema-types
+       (let [schema-types (->> (concat db-property-type/user-built-in-property-types
                                        (when built-in-property?
                                        (when built-in-property?
-                                         db-property-type/internal-builtin-schema-types))
+                                         db-property-type/internal-built-in-property-types))
                                (map (fn [type]
                                (map (fn [type]
                                       {:label (property-type-label type)
                                       {:label (property-type-label type)
                                        :disabled disabled?
                                        :disabled disabled?
@@ -185,14 +188,29 @@
                        :interactive true
                        :interactive true
                        :disabled    false}
                        :disabled    false}
                       (svg/help-circle))]
                       (svg/help-circle))]
-           [:div.col-span-2
+           [:div.flex.items-center.col-span-2
             (ui/select schema-types
             (ui/select schema-types
                        (fn [_e v]
                        (fn [_e v]
-                         (let [type (keyword (string/lower-case v))]
-                           (swap! *property-schema assoc :type type)
-                           (components-pu/update-property! property @*property-name @*property-schema))))]))]
+                         (let [type (keyword (string/lower-case v))
+                               update-schema-fn (apply comp
+                                                       #(assoc % :type type)
+                                                       ;; always delete previous closed values as they
+                                                       ;; are not valid for the new type
+                                                       #(dissoc % :values)
+                                                       (keep
+                                                        (fn [attr]
+                                                          (when-not (db-property-type/property-type-allows-schema-attribute? type attr)
+                                                            #(dissoc % attr)))
+                                                        [:cardinality :classes :position]))]
+                           (swap! *property-schema update-schema-fn)
+                           (components-pu/update-property! property @*property-name @*property-schema))))
+            (ui/tippy {:html        "Changing the property type clears some property configurations."
+                       :class       "tippy-hover ml-2"
+                       :interactive true
+                       :disabled    false}
+                      (svg/info))]))]
 
 
-      (when-not (contains? #{:checkbox :default :template} (:type @*property-schema))
+      (when (db-property-type/property-type-allows-schema-attribute? (:type @*property-schema) :cardinality)
         [:div.grid.grid-cols-4.gap-1.items-center.leading-8
         [:div.grid.grid-cols-4.gap-1.items-center.leading-8
          [:label "Multiple values:"]
          [:label "Multiple values:"]
          (let [many? (boolean (= :many (:cardinality @*property-schema)))]
          (let [many? (boolean (= :many (:cardinality @*property-schema)))]
@@ -203,27 +221,28 @@
                                       (save-property-fn))}))])
                                       (save-property-fn))}))])
 
 
 
 
-      (case (:type @*property-schema)
-        :page
-        (when (empty? (:values @*property-schema))
-          [:div.grid.grid-cols-4.gap-1.items-center.leading-8
-           [:label "Specify classes:"]
-           (class-select *property-schema
-                         (:classes @*property-schema)
-                         (assoc opts
-                                :disabled? disabled?
-                                :save-property-fn save-property-fn))])
-
-        :template
-        [:div.grid.grid-cols-4.gap-1.items-center.leading-8
-         [:label "Specify template:"]
-         (class-select *property-schema (:classes @*property-schema)
-                       (assoc opts
-                              :multiple-choices? false
-                              :disabled? disabled?
-                              :save-property-fn save-property-fn))]
-
-        nil)
+      (when (db-property-type/property-type-allows-schema-attribute? (:type @*property-schema) :classes)
+       (case (:type @*property-schema)
+         :page
+         (when (empty? (:values @*property-schema))
+           [:div.grid.grid-cols-4.gap-1.items-center.leading-8
+            [:label "Specify classes:"]
+            (class-select *property-schema
+                          (:classes @*property-schema)
+                          (assoc opts
+                                 :disabled? disabled?
+                                 :save-property-fn save-property-fn))])
+
+         :template
+         [:div.grid.grid-cols-4.gap-1.items-center.leading-8
+          [:label "Specify template:"]
+          (class-select *property-schema (:classes @*property-schema)
+                        (assoc opts
+                               :multiple-choices? false
+                               :disabled? disabled?
+                               :save-property-fn save-property-fn))]
+
+         nil))
 
 
       (when (and enable-closed-values? (empty? (:classes @*property-schema)))
       (when (and enable-closed-values? (empty? (:classes @*property-schema)))
         [:div.grid.grid-cols-4.gap-1.items-start.leading-8
         [:div.grid.grid-cols-4.gap-1.items-start.leading-8
@@ -676,9 +695,9 @@
                    (empty? class->properties)
                    (empty? class->properties)
                    (not new-property?)
                    (not new-property?)
                    (not (:page-configure? opts)))
                    (not (:page-configure? opts)))
-      [:div.ls-properties-area (cond-> {}
+      [:div.ls-properties-area (cond-> {:class [(if class-schema? "class-properties" "page-properties")]}
                                  (:selected? opts)
                                  (:selected? opts)
-                                 (assoc :class "select-none"))
+                                 (update :class conj "select-none"))
        (properties-section block (if class-schema? properties own-properties) opts)
        (properties-section block (if class-schema? properties own-properties) opts)
 
 
        (when (and (seq full-hidden-properties) (not class-schema?) (not config/publishing?))
        (when (and (seq full-hidden-properties) (not class-schema?) (not config/publishing?))

+ 42 - 30
src/main/frontend/components/property/closed_value.cljs

@@ -10,6 +10,7 @@
             [frontend.components.property.util :as pu-component]
             [frontend.components.property.util :as pu-component]
             [frontend.handler.property :as property-handler]
             [frontend.handler.property :as property-handler]
             [frontend.handler.db-based.property :as db-property-handler]
             [frontend.handler.db-based.property :as db-property-handler]
+            [frontend.config :as config]
             [frontend.components.property.value :as property-value]
             [frontend.components.property.value :as property-value]
             [frontend.db :as db]
             [frontend.db :as db]
             [frontend.state :as state]
             [frontend.state :as state]
@@ -31,12 +32,14 @@
       (if icon
       (if icon
         (icon-component/icon icon)
         (icon-component/icon icon)
         [:span.bullet-container.cursor [:span.bullet]])])
         [:span.bullet-container.cursor [:span.bullet]])])
-   (fn [{:keys [toggle-fn]}]
-     [:div.p-4
-      (icon-component/icon-search
-       {:on-chosen (fn [e icon]
-                     (on-chosen e icon)
-                     (toggle-fn))})])
+   (if config/publishing?
+     (constantly [])
+     (fn [{:keys [toggle-fn]}]
+       [:div.p-4
+        (icon-component/icon-search
+         {:on-chosen (fn [e icon]
+                       (on-chosen e icon)
+                       (toggle-fn))})]))
    {:modal-class (util/hiccup->class
    {:modal-class (util/hiccup->class
                   "origin-top-right.absolute.left-0.rounded-md.shadow-lg")}))
                   "origin-top-right.absolute.left-0.rounded-md.shadow-lg")}))
 
 
@@ -124,7 +127,7 @@
         (ui/icon "X")])]))
         (ui/icon "X")])]))
 
 
 (rum/defc choice-item-content
 (rum/defc choice-item-content
-  [property block dropdown-opts]
+  [property *property-schema block dropdown-opts]
   (let [{:block/keys [uuid]} block]
   (let [{:block/keys [uuid]} block]
     (ui/dropdown
     (ui/dropdown
      (fn [opts]
      (fn [opts]
@@ -133,24 +136,27 @@
         (assoc opts
         (assoc opts
                :delete-choice
                :delete-choice
                (fn []
                (fn []
-                 (db-property-handler/delete-closed-value property block))
+                 (db-property-handler/delete-closed-value! property block)
+                 (swap! *property-schema update :values (fn [vs] (vec (remove #(= uuid %) vs)))))
                :update-icon
                :update-icon
                (fn [icon]
                (fn [icon]
                  (property-handler/set-block-property! (state/get-current-repo) (:block/uuid block) :icon icon)))))
                  (property-handler/set-block-property! (state/get-current-repo) (:block/uuid block) :icon icon)))))
-     (fn [opts]
-       (item-config
-        property
-        block
-        (assoc opts :on-save
-               (fn [value icon description]
-                 (upsert-closed-value! property {:id uuid
-                                                 :value value
-                                                 :description description
-                                                 :icon icon})))))
+     (if config/publishing?
+       (constantly [])
+       (fn [opts]
+         (item-config
+          property
+          block
+          (assoc opts :on-save
+                 (fn [value icon description]
+                   (upsert-closed-value! property {:id uuid
+                                                   :value value
+                                                   :description description
+                                                   :icon icon}))))))
      dropdown-opts)))
      dropdown-opts)))
 
 
 (rum/defc add-existing-values
 (rum/defc add-existing-values
-  [property values {:keys [toggle-fn]}]
+  [property *property-schema values {:keys [toggle-fn]}]
   [:div.flex.flex-col.gap-1.w-64.p-4.overflow-y-auto
   [:div.flex.flex-col.gap-1.w-64.p-4.overflow-y-auto
    {:class "max-h-[50dvh]"}
    {:class "max-h-[50dvh]"}
    [:div "Existing values:"]
    [:div "Existing values:"]
@@ -160,7 +166,8 @@
    (ui/button
    (ui/button
     "Add choices"
     "Add choices"
     {:on-click (fn []
     {:on-click (fn []
-                 (db-property-handler/add-existing-values-to-closed-values! property values)
+                 (let [closed-values (db-property-handler/add-existing-values-to-closed-values! property values)]
+                   (swap! *property-schema assoc :values closed-values))
                  (toggle-fn))})])
                  (toggle-fn))})])
 
 
 (rum/defc choices < rum/reactive
 (rum/defc choices < rum/reactive
@@ -176,7 +183,7 @@
                             (when-let [block (db/sub-block (:db/id (db/entity [:block/uuid id])))]
                             (when-let [block (db/sub-block (:db/id (db/entity [:block/uuid id])))]
                               {:id (str id)
                               {:id (str id)
                                :value id
                                :value id
-                               :content (choice-item-content property block dropdown-opts)}))
+                               :content (choice-item-content property *property-schema block dropdown-opts)}))
                           values))]
                           values))]
        (dnd/items choices
        (dnd/items choices
                   {:on-drag-end (fn [new-values]
                   {:on-drag-end (fn [new-values]
@@ -184,10 +191,12 @@
                                     (swap! *property-schema assoc :values new-values)
                                     (swap! *property-schema assoc :values new-values)
                                     (pu-component/update-property! property @*property-name @*property-schema)))}))
                                     (pu-component/update-property! property @*property-name @*property-schema)))}))
      (ui/dropdown
      (ui/dropdown
-      (fn [{:keys [toggle-fn]}]
-        [:a.fade-link.flex.flex-row.items-center.gap-1.leading-8 {:on-click toggle-fn}
-         (ui/icon "plus" {:size 16})
-         "Add choice"])
+      (if config/publishing?
+        (constantly [])
+        (fn [{:keys [toggle-fn]}]
+          [:a.fade-link.flex.flex-row.items-center.gap-1.leading-8 {:on-click toggle-fn}
+           (ui/icon "plus" {:size 16})
+           "Add choice"]))
       (fn [opts]
       (fn [opts]
         (if (= :page property-type)
         (if (= :page property-type)
           (property-value/select-page property
           (property-value/select-page property
@@ -195,20 +204,23 @@
                                        :dropdown? false
                                        :dropdown? false
                                        :close-modal? false
                                        :close-modal? false
                                        :on-chosen (fn [chosen]
                                        :on-chosen (fn [chosen]
-                                                    (upsert-closed-value! property {:value chosen}))})
+                                                    (let [closed-value (upsert-closed-value! property {:value chosen})]
+                                                      (swap! *property-schema update :values (fnil conj []) closed-value)))})
           (let [values (->> (model/get-block-property-values (:block/uuid property))
           (let [values (->> (model/get-block-property-values (:block/uuid property))
                             (map second)
                             (map second)
                             (remove uuid?)
                             (remove uuid?)
                             (remove string/blank?)
                             (remove string/blank?)
                             distinct)]
                             distinct)]
+
             (if (seq values)
             (if (seq values)
-              (add-existing-values property values opts)
+              (add-existing-values property *property-schema values opts)
               (item-config
               (item-config
                property
                property
                nil
                nil
                (assoc opts :on-save
                (assoc opts :on-save
                       (fn [value icon description]
                       (fn [value icon description]
-                        (upsert-closed-value! property {:value value
-                                                        :description description
-                                                        :icon icon}))))))))
+                        (let [closed-value (upsert-closed-value! property {:value value
+                                                                           :description description
+                                                                           :icon icon})]
+                          (swap! *property-schema update :values (fnil conj []) closed-value)))))))))
       dropdown-opts)]))
       dropdown-opts)]))

+ 9 - 9
src/main/frontend/components/property/value.cljs

@@ -207,6 +207,7 @@
                  (model/get-all-page-original-names repo))
                  (model/get-all-page-original-names repo))
                distinct)
                distinct)
         options (map (fn [p] {:value p}) pages)
         options (map (fn [p] {:value p}) pages)
+        string-classes (remove #(= :logseq.class %) classes)
         opts' (cond->
         opts' (cond->
                (merge
                (merge
                 opts
                 opts
@@ -229,7 +230,7 @@
                  :transform-fn (fn [results input]
                  :transform-fn (fn [results input]
                                  (if-let [[_ new-page class-input] (and (empty? results) (re-find #"(.*)#(.*)$" input))]
                                  (if-let [[_ new-page class-input] (and (empty? results) (re-find #"(.*)#(.*)$" input))]
                                    (let [repo (state/get-current-repo)
                                    (let [repo (state/get-current-repo)
-                                         class-names (map #(:block/original-name (db/entity repo [:block/uuid %])) classes)
+                                         class-names (map #(:block/original-name (db/entity repo [:block/uuid %])) string-classes)
                                          descendent-classes (->> class-names
                                          descendent-classes (->> class-names
                                                                  (mapcat #(db/get-namespace-pages repo %))
                                                                  (mapcat #(db/get-namespace-pages repo %))
                                                                  (map :block/original-name))]
                                                                  (map :block/original-name))]
@@ -241,7 +242,7 @@
                 multiple-choices?
                 multiple-choices?
                 (assoc :on-apply (fn [choices]
                 (assoc :on-apply (fn [choices]
                                    (let [pages (->> choices
                                    (let [pages (->> choices
-                                                    (map #(create-page-if-not-exists! property classes %))
+                                                    (map #(create-page-if-not-exists! property string-classes %))
                                                     (map first))
                                                     (map first))
                                          values (set (map #(pu/get-page-uuid repo %) pages))]
                                          values (set (map #(pu/get-page-uuid repo %) pages))]
                                      (when on-chosen (on-chosen values)))))
                                      (when on-chosen (on-chosen values)))))
@@ -249,7 +250,7 @@
                 (assoc :on-chosen (fn [chosen]
                 (assoc :on-chosen (fn [chosen]
                                     (let [page* (string/trim (if (string? chosen) chosen (:value chosen)))]
                                     (let [page* (string/trim (if (string? chosen) chosen (:value chosen)))]
                                       (when-not (string/blank? page*)
                                       (when-not (string/blank? page*)
-                                        (let [[page id] (create-page-if-not-exists! property classes page*)
+                                        (let [[page id] (create-page-if-not-exists! property string-classes page*)
                                               id' (or id (pu/get-page-uuid repo page))]
                                               id' (or id (pu/get-page-uuid repo page))]
                                           (when on-chosen (on-chosen id'))))))))]
                                           (when on-chosen (on-chosen id'))))))))]
     (select-aux block property opts')))
     (select-aux block property opts')))
@@ -307,12 +308,11 @@
 
 
 (defn create-new-block!
 (defn create-new-block!
   [block property value]
   [block property value]
-  (let [repo (state/get-current-repo)
-        {:keys [page blocks]} (db-property-handler/property-create-new-block block property value editor-handler/wrap-parse-block)
-        last-block-id (:block/uuid (last blocks))]
-    (db/transact! repo (if page (cons page blocks) blocks) {:outliner-op :insert-blocks})
-    (add-property! block (:block/original-name property)
-                   (:block/uuid (first blocks)))
+  (let [last-block-id (db-property-handler/create-property-text-block! block property value
+                                                                       editor-handler/wrap-parse-block
+
+                                                                       {})]
+    (exit-edit-property)
     (editor-handler/edit-block! (db/entity [:block/uuid last-block-id]) :max last-block-id)))
     (editor-handler/edit-block! (db/entity [:block/uuid last-block-id]) :max last-block-id)))
 
 
 (defn create-new-block-from-template!
 (defn create-new-block-from-template!

+ 14 - 16
src/main/frontend/components/repo.cljs

@@ -22,20 +22,20 @@
 (rum/defc normalized-graph-label
 (rum/defc normalized-graph-label
   [{:keys [url remote? GraphName GraphUUID] :as graph} on-click]
   [{:keys [url remote? GraphName GraphUUID] :as graph} on-click]
   (when graph
   (when graph
-    (let [local? (config/local-file-based-graph? url)]
-      [:span.flex.items-center
-       (if local?
-         (let [local-dir (config/get-local-dir url)
-               graph-name (text-util/get-graph-name-from-path url)]
-           [:a.flex.items-center {:title    local-dir
-                                  :on-click #(on-click graph)}
-            [:span graph-name (when GraphName [:strong.px-1 "(" GraphName ")"])]
-            (when remote? [:strong.pr-1.flex.items-center (ui/icon "cloud")])])
-
-         [:a.flex.items-center {:title    GraphUUID
+    [:span.flex.items-center
+     (if (or (config/local-file-based-graph? url)
+             (config/db-based-graph? url))
+       (let [local-dir (config/get-local-dir url)
+             graph-name (text-util/get-graph-name-from-path url)]
+         [:a.flex.items-center {:title    local-dir
                                 :on-click #(on-click graph)}
                                 :on-click #(on-click graph)}
-          (db/get-repo-path (or url GraphName))
-          (when remote? [:strong.pl-1.flex.items-center (ui/icon "cloud")])])])))
+          [:span graph-name (when GraphName [:strong.px-1 "(" GraphName ")"])]
+          (when remote? [:strong.pr-1.flex.items-center (ui/icon "cloud")])])
+
+       [:a.flex.items-center {:title    GraphUUID
+                              :on-click #(on-click graph)}
+        (db/get-repo-path (or url GraphName))
+        (when remote? [:strong.pl-1.flex.items-center (ui/icon "cloud")])])]))
 
 
 (rum/defc repos-inner
 (rum/defc repos-inner
   "Graph list in `All graphs` page"
   "Graph list in `All graphs` page"
@@ -229,9 +229,7 @@
                              (let [remote? (:remote? (first (filter #(= current-repo (:url %)) repos)))
                              (let [remote? (:remote? (first (filter #(= current-repo (:url %)) repos)))
                                    repo-name (db/get-repo-name current-repo)
                                    repo-name (db/get-repo-name current-repo)
                                    short-repo-name (if repo-name
                                    short-repo-name (if repo-name
-                                                     (if (config/db-based-graph? repo-name)
-                                                       (string/replace-first repo-name config/db-version-prefix "")
-                                                       (db/get-short-repo-name repo-name))
+                                                     (db/get-short-repo-name repo-name)
                                                      "Select a Graph")]
                                                      "Select a Graph")]
                                [:a.item.group.flex.items-center.p-2.text-sm.font-medium.rounded-md
                                [:a.item.group.flex.items-center.p-2.text-sm.font-medium.rounded-md
 
 

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

@@ -459,7 +459,7 @@
 
 
                       (cond
                       (cond
                         (or unset? user-binding (false? user-binding))
                         (or unset? user-binding (false? user-binding))
-                        [:code.dark:bg-green-800.bg-green-300
+                        [:code
                          (if unset?
                          (if unset?
                            (t :keymap/unset)
                            (t :keymap/unset)
                            (str (t :keymap/custom) ": "
                            (str (t :keymap/custom) ": "

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

@@ -460,10 +460,7 @@
 
 
 (defn get-repo-fpath
 (defn get-repo-fpath
   [repo-url path]
   [repo-url path]
-  (if (and (or (util/electron?) (mobile-util/native-platform?))
-           (local-file-based-graph? repo-url))
-    (path/path-join (get-repo-dir repo-url) path)
-    (util/node-path.join (get-repo-dir repo-url) path)))
+  (path/path-join (get-repo-dir repo-url) path))
 
 
 (defn get-repo-config-path
 (defn get-repo-config-path
   []
   []

+ 23 - 13
src/main/frontend/db/conn.cljs

@@ -9,17 +9,15 @@
             [logseq.graph-parser.text :as text]
             [logseq.graph-parser.text :as text]
             [logseq.db :as ldb]
             [logseq.db :as ldb]
             [logseq.db.frontend.schema :as db-schema]
             [logseq.db.frontend.schema :as db-schema]
-            [logseq.graph-parser.util :as gp-util]))
+            [logseq.graph-parser.util :as gp-util]
+            [datascript.core :as d]))
 
 
 (defonce conns (atom {}))
 (defonce conns (atom {}))
 
 
 (defn get-repo-path
 (defn get-repo-path
   [url]
   [url]
-  (when url
-    (if (util/starts-with? url "http")
-      (->> (take-last 2 (string/split url #"/"))
-           util/string-join-path)
-      url)))
+  (assert (string? url) (str "url is not a string: " (type url)))
+  url)
 
 
 (defn get-repo-name
 (defn get-repo-name
   [repo-url]
   [repo-url]
@@ -36,15 +34,18 @@
 (defn get-short-repo-name
 (defn get-short-repo-name
   "repo-name: from get-repo-name. Dir/Name => Name"
   "repo-name: from get-repo-name. Dir/Name => Name"
   [repo-name]
   [repo-name]
-  (cond
-    (util/electron?)
-    (text/get-file-basename repo-name)
+  (let [repo-name' (cond
+                     (util/electron?)
+                     (text/get-file-basename repo-name)
 
 
-    (mobile-util/native-platform?)
-    (gp-util/safe-decode-uri-component (text/get-file-basename repo-name))
+                     (mobile-util/native-platform?)
+                     (gp-util/safe-decode-uri-component (text/get-file-basename repo-name))
 
 
-    :else
-    repo-name))
+                     :else
+                     repo-name)]
+    (if (config/db-based-graph? repo-name')
+      (string/replace-first repo-name' config/db-version-prefix "")
+      repo-name')))
 
 
 (defn datascript-db
 (defn datascript-db
   [repo]
   [repo]
@@ -81,6 +82,12 @@
   [repo]
   [repo]
   (swap! conns dissoc (datascript-db repo)))
   (swap! conns dissoc (datascript-db repo)))
 
 
+(defn kv
+  [key value]
+  {:db/id -1
+   :db/ident key
+   key value})
+
 (defn start!
 (defn start!
   ([repo]
   ([repo]
    (start! repo {}))
    (start! repo {}))
@@ -88,6 +95,9 @@
    (let [db-name (datascript-db repo)
    (let [db-name (datascript-db repo)
          db-conn (ldb/start-conn :schema (get-schema repo) :create-default-pages? false)]
          db-conn (ldb/start-conn :schema (get-schema repo) :create-default-pages? false)]
      (swap! conns assoc db-name db-conn)
      (swap! conns assoc db-name db-conn)
+     (when db-graph?
+       (d/transact! db-conn [(kv :db/type "db")])
+       (d/transact! db-conn [(kv :schema/version db-schema/version)]))
      (when listen-handler
      (when listen-handler
        (listen-handler repo))
        (listen-handler repo))
      (ldb/create-default-pages! db-conn {:db-graph? db-graph?}))))
      (ldb/create-default-pages! db-conn {:db-graph? db-graph?}))))

+ 3 - 2
src/main/frontend/db/datascript/entity_plus.cljs

@@ -16,10 +16,11 @@
 
 
      (and (= k :block/content) (config/db-based-graph? (state/get-current-repo)))
      (and (= k :block/content) (config/db-based-graph? (state/get-current-repo)))
      (let [result (lookup-entity e k default-value)
      (let [result (lookup-entity e k default-value)
-           refs (:block/refs e)]
+           refs (:block/refs e)
+           tags (:block/tags e)]
        (or
        (or
         (when (string? result)
         (when (string? result)
-          (db-utils/special-id->page result refs))
+          (db-utils/special-id->page result (distinct (concat refs tags))))
         default-value))
         default-value))
 
 
      :else
      :else

+ 7 - 5
src/main/frontend/db/fix.cljs

@@ -136,12 +136,14 @@
        (concat tx right-tx)))
        (concat tx right-tx)))
    conflicts))
    conflicts))
 
 
+(defn get-conflicts
+  [db page-id]
+  (let [parent-left->es (build-parent-left->es db page-id)]
+    (filter #(> (count (second %)) 1) parent-left->es)))
+
 (defn loop-fix-conflicts
 (defn loop-fix-conflicts
   [repo db page-id transact-opts]
   [repo db page-id transact-opts]
-  (let [get-conflicts (fn [db]
-                        (let [parent-left->es (build-parent-left->es db page-id)]
-                          (filter #(> (count (second %)) 1) parent-left->es)))
-        conflicts (get-conflicts db)
+  (let [conflicts (get-conflicts db page-id)
         fix-conflicts-tx (when (seq conflicts)
         fix-conflicts-tx (when (seq conflicts)
                            (fix-parent-left-conflicts conflicts))]
                            (fix-parent-left-conflicts conflicts))]
     (when (seq fix-conflicts-tx)
     (when (seq fix-conflicts-tx)
@@ -149,7 +151,7 @@
       (util/pprint fix-conflicts-tx)
       (util/pprint fix-conflicts-tx)
       (db/transact! repo fix-conflicts-tx transact-opts)
       (db/transact! repo fix-conflicts-tx transact-opts)
       (let [db (db/get-db repo)]
       (let [db (db/get-db repo)]
-        (when (seq (get-conflicts db))
+        (when (seq (get-conflicts db page-id))
           (loop-fix-conflicts repo db page-id transact-opts))))))
           (loop-fix-conflicts repo db page-id transact-opts))))))
 
 
 (defn fix-page-if-broken!
 (defn fix-page-if-broken!

+ 56 - 57
src/main/frontend/db/model.cljs

@@ -79,7 +79,7 @@
 
 
 (defn get-tag-blocks
 (defn get-tag-blocks
   [repo tag-name]
   [repo tag-name]
-  (d/q '[:find ?b
+  (d/q '[:find [?b ...]
          :in $ ?tag
          :in $ ?tag
          :where
          :where
          [?e :block/name ?tag]
          [?e :block/name ?tag]
@@ -228,11 +228,10 @@
   "Refresh file timestamps to DB"
   "Refresh file timestamps to DB"
   [repo path last-modified-at]
   [repo path last-modified-at]
   (when (and repo path last-modified-at)
   (when (and repo path last-modified-at)
-    (when-let [conn (conn/get-db repo false)]
-      (db-utils/transact! conn
-                   [{:file/path path
-                     :file/last-modified-at last-modified-at}]
-                   {:skip-refresh? true}))))
+    (db-utils/transact! repo
+                        [{:file/path path
+                          :file/last-modified-at last-modified-at}]
+                        {:skip-refresh? true})))
 
 
 (defn get-file-last-modified-at
 (defn get-file-last-modified-at
   [repo path]
   [repo path]
@@ -311,12 +310,13 @@ independent of format as format specific heading characters are stripped"
                 page-name
                 page-name
                 route-name
                 route-name
                 (fn content-matches? [block-content external-content block-id]
                 (fn content-matches? [block-content external-content block-id]
-                  (= (as-> (:block/refs (db-utils/entity repo block-id)) block-refs
-                       (-> block-content
-                           (db-utils/special-id->page block-refs)
-                           (db-utils/special-id-ref->page block-refs)
-                           heading-content->route-name))
-                     (string/lower-case external-content))))
+                  (let [block (db-utils/entity repo block-id)
+                        ref-tags (distinct (concat (:block/tags block) (:block/refs block)))]
+                    (= (-> block-content
+                           (db-utils/special-id->page ref-tags)
+                           (db-utils/special-id-ref->page ref-tags)
+                           heading-content->route-name)
+                       (string/lower-case external-content)))))
            ffirst)
            ffirst)
 
 
       (->> (d/q '[:find (pull ?b [:block/uuid])
       (->> (d/q '[:find (pull ?b [:block/uuid])
@@ -1192,7 +1192,7 @@ independent of format as format specific heading characters are stripped"
 (defn get-all-properties
 (defn get-all-properties
   "Returns a seq of property name strings"
   "Returns a seq of property name strings"
   []
   []
-  (if (react/db-graph?)
+  (if (config/db-based-graph? (state/get-current-repo))
     (db-based-get-all-properties)
     (db-based-get-all-properties)
     (map name (file-based-get-all-properties))))
     (map name (file-based-get-all-properties))))
 
 
@@ -1238,38 +1238,38 @@ independent of format as format specific heading characters are stripped"
   "Returns all property values of a given property for use in a simple query.
   "Returns all property values of a given property for use in a simple query.
    Property values that are references are displayed as page references"
    Property values that are references are displayed as page references"
   [repo property]
   [repo property]
-  (->> (d/q
-        '[:find ?prop-type ?v
-          :in $ ?prop-name
-          :where
-          [?b :block/properties ?bp]
-          [?prop-b :block/name ?prop-name]
-          [?prop-b :block/uuid ?prop-uuid]
-          [?prop-b :block/schema ?prop-schema]
-          [(get ?prop-schema :type) ?prop-type]
-          [(get ?bp ?prop-uuid) ?v]]
-        (conn/get-db repo)
-        (name property))
-       (map (fn [[prop-type v]] [prop-type (if (coll? v) v [v])]))
-       (mapcat (fn [[prop-type vals]]
-                 (case prop-type
-                   :enum
-                   (map #(:block/content (db-utils/entity repo [:block/uuid %])) vals)
-                   :default
+  (let [property-name (if (keyword? property)
+                        (name property)
+                        (util/page-name-sanity-lc property))]
+    (->> (d/q
+         '[:find ?prop-type ?v
+           :in $ ?prop-name
+           :where
+           [?b :block/properties ?bp]
+           [?prop-b :block/name ?prop-name]
+           [?prop-b :block/uuid ?prop-uuid]
+           [?prop-b :block/schema ?prop-schema]
+           [(get ?prop-schema :type) ?prop-type]
+           [(get ?bp ?prop-uuid) ?v]]
+         (conn/get-db repo)
+         property-name)
+        (map (fn [[prop-type v]] [prop-type (if (coll? v) v [v])]))
+        (mapcat (fn [[prop-type vals]]
+                  (case prop-type
+                    :default
                    ;; Remove multi-block properties as there isn't a supported approach to query them yet
                    ;; Remove multi-block properties as there isn't a supported approach to query them yet
-                   (map str (remove uuid? vals))
-                   (:page :date)
-                   (map #(page-ref/->page-ref (:block/original-name (db-utils/entity repo [:block/uuid %])))
-                        vals)
-                   :number
-                   vals
+                    (map str (remove uuid? vals))
+                    (:page :date)
+                    (map #(page-ref/->page-ref (:block/original-name (db-utils/entity repo [:block/uuid %])))
+                         vals)
+                    :number
+                    vals
                    ;; Checkboxes returned as strings as builder doesn't display boolean values correctly
                    ;; Checkboxes returned as strings as builder doesn't display boolean values correctly
-                   (map str vals))))
+                    (map str vals))))
        ;; Remove blanks as they match on everything
        ;; Remove blanks as they match on everything
-       (remove string/blank?)
-       (distinct)
-       (sort)))
-
+        (remove string/blank?)
+        (distinct)
+        (sort))))
 
 
 (defn get-block-property-values
 (defn get-block-property-values
   "Get blocks which have this property."
   "Get blocks which have this property."
@@ -1288,15 +1288,15 @@ independent of format as format specific heading characters are stripped"
   "Get classes which have given property as a class property"
   "Get classes which have given property as a class property"
   [property-uuid]
   [property-uuid]
   (d/q
   (d/q
-    '[:find ?b
-      :in $ ?property-uuid
-      :where
-      [?b :block/schema ?schema]
-      [(get ?schema :properties) ?schema-properties*]
-      [(set ?schema-properties*) ?schema-properties]
-      [(contains? ?schema-properties ?property-uuid)]]
-    (conn/get-db)
-    property-uuid))
+   '[:find [?b ...]
+     :in $ ?property-uuid
+     :where
+     [?b :block/schema ?schema]
+     [(get ?schema :properties) ?schema-properties*]
+     [(set ?schema-properties*) ?schema-properties]
+     [(contains? ?schema-properties ?property-uuid)]]
+   (conn/get-db)
+   property-uuid))
 
 
 (defn get-template-by-name
 (defn get-template-by-name
   [name]
   [name]
@@ -1534,7 +1534,7 @@ independent of format as format specific heading characters are stripped"
 
 
 (defn get-whiteboard-id-nonces
 (defn get-whiteboard-id-nonces
   [repo page-name]
   [repo page-name]
-  (let [key (if (react/db-graph?)
+  (let [key (if (config/db-based-graph? repo)
               (:block/uuid (db-utils/entity [:block/name "logseq.tldraw.shape"]))
               (:block/uuid (db-utils/entity [:block/name "logseq.tldraw.shape"]))
               :logseq.tldraw.shape)
               :logseq.tldraw.shape)
         page (db-utils/entity [:block/name (util/page-name-sanity-lc page-name)])]
         page (db-utils/entity [:block/name (util/page-name-sanity-lc page-name)])]
@@ -1556,17 +1556,16 @@ independent of format as format specific heading characters are stripped"
     (conn/get-db repo)))
     (conn/get-db repo)))
 
 
 (defn get-namespace-children
 (defn get-namespace-children
-  [repo-url eid]
+  [repo eid]
   (->>
   (->>
-   (d/q '[:find ?children
+   (d/q '[:find [?children ...]
           :in $ ?parent %
           :in $ ?parent %
           :where
           :where
           (namespace ?parent ?children)]
           (namespace ?parent ?children)]
-        (conn/get-db repo-url)
+        (conn/get-db repo)
         eid
         eid
         (:namespace rules/rules))
         (:namespace rules/rules))
-   db-utils/seq-flatten
-   (set)))
+   distinct))
 
 
 (defn get-class-objects
 (defn get-class-objects
   [repo class-id]
   [repo class-id]

+ 2 - 7
src/main/frontend/db/outliner.cljs

@@ -1,8 +1,7 @@
 (ns frontend.db.outliner
 (ns frontend.db.outliner
   "Db related fns for the outliner module"
   "Db related fns for the outliner module"
-  (:require [datascript.core :as d]
-            [frontend.db.utils :as db-utils]))
-0
+  (:require [datascript.core :as d]))
+
 (defn get-by-id
 (defn get-by-id
   [conn id]
   [conn id]
   (try
   (try
@@ -16,7 +15,3 @@
     :in $ ?id
     :in $ ?id
     :where
     :where
     [?a :block/parent ?id]])
     [?a :block/parent ?id]])
-
-(defn del-block
-  [conn id-or-look-ref]
-  (db-utils/transact! conn [[:db.fn/retractEntity id-or-look-ref]]))

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

@@ -62,11 +62,7 @@
   (when-let [result-atom (get-in @query-state [k :result])]
   (when-let [result-atom (get-in @query-state [k :result])]
     (reset! result-atom new-result)))
     (reset! result-atom new-result)))
 
 
-(defn kv
-  [key value]
-  {:db/id -1
-   :db/ident key
-   key value})
+(def kv conn/kv)
 
 
 (defn remove-key!
 (defn remove-key!
   [repo-url key]
   [repo-url key]
@@ -380,7 +376,8 @@
       (recur))
       (recur))
     chan))
     chan))
 
 
-(defn db-graph?
-  "Whether the current graph is db-only"
-  []
-  (= "db" (sub-key-value :db/type)))
+(comment
+  (defn db-graph?
+    "Whether the current graph is db-only"
+    []
+    (= "db" (:db/type (db-utils/entity :db/type)))))

+ 37 - 71
src/main/frontend/db/restore.cljs

@@ -2,7 +2,6 @@
   "Fns for DB restore(from text or sqlite)"
   "Fns for DB restore(from text or sqlite)"
   (:require [datascript.core :as d]
   (:require [datascript.core :as d]
             [frontend.config :as config]
             [frontend.config :as config]
-            [frontend.db :as db]
             [frontend.db.conn :as db-conn]
             [frontend.db.conn :as db-conn]
             [frontend.db.file-based.migrate :as db-migrate]
             [frontend.db.file-based.migrate :as db-migrate]
             [frontend.db.persist :as db-persist]
             [frontend.db.persist :as db-persist]
@@ -10,15 +9,13 @@
             [frontend.db.utils :as db-utils]
             [frontend.db.utils :as db-utils]
             [frontend.state :as state]
             [frontend.state :as state]
             [frontend.persist-db :as persist-db]
             [frontend.persist-db :as persist-db]
-            [goog.object :as gobj]
             [logseq.db.frontend.schema :as db-schema]
             [logseq.db.frontend.schema :as db-schema]
-            [logseq.db.sqlite.restore :as sqlite-restore]
             [logseq.db.sqlite.util :as sqlite-util]
             [logseq.db.sqlite.util :as sqlite-util]
             [promesa.core :as p]
             [promesa.core :as p]
             [frontend.util :as util]
             [frontend.util :as util]
             [cljs-time.core :as t]
             [cljs-time.core :as t]
             [logseq.db.frontend.property :as db-property]
             [logseq.db.frontend.property :as db-property]
-            [cljs-bean.core :as bean]
+            [logseq.db.frontend.property.util :as db-property-util]
             [datascript.transit :as dt]))
             [datascript.transit :as dt]))
 
 
 (defn- old-schema?
 (defn- old-schema?
@@ -57,64 +54,39 @@
                 (db-conn/reset-conn! db-conn db)))]
                 (db-conn/reset-conn! db-conn db)))]
     (d/transact! db-conn [{:schema/version db-schema/version}])))
     (d/transact! db-conn [{:schema/version db-schema/version}])))
 
 
-(defn- set-unloaded-block-ids!
-  [repo data]
-  (util/profile
-   "Set unloaded-block-ids"
-   (let [unloaded-block-ids (transient #{})]
-     (doseq [b data]
-       (conj! unloaded-block-ids (gobj/get b "uuid") (gobj/get b "page_uuid")))
-     (state/set-state! [repo :restore/unloaded-blocks] (persistent! unloaded-block-ids)))))
-
 (defn- update-built-in-properties!
 (defn- update-built-in-properties!
   [conn]
   [conn]
-  (let [txs (keep
-             (fn [[k-keyword {:keys [schema original-name]}]]
+  (let [txs (mapcat
+             (fn [[k-keyword {:keys [schema original-name] :as property-config}]]
                (let [k-name (name k-keyword)
                (let [k-name (name k-keyword)
                      property (d/entity @conn [:block/name k-name])]
                      property (d/entity @conn [:block/name k-name])]
-                 (when-not (= {:schema schema
+                 (when (and
+                        (not= {:schema schema
                                :original-name (or original-name k-name)}
                                :original-name (or original-name k-name)}
                               {:schema (:block/schema property)
                               {:schema (:block/schema property)
                                :original-name (:block/original-name property)})
                                :original-name (:block/original-name property)})
+                         ;; Updating closed values disabled until it's worth the effort
+                         ;; to diff closed values
+                        (not (:closed-values property-config)))
                    (if property
                    (if property
-                     {:block/schema schema
-                      :block/original-name (or original-name k-name)
-                      :block/name (util/page-name-sanity-lc k-name)
-                      :block/uuid (:block/uuid property)
-                      :block/type "property"}
-                     (sqlite-util/block-with-timestamps
-                      {:block/schema schema
+                     [{:block/schema schema
                        :block/original-name (or original-name k-name)
                        :block/original-name (or original-name k-name)
                        :block/name (util/page-name-sanity-lc k-name)
                        :block/name (util/page-name-sanity-lc k-name)
-                       :block/uuid (db/new-block-id)
-                       :block/type "property"})))))
+                       :block/uuid (:block/uuid property)
+                       :block/type "property"}]
+                     (if (:closed-values property-config)
+                       (db-property-util/build-closed-values
+                        (or original-name k-name)
+                        (assoc property-config :block/uuid (d/squuid))
+                        {})
+                       [(sqlite-util/build-new-property
+                         {:block/schema schema
+                          :block/original-name (or original-name k-name)
+                          :block/name (util/page-name-sanity-lc k-name)
+                          :block/uuid (d/squuid)})])))))
              db-property/built-in-properties)]
              db-property/built-in-properties)]
     (when (seq txs)
     (when (seq txs)
-      (db/transact! conn txs))))
-
-(defn- restore-other-data-from-sqlite!
-  [repo data uuid->db-id-map]
-  (let [start (util/time-ms)
-        conn (db-conn/get-db repo false)
-        profiled-init-db (fn profiled-init-db [all-datoms schema]
-                           (util/profile
-                            (str "DB init! " (count all-datoms) " datoms")
-                            (d/init-db all-datoms schema)))
-        new-db (sqlite-restore/restore-other-data conn data uuid->db-id-map {:init-db-fn profiled-init-db})]
-
-    (reset! conn new-db)
-
-    (update-built-in-properties! conn)
-
-    (let [end (util/time-ms)]
-      (println "[debug] load others from SQLite: " (int (- end start)) " ms."))
-
-    (p/let [_ (p/delay 150)]          ; More time for UI refresh
-      (state/set-state! [repo :restore/unloaded-blocks] nil)
-      (state/set-state! [repo :restore/unloaded-pages] nil)
-      (state/set-state! :graph/loading? false)
-      (react/clear-query-state!)
-      (state/pub-event! [:ui/re-render-root]))))
+      (d/transact! conn txs))))
 
 
 (defn- restore-graph-from-sqlite!
 (defn- restore-graph-from-sqlite!
   "Load initial data from SQLite"
   "Load initial data from SQLite"
@@ -122,35 +94,29 @@
   (state/set-state! :graph/loading? true)
   (state/set-state! :graph/loading? true)
   (p/let [start-time (t/now)
   (p/let [start-time (t/now)
           data (persist-db/<fetch-init-data repo)
           data (persist-db/<fetch-init-data repo)
-          electron? (util/electron?)
-          {:keys [conn uuid->db-id-map journal-blocks datoms-count]}
-          (comment electron? (sqlite-restore/restore-initial-data data {:conn-from-datoms-fn
-                                                                         (fn profiled-d-conn [& args]
-                                                                           (util/profile :restore-graph-from-sqlite!-init-db (apply d/conn-from-datoms args)))}))
-          [conn datoms-count] (if true
-                                (do
-                                  (assert (some? data) "No data found when reloading db")
-                                  (let [datoms (dt/read-transit-str data)]
-                                    [(d/conn-from-datoms datoms db-schema/schema-for-db-based-graph)
-                                     (count datoms)]))
-                                [conn datoms-count])
+          _ (assert (some? data) "No data found when reloading db")
+          datoms (dt/read-transit-str data)
+          datoms-count (count datoms)
+          conn (d/conn-from-datoms datoms db-schema/schema-for-db-based-graph)
           db-name (db-conn/datascript-db repo)
           db-name (db-conn/datascript-db repo)
           _ (swap! db-conn/conns assoc db-name conn)
           _ (swap! db-conn/conns assoc db-name conn)
           end-time (t/now)]
           end-time (t/now)]
+
+    ;; FIXME: why not do this when creating the db?
+    (update-built-in-properties! conn)
+
     (println :restore-graph-from-sqlite!-prepare (t/in-millis (t/interval start-time end-time)) "ms"
     (println :restore-graph-from-sqlite!-prepare (t/in-millis (t/interval start-time end-time)) "ms"
              " Datoms in total: " datoms-count)
              " Datoms in total: " datoms-count)
 
 
-    ;; TODO: Store schema in sqlite
+    ;; FIXME:
     ;; (db-migrate/migrate attached-db)
     ;; (db-migrate/migrate attached-db)
 
 
-    (comment when-not electron?
-      (js/setTimeout
-       (fn []
-         (p/let [other-data (persist-db/<fetch-blocks-excluding repo (map :uuid journal-blocks))
-                 _ (set-unloaded-block-ids! repo other-data)
-                 _ (p/delay 10)]
-           (restore-other-data-from-sqlite! repo other-data uuid->db-id-map)))
-       100))))
+    (p/let [_ (p/delay 150)]          ; More time for UI refresh
+      (state/set-state! [repo :restore/unloaded-blocks] nil)
+      (state/set-state! [repo :restore/unloaded-pages] nil)
+      (state/set-state! :graph/loading? false)
+      (react/clear-query-state!)
+      (state/pub-event! [:ui/re-render-root]))))
 
 
 (defn restore-graph!
 (defn restore-graph!
   "Restore db from serialized db cache"
   "Restore db from serialized db cache"

+ 87 - 41
src/main/frontend/db/rtc/core.cljs

@@ -7,18 +7,20 @@
             [cljs.core.async :as async :refer [<! >! chan go go-loop]]
             [cljs.core.async :as async :refer [<! >! chan go go-loop]]
             [clojure.set :as set]
             [clojure.set :as set]
             [cognitect.transit :as transit]
             [cognitect.transit :as transit]
+            [frontend.async-util :include-macros true :refer [<?]]
             [frontend.db :as db]
             [frontend.db :as db]
             [frontend.db.react :as react]
             [frontend.db.react :as react]
             [frontend.db.rtc.const :as rtc-const]
             [frontend.db.rtc.const :as rtc-const]
             [frontend.db.rtc.op-mem-layer :as op-mem-layer]
             [frontend.db.rtc.op-mem-layer :as op-mem-layer]
             [frontend.db.rtc.ws :as ws]
             [frontend.db.rtc.ws :as ws]
             [frontend.handler.page :as page-handler]
             [frontend.handler.page :as page-handler]
+            [frontend.handler.property.util :as pu]
             [frontend.handler.user :as user]
             [frontend.handler.user :as user]
+            [frontend.handler.whiteboard :as whiteboard-handler]
             [frontend.modules.outliner.core :as outliner-core]
             [frontend.modules.outliner.core :as outliner-core]
             [frontend.modules.outliner.transaction :as outliner-tx]
             [frontend.modules.outliner.transaction :as outliner-tx]
             [frontend.state :as state]
             [frontend.state :as state]
             [frontend.util :as util]
             [frontend.util :as util]
-            [frontend.async-util :include-macros true :refer [<?]]
             [malli.core :as m]
             [malli.core :as m]
             [malli.util :as mu]))
             [malli.util :as mu]))
 
 
@@ -100,6 +102,15 @@
    {:persist-op? false}
    {:persist-op? false}
    (apply outliner-core/save-block! args)))
    (apply outliner-core/save-block! args)))
 
 
+(defmethod transact-db! :delete-whiteboard-blocks [_ repo block-uuids]
+  (db/transact! repo
+                (mapv (fn [block-uuid] [:db/retractEntity [:block/uuid block-uuid]]) block-uuids)
+                {:persist-op? false}))
+
+(defmethod transact-db! :upsert-whiteboard-block [_ repo blocks]
+  (db/transact! repo blocks {:persist-op? false}))
+
+
 (defmethod transact-db! :raw [_ & args]
 (defmethod transact-db! :raw [_ & args]
   (apply db/transact! args))
   (apply db/transact! args))
 
 
@@ -107,13 +118,25 @@
   [block]
   [block]
   (contains? (set (:block/type block)) "whiteboard"))
   (contains? (set (:block/type block)) "whiteboard"))
 
 
+(defn- group-remote-remove-ops-by-whiteboard-block
+  "return {true [<whiteboard-block-ops>], false [<other-ops>]}"
+  [repo remote-remove-ops]
+  (group-by (fn [{:keys [block-uuid]}]
+              (boolean
+               (when-let [block (db/pull repo [{:block/parent [:block/type]}] [:block/uuid block-uuid])]
+                 (whiteboard-page-block? (:block/parent block)))))
+            remote-remove-ops))
+
 (defn apply-remote-remove-ops
 (defn apply-remote-remove-ops
   [repo remove-ops]
   [repo remove-ops]
   (prn :remove-ops remove-ops)
   (prn :remove-ops remove-ops)
-  (doseq [op remove-ops]
-    (when-let [block (db/pull repo '[*] [:block/uuid (:block-uuid op)])]
-      (transact-db! :delete-blocks [block] {:children? false})
-      (prn :apply-remote-remove-ops (:block-uuid op)))))
+  (let [{whiteboard-block-ops true other-ops false} (group-remote-remove-ops-by-whiteboard-block repo remove-ops)]
+    (transact-db! :delete-whiteboard-blocks (map :block-uuid whiteboard-block-ops))
+
+    (doseq [op other-ops]
+      (when-let [block (db/pull repo '[*] [:block/uuid (:block-uuid op)])]
+        (transact-db! :delete-blocks [block] {:children? false})
+        (prn :apply-remote-remove-ops (:block-uuid op))))))
 
 
 (defn- insert-or-move-block
 (defn- insert-or-move-block
   [repo block-uuid remote-parents remote-left-uuid move?]
   [repo block-uuid remote-parents remote-left-uuid move?]
@@ -126,8 +149,8 @@
           b {:block/uuid block-uuid}
           b {:block/uuid block-uuid}
           ;; b-ent (db/entity repo [:block/uuid (uuid block-uuid-str)])
           ;; b-ent (db/entity repo [:block/uuid (uuid block-uuid-str)])
           ]
           ]
-      (case [(some? local-parent) (some? local-left)]
-        [false true]
+      (case [whiteboard-page-block? (some? local-parent) (some? local-left)]
+        [false false true]
         (if move?
         (if move?
           (transact-db! :move-blocks [b] local-left true)
           (transact-db! :move-blocks [b] local-left true)
           (transact-db! :insert-blocks
           (transact-db! :insert-blocks
@@ -136,7 +159,7 @@
                           :block/format :markdown}]
                           :block/format :markdown}]
                         local-left {:sibling? true :keep-uuid? true}))
                         local-left {:sibling? true :keep-uuid? true}))
 
 
-        [true true]
+        [false true true]
         (let [sibling? (not= (:block/uuid local-parent) (:block/uuid local-left))]
         (let [sibling? (not= (:block/uuid local-parent) (:block/uuid local-left))]
           (if move?
           (if move?
             (transact-db! :move-blocks [b] local-left sibling?)
             (transact-db! :move-blocks [b] local-left sibling?)
@@ -145,13 +168,19 @@
                             :block/format :markdown}]
                             :block/format :markdown}]
                           local-left {:sibling? sibling? :keep-uuid? true})))
                           local-left {:sibling? sibling? :keep-uuid? true})))
 
 
-        [true false]
+        [false true false]
         (if move?
         (if move?
           (transact-db! :move-blocks [b] local-parent false)
           (transact-db! :move-blocks [b] local-parent false)
           (transact-db! :insert-blocks
           (transact-db! :insert-blocks
                         [{:block/uuid block-uuid :block/content ""
                         [{:block/uuid block-uuid :block/content ""
                           :block/format :markdown}]
                           :block/format :markdown}]
                         local-parent {:sibling? false :keep-uuid? true}))
                         local-parent {:sibling? false :keep-uuid? true}))
+
+        ([true true false] [true true true])
+        ;; Don't need to insert-whiteboard-block here,
+        ;; will do :upsert-whiteboard-block in `update-block-attrs`
+        nil
+
         (throw (ex-info "Don't know where to insert" {:block-uuid block-uuid :remote-parents remote-parents
         (throw (ex-info "Don't know where to insert" {:block-uuid block-uuid :remote-parents remote-parents
                                                       :remote-left remote-left-uuid}))))))
                                                       :remote-left remote-left-uuid}))))))
 
 
@@ -201,44 +230,61 @@
       :wrong-pos
       :wrong-pos
       :else nil)))
       :else nil)))
 
 
-
+(defn- upsert-whiteboard-block
+  [repo {:keys [parents properties] :as _op-value}]
+  (let [first-remote-parent (first parents)]
+    (when-let [local-parent (db/pull repo '[*] [:block/uuid first-remote-parent])]
+      (let [page-name (:block/name local-parent)
+            properties* (transit/read transit-r properties)
+            shape-property-id (pu/get-pid :logseq.tldraw.shape)
+            shape (and (map? properties*)
+                       (get properties* shape-property-id))]
+        (assert (some? page-name) local-parent)
+        (assert (some? shape) properties*)
+        (transact-db! :upsert-whiteboard-block repo [(whiteboard-handler/shape->block shape page-name)])))))
 
 
 (defn- update-block-attrs
 (defn- update-block-attrs
-  [repo block-uuid op-value]
+  [repo block-uuid {:keys [parents] :as op-value}]
   (let [key-set (set/intersection
   (let [key-set (set/intersection
                  (conj rtc-const/general-attr-set :content)
                  (conj rtc-const/general-attr-set :content)
                  (set (keys op-value)))]
                  (set (keys op-value)))]
     (when (seq key-set)
     (when (seq key-set)
-      (let [b-ent (db/pull repo '[*] [:block/uuid block-uuid])
-            new-block
-            (cond-> (db/pull repo '[*] (:db/id b-ent))
-              (and (contains? key-set :content)
-                   (not= (:content op-value)
-                         (:block/content b-ent))) (assoc :block/content (:content op-value))
-              (contains? key-set :updated-at)     (assoc :block/updated-at (:updated-at op-value))
-              (contains? key-set :created-at)     (assoc :block/created-at (:created-at op-value))
-              (contains? key-set :alias)          (assoc :block/alias (some->> (seq (:alias op-value))
-                                                                               (map (partial vector :block/uuid))
-                                                                               (db/pull-many repo [:db/id])
-                                                                               (keep :db/id)))
-              (contains? key-set :type)           (assoc :block/type (:type op-value))
-              (contains? key-set :schema)         (assoc :block/schema (transit/read transit-r (:schema op-value)))
-              (contains? key-set :tags)           (assoc :block/tags (some->> (seq (:tags op-value))
-                                                                              (map (partial vector :block/uuid))
-                                                                              (db/pull-many repo [:db/id])
-                                                                              (keep :db/id)))
-              ;; FIXME: it looks save-block won't save :block/properties??
-              ;;        so I need to transact properties myself
-              ;; (contains? key-set :properties)     (assoc :block/properties
-              ;;                                            (transit/read transit-r (:properties op-value)))
-              )]
-        (transact-db! :save-block new-block)
-        (let [properties (transit/read transit-r (:properties op-value))]
-          (transact-db! :raw
-                        repo
-                        [{:block/uuid block-uuid
-                          :block/properties properties}]
-                        {:outliner-op :save-block}))))))
+      (let [first-remote-parent (first parents)
+            local-parent (db/pull repo '[*] [:block/uuid first-remote-parent])
+            whiteboard-page-block? (whiteboard-page-block? local-parent)]
+        (if whiteboard-page-block?
+          (upsert-whiteboard-block repo op-value)
+
+          (let [b-ent (db/pull repo '[*] [:block/uuid block-uuid])
+                new-block
+                (cond-> (db/pull repo '[*] (:db/id b-ent))
+                  (and (contains? key-set :content)
+                       (not= (:content op-value)
+                             (:block/content b-ent))) (assoc :block/content (:content op-value))
+                  (contains? key-set :updated-at)     (assoc :block/updated-at (:updated-at op-value))
+                  (contains? key-set :created-at)     (assoc :block/created-at (:created-at op-value))
+                  (contains? key-set :alias)          (assoc :block/alias (some->> (seq (:alias op-value))
+                                                                                   (map (partial vector :block/uuid))
+                                                                                   (db/pull-many repo [:db/id])
+                                                                                   (keep :db/id)))
+                  (contains? key-set :type)           (assoc :block/type (:type op-value))
+                  (contains? key-set :schema)         (assoc :block/schema (transit/read transit-r (:schema op-value)))
+                  (contains? key-set :tags)           (assoc :block/tags (some->> (seq (:tags op-value))
+                                                                                  (map (partial vector :block/uuid))
+                                                                                  (db/pull-many repo [:db/id])
+                                                                                  (keep :db/id)))
+                  ;; FIXME: it looks save-block won't save :block/properties??
+                  ;;        so I need to transact properties myself
+                  ;; (contains? key-set :properties)     (assoc :block/properties
+                  ;;                                            (transit/read transit-r (:properties op-value)))
+                  )]
+            (transact-db! :save-block new-block)
+            (let [properties (transit/read transit-r (:properties op-value))]
+              (transact-db! :raw
+                            repo
+                            [{:block/uuid block-uuid
+                              :block/properties properties}]
+                            {:outliner-op :save-block}))))))))
 
 
 (defn apply-remote-move-ops
 (defn apply-remote-move-ops
   [repo sorted-move-ops]
   [repo sorted-move-ops]

+ 1 - 2
src/main/frontend/db/rtc/full_upload_download_graph.cljs

@@ -8,7 +8,6 @@
             [cognitect.transit :as transit]
             [cognitect.transit :as transit]
             [datascript.core :as d]
             [datascript.core :as d]
             [frontend.async-util :include-macros true :refer [<? go-try]]
             [frontend.async-util :include-macros true :refer [<? go-try]]
-            [frontend.db :as db]
             [frontend.db.conn :as conn]
             [frontend.db.conn :as conn]
             [frontend.db.rtc.op-mem-layer :as op-mem-layer]
             [frontend.db.rtc.op-mem-layer :as op-mem-layer]
             [frontend.db.rtc.ws :refer [<send!]]
             [frontend.db.rtc.ws :refer [<send!]]
@@ -121,7 +120,7 @@
          conn (d/create-conn db-schema/schema-for-db-based-graph)
          conn (d/create-conn db-schema/schema-for-db-based-graph)
          blocks* (replace-db-id-with-temp-id blocks)
          blocks* (replace-db-id-with-temp-id blocks)
          blocks-with-page-id (fill-block-fields blocks*)]
          blocks-with-page-id (fill-block-fields blocks*)]
-     (db/transact! conn blocks-with-page-id)
+     (d/transact! conn blocks-with-page-id)
      (let [db (d/db conn)
      (let [db (d/db conn)
            blocks*
            blocks*
            (d/pull-many db '[*] (keep (fn [b] (when-let [uuid (:block/uuid b)] [:block/uuid uuid])) blocks))
            (d/pull-many db '[*] (keep (fn [b] (when-let [uuid (:block/uuid b)] [:block/uuid uuid])) blocks))

+ 1 - 1
src/main/frontend/extensions/pdf/assets.cljs

@@ -204,7 +204,7 @@
                        (assoc (pu/get-pid :id) (str id)))
                        (assoc (pu/get-pid :id) (str id)))
                properties (->>
                properties (->>
                            (wrap-props props)
                            (wrap-props props)
-                           (property-handler/replace-key-with-id! (state/get-current-repo)))]
+                           (property-handler/replace-key-with-id (state/get-current-repo)))]
            (when (string? text)
            (when (string? text)
              (editor-handler/api-insert-new-block!
              (editor-handler/api-insert-new-block!
               text (merge {:page        (:block/name ref-page)
               text (merge {:page        (:block/name ref-page)

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

@@ -83,7 +83,7 @@
                   (do
                   (do
                     (reset! *loading? true)
                     (reset! *loading? true)
                     (loader/load
                     (loader/load
-                     (config/asset-uri "/static/js/reveal.js")
+                     (config/asset-uri (if config/publishing? "static/js/reveal.js" "/static/js/reveal.js"))
                      (fn []
                      (fn []
                        (reset! *loading? false)
                        (reset! *loading? false)
                        (render!)))))
                        (render!)))))

+ 32 - 6
src/main/frontend/fs/memory_fs.cljs

@@ -44,6 +44,34 @@
       (p/catch (fn [_error]
       (p/catch (fn [_error]
                  (js/window.pfs.mkdir dir)))))
                  (js/window.pfs.mkdir dir)))))
 
 
+(defn- <exists?
+  "dir is path, without memory:// prefix for simplicity"
+  [dir]
+  (-> (js/window.pfs.stat dir)
+      (p/then (fn [stat]
+                (not (nil? stat))))
+      (p/catch (fn [_]
+                 nil))))
+
+(defn- <mkdir-recur!
+  "mkdir, recursively create parent directories if not exist
+
+   lightning-fs does not support's :recursive in mkdir options"
+  [dir]
+  (p/let [fpath (path/url-to-path dir)
+          sub-dirs (p/loop [top-parent fpath
+                            remains []]
+                     (p/let [exists? (<exists? top-parent)]
+                       (if exists?
+                         (reverse remains) ;; top-parent is the first non-exist dir
+                         (p/recur (path/parent top-parent)
+                                  (conj remains top-parent)))))]
+    (p/loop [remains sub-dirs]
+      (if (empty? remains)
+        (p/resolved nil)
+        (p/do! (js/window.pfs.mkdir (first remains))
+               (p/recur (rest remains)))))))
+
 (defrecord MemoryFs []
 (defrecord MemoryFs []
   protocol/Fs
   protocol/Fs
   (mkdir! [_this dir]
   (mkdir! [_this dir]
@@ -51,13 +79,11 @@
       (let [fpath (path/url-to-path dir)]
       (let [fpath (path/url-to-path dir)]
         (-> (js/window.pfs.mkdir fpath)
         (-> (js/window.pfs.mkdir fpath)
             (p/catch (fn [error] (println "(memory-fs)Mkdir error: " error)))))))
             (p/catch (fn [error] (println "(memory-fs)Mkdir error: " error)))))))
-  (mkdir-recur! [this dir]
-    ;; FIXME: replace this with a recurisve implementation
+  (mkdir-recur! [_this dir]
     (when js/window.pfs
     (when js/window.pfs
-      (p/let [dir' (path/url-to-path dir)
-              parent (path/parent dir')
-              _ (when parent (<ensure-dir! parent))]
-        (protocol/mkdir! this dir'))))
+      (let [fpath (path/url-to-path dir)]
+        (-> (<mkdir-recur! fpath)
+            (p/catch (fn [error] (println "(memory-fs)Mkdir-recur error: " error)))))))
 
 
   (readdir [_this dir]
   (readdir [_this dir]
     (when js/window.pfs
     (when js/window.pfs

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

@@ -2701,7 +2701,7 @@
                     v)))))
                     v)))))
        :flush-fn #(swap! *sync-state sync-state-reset-queued-local->remote-files)
        :flush-fn #(swap! *sync-state sync-state-reset-queued-local->remote-files)
        :stop-ch stop-chan
        :stop-ch stop-chan
-       :distinct-coll? true
+       :distinct-key-fn identity
        :flush-now-ch private-immediately-local->remote-chan
        :flush-now-ch private-immediately-local->remote-chan
        :refresh-timeout-ch private-recent-edited-chan)))
        :refresh-timeout-ch private-recent-edited-chan)))
 
 

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

@@ -53,17 +53,11 @@
 
 
 (defn- set-global-error-notification!
 (defn- set-global-error-notification!
   []
   []
-  (set! js/window.onerror
-        (fn [message, _source, _lineno, _colno, error]
-          (when-not (error/ignored? message)
-            (log/error :exception error)))))
-            ;; (notification/show!
-            ;;  (str "message=" message "\nsource=" source "\nlineno=" lineno "\ncolno=" colno "\nerror=" error)
-            ;;  :error
-            ;;  ;; Don't auto-hide
-            ;;  false)
-
-
+  (when-not config/dev?
+    (set! js/window.onerror
+          (fn [message, _source, _lineno, _colno, error]
+            (when-not (error/ignored? message)
+              (log/error :exception error))))))
 
 
 (defn- watch-for-date!
 (defn- watch-for-date!
   []
   []

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

@@ -76,8 +76,9 @@
 
 
                     (str "assets://" (string/replace rpath' (str "@" (:name alias)) (:dir alias)))
                     (str "assets://" (string/replace rpath' (str "@" (:name alias)) (:dir alias)))
 
 
-                    (if has-schema? (path/path-join graph-root rpath)
-                        (path/path-join "file://" graph-root rpath))))]
+                    (if has-schema?
+                      (path/path-join graph-root rpath)
+                      (path/prepend-protocol "file:" (path/path-join graph-root rpath)))))]
         (convert-platform-protocol ret)))))
         (convert-platform-protocol ret)))))
 
 
 (defn normalize-asset-resource-url
 (defn normalize-asset-resource-url

+ 2 - 2
src/main/frontend/handler/common/page.cljs

@@ -117,7 +117,7 @@
    TODO: Add other options"
    TODO: Add other options"
   ([title]
   ([title]
    (create! title {}))
    (create! title {}))
-  ([title {:keys [redirect? create-first-block? format properties split-namespace? journal? uuid rename? persist-op?]
+  ([title {:keys [redirect? create-first-block? format properties split-namespace? journal? uuid rename? persist-op? whiteboard? class?]
            :or   {redirect?           true
            :or   {redirect?           true
                   create-first-block? true
                   create-first-block? true
                   rename?             false
                   rename?             false
@@ -172,7 +172,7 @@
          (when (seq txs)
          (when (seq txs)
            (db/transact! repo txs {:persist-op? persist-op?})))
            (db/transact! repo txs {:persist-op? persist-op?})))
 
 
-       (when create-first-block?
+       (when (and create-first-block? (not (or whiteboard? class?)))
          (when (or
          (when (or
                 (db/page-empty? repo (:db/id (db/entity [:block/name page-name])))
                 (db/page-empty? repo (:db/id (db/entity [:block/name page-name])))
                 (create-title-property? repo journal? page-name))
                 (create-title-property? repo journal? page-name))

+ 38 - 29
src/main/frontend/handler/db_based/page.cljs

@@ -60,8 +60,8 @@
                      refs)]
                      refs)]
         tx-data))))
         tx-data))))
 
 
-(defn based-merge-pages!
-  [from-page-name to-page-name persist-op?]
+(defn- based-merge-pages!
+  [from-page-name to-page-name persist-op? redirect?]
   (when (and (db/page-exists? from-page-name)
   (when (and (db/page-exists? from-page-name)
              (db/page-exists? to-page-name)
              (db/page-exists? to-page-name)
              (not= from-page-name to-page-name))
              (not= from-page-name to-page-name))
@@ -97,12 +97,12 @@
                                                     (util/get-page-original-name from-page)
                                                     (util/get-page-original-name from-page)
                                                     (util/get-page-original-name to-page)))
                                                     (util/get-page-original-name to-page)))
 
 
-
     (page-common-handler/delete! from-page-name nil :redirect-to-home? false :persist-op? persist-op?)
     (page-common-handler/delete! from-page-name nil :redirect-to-home? false :persist-op? persist-op?)
 
 
-    (route-handler/redirect! {:to          :page
-                              :push        false
-                              :path-params {:name to-page-name}})))
+    (when redirect?
+      (route-handler/redirect! {:to          :page
+                                :push        false
+                                :path-params {:name to-page-name}}))))
 
 
 (defn rename!
 (defn rename!
   ([old-name new-name]
   ([old-name new-name]
@@ -114,30 +114,39 @@
          old-page-name (util/page-name-sanity-lc old-name)
          old-page-name (util/page-name-sanity-lc old-name)
          page-e (db/entity [:block/name old-page-name])
          page-e (db/entity [:block/name old-page-name])
          new-page-name (util/page-name-sanity-lc new-name)
          new-page-name (util/page-name-sanity-lc new-name)
+         new-page-e (db/entity [:block/name new-page-name])
          name-changed? (not= old-name new-name)]
          name-changed? (not= old-name new-name)]
-     (if (and old-name
-              new-name
-              (not (string/blank? new-name))
-              name-changed?)
-       (cond
-         (= old-page-name new-page-name) ; case changed
-         (db/transact! repo
-                       [{:db/id (:db/id page-e)
-                         :block/original-name new-name}]
-                       {:persist-op? persist-op?})
+     (cond
+       (string/blank? new-name)
+       (do
+         (notification/show! "Please use a valid name, empty name is not allowed!" :error)
+         :invalid-empty-name)
+
+       (and page-e new-page-e
+            (or (contains? (:block/type page-e) "whiteboard")
+                (contains? (:block/type new-page-e) "whiteboard")))
+       (do
+         (notification/show! "Can't merge whiteboard pages" :error)
+         :merge-whiteboard-pages)
 
 
-         (and (not= old-page-name new-page-name)
-              (db/entity [:block/name new-page-name])) ; merge page
-         (based-merge-pages! old-page-name new-page-name persist-op?)
+       (and old-name new-name name-changed?)
+       (do
+         (cond
+          (= old-page-name new-page-name) ; case changed
+          (db/transact! repo
+                        [{:db/id (:db/id page-e)
+                          :block/original-name new-name}]
+                        {:persist-op? persist-op?})
 
 
-         :else                          ; rename
-         (page-common-handler/create! new-name
-                                      {:rename? true
-                                       :uuid (:block/uuid page-e)
-                                       :redirect? redirect?
-                                       :create-first-block? false
-                                       :persist-op? persist-op?}))
+          (and (not= old-page-name new-page-name)
+               (db/entity [:block/name new-page-name])) ; merge page
+          (based-merge-pages! old-page-name new-page-name persist-op? redirect?)
 
 
-       (when (string/blank? new-name)
-         (notification/show! "Please use a valid name, empty name is not allowed!" :error)))
-     (ui-handler/re-render-root!))))
+          :else                          ; rename
+          (page-common-handler/create! new-name
+                                       {:rename? true
+                                        :uuid (:block/uuid page-e)
+                                        :redirect? redirect?
+                                        :create-first-block? false
+                                        :persist-op? persist-op?}))
+         (ui-handler/re-render-root!))))))

+ 56 - 53
src/main/frontend/handler/db_based/property.cljs

@@ -12,6 +12,7 @@
             [logseq.graph-parser.util :as gp-util]
             [logseq.graph-parser.util :as gp-util]
             [logseq.db.sqlite.util :as sqlite-util]
             [logseq.db.sqlite.util :as sqlite-util]
             [logseq.db.frontend.property.type :as db-property-type]
             [logseq.db.frontend.property.type :as db-property-type]
+            [logseq.db.frontend.property.util :as db-property-util]
             [malli.util :as mu]
             [malli.util :as mu]
             [malli.error :as me]
             [malli.error :as me]
             [logseq.graph-parser.util.page-ref :as page-ref]
             [logseq.graph-parser.util.page-ref :as page-ref]
@@ -20,15 +21,15 @@
 ;; schema -> type, cardinality, object's class
 ;; schema -> type, cardinality, object's class
 ;;           min, max -> string length, number range, cardinality size limit
 ;;           min, max -> string length, number range, cardinality size limit
 
 
-(defn builtin-schema-types
-  "A frontend version of builtin-schema-types that adds the current database to
+(defn built-in-validation-schemas
+  "A frontend version of built-in-validation-schemas that adds the current database to
    schema fns"
    schema fns"
   [property & {:keys [new-closed-value?]
   [property & {:keys [new-closed-value?]
                :or {new-closed-value? false}}]
                :or {new-closed-value? false}}]
   (into {}
   (into {}
         (map (fn [[property-type property-val-schema]]
         (map (fn [[property-type property-val-schema]]
                (cond
                (cond
-                 (db-property-type/closed-values-schema-types property-type)
+                 (db-property-type/closed-value-property-types property-type)
                  (let [[_ schema-opts schema-fn] property-val-schema
                  (let [[_ schema-opts schema-fn] property-val-schema
                        schema-fn' (if (db-property-type/property-types-with-db property-type) #(schema-fn (db/get-db) %) schema-fn)]
                        schema-fn' (if (db-property-type/property-types-with-db property-type) #(schema-fn (db/get-db) %) schema-fn)]
                    [property-type [:fn
                    [property-type [:fn
@@ -39,7 +40,7 @@
                    [property-type [:fn schema-opts #(schema-fn (db/get-db) %)]])
                    [property-type [:fn schema-opts #(schema-fn (db/get-db) %)]])
                  :else
                  :else
                  [property-type property-val-schema]))
                  [property-type property-val-schema]))
-             db-property-type/builtin-schema-types)))
+             db-property-type/built-in-validation-schemas)))
 
 
 (defn- fail-parse-long
 (defn- fail-parse-long
   [v-str]
   [v-str]
@@ -122,7 +123,7 @@
     (when (and multiple-values? (seq values))
     (when (and multiple-values? (seq values))
       (let [infer-schema (when-not type (infer-schema-from-input-string (first values)))
       (let [infer-schema (when-not type (infer-schema-from-input-string (first values)))
             property-type (or type infer-schema :default)
             property-type (or type infer-schema :default)
-            schema (get (builtin-schema-types property) property-type)
+            schema (get (built-in-validation-schemas property) property-type)
             properties (:block/properties block)
             properties (:block/properties block)
             values' (try
             values' (try
                       (set (map #(convert-property-input-string property-type %) values))
                       (set (map #(convert-property-input-string property-type %) values))
@@ -162,7 +163,7 @@
                                   :block/refs refs}]
                                   :block/refs refs}]
                                 {:outliner-op :save-block}))))))))))
                                 {:outliner-op :save-block}))))))))))
 
 
-(defn resolve-tag
+(defn- resolve-tag
   "Change `v` to a tag's UUID if v is a string tag, e.g. `#book`"
   "Change `v` to a tag's UUID if v is a string tag, e.g. `#book`"
   [v]
   [v]
   (when (and (string? v)
   (when (and (string? v)
@@ -199,7 +200,7 @@
         (when (some? v)
         (when (some? v)
           (let [infer-schema (when-not type (infer-schema-from-input-string v))
           (let [infer-schema (when-not type (infer-schema-from-input-string v))
                 property-type (or type infer-schema :default)
                 property-type (or type infer-schema :default)
-                schema (get (builtin-schema-types property) property-type)
+                schema (get (built-in-validation-schemas property) property-type)
                 properties (:block/properties block)
                 properties (:block/properties block)
                 value (get properties property-uuid)
                 value (get properties property-uuid)
                 v* (try
                 v* (try
@@ -477,7 +478,7 @@
                                      {})
                                      {})
                 (remove-block-property! repo (:block/uuid block) property-id)))))))))
                 (remove-block-property! repo (:block/uuid block) property-id)))))))))
 
 
-(defn replace-key-with-id!
+(defn replace-key-with-id
   "Notice: properties need to be created first"
   "Notice: properties need to be created first"
   [m]
   [m]
   (zipmap
   (zipmap
@@ -592,6 +593,21 @@
     {:page page-tx
     {:page page-tx
      :blocks [parent child-1]}))
      :blocks [parent child-1]}))
 
 
+(defn create-property-text-block!
+  [block property value parse-block {:keys [class-schema?]}]
+  (let [repo (state/get-current-repo)
+        {:keys [page blocks]} (property-create-new-block block property value parse-block)
+        first-block (first blocks)
+        last-block-id (:block/uuid (last blocks))
+        class? (contains? (:block/type block) "class")
+        property-key (:block/original-name property)]
+    (db/transact! repo (if page (cons page blocks) blocks) {:outliner-op :insert-blocks})
+    (when property-key
+      (if (and class? class-schema?)
+        (class-add-property! repo (:block/uuid block) property-key)
+        (set-block-property! repo (:block/uuid block) property-key (:block/uuid first-block) {})))
+    last-block-id))
+
 (defn property-create-new-block-from-template
 (defn property-create-new-block-from-template
   [block property template]
   [block property template]
   (let [current-page-id (:block/uuid (or (:block/page block) block))
   (let [current-page-id (:block/uuid (or (:block/page block) block))
@@ -621,32 +637,21 @@
     {:page page-tx
     {:page page-tx
      :blocks [new-block]}))
      :blocks [new-block]}))
 
 
-(defn- closed-value-new-block
-  [page-id block-id value property]
-  {:block/type #{"closed value"}
-   :block/format :markdown
-   :block/uuid block-id
-   :block/page page-id
-   :block/metadata {:created-from-property (:block/uuid property)}
-   :block/schema {:value value}
-   :block/parent page-id})
-
 (defn- get-property-hidden-page
 (defn- get-property-hidden-page
   [property]
   [property]
-  (let [page-name (str "$$$" (:block/uuid property))
-        page-entity (db/entity [:block/name page-name])]
-    (or page-entity
-        (-> (block/page-name->map page-name true)
-            (assoc :block/type #{"hidden"}
-                   :block/format :markdown)))))
+  (let [page-name (str db-property-util/hidden-page-name-prefix (:block/uuid property))]
+    (or (db/entity [:block/name page-name])
+        (db-property-util/build-property-hidden-page property))))
 
 
 (defn upsert-closed-value
 (defn upsert-closed-value
   "id should be a block UUID or nil"
   "id should be a block UUID or nil"
-  [property {:keys [id value icon description]}]
+  [property {:keys [id value icon description]
+             :or {description ""}}]
   (assert (or (nil? id) (uuid? id)))
   (assert (or (nil? id) (uuid? id)))
   (let [property-type (get-in property [:block/schema :type] :default)]
   (let [property-type (get-in property [:block/schema :type] :default)]
-    (when (contains? db-property-type/closed-values-schema-types property-type)
-      (let [value (if (string? value) (string/trim value) value)
+    (when (contains? db-property-type/closed-value-property-types property-type)
+      (let [property (db/entity (:db/id property))
+            value (if (string? value) (string/trim value) value)
             property-schema (:block/schema property)
             property-schema (:block/schema property)
             closed-values (:values property-schema)
             closed-values (:values property-schema)
             block-values (map (fn [id] (db/entity [:block/uuid id])) closed-values)
             block-values (map (fn [id] (db/entity [:block/uuid id])) closed-values)
@@ -659,12 +664,9 @@
             block (when id (db/entity [:block/uuid id]))
             block (when id (db/entity [:block/uuid id]))
             value-block (when (uuid? value) (db/entity [:block/uuid value]))
             value-block (when (uuid? value) (db/entity [:block/uuid value]))
             validate-message (validate-property-value
             validate-message (validate-property-value
-                              (get (builtin-schema-types property {:new-closed-value? true}) property-type)
+                              (get (built-in-validation-schemas property {:new-closed-value? true}) property-type)
                               resolved-value)]
                               resolved-value)]
         (cond
         (cond
-          (nil? resolved-value)
-          nil
-
           (some (fn [b] (and (= resolved-value (or (db-pu/property-value-when-closed b)
           (some (fn [b] (and (= resolved-value (or (db-pu/property-value-when-closed b)
                                                    (:block/uuid b)))
                                                    (:block/uuid b)))
                              (not= id (:block/uuid b)))) block-values)
                              (not= id (:block/uuid b)))) block-values)
@@ -677,6 +679,9 @@
             (notification/show! validate-message :warning)
             (notification/show! validate-message :warning)
             :value-invalid)
             :value-invalid)
 
 
+          (nil? resolved-value)
+          nil
+
           (:block/name value-block)             ; page
           (:block/name value-block)             ; page
           (let [new-values (vec (conj closed-values value))]
           (let [new-values (vec (conj closed-values value))]
             {:block-id value
             {:block-id value
@@ -703,16 +708,10 @@
                           (let [page (get-property-hidden-page property)
                           (let [page (get-property-hidden-page property)
                                 page-tx (when-not (e/entity? page) page)
                                 page-tx (when-not (e/entity? page) page)
                                 page-id [:block/uuid (:block/uuid page)]
                                 page-id [:block/uuid (:block/uuid page)]
-                                new-block (cond->
-                                           (closed-value-new-block page-id block-id value property)
-                                            icon
-                                            (assoc :block/properties {icon-id icon})
-
-                                            description
-                                            (update :block/schema assoc :description description)
-
-                                            true
-                                            sqlite-util/block-with-timestamps)
+                                new-block (db-property-util/build-closed-value-block
+                                           block-id resolved-value page-id property {:icon-id icon-id
+                                                                                     :icon icon
+                                                                                     :description description})
                                 new-values (vec (conj closed-values block-id))]
                                 new-values (vec (conj closed-values block-id))]
                             (->> (cons page-tx [new-block
                             (->> (cons page-tx [new-block
                                                 {:db/id (:db/id property)
                                                 {:db/id (:db/id property)
@@ -723,6 +722,7 @@
              :tx-data tx-data}))))))
              :tx-data tx-data}))))))
 
 
 (defn add-existing-values-to-closed-values!
 (defn add-existing-values-to-closed-values!
+  "Adds existing values as closed values and returns their new block uuids"
   [property values]
   [property values]
   (when (seq values)
   (when (seq values)
     (let [property-id (:block/uuid property)
     (let [property-id (:block/uuid property)
@@ -731,11 +731,12 @@
           page-tx (when-not (e/entity? page) page)
           page-tx (when-not (e/entity? page) page)
           page-id (:block/uuid page)
           page-id (:block/uuid page)
           closed-value-blocks (map (fn [value]
           closed-value-blocks (map (fn [value]
-                                     (sqlite-util/block-with-timestamps
-                                      (closed-value-new-block [:block/uuid page-id]
-                                                              (db/new-block-id)
-                                                              value
-                                                              property)))
+                                     (db-property-util/build-closed-value-block
+                                      (db/new-block-id)
+                                      value
+                                      [:block/uuid page-id]
+                                      property
+                                      {}))
                                    (remove string/blank? values))
                                    (remove string/blank? values))
           value->block-id (zipmap
           value->block-id (zipmap
                            (map #(get-in % [:block/schema :value]) closed-value-blocks)
                            (map #(get-in % [:block/schema :value]) closed-value-blocks)
@@ -758,18 +759,20 @@
                                :block/properties (assoc properties property-id (get value->block-id value))})))
                                :block/properties (assoc properties property-id (get value->block-id value))})))
                         block-values))]
                         block-values))]
       (db/transact! (state/get-current-repo) tx-data
       (db/transact! (state/get-current-repo) tx-data
-        {:outliner-op :insert-blocks}))))
+                    {:outliner-op :insert-blocks})
+      new-value-ids)))
 
 
-(defn delete-closed-value
-  [property item]
-  (if (seq (:block/_refs item))
+(defn delete-closed-value!
+  [property value-block]
+  (if (seq (:block/_refs value-block))
     (notification/show! "The choice can't be deleted because it's still used." :warning)
     (notification/show! "The choice can't be deleted because it's still used." :warning)
-    (let [schema (:block/schema property)
-          tx-data [[:db/retractEntity (:db/id item)]
+    (let [property (db/entity (:db/id property))
+          schema (:block/schema property)
+          tx-data [[:db/retractEntity (:db/id value-block)]
                    {:db/id (:db/id property)
                    {:db/id (:db/id property)
                     :block/schema (update schema :values
                     :block/schema (update schema :values
                                           (fn [values]
                                           (fn [values]
-                                            (vec (remove #{(:block/uuid item)} values))))}]]
+                                            (vec (remove #{(:block/uuid value-block)} values))))}]]
       (db/transact! tx-data))))
       (db/transact! tx-data))))
 
 
 (defn get-property-block-created-block
 (defn get-property-block-created-block

+ 1 - 2
src/main/frontend/handler/db_based/recent.cljs

@@ -23,6 +23,5 @@
        (distinct)
        (distinct)
        (map (fn [id]
        (map (fn [id]
               (let [e (db/entity [:block/uuid id])]
               (let [e (db/entity [:block/uuid id])]
-                (or (:block/original-name e)
-                    (:block/uuid e)))))
+                (:block/original-name e))))
        (remove string/blank?)))
        (remove string/blank?)))

+ 13 - 12
src/main/frontend/handler/editor.cljs

@@ -64,8 +64,7 @@
             [promesa.core :as p]
             [promesa.core :as p]
             [rum.core :as rum]
             [rum.core :as rum]
             [frontend.handler.db-based.property :as db-property-handler]
             [frontend.handler.db-based.property :as db-property-handler]
-            [frontend.fs.capacitor-fs :as capacitor-fs]
-            [frontend.db.model :as model]))
+            [frontend.fs.capacitor-fs :as capacitor-fs]))
 
 
 ;; FIXME: should support multiple images concurrently uploading
 ;; FIXME: should support multiple images concurrently uploading
 
 
@@ -551,7 +550,7 @@
                             (wrap-parse-block)
                             (wrap-parse-block)
                             (assoc :block/uuid (or custom-uuid (db/new-block-id))))
                             (assoc :block/uuid (or custom-uuid (db/new-block-id))))
               new-block (if (and db-based? (seq properties))
               new-block (if (and db-based? (seq properties))
-                          (assoc new-block :block/properties (db-property-handler/replace-key-with-id! properties))
+                          (assoc new-block :block/properties (db-property-handler/replace-key-with-id properties))
                           new-block)
                           new-block)
               new-block (merge new-block other-attrs)
               new-block (merge new-block other-attrs)
               [block-m sibling?] (cond
               [block-m sibling?] (cond
@@ -1432,7 +1431,8 @@
   "Make asset URL for UI element, to fill img.src"
   "Make asset URL for UI element, to fill img.src"
   [path] ;; path start with "/assets"(editor) or compatible for "../assets"(whiteboards)
   [path] ;; path start with "/assets"(editor) or compatible for "../assets"(whiteboards)
   (if config/publishing?
   (if config/publishing?
-    path
+    ;; Relative path needed since assets are not under '/' if published graph is not under '/'
+    (string/replace-first path #"^/" "")
     (let [repo      (state/get-current-repo)
     (let [repo      (state/get-current-repo)
           repo-dir  (config/get-repo-dir repo)
           repo-dir  (config/get-repo-dir repo)
           ;; Hack for path calculation
           ;; Hack for path calculation
@@ -1448,7 +1448,7 @@
         (assets-handler/resolve-asset-real-path-url (state/get-current-repo) path)
         (assets-handler/resolve-asset-real-path-url (state/get-current-repo) path)
 
 
         (util/electron?)
         (util/electron?)
-        (path/path-join "assets://" full-path)
+        (path/prepend-protocol "assets:" full-path)
 
 
         (mobile-util/native-platform?)
         (mobile-util/native-platform?)
         (mobile-util/convert-file-src full-path)
         (mobile-util/convert-file-src full-path)
@@ -1629,18 +1629,19 @@
         editing-page (and block
         editing-page (and block
                           (when-let [page-id (:db/id (:block/page block))]
                           (when-let [page-id (:db/id (:block/page block))]
                             (:block/name (db/entity page-id))))
                             (:block/name (db/entity page-id))))
-        pages (search/page-search q 100)]
+        pages (search/page-search q)]
     (if editing-page
     (if editing-page
       ;; To prevent self references
       ;; To prevent self references
       (remove (fn [p] (= (util/page-name-sanity-lc p) editing-page)) pages)
       (remove (fn [p] (= (util/page-name-sanity-lc p) editing-page)) pages)
       pages)))
       pages)))
 
 
-(defn get-matched-classes
-  "Return matched class names"
-  [q]
-  (let [classes (->> (db-model/get-all-classes (state/get-current-repo))
-                     (map first))]
-    (search/fuzzy-search classes q {:limit 100})))
+(comment
+  (defn get-matched-classes
+   "Return matched class names"
+   [q]
+   (let [classes (->> (db-model/get-all-classes (state/get-current-repo))
+                      (map first))]
+     (search/fuzzy-search classes q {:limit 100}))))
 
 
 (defn get-matched-blocks
 (defn get-matched-blocks
   [q block-id]
   [q block-id]

+ 4 - 4
src/main/frontend/handler/events.cljs

@@ -23,7 +23,7 @@
             [frontend.components.whiteboard :as whiteboard]
             [frontend.components.whiteboard :as whiteboard]
             [frontend.components.user.login :as login]
             [frontend.components.user.login :as login]
             [frontend.components.repo :as repo]
             [frontend.components.repo :as repo]
-            [frontend.components.page :as page]
+            [frontend.components.db-based.page :as db-page]
             [frontend.config :as config]
             [frontend.config :as config]
             [frontend.context.i18n :refer [t]]
             [frontend.context.i18n :refer [t]]
             [frontend.db :as db]
             [frontend.db :as db]
@@ -139,7 +139,7 @@
     (if empty-graph?
     (if empty-graph?
       (route-handler/redirect! {:to :import :query-params {:from "picker"}})
       (route-handler/redirect! {:to :import :query-params {:from "picker"}})
       (route-handler/redirect-to-home!)))
       (route-handler/redirect-to-home!)))
-  (when-let [dir-name (config/get-repo-dir repo)]
+  (when-let [dir-name (and (not (config/db-based-graph? repo)) (config/get-repo-dir repo))]
     (fs/watch-dir! dir-name))
     (fs/watch-dir! dir-name))
   (file-sync-restart!))
   (file-sync-restart!))
 
 
@@ -168,7 +168,7 @@
        (srs/update-cards-due-count!)
        (srs/update-cards-due-count!)
        (state/pub-event! [:graph/ready graph])
        (state/pub-event! [:graph/ready graph])
        (file-sync-restart!)
        (file-sync-restart!)
-       (when-let [dir-name (config/get-repo-dir graph)]
+       (when-let [dir-name (and (not (config/db-based-graph? graph)) (config/get-repo-dir graph))]
          (fs/watch-dir! dir-name))))))
          (fs/watch-dir! dir-name))))))
 
 
 ;; Parameters for the `persist-db` function, to show the notification messages
 ;; Parameters for the `persist-db` function, to show the notification messages
@@ -832,7 +832,7 @@
   (state/set-modal!
   (state/set-modal!
    #(vector :<>
    #(vector :<>
             (class-component/configure page)
             (class-component/configure page)
-            (page/page-properties page {:configure? true}))
+            (db-page/page-properties page {:configure? true}))
    {:id :page-configure
    {:id :page-configure
     :label "page-configure"
     :label "page-configure"
     :container-overflow-visible? true}))
     :container-overflow-visible? true}))

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

@@ -253,7 +253,7 @@
 (defn watch-for-current-graph-dir!
 (defn watch-for-current-graph-dir!
   []
   []
   (when-let [repo (state/get-current-repo)]
   (when-let [repo (state/get-current-repo)]
-    (when-let [dir (config/get-repo-dir repo)]
+    (when-let [dir (and (not (config/db-based-graph? repo)) (config/get-repo-dir repo))]
       ;; An unwatch shouldn't be needed on startup. However not having this
       ;; An unwatch shouldn't be needed on startup. However not having this
       ;; after an app refresh can cause stale page data to load
       ;; after an app refresh can cause stale page data to load
       (fs/unwatch-dir! dir)
       (fs/unwatch-dir! dir)

+ 16 - 7
src/main/frontend/handler/page.cljs

@@ -34,7 +34,9 @@
             [logseq.graph-parser.util.page-ref :as page-ref]
             [logseq.graph-parser.util.page-ref :as page-ref]
             [promesa.core :as p]
             [promesa.core :as p]
             [logseq.common.path :as path]
             [logseq.common.path :as path]
-            [frontend.handler.property.util :as pu]))
+            [frontend.handler.property.util :as pu]
+            [electron.ipc :as ipc]
+            [frontend.context.i18n :refer [t]]))
 
 
 (def create! page-common-handler/create!)
 (def create! page-common-handler/create!)
 (def delete! page-common-handler/delete!)
 (def delete! page-common-handler/delete!)
@@ -208,7 +210,11 @@
       (fn [chosen e]
       (fn [chosen e]
         (util/stop e)
         (util/stop e)
         (state/clear-editor-action!)
         (state/clear-editor-action!)
-        (let [wrapped? (= page-ref/left-brackets (gp-util/safe-subs edit-content (- pos 2) pos))
+        (let [class? (string/starts-with? chosen (t :new-class))
+              chosen (-> chosen
+                         (string/replace-first (str (t :new-class) " ") "")
+                         (string/replace-first (str (t :new-page) " ") ""))
+              wrapped? (= page-ref/left-brackets (gp-util/safe-subs edit-content (- pos 2) pos))
               wrapped-tag (if (and (util/safe-re-find #"\s+" chosen) (not wrapped?))
               wrapped-tag (if (and (util/safe-re-find #"\s+" chosen) (not wrapped?))
                             (page-ref/->page-ref chosen)
                             (page-ref/->page-ref chosen)
                             chosen)
                             chosen)
@@ -227,10 +233,13 @@
                   (when-not tag-entity
                   (when-not tag-entity
                     (create! tag {:redirect? false
                     (create! tag {:redirect? false
                                   :create-first-block? false
                                   :create-first-block? false
-                                  :class? true}))
-                  (let [tag-entity (or tag-entity (db/entity [:block/name (util/page-name-sanity-lc tag)]))]
-                    (db/transact! [[:db/add [:block/uuid (:block/uuid edit-block)] :block/tags (:db/id tag-entity)]
-                                   [:db/add [:block/uuid (:block/uuid edit-block)] :block/refs (:db/id tag-entity)]]))))))
+                                  :class? class?}))
+                  (when class?
+                    (let [repo (state/get-current-repo)
+                          tag-entity (or tag-entity (db/entity [:block/name (util/page-name-sanity-lc tag)]))
+                          tx-data [[:db/add [:block/uuid (:block/uuid edit-block)] :block/tags (:db/id tag-entity)]
+                                   [:db/add [:block/uuid (:block/uuid edit-block)] :block/refs (:db/id tag-entity)]]]
+                      (db/transact! repo tx-data {:outliner-op :save-block})))))))
 
 
           (editor-handler/insert-command! id
           (editor-handler/insert-command! id
                                           (str "#" wrapped-tag)
                                           (str "#" wrapped-tag)
@@ -319,7 +328,7 @@
   (if-let [file-rpath (and (util/electron?) (page-util/get-page-file-rpath))]
   (if-let [file-rpath (and (util/electron?) (page-util/get-page-file-rpath))]
     (let [repo-dir (config/get-repo-dir (state/get-current-repo))
     (let [repo-dir (config/get-repo-dir (state/get-current-repo))
           file-fpath (path/path-join repo-dir file-rpath)]
           file-fpath (path/path-join repo-dir file-rpath)]
-      (js/window.apis.showItemInFolder file-fpath))
+      (ipc/ipc "openFileInFolder" file-fpath))
     (notification/show! "No file found" :warning)))
     (notification/show! "No file found" :warning)))
 
 
 (defn copy-page-url
 (defn copy-page-url

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

@@ -65,8 +65,8 @@
       (db-property-handler/batch-set-property! repo block-ids key value))
       (db-property-handler/batch-set-property! repo block-ids key value))
     (file-property-handler/batch-set-block-property! block-ids key value)))
     (file-property-handler/batch-set-block-property! block-ids key value)))
 
 
-(defn replace-key-with-id!
+(defn replace-key-with-id
   [repo m]
   [repo m]
   (if (config/db-based-graph? repo)
   (if (config/db-based-graph? repo)
-    (db-property-handler/replace-key-with-id! m)
-    m))
+    (db-property-handler/replace-key-with-id m)
+    m))

+ 7 - 5
src/main/frontend/handler/property/util.cljs

@@ -11,10 +11,12 @@
 (defn lookup
 (defn lookup
   "Get the value of coll's (a map) `key`. For file and db graphs"
   "Get the value of coll's (a map) `key`. For file and db graphs"
   [coll key]
   [coll key]
-  (let [repo (state/get-current-repo)]
-    (if (and (config/db-based-graph? repo)
-             (keyword? key))
-      (when-let [property (db/entity repo [:block/name (gp-util/page-name-sanity-lc (name key))])]
+  (let [repo (state/get-current-repo)
+        property-name (if (keyword? key)
+                        (name key)
+                        key)]
+    (if (config/db-based-graph? repo)
+      (when-let [property (db/entity repo [:block/name (gp-util/page-name-sanity-lc property-name)])]
         (get coll (:block/uuid property)))
         (get coll (:block/uuid property)))
       (get coll key))))
       (get coll key))))
 
 
@@ -47,4 +49,4 @@
   (get-property block :logseq.tldraw.page))
   (get-property block :logseq.tldraw.page))
 
 
 (defn shape-block? [block]
 (defn shape-block? [block]
-  (= :whiteboard-shape (get-property block :ls-type)))
+  (= :whiteboard-shape (get-property block :ls-type)))

+ 10 - 5
src/main/frontend/handler/recent.cljs

@@ -3,13 +3,18 @@
   (:require [frontend.handler.db-based.recent :as db-based]
   (:require [frontend.handler.db-based.recent :as db-based]
             [frontend.handler.file-based.recent :as file-recent-handler]
             [frontend.handler.file-based.recent :as file-recent-handler]
             [frontend.config :as config]
             [frontend.config :as config]
-            [frontend.state :as state]))
+            [frontend.state :as state]
+            [frontend.db.model :as model]))
 
 
 (defn add-page-to-recent!
 (defn add-page-to-recent!
-  [repo page click-from-recent?]
-  (if (config/db-based-graph? repo)
-    (db-based/add-page-to-recent! page click-from-recent?)
-    (file-recent-handler/add-page-to-recent! repo page click-from-recent?)))
+  [repo page-name-or-block-uuid click-from-recent?]
+  (let [page-name (if (uuid? page-name-or-block-uuid)
+                    (when-let [block (model/get-block-by-uuid page-name-or-block-uuid)]
+                      (get-in block [:block/page :block/original-name]))
+                    page-name-or-block-uuid)]
+    (if (config/db-based-graph? repo)
+    (db-based/add-page-to-recent! page-name click-from-recent?)
+    (file-recent-handler/add-page-to-recent! repo page-name click-from-recent?))))
 
 
 (defn get-recent-pages
 (defn get-recent-pages
   []
   []

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

@@ -6,6 +6,7 @@
             [frontend.context.i18n :refer [t]]
             [frontend.context.i18n :refer [t]]
             [frontend.date :as date]
             [frontend.date :as date]
             [frontend.db :as db]
             [frontend.db :as db]
+            [frontend.db.react :as react]
             [frontend.db.restore :as db-restore]
             [frontend.db.restore :as db-restore]
             [logseq.db.frontend.schema :as db-schema]
             [logseq.db.frontend.schema :as db-schema]
             [frontend.fs :as fs]
             [frontend.fs :as fs]
@@ -28,7 +29,7 @@
             [frontend.db.persist :as db-persist]
             [frontend.db.persist :as db-persist]
             [logseq.graph-parser :as graph-parser]
             [logseq.graph-parser :as graph-parser]
             [logseq.graph-parser.config :as gp-config]
             [logseq.graph-parser.config :as gp-config]
-            [logseq.db.sqlite.util :as sqlite-util]
+            [logseq.db.sqlite.create-graph :as sqlite-create-graph]
             [electron.ipc :as ipc]
             [electron.ipc :as ipc]
             [cljs-bean.core :as bean]
             [cljs-bean.core :as bean]
             [clojure.core.async :as async]
             [clojure.core.async :as async]
@@ -36,7 +37,6 @@
             [medley.core :as medley]
             [medley.core :as medley]
             [logseq.common.path :as path]
             [logseq.common.path :as path]
             [logseq.common.config :as common-config]
             [logseq.common.config :as common-config]
-            [frontend.db.react :as react]
             [frontend.db.listener :as db-listener]
             [frontend.db.listener :as db-listener]
             [frontend.db.rtc.op-mem-layer :as op-mem-layer]))
             [frontend.db.rtc.op-mem-layer :as op-mem-layer]))
 
 
@@ -549,7 +549,7 @@
           _ (route-handler/redirect-to-home!)
           _ (route-handler/redirect-to-home!)
           _ (db/transact! full-graph-name [(react/kv :db/type "db")
           _ (db/transact! full-graph-name [(react/kv :db/type "db")
                                            (react/kv :schema/version db-schema/version)])
                                            (react/kv :schema/version db-schema/version)])
-          initial-data (sqlite-util/build-db-initial-data config/config-default-content)
+          initial-data (sqlite-create-graph/build-db-initial-data config/config-default-content)
           _ (db/transact! full-graph-name initial-data)
           _ (db/transact! full-graph-name initial-data)
           _ (repo-config-handler/set-repo-config-state! full-graph-name config/config-default-content)
           _ (repo-config-handler/set-repo-config-state! full-graph-name config/config-default-content)
           ;; TODO: handle global graph
           ;; TODO: handle global graph

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

@@ -15,7 +15,8 @@
             [rum.core :as rum]
             [rum.core :as rum]
             [electron.ipc :as ipc]
             [electron.ipc :as ipc]
             [promesa.core :as p]
             [promesa.core :as p]
-            [logseq.common.path :as path]))
+            [logseq.common.path :as path]
+            [frontend.db.react :as react]))
 
 
 ;; sidebars
 ;; sidebars
 (def *right-sidebar-resized-at (atom (js/Date.now)))
 (def *right-sidebar-resized-at (atom (js/Date.now)))
@@ -80,6 +81,8 @@
    (re-render-root! {}))
    (re-render-root! {}))
   ([_opts]
   ([_opts]
    {:post [(nil? %)]}
    {:post [(nil? %)]}
+   (doseq [component (keys @react/query-components)]
+     (rum/request-render component))
    (when-let [component (state/get-root-component)]
    (when-let [component (state/get-root-component)]
      (rum/request-render component))
      (rum/request-render component))
    nil))
    nil))

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

@@ -134,14 +134,33 @@
           (and (<= 400 (:status resp))
           (and (<= 400 (:status resp))
                (> 500 (:status resp)))
                (> 500 (:status resp)))
           ;; invalid refresh-token
           ;; invalid refresh-token
-          (clear-tokens)
+          (do
+            (prn :debug :refresh-token-failed
+                 :status (:status resp)
+                 :user-id (user-uuid)
+                 :refresh-token refresh-token
+                 :resp resp)
+            (state/pub-event! [:instrument {:type :refresh-token-failed
+                                            :payload {:status (:status resp)
+                                                      :user-id (user-uuid)
+                                                      :refresh-token refresh-token
+                                                      :resp resp}}])
+            (when (and (= 400 (:status resp))
+                       (= (:error (:body resp)) "invalid_grant"))
+              (clear-tokens)))
 
 
           ;; e.g. api return 500, server internal error
           ;; e.g. api return 500, server internal error
           ;; we shouldn't clear tokens if they aren't expired yet
           ;; we shouldn't clear tokens if they aren't expired yet
           ;; the `refresh-tokens-loop` will retry soon
           ;; the `refresh-tokens-loop` will retry soon
           (and (not (http/unexceptional-status? (:status resp)))
           (and (not (http/unexceptional-status? (:status resp)))
                (not (-> (state/get-auth-id-token) parse-jwt expired?)))
                (not (-> (state/get-auth-id-token) parse-jwt expired?)))
-          nil                           ; do nothing
+          (do
+            (prn :debug :refresh-token-failed
+                 :status (:status resp)
+                 :body (:body resp)
+                 :error-code (:error-code resp)
+                 :error-text (:error-text resp))
+            nil)                           ; do nothing
 
 
           (not (http/unexceptional-status? (:status resp)))
           (not (http/unexceptional-status? (:status resp)))
           (notification/show! "exceptional status when refresh-token" :warning true)
           (notification/show! "exceptional status when refresh-token" :warning true)
@@ -217,14 +236,15 @@
 
 
 (defn <ensure-id&access-token
 (defn <ensure-id&access-token
   []
   []
-  (go
-    (when (or (nil? (state/get-auth-id-token))
-              (-> (state/get-auth-id-token) parse-jwt almost-expired-or-expired?))
-      (debug/pprint (str "refresh tokens... " (tc/to-string (t/now))))
-      (<! (<refresh-id-token&access-token))
-      (when (or (nil? (state/get-auth-id-token))
-                (-> (state/get-auth-id-token) parse-jwt expired?))
-        (ex-info "empty or expired token and refresh failed" {:anom :expired-token})))))
+  (let [id-token (state/get-auth-id-token)]
+    (go
+      (when (or (nil? id-token)
+                (-> id-token parse-jwt almost-expired-or-expired?))
+        (debug/pprint (str "refresh tokens... " (tc/to-string (t/now))))
+        (<! (<refresh-id-token&access-token))
+        (when (or (nil? (state/get-auth-id-token))
+                  (-> (state/get-auth-id-token) parse-jwt expired?))
+          (ex-info "empty or expired token and refresh failed" {:anom :expired-token}))))))
 
 
 (defn <user-uuid
 (defn <user-uuid
   []
   []

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

@@ -37,12 +37,6 @@
         additional-props (gp-whiteboard/with-whiteboard-block-props block page-name)]
         additional-props (gp-whiteboard/with-whiteboard-block-props block page-name)]
     (merge block additional-props)))
     (merge block additional-props)))
 
 
-(defn- get-whiteboard-clj [page-name]
-  (when (model/page-exists? page-name)
-    (let [page-block (model/get-page page-name)
-          blocks (:block/_page page-block)]
-      [page-block blocks])))
-
 (defn- build-shapes
 (defn- build-shapes
   [page-block blocks]
   [page-block blocks]
   (let [page-metadata (pu/get-property page-block :logseq.tldraw.page)
   (let [page-metadata (pu/get-property page-block :logseq.tldraw.page)

+ 1 - 0
src/main/frontend/mixins.cljs

@@ -68,6 +68,7 @@
                 nil)))))
                 nil)))))
 
 
 (defn on-key-up
 (defn on-key-up
+  "Caution: This mixin uses a different args than on-key-down"
   [state keycode-map all-handler]
   [state keycode-map all-handler]
   (listen state js/window "keyup"
   (listen state js/window "keyup"
           (fn [e]
           (fn [e]

+ 44 - 37
src/main/frontend/modules/outliner/core.cljs

@@ -77,25 +77,26 @@
 
 
 (defn- remove-orphaned-page-refs!
 (defn- remove-orphaned-page-refs!
   [db-id txs-state old-refs new-refs]
   [db-id txs-state old-refs new-refs]
-  (when (not= old-refs new-refs)
-    (let [new-refs (set (map (fn [ref]
-                               (or (:block/name ref)
-                                   (and (:db/id ref)
-                                        (:block/name (db/entity (:db/id ref)))))) new-refs))
-          old-pages (->> (map :db/id old-refs)
-                         (db-model/get-entities-by-ids)
-                         (remove (fn [e] (contains? new-refs (:block/name e))))
-                         (map :block/name)
-                         (remove nil?))
-          orphaned-pages (when (seq old-pages)
-                           (db-model/get-orphaned-pages {:pages old-pages
-                                                         :empty-ref-f (fn [page]
-                                                                        (let [refs (:block/_refs page)]
-                                                                          (or (zero? (count refs))
-                                                                              (= #{db-id} (set (map :db/id refs))))))}))]
-      (when (seq orphaned-pages)
-        (let [tx (mapv (fn [page] [:db/retractEntity (:db/id page)]) orphaned-pages)]
-          (swap! txs-state (fn [state] (vec (concat state tx)))))))))
+  (let [old-refs (remove #(some #{"class" "property"} (:block/type %)) old-refs)]
+    (when (not= old-refs new-refs)
+      (let [new-refs (set (map (fn [ref]
+                                 (or (:block/name ref)
+                                     (and (:db/id ref)
+                                          (:block/name (db/entity (:db/id ref)))))) new-refs))
+            old-pages (->> (map :db/id old-refs)
+                           (db-model/get-entities-by-ids)
+                           (remove (fn [e] (contains? new-refs (:block/name e))))
+                           (map :block/name)
+                           (remove nil?))
+            orphaned-pages (when (seq old-pages)
+                             (db-model/get-orphaned-pages {:pages old-pages
+                                                           :empty-ref-f (fn [page]
+                                                                          (let [refs (:block/_refs page)]
+                                                                            (or (zero? (count refs))
+                                                                                (= #{db-id} (set (map :db/id refs))))))}))]
+        (when (seq orphaned-pages)
+          (let [tx (mapv (fn [page] [:db/retractEntity (:db/id page)]) orphaned-pages)]
+            (swap! txs-state (fn [state] (vec (concat state tx))))))))))
 
 
 (defn- update-page-when-save-block
 (defn- update-page-when-save-block
   [txs-state block-entity m]
   [txs-state block-entity m]
@@ -150,8 +151,8 @@
                                         (:block/macros block-entity)))))))
                                         (:block/macros block-entity)))))))
 
 
 (defn- create-linked-page-when-save
 (defn- create-linked-page-when-save
-  [txs-state block-entity m structured-tags?]
-  (if structured-tags?
+  [txs-state block-entity m tags-has-class?]
+  (if tags-has-class?
     (let [content (state/get-edit-content)
     (let [content (state/get-edit-content)
           linked-page (some-> content mldoc/extract-plain)
           linked-page (some-> content mldoc/extract-plain)
           sanity-linked-page (some-> linked-page util/page-name-sanity-lc)
           sanity-linked-page (some-> linked-page util/page-name-sanity-lc)
@@ -222,7 +223,8 @@
   (when (config/db-based-graph? repo)
   (when (config/db-based-graph? repo)
     (let [refs (->> (rebuild-block-refs repo block (:block/properties block)
     (let [refs (->> (rebuild-block-refs repo block (:block/properties block)
                                         :skip-content-parsing? true)
                                         :skip-content-parsing? true)
-                    (concat (:block/refs m)))]
+                    (concat (:block/refs m))
+                    (concat (:block/tags m)))]
       (swap! txs-state (fn [txs] (concat txs [{:db/id (:db/id block)
       (swap! txs-state (fn [txs] (concat txs [{:db/id (:db/id block)
                                                :block/refs refs}]))))))
                                                :block/refs refs}]))))))
 
 
@@ -297,29 +299,34 @@
                 (dissoc :block/children :block/meta :block.temp/top? :block.temp/bottom?
                 (dissoc :block/children :block/meta :block.temp/top? :block.temp/bottom?
                         :block/title :block/body :block/level)
                         :block/title :block/body :block/level)
                 gp-util/remove-nils
                 gp-util/remove-nils
-                block-with-timestamps
+                block-with-updated-at
                 fix-tag-ids)
                 fix-tag-ids)
           repo (state/get-current-repo)
           repo (state/get-current-repo)
           db-based? (config/db-based-graph? repo)
           db-based? (config/db-based-graph? repo)
-          id (:db/id (:data this))
-          block-entity (db/entity id)
-          structured-tags? (and db-based? (seq (:block/tags m)))]
-      (when id
+          eid (or (:db/id (:data this))
+                  (when-let [block-uuid (:block/uuid (:data this))] [:block/uuid block-uuid]))
+          block-entity (db/entity eid)
+          tags-has-class? (and db-based?
+                               (some (fn [tag]
+                                       (contains? (:block/type (db/entity [:block/uuid (:block/uuid tag)])) "class"))
+                                     (:block/tags m)))]
+      (when eid
         ;; Retract attributes to prepare for tx which rewrites block attributes
         ;; Retract attributes to prepare for tx which rewrites block attributes
-        (let [retract-attributes (when db-based?
-                                   (remove #{:block/properties} db-schema/retract-attributes))]
-          (swap! txs-state (fn [txs]
-                             (vec
-                              (concat txs
-                                      (map (fn [attribute]
-                                             [:db/retract id attribute])
-                                           retract-attributes))))))
+        (when (:block/content m)
+          (let [retract-attributes (if db-based?
+                                     db-schema/db-version-retract-attributes
+                                     db-schema/retract-attributes)]
+            (swap! txs-state (fn [txs]
+                               (vec
+                                (concat txs
+                                        (map (fn [attribute]
+                                               [:db/retract eid attribute])
+                                             retract-attributes)))))))
 
 
         ;; Update block's page attributes
         ;; Update block's page attributes
         (update-page-when-save-block txs-state block-entity m)
         (update-page-when-save-block txs-state block-entity m)
         ;; Remove macros as they are replaced by new ones
         ;; Remove macros as they are replaced by new ones
         (remove-macros-when-save repo txs-state block-entity)
         (remove-macros-when-save repo txs-state block-entity)
-
         ;; Remove orphaned refs from block
         ;; Remove orphaned refs from block
         (remove-orphaned-refs-when-save txs-state block-entity m))
         (remove-orphaned-refs-when-save txs-state block-entity m))
 
 
@@ -331,7 +338,7 @@
         (swap! txs-state conj
         (swap! txs-state conj
                (dissoc m :db/other-tx)))
                (dissoc m :db/other-tx)))
 
 
-      (create-linked-page-when-save txs-state block-entity m structured-tags?)
+      (create-linked-page-when-save txs-state block-entity m tags-has-class?)
 
 
       (rebuild-refs repo txs-state block-entity m)
       (rebuild-refs repo txs-state block-entity m)
 
 

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

@@ -56,7 +56,7 @@
                    (not (state/input-idle? repo {:diff 3000}))) ;; long page
                    (not (state/input-idle? repo {:diff 3000}))) ;; long page
               ;; when this whiteboard page is just being updated
               ;; when this whiteboard page is just being updated
               (and whiteboard? (not (state/whiteboard-idle? repo))))
               (and whiteboard? (not (state/whiteboard-idle? repo))))
-        (async/put! (state/get-file-write-chan) [repo page-db-id outliner-op])
+        (async/put! (state/get-file-write-chan) [repo page-db-id outliner-op (tc/to-long (t/now))])
         (let [pull-keys (if whiteboard? whiteboard-blocks-pull-keys-with-persisted-ids '[*])
         (let [pull-keys (if whiteboard? whiteboard-blocks-pull-keys-with-persisted-ids '[*])
               blocks (model/get-page-blocks-no-cache repo (:block/name page-block) {:pull-keys pull-keys})
               blocks (model/get-page-blocks-no-cache repo (:block/name page-block) {:pull-keys pull-keys})
               blocks (if whiteboard? (map cleanup-whiteboard-block blocks) blocks)]
               blocks (if whiteboard? (map cleanup-whiteboard-block blocks) blocks)]
@@ -73,7 +73,7 @@
   [pages]
   [pages]
   (when (seq pages)
   (when (seq pages)
     (when-not config/publishing?
     (when-not config/publishing?
-      (doseq [[repo page-id outliner-op] (set pages)]
+      (doseq [[repo page-id outliner-op] (set (map #(take 3 %) pages))] ; remove time to dedupe pages to write
         (try (do-write-file! repo page-id outliner-op)
         (try (do-write-file! repo page-id outliner-op)
              (catch :default e
              (catch :default e
                (notification/show!
                (notification/show!
@@ -101,17 +101,17 @@
 (defn <ratelimit-file-writes!
 (defn <ratelimit-file-writes!
   []
   []
   (util/<ratelimit (state/get-file-write-chan) batch-write-interval
   (util/<ratelimit (state/get-file-write-chan) batch-write-interval
-                 :filter-fn
-                 (fn [[repo _ time]]
-                   (swap! *writes-finished? assoc repo {:time time
-                                                        :value false})
-                   true)
-                 :flush-fn
-                 (fn [col]
-                   (let [start-time (tc/to-long (t/now))
-                         repos (distinct (map first col))]
-                     (write-files! col)
-                     (doseq [repo repos]
-                       (let [last-write-time (get-in @*writes-finished? [repo :time])]
-                         (when (> start-time last-write-time)
-                           (swap! *writes-finished? assoc repo {:value true}))))))))
+                   :filter-fn
+                   (fn [[repo _ _ time]]
+                     (swap! *writes-finished? assoc repo {:time time
+                                                          :value false})
+                     true)
+                   :flush-fn
+                   (fn [col]
+                     (let [start-time (tc/to-long (t/now))
+                           repos (distinct (map first col))]
+                       (write-files! col)
+                       (doseq [repo repos]
+                         (let [last-write-time (get-in @*writes-finished? [repo :time])]
+                           (when (> start-time last-write-time)
+                             (swap! *writes-finished? assoc repo {:value true}))))))))

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

@@ -11,8 +11,7 @@
             [frontend.util :as util]
             [frontend.util :as util]
             [goog.events :as events]
             [goog.events :as events]
             [goog.ui.KeyboardShortcutHandler.EventType :as EventType]
             [goog.ui.KeyboardShortcutHandler.EventType :as EventType]
-            [lambdaisland.glogi :as log]
-            [goog.functions :refer [debounce]])
+            [lambdaisland.glogi :as log])
   (:import [goog.events KeyCodes KeyNames]
   (:import [goog.events KeyCodes KeyNames]
            [goog.ui KeyboardShortcutHandler]))
            [goog.ui KeyboardShortcutHandler]))
 
 

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

@@ -140,10 +140,10 @@
   (let [tmp (cond
   (let [tmp (cond
               (false? binding)
               (false? binding)
               (cond
               (cond
-                (and util/mac? (= k :editor/kill-line-after)) "system default: ctrl k"
-                (and util/mac? (= k :editor/beginning-of-block)) "system default: ctrl a"
-                (and util/mac? (= k :editor/end-of-block)) "system default: ctrl e"
-                (and util/mac? (= k :editor/backward-kill-word)) "system default: opt delete"
+                (and util/mac? (= k :editor/kill-line-after)) "ctrl k"
+                (and util/mac? (= k :editor/beginning-of-block)) "ctrl a"
+                (and util/mac? (= k :editor/end-of-block)) "ctrl e"
+                (and util/mac? (= k :editor/backward-kill-word)) "opt delete"
                 :else (t :keymap/disabled))
                 :else (t :keymap/disabled))
 
 
               (string? binding)
               (string? binding)

+ 0 - 6
src/main/frontend/persist_db.cljs

@@ -41,9 +41,3 @@
    (p/let [ret (protocol/<fetch-initital-data (get-impl) repo opts)]
    (p/let [ret (protocol/<fetch-initital-data (get-impl) repo opts)]
      (js/console.log "fetch-initital" ret)
      (js/console.log "fetch-initital" ret)
      ret)))
      ret)))
-
-(defn <fetch-blocks-excluding
-  ([repo exclude-uuids]
-   (<fetch-blocks-excluding repo exclude-uuids {}))
-  ([repo exclude-uuids opts]
-   (protocol/<fetch-blocks-excluding (get-impl) repo exclude-uuids opts)))

+ 0 - 43
src/main/frontend/persist_db/browser.cljs

@@ -3,8 +3,6 @@
 
 
    This interface uses clj data format as input."
    This interface uses clj data format as input."
   (:require ["comlink" :as Comlink]
   (:require ["comlink" :as Comlink]
-            [cljs-time.coerce :as tc]
-            [cljs-time.core :as t]
             [cljs.core.async.interop :refer [p->c]]
             [cljs.core.async.interop :refer [p->c]]
             [frontend.persist-db.protocol :as protocol]
             [frontend.persist-db.protocol :as protocol]
             [frontend.config :as config]
             [frontend.config :as config]
@@ -52,47 +50,6 @@
                                       (reject nil)) ;; cannot init
                                       (reject nil)) ;; cannot init
                                     20000))))))
                                     20000))))))
 
 
-(defn- type-of-block
-  "
-  TODO: use :block/type
-  | value | meaning                                        |
-  |-------+------------------------------------------------|
-  |     1 | normal block                                   |
-  |     2 | page block                                     |
-  |     3 | init data, (config.edn, custom.js, custom.css) |
-  |     4 | db schema                                      |
-  |     5 | unknown type                                   |
-  |     6 | property block                                 |
-  "
-  [block]
-  (cond
-    (:block/page block) 1
-    (:file/content block) 3
-    (contains? (:block/type block) "property") 6
-    (:block/name block) 2
-    :else 5))
-
-(defn time-ms
-  "Copy of util/time-ms. Too basic to couple this to main app"
-  []
-  (tc/to-long (t/now)))
-
-(defn- ds->sqlite-block
-  "Convert a datascript block to a sqlite map in preparation for a sqlite-db fn.
-
-   @uuid, @type, @page_uuid, @page_journal_day, @name, @content, @datoms, @created_at, @updated_at
-   "
-  [b]
-  {:uuid (str (:block/uuid b))
-   :type (type-of-block b)
-   :page_uuid (str (:page_uuid b))
-   :page_journal_day (:block/journal-day b)
-   :name (or (:file/path b) (:block/name b))
-   :content (or (:file/content b) (:block/content b))
-   :datoms (:datoms b)
-   :created_at (or (:block/created-at b) (time-ms))
-   :updated_at (or (:block/updated-at b) (time-ms))})
-
 (comment
 (comment
   (defn dev-stop!
   (defn dev-stop!
     "For dev env only, stop opfs backend, close all sqlite connections and OPFS sync access handles."
     "For dev env only, stop opfs backend, close all sqlite connections and OPFS sync access handles."

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

@@ -128,7 +128,7 @@
 (defn page-search
 (defn page-search
   "Return a list of page names that match the query"
   "Return a list of page names that match the query"
   ([q]
   ([q]
-   (page-search q 10))
+   (page-search q 500))
   ([q limit]
   ([q limit]
    (when-let [repo (state/get-current-repo)]
    (when-let [repo (state/get-current-repo)]
      (let [q (util/search-normalize q (state/enable-search-remove-accents?))
      (let [q (util/search-normalize q (state/enable-search-remove-accents?))

+ 10 - 4
src/main/frontend/search/db.cljs

@@ -8,7 +8,8 @@
             [frontend.config :as config]
             [frontend.config :as config]
             [frontend.util :as util]
             [frontend.util :as util]
             ["fuse.js" :as fuse]
             ["fuse.js" :as fuse]
-            [datascript.impl.entity :as e]))
+            [datascript.impl.entity :as e]
+            [frontend.handler.file-based.property.util :as property-util]))
 
 
 ;; Notice: When breaking changes happen, bump version in src/electron/electron/search.cljs
 ;; Notice: When breaking changes happen, bump version in src/electron/electron/search.cljs
 
 
@@ -58,17 +59,22 @@
 
 
 (defn block->index
 (defn block->index
   "Convert a block to the index for searching"
   "Convert a block to the index for searching"
-  [{:block/keys [uuid page content properties] :as block}]
+  [{:block/keys [uuid page content properties format]
+    :or {format :markdown}
+    :as block}]
   (let [repo (state/get-current-repo)]
   (let [repo (state/get-current-repo)]
     (when-not (> (count content) (max-len))
     (when-not (> (count content) (max-len))
       (when-not (and (string/blank? content)
       (when-not (and (string/blank? content)
                      (empty? properties))
                      (empty? properties))
-        (let [m {:id (:db/id block)
+        (let [db-based? (config/db-based-graph? repo)
+              content (if db-based? content
+                          (property-util/remove-built-in-properties format content))
+              m {:id (:db/id block)
                  :uuid (str uuid)
                  :uuid (str uuid)
                  :page (if (or (map? page) (e/entity? page)) (:db/id page) page)
                  :page (if (or (map? page) (e/entity? page)) (:db/id page) page)
                  :content (sanitize content)}
                  :content (sanitize content)}
               m' (cond-> m
               m' (cond-> m
-                   (and (config/db-based-graph? repo) (seq properties))
+                   (and db-based? (seq properties))
                    (update :content
                    (update :content
                            (fn [content]
                            (fn [content]
                              (str content "\n"
                              (str content "\n"

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

@@ -9,13 +9,14 @@
             [electron.ipc :as ipc]
             [electron.ipc :as ipc]
             [frontend.colors :as colors]
             [frontend.colors :as colors]
             [frontend.mobile.util :as mobile-util]
             [frontend.mobile.util :as mobile-util]
-            [frontend.storage :as storage]
             [frontend.spec.storage :as storage-spec]
             [frontend.spec.storage :as storage-spec]
+            [frontend.storage :as storage]
             [frontend.util :as util]
             [frontend.util :as util]
             [frontend.util.cursor :as cursor]
             [frontend.util.cursor :as cursor]
             [goog.dom :as gdom]
             [goog.dom :as gdom]
             [goog.object :as gobj]
             [goog.object :as gobj]
             [logseq.graph-parser.config :as gp-config]
             [logseq.graph-parser.config :as gp-config]
+            [malli.core :as m]
             [medley.core :as medley]
             [medley.core :as medley]
             [promesa.core :as p]
             [promesa.core :as p]
             [rum.core :as rum]))
             [rum.core :as rum]))
@@ -35,10 +36,12 @@
      {:route-match                           nil
      {:route-match                           nil
       :today                                 nil
       :today                                 nil
       :system/events                         (async/chan 1000)
       :system/events                         (async/chan 1000)
-      :file/writes                           (async/chan 10000
-                                                         (util/dedupe-by
-                                                          (fn [[repo page-id outliner-op _epoch]]
-                                                            [repo page-id outliner-op])))
+      :file/writes                           (let [coercer (m/coercer [:catn
+                                                                       [:repo :string]
+                                                                       [:page-id :any]
+                                                                       [:outliner-op :any]
+                                                                       [:epoch :int]])]
+                                               (async/chan 10000 (map coercer)))
       :file/unlinked-dirs                    #{}
       :file/unlinked-dirs                    #{}
       :reactive/custom-queries               (async/chan 1000)
       :reactive/custom-queries               (async/chan 1000)
       :notification/show?                    false
       :notification/show?                    false

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

@@ -819,7 +819,7 @@
                 (when-let [f (:init-collapsed (last (:rum/args state)))]
                 (when-let [f (:init-collapsed (last (:rum/args state)))]
                   (f (::collapsed? state)))
                   (f (::collapsed? state)))
                 state)}
                 state)}
-  [state header content {:keys [title-trigger? on-mouse-down
+  [state header content {:keys [title-trigger? on-mouse-down class
                                 _default-collapsed? _init-collapsed]}]
                                 _default-collapsed? _init-collapsed]}]
   (let [collapsed? (get state ::collapsed?)
   (let [collapsed? (get state ::collapsed?)
         on-mouse-down (fn [e]
         on-mouse-down (fn [e]
@@ -828,6 +828,7 @@
                         (when on-mouse-down
                         (when on-mouse-down
                           (on-mouse-down @collapsed?)))]
                           (on-mouse-down @collapsed?)))]
     [:div.flex.flex-col
     [:div.flex.flex-col
+     {:class class}
      (foldable-title {:on-mouse-down on-mouse-down
      (foldable-title {:on-mouse-down on-mouse-down
                       :header header
                       :header header
                       :title-trigger? title-trigger?
                       :title-trigger? title-trigger?

+ 5 - 20
src/main/frontend/util.cljc

@@ -1141,11 +1141,11 @@
                will poll it when its return value is channel,
                will poll it when its return value is channel,
   - :flush-fn exec flush-fn when time to flush, (flush-fn item-coll)
   - :flush-fn exec flush-fn when time to flush, (flush-fn item-coll)
   - :stop-ch stop go-loop when stop-ch closed
   - :stop-ch stop go-loop when stop-ch closed
-  - :distinct-coll? distinct coll when put into CH
+  - :distinct-key-fn distinct coll when put into CH
   - :chan-buffer buffer of return CH, default use (async/chan 1000)
   - :chan-buffer buffer of return CH, default use (async/chan 1000)
   - :flush-now-ch flush the content in the queue immediately
   - :flush-now-ch flush the content in the queue immediately
   - :refresh-timeout-ch refresh (timeout max-duration)"
   - :refresh-timeout-ch refresh (timeout max-duration)"
-     [in-ch max-duration & {:keys [filter-fn flush-fn stop-ch distinct-coll? chan-buffer flush-now-ch refresh-timeout-ch]}]
+     [in-ch max-duration & {:keys [filter-fn flush-fn stop-ch distinct-key-fn chan-buffer flush-now-ch refresh-timeout-ch]}]
      (let [ch (if chan-buffer (async/chan chan-buffer) (async/chan 1000))
      (let [ch (if chan-buffer (async/chan chan-buffer) (async/chan 1000))
            stop-ch* (or stop-ch (async/chan))
            stop-ch* (or stop-ch (async/chan))
            flush-now-ch* (or flush-now-ch (async/chan))
            flush-now-ch* (or flush-now-ch (async/chan))
@@ -1173,8 +1173,8 @@
                                (async/<! filter-v)
                                (async/<! filter-v)
                                filter-v)]
                                filter-v)]
                (if filter-v*
                (if filter-v*
-                 (recur timeout-ch (cond-> (conj coll e)
-                                     distinct-coll? distinct
+                 (recur timeout-ch (cond->> (conj coll e)
+                                     distinct-key-fn (distinct-by distinct-key-fn)
                                      true vec))
                                      true vec))
                  (recur timeout-ch coll)))
                  (recur timeout-ch coll)))
 
 
@@ -1549,19 +1549,4 @@ Arg *stop: atom, reset to true to stop the loop"
           (or
           (or
            (not (string/includes? s " "))
            (not (string/includes? s " "))
            (string/starts-with? s "#[[")
            (string/starts-with? s "#[[")
-           (string/ends-with? s "]]")))))
-
-(defn dedupe-by
-  ([keyfn]
-   (fn [rf]
-     (let [pa (volatile! ::none)]
-       (fn
-         ([] (rf))
-         ([result] (rf result))
-         ([result input]
-          (let [prior @pa
-                key (keyfn input)]
-            (vreset! pa key)
-            (if (= prior key)
-              result
-              (rf result input)))))))))
+           (string/ends-with? s "]]")))))

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

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

+ 604 - 140
src/resources/dicts/it.edn

@@ -20,13 +20,12 @@
  :help/shortcuts "Scorciatoie da tastiera"
  :help/shortcuts "Scorciatoie da tastiera"
  :help/shortcuts-triggers "Attivazione delle scorciatoie"
  :help/shortcuts-triggers "Attivazione delle scorciatoie"
  :help/shortcut "Scorciatoia"
  :help/shortcut "Scorciatoia"
- :help/slash-autocomplete "Barra di completamento automatico"
- :help/reference-autocomplete "Autocompletamente del riferimento di pagina"
+ :help/slash-autocomplete "Completamento automatico con barra (/)"
+ :help/reference-autocomplete "Completamento automatico del riferimento di pagina"
  :help/block-reference "Riferimento di blocco"
  :help/block-reference "Riferimento di blocco"
- :help/open-link-in-sidebar "Apri il link nella barra laterale"
-
+ :help/open-link-in-sidebar "Apri il link nel pannello laterale"
  :search/page-names "Cerca pagine per nome"
  :search/page-names "Cerca pagine per nome"
- :help/context-menu "Menu contestuale del blocco"
+ :help/context-menu "Menù contestuale del blocco"
  :help/markdown-syntax "Sintassi Markdown"
  :help/markdown-syntax "Sintassi Markdown"
  :help/org-mode-syntax "Sintassi Org mode"
  :help/org-mode-syntax "Sintassi Org mode"
  :bold "Grassetto"
  :bold "Grassetto"
@@ -37,9 +36,9 @@
  :right-side-bar/help "Aiuto"
  :right-side-bar/help "Aiuto"
  :right-side-bar/switch-theme "Modalità tema"
  :right-side-bar/switch-theme "Modalità tema"
  :right-side-bar/contents "Contenuti"
  :right-side-bar/contents "Contenuti"
- :right-side-bar/page-graph "Grafico della pagina"
- :right-side-bar/block-ref "Riferimento di Blocco"
- :right-side-bar/graph-view "Vista del grafico"
+ :right-side-bar/page-graph "Grafo della pagina"
+ :right-side-bar/block-ref "Riferimento al blocco"
+ :right-side-bar/graph-view "Vista del grafo"
  :right-side-bar/all-pages "Tutte le pagine"
  :right-side-bar/all-pages "Tutte le pagine"
  :right-side-bar/new-page "Nuova pagina"
  :right-side-bar/new-page "Nuova pagina"
  :right-side-bar/show-journals "Mostra diari"
  :right-side-bar/show-journals "Mostra diari"
@@ -51,7 +50,7 @@
  :page/open-in-finder "Apri nella cartella"
  :page/open-in-finder "Apri nella cartella"
  :page/open-with-default-app "Apri con l'app predefinita"
  :page/open-with-default-app "Apri con l'app predefinita"
  :page/make-public "Segna come pubblico per la pubblicazione"
  :page/make-public "Segna come pubblico per la pubblicazione"
- :page/version-history "Controlla la cronologia della pagina"
+ :page/version-history "Cronologia della pagina"
  :page/open-backup-directory "Apri la cartella dei backup delle pagine"
  :page/open-backup-directory "Apri la cartella dei backup delle pagine"
  :page/make-private "Segna come privato"
  :page/make-private "Segna come privato"
  :page/delete "Elimina pagina"
  :page/delete "Elimina pagina"
@@ -76,11 +75,11 @@
  :editor/cut "Taglia"
  :editor/cut "Taglia"
  :content/copy-block-ref "Copia riferimento di blocco"
  :content/copy-block-ref "Copia riferimento di blocco"
  :content/copy-block-emebed "Copia blocco incorporato"
  :content/copy-block-emebed "Copia blocco incorporato"
- :content/open-in-sidebar "Apri nella barra laterale"
+ :content/open-in-sidebar "Apri nel pannello laterale"
  :content/click-to-edit "Clicca per modificare"
  :content/click-to-edit "Clicca per modificare"
  :settings-page/git-confirm "Devi riavviare l'app dopo aver aggiornato le impostazioni di Git."
  :settings-page/git-confirm "Devi riavviare l'app dopo aver aggiornato le impostazioni di Git."
- :settings-page/git-switcher-label "Abilita Git auto commit"
- :settings-page/git-commit-delay "Secondi per Git auto commit"
+ :settings-page/git-switcher-label "Commit automatico"
+ :settings-page/git-commit-delay "Secondi per commit automatico"
  :settings-page/edit-config-edn "Modifica config.edn"
  :settings-page/edit-config-edn "Modifica config.edn"
  :settings-page/edit-custom-css "Modifica custom.css"
  :settings-page/edit-custom-css "Modifica custom.css"
  :settings-page/custom-configuration "Configurazione personalizzata"
  :settings-page/custom-configuration "Configurazione personalizzata"
@@ -89,15 +88,15 @@
  :settings-page/spell-checker "Correttore ortografico"
  :settings-page/spell-checker "Correttore ortografico"
  :settings-page/auto-updater "Aggiornamento automatico"
  :settings-page/auto-updater "Aggiornamento automatico"
  :settings-page/disable-sentry "Invia dati di utilizzo e diagnostica a Logseq"
  :settings-page/disable-sentry "Invia dati di utilizzo e diagnostica a Logseq"
- :settings-page/preferred-outdenting "Indentamento logico"
+ :settings-page/preferred-outdenting "Indentazione logica"
  :settings-page/custom-date-format "Formato data preferito"
  :settings-page/custom-date-format "Formato data preferito"
  :settings-page/preferred-file-format "Formato file preferito"
  :settings-page/preferred-file-format "Formato file preferito"
  :settings-page/preferred-workflow "Flusso di lavoro preferito"
  :settings-page/preferred-workflow "Flusso di lavoro preferito"
- :settings-page/enable-shortcut-tooltip "Abilita suggerimenti scorciatoie"
+ :settings-page/enable-shortcut-tooltip "Abilita suggerimenti a comparsa"
  :settings-page/enable-timetracking "Tracciamento del tempo"
  :settings-page/enable-timetracking "Tracciamento del tempo"
  :settings-page/enable-tooltip "Suggerimenti"
  :settings-page/enable-tooltip "Suggerimenti"
  :settings-page/enable-journals "Diario"
  :settings-page/enable-journals "Diario"
- :settings-page/enable-all-pages-public "Tutte le pagine pubbliche durante la pubblicazione"
+ :settings-page/enable-all-pages-public "Durante la pubblicazione tutte le pagine sono pubbliche"
  :settings-page/home-default-page "Imposta la home page predefinita"
  :settings-page/home-default-page "Imposta la home page predefinita"
  :settings-page/clear-cache "Pulisci cache"
  :settings-page/clear-cache "Pulisci cache"
  :settings-page/clear "Pulisci"
  :settings-page/clear "Pulisci"
@@ -105,7 +104,7 @@
  :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/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/current-version "Versione attuale"
  :settings-page/tab-general "Generale"
  :settings-page/tab-general "Generale"
- :settings-page/tab-version-control "Controllo di versione"
+ :settings-page/tab-version-control "Controllo versione"
  :settings-page/tab-advanced "Avanzate"
  :settings-page/tab-advanced "Avanzate"
  :settings-page/plugin-system "Sistema di plugin"
  :settings-page/plugin-system "Sistema di plugin"
  :settings-page/network-proxy "Proxy di rete"
  :settings-page/network-proxy "Proxy di rete"
@@ -119,13 +118,12 @@
  :port "Porta"
  :port "Porta"
  :re-index "Re-indicizza"
  :re-index "Re-indicizza"
  :re-index-detail "Ricostruisci il grafo"
  :re-index-detail "Ricostruisci il grafo"
- :re-index-multiple-windows-warning "È necessario chiudere le altre finestre prima di reindicizzare questo grafo."
- :re-index-discard-unsaved-changes-warning "La reindicizzazione elimina il grafo corrente, quindi elabora nuovamente tutti i file poiché sono attualmente archiviati su disco. Perderai le modifiche non salvate e potrebbe volerci del tempo. Continuare?"
+ :re-index-multiple-windows-warning "È necessario chiudere le altre finestre prima di re-indicizzare questo grafo."
+ :re-index-discard-unsaved-changes-warning "La re-indicizzazione elimina il grafo corrente, quindi elabora nuovamente tutti i file poiché sono attualmente archiviati su disco. Perderai le modifiche non salvate e potrebbe volerci del tempo. Continuare?"
  :open-new-window "Nuova finestra"
  :open-new-window "Nuova finestra"
  :sync-from-local-files "Ricarica"
  :sync-from-local-files "Ricarica"
  :sync-from-local-files-detail "Importa cambiamenti da un file locale"
  :sync-from-local-files-detail "Importa cambiamenti da un file locale"
  :sync-from-local-changes-detected "Il ricaricamento rileva ed elabora i file modificati sul disco e divergenti dal contenuto effettivo della pagina Logseq. Continuare?"
  :sync-from-local-changes-detected "Il ricaricamento rileva ed elabora i file modificati sul disco e divergenti dal contenuto effettivo della pagina Logseq. Continuare?"
-
  :new-graph "Aggiungi nuovo grafo"
  :new-graph "Aggiungi nuovo grafo"
  :graph "Grafo"
  :graph "Grafo"
  :graph/persist "Logseq sta sincronizzando lo stato interno, per favore attendi alcuni secondi."
  :graph/persist "Logseq sta sincronizzando lo stato interno, per favore attendi alcuni secondi."
@@ -148,7 +146,7 @@
  :all-journals "Tutte le pagine di diario"
  :all-journals "Tutte le pagine di diario"
  :settings "Impostazioni"
  :settings "Impostazioni"
  :settings-of-plugins "Impostazioni plugin"
  :settings-of-plugins "Impostazioni plugin"
- :plugins "Plugins"
+ :plugins "Plugin"
  :themes "Temi"
  :themes "Temi"
  :relaunch-confirm-to-work "È necessario riavviare l'app per farla funzionare. Vuoi riavviarla ora?"
  :relaunch-confirm-to-work "È necessario riavviare l'app per farla funzionare. Vuoi riavviarla ora?"
  :import "Importa"
  :import "Importa"
@@ -162,11 +160,9 @@
  :language "Lingua"
  :language "Lingua"
  :remove-background "Rimuovi lo sfondo"
  :remove-background "Rimuovi lo sfondo"
  :open-a-directory "Apri una cartella locale"
  :open-a-directory "Apri una cartella locale"
-
  :help/shortcut-page-title "Scorciatoie da tastiera"
  :help/shortcut-page-title "Scorciatoie da tastiera"
-
- :plugin/installed "Installato"
- :plugin/not-installed "Non installato"
+ :plugin/installed "Installati"
+ :plugin/not-installed "Non installati"
  :plugin/installing "Installazione"
  :plugin/installing "Installazione"
  :plugin/install "Installa"
  :plugin/install "Installa"
  :plugin/reload "Ricarica"
  :plugin/reload "Ricarica"
@@ -174,8 +170,8 @@
  :plugin/check-update "Controlla aggiornamenti"
  :plugin/check-update "Controlla aggiornamenti"
  :plugin/check-all-updates "Controlla tutti gli aggiornamenti"
  :plugin/check-all-updates "Controlla tutti gli aggiornamenti"
  :plugin/refresh-lists "Ricarica lista"
  :plugin/refresh-lists "Ricarica lista"
- :plugin/enabled "Abilitato"
- :plugin/disabled "Disabilitato"
+ :plugin/enabled "Abilitati"
+ :plugin/disabled "Disabilitati"
  :plugin/update-available "Aggiornamento disponibile"
  :plugin/update-available "Aggiornamento disponibile"
  :plugin/updating "Aggiornamento"
  :plugin/updating "Aggiornamento"
  :plugin/uninstall "Disinstalla"
  :plugin/uninstall "Disinstalla"
@@ -192,139 +188,607 @@
  :plugin/unpacked-tips "Seleziona la cartella del plugin"
  :plugin/unpacked-tips "Seleziona la cartella del plugin"
  :plugin/contribute "✨ Sviluppa e sottoponici un nuovo plugin"
  :plugin/contribute "✨ Sviluppa e sottoponici un nuovo plugin"
  :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.)"
  :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"
  :pdf/copy-ref "Copia riferimenti"
  :pdf/copy-text "Copia testo"
  :pdf/copy-text "Copia testo"
  :pdf/linked-ref "Riferimenti collegati"
  :pdf/linked-ref "Riferimenti collegati"
  :pdf/toggle-dashed "Stile tratteggiato per evidenziare l'area"
  :pdf/toggle-dashed "Stile tratteggiato per evidenziare l'area"
-
  :updater/new-version-install "Una nuova versione è stata scaricata."
  :updater/new-version-install "Una nuova versione è stata scaricata."
  :updater/quit-and-install "Riavvia per installarla"
  :updater/quit-and-install "Riavvia per installarla"
-
  :paginates/pages "Totale {1} pagine"
  :paginates/pages "Totale {1} pagine"
  :paginates/prev "Precedente"
  :paginates/prev "Precedente"
  :paginates/next "Successivo"
  :paginates/next "Successivo"
-
  :select/default-prompt "Selezionane uno"
  :select/default-prompt "Selezionane uno"
  :select.graph/prompt "Seleziona un grafo"
  :select.graph/prompt "Seleziona un grafo"
  :select.graph/empty-placeholder-description "Non ci sono grafi corrispondenti. Vuoi aggiungerne uno nuovo?"
  :select.graph/empty-placeholder-description "Non ci sono grafi corrispondenti. Vuoi aggiungerne uno nuovo?"
  :select.graph/add-graph "Sì, aggiungi un nuovo grafo"
  :select.graph/add-graph "Sì, aggiungi un nuovo grafo"
-
  :file-sync/other-user-graph "Il grafo locale attuale è associato al grafo remoto di un altro utente. Non è quindi possibile avviare la sincronizzazione."
  :file-sync/other-user-graph "Il grafo locale attuale è associato al grafo remoto di un altro utente. Non è quindi possibile avviare la sincronizzazione."
  :file-sync/graph-deleted "Il grafo attuale è stato eliminato"
  :file-sync/graph-deleted "Il grafo attuale è stato eliminato"
- :settings-page/edit-export-css "Modificare export.css"
+ :settings-page/edit-export-css "Modifica export.css"
  :settings-page/enable-flashcards "Flashcard"
  :settings-page/enable-flashcards "Flashcard"
  :settings-page/export-theme "Esporta tema"
  :settings-page/export-theme "Esporta tema"
-
- :command.date-picker/complete         "Selettore data: scegli il giorno selezionato"
- :command.date-picker/prev-day         "Selettore data: Seleziona il giorno precedente"
- :command.date-picker/next-day         "Selettore data: Seleziona il giorno successivo"
- :command.date-picker/prev-week        "Selettore data: Seleziona la settimana precedente"
- :command.date-picker/next-week        "Selettore data: Seleziona la settimana successiva"
- :command.pdf/previous-page            "Pagina precedente del pdf corrente"
- :command.pdf/next-page                "Pagina successiva del pdf corrente"
- :command.auto-complete/complete       "Auto completamento: Scegli l'oggetto selezionato"
- :command.auto-complete/prev           "Auto completamento: Seleziona l'oggetto precedente"
- :command.auto-complete/next           "Auto completamento: Seleziona l'oggetto successivo"
- :command.auto-complete/shift-complete "Auto completamento: Apri l'oggetto selezionato nella barra laterale"
- :command.auto-complete/open-link      "Auto completamento: Apri l'oggetto selezionato nel browser"
- :command.cards/toggle-answers         "Carte: mostra/nascondi risposte/chiusure"
- :command.cards/next-card              "Carte: prossima carta"
- :command.cards/forgotten              "Carte: dimenticato"
- :command.cards/remembered             "Carte: ricordato"
- :command.cards/recall                 "Carte: ci ho messo un pò a ricordarlo"
- :command.editor/escape-editing        "Esci dalla modifica"
- :command.editor/backspace             "Tasto Backspace / Cancella all'indietro"
- :command.editor/delete                "Tasto Delete / Cancella avanti"
- :command.editor/new-block             "Crea un nuovo blocco"
- :command.editor/new-line              "Nuova riga accapo nel blocco attuale"
- :command.editor/follow-link           "Segui il link sotto al cursore"
- :command.editor/open-link-in-sidebar  "Apri il link nella barra laterale"
- :command.editor/bold                  "Grassetto"
- :command.editor/italics               "Corsivo"
- :command.editor/highlight             "Evidenzia"
- :command.editor/strike-through        "Barrato"
- :command.editor/clear-block           "Elimina l'intero contenuto del blocco"
- :command.editor/kill-line-before      "Cancella la riga prima della posizione del cursore"
- :command.editor/kill-line-after       "Cancella la riga dopo la posizione del cursore"
- :command.editor/beginning-of-block    "Muovi il cursore all'inizio di un blocco"
- :command.editor/end-of-block          "Muovi il cursore alla fine di un blocco"
- :command.editor/forward-word          "Muovi il cursore in avanti di una parola"
- :command.editor/backward-word         "Muovi il cursore all'indietro di una parola"
- :command.editor/forward-kill-word     "Elimina una parola in avanti"
- :command.editor/backward-kill-word    "Elimina una parola all'indietro"
+ :command.date-picker/complete "Selettore data: scegli il giorno selezionato"
+ :command.date-picker/prev-day "Selettore data: Seleziona il giorno precedente"
+ :command.date-picker/next-day "Selettore data: Seleziona il giorno successivo"
+ :command.date-picker/prev-week "Selettore data: Seleziona la settimana precedente"
+ :command.date-picker/next-week "Selettore data: Seleziona la settimana successiva"
+ :command.pdf/previous-page "Pagina precedente del pdf corrente"
+ :command.pdf/next-page "Pagina successiva del pdf corrente"
+ :command.auto-complete/complete "Auto completamento: Scegli l'oggetto selezionato"
+ :command.auto-complete/prev "Auto completamento: Seleziona l'oggetto precedente"
+ :command.auto-complete/next "Auto completamento: Seleziona l'oggetto successivo"
+ :command.auto-complete/shift-complete "Auto completamento: Apri l'oggetto selezionato nel pannello laterale"
+ :command.auto-complete/open-link "Auto completamento: Apri l'oggetto selezionato nel browser"
+ :command.cards/toggle-answers "Carte: mostra/nascondi risposte/chiusure"
+ :command.cards/next-card "Carte: prossima carta"
+ :command.cards/forgotten "Carte: dimenticato"
+ :command.cards/remembered "Carte: ricordato"
+ :command.cards/recall "Carte: ci ho messo un po' a ricordarlo"
+ :command.editor/escape-editing "Esci dalla modifica"
+ :command.editor/backspace "Tasto Backspace / Cancella all'indietro"
+ :command.editor/delete "Tasto Delete / Cancella avanti"
+ :command.editor/new-block "Crea un nuovo blocco"
+ :command.editor/new-line "Nuova riga a capo nel blocco attuale"
+ :command.editor/follow-link "Segui il link sotto al cursore"
+ :command.editor/open-link-in-sidebar "Apri il link nel pannello laterale"
+ :command.editor/bold "Grassetto"
+ :command.editor/italics "Corsivo"
+ :command.editor/highlight "Evidenzia"
+ :command.editor/strike-through "Barrato"
+ :command.editor/clear-block "Elimina l'intero contenuto del blocco"
+ :command.editor/kill-line-before "Cancella la riga prima della posizione del cursore"
+ :command.editor/kill-line-after "Cancella la riga dopo la posizione del cursore"
+ :command.editor/beginning-of-block "Muovi il cursore all'inizio di un blocco"
+ :command.editor/end-of-block "Muovi il cursore alla fine di un blocco"
+ :command.editor/forward-word "Muovi il cursore in avanti di una parola"
+ :command.editor/backward-word "Muovi il cursore all'indietro di una parola"
+ :command.editor/forward-kill-word "Elimina una parola in avanti"
+ :command.editor/backward-kill-word "Elimina una parola all'indietro"
  :command.editor/replace-block-reference-at-point "Sostituisci il riferimento di blocco con il suo contenuto al punto"
  :command.editor/replace-block-reference-at-point "Sostituisci il riferimento di blocco con il suo contenuto al punto"
  :command.editor/paste-text-in-one-block-at-point "Incolla testo in un blocco al punto"
  :command.editor/paste-text-in-one-block-at-point "Incolla testo in un blocco al punto"
- :command.editor/insert-youtube-timestamp         "Inserisci marca temporale di youtube"
- :command.editor/cycle-todo              "Cicla lo stato TODO dell'elemento corrente"
- :command.editor/up                      "Muovi il cursore sopra / Seleziona sopra"
- :command.editor/down                    "Muovi il cursore sotto / Seleziona sotto"
- :command.editor/left                    "Muovi il cursore a sinistra / Apri il blocco selezionato all'inizio"
- :command.editor/right                   "Muovi il cursore a destra / Apri il blocco selezionato all'inizio"
- :command.editor/select-up               "Seleziona il contenuto sopra"
- :command.editor/select-down             "Seleziona il contenuto sotto"
- :command.editor/move-block-up           "Muovi il blocco sopra"
- :command.editor/move-block-down         "Muovi il blocco sotto"
- :command.editor/open-edit               "Modifica il blocco selezionato"
- :command.editor/select-block-up         "Seleziona blocco sopra"
- :command.editor/select-block-down       "Seleziona blocco sotto"
- :command.editor/delete-selection        "Elimina i blocchi selezionati"
- :command.editor/expand-block-children   "Espandi"
+ :command.editor/insert-youtube-timestamp "Inserisci marca temporale di YouTube"
+ :command.editor/cycle-todo "Cicla lo stato TODO dell'elemento corrente"
+ :command.editor/up "Muovi il cursore sopra / Seleziona sopra"
+ :command.editor/down "Muovi il cursore sotto / Seleziona sotto"
+ :command.editor/left "Muovi il cursore a sinistra / Apri il blocco selezionato all'inizio"
+ :command.editor/right "Muovi il cursore a destra / Apri il blocco selezionato all'inizio"
+ :command.editor/select-up "Seleziona il contenuto sopra"
+ :command.editor/select-down "Seleziona il contenuto sotto"
+ :command.editor/move-block-up "Muovi il blocco sopra"
+ :command.editor/move-block-down "Muovi il blocco sotto"
+ :command.editor/open-edit "Modifica il blocco selezionato"
+ :command.editor/select-block-up "Seleziona blocco sopra"
+ :command.editor/select-block-down "Seleziona blocco sotto"
+ :command.editor/delete-selection "Elimina i blocchi selezionati"
+ :command.editor/expand-block-children "Espandi"
  :command.editor/collapse-block-children "Collassa"
  :command.editor/collapse-block-children "Collassa"
- :command.editor/indent                  "Rientra blocco"
- :command.editor/outdent                 "Annulla il rientro blocco"
- :command.editor/copy                    "Copia (copia una selezione o un riferimento di blocco)"
- :command.editor/cut                     "Taglia"
- :command.editor/undo                    "Annulla"
- :command.editor/redo                    "Rifai"
- :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"
- :command.ui/toggle-brackets             "Selezionare se visualizzare le parentesi"
- :command.go/search                      "Ricerca testo completo"
- :command.go/journals                    "Vai ai diari"
- :command.go/backward                    "Indietro"
- :command.go/forward                     "Avanti"
- :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.graph/open                     "Seleziona il diagramma da aprire"
- :command.graph/remove                   "Rimuovi un diagramma"
- :command.graph/add                      "Aggiungi un diagramma"
- :command.graph/save                     "Salva il diagramma corrente su disco"
- :command.command/run                    "Esegui comando git"
- :command.go/home                        "Vai all'inizio"
- :command.go/all-pages                   "Vai a tutte le pagine"
- :command.go/graph-view                  "Vai alla visualizzazione diagramma"
- :command.go/keyboard-shortcuts          "Vai alle scorciatoie da tastiera"
- :command.go/tomorrow                    "Vai a domani"
- :command.go/next-journal                "Vai al prossimo diario"
- :command.go/prev-journal                "Vai al diario precedente"
- :command.go/flashcards                  "Attiva/disattiva carte flash"
- :command.ui/toggle-document-mode        "Attiva/disattiva modalità documento"
- :command.ui/toggle-settings             "Attiva/disattiva impostazioni"
- :command.ui/toggle-right-sidebar        "Attiva/disattiva barra laterale destra"
- :command.ui/toggle-left-sidebar         "Attiva/disattiva barra laterale sinistra"
- :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.command/toggle-favorite        "Aggiungi a/rimuovi dai preferiti"
+ :command.editor/indent "Indenta blocco"
+ :command.editor/outdent "Diminuisci indentazione"
+ :command.editor/copy "Copia (copia una selezione o un riferimento di blocco)"
+ :command.editor/cut "Taglia"
+ :command.editor/undo "Annulla"
+ :command.editor/redo "Rifai"
+ :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"
+ :command.ui/toggle-brackets "Selezionare se visualizzare le parentesi"
+ :command.go/search "Ricerca testo completo"
+ :command.go/journals "Vai ai diari"
+ :command.go/backward "Indietro"
+ :command.go/forward "Avanti"
+ :command.search/re-index "Ricostruisci indice di ricerca"
+ :command.sidebar/open-today-page "Apri la pagina di oggi nel pannello laterale destro"
+ :command.sidebar/clear "Pulisci tutto nel pannello laterale destro"
+ :command.graph/open "Seleziona il diagramma da aprire"
+ :command.graph/remove "Rimuovi un diagramma"
+ :command.graph/add "Aggiungi un diagramma"
+ :command.graph/save "Salva il diagramma corrente su disco"
+ :command.command/run "Esegui comando Git"
+ :command.go/home "Vai all'inizio"
+ :command.go/all-pages "Vai a tutte le pagine"
+ :command.go/graph-view "Vai alla visualizzazione diagramma"
+ :command.go/keyboard-shortcuts "Vai alle scorciatoie da tastiera"
+ :command.go/tomorrow "Vai a domani"
+ :command.go/next-journal "Vai al prossimo diario"
+ :command.go/prev-journal "Vai al diario precedente"
+ :command.go/flashcards "Attiva/disattiva carte flash"
+ :command.ui/toggle-document-mode "Attiva/disattiva modalità documento"
+ :command.ui/toggle-settings "Attiva/disattiva impostazioni"
+ :command.ui/toggle-right-sidebar "Attiva/disattiva pannello laterale destra"
+ :command.ui/toggle-left-sidebar "Attiva/disattiva pannello laterale sinistra"
+ :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 nel pannello laterale"
+ :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-default-app "Apri file nell'app predefinita"
- :command.editor/open-file-in-directory   "Apri file nella directory principale"
- :command.editor/copy-current-file        "Copia file corrente"
- :command.ui/toggle-wide-mode             "Attiva/disattiva modalità ampia"
- :command.ui/select-theme-color           "Seleziona i colori del tema disponibili"
- :command.ui/goto-plugins                 "Vai alla dashboard dei plugin"
- :command.editor/toggle-open-blocks       "Attiva/disattiva i blocchi aperti (comprimi o espandi tutti i blocchi)"
- :command.git/commit                      "Git messaggio di commit"
- :shortcut.category/basics                "Nozioni di base"
- :shortcut.category/formatting            "Formattazione"
- :shortcut.category/navigating            "Navigazione"
- :shortcut.category/block-editing         "Modiifica blocco generale"
+ :command.editor/open-file-in-directory "Apri file nella directory principale"
+ :command.editor/copy-current-file "Copia file corrente"
+ :command.ui/toggle-wide-mode "Attiva/disattiva modalità ampia"
+ :command.ui/select-theme-color "Seleziona i colori del tema disponibili"
+ :command.ui/goto-plugins "Vai alla dashboard dei plugin"
+ :command.editor/toggle-open-blocks "Attiva/disattiva i blocchi aperti (collassa o espandi tutti i blocchi)"
+ :command.git/commit "Messaggio di commit Git"
+ :shortcut.category/basics "Nozioni di base"
+ :shortcut.category/formatting "Formattazione"
+ :shortcut.category/navigating "Navigazione"
+ :shortcut.category/block-editing "Modifica blocco generale"
  :shortcut.category/block-command-editing "Modifica comandi blocco"
  :shortcut.category/block-command-editing "Modifica comandi blocco"
- :shortcut.category/block-selection       "Selezione blocco (premi Esc per uscire dalla selezione)"
- :shortcut.category/toggle                "Attiva/disattiva"
- :shortcut.category/others                "Altri"
- :command.editor/copy-embed               "Copia un incorporamento di blocco che punta al blocco corrente"
- :command.editor/copy-text                "Copia le selezioni come testo"
- :command.pdf/close                       "Chiudi anteprima PDF"}
+ :shortcut.category/block-selection "Selezione blocco (premi Esc per uscire dalla selezione)"
+ :shortcut.category/toggle "Attiva/disattiva"
+ :shortcut.category/others "Altri"
+ :command.editor/copy-embed "Copia un incorporamento di blocco che punta al blocco corrente"
+ :command.editor/copy-text "Copia le selezioni come testo"
+ :command.pdf/close "Chiudi anteprima PDF"
+ :all-whiteboards "Tutte le lavagne"
+ :auto-heading "Titoli automatici"
+ :discourse-title "Il nostro forum!"
+ :export-copied-to-clipboard "Copiato negli appunti!"
+ :export-copy-to-clipboard "Copia negli appunti"
+ :export-save-to-file "Salva come file"
+ :export-transparent-background "Sfondo trasparente"
+ :heading "Titolo {1}"
+ :home "Home"
+ :host "Host"
+ :importing "Importando"
+ :loading "Caricamento..."
+ :logout-user "Disconnettiti ({1})"
+ :new-page "Nuova pagina:"
+ :remove-heading "Rimuovi titolo"
+ :remove-orphaned-pages "Rimuovere le pagine orfane?"
+ :toggle-theme "Inverti tema"
+ :untitled "Senza titolo"
+ :whiteboard "Lavagna"
+ :whiteboards "Lavagne"
+ :accessibility/skip-to-main-content "Salta al contenuto principale"
+ :asset/copy "Copia immagine"
+ :asset/delete "Elimina immagine"
+ :asset/maximize "Ingrandisci immagine"
+ :asset/open-in-browser "Apri immagine nel browser"
+ :asset/show-in-folder "Mostra nella cartella"
+ :bug-report/clipboard-inspector-title "Ispettore dati degli appunti"
+ :bug-report/inspector-page-btn-back "Indietro"
+ :bug-report/inspector-page-btn-copy "Copia il risultato"
+ :bug-report/inspector-page-btn-create-issue "Segnala un problema"
+ :bug-report/inspector-page-copy-notif "Copiato negli appunti!"
+ :bug-report/inspector-page-desc-1 "Premi Ctrl+V / ⌘+V per ispezionare gli appunti"
+ :bug-report/inspector-page-desc-2 "oppure clicca qui per incollare se stai usando la versione mobile"
+ :bug-report/inspector-page-desc-clipboard "Questi sono i dati letti dagli appunti."
+ :bug-report/inspector-page-desc-copy "Se possono essere condivisi, clicca il pulsante per copiare."
+ :bug-report/inspector-page-desc-create-issue "Ora puoi riportare il risultato incollato negli appunti. Per favore, incolla il risultato nella sezione 'Contesto Aggiuntivo' e precisa da dove hai copiato il contesto originale."
+ :bug-report/inspector-page-placeholder "Premi a lungo qui se sei da mobile"
+ :bug-report/inspector-page-tip "Qualcosa non va? Nessun problema, clicca per tornare al passo precedente."
+ :bug-report/main-desc "Puoi aiutarci segnalando un difetto? Ce ne occuperemo il prima possibile."
+ :bug-report/main-title "Segnalazione di un difetto"
+ :bug-report/section-clipboard-btn-desc "Ispeziona e raccogli dati sugli appunti"
+ :bug-report/section-clipboard-btn-title "Aiutante degli appunti"
+ :bug-report/section-clipboard-desc "Puoi usare questi utili strumenti per darci informazioni aggiuntive."
+ :bug-report/section-clipboard-title "Il difetto che hai incontrato ha a che fare con queste funzionalità?"
+ :bug-report/section-issues-btn-desc "Aiutaci a migliorare Logseq!"
+ :bug-report/section-issues-btn-title "Segnala un difetto"
+ :bug-report/section-issues-desc "Se non puoi raccogliere informazioni aggiuntive, segnala il difetto direttamente."
+ :bug-report/section-issues-title "Oppure..."
+ :color/blue "Blu"
+ :color/gray "Grigio"
+ :color/green "Verde"
+ :color/pink "Rosa"
+ :color/purple "Viola"
+ :color/red "Rosso"
+ :color/yellow "Giallo"
+ :command.command-palette/toggle "Cerca una comando"
+ :command.dev/show-block-ast "(Dev) Mostra AST del blocco"
+ :command.dev/show-block-data "(Dev) Mostra dati del blocco"
+ :command.dev/show-page-ast "(Dev) Mostra AST della pagina"
+ :command.dev/show-page-data "(Dev) Mostra dati della pagina"
+ :command.editor/copy-page-url "Copia indirizzo della pagina"
+ :command.editor/insert-link "Link HTML"
+ :command.editor/new-whiteboard "Nuova lavagna"
+ :command.editor/select-parent "Seleziona blocco genitore"
+ :command.editor/toggle-number-list "Attiva/disattiva elenco numerato"
+ :command.editor/toggle-undo-redo-mode "Attiva/disattiva modalità disfare/rifare (globalmente o solo per la pagina)"
+ :command.go/all-graphs "Vai a tutti i grafi"
+ :command.go/electron-find-in-page "Cerca testo nella pagina"
+ :command.go/electron-jump-to-the-next "Vai al prossimo risultato nella ricerca"
+ :command.go/electron-jump-to-the-previous "Vai al precedente risultato nella ricerca"
+ :command.go/search-in-page "Cerca tra i blocchi nella pagina"
+ :command.go/whiteboards "Vai alle lavagne"
+ :command.graph/export-as-html "Esporta pagine pubbliche del grafo come HTML"
+ :command.graph/re-index "Re-indicizza il grafo corrente"
+ :command.misc/copy "Copia"
+ :command.pdf/find "PDF: cerca testo nel documento"
+ :command.sidebar/close-top "Chiudi l'elemento in alto nel pannello laterale destra"
+ :command.ui/clear-all-notifications "Cancella tutte le notifiche"
+ :command.ui/cycle-color "Cicla colore"
+ :command.ui/cycle-color-off "Disattiva cicla colore"
+ :command.ui/install-plugins-from-file "Installa plugin da plugins.edn"
+ :command.whiteboard/bring-forward "Sposta in avanti"
+ :command.whiteboard/bring-to-front "Sposta in fronte"
+ :command.whiteboard/connector "Connettore"
+ :command.whiteboard/ellipse "Ellisse"
+ :command.whiteboard/eraser "Gomma"
+ :command.whiteboard/group "Raggruppa selezione"
+ :command.whiteboard/highlighter "Evidenziatore"
+ :command.whiteboard/lock "Blocca selezione"
+ :command.whiteboard/pan "Muoviti"
+ :command.whiteboard/pencil "Matita"
+ :command.whiteboard/portal "Portale"
+ :command.whiteboard/rectangle "Rettangolo"
+ :command.whiteboard/reset-zoom "Reimposta zoom"
+ :command.whiteboard/select "Seleziona zoom"
+ :command.whiteboard/send-backward "Sposta indietro"
+ :command.whiteboard/send-to-back "Sposta in retro"
+ :command.whiteboard/text "Testo"
+ :command.whiteboard/toggle-grid "Attiva/disattiva griglia"
+ :command.whiteboard/ungroup "Dissocia selezione"
+ :command.whiteboard/unlock "Sblocca selezione"
+ :command.whiteboard/zoom-in "Aumenta zoom"
+ :command.whiteboard/zoom-out "Diminuisci zoom"
+ :command.whiteboard/zoom-to-fit "Centra disegno"
+ :command.whiteboard/zoom-to-selection "Centra selezione"
+ :command.window/close "Chiudi finestra"
+ :content/copy-block-url "Copia indirizzo del blocco"
+ :content/copy-export-as "Copia / Esporta come..."
+ :content/copy-ref "Copia riferimento"
+ :content/delete-ref "Elimina riferimento"
+ :content/replace-with-embed "Sostituisci con un'integrazione"
+ :content/replace-with-text "Sostituisci con testo"
+ :context-menu/input-template-name "Qual è il nome del modello?"
+ :context-menu/make-a-flashcard "Crea flashcard"
+ :context-menu/make-a-template "Crea modello"
+ :context-menu/preview-flashcard "Anteprima della flashcard"
+ :context-menu/template-exists-warning "Modello già esistente!"
+ :context-menu/template-include-parent-block "Includere il blocco genitore nel modello?"
+ :context-menu/toggle-number-list "Attiva/disattiva elenco numerato"
+ :dev/show-block-ast "(Dev) Mostra AST del blocco"
+ :dev/show-block-data "(Dev) Mostra dati del blocco"
+ :dev/show-page-ast "(Dev) Mostra AST della pagina"
+ :dev/show-page-data "(Dev) Mostra dati della pagina"
+ :editor/collapse-block-children "Collassa tutto"
+ :editor/cycle-todo "Alterna lo stato del TODO corrente"
+ :editor/delete-selection "Elimina blocchi selezionati"
+ :editor/expand-block-children "Espandi tutto"
+ :file/validate-existing-file-error "Pagina già esistente con un altro file: {1}, file corrente: {2}. Mantieni solo una pagina e re-indicizza il grafo."
+ :file-rn/all-action "Applica tutte le azioni! ({1})"
+ :file-rn/apply-rename "Rinomina!"
+ :file-rn/close-panel "Chiudi il pannello"
+ :file-rn/confirm-proceed "Aggiorna il formato!"
+ :file-rn/filename-desc-1 "Questa opzione configura il modo in cui le pagine sono salvate in file. Logseq salva una pagina in un file con lo stesso nome."
+ :file-rn/filename-desc-2 "Caratteri come \"/\" o \"?\" non sono ammessi nel nome di un file."
+ :file-rn/filename-desc-3 "Logseq sostituisce caratteri non ammessi con la loro codifica URL equivalente per renderli validi (per esempio \"?\" diventa \"%3F\")."
+ :file-rn/filename-desc-4 "Anche l'operatore di namespace \"/\" viene sostituito con \"___\" (triplo trattino basso) per motivi estetici."
+ :file-rn/format-deprecated "Stai usando un formato obsoleto. Aggiornare al formato più recente è fortemente consigliato. Ricorda di fare un backup dei tuoi dati e chiudere tutte le sessioni Logseq su altri dispositivi prima dell'operazione."
+ :file-rn/instruct-1 "Aggiornare il formato del nome di un file consiste di due passi:"
+ :file-rn/instruct-2 "1. Clicca "
+ :file-rn/instruct-3 "2. Segui le istruzioni che seguono per rinominare il file nel nuovo formato:"
+ :file-rn/legend "🟢 Azioni di rinomina facoltative; 🟡 Azioni di rinomina necessarie per evitare il cambio del titolo; 🔴 Modifica retro-incompatibile."
+ :file-rn/need-action "Le azioni di rinomina file sono suggerite per essere compatibili con il nuovo formato. È richiesta la re-indicizzazione su tutti i dispositivi quando i file rinominati vengono sincronizzati."
+ :file-rn/no-action "Ben fatto! Non ci sono altre azioni necessarie."
+ :file-rn/optional-rename "Suggerimento: "
+ :file-rn/or-select-actions " o rinomina i file singolarmente sotto, poi "
+ :file-rn/or-select-actions-2 ". Queste azioni non saranno disponibili dopo aver chiuso questo pannello."
+ :file-rn/otherwise-breaking "O il titolo diventerà"
+ :file-rn/re-index "La re-indicizzazione è fortemente consigliata dopo che i file rinominati su altri dispositivi vengono sincronizzati."
+ :file-rn/rename "rinominare file da \"{1}\" a \"{2}\""
+ :file-rn/select-confirm-proceed "Dev: formato di scrittura"
+ :file-rn/select-format "(Opzione modalità sviluppatore, Pericolo!) seleziona formato per i nomi dei file"
+ :file-rn/suggest-rename "Azione richiesta: "
+ :file-rn/unreachable-title "Attenzione! Il nome della pagina diventerà {1} sotto il formato corrente per i nomi dei file, salvo che la proprietà `title::` sia impostata manualmente"
+ :file-sync/connectivity-testing-failed "Prova della connettività fallita. Controlla le impostazione di rete. Prova indirizzi URL: "
+ :file-sync/rsapi-cannot-upload-err "Impossibile iniziare la sincronizzazione, controlla che la data e l'ora locali siano corrette."
+ :flashcards/modal-btn-forgotten "Dimenticata"
+ :flashcards/modal-btn-hide-answers "Nascondi risposte"
+ :flashcards/modal-btn-next-card "Prossima"
+ :flashcards/modal-btn-recall "C'è voluto un po' per ricordare"
+ :flashcards/modal-btn-remembered "Ricordata"
+ :flashcards/modal-btn-reset "Ripristina"
+ :flashcards/modal-btn-reset-tip "Ripristina questa carta in modo da poterla rivedere immediatamente."
+ :flashcards/modal-btn-show-answers "Mostra risposte"
+ :flashcards/modal-btn-show-clozes "Mostra cloze"
+ :flashcards/modal-current-total "Attuale/Totale"
+ :flashcards/modal-finished "Congratulazioni, hai ripassato tutte le carte in questo quiz, alla prossima!"
+ :flashcards/modal-overdue-total "In ritardo/totali"
+ :flashcards/modal-select-all "Tutte"
+ :flashcards/modal-select-switch "Passa a"
+ :flashcards/modal-toggle-preview-mode "Attiva/disattiva modalità anteprima"
+ :flashcards/modal-toggle-random-mode "Attiva/disattiva modalità casuale"
+ :flashcards/modal-welcome-desc-1 "Puoi aggiungere \"#card\" a qualsiasi blocco per trasformarlo in una carta o attivare \"/cloze\" per aggiungere cloze."
+ :flashcards/modal-welcome-desc-2 "Puoi "
+ :flashcards/modal-welcome-desc-3 "cliccare questo link"
+ :flashcards/modal-welcome-desc-4 " per controllare la documentazione."
+ :flashcards/modal-welcome-title "Tempo di creare una carta!"
+ :graph/all-graphs "Tutti i grafi"
+ :graph/local-graphs "Grafi locali:"
+ :graph/remote-graphs "Grafi remoti:"
+ :handbook/close "Chiudi"
+ :handbook/help-categories "Categorie di aiuto"
+ :handbook/home "Home"
+ :handbook/popular-topics "Argomenti popolari"
+ :handbook/search "Cerca"
+ :handbook/settings "Impostazioni"
+ :handbook/title "Aiuto"
+ :handbook/topics "Argomenti"
+ :header/go-back "Indietro"
+ :header/go-forward "Avanti"
+ :header/more "Altro"
+ :header/search "Cerca"
+ :header/toggle-left-sidebar "Attiva/disattiva il pannello laterale sinistro"
+ :help/awesome-logseq "Awesome Logseq"
+ :help/forum-community "Forum della comunità"
+ :help/roadmap "Tabella di marcia"
+ :help/search "Cerca pagine/blocchi/comandi"
+ :help/title-about "Chi siamo"
+ :help/title-community "Comunità"
+ :help/title-development "Sviluppo"
+ :help/title-terms "Termini"
+ :help/title-usage "Utilizzo"
+ :keymap/all "Tutti"
+ :keymap/conflicts-for-label "Scorciatoie in conflitto: "
+ :keymap/custom "Personalizzati"
+ :keymap/customize-for-label "Personalizza scorciatoie"
+ :keymap/disabled "Disabilitati"
+ :keymap/keystroke-filter "Digita scorciatoia per filtrare"
+ :keymap/keystroke-record-desc "Premi qualsiasi sequenza di tasti per filtrare le scorciatoie"
+ :keymap/keystroke-record-setup-label "Premi una qualsiasi sequenza di tasti per impostare una scorciatoia"
+ :keymap/restore-to-default "Ripristina alla predefinita"
+ :keymap/search "Cerca"
+ :keymap/total "Scorciatoie totali"
+ :keymap/unset "Non impostata"
+ :left-side-bar/create "Crea"
+ :left-side-bar/new-whiteboard "Nuova lavagna"
+ :left-side-bar/switch "Passa a:"
+ :linked-references/filter-search "Cerca nelle pagine collegate"
+ :notification/clear-all "Pulisci tutte"
+ :on-boarding/command-palette-quick-tour "Tour rapido delle funzionalità"
+ :on-boarding/importing-desc "Se sono in formato JSON, EDN o Markdown Logseq può lavorarci."
+ :on-boarding/importing-lsq-desc "Importa un esportazione EDN o JSON del tuo grafo Logseq"
+ :on-boarding/importing-main-desc "Puoi farlo anche dopo nell'app."
+ :on-boarding/importing-main-title "Importa note esistenti"
+ :on-boarding/importing-opml-desc " Importa file OPML"
+ :on-boarding/importing-roam-desc "Importa un esportazione JSON del tuo grafo Roam"
+ :on-boarding/importing-title "Hai già note che vuoi importare?"
+ :on-boarding/main-desc "Per prima cosa devi scegliere una cartella in cui Logseq conserverò i tuoi pensieri, idee e note."
+ :on-boarding/main-title (fn [] ["Benvenuti in " [:strong "Logseq!"]])
+ :on-boarding/quick-tour-btn-back "Indietro"
+ :on-boarding/quick-tour-btn-finish "Finito"
+ :on-boarding/quick-tour-btn-next "Avanti"
+ :on-boarding/quick-tour-btn-skip "Salta tour rapido"
+ :on-boarding/quick-tour-favorites-desc-1 "Fissa le tue pagine preferite tramite il `...` menù in ogni pagina."
+ :on-boarding/quick-tour-favorites-desc-2 "Abbiamo anche aggiunto alcune pagine modello per aiutarti a partire. Le puoi rimuovere quando cominci a scrivere le tue note."
+ :on-boarding/quick-tour-favorites-title "⭐️ Preferiti"
+ :on-boarding/quick-tour-help-desc "Puoi sempre cliccare qui per aiuto e informazioni su Logseq."
+ :on-boarding/quick-tour-help-title "❓ Aiuto"
+ :on-boarding/quick-tour-journal-page-desc-1 "Questa è la pagina di oggi del diario giornaliero. Qui puoi scaricare i tuoi pensieri, cose che hai imparato e idee. Non preoccuparti dell'organizzazione. Semplicemente scrivi e"
+ :on-boarding/quick-tour-journal-page-desc-2 "[[collega]]"
+ :on-boarding/quick-tour-journal-page-desc-3 "i tuoi pensieri."
+ :on-boarding/quick-tour-journal-page-title "📆 Pagina del diario giornaliero"
+ :on-boarding/quick-tour-left-sidebar-desc "Apri il pannello laterale sinistro per esplorare oggetti importanti in Logseq."
+ :on-boarding/quick-tour-left-sidebar-title "👀 Pannello laterale sinistro"
+ :on-boarding/quick-tour-steps "PASSO "
+ :on-boarding/section-app "Componenti interne dell'APP"
+ :on-boarding/section-assets "Grafica & Documenti"
+ :on-boarding/section-btn-desc "Apri una cartella esistente o creane una nuova"
+ :on-boarding/section-btn-title "Scegli una cartella"
+ :on-boarding/section-computer "computer"
+ :on-boarding/section-config "File di configurazione"
+ :on-boarding/section-desc "Nella cartella che scegli Logseq creerà 4 cartelle."
+ :on-boarding/section-journals "Note giornaliere"
+ :on-boarding/section-pages "PAGINE"
+ :on-boarding/section-phone "telefono"
+ :on-boarding/section-tip-1 "Ogni pagina è un file conservato solo sul tuo {1}."
+ :on-boarding/section-tip-2 "Protrai scegliere se sincronizzare più tardi."
+ :on-boarding/section-title "Come Logseq salva il tuo lavoro"
+ :on-boarding/tour-whiteboard-btn-back "Indietro"
+ :on-boarding/tour-whiteboard-btn-finish "Finito"
+ :on-boarding/tour-whiteboard-btn-next "Avanti"
+ :on-boarding/tour-whiteboard-home "{1} Residenza per le tue lavagne"
+ :on-boarding/tour-whiteboard-home-description "Le lavagne hanno una sezione dedicata nell'app dove puoi vederle con uno sguardo, crearne di nuove o eliminarle facilmente."
+ :on-boarding/tour-whiteboard-new "{1} Crea una nuova lavagna"
+ :on-boarding/tour-whiteboard-new-description "Ci sono diversi modi di creare una nuova lavagna. Uno è sempre qui nella dashboard."
+ :on-boarding/welcome-whiteboard-modal-description "Le lavagne sono un ottimo strumento per pensare e organizzare. Puoi piazzare qualsiasi pensiero da note già esistenti o da nuove, fianco a fianco su una tela dove si possono connettere e associare, creando nuovi modi di capire."
+ :on-boarding/welcome-whiteboard-modal-skip "Salta"
+ :on-boarding/welcome-whiteboard-modal-start "Crea lavagna"
+ :on-boarding/welcome-whiteboard-modal-title "Una nuova tela per i tuoi pensieri."
+ :page/illegal-page-name "Nome di pagina non ammesso!"
+ :page/logseq-is-having-a-problem "Logseq sta riscontrando un problema. Per tentare di farlo tornare a funzionare esegue i seguenti passi in ordine:"
+ :page/page-already-exists "La pagina “{1}” esiste già!"
+ :page/slide-view "Visualizza come diapositive"
+ :page/slide-view-tip-go-fullscreen (fn [] [[:span.opacity-70 "Suggerimento: premi "] [:code "f"] [:span.opacity-70 " per andare in schermo intero"]])
+ :page/something-went-wrong "Qualcosa è andato storto"
+ :page/step "Passo {1}"
+ :page/try "Prova"
+ :page/whiteboard-to-journal-error "Le pagine lavagna non possono essere rinominate con titoli di diario."
+ :pdf/auto-open-context-menu "Apri menù contestuale per le selezioni automaticamente"
+ :pdf/doc-metadata "Metadati del documento"
+ :pdf/hl-block-colored "Etichetta colorata per il blocco evidenziato"
+ :plugin/all-updated "Aggiornati tutti!"
+ :plugin/auto-check-for-updates "Controlla automaticamente per aggiornamenti"
+ :plugin/checking-for-updates "Controllando se ci sono plugin da aggiornare..."
+ :plugin/found-n-updates "Trovati {1} aggiornamenti"
+ :plugin/found-updates "Nuovi aggiornamenti"
+ :plugin/installed-plugin "Plugin installati: {1}"
+ :plugin/list-of-updates "Plugin da aggiornare: "
+ :plugin/open-logseq-dir "Apri"
+ :plugin/open-preferences "Apri preferenze"
+ :plugin/remote-error "Errore remoto: "
+ :plugin/search-plugin "Cerca plugin"
+ :plugin/security-warning "I plugin hanno accesso al tuo grafo e file locali, possono effettuare richieste in rete.\n       Possono anche causare corruzione e perdita di dati. Stiamo lavorando a regole di accesso appropriate sui tuoi grafi.\n       Nel frattempo, assicurati di fare backup dei tuoi grafi regolarmente e installa plugin solo quando puoi leggere e\n       capirne il codice sorgente."
+ :plugin/title "Titolo ({1})"
+ :plugin/up-to-date "È aggiornato {1}"
+ :plugin/update-all-selected "Aggiorna i selezionati"
+ :plugin/update-plugin "Aggiorna i plugin: {1} - {2}"
+ :plugin/updates-downloading "Scaricando aggiornamenti"
+ :plugin.install-from-file/menu-title "Installa da plugins.edn"
+ :plugin.install-from-file/notice "I seguenti plugin sostituiranno i seguenti plugin:"
+ :plugin.install-from-file/success "Tutti i plugin installati!"
+ :plugin.install-from-file/title "Installa plugin da plugins.edn"
+ :query/config-property-settings "Impostazioni per le proprietà di questa richiesta:"
+ :right-side-bar/flashcards "Flashcard"
+ :right-side-bar/history "(Dev) cronologia disfai/rifai"
+ :right-side-bar/history-global "globale"
+ :right-side-bar/history-pageonly "solo pagina corrente"
+ :right-side-bar/history-redos "Rifai"
+ :right-side-bar/history-undos "Disfai"
+ :right-side-bar/pane-close "Chiudi"
+ :right-side-bar/pane-close-all "Chiudi tutti"
+ :right-side-bar/pane-close-others "Chiudi altri"
+ :right-side-bar/pane-collapse "Collassa"
+ :right-side-bar/pane-collapse-all "Collassa tutti"
+ :right-side-bar/pane-collapse-others "Collassa altri"
+ :right-side-bar/pane-expand "Espandi"
+ :right-side-bar/pane-expand-all "Espandi tutti"
+ :right-side-bar/pane-more "Altro"
+ :right-side-bar/pane-open-as-page "Apri come pagina"
+ :right-side-bar/separator "Bordo per ridimensionare il pannello laterale destro"
+ :right-side-bar/toggle-right-sidebar "Attiva/disattiva pannello laterale destro"
+ :right-side-bar/whiteboards "Lavagne"
+ :search-item/no-result "Nessun risultato"
+ :search-item/page "Pagina"
+ :search-item/whiteboard "Lavagna"
+ :select/default-select-multiple "Seleziona uno o molteplici"
+ :settings-page/alpha-features "Funzionalità in Alpha"
+ :settings-page/app-updated "La tua app è aggiornata 🎉"
+ :settings-page/auto-chmod "Cambia permessi dei file automaticamente"
+ :settings-page/auto-chmod-desc "Disabilita per consentire la modifica da più utenti autorizzati dall'appartenenza al gruppo."
+ :settings-page/auto-expand-block-refs "Espandi riferimenti ai blocchi automaticamente quando si ingrandisce"
+ :settings-page/auto-expand-block-refs-tip "Questa opzione controlla se espandere i riferimenti ai blocchi automaticamente quando si ingrandisce."
+ :settings-page/beta-features "Funzionalità Beta"
+ :settings-page/changelog "Che c'è di nuovo?"
+ :settings-page/check-for-updates "Controlla se ci sono aggiornamenti"
+ :settings-page/checking "Controllando..."
+ :settings-page/clear-cache-warning "Pulire la chache scarterà i grafi aperti. Perderai modifiche non salvate."
+ :settings-page/custom-date-format-notification "Devi re-indicizzazione il grafo affinché questo cambiamento abbia effetto"
+ :settings-page/custom-date-format-warning "Re-indicizzazione richiesta! Altrimenti i riferimenti esistenti al diario non funzionerebbero."
+ :settings-page/custom-global-configuration "Configurazione globale personalizzata"
+ :settings-page/disable-sentry-desc "Logseq non raccoglierà mai il database del grafo locale e non venderà mai i tuoi dati."
+ :settings-page/edit-global-config-edn "Modifica config.edn globale"
+ :settings-page/edit-setting "Modifica"
+ :settings-page/enable-whiteboards "Lavagne"
+ :settings-page/filename-format "Formato nomi dei file"
+ :settings-page/git-desc-1 "Per vedere la cronologia delle pagine clicca i tre punti orizzontali in alto a destra e seleziona \"Cronologia della pagina\"."
+ :settings-page/git-desc-2 "Per gli utenti professionali Logseq supporta anche "
+ :settings-page/git-desc-3 " come controllo versione. Usa Git a tuo rischio. Eventuali problemi con Git non sono supportati dal team di Logseq."
+ :settings-page/git-tip "Se la sincronizzazione è abilitata puoi vedere la cronologia di modifica di una pagina direttamente. Questa sezione è solo per esperti."
+ :settings-page/login-prompt "Per avere accesso a nuove funzionalità prima di tutti gli altri devi essere uno sponsor di Open Collective oppure supportare Logseq, quindi devi prima accedere."
+ :settings-page/native-titlebar "Barra del titolo nativa"
+ :settings-page/native-titlebar-desc "Abilita la barra del titolo nativa su Windows e Linux."
+ :settings-page/preferred-outdenting-tip "Il lato sinistro mostra l'indentazione con impostazioni predefinite, e il lato destro mostra l'indentazione logica abilitata."
+ :settings-page/preferred-outdenting-tip-more "→ Scopri di più"
+ :settings-page/preferred-pasting-file "Preferisci incollare file"
+ :settings-page/preferred-pasting-file-hint "Quando abilitata, incollare un'immagine da internet scaricherà e inserirà l'immagine. Quando disabilitata, incollerà il link all'immagine."
+ :settings-page/revision "Revisione: "
+ :settings-page/show-full-blocks "Mostra tutte le righe del riferimento ad un blocco"
+ :settings-page/sync "Sincronizzazione"
+ :settings-page/sync-desc-1 "Clicca"
+ :settings-page/sync-desc-2 "qui"
+ :settings-page/sync-desc-3 "per istruzioni su come impostare e usare la sincronizzazione."
+ :settings-page/sync-diff-merge "Abilita l'unione intelligente durante la sincronizzazione"
+ :settings-page/sync-diff-merge-desc "Unisci cambiamenti locali con file remoti automaticamente quando avviene un conflitto, piuttosto che sovrascrivere il file remoto."
+ :settings-page/sync-diff-merge-warn "L'unione intelligente è attiva su un dispositivo solo dopo la prima sincronizzazione riuscita sul grafo del server remoto, nella nuova versione di Logseq. Abilitare su tutti i dispositivi per avere la migliore esperienza."
+ :settings-page/tab-account "Account"
+ :settings-page/tab-assets "Risorse"
+ :settings-page/tab-editor "Editor"
+ :settings-page/tab-features "Funzionalità"
+ :settings-page/tab-keymap "Scorciatoie"
+ :settings-page/theme-dark "scuro"
+ :settings-page/theme-light "chiaro"
+ :settings-page/theme-system "sistema"
+ :settings-page/update-available "Trovato una nuova versione "
+ :settings-page/update-error-1 "⚠️ Ops, qualcosa è andato storto!"
+ :settings-page/update-error-2 " Per favore, controlla il "
+ :settings-permission/start-granting "Concessione"
+ :shortcut.category/plugins "Plugin"
+ :shortcut.category/whiteboard "Lavagna"
+ :tips/all-done "Tutto fatto!"
+ :whiteboard/add-block-or-page "Aggiungi blocco o pagina"
+ :whiteboard/align-bottom "Allinea in basso"
+ :whiteboard/align-center-horizontally "Allinea al centro orizzontalmente"
+ :whiteboard/align-center-vertically "Allinea al centro verticalmente"
+ :whiteboard/align-left "Allinea a sinistra"
+ :whiteboard/align-right "Allinea a destra"
+ :whiteboard/align-top "Allinea in alto"
+ :whiteboard/arrow-head "Punta della freccia"
+ :whiteboard/auto-resize "Ridimensiona automaticamente"
+ :whiteboard/bold "Grassetto"
+ :whiteboard/cache-outdated "La cache è obsoleta. Clicca 'Re-indicizza' nel menù a discesa del grafo."
+ :whiteboard/circle "Cerchio"
+ :whiteboard/collapse "Collassa"
+ :whiteboard/color "Colore"
+ :whiteboard/connector "Connettore"
+ :whiteboard/copy "Copia"
+ :whiteboard/cut "Taglia"
+ :whiteboard/dashboard-card-created "Creato "
+ :whiteboard/dashboard-card-edited "Modificato "
+ :whiteboard/dashboard-card-new-whiteboard "Nuova lavagna"
+ :whiteboard/delete "Elimina"
+ :whiteboard/deselect-all "Deseleziona tutto"
+ :whiteboard/dev-print-shape-props "(Dev) Stampa proprietà della forma"
+ :whiteboard/distribute-horizontally "Distribuisci orizzontalmente"
+ :whiteboard/distribute-vertically "Distribuisci verticalmente"
+ :whiteboard/draw "Disegna"
+ :whiteboard/edit-pdf "Modifica PDF"
+ :whiteboard/eraser "Gomma"
+ :whiteboard/expand "Espandi"
+ :whiteboard/export "Esporta"
+ :whiteboard/extra-large "Molto grande"
+ :whiteboard/extra-small "Molto piccolo"
+ :whiteboard/fill "Riempi"
+ :whiteboard/flip-horizontally "Capovolgi orizzontalmente"
+ :whiteboard/flip-vertically "Capovolgi verticalmente"
+ :whiteboard/group "Raggruppa"
+ :whiteboard/highlight "Evidenzia"
+ :whiteboard/huge "Enorme"
+ :whiteboard/italic "Corsivo"
+ :whiteboard/large "Grande"
+ :whiteboard/link "Link"
+ :whiteboard/link-to-any-page-or-block "Collega a qualsiasi pagina o blocco"
+ :whiteboard/lock "Blocca"
+ :whiteboard/medium "Medio"
+ :whiteboard/move-to-back "Sposta in retro"
+ :whiteboard/move-to-front "Sposta in fronte"
+ :whiteboard/new-block "Nuovo blocco:"
+ :whiteboard/new-block-no-colon "Nuovo blocco"
+ :whiteboard/new-page "Nuova pagina:"
+ :whiteboard/new-whiteboard "Nuova lavagna"
+ :whiteboard/opacity "Opacità"
+ :whiteboard/open-page "Apri pagina"
+ :whiteboard/open-page-in-sidebar "Apri pagina nel pannello laterale"
+ :whiteboard/open-twitter-url "Apri link Twitter"
+ :whiteboard/open-website-url "Open sito internet"
+ :whiteboard/open-youtube-url "Open link YouTube"
+ :whiteboard/pack-into-rectangle "Forma rettangolo compresso"
+ :whiteboard/pan "Muovi"
+ :whiteboard/paste "Incolla"
+ :whiteboard/paste-as-link "Incolla come link"
+ :whiteboard/rectangle "Rettangolo"
+ :whiteboard/redo "Rifai"
+ :whiteboard/references "Riferimento"
+ :whiteboard/reload "Ricarica"
+ :whiteboard/remove-link "Rimuovi link"
+ :whiteboard/scale-level "Scala"
+ :whiteboard/search-only-blocks "Cerca solo blocchi"
+ :whiteboard/search-only-pages "Cerca solo pagine"
+ :whiteboard/select "Seleziona"
+ :whiteboard/select-all "Seleziona tutto"
+ :whiteboard/select-custom-color "Seleziona colore personalizzato"
+ :whiteboard/shape "Forma"
+ :whiteboard/shape-quick-links "Link rapidi a forma"
+ :whiteboard/small "Piccolo"
+ :whiteboard/snap-to-grid "Aderisci alla griglia"
+ :whiteboard/start-typing-to-search "Inizia a scrivere per cercare..."
+ :whiteboard/stroke-type "Tratto"
+ :whiteboard/text "Testo"
+ :whiteboard/toggle-grid "Attiva/disattiva griglia"
+ :whiteboard/toggle-pen-mode "Attiva/disattiva modalità penna"
+ :whiteboard/triangle "Triangolo"
+ :whiteboard/twitter-url "Indirizzo Twitter"
+ :whiteboard/undo "Disfai"
+ :whiteboard/ungroup "Annulla raggruppamento"
+ :whiteboard/unlock "Blocca"
+ :whiteboard/website-url "Link sito internet"
+ :whiteboard/youtube-url "Link YouTube"
+ :whiteboard/zoom-in "Aumenta zoom"
+ :whiteboard/zoom-out "Diminuisci zoom"
+ :whiteboard/zoom-to-fit "Centra contenuto"
+ :window/close "Chiudi"
+ :window/exit-fullscreen "Esci da schermo intero"
+ :window/maximize "Massimizza"
+ :window/minimize "Minimizza"
+ :window/restore "Ripristina"}

+ 42 - 8
src/resources/dicts/ja.edn

@@ -17,6 +17,14 @@
  :on-boarding/tour-whiteboard-home-description "すぐに見つけられるように、また簡単に新規作成したり削除したりできるように、ホワイトボードは専用の欄が用意されています。"
  :on-boarding/tour-whiteboard-home-description "すぐに見つけられるように、また簡単に新規作成したり削除したりできるように、ホワイトボードは専用の欄が用意されています。"
  :on-boarding/tour-whiteboard-new "{1} ホワイトボードを新規作成する"
  :on-boarding/tour-whiteboard-new "{1} ホワイトボードを新規作成する"
  :on-boarding/tour-whiteboard-new-description "ホワイトボードの新規作成には、複数の方法があります。ダッシュボードのちょうどこの位置に配置されたボタンもその一つです。"
  :on-boarding/tour-whiteboard-new-description "ホワイトボードの新規作成には、複数の方法があります。ダッシュボードのちょうどこの位置に配置されたボタンもその一つです。"
+ :handbook/title "ヘルプ"
+ :handbook/topics "話題"
+ :handbook/popular-topics "人気のある話題"
+ :handbook/help-categories "ヘルプのカテゴリ"
+ :handbook/search "検索"
+ :handbook/home "ホーム"
+ :handbook/settings "設定"
+ :handbook/close "閉じる"
  :on-boarding/tour-whiteboard-btn-next "進む"
  :on-boarding/tour-whiteboard-btn-next "進む"
  :on-boarding/tour-whiteboard-btn-back "戻る"
  :on-boarding/tour-whiteboard-btn-back "戻る"
  :on-boarding/tour-whiteboard-btn-finish "完了"
  :on-boarding/tour-whiteboard-btn-finish "完了"
@@ -102,6 +110,7 @@
  :help/shortcuts "キーボードショートカット"
  :help/shortcuts "キーボードショートカット"
  :help/shortcuts-triggers "トリガー"
  :help/shortcuts-triggers "トリガー"
  :help/shortcut "ショートカット"
  :help/shortcut "ショートカット"
+ :help/search "ページ、ブロック、コマンドを検索"
  :help/slash-autocomplete "スラッシュで自動補完"
  :help/slash-autocomplete "スラッシュで自動補完"
  :help/reference-autocomplete "ページ参照の自動補完"
  :help/reference-autocomplete "ページ参照の自動補完"
  :help/block-reference "ブロック参照"
  :help/block-reference "ブロック参照"
@@ -162,7 +171,7 @@
  :page/slide-view-tip-go-fullscreen (fn [] [[:span.opacity-70 "お役立ち情報:"] [:code "f"] [:span.opacity-70 "を押すことでフルスクリーンにできます"]])
  :page/slide-view-tip-go-fullscreen (fn [] [[:span.opacity-70 "お役立ち情報:"] [:code "f"] [:span.opacity-70 "を押すことでフルスクリーンにできます"]])
  :page/delete-confirmation "このページとページのファイルを削除してもよいですか?"
  :page/delete-confirmation "このページとページのファイルを削除してもよいですか?"
  :page/open-in-finder "ディレクトリで開く"
  :page/open-in-finder "ディレクトリで開く"
- :page/open-with-default-app "デフォルトのアプリで開く"
+ :page/open-with-default-app "既定のアプリで開く"
  :page/make-public "パブリッシュのため公開する"
  :page/make-public "パブリッシュのため公開する"
  :page/version-history "ページ履歴の確認"
  :page/version-history "ページ履歴の確認"
  :page/open-backup-directory "ページのバックアップディレクトリを開く"
  :page/open-backup-directory "ページのバックアップディレクトリを開く"
@@ -254,7 +263,7 @@
  :context-menu/input-template-name "このテンプレートの名前は?"
  :context-menu/input-template-name "このテンプレートの名前は?"
  :context-menu/template-include-parent-block "テンプレートに親ブロックを含めますか?"
  :context-menu/template-include-parent-block "テンプレートに親ブロックを含めますか?"
  :context-menu/template-exists-warning "テンプレートが既に存在します。"
  :context-menu/template-exists-warning "テンプレートが既に存在します。"
- :settings-page/git-tip "もしLogseq Syncが有効なら、ページの編集履歴は直接見ることができます。この節は技術に精通している人のためのものになります。"
+ :settings-page/git-tip "Logseq Syncが有効な場合、ページの編集履歴を直接見ることができます。この節は技術に精通している人のためのものになります。"
  :settings-page/git-desc-1 "ページの編集履歴を見たい場合は、右上の端にある横向き三点ボタンをクリックし、「ページ履歴の確認」を選択してください。"
  :settings-page/git-desc-1 "ページの編集履歴を見たい場合は、右上の端にある横向き三点ボタンをクリックし、「ページ履歴の確認」を選択してください。"
  :settings-page/git-desc-2 "玄人なユーザーには、バージョンコントロールとして"
  :settings-page/git-desc-2 "玄人なユーザーには、バージョンコントロールとして"
  :settings-page/git-desc-3 " の利用も用意しています。Gitの利用はご自身の責任で行ってください。一般的なGitの問題について、Logseqチームはサポートしません。"
  :settings-page/git-desc-3 " の利用も用意しています。Gitの利用はご自身の責任で行ってください。一般的なGitの問題について、Logseqチームはサポートしません。"
@@ -279,7 +288,7 @@
  :settings-page/disable-sentry "使用状況データと診断内容をLogseqへ送信します。"
  :settings-page/disable-sentry "使用状況データと診断内容をLogseqへ送信します。"
  :settings-page/disable-sentry-desc "Logseqはあなたのローカルなグラフを決して取得しませんし、あなたのデータを売ることも決してありません。 "
  :settings-page/disable-sentry-desc "Logseqはあなたのローカルなグラフを決して取得しませんし、あなたのデータを売ることも決してありません。 "
  :settings-page/preferred-outdenting "論理的なアウトデント"
  :settings-page/preferred-outdenting "論理的なアウトデント"
- :settings-page/preferred-outdenting-tip "左側はデフォルトの設定のアウトデントを示しています。右側は論理的なアウトデントを有効にした場合のアウトデントを示しています。"
+ :settings-page/preferred-outdenting-tip "左側は既定のままの場合のアウトデントを示しています。右側は論理的なアウトデントを有効にした場合のアウトデントを示しています。"
  :settings-page/preferred-outdenting-tip-more "→ もっと学ぶ"
  :settings-page/preferred-outdenting-tip-more "→ もっと学ぶ"
  :settings-page/show-full-blocks "ブロック参照の全ての行を表示する"
  :settings-page/show-full-blocks "ブロック参照の全ての行を表示する"
  :settings-page/auto-expand-block-refs "ズームインしたとき、ブロック参照を自動で展開する"
  :settings-page/auto-expand-block-refs "ズームインしたとき、ブロック参照を自動で展開する"
@@ -296,7 +305,7 @@
  :settings-page/enable-tooltip "ツールチップ"
  :settings-page/enable-tooltip "ツールチップ"
  :settings-page/enable-journals "日誌"
  :settings-page/enable-journals "日誌"
  :settings-page/enable-all-pages-public "パブリッシュ時には全てのページを公開する"
  :settings-page/enable-all-pages-public "パブリッシュ時には全てのページを公開する"
- :settings-page/home-default-page "デフォルトのホームページを設定"
+ :settings-page/home-default-page "起動時のホームページを設定"
  :settings-page/clear-cache "キャッシュをクリア"
  :settings-page/clear-cache "キャッシュをクリア"
  :settings-page/clear "クリア"
  :settings-page/clear "クリア"
  :settings-page/clear-cache-warning "キャッシュをクリアすると、開いているグラフは破棄されます。保存されていない変更は失われます。"
  :settings-page/clear-cache-warning "キャッシュをクリアすると、開いているグラフは破棄されます。保存されていない変更は失われます。"
@@ -305,6 +314,7 @@
  :settings-page/current-version "現在のバージョン"
  :settings-page/current-version "現在のバージョン"
  :settings-page/tab-general "一般"
  :settings-page/tab-general "一般"
  :settings-page/tab-editor "エディタ"
  :settings-page/tab-editor "エディタ"
+ :settings-page/tab-keymap "キーマップ"
  :settings-page/tab-version-control "バージョンコントロール"
  :settings-page/tab-version-control "バージョンコントロール"
  :settings-page/tab-account "アカウント"
  :settings-page/tab-account "アカウント"
  :settings-page/tab-advanced "高度な設定"
  :settings-page/tab-advanced "高度な設定"
@@ -316,7 +326,7 @@
  :settings-page/filename-format "ファイル名の書式"
  :settings-page/filename-format "ファイル名の書式"
  :settings-page/alpha-features "アルファ機能"
  :settings-page/alpha-features "アルファ機能"
  :settings-page/beta-features "ベータ機能"
  :settings-page/beta-features "ベータ機能"
- :settings-page/login-prompt "新しい機能を誰よりも早く使いたい場合は、LogseqのOpen Collective Sponsorか後援者になっ上で、ログインしてください。"
+ :settings-page/login-prompt "新しい機能を誰よりも早く使いたい場合は、LogseqのOpen Collective Sponsorか後援者になっ上で、ログインしてください。"
  :settings-page/sync "Sync(同期)"
  :settings-page/sync "Sync(同期)"
  :settings-page/sync-desc-1 "Syncを設定し、使うための手引きを見るには、"
  :settings-page/sync-desc-1 "Syncを設定し、使うための手引きを見るには、"
  :settings-page/sync-desc-2 "ここ"
  :settings-page/sync-desc-2 "ここ"
@@ -337,6 +347,8 @@
  :settings-page/update-error-2 "次のリンクをご確認ください:"
  :settings-page/update-error-2 "次のリンクをご確認ください:"
  :settings-permission/start-granting "認証"
  :settings-permission/start-granting "認証"
 
 
+ :settings-page/auto-chmod "ファイルの権限を自動で変更する"
+ :settings-page/auto-chmod-desc "グループ メンバーシップを用いて、複数人のユーザに編集を許可する権限を与えたい場合、この機能を無効にしてください。"
  :yes "はい"
  :yes "はい"
 
 
  :submit "投稿"
  :submit "投稿"
@@ -445,6 +457,7 @@
  :whiteboard/dashboard-card-edited "編集:"
  :whiteboard/dashboard-card-edited "編集:"
  :whiteboard/toggle-grid "格子の表示/非表示"
  :whiteboard/toggle-grid "格子の表示/非表示"
  :whiteboard/snap-to-grid "図形を格子に合わせる"
  :whiteboard/snap-to-grid "図形を格子に合わせる"
+ :whiteboard/toggle-pen-mode "ペンモードを切り替える"
  :flashcards/modal-welcome-title "フラッシュカードを作成する時間です!"
  :flashcards/modal-welcome-title "フラッシュカードを作成する時間です!"
  :flashcards/modal-welcome-desc-1 "ブロックに「#card」を追加することでカードとすることができます。また、「/cloze」で穴埋めを追加できます。"
  :flashcards/modal-welcome-desc-1 "ブロックに「#card」を追加することでカードとすることができます。また、「/cloze」で穴埋めを追加できます。"
  :flashcards/modal-welcome-desc-2 "ドキュメントを見るには、"
  :flashcards/modal-welcome-desc-2 "ドキュメントを見るには、"
@@ -605,6 +618,7 @@
  :file-sync/other-user-graph "現在のローカルグラフは他のユーザーのリモートグラフにバインドされています。同期を開始できません。"
  :file-sync/other-user-graph "現在のローカルグラフは他のユーザーのリモートグラフにバインドされています。同期を開始できません。"
  :file-sync/graph-deleted "現在のリモートグラフが削除されました"
  :file-sync/graph-deleted "現在のリモートグラフが削除されました"
  :file-sync/rsapi-cannot-upload-err "同期が始まらない場合、お使いのコンピュータの時間が正しいか確認してください。"
  :file-sync/rsapi-cannot-upload-err "同期が始まらない場合、お使いのコンピュータの時間が正しいか確認してください。"
+ :file-sync/connectivity-testing-failed "ネットワーク接続のテストに失敗しました。ネットワークの設定を確認してください。テストのURLは以下になります:"
 
 
  :notification/clear-all "全てクリア"
  :notification/clear-all "全てクリア"
 
 
@@ -618,6 +632,20 @@
  :shortcut.category/whiteboard "ホワイトボード"
  :shortcut.category/whiteboard "ホワイトボード"
  :shortcut.category/others "その他"
  :shortcut.category/others "その他"
  :shortcut.category/plugins "プラグイン"
  :shortcut.category/plugins "プラグイン"
+
+ :keymap/all "全て"
+ :keymap/disabled "無効"
+ :keymap/unset "未定義"
+ :keymap/custom "カスタム"
+ :keymap/search "検索"
+ :keymap/total "ショートカット数:"
+ :keymap/keystroke-filter "キーを打鍵して絞り込む"
+ :keymap/keystroke-record-desc "キーの列を順に押してください。ショートカットを絞り込みます。"
+ :keymap/keystroke-record-setup-label "ショートカットにセットしたいキーの列を順に押してください。"
+ :keymap/restore-to-default "システムの既定値に戻す"
+ :keymap/customize-for-label "ショートカットをカスタマイズ"
+ :keymap/conflicts-for-label "キーマップが衝突しています:"
+
  :window/minimize "最小化"
  :window/minimize "最小化"
  :window/maximize "最大化"
  :window/maximize "最大化"
  :window/restore "復元"
  :window/restore "復元"
@@ -677,7 +705,7 @@
  :command.editor/replace-block-reference-at-point "この場所でブロック参照をそのコンテンツで置き換え"
  :command.editor/replace-block-reference-at-point "この場所でブロック参照をそのコンテンツで置き換え"
  :command.editor/paste-text-in-one-block-at-point "カーソル位置へ文字列として貼り付け"
  :command.editor/paste-text-in-one-block-at-point "カーソル位置へ文字列として貼り付け"
  :command.editor/insert-youtube-timestamp "YouTubeのタイプスタンプを挿入"
  :command.editor/insert-youtube-timestamp "YouTubeのタイプスタンプを挿入"
- :command.editor/cycle-todo "現在の項目の TODO 状態をローテートさせる"
+ :command.editor/cycle-todo "現在の項目の TODO 状態を循環させる"
  :command.editor/up "カーソル上移動 / 上を選択"
  :command.editor/up "カーソル上移動 / 上を選択"
  :command.editor/down "カーソル下移動 / 下を選択"
  :command.editor/down "カーソル下移動 / 下を選択"
  :command.editor/left "カーソル左移動 / 左を選択"
  :command.editor/left "カーソル左移動 / 左を選択"
@@ -735,6 +763,8 @@
  :command.go/electron-jump-to-the-next "文字列検索で次を検索"
  :command.go/electron-jump-to-the-next "文字列検索で次を検索"
  :command.go/electron-jump-to-the-previous "文字列検索で前を検索"
  :command.go/electron-jump-to-the-previous "文字列検索で前を検索"
  :command.go/search "検索"
  :command.go/search "検索"
+ :command.command-palette/toggle "検索コマンド"
+ :command.go/search-in-page "ページ内のブロックを検索"
  :command.go/journals "日誌"
  :command.go/journals "日誌"
  :command.go/backward "戻る"
  :command.go/backward "戻る"
  :command.go/forward "前へ"
  :command.go/forward "前へ"
@@ -767,8 +797,11 @@
  :command.ui/toggle-help "ヘルプの表示/非表示"
  :command.ui/toggle-help "ヘルプの表示/非表示"
  :command.ui/toggle-theme "テーマの切り替え"
  :command.ui/toggle-theme "テーマの切り替え"
  :command.ui/toggle-contents "目次の開閉"
  :command.ui/toggle-contents "目次の開閉"
+ :command.ui/cycle-color-off "循環させた色を元に戻す"
+ :command.ui/cycle-color "色を循環させる"
+
  :command.command/toggle-favorite "お気に入りへ追加/削除"
  :command.command/toggle-favorite "お気に入りへ追加/削除"
- :command.editor/open-file-in-default-app "ファイルを規定のアプリで開く"
+ :command.editor/open-file-in-default-app "ファイルを定のアプリで開く"
  :command.editor/open-file-in-directory "ファイルをディレクトリで開く"
  :command.editor/open-file-in-directory "ファイルをディレクトリで開く"
  :command.editor/copy-current-file "現在のファイルをコピー"
  :command.editor/copy-current-file "現在のファイルをコピー"
  :command.editor/copy-page-url "ページのURLをコピー"
  :command.editor/copy-page-url "ページのURLをコピー"
@@ -782,4 +815,5 @@
  :command.dev/show-block-data "(開発) ブロックのデータを表示"
  :command.dev/show-block-data "(開発) ブロックのデータを表示"
  :command.dev/show-block-ast "(開発) ブロックの抽象構文木の表示"
  :command.dev/show-block-ast "(開発) ブロックの抽象構文木の表示"
  :command.dev/show-page-data "(開発) ページのデータを表示"
  :command.dev/show-page-data "(開発) ページのデータを表示"
- :command.dev/show-page-ast "(開発) ページの抽象構文木の表示"}
+ :command.dev/show-page-ast "(開発) ページの抽象構文木の表示"
+ :command.window/close "ウィンドウを閉じる"}

+ 2 - 2
src/resources/tutorials/tutorial-it.md

@@ -11,8 +11,8 @@ Digita `/` per mostrare tutti i comandi.
 - 1. Creiamo una pagina chiamata [[Come prendere appunti fittizi?]]. Puoi fare clic su di esso per andare a quella pagina, oppure puoi "Maiusc + clic" per aprirlo nella barra laterale destra! Ora dovresti vedere sia _Riferimenti collegati_ che _Riferimenti non collegati_.
 - 1. Creiamo una pagina chiamata [[Come prendere appunti fittizi?]]. Puoi fare clic su di esso per andare a quella pagina, oppure puoi "Maiusc + clic" per aprirlo nella barra laterale destra! Ora dovresti vedere sia _Riferimenti collegati_ che _Riferimenti non collegati_.
 - 2. Facciamo riferimento ad alcuni blocchi su [[Come prendere appunti fittizi?]], puoi fare `Maiusc+Clic` su qualsiasi riferimento di blocco per aprirlo nella barra laterale destra. Prova a fare
 - 2. Facciamo riferimento ad alcuni blocchi su [[Come prendere appunti fittizi?]], puoi fare `Maiusc+Clic` su qualsiasi riferimento di blocco per aprirlo nella barra laterale destra. Prova a fare
 alcune modifiche sulla barra laterale destra, verranno modificati anche quei blocchi di riferimento!
 alcune modifiche sulla barra laterale destra, verranno modificati anche quei blocchi di riferimento!
-    - ((5f713e91-8a3c-4b04-a33a-c39482428e2d)) : This is a block reference.
-    - ((5f713ea8-8cba-403d-ac00-9964b1ec7190)) : This is another block reference.
+    - ((5f713e91-8a3c-4b04-a33a-c39482428e2d)) : Questo è un riferimento ad un blocco.
+    - ((5f713ea8-8cba-403d-ac00-9964b1ec7190)) : Questo è un altro riferimento ad un blocco.
 - 3. Supportate i tag?
 - 3. Supportate i tag?
     - Naturalmente, questo è un tag #falso.
     - Naturalmente, questo è un tag #falso.
 - 4. Supportate le azioni come todo/doing/done e le priorità?
 - 4. Supportate le azioni come todo/doing/done e le priorità?

+ 122 - 0
src/test/frontend/db/db_based_model_test.cljs

@@ -0,0 +1,122 @@
+(ns frontend.db.db-based-model-test
+  (:require [cljs.test :refer [use-fixtures deftest is testing]]
+            [frontend.db.model :as model]
+            [frontend.db :as db]
+            [frontend.test.helper :as test-helper]
+            [datascript.core :as d]
+            [frontend.handler.db-based.property :as db-property-handler]
+            [frontend.handler.page :as page-handler]
+            [frontend.handler.editor :as editor-handler]))
+
+(def repo test-helper/test-db-name-db-version)
+
+(def init-data (test-helper/initial-test-page-and-blocks))
+(defn start-and-destroy-db
+  [f]
+  (test-helper/db-based-start-and-destroy-db
+   f
+   {:init-data (fn [conn] (d/transact! conn init-data))}))
+
+(def fbid (:block/uuid (second init-data)))
+(def sbid (:block/uuid (nth init-data 2)))
+
+(use-fixtures :each start-and-destroy-db)
+
+(deftest get-all-properties-test
+  (db-property-handler/set-block-property! repo fbid "property-1" "value" {})
+  (db-property-handler/set-block-property! repo fbid "property-2" "1" {})
+  (is (= '("property-1" "property-2") (model/get-all-properties))))
+
+(deftest get-block-property-values-test
+  (db-property-handler/set-block-property! repo fbid "property-1" "value 1" {})
+  (db-property-handler/set-block-property! repo sbid "property-1" "value 2" {})
+  (let [property (db/entity [:block/name "property-1"])]
+    (is (= (map second (model/get-block-property-values (:block/uuid property)))
+           ["value 1" "value 2"]))))
+
+(deftest get-db-property-values-test
+  (db-property-handler/set-block-property! repo fbid "property-1" "1" {})
+  (db-property-handler/set-block-property! repo sbid "property-1" "2" {})
+  (is (= [1 2] (model/get-db-property-values repo "property-1"))))
+
+(deftest get-db-property-values-test-with-pages
+  (let [opts {:redirect? false :create-first-block? false}
+        _ (page-handler/create! "page1" opts)
+        _ (page-handler/create! "page2" opts)
+        p1id (:block/uuid (db/entity [:block/name "page1"]))
+        p2id (:block/uuid (db/entity [:block/name "page2"]))]
+    (db-property-handler/upsert-property! repo "property-1" {:type :page} {})
+    (db-property-handler/set-block-property! repo fbid "property-1" p1id {})
+    (db-property-handler/set-block-property! repo sbid "property-1" p2id {})
+    (is (= '("[[page1]]" "[[page2]]") (model/get-db-property-values repo "property-1")))))
+
+(deftest get-all-classes-test
+  (let [opts {:redirect? false :create-first-block? false :class? true}
+        _ (page-handler/create! "class1" opts)
+        _ (page-handler/create! "class2" opts)]
+    (is (= ["class1" "class2"] (map first (model/get-all-classes repo))))))
+
+(deftest get-class-objects-test
+  (let [opts {:redirect? false :create-first-block? false :class? true}
+        _ (page-handler/create! "class1" opts)
+        class (db/entity [:block/name "class1"])
+        _ (editor-handler/save-block! repo fbid "Block 1 #class1")]
+    (is (= (model/get-class-objects repo (:db/id class))
+           [(:db/id (db/entity [:block/uuid fbid]))]))
+
+    (testing "namespace classes"
+      (page-handler/create! "class2" opts)
+      ;; set class2's parent to class1
+      (let [class2 (db/entity [:block/name "class2"])]
+        (db/transact! [{:db/id (:db/id class2)
+                        :block/namespace (:db/id class)}]))
+      (editor-handler/save-block! repo sbid "Block 2 #class2")
+      (is (= (model/get-class-objects repo (:db/id class))
+           [(:db/id (db/entity [:block/uuid fbid]))
+            (:db/id (db/entity [:block/uuid sbid]))])))))
+
+(deftest get-classes-with-property-test
+  (let [opts {:redirect? false :create-first-block? false :class? true}
+        _ (page-handler/create! "class1" opts)
+        _ (page-handler/create! "class2" opts)
+        class1 (db/entity [:block/name "class1"])
+        class2 (db/entity [:block/name "class2"])]
+    (db-property-handler/upsert-property! repo "property-1" {:type :page} {})
+    (db-property-handler/class-add-property! repo (:block/uuid class1) "property-1")
+    (db-property-handler/class-add-property! repo (:block/uuid class2) "property-1")
+    (let [property (db/entity [:block/name "property-1"])
+          class-ids (model/get-classes-with-property (:block/uuid property))]
+      (is (= class-ids [(:db/id class1) (:db/id class2)])))))
+
+(deftest get-tag-blocks-test
+  (let [opts {:redirect? false :create-first-block? false :class? true}
+        _ (page-handler/create! "class1" opts)
+        _ (editor-handler/save-block! repo fbid "Block 1 #class1")
+        _ (editor-handler/save-block! repo sbid "Block 2 #class1")]
+    (is
+     (= (model/get-tag-blocks repo "class1")
+        [(:db/id (db/entity [:block/uuid fbid]))
+         (:db/id (db/entity [:block/uuid sbid]))]))))
+
+(deftest hidden-page-test
+  (let [opts {:redirect? false :create-first-block? false}
+        _ (page-handler/create! "page 1" opts)]
+    (is (false? (model/hidden-page? (db/entity [:block/name "page 1"]))))
+    (is (false? (model/hidden-page? "$$$test")))
+    (is (true? (model/hidden-page? (str "$$$" (random-uuid)))))))
+
+(deftest get-namespace-children-test
+  (let [opts {:redirect? false :create-first-block? false :class? true}
+        _ (page-handler/create! "class1" opts)
+        _ (page-handler/create! "class2" opts)
+        _ (page-handler/create! "class3" opts)
+        class1 (db/entity [:block/name "class1"])
+        class2 (db/entity [:block/name "class2"])
+        class3 (db/entity [:block/name "class3"])
+        _ (db/transact! [{:db/id (:db/id class2)
+                          :block/namespace (:db/id class1)}
+                         {:db/id (:db/id class3)
+                          :block/namespace (:db/id class2)}])]
+    (is
+     (= (model/get-namespace-children repo (:db/id (db/entity [:block/name "class1"])))
+        [(:db/id class2) (:db/id class3)]))))

+ 0 - 25
src/test/frontend/db/outliner_test.cljs

@@ -15,20 +15,6 @@
         result (outliner/get-by-id conn [:block/uuid block-id])]
         result (outliner/get-by-id conn [:block/uuid block-id])]
     (is (= block-id (:block/uuid result)))))
     (is (= block-id (:block/uuid result)))))
 
 
-;; (deftest test-get-by-parent-&-left
-;;   (let [conn (core-test/get-current-conn)
-;;         data [{:block/uuid "1"}
-;;               {:block/uuid "2"
-;;                :block/parent [:block/uuid "1"]
-;;                :block/left [:block/uuid "1"]}
-;;               {:block/uuid "3"
-;;                :block/parent [:block/uuid "1"]
-;;                :block/left [:block/uuid "2"]}]
-;;         _ (d/transact! conn data)
-;;         result (outliner/get-by-parent-&-left
-;;                  @conn [:block/uuid "1"] [:block/uuid "2"])]
-;;     (is (= "3" (:block/uuid result)))))
-
 (deftest test-get-by-parent-id
 (deftest test-get-by-parent-id
   (let [conn (core-test/get-current-conn)
   (let [conn (core-test/get-current-conn)
         data [{:block/uuid "1"}
         data [{:block/uuid "1"}
@@ -42,14 +28,3 @@
         r (d/q outliner/get-by-parent-id @conn [:block/uuid "1"])
         r (d/q outliner/get-by-parent-id @conn [:block/uuid "1"])
         result (flatten r)]
         result (flatten r)]
     (is (= ["2" "3"] (mapv :block/uuid result)))))
     (is (= ["2" "3"] (mapv :block/uuid result)))))
-
-(deftest test-retract
-  (let [conn (core-test/get-current-conn)
-        data [{:block/uuid "1"}
-              {:block/uuid "2"
-               :block/parent [:block/uuid "1"]
-               :block/left [:block/uuid "1"]}]
-        _ (d/transact! conn data)
-        _ (outliner/del-block conn [:block/uuid "2"])
-        result (d/entity @conn [:block/uuid "2"])]
-    (is (nil? result))))

部分文件因为文件数量过多而无法显示