Explorar el Código

Merge branch 'feat/db' into refactor/narrow-gap-between-page-and-block

Tienson Qin hace 1 año
padre
commit
b3a786db02
Se han modificado 34 ficheros con 467 adiciones y 306 borrados
  1. 1 1
      deps.edn
  2. 1 1
      deps/common/package.json
  3. 1 1
      deps/common/src/logseq/common/uuid.cljs
  4. 3 3
      deps/common/yarn.lock
  5. 1 1
      deps/db/deps.edn
  6. 1 1
      deps/db/package.json
  7. 3 3
      deps/db/yarn.lock
  8. 2 1
      deps/graph-parser/deps.edn
  9. 2 2
      deps/graph-parser/package.json
  10. 37 39
      deps/graph-parser/src/logseq/graph_parser/exporter.cljs
  11. 28 1
      deps/graph-parser/test/logseq/graph_parser/exporter_test.cljs
  12. 43 7
      deps/graph-parser/test/resources/exporter-test-graph/whiteboards/block tests.edn
  13. 3 3
      deps/graph-parser/yarn.lock
  14. 1 1
      deps/outliner/deps.edn
  15. 1 1
      deps/outliner/package.json
  16. 3 2
      deps/outliner/src/logseq/outliner/pipeline.cljs
  17. 3 3
      deps/outliner/yarn.lock
  18. 1 1
      deps/publishing/package.json
  19. 3 3
      deps/publishing/yarn.lock
  20. 1 1
      scripts/package.json
  21. 3 3
      scripts/yarn.lock
  22. 4 4
      src/main/frontend/components/block.cljs
  23. 3 1
      src/main/frontend/components/imports.cljs
  24. 124 114
      src/main/frontend/components/repo.cljs
  25. 20 2
      src/main/frontend/components/repo.css
  26. 18 2
      src/main/frontend/components/user/login.cljs
  27. 26 21
      src/main/frontend/handler/editor.cljs
  28. 13 11
      src/main/frontend/handler/events.cljs
  29. 8 7
      src/main/frontend/handler/file_based/events.cljs
  30. 36 30
      src/main/frontend/state.cljs
  31. 2 3
      src/main/frontend/worker/rtc/client.cljs
  32. 5 6
      src/main/frontend/worker/rtc/const.cljs
  33. 64 24
      src/main/frontend/worker/rtc/skeleton.cljs
  34. 2 2
      src/resources/dicts/en.edn

+ 1 - 1
deps.edn

@@ -4,7 +4,7 @@
   rum/rum                               {:mvn/version "0.12.9"}
 
   datascript/datascript                 {:git/url "https://github.com/logseq/datascript" ;; fork
-                                         :sha     "18a9b14a6f448ac25cf200952eda5c656e243f58"}
+                                         :sha     "8c7168b0c29ae2dc3c3efb5d28188916e991bde1"}
 
   datascript-transit/datascript-transit {:mvn/version "0.3.0"}
   borkdude/rewrite-edn                  {:mvn/version "0.4.7"}

+ 1 - 1
deps/common/package.json

@@ -3,7 +3,7 @@
   "version": "1.0.0",
   "private": true,
   "devDependencies": {
-    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v14"
+    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v15"
   },
   "scripts": {
     "test": "yarn nbb-logseq -cp test -m nextjournal.test-runner"

+ 1 - 1
deps/common/src/logseq/common/uuid.cljs

@@ -23,7 +23,7 @@ the remaining chars for data of this type"
   "00000002-<hash-of-db-ident>-<padding-with-0>"
   [db-ident]
   {:pre [(keyword? db-ident)]}
-  (let [hash-num (str (abs (hash db-ident)))
+  (let [hash-num (str (Math/abs (hash db-ident)))
         part1 (fill-with-0 (subs hash-num 0 4) 4)
         part2 (fill-with-0 (subs hash-num 4 8) 4)
         part3 (fill-with-0 (subs hash-num 8 12) 4)

+ 3 - 3
deps/common/yarn.lock

@@ -2,9 +2,9 @@
 # yarn lockfile v1
 
 
-"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v14":
-  version "1.2.173-feat-db-v14"
-  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/675a60baadd6e76829d30fbaaca89917c51cf0a9"
+"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v15":
+  version "1.2.173-feat-db-v15"
+  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/2555e106853ad4f14e21435380ffc0ce17854def"
   dependencies:
     import-meta-resolve "^2.1.0"
 

+ 1 - 1
deps/db/deps.edn

@@ -1,7 +1,7 @@
 {:deps
  ;; These deps are kept in sync with https://github.com/logseq/nbb-logseq/blob/main/bb.edn
  {datascript/datascript {:git/url "https://github.com/logseq/datascript" ;; fork
-                         :sha     "18a9b14a6f448ac25cf200952eda5c656e243f58"}
+                         :sha     "8c7168b0c29ae2dc3c3efb5d28188916e991bde1"}
   datascript-transit/datascript-transit {:mvn/version "0.3.0"
                                          :exclusions [datascript/datascript]}
   cljs-bean/cljs-bean         {:mvn/version "1.5.0"}

+ 1 - 1
deps/db/package.json

@@ -3,7 +3,7 @@
   "version": "1.0.0",
   "private": true,
   "devDependencies": {
-    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v14"
+    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v15"
   },
   "dependencies": {
     "better-sqlite3": "9.3.0"

+ 3 - 3
deps/db/yarn.lock

@@ -2,9 +2,9 @@
 # yarn lockfile v1
 
 
-"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v14":
-  version "1.2.173-feat-db-v14"
-  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/675a60baadd6e76829d30fbaaca89917c51cf0a9"
+"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v15":
+  version "1.2.173-feat-db-v15"
+  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/2555e106853ad4f14e21435380ffc0ce17854def"
   dependencies:
     import-meta-resolve "^2.1.0"
 

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

@@ -18,7 +18,8 @@
  ;; with karma, shadow-cljs.edn and headless mode on CI
  {:test {:extra-paths ["test"]
          :extra-deps  {olical/cljs-test-runner   {:mvn/version "3.8.0"}
-                       org.clojure/clojurescript {:mvn/version "1.11.132"}}
+                       org.clojure/clojurescript {:mvn/version "1.11.132"}
+                       logseq/outliner {:local/root "../outliner"}}
          :main-opts   ["-m" "cljs-test-runner.main"]}
 
   :clj-kondo {:replace-deps {clj-kondo/clj-kondo {:mvn/version "2023.05.26"}}

+ 2 - 2
deps/graph-parser/package.json

@@ -3,13 +3,13 @@
   "version": "1.0.0",
   "private": true,
   "devDependencies": {
-    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v14",
+    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v15",
     "better-sqlite3": "9.3.0"
   },
   "dependencies": {
     "mldoc": "^1.5.8"
   },
   "scripts": {
-    "test": "nbb-logseq -cp test -m nextjournal.test-runner"
+    "test": "nbb-logseq -cp test:../outliner/src -m nextjournal.test-runner"
   }
 }

+ 37 - 39
deps/graph-parser/src/logseq/graph_parser/exporter.cljs

@@ -25,12 +25,8 @@
             [logseq.db.frontend.order :as db-order]
             [logseq.db.frontend.db-ident :as db-ident]
             [logseq.db.frontend.property.build :as db-property-build]
-            [logseq.db.frontend.malli-schema :as db-malli-schema]))
-
-(defn- get-pid
-  "Get a property's id (name or uuid) given its name. For db graphs"
-  [db property-name]
-  (:block/uuid (ldb/get-page db property-name)))
+            [logseq.db.frontend.malli-schema :as db-malli-schema]
+            [logseq.graph-parser.property :as gp-property]))
 
 (defn- add-missing-timestamps
   "Add updated-at or created-at timestamps if they doesn't exist"
@@ -63,9 +59,11 @@
    because it has been moved"
   [db tag-block page-names-to-uuids tag-classes all-idents]
   (if-let [new-class (:block.temp/new-class tag-block)]
-    (merge (find-or-create-class db new-class all-idents)
-           (when-let [existing-tag-uuid (get page-names-to-uuids (common-util/page-name-sanity-lc new-class))]
-             {:block/uuid existing-tag-uuid}))
+    (let [class-m (find-or-create-class db new-class all-idents)]
+      (merge class-m
+             (if-let [existing-tag-uuid (get page-names-to-uuids (common-util/page-name-sanity-lc new-class))]
+               {:block/uuid existing-tag-uuid}
+               {:block/uuid (common-uuid/gen-uuid :db-ident-block-uuid (:db/ident class-m))})))
     (when (contains? tag-classes (:block/name tag-block))
       (if-let [existing-tag-uuid (first
                                   (d/q '[:find [?uuid ...]
@@ -161,12 +159,10 @@
           (assoc :logseq.task/status status-ident)
           (update :block/title string/replace-first (re-pattern (str marker "\\s*")) "")
           (update :block/tags (fnil conj []) :logseq.class/task)
+          ;; FIXME: block/refs property calculation should be handled by a listener
           (update :block/refs (fn [refs]
                                 (into (remove #(= marker (:block/title %)) refs)
-                                      [:logseq.class/task :logseq.task/status status-ident])))
-          (update :block/path-refs (fn [refs]
-                                     (into (remove #(= marker (:block/title %)) refs)
-                                           [:logseq.class/task :logseq.task/status status-ident])))
+                                      [:logseq.class/task :logseq.task/status])))
           (dissoc :block/marker)))
     block))
 
@@ -183,12 +179,10 @@
       (-> block
           (assoc :logseq.task/priority priority-value)
           (update :block/title string/replace-first (re-pattern (str "\\[#" priority "\\]" "\\s*")) "")
+          ;; FIXME: block/refs property calculation should be handled by a listener
           (update :block/refs (fn [refs]
                                 (into (remove #(= priority (:block/title %)) refs)
-                                      [:logseq.task/priority priority-value])))
-          (update :block/path-refs (fn [refs]
-                                     (into (remove #(= priority (:block/title %)) refs)
-                                           [:logseq.task/priority priority-value])))
+                                      [:logseq.task/priority])))
           (dissoc :block/priority)))
     block))
 
@@ -214,7 +208,6 @@
        (-> block
            (assoc :logseq.task/deadline [:block/uuid (:block/uuid deadline-page)])
            (update :block/refs (fnil into []) [:logseq.task/deadline [:block/uuid (:block/uuid deadline-page)]])
-           (update :block/path-refs (fnil into []) [:logseq.task/deadline [:block/uuid (:block/uuid deadline-page)]])
            (dissoc :block/deadline :block/scheduled :block/repeated?))
        :properties-tx (when-not existing-journal-page [deadline-page])})
     {:block block :properties-tx []}))
