Browse Source

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

Tienson Qin 1 year ago
parent
commit
ea3f5973b7
100 changed files with 2280 additions and 1303 deletions
  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.result query-result
              frontend.components.class class-component
+             frontend.components.property property-component
              frontend.config config
              frontend.date date
              frontend.db db
@@ -123,6 +124,7 @@
              logseq.common.config common-config
              logseq.db.frontend.property db-property
              logseq.db.frontend.property.type db-property-type
+             logseq.db.frontend.property.util db-property-util
              logseq.db.frontend.rules rules
              logseq.db.frontend.schema db-schema
              logseq.db.sqlite.db sqlite-db

+ 2 - 2
android/app/build.gradle

@@ -7,8 +7,8 @@ android {
         applicationId "com.logseq.app"
         minSdkVersion rootProject.ext.minSdkVersion
         targetSdkVersion rootProject.ext.targetSdkVersion
-        versionCode 73
-        versionName "0.9.20"
+        versionCode 74
+        versionName "0.10.0"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         aaptOptions {
              // 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/"]
                   :extra-deps  {org.clojure/clojurescript        {:mvn/version "1.11.54"}
                                 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"}
                                 tortue/spy                       {:mvn/version "2.14.0"}}
                   :main-opts   ["-m" "shadow.cljs.devtools.cli"]}
@@ -53,7 +53,7 @@
                                 pjstadig/humane-test-output      {:mvn/version "0.11.0"}
                                 org.clojars.knubie/cljs-run-test {:mvn/version "1.0.1"}
                                 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"]}
 
            :bench {:extra-paths ["src/bench/"]

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

@@ -168,7 +168,28 @@
 
   (if (is-file-url? base)
     (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
@@ -204,13 +225,16 @@
     ;; Check file:// and assets://, pathname behavior is different
     (let [^js url (js/URL. (string/replace original-url "assets://" "file://"))
           path (safe-decode-uri-component (.-pathname url))
+          host (.-host url)
           path (if (string/starts-with? path "///")
                  (subs path 2)
                  path)
           path (if (re-find #"(?i)^/[a-zA-Z]:" path) ;; Win path fix
                  (subs path 1)
                  path)]
-      path)
+      (if (string/blank? host)
+        path
+        (str "//" host path)))
     original-url))
 
 (defn trim-dir-prefix

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

@@ -35,7 +35,15 @@
         "global dir")
     (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 (= "//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
   (testing "absolute"

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

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

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

@@ -26,7 +26,7 @@
         conn (read-graph graph-name)
         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)))]
-    (println "DB contains" (count (d/datoms @conn :eavt)) "datoms")
+    #_(println "DB contains" (count (d/datoms @conn :eavt)) "datoms")
     (prn results)))
 
 (when (= nbb/*file* (:file (meta #'-main)))

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

@@ -65,7 +65,7 @@
                                          [:block/schema :type]))}]
    (map (fn [[prop-type 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
   "Validates a slightly modified version of :block/properties. Properties are
@@ -118,10 +118,9 @@
     page-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]]
    ;; For closed values
    [:values {:optional true}  [:vector :uuid]]
@@ -130,28 +129,47 @@
    ;; For :page and :template
    [: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
   (vec
    (concat
     [:map
      [: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-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
   (vec
    (concat
     [: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-or-block-attrs)))
 

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

@@ -46,6 +46,10 @@
    ;; color props
    :logseq.color {:schema
                   {: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}
    ;; table-v2 props
    :logseq.table.version {:schema {:type :number :hide? true}
@@ -54,9 +58,15 @@
                           :visible true}
    :logseq.table.headers {:schema
                           {:type :default :hide? true}
+                          :closed-values
+                          (mapv #(hash-map :value % :uuid (random-uuid))
+                                ["uppercase" "capitalize" "capitalize-first" "lowercase"])
                           :visible true}
    :logseq.table.hover {:schema
                         {:type :default :hide? true}
+                        :closed-values
+                        (mapv #(hash-map :value % :uuid (random-uuid))
+                              ["row" "col" "both" "none"])
                         :visible true}
    :logseq.table.borders {:schema {:type :checkbox :hide? true}
                           :visible true}

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

@@ -1,23 +1,45 @@
 (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]
             [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})
 
-(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])
 
-(def closed-values-schema-types
+(def closed-value-property-types
   "Valid schema :type for closed values"
   #{: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")
 
+(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:
 ;; 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)
       (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
               {:error/message "should be a text"}
               ;; uuid check needed for property block values
@@ -97,11 +120,19 @@
    :coll     coll?
    :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
   "Property types whose validation fn requires a datascript db"
   #{: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)
    {:file/last-modified-at {}}))
 
-;; TODO: some attributes shouldn't be retracted for the db version
 (def retract-attributes
   #{
     :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)`
 ;;; 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]]
                (when (not (:db/valueType v))
                  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]
             ["better-sqlite3" :as sqlite3]
             [clojure.string :as string]
-            [cljs-bean.core :as bean]
+            [logseq.db.sqlite.util :as sqlite-util]
             [datascript.storage :refer [IStorage]]
-            [cognitect.transit :as t]
             [cljs.cache :as cache]
             [datascript.core :as d]
             [goog.object :as gobj]
             [logseq.db.frontend.schema :as db-schema]
-            [datascript.transit :as dt]
             [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.
 
 ;; sqlite databases
@@ -35,7 +31,7 @@
 (defn sanitize-db-name
   [db-name]
   (-> db-name
-      (string/replace "logseq_db_" "")
+      (string/replace sqlite-util/db-version-prefix "")
       (string/replace "/" "_")
       (string/replace "\\" "_")
       (string/replace ":" "_"))) ;; windows

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

@@ -4,9 +4,7 @@
             [cljs-time.core :as t]
             [clojure.string :as string]
             [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
   "
@@ -31,6 +29,8 @@
     (contains? (set (:block/type block)) "macro") 7
     :else 5))
 
+(defonce db-version-prefix "logseq_db_")
+
 (defn time-ms
   "Copy of util/time-ms. Too basic to couple this to main app"
   []
@@ -94,29 +94,4 @@
    (merge {:block/type "property"
            :block/journal? false
            :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))
      (concat title body))
     (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)
                               children-pages (->> (mapcat (fn [p]
                                                             (let [p (if (map? p)
@@ -369,9 +369,7 @@
                              (let [macro? (and (map? item)
                                                (= "macro" (:type item)))]
                                (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
              :refs (ref->map-fn *refs false)
              :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
   (testing "Counts"
     (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
            (ffirst

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

@@ -5,7 +5,8 @@ necessary db filtering"
             [goog.string :as gstring]
             [goog.string.format]
             [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
 ;; 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')
 
         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
         ;; it's a db graph or not
         state (assoc app-state

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

@@ -3,11 +3,12 @@
     [clojure.string :as str]
     [rum.core :as rum]
     [logseq.shui.icon.v2 :as icon]
-    [clojure.string :as string]))
+    [clojure.string :as string]
+    [goog.userAgent]))
 
 (rum/defcs root < rum/reactive
   (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]
   (let [*hover-theme (::hover-theme state)
         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-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
        (icon/root icon icon-props))
@@ -44,9 +47,9 @@
        (for [key shortcut]
          [:div.ui__button-shortcut-key
           (case key
-            "cmd" [:div "⌘"]
+            "cmd" [:div (if goog.userAgent/MAC "⌘" "Ctrl")]
             "shift" [:div "⇧"]
-            "return" [:div ""]
+            "return" [:div ""]
             "esc" [:div.tracking-tightest {:style {:transform "scaleX(0.8) scaleY(1.2) "
                                                    :font-size "0.5rem"
                                                    :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
                         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}}
                 {:keys [app-config] :as context}]
   (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!
      (fn []
        (when (and highlighted on-highlight)
@@ -99,6 +99,8 @@
                      (not highlighted) (str " "))
             :ref ref
             :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)}
            component-opts)
      ;; header
@@ -133,10 +135,7 @@
            [:span.text-gray-11 (str (to-string value-label))])
          (when 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)])]]))

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

@@ -6,35 +6,42 @@
 
 (def mac? goog.userAgent/MAC)
 (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]
   (cond
     (string? input) input
@@ -45,57 +52,38 @@
     (nil? 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
-  [shortcut context & {:keys [tiled size theme]
-                       :or {tiled true
-                            size :sm
+  [shortcut context & {:keys [size theme]
+                       :or {size :sm
                             theme :gray}}]
   (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'
 
 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 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
   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)
 })
 

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

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

+ 6 - 3
resources/css/shui.css

@@ -23,19 +23,22 @@
 }
 
 .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 {
-  @apply h-6 w-6;
+  @apply h-6;
+  min-width: 1.5rem;
 }
 
 .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 {
   @apply w-px h-full bg-gray-08-alpha;
+  min-height: 14px;
 }
 
 .ui__button-theme-text {

+ 1 - 1
resources/forge.config.js

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

+ 0 - 8
resources/js/preload.js

@@ -90,14 +90,6 @@ contextBridge.exposeInMainWorld('apis', {
     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
    *

+ 1 - 1
resources/package.json

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

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

@@ -5,6 +5,8 @@
   graph and current limitations"
   (:require [logseq.db.sqlite.db :as sqlite-db]
             [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.db :as ldb]
             [clojure.string :as string]
@@ -27,7 +29,7 @@
   (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.")
                                "{}"))]
-    (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
   "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\"}`.
        :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
-     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
    values.  Multiple property values for a many cardinality property are defined
    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
-   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
         pages-and-blocks' (mapv (fn [{:keys [page blocks]}]
                                   (cond-> {:page (merge {:block/uuid (random-uuid)} page)}
@@ -157,19 +164,28 @@
         property-db-ids (->> property-uuids
                              (map #(vector (name (first %)) (new-db-id)))
                              (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
         (vec
          (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"
   (:require [logseq.tasks.db-graph.create-graph :as create-graph]
             [logseq.db.sqlite.util :as sqlite-util]
+            [logseq.db.frontend.property.type :as db-property-type]
             [clojure.string :as string]
             [datascript.core :as d]
             ["path" :as node-path]
@@ -25,10 +26,36 @@
   [date days]
   (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
   []
   (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
      [{:page
        {:block/name (date-journal-title today) :block/journal? true :block/journal-day (date-journal-day today)}
@@ -37,19 +64,26 @@
         {:block/content "[[Queries]]"}]}
       {:page
        {: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"}
        :blocks
        [{: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-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 "number property block" :properties {:number 5}}
         {: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-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-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"}
        :blocks
        [{:block/content "{{query (property :default \"haha\")}}"}
@@ -66,12 +100,17 @@
        :blocks
        [{:block/content "yee"}
         {:block/content "haw"}]}
-      {:page {:block/name "page 2"}}]
+      {:page {:block/name "page 2"}}
+      {:page {:block/name "page 3"}}]
      :properties
      (->> [:default :url :checkbox :number :page :date]
           (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}}])))
+          (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 {}))}))
 
 (defn -main [args]
@@ -83,7 +122,9 @@
                         ((juxt node-path/dirname node-path/basename) graph-dir)
                         [(node-path/join (os/homedir) "logseq" "graphs") graph-dir])
         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"
              (count (filter :block/content blocks-tx)) "blocks ...")
     (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"
          "electron.db"
          "frontend.handler.db-based."
-         "frontend.components.property" "frontend.components.class"]))
+         "frontend.components.property" "frontend.components.class" "frontend.components.db-based"]))
 
 (def file-graph-ns
   "Namespaces or parent namespaces _only_ for file graphs"
@@ -32,6 +32,8 @@
   ["src/main/frontend/handler/db_based"
    "src/main/frontend/components/class.cljs"
    "src/main/frontend/components/property.cljs"
+   "src/main/frontend/components/property"
+   "src/main/frontend/components/db_based"
    "src/electron/electron/db.cljs"])
 
 (def file-graph-paths

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

@@ -19,7 +19,7 @@
                        static-dir
                        graph-dir
                        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]
   (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-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}
-   :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}
    :pl #{:port}
    :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)
            path (string/replace url "assets://" "")
            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
    protocol FILE_LSP_SCHEME

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

@@ -86,8 +86,14 @@
 
 (defmethod handle :openFileBackupDir [_window [_ repo 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]]
   (utils/read-file path))
@@ -246,7 +252,7 @@
     (->> (common-graph/read-directories dir)
          (remove (fn [s] (= s db/unlinked-graphs-dir)))
          (map graph-name->path)
-         (map (fn [s] (str "logseq_db_" s))))))
+         (map (fn [s] (str sqlite-util/db-version-prefix s))))))
 
 (defn- get-graphs
   []
@@ -256,13 +262,15 @@
 
 ;; TODO support alias mechanism
 (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]
   (->> (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 [_]]
@@ -385,7 +393,7 @@
   (db/open-db! 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)
 
 ;; DB related IPCs End

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

@@ -6,7 +6,9 @@
             [clojure.string :as string]
             [electron.configs :as cfgs]
             [electron.logger :as logger]
+            [logseq.db.sqlite.util :as sqlite-util]
             [cljs-bean.core :as bean]
+            [electron.db :as db]
             [promesa.core :as p]))
 
 (defonce *win (atom nil)) ;; The main window
@@ -43,6 +45,14 @@
       (string/replace 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
   []
   (let [lg-dir (node-path/join (.getPath app "home") ".logseq")]
@@ -250,13 +260,17 @@
 (defn get-graph-dir
   "required by all internal state in the electron section"
   [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
-  "reversing `get-graph-dir`"
+  "Reverse `get-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
   [schema-path]

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

@@ -63,7 +63,7 @@
 
 
 (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]
                (str "var(--rx-"
                  (name (nth color-list (mod (+ color-index dist) (count color-list))))
@@ -85,5 +85,6 @@
                        (str/replace "hsl(" "")
                        (str/replace ")" "")
                        (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))))

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

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

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

@@ -83,7 +83,8 @@
             [reitit.frontend.easy :as rfe]
             [rum.core :as rum]
             [shadow.loader :as loader]
-            [logseq.common.path :as path]))
+            [logseq.common.path :as path]
+            [electron.ipc :as ipc]))
 
 ;; local state
 (defonce *dragging?
@@ -316,7 +317,7 @@
                  :on-click      (fn [e]
                                   (util/stop e)
                                   (if local?
-                                    (js/window.apis.showItemInFolder image-src)
+                                    (ipc/ipc "openFileInFolder" image-src)
                                     (js/window.apis.openExternal image-src)))}
                 image-src])
              [:.flex
@@ -2319,7 +2320,7 @@
       [:div.closed-values-properties.flex.flex-row.items-center.gap-1.select-none.h-full
        (for [pid closed-values-properties]
          (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
   [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)))))
                  {: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)]
         (for [[page page-blocks] blocks]
           (ui/lazy-visible
@@ -3616,7 +3615,7 @@
                    page (db/entity (:db/id page))
                    ;; FIXME: parents need to be sorted
                    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
                  [:div
                   (page-cp config page)

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

@@ -479,6 +479,7 @@
 
 .color-level {
   background-color: or(--ls-right-sidebar-content-background, --lx-gray-02, --color-level-1);
+
   .dark & {
     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
 (defmethod load-results :pages [group state]
   (let [!input (::input state)
-        !results (::results state)]
+        !results (::results state)
+        repo (state/get-current-repo)]
     (swap! !results assoc-in [group :status] :loading)
     (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}))))
 
 ;; The blocks search action uses an existing handler
@@ -256,6 +264,27 @@
                    files)]
       (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
   [input]
   (or (when (string/starts-with? input "/")
@@ -284,24 +313,22 @@
       (load-results :filters state)
       (load-results :files state))))
 
-(defn close-unless-alt! [state]
-  (when-not (some-> state ::alt? deref)
-    (state/close-modal!)))
-
 (defn- copy-block-ref [state]
   (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)
-    (close-unless-alt! state)))
+    (state/close-modal!)))
 
 (defmulti handle-action (fn [action _state _event] action))
 
 (defmethod handle-action :open-page [_ state _event]
   (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")
-        (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]
   (let [block-id (some-> state state->highlighted-item :source-block :block/uuid uuid)
@@ -311,18 +338,20 @@
         (if (= (:block/type page) "whiteboard")
           (route-handler/redirect-to-whiteboard! page-name {:block-id 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]
   (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]
   (when-let [block-uuid (some-> state state->highlighted-item :source-block :block/uuid uuid)]
     (editor-handler/open-block-in-sidebar! block-uuid)
-    (close-unless-alt! state)))
+    (state/close-modal!)))
 
 (defmethod handle-action :open [_ state event]
   (when-let [item (some-> state state->highlighted-item)]
@@ -355,7 +384,7 @@
     (when-let [action (:action command)]
       (action)
       (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]
   (let [item (state->highlighted-item state)
@@ -363,20 +392,17 @@
         create-class? (string/starts-with? @!input "#")
         create-whiteboard? (= :whiteboard (:source-create item))
         create-page? (= :page (:source-create item))
-        alt? (some-> state ::alt deref)
         class (when create-class? (get-class-from-input @!input))]
     (cond
       create-class? (page-handler/create! class
                                           {:redirect? false
                                            :create-first-block? false
                                            :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?
       (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
   [input]
@@ -441,7 +467,12 @@
      [:div {:class         "border-b border-gray-06 pb-1 last:border-b-0"
             :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 "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)
          [:div {:class "pl-1.5 text-gray-12 rounded-full"
                 :style {:font-size "0.7rem"}}
@@ -474,7 +505,6 @@
                                       :rounded false
                                       :hoverable @*mouse-active?
                                       :highlighted highlighted?
-                                      :display-shortcut-on-highlight? true
                                       ;; for some reason, the highlight effect does not always trigger on a
                                       ;; boolean value change so manually pass in the dep
                                       :on-highlight-dep highlighted-item
@@ -529,12 +559,12 @@
 (defn- keydown-handler
   [state e]
   (let [shift? (.-shiftKey e)
-        meta? (.-metaKey e)
-        alt? (.-altKey e)
+        meta? (util/meta-key? e)
         ctrl? (.-ctrlKey e)
         keyname (.-key e)
         enter? (= keyname "Enter")
         esc? (= keyname "Escape")
+        composing? (util/event-is-composing? e)
         highlighted-group @(::highlighted-group state)
         show-less (fn [] (swap! (::results state) assoc-in [highlighted-group :show] :less))
         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")))]
     (reset! (::shift? state) shift?)
     (reset! (::meta? state) meta?)
-    (reset! (::alt? state) alt?)
     (when (or as-keydown? as-keyup?)
       (.preventDefault e))
 
@@ -559,9 +588,9 @@
       as-keyup? (if meta?
                   (show-less)
                   (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)]
              (when (or (and filter @(::input-changed? state))
                        (not (string/blank? input)))
@@ -573,13 +602,11 @@
                                     (util/stop-propagation e))
       :else nil)))
 
-(defn keyup-handler
+(defn- keyup-handler
   [state e]
   (let [shift? (.-shiftKey e)
-        meta? (.-metaKey e)
-        alt? (.-altKey e)]
+        meta? (util/meta-key? e)]
     (reset! (::shift? state) shift?)
-    (reset! (::alt? state) alt?)
     (reset! (::meta? state) meta?)))
 
 (defn- input-placeholder
@@ -640,8 +667,8 @@
      (shui/shortcut "/" context)
      [:div "to filter search results"]]
     [: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 <
   {:init (fn [state]
@@ -751,12 +778,10 @@
        (mixins/on-key-down state {}
                            {:target ref
                             :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 ::meta?)
-  (rum/local false ::alt?)
   (rum/local nil ::highlighted-group)
   (rum/local nil ::highlighted-item)
   (rum/local default-results ::results)

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

@@ -80,7 +80,7 @@
 }
 
 .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 {
@@ -683,7 +683,7 @@
   }
 
   .references {
-    margin-left: 12px;
+    @apply mx-[28px];
   }
 
   .sidebar-drop-indicator {
@@ -757,6 +757,10 @@
       }
     }
   }
+
+  .page-hierarchy {
+    @apply pl-[28px];
+  }
 }
 
 .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]
             [frontend.commands :as commands
              :refer [*first-command-group *matched-block-commands *matched-commands]]
-            [frontend.components.block :as block]
             [frontend.components.datetime :as datetime-comp]
             [frontend.components.svg :as svg]
             [frontend.components.search :as search]
@@ -101,9 +100,6 @@
                                                       :command :block-commands}))
         :class     "black"}))))
 
-(defn- in-sidebar? [el]
-  (not (.contains (.getElementById js/document "left-container") el)))
-
 (defn- page-on-chosen-handler
   [embed? input id q pos format]
   (if embed?
@@ -143,7 +139,6 @@
         (when input
           (let [current-pos (cursor/pos input)
                 edit-content (state/sub-edit-content)
-                sidebar? (in-sidebar? input)
                 q (or
                    (editor-handler/get-selected-text)
                    (when (= action :page-search-hashtag)
@@ -151,11 +146,9 @@
                    (when (> (count edit-content) 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
                                 (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
@@ -163,16 +156,18 @@
                                            [(count m) m])
                                          matched-pages)
 
-                                (and (string/blank? q) (not db-tag?))
+                                (string/blank? q)
                                 nil
 
-                                (and (string/blank? q) db-tag?)
-                                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
                                 (let [matched-pages (remove nil? matched-pages)
                                       matched-pages (sort-by
@@ -196,27 +191,14 @@
              (ui/auto-complete
               matched-pages
               {: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?
-                                                                          "Search for a class"
+                                                                          "Search for a page or a class"
                                                                           "Search for a page")]
                :class       "black"})]))))))
 

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

@@ -9,9 +9,9 @@
             [frontend.components.query :as query]
             [frontend.components.reference :as reference]
             [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.class :as class-component]
+            [frontend.components.db-based.page :as db-page]
             [frontend.handler.property.util :as pu]
             [frontend.handler.db-based.property :as db-property-handler]
             [frontend.handler.db-based.property.util :as db-pu]
@@ -316,106 +316,6 @@
       :on-focus (fn []
                   (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/local false ::edit?)
   (rum/local "" ::input-value)
@@ -456,12 +356,12 @@
        (when icon
          [:div.page-icon {:on-mouse-down util/stop-propagation}
           (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)])
 
        [:div.flex.flex-1.flex-row.flex-wrap.items-center.gap-4
@@ -519,11 +419,11 @@
          [:div.absolute.bottom-2.left-0
           [: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?))
-             (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))
                      (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
   [e *control-show? *all-collapsed?]
@@ -554,84 +454,6 @@
                          "control-show cursor-pointer" "control-hide")}
     (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
   [state page-name]
   (or page-name
@@ -724,8 +546,8 @@
                [:div.mb-4
                 (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
            (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)
                   file-fpath (path/path-join repo-dir file-rpath)]
               [{: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)
                 :options {:on-click #(js/window.apis.openPath file-fpath)}}]))
 

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

@@ -109,7 +109,10 @@
     "Text"
     ((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
   rum/reactive
   db-mixins/query
@@ -137,7 +140,7 @@
         class? (contains? (:block/type block) "class")
         property-type (get-in property [:block/schema :type])
         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
      {: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)}
@@ -168,9 +171,9 @@
 
       [:div.grid.grid-cols-4.gap-1.items-center.leading-8
        [: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?
-                                         db-property-type/internal-builtin-schema-types))
+                                         db-property-type/internal-built-in-property-types))
                                (map (fn [type]
                                       {:label (property-type-label type)
                                        :disabled disabled?
@@ -185,14 +188,29 @@
                        :interactive true
                        :disabled    false}
                       (svg/help-circle))]
-           [:div.col-span-2
+           [:div.flex.items-center.col-span-2
             (ui/select schema-types
                        (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
          [:label "Multiple values:"]
          (let [many? (boolean (= :many (:cardinality @*property-schema)))]
@@ -203,27 +221,28 @@
                                       (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)))
         [:div.grid.grid-cols-4.gap-1.items-start.leading-8
@@ -676,9 +695,9 @@
                    (empty? class->properties)
                    (not new-property?)
                    (not (:page-configure? opts)))
-      [:div.ls-properties-area (cond-> {}
+      [:div.ls-properties-area (cond-> {:class [(if class-schema? "class-properties" "page-properties")]}
                                  (:selected? opts)
-                                 (assoc :class "select-none"))
+                                 (update :class conj "select-none"))
        (properties-section block (if class-schema? properties own-properties) opts)
 
        (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.handler.property :as property-handler]
             [frontend.handler.db-based.property :as db-property-handler]
+            [frontend.config :as config]
             [frontend.components.property.value :as property-value]
             [frontend.db :as db]
             [frontend.state :as state]
@@ -31,12 +32,14 @@
       (if icon
         (icon-component/icon icon)
         [: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
                   "origin-top-right.absolute.left-0.rounded-md.shadow-lg")}))
 
@@ -124,7 +127,7 @@
         (ui/icon "X")])]))
 
 (rum/defc choice-item-content
-  [property block dropdown-opts]
+  [property *property-schema block dropdown-opts]
   (let [{:block/keys [uuid]} block]
     (ui/dropdown
      (fn [opts]
@@ -133,24 +136,27 @@
         (assoc opts
                :delete-choice
                (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
                (fn [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)))
 
 (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
    {:class "max-h-[50dvh]"}
    [:div "Existing values:"]
@@ -160,7 +166,8 @@
    (ui/button
     "Add choices"
     {: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))})])
 
 (rum/defc choices < rum/reactive
@@ -176,7 +183,7 @@
                             (when-let [block (db/sub-block (:db/id (db/entity [:block/uuid id])))]
                               {:id (str id)
                                :value id
-                               :content (choice-item-content property block dropdown-opts)}))
+                               :content (choice-item-content property *property-schema block dropdown-opts)}))
                           values))]
        (dnd/items choices
                   {:on-drag-end (fn [new-values]
@@ -184,10 +191,12 @@
                                     (swap! *property-schema assoc :values new-values)
                                     (pu-component/update-property! property @*property-name @*property-schema)))}))
      (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]
         (if (= :page property-type)
           (property-value/select-page property
@@ -195,20 +204,23 @@
                                        :dropdown? false
                                        :close-modal? false
                                        :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))
                             (map second)
                             (remove uuid?)
                             (remove string/blank?)
                             distinct)]
+
             (if (seq values)
-              (add-existing-values property values opts)
+              (add-existing-values property *property-schema values opts)
               (item-config
                property
                nil
                (assoc opts :on-save
                       (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)]))

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

@@ -207,6 +207,7 @@
                  (model/get-all-page-original-names repo))
                distinct)
         options (map (fn [p] {:value p}) pages)
+        string-classes (remove #(= :logseq.class %) classes)
         opts' (cond->
                (merge
                 opts
@@ -229,7 +230,7 @@
                  :transform-fn (fn [results input]
                                  (if-let [[_ new-page class-input] (and (empty? results) (re-find #"(.*)#(.*)$" input))]
                                    (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
                                                                  (mapcat #(db/get-namespace-pages repo %))
                                                                  (map :block/original-name))]
@@ -241,7 +242,7 @@
                 multiple-choices?
                 (assoc :on-apply (fn [choices]
                                    (let [pages (->> choices
-                                                    (map #(create-page-if-not-exists! property classes %))
+                                                    (map #(create-page-if-not-exists! property string-classes %))
                                                     (map first))
                                          values (set (map #(pu/get-page-uuid repo %) pages))]
                                      (when on-chosen (on-chosen values)))))
@@ -249,7 +250,7 @@
                 (assoc :on-chosen (fn [chosen]
                                     (let [page* (string/trim (if (string? chosen) chosen (:value chosen)))]
                                       (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))]
                                           (when on-chosen (on-chosen id'))))))))]
     (select-aux block property opts')))
@@ -307,12 +308,11 @@
 
 (defn create-new-block!
   [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)))
 
 (defn create-new-block-from-template!

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

@@ -22,20 +22,20 @@
 (rum/defc normalized-graph-label
   [{:keys [url remote? GraphName GraphUUID] :as graph} on-click]
   (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)}
-          (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
   "Graph list in `All graphs` page"
@@ -229,9 +229,7 @@
                              (let [remote? (:remote? (first (filter #(= current-repo (:url %)) repos)))
                                    repo-name (db/get-repo-name current-repo)
                                    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")]
                                [: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
                         (or unset? user-binding (false? user-binding))
-                        [:code.dark:bg-green-800.bg-green-300
+                        [:code
                          (if unset?
                            (t :keymap/unset)
                            (str (t :keymap/custom) ": "

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

@@ -460,10 +460,7 @@
 
 (defn get-repo-fpath
   [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
   []

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

@@ -9,17 +9,15 @@
             [logseq.graph-parser.text :as text]
             [logseq.db :as ldb]
             [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 {}))
 
 (defn get-repo-path
   [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
   [repo-url]
@@ -36,15 +34,18 @@
 (defn get-short-repo-name
   "repo-name: from get-repo-name. Dir/Name => 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
   [repo]
@@ -81,6 +82,12 @@
   [repo]
   (swap! conns dissoc (datascript-db repo)))
 
+(defn kv
+  [key value]
+  {:db/id -1
+   :db/ident key
+   key value})
+
 (defn start!
   ([repo]
    (start! repo {}))
@@ -88,6 +95,9 @@
    (let [db-name (datascript-db repo)
          db-conn (ldb/start-conn :schema (get-schema repo) :create-default-pages? false)]
      (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
        (listen-handler repo))
      (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)))
      (let [result (lookup-entity e k default-value)
-           refs (:block/refs e)]
+           refs (:block/refs e)
+           tags (:block/tags e)]
        (or
         (when (string? result)
-          (db-utils/special-id->page result refs))
+          (db-utils/special-id->page result (distinct (concat refs tags))))
         default-value))
 
      :else

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

@@ -136,12 +136,14 @@
        (concat tx right-tx)))
    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
   [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-parent-left-conflicts conflicts))]
     (when (seq fix-conflicts-tx)
@@ -149,7 +151,7 @@
       (util/pprint fix-conflicts-tx)
       (db/transact! repo fix-conflicts-tx transact-opts)
       (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))))))
 
 (defn fix-page-if-broken!

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

@@ -79,7 +79,7 @@
 
 (defn get-tag-blocks
   [repo tag-name]
-  (d/q '[:find ?b
+  (d/q '[:find [?b ...]
          :in $ ?tag
          :where
          [?e :block/name ?tag]
@@ -228,11 +228,10 @@
   "Refresh file timestamps to DB"
   [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
   [repo path]
@@ -311,12 +310,13 @@ independent of format as format specific heading characters are stripped"
                 page-name
                 route-name
                 (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)
 
       (->> (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
   "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)
     (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.
    Property values that are references are displayed as page references"
   [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
-                   (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
-                   (map str vals))))
+                    (map str vals))))
        ;; Remove blanks as they match on everything
-       (remove string/blank?)
-       (distinct)
-       (sort)))
-
+        (remove string/blank?)
+        (distinct)
+        (sort))))
 
 (defn get-block-property-values
   "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"
   [property-uuid]
   (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
   [name]
@@ -1534,7 +1534,7 @@ independent of format as format specific heading characters are stripped"
 
 (defn get-whiteboard-id-nonces
   [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"]))
               :logseq.tldraw.shape)
         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)))
 
 (defn get-namespace-children
-  [repo-url eid]
+  [repo eid]
   (->>
-   (d/q '[:find ?children
+   (d/q '[:find [?children ...]
           :in $ ?parent %
           :where
           (namespace ?parent ?children)]
-        (conn/get-db repo-url)
+        (conn/get-db repo)
         eid
         (:namespace rules/rules))
-   db-utils/seq-flatten
-   (set)))
+   distinct))
 
 (defn get-class-objects
   [repo class-id]

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

@@ -1,8 +1,7 @@
 (ns frontend.db.outliner
   "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
   [conn id]
   (try
@@ -16,7 +15,3 @@
     :in $ ?id
     :where
     [?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])]
     (reset! result-atom new-result)))
 
-(defn kv
-  [key value]
-  {:db/id -1
-   :db/ident key
-   key value})
+(def kv conn/kv)
 
 (defn remove-key!
   [repo-url key]
@@ -380,7 +376,8 @@
       (recur))
     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)"
   (:require [datascript.core :as d]
             [frontend.config :as config]
-            [frontend.db :as db]
             [frontend.db.conn :as db-conn]
             [frontend.db.file-based.migrate :as db-migrate]
             [frontend.db.persist :as db-persist]
@@ -10,15 +9,13 @@
             [frontend.db.utils :as db-utils]
             [frontend.state :as state]
             [frontend.persist-db :as persist-db]
-            [goog.object :as gobj]
             [logseq.db.frontend.schema :as db-schema]
-            [logseq.db.sqlite.restore :as sqlite-restore]
             [logseq.db.sqlite.util :as sqlite-util]
             [promesa.core :as p]
             [frontend.util :as util]
             [cljs-time.core :as t]
             [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]))
 
 (defn- old-schema?
@@ -57,64 +54,39 @@
                 (db-conn/reset-conn! db-conn db)))]
     (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!
   [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)
                      property (d/entity @conn [:block/name k-name])]
-                 (when-not (= {:schema schema
+                 (when (and
+                        (not= {:schema schema
                                :original-name (or original-name k-name)}
                               {:schema (:block/schema 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
-                     {: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/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)]
     (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!
   "Load initial data from SQLite"
@@ -122,35 +94,29 @@
   (state/set-state! :graph/loading? true)
   (p/let [start-time (t/now)
           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)
           _ (swap! db-conn/conns assoc db-name conn)
           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"
              " Datoms in total: " datoms-count)
 
-    ;; TODO: Store schema in sqlite
+    ;; FIXME:
     ;; (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!
   "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]]
             [clojure.set :as set]
             [cognitect.transit :as transit]
+            [frontend.async-util :include-macros true :refer [<?]]
             [frontend.db :as db]
             [frontend.db.react :as react]
             [frontend.db.rtc.const :as rtc-const]
             [frontend.db.rtc.op-mem-layer :as op-mem-layer]
             [frontend.db.rtc.ws :as ws]
             [frontend.handler.page :as page-handler]
+            [frontend.handler.property.util :as pu]
             [frontend.handler.user :as user]
+            [frontend.handler.whiteboard :as whiteboard-handler]
             [frontend.modules.outliner.core :as outliner-core]
             [frontend.modules.outliner.transaction :as outliner-tx]
             [frontend.state :as state]
             [frontend.util :as util]
-            [frontend.async-util :include-macros true :refer [<?]]
             [malli.core :as m]
             [malli.util :as mu]))
 
@@ -100,6 +102,15 @@
    {:persist-op? false}
    (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]
   (apply db/transact! args))
 
@@ -107,13 +118,25 @@
   [block]
   (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
   [repo 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
   [repo block-uuid remote-parents remote-left-uuid move?]
@@ -126,8 +149,8 @@
           b {:block/uuid block-uuid}
           ;; 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?
           (transact-db! :move-blocks [b] local-left true)
           (transact-db! :insert-blocks
@@ -136,7 +159,7 @@
                           :block/format :markdown}]
                         local-left {:sibling? true :keep-uuid? true}))
 
-        [true true]
+        [false true true]
         (let [sibling? (not= (:block/uuid local-parent) (:block/uuid local-left))]
           (if move?
             (transact-db! :move-blocks [b] local-left sibling?)
@@ -145,13 +168,19 @@
                             :block/format :markdown}]
                           local-left {:sibling? sibling? :keep-uuid? true})))
 
-        [true false]
+        [false true false]
         (if move?
           (transact-db! :move-blocks [b] local-parent false)
           (transact-db! :insert-blocks
                         [{:block/uuid block-uuid :block/content ""
                           :block/format :markdown}]
                         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
                                                       :remote-left remote-left-uuid}))))))
 
@@ -201,44 +230,61 @@
       :wrong-pos
       :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
-  [repo block-uuid op-value]
+  [repo block-uuid {:keys [parents] :as op-value}]
   (let [key-set (set/intersection
                  (conj rtc-const/general-attr-set :content)
                  (set (keys op-value)))]
     (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
   [repo sorted-move-ops]

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

@@ -8,7 +8,6 @@
             [cognitect.transit :as transit]
             [datascript.core :as d]
             [frontend.async-util :include-macros true :refer [<? go-try]]
-            [frontend.db :as db]
             [frontend.db.conn :as conn]
             [frontend.db.rtc.op-mem-layer :as op-mem-layer]
             [frontend.db.rtc.ws :refer [<send!]]
@@ -121,7 +120,7 @@
          conn (d/create-conn db-schema/schema-for-db-based-graph)
          blocks* (replace-db-id-with-temp-id 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)
            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)))
                properties (->>
                            (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)
              (editor-handler/api-insert-new-block!
               text (merge {:page        (:block/name ref-page)

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

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

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

@@ -44,6 +44,34 @@
       (p/catch (fn [_error]
                  (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 []
   protocol/Fs
   (mkdir! [_this dir]
@@ -51,13 +79,11 @@
       (let [fpath (path/url-to-path dir)]
         (-> (js/window.pfs.mkdir fpath)
             (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
-      (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]
     (when js/window.pfs

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

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

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

@@ -53,17 +53,11 @@
 
 (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!
   []

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

@@ -76,8 +76,9 @@
 
                     (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)))))
 
 (defn normalize-asset-resource-url

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

@@ -117,7 +117,7 @@
    TODO: Add other options"
   ([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
                   create-first-block? true
                   rename?             false
@@ -172,7 +172,7 @@
          (when (seq txs)
            (db/transact! repo txs {:persist-op? persist-op?})))
 
-       (when create-first-block?
+       (when (and create-first-block? (not (or whiteboard? class?)))
          (when (or
                 (db/page-empty? repo (:db/id (db/entity [:block/name page-name])))
                 (create-title-property? repo journal? page-name))

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

@@ -60,8 +60,8 @@
                      refs)]
         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)
              (db/page-exists? 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 to-page)))
 
-
     (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!
   ([old-name new-name]
@@ -114,30 +114,39 @@
          old-page-name (util/page-name-sanity-lc old-name)
          page-e (db/entity [:block/name old-page-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)]
-     (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.db.sqlite.util :as sqlite-util]
             [logseq.db.frontend.property.type :as db-property-type]
+            [logseq.db.frontend.property.util :as db-property-util]
             [malli.util :as mu]
             [malli.error :as me]
             [logseq.graph-parser.util.page-ref :as page-ref]
@@ -20,15 +21,15 @@
 ;; schema -> type, cardinality, object's class
 ;;           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"
   [property & {:keys [new-closed-value?]
                :or {new-closed-value? false}}]
   (into {}
         (map (fn [[property-type property-val-schema]]
                (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
                        schema-fn' (if (db-property-type/property-types-with-db property-type) #(schema-fn (db/get-db) %) schema-fn)]
                    [property-type [:fn
@@ -39,7 +40,7 @@
                    [property-type [:fn schema-opts #(schema-fn (db/get-db) %)]])
                  :else
                  [property-type property-val-schema]))
-             db-property-type/builtin-schema-types)))
+             db-property-type/built-in-validation-schemas)))
 
 (defn- fail-parse-long
   [v-str]
@@ -122,7 +123,7 @@
     (when (and multiple-values? (seq values))
       (let [infer-schema (when-not type (infer-schema-from-input-string (first values)))
             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)
             values' (try
                       (set (map #(convert-property-input-string property-type %) values))
@@ -162,7 +163,7 @@
                                   :block/refs refs}]
                                 {: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`"
   [v]
   (when (and (string? v)
@@ -199,7 +200,7 @@
         (when (some? v)
           (let [infer-schema (when-not type (infer-schema-from-input-string v))
                 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)
                 value (get properties property-uuid)
                 v* (try
@@ -477,7 +478,7 @@
                                      {})
                 (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"
   [m]
   (zipmap
@@ -592,6 +593,21 @@
     {:page page-tx
      :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
   [block property template]
   (let [current-page-id (:block/uuid (or (:block/page block) block))
@@ -621,32 +637,21 @@
     {:page page-tx
      :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
   [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
   "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)))
   (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)
             closed-values (:values property-schema)
             block-values (map (fn [id] (db/entity [:block/uuid id])) closed-values)
@@ -659,12 +664,9 @@
             block (when id (db/entity [:block/uuid id]))
             value-block (when (uuid? value) (db/entity [:block/uuid 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)]
         (cond
-          (nil? resolved-value)
-          nil
-
           (some (fn [b] (and (= resolved-value (or (db-pu/property-value-when-closed b)
                                                    (:block/uuid b)))
                              (not= id (:block/uuid b)))) block-values)
@@ -677,6 +679,9 @@
             (notification/show! validate-message :warning)
             :value-invalid)
 
+          (nil? resolved-value)
+          nil
+
           (:block/name value-block)             ; page
           (let [new-values (vec (conj closed-values value))]
             {:block-id value
@@ -703,16 +708,10 @@
                           (let [page (get-property-hidden-page property)
                                 page-tx (when-not (e/entity? page) 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))]
                             (->> (cons page-tx [new-block
                                                 {:db/id (:db/id property)
@@ -723,6 +722,7 @@
              :tx-data tx-data}))))))
 
 (defn add-existing-values-to-closed-values!
+  "Adds existing values as closed values and returns their new block uuids"
   [property values]
   (when (seq values)
     (let [property-id (:block/uuid property)
@@ -731,11 +731,12 @@
           page-tx (when-not (e/entity? page) page)
           page-id (:block/uuid page)
           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))
           value->block-id (zipmap
                            (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-values))]
       (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)
-    (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)
                     :block/schema (update schema :values
                                           (fn [values]
-                                            (vec (remove #{(:block/uuid item)} values))))}]]
+                                            (vec (remove #{(:block/uuid value-block)} values))))}]]
       (db/transact! tx-data))))
 
 (defn get-property-block-created-block

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

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

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

@@ -64,8 +64,7 @@
             [promesa.core :as p]
             [rum.core :as rum]
             [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
 
@@ -551,7 +550,7 @@
                             (wrap-parse-block)
                             (assoc :block/uuid (or custom-uuid (db/new-block-id))))
               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 (merge new-block other-attrs)
               [block-m sibling?] (cond
@@ -1432,7 +1431,8 @@
   "Make asset URL for UI element, to fill img.src"
   [path] ;; path start with "/assets"(editor) or compatible for "../assets"(whiteboards)
   (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)
           repo-dir  (config/get-repo-dir repo)
           ;; Hack for path calculation
@@ -1448,7 +1448,7 @@
         (assets-handler/resolve-asset-real-path-url (state/get-current-repo) path)
 
         (util/electron?)
-        (path/path-join "assets://" full-path)
+        (path/prepend-protocol "assets:" full-path)
 
         (mobile-util/native-platform?)
         (mobile-util/convert-file-src full-path)
@@ -1629,18 +1629,19 @@
         editing-page (and block
                           (when-let [page-id (:db/id (:block/page block))]
                             (:block/name (db/entity page-id))))
-        pages (search/page-search q 100)]
+        pages (search/page-search q)]
     (if editing-page
       ;; To prevent self references
       (remove (fn [p] (= (util/page-name-sanity-lc p) editing-page)) 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
   [q block-id]

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

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

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

@@ -253,7 +253,7 @@
 (defn watch-for-current-graph-dir!
   []
   (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
       ;; after an app refresh can cause stale page data to load
       (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]
             [promesa.core :as p]
             [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 delete! page-common-handler/delete!)
@@ -208,7 +210,11 @@
       (fn [chosen e]
         (util/stop e)
         (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?))
                             (page-ref/->page-ref chosen)
                             chosen)
@@ -227,10 +233,13 @@
                   (when-not tag-entity
                     (create! tag {:redirect? 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
                                           (str "#" wrapped-tag)
@@ -319,7 +328,7 @@
   (if-let [file-rpath (and (util/electron?) (page-util/get-page-file-rpath))]
     (let [repo-dir (config/get-repo-dir (state/get-current-repo))
           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)))
 
 (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))
     (file-property-handler/batch-set-block-property! block-ids key value)))
 
-(defn replace-key-with-id!
+(defn replace-key-with-id
   [repo m]
   (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
   "Get the value of coll's (a map) `key`. For file and db graphs"
   [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 key))))
 
@@ -47,4 +49,4 @@
   (get-property block :logseq.tldraw.page))
 
 (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]
             [frontend.handler.file-based.recent :as file-recent-handler]
             [frontend.config :as config]
-            [frontend.state :as state]))
+            [frontend.state :as state]
+            [frontend.db.model :as model]))
 
 (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
   []

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

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

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

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

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

@@ -134,14 +134,33 @@
           (and (<= 400 (:status resp))
                (> 500 (:status resp)))
           ;; 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
           ;; we shouldn't clear tokens if they aren't expired yet
           ;; the `refresh-tokens-loop` will retry soon
           (and (not (http/unexceptional-status? (:status resp)))
                (not (-> (state/get-auth-id-token) parse-jwt expired?)))
-          nil                           ; do nothing
+          (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)))
           (notification/show! "exceptional status when refresh-token" :warning true)
@@ -217,14 +236,15 @@
 
 (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
   []

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

@@ -37,12 +37,6 @@
         additional-props (gp-whiteboard/with-whiteboard-block-props block page-name)]
     (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
   [page-block blocks]
   (let [page-metadata (pu/get-property page-block :logseq.tldraw.page)

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

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

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

@@ -77,25 +77,26 @@
 
 (defn- remove-orphaned-page-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
   [txs-state block-entity m]
@@ -150,8 +151,8 @@
                                         (:block/macros block-entity)))))))
 
 (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)
           linked-page (some-> content mldoc/extract-plain)
           sanity-linked-page (some-> linked-page util/page-name-sanity-lc)
@@ -222,7 +223,8 @@
   (when (config/db-based-graph? repo)
     (let [refs (->> (rebuild-block-refs repo block (:block/properties block)
                                         :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)
                                                :block/refs refs}]))))))
 
@@ -297,29 +299,34 @@
                 (dissoc :block/children :block/meta :block.temp/top? :block.temp/bottom?
                         :block/title :block/body :block/level)
                 gp-util/remove-nils
-                block-with-timestamps
+                block-with-updated-at
                 fix-tag-ids)
           repo (state/get-current-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
-        (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-page-when-save-block txs-state block-entity m)
         ;; Remove macros as they are replaced by new ones
         (remove-macros-when-save repo txs-state block-entity)
-
         ;; Remove orphaned refs from block
         (remove-orphaned-refs-when-save txs-state block-entity m))
 
@@ -331,7 +338,7 @@
         (swap! txs-state conj
                (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)
 

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

@@ -56,7 +56,7 @@
                    (not (state/input-idle? repo {:diff 3000}))) ;; long page
               ;; when this whiteboard page is just being updated
               (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 '[*])
               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)]
@@ -73,7 +73,7 @@
   [pages]
   (when (seq pages)
     (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)
              (catch :default e
                (notification/show!
@@ -101,17 +101,17 @@
 (defn <ratelimit-file-writes!
   []
   (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]
             [goog.events :as events]
             [goog.ui.KeyboardShortcutHandler.EventType :as EventType]
-            [lambdaisland.glogi :as log]
-            [goog.functions :refer [debounce]])
+            [lambdaisland.glogi :as log])
   (:import [goog.events KeyCodes KeyNames]
            [goog.ui KeyboardShortcutHandler]))
 

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

@@ -140,10 +140,10 @@
   (let [tmp (cond
               (false? binding)
               (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))
 
               (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)]
      (js/console.log "fetch-initital" 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."
   (:require ["comlink" :as Comlink]
-            [cljs-time.coerce :as tc]
-            [cljs-time.core :as t]
             [cljs.core.async.interop :refer [p->c]]
             [frontend.persist-db.protocol :as protocol]
             [frontend.config :as config]
@@ -52,47 +50,6 @@
                                       (reject nil)) ;; cannot init
                                     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
   (defn dev-stop!
     "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
   "Return a list of page names that match the query"
   ([q]
-   (page-search q 10))
+   (page-search q 500))
   ([q limit]
    (when-let [repo (state/get-current-repo)]
      (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.util :as util]
             ["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
 
@@ -58,17 +59,22 @@
 
 (defn block->index
   "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)]
     (when-not (> (count content) (max-len))
       (when-not (and (string/blank? content)
                      (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)
                  :page (if (or (map? page) (e/entity? page)) (:db/id page) page)
                  :content (sanitize content)}
               m' (cond-> m
-                   (and (config/db-based-graph? repo) (seq properties))
+                   (and db-based? (seq properties))
                    (update :content
                            (fn [content]
                              (str content "\n"

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

@@ -9,13 +9,14 @@
             [electron.ipc :as ipc]
             [frontend.colors :as colors]
             [frontend.mobile.util :as mobile-util]
-            [frontend.storage :as storage]
             [frontend.spec.storage :as storage-spec]
+            [frontend.storage :as storage]
             [frontend.util :as util]
             [frontend.util.cursor :as cursor]
             [goog.dom :as gdom]
             [goog.object :as gobj]
             [logseq.graph-parser.config :as gp-config]
+            [malli.core :as m]
             [medley.core :as medley]
             [promesa.core :as p]
             [rum.core :as rum]))
@@ -35,10 +36,12 @@
      {:route-match                           nil
       :today                                 nil
       :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                    #{}
       :reactive/custom-queries               (async/chan 1000)
       :notification/show?                    false

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

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

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

@@ -1141,11 +1141,11 @@
                will poll it when its return value is channel,
   - :flush-fn exec flush-fn when time to flush, (flush-fn item-coll)
   - :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)
   - :flush-now-ch flush the content in the queue immediately
   - :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))
            stop-ch* (or stop-ch (async/chan))
            flush-now-ch* (or flush-now-ch (async/chan))
@@ -1173,8 +1173,8 @@
                                (async/<! filter-v)
                                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))
                  (recur timeout-ch coll)))
 
@@ -1549,19 +1549,4 @@ Arg *stop: atom, reset to true to stop the loop"
           (or
            (not (string/includes? 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)
 
-(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-triggers "Attivazione delle scorciatoie"
  :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/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"
- :help/context-menu "Menu contestuale del blocco"
+ :help/context-menu "Menù contestuale del blocco"
  :help/markdown-syntax "Sintassi Markdown"
  :help/org-mode-syntax "Sintassi Org mode"
  :bold "Grassetto"
@@ -37,9 +36,9 @@
  :right-side-bar/help "Aiuto"
  :right-side-bar/switch-theme "Modalità tema"
  :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/new-page "Nuova pagina"
  :right-side-bar/show-journals "Mostra diari"
@@ -51,7 +50,7 @@
  :page/open-in-finder "Apri nella cartella"
  :page/open-with-default-app "Apri con l'app predefinita"
  :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/make-private "Segna come privato"
  :page/delete "Elimina pagina"
@@ -76,11 +75,11 @@
  :editor/cut "Taglia"
  :content/copy-block-ref "Copia riferimento di blocco"
  :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"
  :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-custom-css "Modifica custom.css"
  :settings-page/custom-configuration "Configurazione personalizzata"
@@ -89,15 +88,15 @@
  :settings-page/spell-checker "Correttore ortografico"
  :settings-page/auto-updater "Aggiornamento automatico"
  :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/preferred-file-format "Formato file 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-tooltip "Suggerimenti"
  :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/clear-cache "Pulisci cache"
  :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/current-version "Versione attuale"
  :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/plugin-system "Sistema di plugin"
  :settings-page/network-proxy "Proxy di rete"
@@ -119,13 +118,12 @@
  :port "Porta"
  :re-index "Re-indicizza"
  :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"
  :sync-from-local-files "Ricarica"
  :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?"
-
  :new-graph "Aggiungi nuovo grafo"
  :graph "Grafo"
  :graph/persist "Logseq sta sincronizzando lo stato interno, per favore attendi alcuni secondi."
@@ -148,7 +146,7 @@
  :all-journals "Tutte le pagine di diario"
  :settings "Impostazioni"
  :settings-of-plugins "Impostazioni plugin"
- :plugins "Plugins"
+ :plugins "Plugin"
  :themes "Temi"
  :relaunch-confirm-to-work "È necessario riavviare l'app per farla funzionare. Vuoi riavviarla ora?"
  :import "Importa"
@@ -162,11 +160,9 @@
  :language "Lingua"
  :remove-background "Rimuovi lo sfondo"
  :open-a-directory "Apri una cartella locale"
-
  :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/install "Installa"
  :plugin/reload "Ricarica"
@@ -174,8 +170,8 @@
  :plugin/check-update "Controlla aggiornamenti"
  :plugin/check-all-updates "Controlla tutti gli aggiornamenti"
  :plugin/refresh-lists "Ricarica lista"
- :plugin/enabled "Abilitato"
- :plugin/disabled "Disabilitato"
+ :plugin/enabled "Abilitati"
+ :plugin/disabled "Disabilitati"
  :plugin/update-available "Aggiornamento disponibile"
  :plugin/updating "Aggiornamento"
  :plugin/uninstall "Disinstalla"
@@ -192,139 +188,607 @@
  :plugin/unpacked-tips "Seleziona la cartella del 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.)"
-
  :pdf/copy-ref "Copia riferimenti"
  :pdf/copy-text "Copia testo"
  :pdf/linked-ref "Riferimenti collegati"
  :pdf/toggle-dashed "Stile tratteggiato per evidenziare l'area"
-
  :updater/new-version-install "Una nuova versione è stata scaricata."
  :updater/quit-and-install "Riavvia per installarla"
-
  :paginates/pages "Totale {1} pagine"
  :paginates/prev "Precedente"
  :paginates/next "Successivo"
-
  :select/default-prompt "Selezionane uno"
  :select.graph/prompt "Seleziona un grafo"
  :select.graph/empty-placeholder-description "Non ci sono grafi corrispondenti. Vuoi aggiungerne uno nuovo?"
  :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/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/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/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/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-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-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-new "{1} ホワイトボードを新規作成する"
  :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-back "戻る"
  :on-boarding/tour-whiteboard-btn-finish "完了"
@@ -102,6 +110,7 @@
  :help/shortcuts "キーボードショートカット"
  :help/shortcuts-triggers "トリガー"
  :help/shortcut "ショートカット"
+ :help/search "ページ、ブロック、コマンドを検索"
  :help/slash-autocomplete "スラッシュで自動補完"
  :help/reference-autocomplete "ページ参照の自動補完"
  :help/block-reference "ブロック参照"
@@ -162,7 +171,7 @@
  :page/slide-view-tip-go-fullscreen (fn [] [[:span.opacity-70 "お役立ち情報:"] [:code "f"] [:span.opacity-70 "を押すことでフルスクリーンにできます"]])
  :page/delete-confirmation "このページとページのファイルを削除してもよいですか?"
  :page/open-in-finder "ディレクトリで開く"
- :page/open-with-default-app "デフォルトのアプリで開く"
+ :page/open-with-default-app "既定のアプリで開く"
  :page/make-public "パブリッシュのため公開する"
  :page/version-history "ページ履歴の確認"
  :page/open-backup-directory "ページのバックアップディレクトリを開く"
@@ -254,7 +263,7 @@
  :context-menu/input-template-name "このテンプレートの名前は?"
  :context-menu/template-include-parent-block "テンプレートに親ブロックを含めますか?"
  :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-2 "玄人なユーザーには、バージョンコントロールとして"
  :settings-page/git-desc-3 " の利用も用意しています。Gitの利用はご自身の責任で行ってください。一般的なGitの問題について、Logseqチームはサポートしません。"
@@ -279,7 +288,7 @@
  :settings-page/disable-sentry "使用状況データと診断内容をLogseqへ送信します。"
  :settings-page/disable-sentry-desc "Logseqはあなたのローカルなグラフを決して取得しませんし、あなたのデータを売ることも決してありません。 "
  :settings-page/preferred-outdenting "論理的なアウトデント"
- :settings-page/preferred-outdenting-tip "左側はデフォルトの設定のアウトデントを示しています。右側は論理的なアウトデントを有効にした場合のアウトデントを示しています。"
+ :settings-page/preferred-outdenting-tip "左側は既定のままの場合のアウトデントを示しています。右側は論理的なアウトデントを有効にした場合のアウトデントを示しています。"
  :settings-page/preferred-outdenting-tip-more "→ もっと学ぶ"
  :settings-page/show-full-blocks "ブロック参照の全ての行を表示する"
  :settings-page/auto-expand-block-refs "ズームインしたとき、ブロック参照を自動で展開する"
@@ -296,7 +305,7 @@
  :settings-page/enable-tooltip "ツールチップ"
  :settings-page/enable-journals "日誌"
  :settings-page/enable-all-pages-public "パブリッシュ時には全てのページを公開する"
- :settings-page/home-default-page "デフォルトのホームページを設定"
+ :settings-page/home-default-page "起動時のホームページを設定"
  :settings-page/clear-cache "キャッシュをクリア"
  :settings-page/clear "クリア"
  :settings-page/clear-cache-warning "キャッシュをクリアすると、開いているグラフは破棄されます。保存されていない変更は失われます。"
@@ -305,6 +314,7 @@
  :settings-page/current-version "現在のバージョン"
  :settings-page/tab-general "一般"
  :settings-page/tab-editor "エディタ"
+ :settings-page/tab-keymap "キーマップ"
  :settings-page/tab-version-control "バージョンコントロール"
  :settings-page/tab-account "アカウント"
  :settings-page/tab-advanced "高度な設定"
@@ -316,7 +326,7 @@
  :settings-page/filename-format "ファイル名の書式"
  :settings-page/alpha-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-desc-1 "Syncを設定し、使うための手引きを見るには、"
  :settings-page/sync-desc-2 "ここ"
@@ -337,6 +347,8 @@
  :settings-page/update-error-2 "次のリンクをご確認ください:"
  :settings-permission/start-granting "認証"
 
+ :settings-page/auto-chmod "ファイルの権限を自動で変更する"
+ :settings-page/auto-chmod-desc "グループ メンバーシップを用いて、複数人のユーザに編集を許可する権限を与えたい場合、この機能を無効にしてください。"
  :yes "はい"
 
  :submit "投稿"
@@ -445,6 +457,7 @@
  :whiteboard/dashboard-card-edited "編集:"
  :whiteboard/toggle-grid "格子の表示/非表示"
  :whiteboard/snap-to-grid "図形を格子に合わせる"
+ :whiteboard/toggle-pen-mode "ペンモードを切り替える"
  :flashcards/modal-welcome-title "フラッシュカードを作成する時間です!"
  :flashcards/modal-welcome-desc-1 "ブロックに「#card」を追加することでカードとすることができます。また、「/cloze」で穴埋めを追加できます。"
  :flashcards/modal-welcome-desc-2 "ドキュメントを見るには、"
@@ -605,6 +618,7 @@
  :file-sync/other-user-graph "現在のローカルグラフは他のユーザーのリモートグラフにバインドされています。同期を開始できません。"
  :file-sync/graph-deleted "現在のリモートグラフが削除されました"
  :file-sync/rsapi-cannot-upload-err "同期が始まらない場合、お使いのコンピュータの時間が正しいか確認してください。"
+ :file-sync/connectivity-testing-failed "ネットワーク接続のテストに失敗しました。ネットワークの設定を確認してください。テストのURLは以下になります:"
 
  :notification/clear-all "全てクリア"
 
@@ -618,6 +632,20 @@
  :shortcut.category/whiteboard "ホワイトボード"
  :shortcut.category/others "その他"
  :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/maximize "最大化"
  :window/restore "復元"
@@ -677,7 +705,7 @@
  :command.editor/replace-block-reference-at-point "この場所でブロック参照をそのコンテンツで置き換え"
  :command.editor/paste-text-in-one-block-at-point "カーソル位置へ文字列として貼り付け"
  :command.editor/insert-youtube-timestamp "YouTubeのタイプスタンプを挿入"
- :command.editor/cycle-todo "現在の項目の TODO 状態をローテートさせる"
+ :command.editor/cycle-todo "現在の項目の TODO 状態を循環させる"
  :command.editor/up "カーソル上移動 / 上を選択"
  :command.editor/down "カーソル下移動 / 下を選択"
  :command.editor/left "カーソル左移動 / 左を選択"
@@ -735,6 +763,8 @@
  :command.go/electron-jump-to-the-next "文字列検索で次を検索"
  :command.go/electron-jump-to-the-previous "文字列検索で前を検索"
  :command.go/search "検索"
+ :command.command-palette/toggle "検索コマンド"
+ :command.go/search-in-page "ページ内のブロックを検索"
  :command.go/journals "日誌"
  :command.go/backward "戻る"
  :command.go/forward "前へ"
@@ -767,8 +797,11 @@
  :command.ui/toggle-help "ヘルプの表示/非表示"
  :command.ui/toggle-theme "テーマの切り替え"
  :command.ui/toggle-contents "目次の開閉"
+ :command.ui/cycle-color-off "循環させた色を元に戻す"
+ :command.ui/cycle-color "色を循環させる"
+
  :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/copy-current-file "現在のファイルをコピー"
  :command.editor/copy-page-url "ページのURLをコピー"
@@ -782,4 +815,5 @@
  :command.dev/show-block-data "(開発) ブロックのデータを表示"
  :command.dev/show-block-ast "(開発) ブロックの抽象構文木の表示"
  :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_.
 - 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!
-    - ((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?
     - Naturalmente, questo è un tag #falso.
 - 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])]
     (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
   (let [conn (core-test/get-current-conn)
         data [{:block/uuid "1"}
@@ -42,14 +28,3 @@
         r (d/q outliner/get-by-parent-id @conn [:block/uuid "1"])
         result (flatten r)]
     (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))))

Some files were not shown because too many files changed in this diff