@@ -324,14 +317,14 @@
                     (let [property-classes (set (map keyword (:property-classes options)))]
                       (try
                         (mapv #(cond (#{:page :block :created-at :updated-at} %)
-                                    %
-                                    (property-classes %)
-                                    :block/tags
-                                    (= :tags %)
+                                     %
+                                     (property-classes %)
+                                     :block/tags
+                                     (= :tags %)
                                      ;; This could also be :logseq.property/page-tags
-                                    :block/tags
-                                    :else
-                                    (get-ident @all-idents %))
+                                     :block/tags
+                                     :else
+                                     (get-ident @all-idents %))
                               (edn/read-string val))
                         (catch :default e
                           (js/console.error "Translating query properties failed with:" e)
@@ -537,9 +530,7 @@
              ;; Add a map of {:block.temp/new-class TAG} to be processed later
              (update :block/tags
                      (fnil into [])
-                     (map #(hash-map :block.temp/new-class %
-                                     :block/uuid (or (get-pid db %) (d/squuid)))
-                          classes-from-properties)))
+                     (map #(hash-map :block.temp/new-class %) classes-from-properties)))
            :properties-tx pvalues-tx})
         {:block block :properties-tx []})
       (update :block dissoc :block/properties :block/properties-text-values :block/properties-order :block/invalid-properties)))
@@ -558,13 +549,14 @@
               (merge (find-or-create-class db (:block/title block) (:all-idents import-state)))
               (seq parent-classes-from-properties)
               (assoc :class/parent
-                     (let [new-class (first parent-classes-from-properties)]
+                     (let [new-class (first parent-classes-from-properties)
+                           class-m (find-or-create-class db new-class (:all-idents import-state))]
                        (when (> (count parent-classes-from-properties) 1)
                          (log-fn :skipped-parent-classes "Only one parent class is allowed so skipped ones after the first one" :classes parent-classes-from-properties))
-                       (merge (find-or-create-class db new-class (:all-idents import-state))
+                       (merge class-m
                               (if-let [existing-tag-uuid (get page-names-to-uuids (common-util/page-name-sanity-lc new-class))]
                                 {:block/uuid existing-tag-uuid}
-                                {:block/uuid (d/squuid)}))))))
+                                {:block/uuid (common-uuid/gen-uuid :db-ident-block-uuid (:db/ident class-m))}))))))
           (dissoc block* :block/properties))]
     {:block block' :properties-tx properties-tx}))
 
@@ -576,10 +568,7 @@
      (cond-> block
        (and (seq property-classes) (seq (:block/refs block*)))
        ;; remove unused, nonexistent property page
-       (update :block/refs (fn [refs] (remove #(property-classes (keyword (:block/name %))) refs)))
-       (and (seq property-classes) (seq (:block/path-refs block*)))
-       ;; remove unused, nonexistent property page
-       (update :block/path-refs (fn [refs] (remove #(property-classes (keyword (:block/name %))) refs))))
+       (update :block/refs (fn [refs] (remove #(property-classes (keyword (:block/name %))) refs))))
      :properties-tx properties-tx}))
 
 (defn- update-block-refs
@@ -843,6 +832,13 @@
      :property-pages-tx (concat property-pages-tx converted-property-pages-tx)
      :property-page-properties-tx property-page-properties-tx}))
 
+(defn- update-whiteboard-blocks [blocks format]
+  (map (fn [b]
+         (if (seq (:block/properties b))
+           (update b :block/title #(gp-property/remove-properties format %))
+           b))
+       blocks))
+
 (defn- extract-pages-and-blocks
   [db file content {:keys [extract-options notify-user]}]
   (let [format (common-util/get-format file)
@@ -857,7 +853,8 @@
           (extract/extract file content extract-options')
 
           (common-config/whiteboard? file)
-          (extract/extract-whiteboard-edn file content extract-options')
+          (-> (extract/extract-whiteboard-edn file content extract-options')
+              (update :blocks update-whiteboard-blocks format))
 
           :else
           (notify-user {:msg (str "Skipped file since its format is not supported: " file)}))))
@@ -900,7 +897,7 @@
         {:keys [property-pages-tx property-page-properties-tx] pages-tx' :pages-tx}
         (split-pages-and-properties-tx pages-tx old-properties existing-pages (:import-state options))
         ;; Necessary to transact new property entities first so that block+page properties can be transacted next
-        main-props-tx-report (d/transact! conn property-pages-tx)
+        main-props-tx-report (d/transact! conn property-pages-tx {:new-graph? true})
 
         ;; Build indices
         pages-index (map #(select-keys % [:block/uuid]) pages-tx')
@@ -917,11 +914,12 @@
         tx (concat whiteboard-pages pages-index page-properties-tx property-page-properties-tx pages-tx' blocks-index blocks-tx)
         tx' (common-util/fast-remove-nils tx)
         ;; _ (when (not (seq whiteboard-pages)) (cljs.pprint/pprint {:tx tx'}))
-        main-tx-report (d/transact! conn tx')
+        ;; :new-graph? needed for :block/path-refs to be calculated
+        main-tx-report (d/transact! conn tx' {:new-graph? true})
 
         upstream-properties-tx
         (build-upstream-properties-tx @conn @(:upstream-properties tx-options) (:import-state options) log-fn)
-        upstream-tx-report (when (seq upstream-properties-tx) (d/transact! conn upstream-properties-tx))]
+        upstream-tx-report (when (seq upstream-properties-tx) (d/transact! conn upstream-properties-tx {:new-graph? true}))]
 
     ;; Return all tx-reports that occurred in this fn as UI needs to know what changed
     [main-props-tx-report main-tx-report upstream-tx-report]))

+ 28 - 1
deps/graph-parser/test/logseq/graph_parser/exporter_test.cljs

@@ -4,6 +4,7 @@
             [logseq.graph-parser.test.docs-graph-helper :as docs-graph-helper]
             [datascript.core :as d]
             [clojure.string :as string]
+            [clojure.set :as set]
             ["path" :as node-path]
             ["fs" :as fs]
             [logseq.common.graph :as common-graph]
@@ -16,7 +17,8 @@
             [logseq.db.frontend.property :as db-property]
             [logseq.db.frontend.property.type :as db-property-type]
             [logseq.common.config :as common-config]
-            [logseq.db :as ldb]))
+            [logseq.db :as ldb]
+            [logseq.outliner.db-pipeline :as db-pipeline]))
 
 ;; Helpers
 ;; =======
@@ -149,6 +151,8 @@
   (p/let [file-graph-dir "test/resources/exporter-test-graph"
           conn (d/create-conn db-schema/schema-for-db-based-graph)
           _ (d/transact! conn (sqlite-create-graph/build-db-initial-data "{}"))
+          ;; Simulate frontend path-refs being calculated
+          _ (db-pipeline/add-listener conn)
           assets (atom [])
           {:keys [import-state]} (import-file-graph-to-db file-graph-dir conn {:assets assets})]
 
@@ -338,6 +342,29 @@
                   count))
           "A block with different case of same ref names has 1 distinct ref"))
 
+    (testing "block refs and path-refs"
+      (let [block (find-block-by-content @conn "old todo block")]
+        (is (set/subset?
+             #{:logseq.task/status :logseq.class/task}
+             (->> block
+                  :block/path-refs
+                  (map #(:db/ident (d/entity @conn (:db/id %))))
+                  set))
+            "Correct :block/refs")
+        (is (set/subset?
+             #{:logseq.task/status :logseq.class/task}
+             (->> block
+                  :block/path-refs
+                  (map #(:db/ident (d/entity @conn (:db/id %))))
+                  set))
+            "Correct :block/path-refs")))
+
+    (testing "whiteboards"
+      (let [block-with-props (find-block-by-content @conn #"block with props")]
+        (is (= {:user.property/prop-num 10}
+               (readable-properties @conn block-with-props)))
+        (is (= "block with props" (:block/title block-with-props)))))
+
     (testing "tags without tag options"
       (let [block (find-block-by-content @conn #"Inception")
             tag-page (find-page-by-name @conn "Movie")

+ 43 - 7
deps/graph-parser/test/resources/exporter-test-graph/whiteboards/ref page.edn → deps/graph-parser/test/resources/exporter-test-graph/whiteboards/block tests.edn

@@ -7,9 +7,9 @@
 :block/properties
 {}
 :block/updated-at 1720809014394
-:block/uuid #uuid "6691677f-c208-4c83-aa40-6efc4286100c"}
-{:block/created-at 1720808993087
-:block/properties
+:block/uuid #uuid "6691677f-c208-4c83-aa40-6efc4286100c"} 
+{:block/created-at 1721935480784
+:block/properties 
 {:ls-type :whiteboard-shape
 :logseq.tldraw.shape
 {:blockType "B"
@@ -34,7 +34,7 @@
 :collapsedHeight 0
 :nonce 1720805246071
 :pageName nil}}
-:block/updated-at 1720808993087}
+:block/updated-at 1721935480784} 
 {:block/title "block with block ref ((669168ed-8734-4943-8a86-5e3a553a526d))"
 :block/created-at 1720808993012
 :block/format :markdown
@@ -70,9 +70,45 @@
 :nonce 1720808990579
 :pageName nil}}
 :block/updated-at 1720809157310
-:block/created-at 1720809157310})
+:block/created-at 1720809157310} 
+{:block/title "block with props\nprop-num:: 10"
+:block/created-at 1721935480737
+:block/format :markdown
+:block/parent 
+{:block/uuid #uuid "6691676f-2eed-4619-b56a-69fd7d572c59"}
+:block/properties 
+{:prop-num 10}
+:block/updated-at 1721935504617
+:block/uuid #uuid "66a2a678-1cea-44b6-a458-4b8c15e18a8d"} 
+{:block/properties 
+{:ls-type :whiteboard-shape
+:logseq.tldraw.shape 
+{:blockType "B"
+:stroke ""
+:collapsed false
+:borderRadius 8
+:scale [1 1]
+:pageId "66a2a678-1cea-44b6-a458-4b8c15e18a8d"
+:scaleLevel "md"
+:fill ""
+:compact true
+:isAutoResizing true
+:type "logseq-portal"
+:size [400 320]
+:strokeType "line"
+:strokeWidth 2
+:opacity 1
+:id "86f86420-4abb-11ef-9161-b98dd17dbef1"
+:noFill false
+:point [648.5671437694965 179.003191006195]
+:parentId "6691676f-2eed-4619-b56a-69fd7d572c59"
+:collapsedHeight 0
+:nonce 1721935475555
+:pageName nil}}
+:block/updated-at 1721935504609
+:block/created-at 1721935504609})
 :pages (
-{:block/tx-id 536871072
+{:block/tx-id 536871657
 :block/uuid #uuid "6691676f-2eed-4619-b56a-69fd7d572c59"
 :block/properties
 {:ls-type :whiteboard-page
@@ -83,7 +119,7 @@
 {}
 :nonce 1
 :assets []}}
-:block/updated-at 1720809157310
+:block/updated-at 1721935504609
 :block/created-at 1720805231835
 :block/format :markdown
 :block/type ["page" "whiteboard"]

+ 3 - 3
deps/graph-parser/yarn.lock

@@ -2,9 +2,9 @@
 # yarn lockfile v1
 
 
-"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v14":
-  version "1.2.173-feat-db-v14"
-  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/675a60baadd6e76829d30fbaaca89917c51cf0a9"
+"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v15":
+  version "1.2.173-feat-db-v15"
+  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/2555e106853ad4f14e21435380ffc0ce17854def"
   dependencies:
     import-meta-resolve "^2.1.0"
 

+ 1 - 1
deps/outliner/deps.edn

@@ -1,7 +1,7 @@
 {:deps
  ;; External deps should be kept in sync with https://github.com/logseq/nbb-logseq/blob/main/bb.edn
  {datascript/datascript {:git/url "https://github.com/logseq/datascript" ;; fork
-                         :sha     "18a9b14a6f448ac25cf200952eda5c656e243f58"}
+                         :sha     "8c7168b0c29ae2dc3c3efb5d28188916e991bde1"}
   logseq/db             {:local/root "../db"}
   logseq/graph-parser   {:local/root "../db"}
   com.cognitect/transit-cljs {:mvn/version "0.8.280"}

+ 1 - 1
deps/outliner/package.json

@@ -3,7 +3,7 @@
   "version": "1.0.0",
   "private": true,
   "devDependencies": {
-    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v14"
+    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v15"
   },
   "dependencies": {
     "better-sqlite3": "9.3.0",

+ 3 - 2
deps/outliner/src/logseq/outliner/pipeline.cljs

@@ -144,12 +144,13 @@
 (defn db-rebuild-block-refs
   "Rebuild block refs for DB graphs"
   [db block]
-  (let [built-in-props (set (keys db-property/built-in-properties))
+  (let [private-built-in-props (set (keep (fn [[k v]] (when-not (get-in v [:schema :public?]) k))
+                                          db-property/built-in-properties))
         ;; explicit lookup in order to be nbb compatible
         properties (->> (entity-plus/lookup-kv-then-entity (d/entity db (:db/id block)) :block/properties)
                         (into {}))
         property-key-refs (->> (keys properties)
-                               (remove built-in-props))
+                               (remove private-built-in-props))
         page-or-object? (fn [block] (and (de/entity? block)
                                          (or (ldb/page? block)
                                              (seq (:block/tags block)))))

+ 3 - 3
deps/outliner/yarn.lock

@@ -2,9 +2,9 @@
 # yarn lockfile v1
 
 
-"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v14":
-  version "1.2.173-feat-db-v14"
-  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/675a60baadd6e76829d30fbaaca89917c51cf0a9"
+"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v15":
+  version "1.2.173-feat-db-v15"
+  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/2555e106853ad4f14e21435380ffc0ce17854def"
   dependencies:
     import-meta-resolve "^2.1.0"
 

+ 1 - 1
deps/publishing/package.json

@@ -3,7 +3,7 @@
   "version": "1.0.0",
   "private": true,
   "devDependencies": {
-    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v14",
+    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v15",
     "mldoc": "^1.5.8"
   },
   "dependencies": {

+ 3 - 3
deps/publishing/yarn.lock

@@ -2,9 +2,9 @@
 # yarn lockfile v1
 
 
-"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v14":
-  version "1.2.173-feat-db-v14"
-  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/675a60baadd6e76829d30fbaaca89917c51cf0a9"
+"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v15":
+  version "1.2.173-feat-db-v15"
+  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/2555e106853ad4f14e21435380ffc0ce17854def"
   dependencies:
     import-meta-resolve "^2.1.0"
 

+ 1 - 1
scripts/package.json

@@ -3,7 +3,7 @@
   "version": "1.0.0",
   "private": true,
   "devDependencies": {
-    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v14"
+    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v15"
   },
   "dependencies": {
     "better-sqlite3": "9.3.0",

+ 3 - 3
scripts/yarn.lock

@@ -2,9 +2,9 @@
 # yarn lockfile v1
 
 
-"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v14":
-  version "1.2.173-feat-db-v14"
-  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/675a60baadd6e76829d30fbaaca89917c51cf0a9"
+"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v15":
+  version "1.2.173-feat-db-v15"
+  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/2555e106853ad4f14e21435380ffc0ce17854def"
   dependencies:
     import-meta-resolve "^2.1.0"
 

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

@@ -2984,7 +2984,7 @@
         edit-input-id (str "edit-block-" (:block/uuid block))
         container-id (:container-id config*)
         editing? (or (state/sub-editing? [container-id (:block/uuid block)])
-                  (state/sub-editing? [:unknown-container (:block/uuid block)]))
+                     (state/sub-editing? [:unknown-container (:block/uuid block)]))
         table? (:table? config*)
         custom-query? (boolean (:custom-query? config*))
         ref-or-custom-query? (or ref? custom-query?)
@@ -3069,8 +3069,8 @@
                        (block-handler/on-touch-end event block uuid *show-left-menu? *show-right-menu?))
        :on-touch-cancel (fn [_e]
                           (block-handler/on-touch-cancel *show-left-menu? *show-right-menu?))
-       :on-mouse-over (fn [e]
-                        (block-mouse-over e *control-show? block-id doc-mode?))
+       :on-mouse-enter (fn [e]
+                         (block-mouse-over e *control-show? block-id doc-mode?))
        :on-mouse-leave (fn [e]
                          (block-mouse-leave e *control-show? block-id doc-mode?))}
       (when (and (not slide?) (not in-whiteboard?) (not table?))
@@ -3092,7 +3092,7 @@
         [:div.flex.flex-col.w-full
          (let [block (merge block (block/parse-title-and-body uuid (:block/format block) pre-block? title))
                hide-block-refs-count? (or (and (:embed? config)
-                                           (= (:block/uuid block) (:embed-id config)))
+                                               (= (:block/uuid block) (:embed-id config)))
                                           table?)]
            (block-content-or-editor config block
                                     {:edit-input-id edit-input-id

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

@@ -263,7 +263,9 @@
                      (if (:page location)
                        (str "Page icons can't be imported. Go to the page " (pr-str (:page location)) " to manually import it.")
                        (str "Block icons can't be imported. Manually import it at the block: " (pr-str (:block location))))
-                     (str "Property value has type " (get-in schema [:type :to]) " instead of type " (get-in schema [:type :from])))]))
+                     (if (not= (get-in schema [:type :to]) (get-in schema [:type :from]))
+                       (str "Property value has type " (get-in schema [:type :to]) " instead of type " (get-in schema [:type :from]))
+                       (str "Property should be imported manually")))]))
            (map (fn [[k v]]
                   [:dl.my-2.mb-0
                    [:dt.m-0 [:strong (str k)]]

+ 124 - 114
src/main/frontend/components/repo.cljs

@@ -5,6 +5,7 @@
             [frontend.db :as db]
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.file-based.nfs :as nfs-handler]
+            [frontend.handler.route :as route-handler]
             [frontend.state :as state]
             [frontend.ui :as ui]
             [frontend.util :as util]
@@ -17,7 +18,6 @@
             [cljs.core.async :as async :refer [go <!]]
             [clojure.string :as string]
             [frontend.handler.file-sync :as file-sync]
-            [reitit.frontend.easy :as rfe]
             [frontend.handler.notification :as notification]
             [frontend.util.fs :as fs-util]
             [frontend.handler.user :as user-handler]
@@ -200,16 +200,16 @@
     (p/let [multiple-windows? (ipc/ipc "graphHasMultipleWindows" (state/get-current-repo))]
       (reset! (::electron-multiple-windows? state) multiple-windows?))))
 
-(defn- repos-dropdown-links [repos current-repo downloading-graph-id *multiple-windows? & {:as opts}]
+(defn- repos-dropdown-links [repos current-repo downloading-graph-id & {:as opts}]
   (let [switch-repos (if-not (nil? current-repo)
                        (remove (fn [repo] (= current-repo (:url repo))) repos) repos) ; exclude current repo
         repo-links (mapv
-                     (fn [{:keys [url remote? rtc-graph? GraphName GraphUUID] :as graph}]
-                       (let [local? (config/local-file-based-graph? url)
-                             db-only? (config/db-based-graph? url)
-                             repo-url (cond
-                                        local? (db/get-repo-name url)
-                                        db-only? url
+                    (fn [{:keys [url remote? rtc-graph? GraphName GraphUUID] :as graph}]
+                      (let [local? (config/local-file-based-graph? url)
+                            db-only? (config/db-based-graph? url)
+                            repo-url (cond
+                                       local? (db/get-repo-name url)
+                                       db-only? url
                                        :else GraphName)
                             short-repo-name (if (or local? db-only?)
                                               (text-util/get-graph-name-from-path repo-url)
@@ -239,35 +239,42 @@
 
                                                             :else
                                                             (state/pub-event! [:graph/pull-down-remote-graph graph])))))}})))
-                    switch-repos)
-        refresh-link (let [nfs-repo? (config/local-file-based-graph? current-repo)]
-                       (when (and nfs-repo?
-                                  (not= current-repo config/demo-repo)
-                                  (or (nfs-handler/supported?)
-                                      (mobile-util/native-platform?)))
-                         {:title (t :sync-from-local-files)
-                          :hover-detail (t :sync-from-local-files-detail)
-                          :options {:on-click #(state/pub-event! [:graph/ask-for-re-fresh])}}))
-        reindex-link {:title        (t :re-index)
-                      :hover-detail (t :re-index-detail)
-                      :options (cond->
-                                {:on-click
-                                 (fn []
-                                   (state/pub-event! [:graph/ask-for-re-index *multiple-windows? nil]))})}]
-    (->>
-     (concat repo-links
-             [(when (seq repo-links) {:hr true})
-              (if (or (nfs-handler/supported?) (mobile-util/native-platform?))
-                {:title (t :new-graph) :options {:on-click #(state/pub-event! [:graph/setup-a-repo])}}
-                {:title (t :new-graph) :options {:href (rfe/href :repos)}}) ;; Brings to the repos page for showing fallback message
-              (when config/db-graph-enabled?
-                {:title (str (t :new-graph) " - DB version")
-                 :options {:on-click #(state/pub-event! [:graph/new-db-graph])}})
-              {:title (t :all-graphs) :options {:href (rfe/href :repos)}}
-              refresh-link
-              (when-not (config/db-based-graph? current-repo)
-                reindex-link)])
-     (remove nil?))))
+                    switch-repos)]
+    (->> repo-links (remove nil?))))
+
+(defn- repos-footer [multiple-windows? db-based?]
+  [:div.cp__repos-quick-actions
+   {:on-click #(shui/popup-hide!)}
+
+   (when-not db-based?
+     [:<>
+      (shui/button {:size :sm :variant :ghost
+                    :title (t :sync-from-local-files-detail)
+                    :on-click (fn []
+                                (state/pub-event! [:graph/ask-for-re-fresh]))}
+                   (shui/tabler-icon "file-report") [:span (t :sync-from-local-files)])
+
+      (shui/button {:size :sm :variant :ghost
+                    :title (t :re-index-detail)
+                    :on-click (fn []
+                                (state/pub-event! [:graph/ask-for-re-index multiple-windows? nil]))}
+                   (shui/tabler-icon "folder-bolt") [:span (t :re-index)])])
+
+   (shui/button {:size :sm :variant :ghost
+                 :on-click (fn []
+                             (if (or (nfs-handler/supported?) (mobile-util/native-platform?))
+                               (state/pub-event! [:graph/setup-a-repo])
+                               (route-handler/redirect-to-all-graphs)))}
+                (shui/tabler-icon "folder-plus")
+                [:span (t :new-graph)])
+
+   (shui/button {:size :sm :variant :ghost
+                 :on-click #(state/pub-event! [:graph/new-db-graph])}
+                (shui/tabler-icon "cylinder-plus") [:span "Add new graph (DB version)"])
+
+   (shui/button {:size :sm :variant :ghost
+                 :on-click #(route-handler/redirect-to-all-graphs)}
+                (shui/tabler-icon "layout-2") [:span (t :all-graphs)])])
 
 (rum/defcs repos-dropdown < rum/reactive
   (rum/local false ::electron-multiple-windows?)
@@ -276,83 +283,86 @@
         current-repo (state/sub :git/current-repo)
         login? (boolean (state/sub :auth/id-token))
         remotes-loading? (state/sub [:file-sync/remote-graphs :loading])]
-    (when (or login? current-repo)
-      (let [repos (state/sub [:me :repos])
-            remotes (state/sub [:file-sync/remote-graphs :graphs])
-            rtc-graphs (state/sub :rtc/graphs)
-            downloading-graph-id (state/sub :rtc/downloading-graph-uuid)
-            repos (sort-repos-with-metadata-local repos)
-            repos (distinct
-                   (if (and (or (seq remotes) (seq rtc-graphs)) login?)
-                     (repo-handler/combine-local-&-remote-graphs repos (concat remotes rtc-graphs)) repos))
-            items-fn #(repos-dropdown-links repos current-repo downloading-graph-id multiple-windows? opts)
-            header-fn #(when (> (count repos) 1)            ; show switch to if there are multiple repos
-                         [:div.font-medium.text-sm.opacity-50.px-1.py-1.flex.flex-row.justify-between.items-center
-                          [:div (t :left-side-bar/switch)]
-
-                          (when (and (file-sync/enable-sync?) login?)
-                            (if remotes-loading?
-                              (ui/loading "")
-                              (shui/button
-                               {:variant :ghost
-                                :size :sm
-                                :title "Refresh remote graphs"
-                                :class "!h-6 !px-1 relative right-[-4px]"
-                                :on-click (fn []
-                                            (file-sync/load-session-graphs)
-                                            (rtc-handler/<get-remote-graphs))}
-                               (ui/icon "refresh" {:size 15}))))])]
-        (when (seq repos)
-          (let [remote? (and current-repo (:remote? (first (filter #(= current-repo (:url %)) repos))))
-                repo-name (when current-repo (db/get-repo-name current-repo))
-                short-repo-name (if current-repo
-                                  (db/get-short-repo-name repo-name)
-                                  "Select a Graph")]
-            (shui/trigger-as :a
-                             {:tab-index 0
-                              :class "item cp__repos-select-trigger"
-                              :on-pointer-down
-                              (fn [^js e]
-                                (check-multiple-windows? state)
-                                (some-> (.-target e)
-                                        (.closest "a.item")
-                                        (shui/popup-show!
-                                         (fn [{:keys [id]}]
-                                           [:<>
-                                            (header-fn)
-                                            (for [{:keys [hr item hover-detail title options icon]} (items-fn)]
-                                              (let [on-click' (:on-click options)
-                                                    href' (:href options)]
-                                                (if hr
-                                                  (shui/dropdown-menu-separator)
-                                                  (shui/dropdown-menu-item
-                                                   (assoc options
-                                                          :title hover-detail
-                                                          :on-click (fn [^js e]
-                                                                      (when on-click'
-                                                                        (when-not (false? (on-click' e))
-                                                                          (shui/popup-hide! id)))))
-                                                   (or item
-                                                       (if href'
-                                                         [:a.flex.items-center.w-full
-                                                          {:href href' :on-click #(shui/popup-hide! id)
-                                                           :style {:color "inherit"}} title]
-                                                         [:span.flex.items-center.gap-1.w-full
-                                                          icon [:div title]]))))))])
-                                         {:as-dropdown? true
-                                          :auto-focus? false
-                                          :align "start"
-                                          :content-props {:class "repos-list"}})))
-                              :title repo-name}                            ;; show full path on hover
-                             [:div.flex.relative.graph-icon.rounded
-                              (shui/tabler-icon "database" {:size 15})]
-
-                             [:div.repo-switch.pr-2.whitespace-nowrap
-                              [:span.repo-name.font-medium
-                               [:span.repo-text.overflow-hidden.text-ellipsis
-                                (if (= config/demo-repo short-repo-name) "Demo" short-repo-name)]
-                               (when remote? [:span.pl-1 (ui/icon "cloud")])]
-                              [:span.dropdown-caret]])))))))
+    (let [repos (state/sub [:me :repos])
+          remotes (state/sub [:file-sync/remote-graphs :graphs])
+          rtc-graphs (state/sub :rtc/graphs)
+          downloading-graph-id (state/sub :rtc/downloading-graph-uuid)
+          db-based? (config/db-based-graph? current-repo)
+          repos (sort-repos-with-metadata-local repos)
+          repos (distinct
+                 (if (and (or (seq remotes) (seq rtc-graphs)) login?)
+                   (repo-handler/combine-local-&-remote-graphs repos (concat remotes rtc-graphs)) repos))
+          items-fn #(repos-dropdown-links repos current-repo downloading-graph-id opts)
+          header-fn #(when (> (count repos) 1)              ; show switch to if there are multiple repos
+                       [:div.font-medium.text-sm.opacity-50.px-1.py-1.flex.flex-row.justify-between.items-center
+                        [:h4.pb-1 (t :left-side-bar/switch)]
+
+                        (when (and (file-sync/enable-sync?) login?)
+                          (if remotes-loading?
+                            (ui/loading "")
+                            (shui/button
+                             {:variant :ghost
+                              :size :sm
+                              :title "Refresh remote graphs"
+                              :class "!h-6 !px-1 relative right-[-4px]"
+                              :on-click (fn []
+                                          (file-sync/load-session-graphs)
+                                          (rtc-handler/<get-remote-graphs))}
+                             (ui/icon "refresh" {:size 15}))))])]
+      (when (seq repos)
+        (let [remote? (and current-repo (:remote? (first (filter #(= current-repo (:url %)) repos))))
+              repo-name (when current-repo (db/get-repo-name current-repo))
+              short-repo-name (if current-repo
+                                (db/get-short-repo-name repo-name)
+                                "Select a Graph")]
+          (shui/trigger-as :a
+            {:tab-index 0
+             :class "item cp__repos-select-trigger"
+             :on-pointer-down
+             (fn [^js e]
+               (check-multiple-windows? state)
+               (some-> (.-target e)
+                 (.closest "a.item")
+                 (shui/popup-show!
+                   (fn [{:keys [id]}]
+                     [:<>
+                      (header-fn)
+                      [:div.cp__repos-list-wrap
+                       (for [{:keys [hr item hover-detail title options icon]} (items-fn)]
+                         (let [on-click' (:on-click options)
+                               href' (:href options)]
+                           (if hr
+                             (shui/dropdown-menu-separator)
+                             (shui/dropdown-menu-item
+                               (assoc options
+                                 :title hover-detail
+                                 :on-click (fn [^js e]
+                                             (when on-click'
+                                               (when-not (false? (on-click' e))
+                                                 (shui/popup-hide! id)))))
+                               (or item
+                                 (if href'
+                                   [:a.flex.items-center.w-full
+                                    {:href href' :on-click #(shui/popup-hide! id)
+                                     :style {:color "inherit"}} title]
+                                   [:span.flex.items-center.gap-1.w-full
+                                    icon [:div title]]))))))]
+                      (repos-footer multiple-windows? db-based?)])
+                   {:as-dropdown? true
+                    :auto-focus? false
+                    :align "start"
+                    :content-props {:class "repos-list"
+                                    :data-mode (when db-based? "db")}})))
+             :title repo-name}                              ;; show full path on hover
+            [:div.flex.relative.graph-icon.rounded
+             (shui/tabler-icon "database" {:size 15})]
+
+            [:div.repo-switch.pr-2.whitespace-nowrap
+             [:span.repo-name.font-medium
+              [:span.repo-text.overflow-hidden.text-ellipsis
+               (if (= config/demo-repo short-repo-name) "Demo" short-repo-name)]
+              (when remote? [:span.pl-1 (ui/icon "cloud")])]
+             [:span.dropdown-caret]]))))))
 
 (defn invalid-graph-name-warning
   []

+ 20 - 2
src/main/frontend/components/repo.css

@@ -16,12 +16,20 @@
 
 .ui__dropdown-menu-content {
   &.repos-list {
-    @apply px-2;
-    @apply min-w-[260px] sm:max-w-[380px] max-h-[70vh];
+    @apply px-2 pb-[210px] relative;
+    @apply min-w-[280px] sm:max-w-[320px] max-h-[66vh];
+
+    &[data-mode=db] {
+      @apply pb-[130px];
+    }
 
     .ui__dropdown-menu-item {
       @apply overflow-hidden overflow-ellipsis;
     }
+
+    .cp__repos-list-wrap {
+      @apply max-h-96 overflow-scroll m-[-8px] px-2 pb-2;
+    }
   }
 }
 
@@ -45,4 +53,14 @@
       @apply relative border-t-gray-09 left-1.5 top-[2px];
     }
   }
+}
+
+.cp__repos-quick-actions {
+  @apply absolute left-[1px] right-[1px] bottom-[1px] bg-gray-01 px-2 py-3 border-t
+  flex flex-col rounded-b overflow-hidden;
+
+  .ui__button {
+    @apply w-full !py-4 !justify-start opacity-70 font-medium hover:opacity-90
+    items-center gap-1.5 hover:bg-gray-03;
+  }
 }

+ 18 - 2
src/main/frontend/components/user/login.cljs

@@ -55,14 +55,30 @@
 (rum/defc page-impl
   []
   (let [[ready?, set-ready?] (rum/use-state false)
+        [tab, set-tab!] (rum/use-state :login)
         *ref-el (rum/use-ref nil)]
 
     (rum/use-effect!
       (fn [] (setup-configure!)
         (set-ready? true)
+        (js/setTimeout
+          (fn []
+            (when-let [^js el (some-> (rum/deref *ref-el) (.querySelector ".amplify-tabs"))]
+              (let [btn1 (.querySelector el "button")]
+                (.addEventListener el "pointerdown"
+                  (fn [^js e]
+                    (if (= (.-target e) btn1)
+                      (set-tab! :login)
+                      (set-tab! :create-account)))))))))
+      [])
+
+    (rum/use-effect!
+      (fn []
         (when-let [^js el (rum/deref *ref-el)]
-          (js/setTimeout #(some-> (.querySelector el "input[name=username]")
-                                  (.focus)) 100))) [])
+          (js/setTimeout
+            #(some-> (.querySelector el (str "input[name=" (if (= tab :login) "username" "email") "]"))
+               (.focus)) 100)))
+      [tab])
 
     [:div.cp__user-login
      {:ref *ref-el}

+ 26 - 21
src/main/frontend/handler/editor.cljs

@@ -1236,28 +1236,33 @@
       (delete-block-aux! block))))
 
 (defn highlight-selection-area!
-  [end-block & {:keys [append?]}]
+  [end-block-id & {:keys [append?]}]
   (when-let [start-block (state/get-selection-start-block-or-first)]
-    (let [node (gdom/getElement start-block)
-          visible? (and node (util/el-visible-in-viewport? node))
-          selected-blocks (state/get-selection-blocks)
-          latest-visible-block (if visible?
-                                 node
-                                 (or (when-let [node (last selected-blocks)]
-                                       (gdom/getElement (.-id ^js node)))
-                                     (when-let [node (first selected-blocks)]
-                                       (gdom/getElement (.-id ^js node)))))]
-      (when latest-visible-block
-        (let [blocks (util/get-nodes-between-two-nodes latest-visible-block end-block "ls-block")
-              direction (util/get-direction-between-two-nodes latest-visible-block end-block "ls-block")
-              blocks (if (= :up direction)
-                       (reverse blocks)
-                       blocks)]
-          (if append?
-            (do (state/clear-edit!)
-                (state/conj-selection-block! blocks direction))
-            (state/exit-editing-and-set-selected-blocks! blocks direction)))))))
-
+    (let [end-block-node (gdom/getElement end-block-id)
+          start-node (gdom/getElement start-block)
+          select-direction (state/get-selection-direction)
+          selected-blocks (state/get-unsorted-selection-blocks)
+          last-node (when-let [node (last selected-blocks)]
+                      (gdom/getElement (.-id ^js node)))
+          latest-visible-block (or last-node start-node)
+          latest-block-id (when latest-visible-block (.-id latest-visible-block))]
+      (if (and start-node end-block-node)
+        (let [blocks (util/get-nodes-between-two-nodes start-block end-block-id "ls-block")
+              direction (util/get-direction-between-two-nodes start-block end-block-id "ls-block")
+              blocks (if (= direction :up) (reverse blocks) blocks)]
+          (state/exit-editing-and-set-selected-blocks! blocks direction))
+        (when latest-visible-block
+          (let [blocks (util/get-nodes-between-two-nodes latest-block-id end-block-id "ls-block")
+                direction (if (= latest-block-id end-block-id)
+                            select-direction
+                            (util/get-direction-between-two-nodes latest-block-id end-block-id "ls-block"))
+                blocks (if (= direction :up) (reverse (util/sort-by-height blocks)) (util/sort-by-height blocks))]
+            (if append?
+              (do (state/clear-edit!)
+                  (if (and select-direction (not= direction select-direction))
+                    (state/drop-selection-blocks-starts-with! end-block-node)
+                    (state/conj-selection-block! blocks direction)))
+              (state/exit-editing-and-set-selected-blocks! blocks direction))))))))
 
 (defn- select-block-up-down
   [direction]

+ 13 - 11
src/main/frontend/handler/events.cljs

@@ -679,13 +679,14 @@
   (shui/dialog-open!
     [:div {:style {:max-width 700}}
      [:p (t :sync-from-local-changes-detected)]
-     (ui/button
-       (t :yes)
-       :autoFocus "on"
-       :class "ui__modal-enter"
-       :on-click (fn []
-                   (state/close-modal!)
-                   (nfs-handler/refresh! (state/get-current-repo) refresh-cb)))]))
+     [:div.flex.justify-end
+      (ui/button
+        (t :yes)
+        :autoFocus "on"
+        :class "ui__modal-enter"
+        :on-click (fn []
+                    (shui/dialog-close!)
+                    (nfs-handler/refresh! (state/get-current-repo) refresh-cb)))]]))
 
 (defmethod handle :sync/create-remote-graph [[_ current-repo]]
   (let [graph-name (js/decodeURI (util/node-path.basename current-repo))]
@@ -1031,10 +1032,11 @@
         word word)]
      [:div.text-lg
       [:p "Switch to another repo: "]
-      (repo/repos-dropdown {:on-click (fn [e]
-                                        (util/stop e)
-                                        (state/set-state! :error/multiple-tabs-access-opfs? false)
-                                        (state/close-modal!))})]]))
+      [:div.border.rounded.bg-gray-01.overflow-hidden.w-60
+       (repo/repos-dropdown {:on-click (fn [e]
+                                         (util/stop e)
+                                         (state/set-state! :error/multiple-tabs-access-opfs? false)
+                                         (shui/dialog-close!))})]]]))
 
 (defmethod handle :show/multiple-tabs-error-dialog [_]
   (state/set-state! :error/multiple-tabs-access-opfs? true)

+ 8 - 7
src/main/frontend/handler/file_based/events.cljs

@@ -26,13 +26,14 @@
       [:div {:style {:max-width 700}}
        (when (not (nil? ui)) ui)
        [:p (t :re-index-discard-unsaved-changes-warning)]
-       (ui/button
-         (t :yes)
-         :autoFocus "on"
-         :class "ui__modal-enter"
-         :on-click (fn []
-                     (shui/dialog-close!)
-                     (state/pub-event! [:graph/re-index])))])))
+       [:div.flex.justify-end.pt-2
+        (ui/button
+          (t :yes)
+          :autoFocus "on"
+          :class "ui__modal-enter"
+          :on-click (fn []
+                      (shui/dialog-close!)
+                      (state/pub-event! [:graph/re-index])))]])))
 
 (defmethod events/handle :graph/re-index [[_]]
   ;; Ensure the graph only has ONE window instance

+ 36 - 30
src/main/frontend/state.cljs

@@ -162,9 +162,9 @@
       ;; Warning: blocks order is determined when setting this attribute
       :selection/blocks                      (atom [])
       :selection/start-block                 (atom nil)
-      ;; either :up or :down, defaults to down
+      ;; nil, :up or :down
       ;; used to determine selection direction when two or more blocks are selected
-      :selection/direction                   (atom :down)
+      :selection/direction                   (atom nil)
       :selection/selected-all?               (atom false)
       :custom-context-menu/show?             false
       :custom-context-menu/links             nil
@@ -1141,9 +1141,21 @@ Similar to re-frame subscriptions"
   [start-block]
   (set-state! :selection/start-block start-block))
 
+(defn get-selection-direction
+  []
+  @(:selection/direction @state))
+
+(defn get-unsorted-selection-blocks
+  []
+  @(:selection/blocks @state))
+
 (defn get-selection-blocks
   []
-  (util/sort-by-height @(:selection/blocks @state)))
+  (let [result (get-unsorted-selection-blocks)
+        direction (get-selection-direction)]
+    (if (= direction :up)
+      (vec (reverse result))
+      result)))
 
 (defn get-selection-block-ids
   []
@@ -1176,13 +1188,13 @@ Similar to re-frame subscriptions"
 
 (defn set-selection-blocks!
   ([blocks]
-   (set-selection-blocks! blocks :down))
+   (set-selection-blocks! blocks nil))
   ([blocks direction]
    (when (seq blocks)
-     (let [blocks (vec (util/sort-by-height (remove nil? blocks)))]
+     (let [blocks (vec (remove nil? blocks))]
        (set-state! :selection/mode true)
        (set-selection-blocks-aux! blocks)
-       (set-state! :selection/direction direction)))))
+       (when direction (set-state! :selection/direction direction))))))
 
 (defn into-selection-mode!
   []
@@ -1192,7 +1204,7 @@ Similar to re-frame subscriptions"
   []
   (set-state! :selection/mode false)
   (set-state! :selection/blocks nil)
-  (set-state! :selection/direction :down)
+  (set-state! :selection/direction nil)
   (set-state! :selection/start-block nil)
   (set-state! :selection/selected-all? false))
 
@@ -1218,40 +1230,34 @@ Similar to re-frame subscriptions"
 
 (defn conj-selection-block!
   [block-or-blocks direction]
-  (let [selection-blocks (get-selection-blocks)
-        blocks (-> (if (sequential? block-or-blocks)
-                     (apply conj selection-blocks block-or-blocks)
-                     (conj selection-blocks block-or-blocks))
+  (let [selection-blocks (get-unsorted-selection-blocks)
+        block-or-blocks (if (sequential? block-or-blocks) block-or-blocks [block-or-blocks])
+        blocks (-> (concat selection-blocks block-or-blocks)
                    distinct)]
     (set-selection-blocks! blocks direction)))
 
 (defn drop-selection-block!
   [block]
   (set-state! :selection/mode true)
-  (set-selection-blocks-aux! (-> (remove #(= block %) (get-selection-blocks))
-                                 util/sort-by-height
+  (set-selection-blocks-aux! (-> (remove #(= block %) (get-unsorted-selection-blocks))
                                  vec)))
 
+(defn drop-selection-blocks-starts-with!
+  [block]
+  (set-state! :selection/mode true)
+  (let [blocks (get-unsorted-selection-blocks)
+        blocks' (-> (take-while (fn [b] (not= (.-id b) (.-id block))) blocks)
+                    vec
+                    (conj block))]
+    (set-selection-blocks-aux! blocks')))
+
 (defn drop-last-selection-block!
   []
-  (let [direction @(:selection/direction @state)
-        up? (= direction :up)
-        blocks @(:selection/blocks @state)
-        last-block (if up?
-                     (first blocks)
-                     (peek (vec blocks)))
-        blocks' (-> (if up?
-                      (rest blocks)
-                      (pop (vec blocks)))
-                    util/sort-by-height
-                    vec)]
+  (let [blocks @(:selection/blocks @state)
+        blocks' (vec (butlast blocks))]
     (set-state! :selection/mode true)
     (set-selection-blocks-aux! blocks')
-    last-block))
-
-(defn get-selection-direction
-  []
-  @(:selection/direction @state))
+    (last blocks)))
 
 (defn hide-custom-context-menu!
   []
@@ -2015,7 +2021,7 @@ Similar to re-frame subscriptions"
 
 (defn exit-editing-and-set-selected-blocks!
   ([blocks]
-   (exit-editing-and-set-selected-blocks! blocks :down))
+   (exit-editing-and-set-selected-blocks! blocks nil))
   ([blocks direction]
    (clear-edit!)
    (set-selection-blocks! blocks direction)))

+ 2 - 3
src/main/frontend/worker/rtc/client.cljs

@@ -60,9 +60,8 @@
                 (take 5 (drop 2 c.m/delays)) ;retry 5 times if remote-graph is creating (4000 8000 16000 32000 64000)
                 (register-graph-updates get-ws-create-task graph-uuid repo)))
           (let [t (client-op/get-local-tx repo)]
-            (when (and (zero? (client-op/get-unpushed-ops-count repo))
-                       (or (nil? @*last-calibrate-t)
-                           (< 500 (- t @*last-calibrate-t))))
+            (when (or (nil? @*last-calibrate-t)
+                      (< 500 (- t @*last-calibrate-t)))
               (m/? (r.skeleton/new-task--calibrate-graph-skeleton get-ws-create-task graph-uuid conn t))
               (reset! *last-calibrate-t t)))
           (swap! *sent assoc ws true))

+ 5 - 6
src/main/frontend/worker/rtc/const.cljs

@@ -79,7 +79,9 @@
    [:t-before {:optional true} :int]
    [:failed-ops {:optional true} [:sequential to-ws-op-schema]]
    [:s3-presign-url {:optional true} :string]
-   [:diff-data {:optional true} [:map-of :keyword :any]]
+   [:server-schema-version {:optional true} :int]
+   [:server-only-db-ident-blocks {:optional true} :string ;;transit
+    ]
    [:users {:optional true} [:sequential
                              [:map {:closed true}
                               [:user/uuid :uuid]
@@ -233,14 +235,11 @@
       [:action :string]
       [:graph-uuid :string]
       [:t :int]
+      [:schema-version :int]
       [:db-ident-blocks [:sequential
                          [:map
-                          [:block/uuid :uuid]
                           [:db/ident :keyword]
-                          [:block/parent {:optional true} :uuid]
-                          [:block/type {:optional true} [:set :string]]
-                          [:block/order {:optional true} :string]
-                          [:block/title {:optional true} :string]]]]]]
+                          [::m/default extra-attr-map-schema]]]]]]
 
     ["get-assets-upload-urls"
      [:map

+ 64 - 24
src/main/frontend/worker/rtc/skeleton.cljs

@@ -2,42 +2,82 @@
   "Validate skeleton data between server and client"
   (:require [datascript.core :as d]
             [frontend.worker.rtc.ws-util :as ws-util]
+            [logseq.db :as ldb]
             [missionary.core :as m]))
 
-(defn- get-all-db-ident-blocks
+(defn- block-ref-type-attributes
+  [db-schema]
+  (->> db-schema
+       (keep (fn [[k v]]
+               (when (and (keyword? k)
+                          (= :db.type/ref (:db/valueType v)))
+                 k)))
+       set))
+
+(defn- block-card-many-attributes
+  [db-schema]
+  (->> db-schema
+       (keep (fn [[k v]]
+               (when (and (keyword? k)
+                          (= :db.cardinality/many (:db/cardinality v)))
+                 k)))
+       set))
+
+(defn- get-builtin-db-ident-blocks
+  [db]
+  (let [db-schema (d/schema db)
+        block-ref-type-attrs (block-ref-type-attributes db-schema)
+        block-card-many-attrs (block-card-many-attributes db-schema)
+        pull-pattern ['* (into {} (map (fn [k] [k [:block/uuid]]) block-ref-type-attrs))]
+        block-ids (->> (d/q '[:find ?b
+                              :in $
+                              :where
+                              [?b :db/ident]
+                              [?b :block/uuid]
+                              [?b :logseq.property/built-in?]]
+                            db)
+                       (apply concat))]
+    (map (fn [m]
+           (into {}
+                 (keep
+                  (fn [[k v]]
+                    (when-not (contains? #{:db/id} k)
+                      (let [v-converter
+                            (case [(contains? block-ref-type-attrs k)
+                                   (contains? block-card-many-attrs k)]
+                              [true true] (partial map :block/uuid)
+                              [true false] :block/uuid
+                              [false true] (partial map ldb/write-transit-str)
+                              [false false] ldb/write-transit-str)
+                            v* (v-converter v)
+                            v** (if (contains? #{:db/ident :block/order} k)
+                                  (ldb/read-transit-str v*)
+                                  v*)]
+                        [k v**]))))
+                 m))
+         (d/pull-many db pull-pattern block-ids))))
+
+(defn- get-schema-version
   [db]
-  (let [db-ident-coll (map :v (d/datoms db :avet :db/ident))
-        db-ident-blocks (->> db-ident-coll
-                             (d/pull-many db [:db/ident
-                                              :block/uuid
-                                              {:block/parent [:block/uuid]}
-                                              :block/order
-                                              :block/type
-                                              :block/title])
-                             (filter :block/uuid))]
-    (map
-     (fn [block]
-       (cond-> block
-         (:block/parent block) (update :block/parent :block/uuid)))
-     db-ident-blocks)))
+  (:kv/value (d/entity db :logseq.kv/schema-version)))
 
 (defn new-task--calibrate-graph-skeleton
   [get-ws-create-task graph-uuid conn t]
   (m/sp
     (let [db @conn
-          db-ident-blocks (get-all-db-ident-blocks db)
+          db-ident-blocks (get-builtin-db-ident-blocks db)
           r (m/? (ws-util/send&recv get-ws-create-task
                                     {:action "calibrate-graph-skeleton"
                                      :graph-uuid graph-uuid
                                      :t t
-                                     :db-ident-blocks db-ident-blocks}))]
+                                     :db-ident-blocks db-ident-blocks
+                                     :schema-version (get-schema-version db)}))]
       (if-let [remote-ex (:ex-data r)]
-        (case (:type remote-ex)
-          :t-not-matched nil
-        ;;else
-          (throw (ex-info "Unavailable" {:remote-ex remote-ex})))
-        (let [diff-data (:diff-data r)]
-          (when (seq diff-data)
+        (throw (ex-info "Unavailable" {:remote-ex remote-ex}))
+        (let [server-only-db-ident-blocks (some-> (:server-only-db-ident-blocks r)
+                                                  ldb/read-transit-str)]
+          (when (seq server-only-db-ident-blocks)
             (throw (ex-info "different graph skeleton between server and client"
                             {:type :rtc.exception/different-graph-skeleton
-                             :diff-data diff-data}))))))))
+                             :server-schema-version (:server-schema-version r)
+                             :server-only-db-ident-blocks server-only-db-ident-blocks}))))))))

+ 2 - 2
src/resources/dicts/en.edn

@@ -346,11 +346,11 @@
  :type "Type"
  :host "Host"
  :port "Port"
- :re-index "Re-index"
+ :re-index "Re-index current graph"
  :re-index-detail "Rebuild the graph"
  :re-index-multiple-windows-warning "You need to close the other windows before re-index this graph."
  :re-index-discard-unsaved-changes-warning "Re-index will discard the current graph, and then processes all the files again as they are currently stored on disk. You will lose unsaved changes and it might take a while. Continue?"
- :sync-from-local-files "Refresh"
+ :sync-from-local-files "Refresh local files"
  :sync-from-local-files-detail "Import changes from local files"
  :sync-from-local-changes-detected "Refresh detects and processes files modified on your disk that have diverged from the current Logseq page content. Continue?"