Forráskód Böngészése

refactor: replace :block/left with :block/order

Finally no need to worry about parent-left conflicts and broken chain.
With :block/order, we only need to re-compute new orders for siblings
with same order (it can happens if there're bugs in our code, or
updates from rtc), but it doesn't break UI.

Another huge potential benefit after discussing with Zhiyuan is:
Ee might be able to simplify both RTC and undo/redo, currently, we
need to handle each new op for both of them, with recently
refactorings like properties being db attributes, :block/order
is a string instead of a ref, we can handle most property value
conflicts using last-write-wins, and others (e.g. :block/parent,
property with :default type) specifically.

I haven't fixed the issues of using :block/left in RTC and undo/redo,
because we might change both soon.
Tienson Qin 1 éve
szülő
commit
1ec4796eff
65 módosított fájl, 504 hozzáadás és 1413 törlés
  1. 1 0
      .gitignore
  2. 4 4
      deps/common/src/logseq/common/fractional_index.cljs
  3. 50 0
      deps/common/test/logseq/common/fractional_index_test.cljs
  4. 47 55
      deps/db/src/logseq/db.cljs
  5. 1 2
      deps/db/src/logseq/db/frontend/malli_schema.cljs
  6. 9 8
      deps/db/src/logseq/db/frontend/order.cljs
  7. 1 3
      deps/db/src/logseq/db/frontend/schema.cljs
  8. 6 11
      deps/db/src/logseq/db/sqlite/common_db.cljs
  9. 13 17
      deps/graph-parser/src/logseq/graph_parser/block.cljs
  10. 4 5
      deps/graph-parser/src/logseq/graph_parser/exporter.cljs
  11. 2 1
      deps/graph-parser/src/logseq/graph_parser/extract.cljc
  12. 3 2
      deps/graph-parser/test/logseq/graph_parser/cli_test.cljs
  13. 2 7
      deps/graph-parser/test/logseq/graph_parser/extract_test.cljs
  14. 126 434
      deps/outliner/src/logseq/outliner/core.cljs
  15. 6 21
      deps/outliner/src/logseq/outliner/tree.cljs
  16. 3 3
      docs/dev-practices.md
  17. 8 9
      scripts/src/logseq/tasks/db_graph/create_graph.cljs
  18. 1 1
      scripts/src/logseq/tasks/dev.clj
  19. 0 59
      src/dev-cljs/shadow/build_large_graph.cljs
  20. 1 1
      src/main/frontend/common_keywords.cljs
  21. 2 2
      src/main/frontend/components/block.cljs
  22. 6 5
      src/main/frontend/components/export.cljs
  23. 2 2
      src/main/frontend/components/page.cljs
  24. 2 2
      src/main/frontend/components/property/closed_value.cljs
  25. 4 6
      src/main/frontend/components/property/value.cljs
  26. 0 12
      src/main/frontend/components/query_table.cljs
  27. 1 1
      src/main/frontend/db.cljs
  28. 1 1
      src/main/frontend/db/async.cljs
  29. 1 21
      src/main/frontend/db/debug.cljs
  30. 11 17
      src/main/frontend/db/model.cljs
  31. 4 4
      src/main/frontend/db_worker.cljs
  32. 9 7
      src/main/frontend/handler/block.cljs
  33. 1 2
      src/main/frontend/handler/db_based/editor.cljs
  34. 3 7
      src/main/frontend/handler/db_based/property.cljs
  35. 10 12
      src/main/frontend/handler/dnd.cljs
  36. 62 84
      src/main/frontend/handler/editor.cljs
  37. 2 1
      src/main/frontend/handler/export/html.cljs
  38. 2 1
      src/main/frontend/handler/export/text.cljs
  39. 5 4
      src/main/frontend/handler/file_based/editor.cljs
  40. 1 2
      src/main/frontend/handler/file_based/page_property.cljs
  41. 1 1
      src/main/frontend/handler/import.cljs
  42. 2 5
      src/main/frontend/handler/page.cljs
  43. 1 1
      src/main/frontend/handler/paste.cljs
  44. 0 2
      src/main/frontend/modules/outliner/tree.cljs
  45. 1 1
      src/main/frontend/search.cljs
  46. 2 195
      src/main/frontend/worker/db/fix.cljs
  47. 2 2
      src/main/frontend/worker/file.cljs
  48. 4 3
      src/main/frontend/worker/file/core.cljs
  49. 4 3
      src/main/frontend/worker/handler/page.cljs
  50. 3 10
      src/main/frontend/worker/handler/page/db_based/rename.cljs
  51. 6 14
      src/main/frontend/worker/handler/page/file_based/rename.cljs
  52. 12 20
      src/main/frontend/worker/pipeline.cljs
  53. 1 1
      src/main/frontend/worker/react.cljs
  54. 2 3
      src/main/frontend/worker/rtc/core.cljs
  55. 1 1
      src/main/frontend/worker/rtc/db_listener.cljs
  56. 9 10
      src/main/logseq/api.cljs
  57. 0 9
      src/test/frontend/core_test.cljs
  58. 0 212
      src/test/frontend/db/fix_test.cljs
  59. 4 3
      src/test/frontend/handler/editor_test.cljs
  60. 21 60
      src/test/frontend/modules/outliner/core_test.cljs
  61. 3 5
      src/test/frontend/test/generators.cljs
  62. 4 3
      src/test/frontend/test/helper.cljs
  63. 2 9
      src/test/frontend/worker/handler/page/rename_test.cljs
  64. 1 1
      src/test/frontend/worker/undo_redo_test.cljs
  65. 1 3
      src/test/logseq/api_test.cljs

+ 1 - 0
.gitignore

@@ -63,3 +63,4 @@ deps/shui/.lsp
 deps/shui/.lsp-cache
 deps/shui/.clj-kondo
 deps/shui/shui-graph/logseq/bak
+tx-log*

+ 4 - 4
deps/common/src/logseq/common/fractional_index.cljs

@@ -98,13 +98,13 @@
       (throw (js/Error. (str a " >= " b " or trailing zero"))))
     (let [n (when b
               (first (keep-indexed (fn [i _c] (when-not (= (nth a i zero) (nth b i)) i)) b)))]
-      (if (> n 0)
+      (if (and n (> n 0))
         (str (subs b 0 n) (midpoint (subs a n) (subs b n) digits))
-        (let [digit-a (if a (.indexOf digits (first a)) 0)
-              digit-b (if b (.indexOf digits (first b)) (count digits))]
+        (let [digit-a (if (seq a) (.indexOf digits (first a)) 0)
+              digit-b (if (seq b) (.indexOf digits (first b)) (count digits))]
           (if (> (- digit-b digit-a) 1)
             (str (nth digits (Math/round (* 0.5 (+ digit-a digit-b)))))
-            (if (and b (> (count b) 1))
+            (if (and (seq b) (> (count b) 1))
               (subs b 0 1)
               (str (nth digits digit-a) (midpoint (subs a 1) nil digits)))))))))
 

+ 50 - 0
deps/common/test/logseq/common/fractional_index_test.cljs

@@ -0,0 +1,50 @@
+(ns logseq.common.fractional-index-test
+    (:require [clojure.test :refer [deftest are]]
+              [logseq.common.fractional-index :as index]))
+
+(deftest generate-n-keys-between-test
+  (are [x y]
+       (= (index/generate-n-keys-between (first x) (second x) 20) y)
+    ["ZxV" "Zy7"]
+    ["ZxX"
+     "ZxZ"
+     "Zxd"
+     "Zxf"
+     "Zxh"
+     "Zxl"
+     "Zxn"
+     "Zxp"
+     "Zxt"
+     "Zxx"
+     "Zy"
+     "Zy0V"
+     "Zy1"
+     "Zy2"
+     "Zy3"
+     "Zy4"
+     "Zy4V"
+     "Zy5"
+     "Zy6"
+     "Zy6V"]
+
+    ["Zy7" "axV"]
+    ["ZyB"
+     "ZyE"
+     "ZyL"
+     "ZyP"
+     "ZyS"
+     "ZyZ"
+     "Zyd"
+     "Zyg"
+     "Zyn"
+     "Zyu"
+     "Zz"
+     "Zz8"
+     "ZzG"
+     "ZzV"
+     "Zzl"
+     "a0"
+     "a0G"
+     "a0V"
+     "a1"
+     "a2"]))

+ 47 - 55
deps/db/src/logseq/db.cljs

@@ -6,19 +6,19 @@
             [logseq.common.util :as common-util]
             [logseq.common.config :as common-config]
             [logseq.db.frontend.content :as db-content]
-            [clojure.set :as set]
             [logseq.db.frontend.rules :as rules]
             [logseq.db.frontend.entity-plus :as entity-plus]
             [logseq.db.sqlite.util :as sqlite-util]
             [logseq.db.sqlite.common-db :as sqlite-common-db]
-            [logseq.db.frontend.delete-blocks :as delete-blocks]))
+            [logseq.db.frontend.delete-blocks :as delete-blocks]
+            [datascript.impl.entity :as de]))
 
 ;; Use it as an input argument for datalog queries
 (def block-attrs
   '[:db/id
     :block/uuid
     :block/parent
-    :block/left
+    :block/order
     :block/collapsed?
     :block/collapsed-properties
     :block/format
@@ -78,23 +78,9 @@
        (let [f (or @*transact-fn d/transact!)]
          (f repo-or-conn tx-data tx-meta))))))
 
-(defn sort-by-left
-  [blocks parent]
-  (let [left->blocks (->> (reduce (fn [acc b] (assoc! acc (:db/id (:block/left b)) b))
-                                  (transient {}) blocks)
-                          (persistent!))]
-    (loop [block parent
-           result (transient [])]
-      (if-let [next (get left->blocks (:db/id block))]
-        (recur next (conj! result next))
-        (vec (persistent! result))))))
-
-(defn try-sort-by-left
-  [blocks parent]
-  (let [result' (sort-by-left blocks parent)]
-    (if (= (count result') (count blocks))
-      result'
-      blocks)))
+(defn sort-by-order
+  [blocks]
+  (sort-by :block/order blocks))
 
 ;; TODO: use the tree directly
 (defn flatten-tree
@@ -142,24 +128,29 @@
   [db page-id]
   (count (d/datoms db :avet :block/page page-id)))
 
-(defn get-by-parent-&-left
-  [db parent-id left-id]
-  (when (and parent-id left-id)
-    (let [lefts (:block/_left (d/entity db left-id))]
-      (some (fn [node] (when (and (= parent-id (:db/id (:block/parent node)))
-                                  (not= parent-id (:db/id node)))
-                         node)) lefts))))
-
 (defn get-right-sibling
-  [db db-id]
-  (when-let [block (d/entity db db-id)]
-    (get-by-parent-&-left db
-                          (:db/id (:block/parent block))
-                          db-id)))
+  [block]
+  (assert (or (de/entity? block) (nil? block)))
+  (when-let [parent (:block/parent block)]
+    (let [children (sort-by-order (:block/_parent parent))
+          right (some (fn [child] (when (> (compare (:block/order child) (:block/order block)) 0) child)) children)]
+      (when (not= (:db/id right) (:db/id block))
+        right))))
+
+(defn get-left-sibling
+  [block]
+  (assert (or (de/entity? block) (nil? block)))
+  (when-let [parent (:block/parent block)]
+    (let [children (reverse (sort-by-order (:block/_parent parent)))
+          left (some (fn [child] (when (< (compare (:block/order child) (:block/order block)) 0) child)) children)]
+      (when (not= (:db/id left) (:db/id block))
+        left))))
+
+(defn get-down
+  [block]
+  (assert (or (de/entity? block) (nil? block)))
+  (first (sort-by-order (:block/_parent block))))
 
-(defn get-by-id
-  [db id]
-  (d/pull db '[*] id))
 
 (defn hidden-page?
   [page]
@@ -269,16 +260,20 @@
      (when (if not-collapsed?
              (not (collapsed-and-has-children? db block))
              true)
-       (let [children (:block/_parent block)
-             all-left (set (concat (map (comp :db/id :block/left) children) [db-id]))
-             all-ids (set (map :db/id children))]
-         (first (set/difference all-ids all-left)))))))
+       (let [children (sort-by :block/order (:block/_parent block))]
+         (:db/id (last children)))))))
 
 (defn get-block-immediate-children
   "Doesn't include nested children."
-  [db block-uuid]
-  (when-let [parent (d/entity db [:block/uuid block-uuid])]
-    (sort-by-left (:block/_parent parent) parent)))
+  [db block-entity-or-eid]
+  (when-let [parent (cond
+                      (number? block-entity-or-eid)
+                      (d/entity db block-entity-or-eid)
+                      (uuid? block-entity-or-eid)
+                      (d/entity db [:block/uuid block-entity-or-eid])
+                      :else
+                      block-entity-or-eid)]
+    (sort-by-order (:block/_parent parent))))
 
 (defn get-block-parents
   [db block-id {:keys [depth] :or {depth 100}}]
@@ -294,16 +289,20 @@
 (def get-block-children-ids sqlite-common-db/get-block-children-ids)
 (def get-block-children sqlite-common-db/get-block-children)
 
+(defn get-first-child
+  [db id]
+  (first (sort-by-order (:block/_parent (d/entity db id)))))
+
 (defn- get-sorted-page-block-ids
   [db page-id]
   (let [root (d/entity db page-id)]
     (loop [result []
-           children (sort-by-left (:block/_parent root) root)]
+           children (sort-by-order (:block/_parent root))]
       (if (seq children)
         (let [child (first children)]
           (recur (conj result (:db/id child))
                  (concat
-                  (sort-by-left (:block/_parent child) child)
+                  (sort-by-order (:block/_parent child))
                   (rest children))))
         result))))
 
@@ -317,13 +316,6 @@
         blocks-map (zipmap (map :db/id blocks) blocks)]
     (keep blocks-map sorted-ids)))
 
-(defn get-prev-sibling
-  [db id]
-  (when-let [e (d/entity db id)]
-    (let [left (:block/left e)]
-      (when (not= (:db/id left) (:db/id (:block/parent e)))
-        left))))
-
 (defn last-child-block?
   "The child block could be collapsed."
   [db parent-id child-id]
@@ -332,7 +324,7 @@
       (= parent-id child-id)
       true
 
-      (get-right-sibling db child-id)
+      (get-right-sibling child)
       false
 
       :else
@@ -344,8 +336,8 @@
                  (and (= (:block/page block-1) (:block/page block-2))
                       (or
                        ;; sibling or child
-                       (= (:db/id (:block/left block-2)) (:db/id block-1))
-                       (when-let [prev-sibling (get-prev-sibling db (:db/id block-2))]
+                       (= (:db/id (get-left-sibling block-2)) (:db/id block-1))
+                       (when-let [prev-sibling (get-left-sibling (d/entity db (:db/id block-2)))]
                          (last-child-block? db (:db/id prev-sibling) (:db/id block-1))))))]
     (or (aux-fn block-1 block-2) (aux-fn block-2 block-1))))
 

+ 1 - 2
deps/db/src/logseq/db/frontend/malli_schema.cljs

@@ -311,7 +311,6 @@
 (def block-attrs
   "Common attributes for normal blocks"
   [[:block/content :string]
-   [:block/left :int]
    [:block/parent :int]
    [:block/order {:optional true} :string]
    ;; refs
@@ -351,7 +350,7 @@
       [:map
        [:value [:or :string :double]]
        [:description {:optional true} :string]]]]
-    (remove #(#{:block/content :block/left} (first %)) block-attrs)
+    (remove #(#{:block/content} (first %)) block-attrs)
     page-or-block-attrs)))
 
 (def normal-block

+ 9 - 8
deps/db/src/logseq/db/frontend/order.cljs

@@ -12,12 +12,14 @@
     (reset! *max-key key)))
 
 (defn gen-key
+  ([]
+   (gen-key @*max-key nil))
   ([end]
    (gen-key @*max-key end))
   ([start end]
    (let [k (index/generate-key-between start end)]
-    (reset-max-key! k)
-    k)))
+     (reset-max-key! k)
+     k)))
 
 (defn get-max-order
   [db]
@@ -31,9 +33,8 @@
   [db current-key]
   (:v (second (d/seek-datoms db :avet :block/order current-key))))
 
-(comment
-  (defn gen-n-keys
-    [n start end]
-    (let [ks (index/generate-n-keys-between start end n)]
-      (reset! *max-key (last ks))
-      ks)))
+(defn gen-n-keys
+  [n start end]
+  (let [ks (index/generate-n-keys-between start end n)]
+    (reset! *max-key (last ks))
+    ks))

+ 1 - 3
deps/db/src/logseq/db/frontend/schema.cljs

@@ -20,8 +20,7 @@
    :block/uuid {:db/unique :db.unique/identity}
    :block/parent {:db/valueType :db.type/ref
                   :db/index true}
-   :block/left   {:db/valueType :db.type/ref
-                  :db/index true}
+   :block/order {:db/index true}
    :block/collapsed? {}
    :block/collapsed-properties {:db/valueType :db.type/ref
                                 :db/cardinality :db.cardinality/many}
@@ -123,7 +122,6 @@
            :block/namespace :block/properties-text-values :block/pre-block? :recent/pages :file/handle :block/file
            :block/properties :block/properties-order :block/repeated? :block/deadline :block/scheduled :block/priority :block/marker)
    {:block/name {:db/index true}        ; remove db/unique for :block/name
-    :block/order {:db/index true}
     ;; class properties
     :class/parent {:db/valueType :db.type/ref
                    :db/index true}

+ 6 - 11
deps/db/src/logseq/db/sqlite/common_db.cljs

@@ -54,22 +54,17 @@
   [db block]
   (update block :block/refs (fn [refs] (map (fn [ref] (d/pull db '[*] (:db/id ref))) refs))))
 
-(defn with-parent-and-left
+(defn with-parent
   [db block]
   (cond
-    (:block/name block)
-    block
     (:block/page block)
-    (let [left (when-let [e (d/entity db (:db/id (:block/left block)))]
-                 (select-keys e [:db/id :block/uuid]))
-          parent (when-let [e (d/entity db (:db/id (:block/parent block)))]
+    (let [parent (when-let [e (d/entity db (:db/id (:block/parent block)))]
                    (select-keys e [:db/id :block/uuid]))]
       (->>
-       (assoc block
-              :block/left left
-              :block/parent parent)
+       (assoc block :block/parent parent)
        (common-util/remove-nils-non-nested)
        (with-block-refs db)))
+
     :else
     block))
 
@@ -154,7 +149,7 @@
                        (let [long-page? (and (> (count children) 500) (not (contains? (:block/type block) "whiteboard")))]
                          (if long-page?
                            (map (fn [e]
-                                  (select-keys e [:db/id :block/uuid :block/page :block/left :block/parent :block/collapsed?]))
+                                  (select-keys e [:db/id :block/uuid :block/page :block/order :block/parent :block/collapsed?]))
                                 children)
                            (->> (d/pull-many db '[*] (map :db/id children))
                                 (map #(with-block-refs db %))
@@ -169,7 +164,7 @@
     (when block
       (if (:block/page block) ; not a page
         (let [block' (->> (d/pull db '[*] (:db/id block))
-                          (with-parent-and-left db)
+                          (with-parent db)
                           (with-block-refs db)
                           mark-block-fully-loaded)]
           (cond->

+ 13 - 17
deps/graph-parser/src/logseq/graph_parser/block.cljs

@@ -14,7 +14,8 @@
             [logseq.common.util.block-ref :as block-ref]
             [logseq.common.util.page-ref :as page-ref]
             [datascript.impl.entity :as de]
-            [logseq.db :as ldb]))
+            [logseq.db :as ldb]
+            [logseq.db.frontend.order :as db-order]))
 
 (defn heading-block?
   [block]
@@ -675,7 +676,7 @@
         result (with-pre-block-if-exists blocks body pre-block-properties encoded-content options)]
     (map #(dissoc % :block/meta) result)))
 
-(defn with-parent-and-left
+(defn with-parent-and-order
   [page-id blocks]
   (let [[blocks other-blocks] (split-with
                                (fn [b]
@@ -697,7 +698,6 @@
                            (= level-spaces parent-spaces)        ; sibling
                            (let [block (assoc block
                                               :block/parent parent
-                                              :block/left [:block/uuid uuid]
                                               :block/level level)
                                  parents' (conj (vec (butlast parents)) block)
                                  result' (conj result block)]
@@ -706,9 +706,8 @@
                            (> level-spaces parent-spaces)         ; child
                            (let [parent (if uuid [:block/uuid uuid] (:page/id last-parent))
                                  block (cond->
-                                         (assoc block
-                                                :block/parent parent
-                                                :block/left parent)
+                                        (assoc block
+                                               :block/parent parent)
                                          ;; fix block levels with wrong order
                                          ;; For example:
                                          ;;   - a
@@ -724,10 +723,8 @@
                            (cond
                              (some #(= (:block/level-spaces %) (:block/level-spaces block)) parents) ; outdent
                              (let [parents' (vec (filter (fn [p] (<= (:block/level-spaces p) level-spaces)) parents))
-                                   left (last parents')
                                    blocks (cons (assoc (first blocks)
-                                                       :block/level (dec level)
-                                                       :block/left [:block/uuid (:block/uuid left)])
+                                                       :block/level (dec level))
                                                 (rest blocks))]
                                [blocks parents' result])
 
@@ -737,18 +734,17 @@
                                    parent-id (if-let [block-id (:block/uuid (last f))]
                                                [:block/uuid block-id]
                                                page-id)
-                                   block (cond->
-                                           (assoc block
-                                                  :block/parent parent-id
-                                                  :block/left [:block/uuid (:block/uuid left)]
-                                                  :block/level (:block/level left)
-                                                  :block/level-spaces (:block/level-spaces left)))
+                                   block (assoc block
+                                                :block/parent parent-id
+                                                :block/level (:block/level left)
+                                                :block/level-spaces (:block/level-spaces left))
 
                                    parents' (->> (concat f [block]) vec)
                                    result' (conj result block)]
                                [others parents' result'])))]
-                     (recur blocks parents result))))]
-    (concat result other-blocks)))
+                     (recur blocks parents result))))
+        result' (map (fn [block] (assoc block :block/order (db-order/gen-key))) result)]
+    (concat result' other-blocks)))
 
 (defn extract-plain
   "Extract plain elements including page refs"

+ 4 - 5
deps/graph-parser/src/logseq/graph_parser/exporter.cljs

@@ -19,7 +19,8 @@
             [logseq.db.frontend.rules :as rules]
             [logseq.db.frontend.class :as db-class]
             [logseq.common.util.page-ref :as page-ref]
-            [promesa.core :as p]))
+            [promesa.core :as p]
+            [logseq.db.frontend.order :as db-order]))
 
 (defn- get-pid
   "Get a property's id (name or uuid) given its name. For db graphs"
@@ -544,10 +545,8 @@
     block))
 
 (defn- fix-pre-block-references
-  [{:block/keys [left parent page] :as block} pre-blocks]
+  [{:block/keys [parent page] :as block} pre-blocks]
   (cond-> block
-    (and (vector? left) (contains? pre-blocks (second left)))
-    (assoc :block/left page)
     ;; Children blocks of pre-blocks get lifted up to the next level which can cause conflicts
     ;; TODO: Detect sibling blocks to avoid parent-left conflicts
     (and (vector? parent) (contains? pre-blocks (second parent)))
@@ -922,7 +921,7 @@
                             (merge (ldb/build-favorite-tx favorite-id)
                                    {:block/uuid (d/squuid)
                                     :db/id (or (some-> (:db/id (last acc)) dec) -1)
-                                    :block/left {:db/id (or (:db/id (last acc)) page-id)}
+                                    :block/order (db-order/gen-key nil)
                                     :block/parent page-id
                                     :block/page page-id}))))
                    []

+ 2 - 1
deps/graph-parser/src/logseq/graph_parser/extract.cljc

@@ -229,7 +229,7 @@
                       (attach-block-ids-if-match override-uuids)
                       (mapv #(gp-block/fix-block-id-if-duplicated! db page-name extracted-block-ids %))
                       ;; FIXME: use page uuid
-                      (gp-block/with-parent-and-left {:block/name page-name})
+                      (gp-block/with-parent-and-order {:block/name page-name})
                       (vec))
           ref-pages (atom #{})
           blocks (map (fn [block]
@@ -318,6 +318,7 @@
                 (fn [block]
                   (-> block
                       (common-util/dissoc-in [:block/parent :block/name])
+                      ;; :block/left here for backward compatiblity
                       (common-util/dissoc-in [:block/left :block/name])))
                 blocks)
         serialized-page (first pages)

+ 3 - 2
deps/graph-parser/test/logseq/graph_parser/cli_test.cljs

@@ -9,7 +9,8 @@
             [clojure.set :as set]
             ["fs" :as fs]
             ["process" :as process]
-            ["path" :as path]))
+            ["path" :as path]
+            [logseq.db.frontend.order :as db-order]))
 
 (use-fixtures
   :each
@@ -117,7 +118,7 @@
                                :block/uuid (random-uuid)
                                :block/format :markdown
                                :block/page {:db/id page-id}
-                               :block/left {:db/id page-id}
+                               :block/order (db-order/gen-key nil)
                                :block/parent {:db/id page-id}
                                :block/created-at created-at
                                :block/updated-at created-at})

+ 2 - 7
deps/graph-parser/test/logseq/graph_parser/extract_test.cljs

@@ -51,13 +51,8 @@
 
 (defn- extract-block-content
   [text]
-  (let [{:keys [blocks]} (extract "a.md" text)
-        lefts (map (juxt :block/parent :block/left) blocks)]
-    (if (not= (count lefts) (count (distinct lefts)))
-      (do
-        (pprint/pprint (map (fn [x] (select-keys x [:block/uuid :block/level :block/content :block/left])) blocks))
-        (throw (js/Error. ":block/parent && :block/left conflicts")))
-      (mapv :block/content blocks))))
+  (let [{:keys [blocks]} (extract "a.md" text)]
+    (mapv :block/content blocks)))
 
 (defn- extract-title [file text]
   (-> (extract file text) :pages first :block/properties :title))

+ 126 - 434
deps/outliner/src/logseq/outliner/core.cljs

@@ -3,11 +3,10 @@
   (:require [clojure.set :as set]
             [clojure.string :as string]
             [datascript.core :as d]
-            [datascript.impl.entity :as de]
+            [datascript.impl.entity :as de :refer [Entity]]
             [logseq.db.frontend.schema :as db-schema]
             [logseq.outliner.datascript :as ds]
             [logseq.outliner.tree :as otree]
-            [logseq.outliner.util :as outliner-u]
             [logseq.common.util :as common-util]
             [malli.core :as m]
             [malli.util :as mu]
@@ -17,12 +16,11 @@
             [logseq.graph-parser.db :as gp-db]
             [logseq.db.frontend.property.util :as db-property-util]
             [logseq.db.sqlite.util :as sqlite-util]
-            [cljs.pprint :as pprint]
             [logseq.common.marker :as common-marker]
             [logseq.db.frontend.content :as db-content]
-            [logseq.db.frontend.property :as db-property]
             [logseq.db.sqlite.create-graph :as sqlite-create-graph]
-            [frontend.worker.batch-tx :include-macros true :as batch-tx]))
+            [frontend.worker.batch-tx :include-macros true :as batch-tx]
+            [logseq.db.frontend.order :as db-order]))
 
 (def ^:private block-map
   (mu/optional-keys
@@ -30,47 +28,13 @@
     [:db/id :int]
     ;; FIXME: tests use ints when they should use uuids
     [:block/uuid [:or :uuid :int]]
-    [:block/left :map]
+    [:block/order :string]
     [:block/parent :map]
     [:block/page :map]]))
 
 (def ^:private block-map-or-entity
   [:or [:fn de/entity?] block-map])
 
-(defrecord ^:api Block [data])
-
-(defn ^:api block
-  [db m]
-  (assert (or (map? m) (de/entity? m)) (common-util/format "block data must be map or entity, got: %s %s" (type m) m))
-  (let [e (if (or (de/entity? m)
-                  (and (:block/uuid m) (:db/id m)))
-            m
-            (let [eid (if (:block/uuid m)
-                        [:block/uuid (:block/uuid m)]
-                        (:db/id m))]
-              (assert eid "eid doesn't exist")
-              (let [entity (d/entity db eid)]
-                (assoc m :db/id (:db/id entity)
-                       :block/uuid (:block/uuid entity)))))]
-    (->Block e)))
-
-(defn ^:api get-data
-  [block]
-  (:data block))
-
-(defn- get-block-by-id
-  [db id]
-  (let [r (ldb/get-by-id db (outliner-u/->block-lookup-ref id))]
-    (when r (->Block r))))
-
-(defn- get-by-parent-&-left
-  [db parent-uuid left-uuid]
-  (let [parent-id (:db/id (d/entity db [:block/uuid parent-uuid]))
-        left-id (:db/id (d/entity db [:block/uuid left-uuid]))
-        entity (ldb/get-by-parent-&-left db parent-id left-id)]
-    (when entity
-      (block db entity))))
-
 (defn ^:api block-with-timestamps
   [block]
   (let [updated-at (common-util/time-ms)
@@ -86,10 +50,13 @@
     (assoc block :block/updated-at updated-at)))
 
 (defn- filter-top-level-blocks
-  [blocks]
+  [db blocks]
   (let [parent-ids (set/intersection (set (map (comp :db/id :block/parent) blocks))
                                      (set (map :db/id blocks)))]
-    (remove (fn [e] (contains? parent-ids (:db/id (:block/parent e)))) blocks)))
+    (->> blocks
+         (remove (fn [e] (contains? parent-ids (:db/id (:block/parent e)))))
+         (map (fn [block]
+                (if (de/entity? block) block (d/entity db (:db/id block))))))))
 
 (defn- remove-orphaned-page-refs!
   [db {db-id :db/id} txs-state *old-refs new-refs {:keys [db-graph?]}]
@@ -305,12 +272,9 @@
                                    tags)))
       m)))
 
-;; -get-id, -get-parent-id, -get-left-id return block-id
-;; the :block/parent, :block/left should be datascript lookup ref
-
 ;; TODO: don't parse marker and deprecate typing marker to set status
 (defn- db-marker-handle
-  [conn block-entity txs-state m]
+  [conn m]
   (or
    (let [marker (:block/marker m)
          property (d/entity @conn :logseq.task/status)
@@ -336,55 +300,13 @@
        (dissoc :block/marker :block/priority)))
    m))
 
-(extend-type Block
+(extend-type Entity
   otree/INode
-  (-get-id [this conn]
-    (or
-     (when-let [block-id (get-in this [:data :block/uuid])]
-       block-id)
-     (when-let [data (:data this)]
-       (let [uuid (:block/uuid data)]
-         (if uuid
-           uuid
-           (let [new-id (ldb/new-block-id)]
-             (ldb/transact! conn [{:db/id (:db/id data)
-                                   :block/uuid new-id}])
-             new-id))))))
-
-  (-get-parent-id [this conn]
-    (when-let [id (:db/id (get-in this [:data :block/parent]))]
-      (:block/uuid (d/entity @conn id))))
-
-  (-get-left-id [this conn]
-    (when-let [id (:db/id (get-in this [:data :block/left]))]
-      (:block/uuid (d/entity @conn id))))
-
-  (-set-left-id [this left-id _conn]
-    (outliner-u/check-block-id left-id)
-    (update this :data assoc :block/left [:block/uuid left-id]))
-
-  (-get-parent [this conn]
-    (when-let [parent-id (otree/-get-parent-id this conn)]
-      (get-block-by-id @conn parent-id)))
-
-  (-get-left [this conn]
-    (when-let [left-id (otree/-get-left-id this conn)]
-      (get-block-by-id @conn left-id)))
-
-  (-get-right [this conn]
-    (let [left-id (otree/-get-id this conn)
-          parent-id (otree/-get-parent-id this conn)]
-      (get-by-parent-&-left @conn parent-id left-id)))
-
-  (-get-down [this conn]
-    (let [parent-id (otree/-get-id this conn)]
-      (get-by-parent-&-left @conn parent-id parent-id)))
-
   (-save [this txs-state conn repo date-formatter {:keys [retract-attributes? retract-attributes]
                                                    :or {retract-attributes? true}}]
     (assert (ds/outliner-txs-state? txs-state)
             "db should be satisfied outliner-tx-state?")
-    (let [data (:data this)
+    (let [data this
           db-based? (sqlite-util/db-based-graph? repo)
           data' (cond->
                  (if (de/entity? data)
@@ -399,13 +321,13 @@
                 block-with-updated-at
                 fix-tag-ids)
           db @conn
-          db-id (:db/id (:data this))
-          block-uuid (:block/uuid (:data this))
+          db-id (:db/id this)
+          block-uuid (:block/uuid this)
           eid (or db-id (when block-uuid [:block/uuid block-uuid]))
           block-entity (d/entity db eid)
           m (cond->> m
               db-based?
-              (db-marker-handle conn block-entity txs-state))
+              (db-marker-handle conn))
           m (if db-based?
               (update m :block/tags (fn [tags]
                                       (concat (keep :db/id (:block/tags block-entity))
@@ -456,7 +378,7 @@
   (-del [this txs-state conn]
     (assert (ds/outliner-txs-state? txs-state)
             "db should be satisfied outliner-tx-state?")
-    (let [block-id (otree/-get-id this conn)
+    (let [block-id (:block/uuid this)
           ids (->>
                (let [children (ldb/get-block-children @conn block-id)
                      children-ids (map :block/uuid children)]
@@ -472,17 +394,7 @@
                          [:db/retract id :block/alias]
                          [:db/retract id :block/tags]])))]
       (swap! txs-state concat txs page-tx)
-      block-id))
-
-  (-get-children [this conn]
-    (let [parent-id (otree/-get-id this conn)
-          children (ldb/get-block-immediate-children @conn parent-id)]
-      (map #(block @conn %) children))))
-
-(defn ^:api get-right-sibling
-  [db db-id]
-  (when db-id
-    (ldb/get-right-sibling db db-id)))
+      block-id)))
 
 (defn- assoc-level-aux
   [tree-vec children-key init-level]
@@ -510,23 +422,10 @@
                                       :block/uuid (:block/uuid target-block))]
                               [[:db/retractEntity (:db/id target-block)] ; retract target-block first
                                (assoc block
-                                      :db/id db-id
-                                      :block/left (:db/id (:block/left target-block)))]))
+                                      :db/id db-id)]))
                           [(assoc block :db/id (dec (- idx)))]))) blocks)
        (apply concat)))
 
-(defn- find-outdented-block-prev-hop
-  [outdented-block blocks]
-  (let [blocks (reverse
-                (take-while #(not= (:db/id outdented-block)
-                                   (:db/id %)) blocks))
-        blocks (drop-while #(= (:db/id (:block/parent outdented-block)) (:db/id (:block/parent %))) blocks)]
-    (when (seq blocks)
-      (loop [blocks blocks
-             matched (first blocks)]
-        (if (= (:block/parent (first blocks)) (:block/parent matched))
-          (recur (rest blocks) (first blocks))
-          matched)))))
 
 (defn- get-id
   [x]
@@ -541,7 +440,7 @@
     x))
 
 (defn- compute-block-parent
-  [block parent target-block prev-hop top-level? sibling? get-new-id outliner-op replace-empty-target? idx]
+  [block parent target-block top-level? sibling? get-new-id outliner-op replace-empty-target? idx]
   (cond
     ;; replace existing block
     (and (contains? #{:paste :insert-blocks} outliner-op)
@@ -550,9 +449,6 @@
          (zero? idx))
     (get-id (:block/parent target-block))
 
-    prev-hop
-    (:db/id (:block/parent prev-hop))
-
     top-level?
     (if sibling?
       (:db/id (:block/parent target-block))
@@ -561,44 +457,6 @@
     :else
     (get-new-id block parent)))
 
-(defn- compute-block-left
-  [blocks block left target-block prev-hop idx replace-empty-target? left-exists-in-blocks? get-new-id]
-  (cond
-    (zero? idx)
-    (if replace-empty-target?
-      (:db/id (:block/left target-block))
-      (:db/id target-block))
-
-    (and prev-hop (not left-exists-in-blocks?))
-    (:db/id (:block/left prev-hop))
-
-    :else
-    (or (get-new-id block left)
-        (get-new-id block (nth blocks (dec idx))))))
-
-(defn- get-left-nodes
-  [conn node limit]
-  (let [parent (otree/-get-parent node conn)]
-    (loop [node node
-           limit limit
-           result []]
-      (if (zero? limit)
-        result
-        (if-let [left (otree/-get-left node conn)]
-          (if-not (= left parent)
-            (recur left (dec limit) (conj result (otree/-get-id left conn)))
-            result)
-          result)))))
-
-(defn- page-first-child?
-  [block]
-  (= (:block/left block)
-     (:block/page block)))
-
-(defn- page-block?
-  [block]
-  (some? (:block/name block)))
-
 ;;; ### public utils
 
 (defn tree-vec-flatten
@@ -618,27 +476,31 @@
 
 (defn ^:api save-block
   "Save the `block`."
-  [repo conn date-formatter block' opts]
-  {:pre [(map? block')]}
-  (let [txs-state (atom [])]
-    (otree/-save (block @conn block') txs-state conn repo date-formatter opts)
+  [repo conn date-formatter block opts]
+  {:pre [(map? block)]}
+  (let [txs-state (atom [])
+        block' (if (de/entity? block)
+                 block
+                 (do
+                   (assert (or (:db/id block) (:block/uuid block)) "save-block db/id not exists")
+                   (when-let [eid (or (:db/id block) (when-let [id (:block/uuid block)] [:block/uuid id]))]
+                     (merge (d/entity @conn eid) block))))]
+    (otree/-save block' txs-state conn repo date-formatter opts)
     {:tx-data @txs-state}))
 
 (defn- get-right-siblings
   "Get `node`'s right siblings."
-  [conn node]
-  {:pre [(otree/satisfied-inode? node)]}
-  (when-let [parent (otree/-get-parent node conn)]
-    (let [children (otree/-get-children parent conn)]
-      (->> (split-with #(not= (otree/-get-id node conn) (otree/-get-id % conn)) children)
+  [node]
+  (when-let [parent (:block/parent node)]
+    (let [children (ldb/sort-by-order (:block/_parent parent))]
+      (->> (split-with #(not= (:block/uuid node) (:block/uuid %)) children)
            last
            rest))))
 
 (defn- blocks-with-ordered-list-props
   [repo conn blocks target-block sibling?]
   (let [db @conn
-        tb (when target-block (block db target-block))
-        target-block (if sibling? target-block (when tb (:block (otree/-get-down tb conn))))
+        target-block (if sibling? target-block (when target-block (ldb/get-down target-block)))
         list-type-fn (fn [block] (db-property-util/get-block-property-value repo db block :logseq.property/order-list-type))
         db-based? (sqlite-util/db-based-graph? repo)]
     (if-let [list-type (and target-block (list-type-fn target-block))]
@@ -660,36 +522,9 @@
 
 ;;; ### insert-blocks, delete-blocks, move-blocks
 
-(defn ^:api fix-top-level-blocks
-  "Blocks with :block/level"
-  [blocks]
-  (let [top-level-blocks (filter #(= (:block/level %) 1) blocks)
-        id->block (zipmap (map :db/id top-level-blocks) top-level-blocks)
-        uuid->block (zipmap (map :block/uuid top-level-blocks) top-level-blocks)]
-    (if (every? (fn [block]
-                  (let [left (:block/left block)
-                        id (if (map? left) (:db/id left) (second left))]
-                    (some? (or (get id->block id) (get uuid->block id))))) (rest top-level-blocks))
-      ;; no need to fix
-      blocks
-      (loop [blocks blocks
-             last-top-level-block nil
-             result []]
-        (if-let [block (first blocks)]
-          (if (= 1 (:block/level block))
-            (do
-              (assert (:db/id last-top-level-block) (str "last-top-level-block :block/left not exists: " last-top-level-block))
-              (let [block' (assoc block
-                                  :block/left {:db/id (:db/id last-top-level-block)}
-                                  :block/parent (:block/parent last-top-level-block))]
-                (recur (rest blocks) block (conj result block'))))
-            (recur (rest blocks) last-top-level-block (conj result block)))
-          result)))))
-
 (defn- insert-blocks-aux
-  [blocks target-block {:keys [sibling? replace-empty-target? keep-uuid? move? outliner-op]}]
+  [blocks target-block {:keys [sibling? replace-empty-target? keep-uuid? outliner-op]}]
   (let [block-uuids (map :block/uuid blocks)
-        ids (set (map :db/id blocks))
         uuids (zipmap block-uuids
                       (if keep-uuid?
                         block-uuids
@@ -716,30 +551,30 @@
 
                        :else
                        (throw (js/Error. (str "[insert-blocks] illegal lookup: " lookup ", block: " block)))))
-        indent-outdent? (= outliner-op :indent-outdent-blocks)]
-    (map-indexed (fn [idx {:block/keys [parent left] :as block}]
+        target-order (:block/order target-block)
+        next-sibling-order (:block/order (ldb/get-right-sibling target-block))
+        first-child (ldb/get-down target-block)
+        first-child-order (:block/order first-child)
+        start-order (when sibling? target-order)
+        end-order (if sibling? next-sibling-order first-child-order)
+        orders (db-order/gen-n-keys (count blocks) start-order end-order)]
+    (map-indexed (fn [idx {:block/keys [parent] :as block}]
                    (when-let [uuid (get uuids (:block/uuid block))]
                      (let [top-level? (= (:block/level block) 1)
-                           outdented-block? (and indent-outdent?
-                                                 top-level?
-                                                 (not= (:block/parent block) (:block/parent target-block)))
-                           prev-hop (if outdented-block? (find-outdented-block-prev-hop block blocks) nil)
-                           left-exists-in-blocks? (contains? ids (:db/id (:block/left block)))
-                           parent (compute-block-parent block parent target-block prev-hop top-level? sibling? get-new-id outliner-op replace-empty-target? idx)
-                           left (compute-block-left blocks block left target-block prev-hop idx replace-empty-target? left-exists-in-blocks? get-new-id)
-                           _ (assert (and parent left) (str "Parent or left is nil: " {:parent parent :left left}))
+                           parent (compute-block-parent block parent target-block top-level? sibling? get-new-id outliner-op replace-empty-target? idx)
+
+                           order (nth orders idx)
+                           _ (assert (and parent order) (str "Parent or order is nil: " {:parent parent :order order}))
                            m {:db/id (:db/id block)
                               :block/uuid uuid
                               :block/page target-page
                               :block/parent parent
-                              :block/left left}]
-                       (cond-> (if (de/entity? block)
-                                 (assoc m :block/level (:block/level block))
-                                 (merge block m))
-                           ;; We'll keep the original `:db/id` if it's a move operation,
-                           ;; e.g. internal cut or drag and drop shouldn't change the ids.
-                         (not move?)
-                         (dissoc :db/id)))))
+                              :block/order order}]
+                       (->
+                        (if (de/entity? block)
+                          (assoc m :block/level (:block/level block))
+                          (merge block m))
+                        (dissoc :db/id)))))
                  blocks)))
 
 (defn- get-target-block
@@ -777,7 +612,8 @@
 
                              :else
                              [block sibling?])
-          sibling? (if (ldb/page? block) false sibling?)]
+          sibling? (if (ldb/page? block) false sibling?)
+          block (if (de/entity? block) block (d/entity db (:db/id block)))]
       [block sibling?])))
 
 
@@ -830,24 +666,19 @@
                                   :or {update-timestamps? true}}]
   {:pre [(seq blocks)
          (m/validate block-map-or-entity target-block)]}
-  (let [[target-block' sibling?] (get-target-block @conn blocks target-block opts)
-        _ (assert (some? target-block') (str "Invalid target: " target-block))
-        sibling? (if (page-block? target-block') false sibling?)
-        move? (contains? #{:move-blocks :move-blocks-up-down :indent-outdent-blocks} outliner-op)
-        keep-uuid? (if move? true keep-uuid?)
+  (let [[target-block sibling?] (get-target-block @conn blocks target-block opts)
+        _ (assert (some? target-block) (str "Invalid target: " target-block))
+        sibling? (if (ldb/page? target-block) false sibling?)
         replace-empty-target? (if (and (some? replace-empty-target?)
-                                       (:block/content target-block')
-                                       (string/blank? (:block/content target-block')))
+                                       (:block/content target-block)
+                                       (string/blank? (:block/content target-block)))
                                 replace-empty-target?
                                 (and sibling?
-                                     (:block/content target-block')
-                                     (string/blank? (:block/content target-block'))
-                                     (> (count blocks) 1)
-                                     (not move?)))
+                                     (:block/content target-block)
+                                     (string/blank? (:block/content target-block))
+                                     (> (count blocks) 1)))
         blocks' (let [blocks' (blocks-with-level blocks)]
                   (cond->> (blocks-with-ordered-list-props repo conn blocks' target-block sibling?)
-                    (= outliner-op :paste)
-                    fix-top-level-blocks
                     update-timestamps?
                     (mapv (fn [b] (block-with-timestamps (dissoc b :block/created-at :block/updated-at))))
                     true
@@ -855,64 +686,22 @@
         insert-opts {:sibling? sibling?
                      :replace-empty-target? replace-empty-target?
                      :keep-uuid? keep-uuid?
-                     :move? move?
                      :outliner-op outliner-op}
-        tx' (insert-blocks-aux blocks' target-block' insert-opts)]
-    (if (some (fn [b] (or (nil? (:block/parent b)) (nil? (:block/left b)))) tx')
+        tx' (insert-blocks-aux blocks' target-block insert-opts)]
+    (if (some (fn [b] (or (nil? (:block/parent b)) (nil? (:block/order b)))) tx')
       (throw (ex-info "Invalid outliner data"
                       {:opts insert-opts
                        :tx (vec tx')
                        :blocks (vec blocks)
-                       :target-block target-block'}))
+                       :target-block target-block}))
       (let [uuids-tx (->> (map :block/uuid tx')
                           (remove nil?)
                           (map (fn [uuid] {:block/uuid uuid})))
-            tx (if move?
-                 tx'
-                 (assign-temp-id tx' replace-empty-target? target-block'))
-            target-node (block @conn target-block')
-            next (if sibling?
-                   (otree/-get-right target-node conn)
-                   (otree/-get-down target-node conn))
-            next-tx (when (and next
-                               (if move? (not (contains? (set (map :db/id blocks)) (:db/id (:data next)))) true))
-                      (if-let [left (last (filter (fn [b] (= 1 (:block/level b))) tx))]
-                        [{:block/uuid (otree/-get-id next conn)
-                          :block/left (:db/id left)}]
-                        (prn :debug :insert-blocks :tx tx)))
-            full-tx (common-util/concat-without-nil (if (and keep-uuid? replace-empty-target?) (rest uuids-tx) uuids-tx) tx next-tx)]
+            tx (assign-temp-id tx' replace-empty-target? target-block)
+            full-tx (common-util/concat-without-nil (if (and keep-uuid? replace-empty-target?) (rest uuids-tx) uuids-tx) tx)]
         {:tx-data full-tx
          :blocks  tx}))))
 
-(defn- build-move-block-next-tx
-  [db block target-block sibling?]
-  (let [target-id (:db/id target-block)]
-    [(when-let [right-block (get-right-sibling db (:db/id block))]
-       {:db/id (:db/id right-block)
-        :block/left (:db/id (:block/left block))})
-     (when-let [target-next-block (if sibling?
-                                    (get-right-sibling db (:db/id target-block))
-                                    (ldb/get-by-parent-&-left db target-id target-id))]
-       {:db/id (:db/id target-next-block)
-        :block/left (:db/id block)})]))
-
-(defn- find-new-left
-  [db block moved-ids target-block current-block {:keys [sibling? delete-blocks?] :as opts}]
-  (if (= (:db/id target-block) (:db/id (:block/left current-block)))
-    (if delete-blocks?
-      (if sibling?
-        (d/entity db (last moved-ids))
-        target-block)
-      ;; move blocks
-      (let [parent-of-first-block? (= (:db/id target-block) (:db/id (:block/parent current-block)))]
-        (if (or sibling? parent-of-first-block?)
-          (d/entity db (last moved-ids))
-          target-block)))
-    (let [left (d/entity db (:db/id (:block/left block)))]
-      (if (contains? (set moved-ids) (:db/id left))
-        (find-new-left db left moved-ids target-block current-block opts)
-        left))))
-
 (defn- sort-non-consecutive-blocks
   [db blocks]
   (let [page-blocks (group-by :block/page blocks)]
@@ -920,137 +709,76 @@
               (ldb/sort-page-random-blocks db blocks))
             page-blocks)))
 
-(defn- fix-non-consecutive-blocks
-  [db blocks target-block sibling? delete-blocks?]
-  (when (> (count blocks) 1)
-    (let [page-blocks (group-by :block/page blocks)
-          near-by? (= (:db/id target-block) (:db/id (:block/left (first blocks))))
-          parent-of-first-block? (= (:db/id target-block) (:db/id (:block/parent (first blocks))))]
-      (->>
-       (mapcat (fn [[_page blocks]]
-                 (let [blocks (ldb/sort-page-random-blocks db blocks)
-                       non-consecutive-blocks (->> (conj (ldb/get-non-consecutive-blocks db blocks) (last blocks))
-                                                   (common-util/distinct-by :db/id))]
-                   (when (seq non-consecutive-blocks)
-                     (map-indexed (fn [idx block]
-                                    (when-let [right (get-right-sibling db (:db/id block))]
-                                      (cond
-                                        (and (zero? idx) parent-of-first-block? sibling?)
-                                        {:db/id (:db/id right)
-                                         :block/left (:db/id target-block)}
-                                        (and (zero? idx) near-by? sibling?)
-                                        {:db/id (:db/id right)
-                                         :block/left (:db/id (last blocks))}
-                                        :else
-                                        (let [new-left (find-new-left db right (distinct (map :db/id blocks)) target-block block
-                                                                      {:sibling? sibling?
-                                                                       :delete-blocks? delete-blocks?
-                                                                       :idx idx})]
-                                          (assert new-left (str "Can't find new left, :delete-blocks? " delete-blocks?))
-                                          {:db/id      (:db/id right)
-                                           :block/left (:db/id new-left)}))))
-                                  non-consecutive-blocks)))) page-blocks)
-       (remove nil?)))))
-
 (defn delete-block
-  [repo conn txs-state node {:keys [date-formatter]}]
-  (let [right-node (otree/-get-right node conn)]
-    (otree/-del node txs-state conn)
-    (when (otree/satisfied-inode? right-node)
-      (when-let [left-node (otree/-get-left node conn)]
-        (let [new-right-node (otree/-set-left-id right-node (otree/-get-id left-node conn) conn)]
-          (otree/-save new-right-node txs-state conn repo date-formatter {}))))
-    @txs-state))
+  [_repo conn txs-state node {:keys [_date-formatter]}]
+  (otree/-del node txs-state conn)
+  @txs-state)
+
+(defn- get-top-level-blocks
+  [top-level-blocks non-consecutive?]
+  (let [reversed? (and (not non-consecutive?)
+                       (:block/order (first top-level-blocks))
+                       (:block/order (second top-level-blocks))
+                       (> (compare (:block/order (first top-level-blocks))
+                                   (:block/order (second top-level-blocks))) 0))]
+    (if reversed? (reverse top-level-blocks) top-level-blocks)))
 
 (defn ^:api ^:large-vars/cleanup-todo delete-blocks
   "Delete blocks from the tree.
   `blocks` need to be sorted by left&parent(from top to bottom)"
   [repo conn date-formatter blocks delete-opts]
   [:pre [(seq blocks)]]
-  (let [top-level-blocks (filter-top-level-blocks blocks)
+  (let [top-level-blocks (filter-top-level-blocks @conn blocks)
         non-consecutive? (and (> (count top-level-blocks) 1) (seq (ldb/get-non-consecutive-blocks @conn top-level-blocks)))
-        reversed? (and (not non-consecutive?)
-                       (= (:db/id (:block/left (first top-level-blocks)))
-                          (:db/id (second top-level-blocks))))
-        top-level-blocks (if reversed? (reverse top-level-blocks) top-level-blocks)
+        top-level-blocks (get-top-level-blocks top-level-blocks non-consecutive?)
         txs-state (ds/new-outliner-txs-state)
         block-ids (map (fn [b] [:block/uuid (:block/uuid b)]) top-level-blocks)
         start-block (first top-level-blocks)
-        end-block (last top-level-blocks)
-        start-node (block @conn start-block)
-        end-node (block @conn end-block)]
+        end-block (last top-level-blocks)]
     (if (or
          (= 1 (count top-level-blocks))
-         (= start-node end-node))
-      (delete-block repo conn txs-state start-node (assoc delete-opts :date-formatter date-formatter))
-      (do
-        (when-not non-consecutive?
-          (let [sibling? (= (otree/-get-parent-id start-node conn)
-                            (otree/-get-parent-id end-node conn))
-                right-node (otree/-get-right end-node conn)]
-            (when (otree/satisfied-inode? right-node)
-              (let [left-node-id (if sibling?
-                                   (otree/-get-id (otree/-get-left start-node conn) conn)
-                                   (let [end-node-left-nodes (get-left-nodes conn end-node (count block-ids))
-                                         parents (->>
-                                                  (ldb/get-block-parents
-                                                   @conn
-                                                   (otree/-get-id start-node conn)
-                                                   {:depth 1000})
-                                                  (map :block/uuid)
-                                                  (set))
-                                         result (first (set/intersection (set end-node-left-nodes) parents))]
-                                     (when (and (not non-consecutive?) (not result))
-                                       (pprint/pprint {:parents parents
-                                                       :end-node-left-nodes end-node-left-nodes}))
-                                     result))]
-                (when (nil? left-node-id)
-                  (assert left-node-id
-                          (str "Can't find the left-node-id: "
-                               (pr-str {:start (d/entity @conn [:block/uuid (otree/-get-id start-node conn)])
-                                        :end (d/entity @conn [:block/uuid (otree/-get-id end-node conn)])
-                                        :right-node (d/entity @conn [:block/uuid (otree/-get-id right-node conn)])
-                                        :blocks top-level-blocks}))))
-                (let [new-right-node (otree/-set-left-id right-node left-node-id conn)]
-                  (otree/-save new-right-node txs-state conn repo date-formatter {}))))))
-        (doseq [id block-ids]
-          (let [node (block @conn (d/entity @conn id))]
-            (otree/-del node txs-state conn)))
-        (when non-consecutive?
-          (let [fix-non-consecutive-tx (fix-non-consecutive-blocks @conn top-level-blocks nil false true)]
-            (swap! txs-state concat fix-non-consecutive-tx)))))
+         (= start-block end-block))
+      (delete-block repo conn txs-state start-block (assoc delete-opts :date-formatter date-formatter))
+      (doseq [id block-ids]
+        (let [node (d/entity @conn id)]
+          (otree/-del node txs-state conn))))
     {:tx-data @txs-state}))
 
 (defn- move-to-original-position?
   [blocks target-block sibling? non-consecutive-blocks?]
   (and (not non-consecutive-blocks?)
-       (= (:db/id (:block/left (first blocks))) (:db/id target-block))
+       (= (:db/id (ldb/get-left-sibling (first blocks))) (:db/id target-block))
        (not= (= (:db/id (:block/parent (first blocks)))
                 (:db/id target-block))
              sibling?)))
 
 (defn- move-block
-  [db block target-block sibling?]
-  (let [target-block (d/entity db (:db/id target-block))
+  [conn block target-block sibling?]
+  (let [db @conn
+        target-block (d/entity db (:db/id target-block))
         block (d/entity db (:db/id block))
         first-block-page (:db/id (:block/page block))
         target-page (or (:db/id (:block/page target-block))
                         (:db/id target-block))
         not-same-page? (not= first-block-page target-page)
+        block-order (if sibling?
+                      (db-order/gen-key (:block/order target-block)
+                                        (:block/order (ldb/get-right-sibling target-block)))
+                      (db-order/gen-key nil
+                                        (:block/order (ldb/get-down target-block))))
         tx-data [(cond->
                   {:db/id (:db/id block)
-                   :block/left (:db/id target-block)
                    :block/parent (if sibling?
                                    (:db/id (:block/parent target-block))
-                                   (:db/id target-block))}
+                                   (:db/id target-block))
+                   :block/order block-order}
                    not-same-page?
                    (assoc :block/page target-page))]
-        move-blocks-next-tx (build-move-block-next-tx db block target-block sibling?)
         children-page-tx (when not-same-page?
                            (let [children-ids (ldb/get-block-children-ids db (:block/uuid block))]
                              (map (fn [id] {:block/uuid id
                                             :block/page target-page}) children-ids)))]
-    (common-util/concat-without-nil tx-data move-blocks-next-tx children-page-tx)))
+    (common-util/concat-without-nil tx-data children-page-tx)))
 
 (defn- move-blocks
   "Move `blocks` to `target-block` as siblings or children."
@@ -1058,20 +786,18 @@
                                    :as opts}]
   {:pre [(seq blocks)
          (m/validate block-map-or-entity target-block)]}
-  (assert (every? (fn [block] (and (:db/id (:block/parent block)) (:db/id (:block/left block)))) blocks)
-          (str "Invalid blocks (without either parent or left): "
-               (remove (fn [block] (and (:db/id (:block/parent block)) (:db/id (:block/left block)))) blocks)))
   (let [db @conn
-        top-level-blocks (filter-top-level-blocks blocks)
+        top-level-blocks (filter-top-level-blocks db blocks)
         [target-block sibling?] (get-target-block db top-level-blocks target-block opts)
         non-consecutive? (and (> (count top-level-blocks) 1) (seq (ldb/get-non-consecutive-blocks db top-level-blocks)))
-        reversed? (and (not non-consecutive?)
-                       (= (:db/id (:block/left (first top-level-blocks)))
-                          (:db/id (second top-level-blocks))))
-        top-level-blocks (if reversed? (reverse top-level-blocks) top-level-blocks)
-        blocks (if non-consecutive?
-                 (sort-non-consecutive-blocks db top-level-blocks)
-                 top-level-blocks)
+        top-level-blocks (get-top-level-blocks top-level-blocks non-consecutive?)
+        blocks (->> (if non-consecutive?
+                      (sort-non-consecutive-blocks db top-level-blocks)
+                      top-level-blocks)
+                    (map (fn [block]
+                           (if (de/entity? block)
+                             block
+                             (d/entity db (:db/id block))))))
         original-position? (move-to-original-position? blocks target-block sibling? non-consecutive?)]
     (when (and (not (contains? (set (map :db/id blocks)) (:db/id target-block)))
                (not original-position?))
@@ -1087,11 +813,11 @@
                     target-block (if first-block? target-block
                                      (d/entity @conn (:db/id (nth blocks (dec idx)))))
                     block (d/entity @conn (:db/id block))]
-                (when-not (and (= (:db/id (:block/left block)) (:db/id target-block))
+                (when-not (and (= (:db/id (ldb/get-left-sibling block)) (:db/id target-block))
                                (if sibling?
                                  (= (:db/id (:block/parent block)) (:db/id (:block/parent target-block)))
                                  (= (:db/id (:block/parent block)) (:db/id target-block))))
-                  (let [tx-data (move-block @conn block target-block sibling?)]
+                  (let [tx-data (move-block conn block target-block sibling?)]
                     (ldb/transact! conn tx-data {:sibling? sibling?
                                                  :outliner-op (or outliner-op :move-blocks)}))))))
           nil)))))
@@ -1101,13 +827,15 @@
   [repo conn blocks up?]
   {:pre [(seq blocks) (boolean? up?)]}
   (let [db @conn
-        top-level-blocks (filter-top-level-blocks blocks)
+        top-level-blocks (filter-top-level-blocks db blocks)
         opts {:outliner-op :move-blocks-up-down}]
     (if up?
       (let [first-block (d/entity db (:db/id (first top-level-blocks)))
             first-block-parent (:block/parent first-block)
-            left (:block/left first-block)
-            left-left (:block/left left)
+            left-or-parent (or (ldb/get-left-sibling first-block)
+                               first-block-parent)
+            left-left (or (ldb/get-left-sibling left-or-parent)
+                          first-block-parent)
             sibling? (= (:db/id (:block/parent left-left))
                         (:db/id first-block-parent))]
         (when (and left-left
@@ -1117,13 +845,13 @@
                                                                          :up? up?}))))
 
       (let [last-top-block (last top-level-blocks)
-            last-top-block-right (get-right-sibling db (:db/id last-top-block))
+            last-top-block-right (ldb/get-right-sibling last-top-block)
             right (or
                    last-top-block-right
                    (let [parent (:block/parent last-top-block)
-                         parent-id (when (:block/page (d/entity db (:db/id parent)))
-                                     (:db/id parent))]
-                     (some->> parent-id (get-right-sibling db))))
+                         parent (when (:block/page (d/entity db (:db/id parent)))
+                                  parent)]
+                     (ldb/get-right-sibling parent)))
             sibling? (= (:db/id (:block/parent last-top-block))
                         (:db/id (:block/parent right)))]
         (when right
@@ -1135,16 +863,12 @@
   [repo conn blocks indent? & {:keys [parent-original logical-outdenting?]}]
   {:pre [(seq blocks) (boolean? indent?)]}
   (let [db @conn
-        top-level-blocks (->> (map (fn [b] (d/entity db (:db/id b))) blocks)
-                              filter-top-level-blocks)
+        top-level-blocks (filter-top-level-blocks db blocks)
         non-consecutive? (and (> (count top-level-blocks) 1) (seq (ldb/get-non-consecutive-blocks @conn top-level-blocks)))
-        reversed? (and (not non-consecutive?)
-                       (= (:db/id (:block/left (first top-level-blocks)))
-                          (:db/id (second top-level-blocks))))
-        top-level-blocks (if reversed? (reverse top-level-blocks) top-level-blocks)]
+        top-level-blocks (get-top-level-blocks top-level-blocks non-consecutive?)]
     (when-not non-consecutive?
       (let [first-block (d/entity db (:db/id (first top-level-blocks)))
-            left (d/entity db (:db/id (:block/left first-block)))
+            left (ldb/get-left-sibling first-block)
             parent (:block/parent first-block)
             concat-tx-fn (fn [& results]
                            {:tx-data (->> (map :tx-data results)
@@ -1152,7 +876,7 @@
                             :tx-meta (:tx-meta (first results))})
             opts {:outliner-op :indent-outdent-blocks}]
         (if indent?
-          (when (and left (not (page-first-child? first-block)))
+          (when left
             (let [last-direct-child-id (ldb/get-block-last-direct-child-id db (:db/id left))
                   blocks' (drop-while (fn [b]
                                         (= (:db/id (:block/parent b))
@@ -1179,7 +903,7 @@
                                                                           :sibling? true
                                                                           :indent? false})))
 
-            (when (and parent (not (page-block? (d/entity db (:db/id parent)))))
+            (when (and parent (not (ldb/page? (d/entity db (:db/id parent)))))
               (let [blocks' (take-while (fn [b]
                                           (not= (:db/id (:block/parent b))
                                                 (:db/id (:block/parent parent))))
@@ -1189,8 +913,7 @@
                   result
                   ;; direct outdenting (default behavior)
                   (let [last-top-block (d/entity db (:db/id (last blocks')))
-                        right-siblings (->> (get-right-siblings conn (block db last-top-block))
-                                            (map :data))]
+                        right-siblings (get-right-siblings last-top-block)]
                     (if (seq right-siblings)
                       (if-let [last-direct-child-id (ldb/get-block-last-direct-child-id db (:db/id last-top-block))]
                         (move-blocks repo conn right-siblings (d/entity db last-direct-child-id) (merge opts {:sibling? true}))
@@ -1199,41 +922,10 @@
 
 ;;; ### write-operations have side-effects (do transactions) ;;;;;;;;;;;;;;;;
 
-(defn- validate-tx-data
-  "Ensure :block/left and :block/parent not point to itself"
-  [db tx-data tx-meta args]
-  (let [blocks (filter (fn [data] (and (map? data)
-                                       (or (:block/left data) (:block/parent data)))) tx-data)]
-    (every?
-     (fn left-parent-not-self
-       [{:block/keys [left parent] :as block}]
-       (let [eid (or (:db/id block) [:block/uuid (:block/uuid block)])]
-         (when-not (and (number? eid) (neg? eid))
-           (let [block-db-id (:db/id (d/entity db eid))
-                 left-id (some->> (when left (or (and (map? left) (:db/id left)) left))
-                                  (d/entity db)
-                                  :db/id)
-                 parent-id (some->> (when parent (or (and (map? parent) (:db/id parent)) parent))
-                                    (d/entity db)
-                                    :db/id)
-                 point-to-self? (some #(= block-db-id %) (remove nil? [left-id parent-id]))]
-             (when point-to-self?
-               (prn :error ":block/parent or :block/left points to self"
-                    {:block-id block-db-id
-                     :left-id left-id
-                     :parent-id parent-id
-                     :tx-data tx-data
-                     :tx-meta tx-meta
-                     :args (drop 2 args)})
-               (prn :datascript-db (ldb/write-transit-str db)))
-             (assert (not point-to-self?) ":block/parent or :block/left points to self")))))
-     blocks)))
-
 (defn- op-transact!
   [fn-var & args]
   {:pre [(var? fn-var)]}
   (let [result (apply @fn-var args)]
-    (validate-tx-data @(nth args 1) (:tx-data result) (:tx-meta result) args)
     (when result
       (ldb/transact! (second args) (:tx-data result) (:tx-meta result)))
     result))

+ 6 - 21
deps/outliner/src/logseq/outliner/tree.cljs

@@ -5,21 +5,8 @@
             [datascript.core :as d]))
 
 (defprotocol INode
-  (-get-id [this conn])
-  (-get-parent-id [this conn])
-  (-get-left-id [this conn])
-  (-set-left-id [this left-id conn])
-  (-get-parent [this conn])
-  (-get-left [this conn])
-  (-get-right [this conn])
-  (-get-down [this conn])
   (-save [this txs-state conn repo date-formatter opts])
-  (-del [this db conn])
-  (-get-children [this conn]))
-
-(defn satisfied-inode?
-  [node]
-  (satisfies? INode node))
+  (-del [this db conn]))
 
 (defn- blocks->vec-tree-aux
   [repo db blocks root]
@@ -28,12 +15,12 @@
         parent-blocks (group-by #(get-in % [:block/parent :db/id]) blocks) ;; exclude whiteboard shapes
         sort-fn (fn [parent]
                   (when-let [children (get parent-blocks parent)]
-                    (ldb/sort-by-left children {:db/id parent})))
+                    (ldb/sort-by-order children)))
         block-children (fn block-children [parent level]
                          (map (fn [m]
                                 (let [id (:db/id m)
                                       children (-> (block-children id (inc level))
-                                                   (ldb/sort-by-left m))]
+                                                   (ldb/sort-by-order))]
                                   (assoc m
                                          :block/level level
                                          :block/children children)))
@@ -84,7 +71,7 @@
                             b')))
                       (let [parent {:db/id parent-id}]
                         (-> (get parent->children parent)
-                            (ldb/try-sort-by-left parent)))))
+                            (ldb/sort-by-order)))))
         children (nodes root-id 1)
         root' (assoc root :block/level (or default-level 1))]
     (if (seq children)
@@ -97,8 +84,6 @@
            :block/uuid (:block/uuid e)
            :block/parent {:db/id (:db/id (:block/parent e))}
            :block/page (:block/page e)}
-    (:db/id (:block/left e))
-    (assoc :block/left {:db/id (:db/id (:block/left e))})
     (:block/refs e)
     (assoc :block/refs (:block/refs e))
     (:block/children e)
@@ -118,7 +103,7 @@
   ([blocks default-level]
    (let [blocks (map block-entity->map blocks)
          top-level-blocks (filter-top-level-blocks blocks)
-         top-level-blocks' (ldb/try-sort-by-left top-level-blocks (:block/parent (first top-level-blocks)))
+         top-level-blocks' (ldb/sort-by-order top-level-blocks)
          parent->children (group-by :block/parent blocks)]
      (map #(tree parent->children % (or default-level 1)) top-level-blocks'))))
 
@@ -126,7 +111,7 @@
   [parents parent-groups]
   (mapv (fn [parent]
           (let [parent-id {:db/id (:db/id parent)}
-                children (ldb/sort-by-left (get @parent-groups parent-id) parent)
+                children (ldb/sort-by-order (get @parent-groups parent-id))
                 _ (swap! parent-groups #(dissoc % parent-id))
                 sorted-nested-children (when (not-empty children) (sort-blocks-aux children parent-groups))]
                     (if sorted-nested-children [parent sorted-nested-children] [parent])))

+ 3 - 3
docs/dev-practices.md

@@ -365,7 +365,7 @@ These tasks are specific to database graphs. For these tasks there is a one time
   ```sh
   $ bb dev:db-query woot '[:find (pull ?b [*]) :where (block-content ?b "Dogma")]'
   DB contains 833 datoms
-  [{:block/tx-id 536870923, :block/link #:db{:id 100065}, :block/uuid #uuid "65565c26-f972-4400-bce4-a15df488784d", :block/updated-at 1700158508564, :block/left #:db{:id 100051}, :block/refs [#:db{:id 100064}], :block/created-at 1700158502056, :block/format :markdown, :block/tags [#:db{:id 100064}], :block/content "Dogma #~^65565c2a-b1c5-4dc8-a0f0-81b786bc5c6d", :db/id 100090, :block/path-refs [#:db{:id 100051} #:db{:id 100064}], :block/parent #:db{:id 100051}, :block/page #:db{:id 100051}}]
+  [{:block/tx-id 536870923, :block/link #:db{:id 100065}, :block/uuid #uuid "65565c26-f972-4400-bce4-a15df488784d", :block/updated-at 1700158508564, :block/order "a0", :block/refs [#:db{:id 100064}], :block/created-at 1700158502056, :block/format :markdown, :block/tags [#:db{:id 100064}], :block/content "Dogma #~^65565c2a-b1c5-4dc8-a0f0-81b786bc5c6d", :db/id 100090, :block/path-refs [#:db{:id 100051} #:db{:id 100064}], :block/parent #:db{:id 100051}, :block/page #:db{:id 100051}}]
   ```
 
 * `dev:db-transact` - Run a `d/transact!` against the queried results of a DB graph
@@ -420,7 +420,7 @@ These tasks are specific to database graphs. For these tasks there is a one time
     #uuid "6581c8db-a2a2-4e09-b30d-cdea6ad69512"
     536871037
     true]]]
-  
+
   # By default this task ignores commonly changing datascript attributes.
   # To see all changed attributes, tell the task to ignore a nonexistent attribute:
   $ bb dev:diff-datoms w2.edn w3.edn -i a
@@ -439,7 +439,7 @@ These tasks are specific to database graphs. For these tasks there is a one time
     [162 :block/content "b7" 536871039 true]
     [162 :block/created-at 1703004379103 536871037 true]
     [162 :block/format :markdown 536871037 true]
-    [162 :block/left 149 536871037 true]
+    [162 :block/order "a0" 536871037 true]
     [162 :block/page 149 536871037 true]
     [162 :block/parent 149 536871037 true]
     [162 :block/path-refs 108 536871044 true]

+ 8 - 9
scripts/src/logseq/tasks/db_graph/create_graph.cljs

@@ -15,7 +15,8 @@
             ["fs" :as fs]
             ["path" :as node-path]
             [nbb.classpath :as cp]
-            [logseq.db.frontend.property :as db-property]))
+            [logseq.db.frontend.property :as db-property]
+            [logseq.db.frontend.order :as db-order]))
 
 (defn- find-on-classpath [rel-path]
   (some (fn [dir]
@@ -110,13 +111,13 @@
   "Provides the next temp :db/id to use in a create-graph transact!"
   #(swap! current-db-id dec))
 
-(defn- ->block-tx [m uuid-maps all-idents page-id last-block block-id-fn]
+(defn- ->block-tx [m uuid-maps all-idents page-id]
   (merge (dissoc m :properties)
          (sqlite-util/block-with-timestamps
           {:db/id (new-db-id)
            :block/format :markdown
            :block/page {:db/id page-id}
-           :block/left {:db/id (if last-block (block-id-fn last-block) page-id)}
+           :block/order (db-order/gen-key nil)
            :block/parent {:db/id page-id}})
          (when (seq (:properties m))
            (merge (->block-properties (:properties m) uuid-maps all-idents)
@@ -172,7 +173,7 @@
    have the following limitations:
 
  * Only top level blocks can be easily defined. Other level blocks can be
-   defined but they require explicit setting of attributes like :block/left and :block/parent
+   defined but they require explicit setting of attributes like :block/order and :block/parent
  * Block content containing page refs or tags is not supported yet
 
    The EDN map has the following keys:
@@ -190,8 +191,6 @@
   * :graph-namespace - namespace to use for db-ident creation. Useful when importing an ontology
   * :page-id-fn - custom fn that returns ent lookup id for page refs e.g. `[:block/uuid X]`
     Default is :db/id
-  * :block-id-fn - custom fn that returns ent lookup id for page refs e.g. `[:block/uuid X]`
-    Default is :db/id
 
    The :properties in :pages-and-blocks is a map of property names to property
    values.  Multiple property values for a many cardinality property are defined
@@ -199,8 +198,8 @@
    :checkbox, :number, :page and :date. :checkbox and :number values are written
    as booleans and integers/floats. :page references are written as
    vectors e.g. `[:page \"PAGE NAME\"]`"
-  [{:keys [pages-and-blocks properties graph-namespace page-id-fn block-id-fn]
-    :or {page-id-fn :db/id block-id-fn :db/id}
+  [{:keys [pages-and-blocks properties graph-namespace page-id-fn]
+    :or {page-id-fn :db/id}
     :as options}]
   (let [_ (validate-options options)
         ;; add uuids before tx for refs in :properties
@@ -244,7 +243,7 @@
                ;; blocks tx
                (reduce (fn [acc m]
                          (conj acc
-                               (->block-tx m uuid-maps all-idents (page-id-fn new-page) (last acc) block-id-fn)))
+                               (->block-tx m uuid-maps all-idents (page-id-fn new-page))))
                        []
                        blocks))))
           pages-and-blocks'))]

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

@@ -60,7 +60,7 @@
   [file1 file2 & args]
   (let [spec {:ignored-attributes
               ;; Ignores some attributes by default that are expected to change often
-              {:alias :i :coerce #{:keyword} :default #{:block/tx-id :block/left :block/updated-at}}}
+              {:alias :i :coerce #{:keyword} :default #{:block/tx-id :block/order :block/updated-at}}}
         {{:keys [ignored-attributes]} :opts} (cli/parse-args args {:spec spec})
         datom-filter (fn [[e a _ _ _]] (contains? ignored-attributes a))
         data-diff* (apply data/diff (map (fn [x] (->> x slurp edn/read-string (remove datom-filter))) [file1 file2]))

+ 0 - 59
src/dev-cljs/shadow/build_large_graph.cljs

@@ -1,59 +0,0 @@
-(ns shadow.build-large-graph)
-
-(comment
-
-  (in-ns 'frontend.db-worker)
-  (def repo "logseq_db_large-db-demo")
-  (def conn (worker-state/get-datascript-conn repo))
-
-  (defonce *ids (atom (set (map :v (d/datoms @conn :avet :block/uuid)))))
-  (defn get-next-id
-    []
-    (let [id (random-uuid)]
-      (if (@*ids id)
-        (get-next-id)
-        (do
-          (swap! *ids conj id)
-          id))))
-
-  (defn pages
-    [start-idx n]
-    (let [ids (repeatedly n get-next-id)]
-      (map-indexed
-       (fn [idx id]
-         {:block/uuid id
-          :block/original-name (str "page-" (+ start-idx idx))
-          :block/name (str "page-" (+ start-idx idx))
-          :block/format :markdown})
-       ids)))
-
-  (defn blocks
-    [page-id size]
-    (let [page-id [:block/uuid page-id]
-          blocks (vec (repeatedly size (fn []
-                                         (let [id (get-next-id)]
-                                           {:block/uuid id
-                                            :block/content (str id)
-                                            :block/format :markdown
-                                            :block/page page-id
-                                            :block/parent page-id}))))]
-      (map-indexed
-       (fn [i b]
-         (if (zero? i)
-           (assoc b :block/left page-id)
-           (let [left (nth blocks (dec i))]
-             (assoc b :block/left [:block/uuid (:block/uuid left)]))))
-       blocks)))
-
-  (defn create-graph!
-    [conn page-size blocks-size start-idx]
-    (let [pages (pages start-idx page-size)
-          page-blocks (map (fn [p]
-                             (cons p
-                                   (blocks (:block/uuid p) blocks-size))) pages)]
-      (doseq [data (partition-all 1000 page-blocks)]
-        (let [tx-data (apply concat data)]
-          (prn :debug :progressing (:block/name (first tx-data)))
-          (d/transact! conn tx-data {:new-graph? true})))))
-
-  (create-graph! conn 30000 20 0))

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

@@ -22,7 +22,7 @@
 (sr/defkeyword :block/parent
   "page blocks don't have this attr")
 
-(sr/defkeyword :block/left
+(sr/defkeyword :block/order
   "
 - page blocks don't have this attr
 - some no-order blocks don't have this attr too,

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

@@ -858,7 +858,7 @@
                       page-name))
            (if whiteboard-page?
                ((state/get-component :whiteboard/tldraw-preview) (:block/uuid block))
-               (let [blocks (db/sort-by-left (:block/_parent block) block)]
+               (let [blocks (db/sort-by-order (:block/_parent block))]
                  (blocks-container blocks (assoc config
                                                  :db/id (:db/id block)
                                                  :id page-name
@@ -3093,7 +3093,7 @@
                              :in-block-container? true})])
 
        (when-not (or (:hide-children? config) in-whiteboard?)
-         (let [children' (db/sort-by-left children block)
+         (let [children' (db/sort-by-order children)
                config' (-> (update config :level inc)
                            (dissoc :original-block))]
            (block-children config' block children' collapsed?)))

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

@@ -95,14 +95,15 @@
 
 (defn- get-image-blob
   [block-uuids-or-page-name {:keys [transparent-bg? x y width height zoom]} callback]
-  (let [style (js/window.getComputedStyle js/document.body)
+  (let [top-block-id (if (coll? block-uuids-or-page-name) (first block-uuids-or-page-name) block-uuids-or-page-name)
+        style (js/window.getComputedStyle js/document.body)
         background (when-not transparent-bg? (.getPropertyValue style "--ls-primary-background-color"))
-        page? (string? block-uuids-or-page-name)
+        page? (and (uuid? top-block-id) (db/page? (db/entity [:block/uuid top-block-id])))
         selector (if page?
                    "#main-content-container"
-                   (str "[blockid='" (str (first block-uuids-or-page-name)) "']"))
+                   (str "[blockid='" top-block-id "']"))
         container  (js/document.querySelector selector)
-        scale (if page? (/ 1 (or zoom (get-zoom-level block-uuids-or-page-name))) 1)
+        scale (if page? (/ 1 (or zoom (get-zoom-level top-block-id))) 1)
         options #js {:allowTaint true
                      :useCORS true
                      :backgroundColor (or background "transparent")
@@ -113,7 +114,7 @@
                      :scrollX 0
                      :scrollY 0
                      :scale scale
-                     :windowHeight (when (string? block-uuids-or-page-name)
+                     :windowHeight (when page?
                                      (.-scrollHeight container))}]
     (-> (js/html2canvas container options)
         (.then (fn [canvas] (.toBlob canvas (fn [blob]

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

@@ -124,7 +124,7 @@
                                     selected (db/pull-many (state/get-current-repo) '[*] lookup-refs)
                                     blocks (if (seq selected) selected [@component-block/*dragging-block])
                                     _ (editor-handler/insert-first-page-block-if-not-exists! page-name {:redirect? false})]
-                              (js/setTimeout #(let [target-block (db/pull (:db/id (db/get-page page-name)))]
+                              (js/setTimeout #(let [target-block (db/entity (:db/id (db/get-page page-name)))]
                                                 (dnd/move-blocks event blocks target-block nil :sibling))
                                 0)))]
       [:div.ls-block.flex-1.flex-col.rounded-sm
@@ -200,7 +200,7 @@
                                :disable-lazy-load? short-page?}
                               config)
               config (common-handler/config-with-document-mode hiccup-config)
-              blocks (if block? [block] (db/sort-by-left children block))]
+              blocks (if block? [block] (db/sort-by-order children block))]
           [:div
            (page-blocks-inner page-e blocks config sidebar? whiteboard? block-id)
            (when-not config/publishing?

+ 2 - 2
src/main/frontend/components/property/closed_value.cljs

@@ -125,7 +125,7 @@
                                                 (update-icon icon))})
       (cond
         property-block?
-        (let [first-child (ldb/get-by-parent-&-left (db/get-db) (:db/id item) (:db/id item))]
+        (let [first-child (ldb/get-first-child (db/get-db) item)]
           (:block/content first-child))
 
         date?
@@ -192,7 +192,7 @@
       [:li (if (uuid? value)
              (let [result (db/entity [:block/uuid value])]
                (if (db-property/property-created-block? result)
-                 (let [first-child (ldb/get-by-parent-&-left (db/get-db) (:db/id result) (:db/id result))]
+                 (let [first-child (ldb/get-first-child (db/get-db) result)]
                    (:block/content first-child))
                  (:block/original-name result)))
              (str value))])]

+ 4 - 6
src/main/frontend/components/property/value.cljs

@@ -392,7 +392,7 @@
                             (when-let [block (when id (db/entity [:block/uuid id]))]
                               (let [icon (pu/get-block-property-value block :logseq.property/icon)
                                     value (if (db-property/property-created-block? block)
-                                            (let [first-child (ldb/get-by-parent-&-left (db/get-db) (:db/id block) (:db/id block))]
+                                            (let [first-child (ldb/get-first-child (db/get-db) block)]
                                               (:block/content first-child))
                                             (db-property/closed-value-name block))]
                                 {:label (if icon
@@ -416,7 +416,7 @@
             add-property-f #(<add-property! block (:db/ident property) %)
             on-chosen (fn [chosen]
                         (p/do!
-                          (add-property-f (if (map? chosen) (:value chosen) chosen))
+                         (add-property-f (if (map? chosen) (:value chosen) chosen))
                          (when-let [f (:on-chosen select-opts)] (f))))
             selected-choices' (get block (:db/ident property))
             selected-choices (if (coll? selected-choices')
@@ -464,9 +464,7 @@
   [parent block-cp editor-box & {:keys [closed-values?]}]
   (if (and (:block/uuid parent) (state/sub-async-query-loading (:block/uuid parent)))
     [:div.text-sm.opacity-70 "loading"]
-    (let [children (model/sort-by-left
-                    (:block/_parent (db/entity (:db/id parent)))
-                    parent)
+    (let [children (model/sort-by-order (:block/_parent (db/entity (:db/id parent))))
           hide-bullet? (and (= (count children) 1)
                             (not (editor-handler/collapsable? (:block/uuid (first children)))))]
       (if (seq children)
@@ -546,7 +544,7 @@
         (let [property-block? (db-property/property-created-block? block)
               value' (or (get-in block [:block/schema :value])
                          (when property-block?
-                           (let [first-child (ldb/get-by-parent-&-left (db/get-db) (:db/id value) (:db/id value))]
+                           (let [first-child (ldb/get-first-child (db/get-db) value)]
                              (inline-text {} :markdown (:block/content first-child)))))
               icon (pu/get-block-property-value block :logseq.property/icon)]
           (cond

+ 0 - 12
src/main/frontend/components/query_table.cljs

@@ -175,18 +175,6 @@
                    ;; Fallback to original properties for page blocks
                    (get-in row [:block/properties column])))]))
 
-(defn build-column-text [row column]
-  (case column
-    :page  (or (get-in row [:block/page :block/original-name])
-               (get-in row [:block/original-name])
-               (get-in row [:block/content]))
-    :block (or (get-in row [:block/original-name])
-               (get-in row [:block/content]))
-
-           (or (get-in row [:block/properties column])
-               (get-in row [:block/properties-text-values column])
-               (get-in row [(keyword :block column)]))))
-
 (defn- render-column-value
   [{:keys [row-block row-format cell-format value]} page-cp inline-text {:keys [uuid-names db-graph?]}]
   (cond

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

@@ -28,7 +28,7 @@
  [frontend.db.model
   delete-blocks get-pre-block
   delete-files delete-pages-by-files
-  get-block-and-children get-block-by-uuid get-block-children sort-by-left
+  get-block-and-children get-block-by-uuid get-block-children sort-by-order
   get-block-parent get-block-parents parents-collapsed? get-block-referenced-blocks
   get-block-immediate-children get-block-page
   get-custom-css

+ 1 - 1
src/main/frontend/db/async.cljs

@@ -210,7 +210,7 @@
                              future-day
                              db-model/block-attrs)]
             (->> result
-                 db-model/sort-by-left-recursive
+                 db-model/sort-by-order-recursive
                  db-utils/group-by-page)))))))
 
 (defn <get-tag-pages

+ 1 - 21
src/main/frontend/db/debug.cljs

@@ -1,38 +1,18 @@
 (ns ^:no-doc frontend.db.debug
   (:require [frontend.db.utils :as db-utils]
             [frontend.db :as db]
-            [datascript.core :as d]
-            [frontend.util :as util]))
+            [datascript.core :as d]))
 
 ;; shortcut for query a block with string ref
 (defn qb
   [string-id]
   (db-utils/pull [:block/uuid (uuid string-id)]))
 
-(defn check-left-id-conflicts
-  []
-  (let [db (db/get-db)
-        blocks (->> (d/datoms db :avet :block/uuid)
-                    (map :v)
-                    (map (fn [id]
-                           (let [e (db-utils/entity [:block/uuid id])]
-                             (if (:block/name e)
-                               nil
-                               {:block/left (:db/id (:block/left e))
-                                :block/parent (:db/id (:block/parent e))}))))
-                    (remove nil?))
-        count-1 (count blocks)
-        count-2 (count (distinct blocks))]
-    (assert (= count-1 count-2) (util/format "Blocks count: %d, repeated blocks count: %d"
-                                             count-1
-                                             (- count-1 count-2)))))
-
 (defn block-uuid-nil?
   [block]
   (->>
    (concat
     [(:block/parent block)
-     (:block/left block)
      (:block/page block)]
     (:block/tags block)
     (:block/alias block)

+ 11 - 17
src/main/frontend/db/model.cljs

@@ -248,7 +248,7 @@ independent of format as format specific heading characters are stripped"
                 blocks)]
     blocks))
 
-(def sort-by-left ldb/sort-by-left)
+(def sort-by-order ldb/sort-by-order)
 
 (defn sub-block
   [id]
@@ -262,7 +262,7 @@ independent of format as format specific heading characters are stripped"
      react
      first)))
 
-(defn sort-by-left-recursive
+(defn sort-by-order-recursive
   [form]
   (walk/postwalk (fn [f]
                    (if (and (map? f)
@@ -270,7 +270,7 @@ independent of format as format specific heading characters are stripped"
                      (let [children (:block/_parent f)]
                        (-> f
                            (dissoc :block/_parent)
-                           (assoc :block/children (sort-by-left children f))))
+                           (assoc :block/children (sort-by-order children))))
                      f))
                  form))
 
@@ -279,19 +279,19 @@ independent of format as format specific heading characters are stripped"
 (defn get-sorted-page-block-ids-and-levels
   "page-name: the page name, original name
    return: a list with elements in:
-       :id    - a list of block ids, sorted by :block/left
+       :id    - a list of block ids, sorted by :block/order
        :level - the level of the block, 1 for root, 2 for children of root, etc."
   [page-name]
   {:pre [(string? page-name)]}
   (let [root (ldb/get-page (conn/get-db) page-name)]
     (loop [result []
-           children (sort-by-left (:block/_parent root) root)
+           children (sort-by-order (:block/_parent root))
            ;; BFS log of walking depth
            levels (repeat (count children) 1)]
       (if (seq children)
         (let [child (first children)
               cur-level (first levels)
-              next-children (sort-by-left (:block/_parent child) child)]
+              next-children (sort-by-order (:block/_parent child))]
           (recur (conj result {:id (:db/id child) :level cur-level})
                  (concat
                   next-children
@@ -307,8 +307,6 @@ independent of format as format specific heading characters are stripped"
   ([db block-id]
    (ldb/has-children? db block-id)))
 
-(def get-by-parent-&-left ldb/get-by-parent-&-left)
-
 (defn top-block?
   [block]
   (= (:db/id (:block/parent block))
@@ -346,9 +344,7 @@ independent of format as format specific heading characters are stripped"
           (recur e)))
       nil)))
 
-(def get-prev-sibling ldb/get-prev-sibling)
-
-(def get-right-sibling ldb/get-right-sibling)
+(def page? ldb/page?)
 
 (defn get-next
   "Get next block, either its right sibling, or loop to find its next block."
@@ -358,19 +354,17 @@ independent of format as format specific heading characters are stripped"
                :as opts}]
   (when-let [entity (db-utils/entity db db-id)]
     (or (when-not (and (:block/collapsed? entity) skip-collapsed? init?)
-          (get-right-sibling db db-id))
+          (ldb/get-right-sibling (d/entity db db-id)))
         (let [parent-id (:db/id (:block/parent (db-utils/entity db db-id)))]
           (get-next db parent-id (assoc opts :init? false))))))
 
-(def page? ldb/page?)
-
 (defn get-prev
   "Get prev block, either its left sibling if the sibling is collapsed or no children,
   or get sibling's last deep displayable child (collaspsed parent or non-collapsed child)."
   [db db-id]
   (when-let [entity (db-utils/entity db db-id)]
     (or
-     (when-let [prev-sibling (get-prev-sibling db db-id)]
+     (when-let [prev-sibling (ldb/get-left-sibling (d/entity db db-id))]
        (if (or (:block/collapsed? prev-sibling)
                (empty? (:block/_parent prev-sibling)))
          prev-sibling
@@ -653,7 +647,7 @@ independent of format as format specific heading characters are stripped"
                                                          (:block/_path-refs (db-utils/entity id))) pages)
                                       blocks (map (fn [e]
                                                     {:block/parent (:block/parent e)
-                                                     :block/left (:block/left e)
+                                                     :block/order (:block/order e)
                                                      :block/page (:block/page e)
                                                      :block/collapsed? (:block/collapsed? e)}) entities)]
                                   {:entities entities
@@ -683,7 +677,7 @@ independent of format as format specific heading characters are stripped"
                                         block-id
                                         block-attrs)
                                react
-                               (sort-by-left-recursive))]
+                               (sort-by-order-recursive))]
          (db-utils/group-by-page query-result))))))
 
 (defn journal-page?

+ 4 - 4
src/main/frontend/db_worker.cljs

@@ -306,7 +306,7 @@
            eid (when (and (vector? id) (= :block/name (first id)))
                  (:db/id (ldb/get-page @conn (second id))))
            result (->> (d/pull @conn selector (or eid id))
-                       (sqlite-common-db/with-parent-and-left @conn))]
+                       (sqlite-common-db/with-parent @conn))]
        (ldb/write-transit-str result))))
 
   (pull-many
@@ -320,7 +320,7 @@
   (get-right-sibling
    [_this repo db-id]
    (when-let [conn (worker-state/get-datascript-conn repo)]
-     (let [result (ldb/get-right-sibling @conn db-id)]
+     (let [result (ldb/get-right-sibling (d/entity @conn db-id))]
        (ldb/write-transit-str result))))
 
   (get-block-and-children
@@ -369,9 +369,9 @@
                         (concat tx-data
                                 (db-fix/fix-cardinality-many->one @conn (:property-id tx-meta)))
                         tx-data)
-             tx-data' (if (= :new-property (:outliner-op tx-meta))
+             tx-data' (if (contains? #{:new-property :insert-blocks} (:outliner-op tx-meta))
                         (map (fn [m]
-                               (if (and (map? m))
+                               (if (and (map? m) (nil? (:block/order m)))
                                  (assoc m :block/order (db-order/gen-key nil))
                                  m)) tx-data')
                         tx-data')

+ 9 - 7
src/main/frontend/handler/block.cljs

@@ -4,6 +4,7 @@
    [clojure.string :as string]
    [clojure.walk :as walk]
    [frontend.db :as db]
+   [logseq.db :as ldb]
    [frontend.db.model :as db-model]
    [frontend.mobile.haptics :as haptics]
    [logseq.outliner.core :as outliner-core]
@@ -53,9 +54,10 @@
   (get-timestamp block "Deadline"))
 
 (defn indentable?
-  [{:block/keys [parent left]}]
+  [{:block/keys [parent] :as block}]
   (when parent
-    (not= parent left)))
+    (not= (:db/id (ldb/get-first-child (db/get-db) (:db/id parent)))
+          (:db/id block))))
 
 (defn outdentable?
   [{:block/keys [level] :as _block}]
@@ -118,17 +120,17 @@
                           (let [properties (:block/properties block)
                                 type (pu/lookup properties :logseq.property/order-list-type)]
                             (= type order-list-type)))
-        prev-block-fn   #(some->> (:db/id %) (db-model/get-prev-sibling (db/get-db)))
+        prev-block-fn   #(some-> (db/entity (:db/id %)) ldb/get-left-sibling)
         prev-block      (prev-block-fn block)]
     (letfn [(page-fn? [b] (some-> b :block/name some?))
             (order-sibling-list [b]
               (lazy-seq
-                (when (and (not (page-fn? b)) (order-block-fn? b))
-                  (cons b (order-sibling-list (prev-block-fn b))))))
+               (when (and (not (page-fn? b)) (order-block-fn? b))
+                 (cons b (order-sibling-list (prev-block-fn b))))))
             (order-parent-list [b]
               (lazy-seq
-                (when (and (not (page-fn? b)) (order-block-fn? b))
-                  (cons b (order-parent-list (db-model/get-block-parent (:block/uuid b)))))))]
+               (when (and (not (page-fn? b)) (order-block-fn? b))
+                 (cons b (order-parent-list (db-model/get-block-parent (:block/uuid b)))))))]
       (let [idx           (if prev-block
                             (count (order-sibling-list block)) 1)
             order-parents-count (dec (count (order-parent-list block)))

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

@@ -71,7 +71,7 @@
     (assoc block :block/content content')))
 
 (defn wrap-parse-block
-  [{:block/keys [content left level] :as block}]
+  [{:block/keys [content level] :as block}]
   (let [block (or (and (:db/id block) (db/pull (:db/id block))) block)
         block (if (nil? content)
                 block
@@ -86,7 +86,6 @@
                                (update block' :block/properties (fn [m] (merge m (:block/properties block))))
                                block')]
                   (update block' :block/refs remove-non-existed-refs!)))
-        block (if (and left (not= (:block/left block) left)) (assoc block :block/left left) block)
         result (-> block
                    (merge (if level {:block/level level} {}))
                    (replace-page-refs-with-ids))]

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

@@ -368,7 +368,7 @@
                                                  (outliner-core/delete-block repo
                                                                              (db/get-db false)
                                                                              txs-state
-                                                                             (outliner-core/->Block property-block)
+                                                                             property-block
                                                                              {:children? true})
                                                  @txs-state))]
                        (concat
@@ -488,8 +488,7 @@
                     :block/content ""
                     :block/page page-id
                     :block/parent page-id
-                    :block/left (or (when page-entity (model/get-block-last-direct-child-id (db/get-db) (:db/id page-entity)))
-                                    page-id)
+                    :block/order nil
                     :logseq.property/created-from-block (:db/id block)
                     :logseq.property/created-from-property (:db/id property)}
                    sqlite-util/block-with-timestamps)
@@ -498,8 +497,7 @@
                      :block/format :markdown
                      :block/content value
                      :block/page page-id
-                     :block/parent [:block/uuid parent-id]
-                     :block/left [:block/uuid parent-id]}
+                     :block/parent [:block/uuid parent-id]}
                     sqlite-util/block-with-timestamps
                     parse-block)]
     {:page page-tx
@@ -542,8 +540,6 @@
                        :block/tags #{(:db/id template)}
                        :block/page page-id
                        :block/parent page-id
-                       :block/left (or (when page-entity (model/get-block-last-direct-child-id (db/get-db) (:db/id page-entity)))
-                                       page-id)
                        :logseq.property/created-from-block [:block/uuid (:block/uuid block)]
                        :logseq.property/created-from-property (:db/id property)
                        :logseq.property/created-from-template [:block/uuid (:block/uuid template)]}

+ 10 - 12
src/main/frontend/handler/dnd.cljs

@@ -2,8 +2,7 @@
   "Provides fns for drag and drop"
   (:require [frontend.handler.editor :as editor-handler]
             [frontend.handler.property :as property-handler]
-            [logseq.outliner.tree :as otree]
-            [logseq.outliner.core :as outliner-core]
+            [logseq.db :as ldb]
             [frontend.modules.outliner.ui :as ui-outliner-tx]
             [frontend.modules.outliner.op :as outliner-op]
             [logseq.common.util.block-ref :as block-ref]
@@ -13,7 +12,8 @@
 
 (defn move-blocks
   [^js event blocks target-block original-block move-to]
-  (let [blocks' (map #(db/pull (:db/id %)) blocks)
+  (let [target-block (db/entity (:db/id target-block))
+        blocks' (map #(db/entity (:db/id %)) blocks)
         first-block (first blocks')
         top? (= move-to :top)
         nested? (= move-to :nested)
@@ -41,21 +41,19 @@
                           :clear? true}])
 
       (every? map? (conj blocks' target-block))
-      (let [target-node (outliner-core/block (db/get-db) target-block)
-            conn (db/get-db false)
-            blocks' (block-handler/get-top-level-blocks blocks')]
+      (let [blocks' (block-handler/get-top-level-blocks blocks')]
         (ui-outliner-tx/transact!
          {:outliner-op :move-blocks}
          (editor-handler/save-current-block!)
          (if top?
            (let [first-child?
-                 (= (otree/-get-parent-id target-node conn)
-                    (otree/-get-left-id target-node conn))]
+                 (= (:block/uuid (:block/parent target-block))
+                    (:block/uuid (ldb/get-left-sibling target-block)))]
              (if first-child?
-               (when-let [parent (otree/-get-parent target-node conn)]
-                 (outliner-op/move-blocks! blocks' (:data parent) false))
-               (when-let [before-node (otree/-get-left target-node conn)]
-                 (outliner-op/move-blocks! blocks' (:data before-node) true))))
+               (when-let [parent (:block/parent target-block)]
+                 (outliner-op/move-blocks! blocks' parent false))
+               (when-let [before-node (ldb/get-left-sibling target-block)]
+                 (outliner-op/move-blocks! blocks' before-node true))))
            (outliner-op/move-blocks! blocks' target-block (not nested?)))))
 
       :else

+ 62 - 84
src/main/frontend/handler/editor.cljs

@@ -34,7 +34,6 @@
             [frontend.modules.outliner.op :as outliner-op]
             [frontend.modules.outliner.ui :as ui-outliner-tx]
             [frontend.modules.outliner.tree :as tree]
-            [logseq.outliner.tree :as otree]
             [frontend.search :as search]
             [frontend.state :as state]
             [frontend.template :as template]
@@ -369,10 +368,10 @@
         input-text-selected? (util/input-text-selected? input)
         new-m {:block/uuid (db/new-block-id)
                :block/content ""}
-        prev-block (-> (merge (select-keys block [:block/parent :block/left :block/format :block/page])
+        prev-block (-> (merge (select-keys block [:block/parent :block/format :block/page])
                               new-m)
                        (wrap-parse-block))
-        left-block (db/pull (:db/id (:block/left block)))]
+        left-block (ldb/get-left-sibling (db/entity (:db/id block)))]
     (when input-text-selected?
       (let [selection-start (util/get-selection-start input)
             selection-end (util/get-selection-end input)
@@ -397,7 +396,7 @@
         current-block (apply dissoc current-block db-schema/retract-attributes)
         new-m {:block/uuid (db/new-block-id)
                :block/content snd-block-text}
-        next-block (-> (merge (select-keys block [:block/parent :block/left :block/format :block/page])
+        next-block (-> (merge (select-keys block [:block/parent :block/format :block/page])
                               new-m)
                        (wrap-parse-block))
         sibling? (when block-self? false)]
@@ -422,7 +421,7 @@
          :sidebar? sidebar?
          :format format
          :id id
-         :block (or (db/pull [:block/uuid (:block/uuid block)]) block)
+         :block (or (db/entity [:block/uuid (:block/uuid block)]) block)
          :block-id block-id
          :block-parent-id block-parent-id
          :node node
@@ -447,7 +446,7 @@
              selection-end (util/get-selection-end input)
              [fst-block-text snd-block-text] (compute-fst-snd-block-text value selection-start selection-end)
              insert-above? (and (string/blank? fst-block-text) (not (string/blank? snd-block-text)))
-             block' (or (db/pull [:block/uuid block-id]) block)
+             block' (or (db/entity [:block/uuid block-id]) block)
              original-block (:original-block config)
              block'' (or
                       (when original-block
@@ -490,10 +489,10 @@
       (when block
         (let [last-block (when (not sibling?)
                            (let [children (:block/_parent block)
-                                 blocks (db/sort-by-left children block)
+                                 blocks (db/sort-by-order children)
                                  last-block-id (:db/id (last blocks))]
                              (when last-block-id
-                               (db/pull last-block-id))))
+                               (db/entity last-block-id))))
               format (or
                       (:block/format block)
                       (db/get-page-format (:db/id block))
@@ -514,23 +513,20 @@
               new-block (merge new-block other-attrs)
               [block-m sibling?] (cond
                                    before?
-                                   (let [first-child? (->> [:block/parent :block/left]
-                                                           (map #(:db/id (get block %)))
-                                                           (apply =))
-                                         block (db/pull (:db/id (:block/left block)))
-                                         sibling? (if (or first-child? ;; insert as first child
-                                                          (:block/name block))
+                                   (let [left-or-parent (or (ldb/get-left-sibling block)
+                                                            (:block/parent block))
+                                         sibling? (if (= (:db/id (:block/parent block)) (:db/id left-or-parent))
                                                     false sibling?)]
-                                     [block sibling?])
+                                     [left-or-parent sibling?])
 
                                    sibling?
-                                   [(db/pull (:db/id block)) sibling?]
+                                   [(db/entity (:db/id block)) sibling?]
 
                                    last-block
                                    [last-block true]
 
                                    block
-                                   [(db/pull (:db/id block)) sibling?]
+                                   [(db/entity (:db/id block)) sibling?]
 
                                    ;; FIXME: assert
                                    :else
@@ -701,9 +697,8 @@
      (save-block-if-changed! block new-content))))
 
 (defn delete-block-aux!
-  [{:block/keys [uuid repo] :as _block}]
-  (let [repo (or repo (state/get-current-repo))
-        block (db/pull repo '[*] [:block/uuid uuid])]
+  [{:block/keys [uuid] :as _block}]
+  (let [block (db/entity [:block/uuid uuid])]
     (when block
       (state/set-state! :editor/deleting-block uuid)
       (let [blocks (block-handler/get-top-level-blocks [block])]
@@ -742,8 +737,8 @@
                        0)
                      0)
                 [edit-target container-id] (if edit-block-has-refs?
-                                             [(db/pull (:db/id edit-block)) (:editor/container-id @state/state)]
-                                             [(db/pull (:db/id block)) (some-> (dom/attr sibling-block "containerid")
+                                             [(db/entity (:db/id edit-block)) (:editor/container-id @state/state)]
+                                             [(db/entity (:db/id block)) (some-> (dom/attr sibling-block "containerid")
                                                                                util/safe-parse-int)])]
             (edit-block! edit-target
                          pos
@@ -776,10 +771,10 @@
 
           :else
           (let [has-children? (seq (:block/_parent block-e))
-                block (db/pull (:db/id block-e))
-                left (otree/-get-left (outliner-core/block (db/get-db) block) (db/get-db false))
+                block (db/entity (:db/id block-e))
+                left (ldb/get-left-sibling block)
                 left-has-children? (and left
-                                        (when-let [block-id (:block/uuid (:data left))]
+                                        (when-let [block-id (:block/uuid left)]
                                           (let [block (db/entity [:block/uuid block-id])]
                                             (seq (:block/_parent block)))))]
             (when-not (and has-children? left-has-children?)
@@ -793,8 +788,7 @@
                       {:keys [prev-block new-content]} (move-to-prev-block repo sibling-block format id value)
                       concat-prev-block? (boolean (and prev-block new-content))
                       transact-opts {:outliner-op :delete-blocks}
-                      db-based? (config/db-based-graph? repo)
-                      db (db/get-db repo)]
+                      db-based? (config/db-based-graph? repo)]
                   (ui-outliner-tx/transact!
                    transact-opts
                    (cond
@@ -814,32 +808,16 @@
                            (delete-block-aux! prev-block)
                            (save-block! repo block new-content {})
                            (outliner-save-block! {:db/id (:db/id block)
-                                                  :block/parent (:db/id (:block/parent prev-block))
-                                                  :block/left (or (:db/id (:block/left prev-block))
-                                                                  (:db/id (:block/parent prev-block)))})
-
-                             ;; block->right needs to point its `left` to block->left
-                           (let [block-right (outliner-core/get-right-sibling db (:db/id block))]
-                             (when (and block-right (not= (:db/id (:block/parent prev-block))
-                                                          (:db/id (:block/parent block))))
-                               (outliner-save-block! {:db/id (:db/id block-right)
-                                                      :block/left (:db/id (:block/left block))})))
-
-                             ;; update prev-block's children to point to the refed block
+                                                  :block/parent (:db/id (:block/parent prev-block))})
+
+                           ;; update prev-block's children to point to the refed block
                            (when (or (:block/collapsed? prev-block)
                                      (= (:db/id prev-block) (:db/id (:block/parent block))))
                              (let [children (:block/_parent prev-block)]
                                (doseq [child children]
                                  (when-not (= (:db/id child) (:db/id block))
                                    (outliner-save-block! {:db/id (:db/id child)
-                                                          :block/parent (:db/id block)
-                                                          :block/left (:db/id block)})))))
-
-                             ;; parent will be removed
-                           (when (= (:db/id prev-block) (:db/id (:block/parent block)))
-                             (when-let [parent-right (when prev-block (outliner-core/get-right-sibling db (:db/id prev-block)))]
-                               (outliner-save-block! {:db/id (:db/id parent-right)
-                                                      :block/left (:db/id block)})))
+                                                          :block/parent (:db/id block)})))))
 
                            (when db-based?
                              (outliner-save-block! {:db/id (:db/id block)
@@ -911,7 +889,7 @@
   (let [key (string/lower-case (str key))
         block-id (if (string? block-id) (uuid block-id) block-id)
         value (str value)]
-    (when-let [block (db/pull [:block/uuid block-id])]
+    (when-let [block (db/entity [:block/uuid block-id])]
       (let [{:block/keys [content]} block
             content (or content (state/get-edit-content))
             new-content (-> (text-util/remove-timestamp content key)
@@ -1020,7 +998,7 @@
                                          (inc (- level @root-level)))
                                        level)})
                            blocks)
-          block (db/pull [:block/uuid (:id first-block)])
+          block (db/entity [:block/uuid (:id first-block)])
           copy-str (some->> adjusted-blocks
                             (map (fn [{:keys [id level]}]
                                    (condp = (:block/format block)
@@ -1205,7 +1183,7 @@
 
 (defn cut-block!
   [block-id]
-  (when-let [block (db/pull [:block/uuid block-id])]
+  (when-let [block (db/entity [:block/uuid block-id])]
     (let [repo (state/get-current-repo)
           ;; TODO: support org mode
           [_top-level-block-uuids md-content] (compose-copied-blocks-contents repo [block-id])
@@ -1332,7 +1310,7 @@
          (let [input-id (state/get-edit-input-id)
                block (state/get-edit-block)
                db-block (when-let [block-id (:block/uuid block)]
-                          (db/pull [:block/uuid block-id]))
+                          (db/entity [:block/uuid block-id]))
                elem (and input-id (gdom/getElement input-id))
                db-content (:block/content db-block)
                db-content-without-heading (and db-content
@@ -1734,7 +1712,7 @@
                            (.scrollIntoView block-node #js {:behavior "smooth" :block "nearest"}))
                          result))]
       (if edit-block-id
-        (when-let [block (db/pull [:block/uuid edit-block-id])]
+        (when-let [block (db/entity [:block/uuid edit-block-id])]
           (let [blocks [(assoc block :block/content (state/get-edit-content))]
                 pos (state/get-edit-pos)]
             (p/do!
@@ -1751,7 +1729,7 @@
         (let [ids (state/get-selection-block-ids)]
           (when (seq ids)
             (let [lookup-refs (map (fn [id] [:block/uuid id]) ids)
-                  blocks (db/pull-many (state/get-current-repo) '[*] lookup-refs)]
+                  blocks (map db/entity lookup-refs)]
               (move-nodes blocks))))))))
 
 (defn get-selected-ordered-blocks
@@ -1833,7 +1811,7 @@
   (let [new-meta (merge metadata size)
         image-part (first (string/split full_text #"\{"))
         new-full-text (str image-part (pr-str new-meta))
-        block (db/pull [:block/uuid block-id])
+        block (db/entity [:block/uuid block-id])
         value (:block/content block)
         new-value (string/replace value full_text new-full-text)]
     (save-block-aux! block new-value {})))
@@ -2009,7 +1987,7 @@
    (fn []
      (when-let [last-block (last (:blocks result))]
        (clear-when-saved!)
-       (let [last-block' (db/pull [:block/uuid (:block/uuid last-block)])]
+       (let [last-block' (db/entity [:block/uuid (:block/uuid last-block)])]
          (edit-block! last-block' :max))))))
 
 (defn- nested-blocks
@@ -2030,10 +2008,10 @@
            :or {exclude-properties []}}]
   (state/set-editor-op! :paste-blocks)
   (let [editing-block (when-let [editing-block (state/get-edit-block)]
-                        (some-> (db/pull [:block/uuid (:block/uuid editing-block)])
+                        (some-> (db/entity [:block/uuid (:block/uuid editing-block)])
                                 (assoc :block/content (state/get-edit-content))))
         has-unsaved-edits (and editing-block
-                               (not= (:block/content (db/pull (:db/id editing-block)))
+                               (not= (:block/content (db/entity (:db/id editing-block)))
                                      (state/get-edit-content)))
         target-block (or target-block editing-block)
         block (db/entity (:db/id target-block))
@@ -2048,7 +2026,7 @@
                                        (and target-block-has-children? (= (count blocks) 1)))
                                    (block-has-no-ref? (:db/id target-block)))
         target-block' (if (and empty-target? target-block-has-children? paste-nested-blocks?)
-                        (db/pull (:db/id (:block/left target-block)))
+                        (ldb/get-left-sibling target-block)
                         target-block)
         sibling? (cond
                    (and paste-nested-blocks? empty-target?)
@@ -2110,7 +2088,7 @@
         page-id (:db/id (:block/page target-block))
         page-name (some-> page-id (db/entity) :block/name)
         blocks (block-tree->blocks repo tree-vec format keep-uuid? page-name)
-        blocks (gp-block/with-parent-and-left page-id blocks)
+        blocks (gp-block/with-parent-and-order page-id blocks)
         block-refs (->> (mapcat :block/refs blocks)
                         (set)
                         (filter (fn [ref] (and (vector? ref) (= :block/uuid (first ref))))))]
@@ -2123,10 +2101,10 @@
    A block element: {:content :properties :children [block-1, block-2, ...]}"
   [target-block-id sibling? tree-vec format keep-uuid?]
   (insert-block-tree tree-vec format
-    {:target-block       (db/pull target-block-id)
-     :keep-uuid?         keep-uuid?
-     :skip-empty-target? true
-     :sibling?           sibling?}))
+                     {:target-block       (db/entity target-block-id)
+                      :keep-uuid?         keep-uuid?
+                      :skip-empty-target? true
+                      :sibling?           sibling?}))
 
 (defn insert-template!
   ([element-id db-id]
@@ -2149,7 +2127,7 @@
                  block-uuid (:block/uuid block)
                  template-including-parent? (not (false? (:template-including-parent (:block/properties block))))
                  blocks (db/get-block-and-children repo block-uuid)
-                 root-block (db/pull db-id)
+                 root-block (db/entity db-id)
                  blocks-exclude-root (remove (fn [b] (= (:db/id b) db-id)) blocks)
                  sorted-blocks (tree/sort-blocks blocks-exclude-root root-block)
                  sorted-blocks (cons
@@ -2249,10 +2227,10 @@
 (defn outdent-on-enter
   [node]
   (let [original-block (block-handler/get-current-editing-original-block)
-        parent-node (otree/-get-parent node (db/get-db false))
-        target (or original-block (:data parent-node))
+        parent-node (:block/parent node)
+        target (or original-block parent-node)
         pos (state/get-edit-pos)
-        block (:data node)]
+        block node]
     (p/do!
      (ui-outliner-tx/transact!
       {:outliner-op :move-blocks
@@ -2265,12 +2243,12 @@
        (util/schedule #(edit-block! block pos))))))
 
 (defn- last-top-level-child?
-  [{:keys [id]} current-node]
+  [{:keys [id]} block]
   (when id
     (when-let [entity (if-let [id' (parse-uuid (str id))]
                         (db/entity [:block/uuid id'])
                         (db/get-page id))]
-      (= (:block/uuid entity) (otree/-get-parent-id current-node (db/get-db false))))))
+      (= (:block/uuid entity) (:block/uuid (:block/parent block))))))
 
 (defn insert
   ([insertion]
@@ -2468,13 +2446,12 @@
   (when-not (auto-complete?)
     (let [{:keys [block config]} (get-state)]
       (when block
-        (let [input (state/get-input)
+        (let [block (db/entity (:db/id block))
+              input (state/get-input)
               config (assoc config :keydown-new-block true)
               content (gobj/get input "value")
               pos (cursor/pos input)
-              current-node (outliner-core/block (db/get-db) block)
-              has-right? (-> (otree/-get-right current-node (db/get-db false))
-                             (tree/satisfied-inode?))
+              has-right? (ldb/get-right-sibling block)
               db-based? (config/db-based-graph? (state/get-current-repo))
               thing-at-point ;intern is not supported in cljs, need a more elegant solution
               (or (when (thingatpt/get-setting :admonition&src?)
@@ -2522,8 +2499,8 @@
             (and
              (string/blank? content)
              (not has-right?)
-             (not (last-top-level-child? config current-node)))
-            (outdent-on-enter current-node)
+             (not (last-top-level-child? config block)))
+            (outdent-on-enter block)
 
             :else
             (profile
@@ -2614,7 +2591,7 @@
                         (string/trim value))
               (save-block! repo uuid value))
             (let [new-uuid (cljs.core/uuid sibling-block-id)
-                  block (db/pull repo '[*] [:block/uuid new-uuid])]
+                  block (db/entity [:block/uuid new-uuid])]
               (edit-block! block
                            (or (:pos move-opts)
                                [direction line-pos])
@@ -2662,7 +2639,7 @@
           (when (and value (not= (clean-content! repo format content) (string/trim value)))
             (save-block! repo uuid value)))
         (let [container-id (some-> (dom/attr sibling-block "containerid") js/parseInt)
-              block (db/pull repo '[*] [:block/uuid (cljs.core/uuid sibling-block-id)])]
+              block (db/entity repo [:block/uuid (cljs.core/uuid sibling-block-id)])]
           (edit-block! block pos {:container-id container-id}))))))
 
 (defn keydown-arrow-handler
@@ -2700,7 +2677,7 @@
                        (let [db (db/get-db repo)]
                          (when-let [e (or
                                        ;; first child or next sibling
-                                       (db-model/get-by-parent-&-left db (:db/id current-block) (:db/id current-block))
+                                       (ldb/get-first-child db (:db/id current-block))
                                        (db-model/get-next db (:db/id current-block)))]
                          (db/entity (:db/id e)))))]
     (cond
@@ -2753,7 +2730,8 @@
         selected-end (util/get-selection-end input)
         block (state/get-edit-block)
         repo (state/get-current-repo)
-        top-block? (= (:block/left block) (:block/page block))
+        top-block? (= (:db/id (ldb/get-left-sibling (db/entity (:db/id block))))
+                      (:db/id (:block/page block)))
         single-block? (inside-of-single-block (.-target e))
         root-block? (= (:block.temp/container block) (str (:block/uuid block)))]
     (block-handler/mark-last-input-time! repo)
@@ -2780,8 +2758,8 @@
             (delete-block! repo))))
 
       (and (> current-pos 0)
-        (contains? #{commands/command-trigger commands/angle-bracket commands/command-ask}
-          (util/nth-safe value (dec current-pos))))
+           (contains? #{commands/command-trigger commands/angle-bracket commands/command-ask}
+                      (util/nth-safe value (dec current-pos))))
       (do
         (util/stop e)
         (commands/restore-state)
@@ -3606,7 +3584,7 @@
       (when-let [block (state/get-edit-block)]
         ;; get-edit-block doesn't track the latest collapsed state, so we need to reload from db.
         (let [block-id (:block/uuid block)
-              block (db/pull [:block/uuid block-id])]
+              block (db/entity [:block/uuid block-id])]
           (if (:block/collapsed? block)
             (expand! e clear-selection?)
             (collapse! e clear-selection?))))
@@ -3618,7 +3596,7 @@
           (when first-block-id
             ;; If multiple blocks are selected, they may not have all the same collapsed state.
             ;; For simplicity, use the first block's state to decide whether to collapse/expand all.
-            (let [first-block (db/pull [:block/uuid first-block-id])]
+            (let [first-block (db/entity [:block/uuid first-block-id])]
               (if (:block/collapsed? first-block)
                 (doseq [block-id block-ids] (expand-block! block-id))
                 (doseq [block-id block-ids] (collapse-block! block-id))))))
@@ -3767,7 +3745,7 @@
   []
   (let [repo (state/get-current-repo)]
     (when-let [{:keys [start end link]} (thingatpt/block-ref-at-point)]
-      (when-let [block (db/pull [:block/uuid link])]
+      (when-let [block (db/entity [:block/uuid link])]
         (let [block-content (:block/content block)
               format (or (:block/format block) :markdown)
               block-content-without-prop (-> (property-file/remove-properties-when-file-based repo format block-content)

+ 2 - 1
src/main/frontend/handler/export/html.cljs

@@ -424,7 +424,8 @@
           ;; page
           (common/get-page-content root-block-uuids-or-page-uuid)
           (common/root-block-uuids->content repo root-block-uuids-or-page-uuid))
-        first-block (db/entity [:block/uuid (first root-block-uuids-or-page-uuid)])
+        first-block (and (coll? root-block-uuids-or-page-uuid)
+                          (db/entity [:block/uuid (first root-block-uuids-or-page-uuid)]))
         format (or (:block/format first-block) (state/get-preferred-format))]
     (export-helper content format options)))
 

+ 2 - 1
src/main/frontend/handler/export/text.cljs

@@ -520,7 +520,8 @@
              ;; page
              (common/get-page-content root-block-uuids-or-page-uuid)
              (common/root-block-uuids->content repo root-block-uuids-or-page-uuid))
-           first-block (db/entity [:block/uuid (first root-block-uuids-or-page-uuid)])
+           first-block (and (coll? root-block-uuids-or-page-uuid)
+                          (db/entity [:block/uuid (first root-block-uuids-or-page-uuid)]))
            format (or (:block/format first-block) (state/get-preferred-format))]
        (export-helper content format options))
      (catch :default e

+ 5 - 4
src/main/frontend/handler/file_based/editor.cljs

@@ -17,7 +17,8 @@
             [frontend.handler.file-based.property :as file-property-handler]
             [frontend.handler.file-based.property.util :as property-util]
             [logseq.db.frontend.schema :as db-schema]
-            [logseq.common.util.block-ref :as block-ref]))
+            [logseq.common.util.block-ref :as block-ref]
+            [logseq.db :as ldb]))
 
 (defn- remove-non-existed-refs!
   [refs]
@@ -70,7 +71,7 @@
     value))
 
 (defn wrap-parse-block
-  [{:block/keys [content format left page uuid level pre-block?] :as block
+  [{:block/keys [content format page uuid level pre-block?] :as block
     :or {format :markdown}}]
   (let [repo (state/get-current-repo)
         block (or (and (:db/id block) (db/pull (:db/id block))) block)
@@ -87,7 +88,8 @@
                   content)
         content (drawer/with-logbook block content)
         content (with-timetracking block content)
-        first-block? (= left page)
+        first-block? (= (:block/uuid (ldb/get-first-child (db/get-db) page))
+                        (:block/uuid block))
         ast (mldoc/->edn (string/trim content) format)
         first-elem-type (first (ffirst ast))
         first-elem-meta (second (ffirst ast))
@@ -117,7 +119,6 @@
                 block
                 (dissoc block :block/pre-block?))
         block (update block :block/refs remove-non-existed-refs!)
-        block (if (and left (not= (:block/left block) left)) (assoc block :block/left left) block)
         new-properties (merge
                         (select-keys properties (file-property-handler/hidden-properties))
                         (:block/properties block))]

+ 1 - 2
src/main/frontend/handler/file_based/page_property.cljs

@@ -71,7 +71,6 @@
                 block]]
         (db/transact! tx))
       (let [block {:block/uuid (db/new-block-id)
-                   :block/left page-id
                    :block/parent page-id
                    :block/page page-id
                    :block/content (if org?
@@ -84,4 +83,4 @@
         (ui-outliner-tx/transact!
          {:outliner-op :insert-blocks
           :additional-tx page-properties-tx}
-         (outliner-op/insert-blocks! block page {:sibling? false}))))))
+         (outliner-op/insert-blocks! [block] page {:sibling? false}))))))

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

@@ -103,7 +103,7 @@
          (page-handler/<create! page-name {:redirect? false}))
        (let [page-block (db/get-page page-name)
              children (:block/_parent page-block)
-             blocks (db/sort-by-left children page-block)
+             blocks (db/sort-by-order children)
              last-block (last blocks)
              snd-last-block (last (butlast blocks))
              [target-block sibling?] (if (and last-block (seq (:block/content last-block)))

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

@@ -84,9 +84,7 @@
     (let [repo (state/get-current-repo)]
       (if (config/db-based-graph? repo)
         (when-let [page (ldb/get-page db common-config/favorites-page-name)]
-          (let [blocks (ldb/sort-by-left
-                        (ldb/get-page-blocks db (:db/id page) {})
-                        page)]
+          (let [blocks (ldb/sort-by-order (:block/_parent page))]
             (keep (fn [block]
                     (when-let [block-db-id (:db/id (:block/link block))]
                       (d/entity db block-db-id))) blocks)))
@@ -152,8 +150,7 @@
             (keep (fn [page-uuid]
                     (:db/id (db/get-page page-uuid)))
                   favorites)
-            current-blocks (ldb/sort-by-left (ldb/get-page-blocks @conn (:db/id favorites-page) {})
-                                             favorites-page)]
+            current-blocks (ldb/sort-by-order (ldb/get-page-blocks @conn (:db/id favorites-page) {}))]
         (p/do!
          (ui-outliner-tx/transact!
           {}

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

@@ -27,7 +27,7 @@
                   (mldoc/->edn text format)
                   text format
                   {:page-name (:block/name (db/entity page-id))})
-          blocks' (gp-block/with-parent-and-left page-id blocks)]
+          blocks' (gp-block/with-parent-and-order page-id blocks)]
       (editor-handler/paste-blocks blocks' {:keep-uuid? true}))))
 
 (defn- paste-segmented-text

+ 0 - 2
src/main/frontend/modules/outliner/tree.cljs

@@ -3,8 +3,6 @@
             [frontend.state :as state]
             [logseq.outliner.tree :as otree]))
 
-(def satisfied-inode? otree/satisfied-inode?)
-
 (defn blocks->vec-tree
   "`blocks` need to be in the same page."
   ([blocks root-id]

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

@@ -147,5 +147,5 @@
             result' (when result (ldb/read-transit-str result))]
       (when result' (db/transact! repo result'))
       (some->> result'
-               db-model/sort-by-left-recursive
+               db-model/sort-by-order-recursive
                db-utils/group-by-page))))

+ 2 - 195
src/main/frontend/worker/db/fix.cljs

@@ -1,199 +1,6 @@
 (ns frontend.worker.db.fix
-  "DB validation and fix.
-  For pages:
-  1. Each block should has a unique [:block/parent :block/left] position.
-  2. For any block, its children should be connected by :block/left (no broken chain, no circle, no left to self)."
-  (:require [datascript.core :as d]
-            [cljs.pprint :as pprint]
-            [logseq.db :as ldb]
-            [frontend.worker.util :as worker-util]))
-
-(defn- fix-parent-broken-chain
-  [db parent-id tx-report from-fix-test?]
-  (let [parent (d/entity db parent-id)
-        parent-id (:db/id parent)
-        blocks (:block/_parent parent)]
-    (when (seq blocks)
-      (let [children-ids (set (map :db/id blocks))
-            sorted (ldb/sort-by-left blocks parent)
-            valid-left-ids (set (cons (:db/id parent) (map :db/id blocks)))
-            ;; :block/left points to other blocks that're not current parent or its children
-            invalid-left? (not (every? (fn [b] (contains? valid-left-ids (:db/id (:block/left b)))) blocks))
-            broken-chain? (or (not= (count sorted) (count blocks)) invalid-left?)]
-        (when (and (not from-fix-test?) (exists? js/process) broken-chain?)
-          (throw (ex-info "outliner broken chain" {:type (if invalid-left? :invalid-left :broken-chain)
-                                                   :tx-meta (:tx-meta tx-report)
-                                                   :tx-data (:tx-data tx-report)
-                                                   :db-before (ldb/write-transit-str (:db-before tx-report))})))
-        (when broken-chain?
-          (let [parent-data {:db/id parent-id
-                             :block/uuid (:block/uuid parent)
-                             :block/content (:block/content parent)}
-                error-data (if invalid-left?
-                             {:type :invalid-left
-                              :parent parent-data
-                              :children (remove (fn [b] (contains? valid-left-ids (:db/id (:block/left b)))) blocks)}
-                             {:type :broken-chain
-                              :parent parent-data
-                              :children (mapv (fn [b]
-                                                {:db/id (:db/id b)
-                                                 :block/content (:block/content b)
-                                                 :block/left (:db/id (:block/left b))}) blocks)})]
-            (prn :debug "Broken chain:")
-            (pprint/pprint error-data)
-            (worker-util/post-message :notification
-                                      [[:div
-                                        (str "Broken chain detected:\n" error-data)]
-                                       :error]))
-          (let [first-child-id (:db/id (ldb/get-by-parent-&-left db parent-id parent-id))
-                *ids (atom children-ids)
-                sections (loop [sections []]
-                           (if (seq @*ids)
-                             (let [last-section (last sections)
-                                   current-section (if (seq (last sections))
-                                                     last-section
-                                                     (if (and (empty? sections) first-child-id)
-                                                       (do
-                                                         (swap! *ids disj first-child-id)
-                                                         [first-child-id])
-                                                       (let [id (first @*ids)]
-                                                         (swap! *ids disj id)
-                                                         [id])))
-                                   section-with-left (or
-                                                      (when-let [left-id (:db/id (:block/left (d/entity db (first current-section))))]
-                                                        (swap! *ids disj left-id)
-                                                        (when (and
-                                                               (not (contains? (set current-section) left-id)) ; circle
-                                                               (contains? children-ids left-id))
-                                                          (vec (cons left-id current-section))))
-                                                      current-section)
-                                   section-with-right (or
-                                                       (when-let [right-id (:db/id (ldb/get-right-sibling db (last section-with-left)))]
-                                                         (swap! *ids disj right-id)
-                                                         (when (and (not (contains? (set section-with-left) right-id)) ; circle
-                                                                    (contains? children-ids right-id))
-                                                           (conj section-with-left right-id)))
-                                                       section-with-left)
-                                   new-sections (cond
-                                                  (empty? last-section)
-                                                  (conj (vec (butlast sections)) section-with-right)
-
-                                                  (= section-with-right current-section)
-                                                  (conj sections [])
-
-                                                  :else
-                                                  (conj (vec (butlast sections)) section-with-right))]
-                               (recur new-sections))
-                             sections))]
-            (->>
-             (map-indexed
-              (fn [idx section]
-                (map-indexed
-                 (fn [idx' item]
-                   (let [m {:db/id item}
-                         left (cond
-                                (and (zero? idx) (zero? idx'))
-                                parent-id
-
-                                (and (not (zero? idx)) (zero? idx')) ; first one need to connected to the last section
-                                (last (nth sections (dec idx)))
-
-                                (> idx' 0)
-                                (nth section (dec idx')))]
-                     (assoc m :block/left left)))
-                 section))
-              sections)
-             (apply concat))))))))
-
-(defn- fix-broken-chain
-  [db parent-left->es tx-report from-fix-test?]
-  (let [parents (distinct (map first (keys parent-left->es)))]
-    (mapcat #(fix-parent-broken-chain db % tx-report from-fix-test?) parents)))
-
-(defn- build-parent-left->es
-  [db page-id]
-  (let [parent-left-f (fn [b]
-                        [(get-in b [:block/parent :db/id])
-                         (get-in b [:block/left :db/id])])
-        page (d/entity db page-id)
-        blocks (:block/_page page)]
-    (->> (group-by parent-left-f blocks)
-         (remove (fn [[k _v]] (= k [nil nil])))
-         (into {}))))
-
-(defn- fix-parent-left-conflicts
-  [db conflicts page-id]
-  (when (seq conflicts)
-    (prn :debug "Parent left id conflicts:")
-    (worker-util/post-message :notification [[:div
-                                              (str "Parent-left conflicts detected on page "
-                                                   (:block/original-name (d/entity db page-id))
-                                                   ":\n"
-                                                   conflicts)]
-                                             :error]))
-  (mapcat
-   (fn [[_parent-left blocks]]
-     (let [items (sort-by :block/created-at blocks)
-           [first-item & others] items
-           tx (map-indexed
-               (fn [idx other]
-                 {:db/id (:db/id other)
-                  :block/left (:db/id (nth items (if (zero? idx) idx (dec idx))))
-                  :block/parent (:db/id (:block/parent first-item))})
-               others)
-           right-tx (when-let [right (ldb/get-right-sibling db (:db/id first-item))]
-                      [{:db/id (:db/id right)
-                        :block/left (:db/id (last items))}])]
-       (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
-  [conn page-id transact-opts *fix-tx-data tx-report from-fix-test?]
-  (let [db @conn
-        conflicts (get-conflicts db page-id)
-        _ (when (and (not from-fix-test?) (exists? js/process) (seq conflicts))
-            (throw (ex-info "outliner core conflicts" {:conflicts conflicts
-                                                       :db-before (ldb/write-transit-str (:db-before tx-report))
-                                                       :tx-data (:tx-data tx-report)
-                                                       :tx-meta (:tx-meta tx-report)})))
-        fix-conflicts-tx (when (seq conflicts)
-                           (fix-parent-left-conflicts db conflicts page-id))]
-    (when (seq fix-conflicts-tx)
-      (prn :debug :conflicts-tx)
-      (pprint/pprint fix-conflicts-tx)
-      (let [tx-data (:tx-data (ldb/transact! conn fix-conflicts-tx transact-opts))]
-        (swap! *fix-tx-data (fn [old-data] (concat old-data tx-data))))
-      (when (seq (get-conflicts @conn page-id))
-        (loop-fix-conflicts conn page-id transact-opts *fix-tx-data tx-report from-fix-test?)))))
-
-(defn fix-page-if-broken!
-  "Fix the page if it has either parent-left conflicts or broken chains."
-  [conn page-id {:keys [fix-parent-left? fix-broken-chain? replace-tx? tx-report from-fix-test?]
-                 :or {fix-parent-left? true
-                      fix-broken-chain? true
-                      replace-tx? false}
-                 :as _opts}]
-  (let [db @conn
-        page (d/entity db page-id)]
-    (when-not (or (ldb/whiteboard-page? page)
-                  (ldb/hidden-page? page))
-      (let [transact-opts (if replace-tx? {:pipeline-replace? true} {})
-            *fix-tx-data (atom [])]
-        (when fix-parent-left?
-          (loop-fix-conflicts conn page-id transact-opts *fix-tx-data tx-report from-fix-test?))
-        (when fix-broken-chain?
-          (let [db' @conn
-                parent-left->es' (build-parent-left->es db page-id)
-                fix-broken-chain-tx (fix-broken-chain db' parent-left->es' tx-report from-fix-test?)]
-            (when (seq fix-broken-chain-tx)
-              (let [tx-data (:tx-data (ldb/transact! conn fix-broken-chain-tx transact-opts))]
-                (swap! *fix-tx-data (fn [old-data] (concat old-data tx-data)))))))
-        @*fix-tx-data))))
+  "Fix db"
+  (:require [datascript.core :as d]))
 
 (defn fix-cardinality-many->one
   [db property-id]

+ 2 - 2
src/main/frontend/worker/file.cljs

@@ -35,13 +35,13 @@
 (def whiteboard-blocks-pull-keys-with-persisted-ids
   '[:block/properties
     :block/uuid
+    :block/order
     :block/content
     :block/format
     :block/created-at
     :block/updated-at
     :block/collapsed?
     {:block/page      [:block/uuid]}
-    {:block/left      [:block/uuid]}
     {:block/parent    [:block/uuid]}])
 
 (defn- cleanup-whiteboard-block
@@ -53,7 +53,7 @@
             :block/collapsed?
             :block/content
             :block/format
-            :block/left
+            :block/order
             :block/page
             :block/parent) ;; these are auto-generated for whiteboard shapes
     (dissoc block :db/id :block/page)))

+ 4 - 3
src/main/frontend/worker/file/core.cljs

@@ -48,15 +48,16 @@
     content))
 
 (defn transform-content
-  [repo db {:block/keys [collapsed? format pre-block? content left page parent properties] :as b} level {:keys [heading-to-list?]} context]
+  [repo db {:block/keys [collapsed? format pre-block? content page properties] :as b} level {:keys [heading-to-list?]} context]
   (let [block-ref-not-saved? (and (seq (:block/_refs (d/entity db (:db/id b))))
                                   (not (string/includes? content (str (:block/uuid b))))
                                   (not (sqlite-util/db-based-graph? repo)))
         heading (:heading properties)
         markdown? (= :markdown format)
         content (or content "")
+        page-first-child? (= (:db/id b) (ldb/get-first-child db (:db/id page)))
         pre-block? (or pre-block?
-                       (and (= page parent left) ; first block
+                       (and page-first-child?
                             markdown?
                             (string/includes? (first (string/split-lines content)) ":: ")))
         content (cond
@@ -67,7 +68,7 @@
                   :else
                   (let [;; first block is a heading, Markdown users prefer to remove the `-` before the content
                         markdown-top-heading? (and markdown?
-                                                   (= parent page left)
+                                                   page-first-child?
                                                    heading)
                         [prefix spaces-tabs]
                         (cond

+ 4 - 3
src/main/frontend/worker/handler/page.cljs

@@ -12,7 +12,8 @@
             [logseq.common.config :as common-config]
             [logseq.db.frontend.content :as db-content]
             [medley.core :as medley]
-            [frontend.worker.date :as date]))
+            [frontend.worker.date :as date]
+            [logseq.db.frontend.order :as db-order]))
 
 (defn properties-block
   [repo conn config date-formatter properties format page]
@@ -23,7 +24,7 @@
      :block/properties properties
      :block/properties-order (keys properties)
      :block/refs refs
-     :block/left page
+     :block/order (db-order/gen-key nil nil)
      :block/format format
      :block/content content
      :block/parent page
@@ -130,7 +131,7 @@
                                                         {:block/uuid (ldb/new-block-id)
                                                          :block/page page-id
                                                          :block/parent page-id
-                                                         :block/left page-id
+                                                         :block/order (db-order/gen-key nil nil)
                                                          :block/content ""
                                                          :block/format format})]))
                                    txs      (concat

+ 3 - 10
src/main/frontend/worker/handler/page/db_based/rename.cljs

@@ -21,24 +21,17 @@
        (let [db @conn
              to-id (:db/id to-page)
              from-id (:db/id from-page)
-             from-first-child (some->> (d/pull db '[*] from-id)
-                                       (outliner-core/block @conn)
-                                       (#(otree/-get-down % conn))
-                                       (outliner-core/get-data))
-             to-last-direct-child-id (ldb/get-block-last-direct-child-id db to-id)
              db-based? (sqlite-util/db-based-graph? repo)
              datoms (d/datoms @conn :avet :block/page from-id)
              block-eids (mapv :e datoms)
-             blocks (d/pull-many db '[:db/id :block/page :block/refs :block/path-refs :block/left :block/parent] block-eids)
+             blocks (d/pull-many db '[:db/id :block/page :block/refs :block/path-refs :block/parent] block-eids)
              blocks-tx-data (map (fn [block]
                                    (let [id (:db/id block)]
                                      (cond->
                                       {:db/id id
                                        :block/page {:db/id to-id}
-                                       :block/refs (rename-update-block-refs! (:block/refs block) from-id to-id)}
-
-                                       (and from-first-child (= id (:db/id from-first-child)))
-                                       (assoc :block/left {:db/id (or to-last-direct-child-id to-id)})
+                                       :block/refs (rename-update-block-refs! (:block/refs block) from-id to-id)
+                                       :block/order (db-order/gen-key nil)}
 
                                        (= (:block/parent block) {:db/id from-id})
                                        (assoc :block/parent {:db/id to-id})))) blocks)

+ 6 - 14
src/main/frontend/worker/handler/page/file_based/rename.cljs

@@ -1,8 +1,6 @@
 (ns frontend.worker.handler.page.file-based.rename
   "File based page rename"
-  (:require [logseq.outliner.core :as outliner-core]
-            [logseq.outliner.tree :as otree]
-            [frontend.worker.handler.page :as worker-page]
+  (:require [frontend.worker.handler.page :as worker-page]
             [datascript.core :as d]
             [clojure.string :as string]
             [logseq.common.util.page-ref :as page-ref]
@@ -11,7 +9,8 @@
             [logseq.db.sqlite.util :as sqlite-util]
             [logseq.db :as ldb]
             [logseq.common.util :as common-util]
-            [logseq.graph-parser.text :as text]))
+            [logseq.graph-parser.text :as text]
+            [logseq.db.frontend.order :as db-order]))
 
 (defn rename-update-namespace!
   "update :block/namespace of the renamed block"
@@ -56,23 +55,16 @@
           to-id (:db/id to-page)
           from-page (d/entity db [:block/name from-page-name])
           from-id (:db/id from-page)
-          from-first-child (some->> (d/pull db '[*] from-id)
-                                    (outliner-core/block @conn)
-                                    (#(otree/-get-down % conn))
-                                    (outliner-core/get-data))
-          to-last-direct-child-id (ldb/get-block-last-direct-child-id db to-id)
           datoms (d/datoms @conn :avet :block/page from-id)
           block-eids (mapv :e datoms)
-          blocks (d/pull-many db '[:db/id :block/page :block/refs :block/path-refs :block/left :block/parent] block-eids)
+          blocks (d/pull-many db '[:db/id :block/page :block/refs :block/path-refs :block/order :block/parent] block-eids)
           blocks-tx-data (map (fn [block]
                                 (let [id (:db/id block)]
                                   (cond->
                                    {:db/id id
                                     :block/page {:db/id to-id}
-                                    :block/refs (rename-update-block-refs! (:block/refs block) from-id to-id)}
-
-                                    (and from-first-child (= id (:db/id from-first-child)))
-                                    (assoc :block/left {:db/id (or to-last-direct-child-id to-id)})
+                                    :block/refs (rename-update-block-refs! (:block/refs block) from-id to-id)
+                                    :block/order (db-order/gen-key nil)}
 
                                     (= (:block/parent block) {:db/id from-id})
                                     (assoc :block/parent {:db/id to-id})))) blocks)

+ 12 - 20
src/main/frontend/worker/pipeline.cljs

@@ -1,7 +1,6 @@
 (ns frontend.worker.pipeline
   "Pipeline work after transaction"
   (:require [datascript.core :as d]
-            [frontend.worker.db.fix :as db-fix]
             [frontend.worker.file :as file]
             [frontend.worker.react :as worker-react]
             [frontend.worker.util :as worker-util]
@@ -49,31 +48,24 @@
                empty-property-parents)
        (remove nil?)))))
 
-(defn fix-db!
-  [conn {:keys [db-before db-after tx-data] :as tx-report} context]
-  (let [changed-pages (->> (filter (fn [d] (contains? #{:block/left :block/parent} (:a d))) tx-data)
-                           (map :e)
-                           distinct
-                           (map (fn [id]
-                                  (-> (or (d/entity db-after id)
-                                          (d/entity db-before id))
-                                      :block/page
-                                      :db/id)))
-                           (remove nil?)
-                           (distinct))]
-    (doseq [changed-page-id changed-pages]
-      (db-fix/fix-page-if-broken! conn changed-page-id {:tx-report tx-report
-                                                        :context context}))))
-
-(defn validate-and-fix-db!
+(defn validate-db!
   [repo conn tx-report context]
   (when (and (:dev? context) (not (:importing? context)) (sqlite-util/db-based-graph? repo))
     (let [valid? (db-validate/validate-tx-report! tx-report (:validate-db-options context))]
       (when (and (get-in context [:validate-db-options :fail-invalid?]) (not valid?))
         (worker-util/post-message :notification
                                   [["Invalid DB!"] :error]))))
+
+  ;; Ensure :block/order is unique for any block that has :block/parent
   (when (or (:dev? context) (exists? js/process))
-    (fix-db! conn tx-report context)))
+    (let [order-datoms (filter (fn [d] (= :block/order (:a d))) (:tx-data tx-report))]
+      (doseq [datom order-datoms]
+        (let [entity (d/entity @conn (:db/id datom))
+              parent (:block/parent entity)]
+          (when parent
+            (let [children (:block/_parent parent)]
+              (assert (= (count (distinct (map :block/order children))) (count children))
+                      (str ":block/order is not unique for children blocks, parent id: " (:db/id parent))))))))))
 
 (defn invoke-hooks
   [repo conn {:keys [tx-meta] :as tx-report} context]
@@ -127,7 +119,7 @@
                           (do
                             (when-not (exists? js/process) (d/store @conn))
                             tx-report))
-              fix-tx-data (validate-and-fix-db! repo conn tx-report context)
+              fix-tx-data (validate-db! repo conn tx-report context)
               full-tx-data (concat (:tx-data tx-report)
                                    fix-tx-data
                                    (:tx-data tx-report'))

+ 1 - 1
src/main/frontend/worker/react.cljs

@@ -29,7 +29,7 @@
   "Get affected queries through transaction datoms."
   [{:keys [tx-data db-after]}]
   {:post [(s/valid? ::affected-keys %)]}
-  (let [blocks (->> (filter (fn [datom] (contains? #{:block/left :block/parent :block/page} (:a datom))) tx-data)
+  (let [blocks (->> (filter (fn [datom] (contains? #{:block/parent :block/page} (:a datom))) tx-data)
                     (map :v)
                     (distinct))
         refs (->> (filter (fn [datom]

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

@@ -484,11 +484,10 @@
 
 (defn- move-all-blocks-to-another-page
   [repo conn from-page-name to-page-name]
-  (let [blocks (ldb/get-page-blocks @conn (:db/id (ldb/get-page @conn from-page-name)) {})
-        from-page-block (some-> (first blocks) :block/page)
+  (let [blocks (:block/_page (ldb/get-page @conn from-page-name))
         target-page-block (ldb/get-page @conn to-page-name)]
     (when (and (seq blocks) target-page-block)
-      (let [blocks* (ldb/sort-by-left blocks from-page-block)]
+      (let [blocks* (ldb/sort-by-order blocks)]
         (outliner-tx/transact!
          {:persist-op? true
           :gen-undo-ops? false

+ 1 - 1
src/main/frontend/worker/rtc/db_listener.cljs

@@ -43,7 +43,7 @@
             {[_e _a block-uuid _t add1?] :block/uuid
              [_e _a _v _t add2?]         :block/name
              [_e _a _v _t add3?]         :block/parent
-             [_e _a _v _t add4?]         :block/left
+             [_e _a _v _t add4?]         :block/order
              [_e _a _v _t add5?]         :block/original-name} attr->datom
             ops (cond
                   (and (not add1?) block-uuid

+ 9 - 10
src/main/logseq/api.cljs

@@ -661,9 +661,7 @@
                                                  (util/format "Custom block UUID already exists (%s)." custom-uuid))))
                 block-uuid'            (if (and (not sibling) before block-uuid)
                                          (let [block       (db/entity [:block/uuid block-uuid])
-                                               first-child (db-model/get-by-parent-&-left (db/get-db)
-                                                                                          (:db/id block)
-                                                                                          (:db/id block))]
+                                               first-child (ldb/get-first-child (db/get-db) (:db/id block))]
                                            (if first-child
                                              (:block/uuid first-child)
                                              block-uuid))
@@ -703,7 +701,7 @@
                                          (throw (js/Error.
                                                  (util/format "Custom block UUID already exists (%s)." uuid)))))))
                 block (if (and before sibling)
-                        (db/pull (:db/id (:block/left block))) block)
+                        (db/pull (:db/id (ldb/get-left-sibling (db/entity (:db/id block))))) block)
                 _ (editor-handler/insert-block-tree-after-target
                    (:db/id block) sibling bb (:block/format block) keep-uuid?)]
             nil))))))
@@ -758,12 +756,13 @@
 (def ^:export get_previous_sibling_block
   (fn [block-uuid]
     (p/let [id (sdk-utils/uuid-or-throw-error block-uuid)
-            block (<pull-block id)]
+            block (<pull-block id)
+            ;; Load all children blocks
+            _ (db-async/<get-block (state/get-current-repo) (:block/uuid (:block/parent block)) {:children? true})]
       (when block
-       (p/let [{:block/keys [parent left]} block
-               block (when-not (= parent left) (<pull-block (:db/id left)))]
-         (when block
-           (bean/->js (sdk-utils/normalize-keyword-for-json block))))))))
+        (when-let [left-sibling (ldb/get-left-sibling (db/entity (:db/id block)))]
+          (let [block (db/pull (:db/id left-sibling))]
+            (bean/->js (sdk-utils/normalize-keyword-for-json block))))))))
 
 (def ^:export get_next_sibling_block
   (fn [block-uuid]
@@ -869,7 +868,7 @@
 (defn last-child-of-block
   [block]
   (when-let [children (:block/_parent block)]
-    (last (db-model/sort-by-left children block))))
+    (last (db-model/sort-by-order children))))
 
 (defn ^:export prepend_block_in_page
   [uuid-or-page-name content ^js opts]

+ 0 - 9
src/test/frontend/core_test.cljs

@@ -1,9 +0,0 @@
-(ns frontend.core-test
-  (:require [frontend.state :as state]
-            [frontend.db.conn :as conn]))
-
-(defn get-current-conn
-  []
-  (->
-    (state/get-current-repo)
-    (conn/get-db false)))

+ 0 - 212
src/test/frontend/db/fix_test.cljs

@@ -1,212 +0,0 @@
-(ns frontend.db.fix-test
-  (:require [cljs.test :refer [deftest is use-fixtures]]
-            [datascript.core :as d]
-            [frontend.core-test :as core-test]
-            [frontend.test.fixtures :as fixtures]
-            [frontend.worker.db.fix :as db-fix]))
-
-(use-fixtures :each fixtures/reset-db)
-
-(defonce init-conflicts
-  [{:block/uuid "1"}
-   {:block/uuid "2"
-    :block/page [:block/uuid "1"]
-    :block/parent [:block/uuid "1"]
-    :block/left [:block/uuid "1"]}
-   {:block/uuid "3"
-    :block/page [:block/uuid "1"]
-    :block/parent [:block/uuid "1"]
-    :block/left [:block/uuid "1"]}])
-
-(defn fix-page-if-broken!
-  [conn page-id opts]
-  (db-fix/fix-page-if-broken! conn page-id (merge opts {:from-fix-test? true})))
-
-(deftest test-conflicts
-  (let [conn (core-test/get-current-conn)
-        _ (d/transact! conn init-conflicts)
-        page-id (:db/id (d/entity @conn 1))
-        _ (fix-page-if-broken! conn page-id {})]
-    (is (= 2 (:db/id (:block/left (d/entity @conn 3)))))))
-
-(deftest test-conflicts-with-right
-  (let [conn (core-test/get-current-conn)
-        data (concat init-conflicts
-                     [{:block/uuid "4"
-                       :block/page [:block/uuid "1"]
-                       :block/parent [:block/uuid "1"]
-                       :block/left [:block/uuid "2"]}])
-        _ (d/transact! conn data)
-        page-id (:db/id (d/entity @conn 1))
-        _ (fix-page-if-broken! conn page-id {})]
-    (is (= 3 (:db/id (:block/left (d/entity @conn 4)))))))
-
-(def init-broken-chain
-  [{:block/uuid "1"}
-   {:block/uuid "2"
-    :block/page [:block/uuid "1"]
-    :block/parent [:block/uuid "1"]
-    :block/left [:block/uuid "1"]}
-   {:block/uuid "3"
-    :block/page [:block/uuid "1"]
-    :block/parent [:block/uuid "1"]
-    :block/left [:block/uuid "2"]}
-   {:block/uuid "4"}
-   {:block/uuid "5"
-    :block/page [:block/uuid "1"]
-    :block/parent [:block/uuid "1"]
-    :block/left [:block/uuid "4"]}])
-
-(deftest test-broken-chain
-  (let [conn (core-test/get-current-conn)
-        data init-broken-chain
-        _ (d/transact! conn data)
-        page-id (:db/id (d/entity @conn 1))
-        _ (fix-page-if-broken! conn page-id {})]
-    (is
-     (=
-      (set [{:db/id 2, :block/left 1}
-            {:db/id 3, :block/left 2}
-            {:db/id 5, :block/left 3}])
-      (set
-       (map (fn [b]
-              {:db/id (:db/id b)
-               :block/left (:db/id (:block/left b))})
-            (:block/_parent (d/entity @conn 1))))))))
-
-(deftest test-broken-chain-with-no-start
-  (let [conn (core-test/get-current-conn)
-        data [{:block/uuid "1"}
-              {:block/uuid "5"}
-              {:block/uuid "2"
-               :block/page [:block/uuid "1"]
-               :block/parent [:block/uuid "1"]
-               :block/left [:block/uuid "5"]}
-              {:block/uuid "3"
-               :block/page [:block/uuid "1"]
-               :block/parent [:block/uuid "1"]
-               :block/left [:block/uuid "2"]}]
-        _ (d/transact! conn data)
-        page-id (:db/id (d/entity @conn 1))
-        _ (fix-page-if-broken! conn page-id {})]
-    (is
-     (=
-      (set [{:db/id 3, :block/left 1}
-            {:db/id 4, :block/left 3}])
-      (set (map (fn [b]
-                  {:db/id (:db/id b)
-                   :block/left (:db/id (:block/left b))})
-                (:block/_parent (d/entity @conn 1))))))))
-
-(deftest test-broken-chain-with-circle
-  (let [conn (core-test/get-current-conn)
-        data [{:block/uuid "1"}
-              {:block/uuid "2"
-               :block/page [:block/uuid "1"]
-               :block/parent [:block/uuid "1"]
-               :block/left [:block/uuid "1"]}
-              {:block/uuid "4"}
-              {:block/uuid "3"
-               :block/page [:block/uuid "1"]
-               :block/parent [:block/uuid "1"]
-               :block/left [:block/uuid "4"]}
-              {:block/uuid "4"
-               :block/page [:block/uuid "1"]
-               :block/parent [:block/uuid "1"]
-               :block/left [:block/uuid "3"]}]
-        _ (d/transact! conn data)
-        page-id (:db/id (d/entity @conn 1))
-        _ (fix-page-if-broken! conn page-id {})]
-    (is
-     (=
-      (set [{:db/id 2, :block/left 1}
-            {:db/id 4, :block/left 2}
-            {:db/id 3, :block/left 4}])
-      (set (map (fn [b]
-                  {:db/id (:db/id b)
-                   :block/left (:db/id (:block/left b))})
-             (:block/_parent (d/entity @conn 1))))))))
-
-(deftest test-broken-chain-with-no-start-and-circle
-  (let [conn (core-test/get-current-conn)
-        data [{:block/uuid "1"
-               :db/id 1}
-              {:block/uuid "5"
-               :db/id 5}
-              {:block/uuid "2"
-               :db/id 2
-               :block/page [:block/uuid "1"]
-               :block/parent [:block/uuid "1"]
-               :block/left [:block/uuid "5"]}
-              {:block/uuid "3"
-               :db/id 3
-               :block/page [:block/uuid "1"]
-               :block/parent [:block/uuid "1"]
-               :block/left [:block/uuid "2"]}
-              {:block/uuid "4"
-               :db/id 4
-               :block/page [:block/uuid "1"]
-               :block/parent [:block/uuid "1"]
-               :block/left [:block/uuid "3"]}
-              {:block/uuid "5"
-               :block/page [:block/uuid "1"]
-               :block/parent [:block/uuid "1"]
-               :block/left [:block/uuid "2"]}]
-        _ (d/transact! conn data)
-        page-id (:db/id (d/entity @conn 1))
-        _ (fix-page-if-broken! conn page-id {})]
-    (is
-     (=
-      #{{:db/id 3, :block/left 1}
-        {:db/id 5, :block/left 3}
-        {:db/id 2, :block/left 5}
-        {:db/id 4, :block/left 2}}
-      (set (map (fn [b]
-                  {:db/id (:db/id b)
-                   :block/left (:db/id (:block/left b))})
-             (:block/_parent (d/entity @conn 1))))))))
-
-(deftest test-multiple-broken-chains
-  (let [conn (core-test/get-current-conn)
-        data [{:block/uuid "1"
-               :db/id 1}
-              {:block/uuid "2"
-               :db/id 2
-               :block/page [:block/uuid "1"]
-               :block/parent [:block/uuid "1"]
-               :block/left [:block/uuid "1"]}
-              {:block/uuid "4"
-               :db/id 4}
-              {:block/uuid "3"
-               :db/id 3
-               :block/page [:block/uuid "1"]
-               :block/parent [:block/uuid "1"]
-               :block/left [:block/uuid "4"]}
-              {:block/uuid "5"
-               :block/page [:block/uuid "1"]
-               :block/parent [:block/uuid "1"]
-               :block/left [:block/uuid "3"]}
-              {:block/uuid "6"
-               :db/id 6}
-              {:block/uuid "7"
-               :block/page [:block/uuid "1"]
-               :block/parent [:block/uuid "1"]
-               :block/left [:block/uuid "5"]}]
-        _ (d/transact! conn data)
-        page-id (:db/id (d/entity @conn 1))
-        _ (fix-page-if-broken! conn page-id {})]
-    (is
-     (=
-      #{{:db/id 2, :block/left 1}
-        {:db/id 3, :block/left 2}
-        {:db/id 5, :block/left 3}
-        {:db/id 7, :block/left 5}}
-      (set (map (fn [b]
-                  {:db/id (:db/id b)
-                   :block/left (:db/id (:block/left b))})
-                (:block/_parent (d/entity @conn 1))))))))
-
-(comment
-  (do
-    (frontend.test.fixtures/reset-datascript test-db)
-    nil))

+ 4 - 3
src/test/frontend/handler/editor_test.cljs

@@ -8,7 +8,8 @@
             [frontend.state :as state]
             [frontend.util.cursor :as cursor]
             [goog.dom :as gdom]
-            [frontend.util :as util]))
+            [frontend.util :as util]
+            [logseq.db :as ldb]))
 
 (use-fixtures :each test-helper/start-and-destroy-db)
 
@@ -260,8 +261,8 @@
 
 (defn- delete-block
   [db block {:keys [embed?]}]
-  (let [sibling-block (d/entity db (get-in block [:block/left :db/id]))
-        first-block (d/entity db (get-in sibling-block [:block/left :db/id]))
+  (let [sibling-block (ldb/get-left-sibling (d/entity db (:db/id block)))
+        first-block (ldb/get-left-sibling sibling-block)
         block-dom-id "ls-block-block-to-delete"]
     (with-redefs [editor/get-state (constantly {:block-id (:block/uuid block)
                                                 :block-parent-id block-dom-id

+ 21 - 60
src/test/frontend/modules/outliner/core_test.cljs

@@ -4,7 +4,6 @@
             [frontend.test.fixtures :as fixtures]
             [logseq.outliner.core :as outliner-core]
             [frontend.modules.outliner.tree :as tree]
-            [logseq.outliner.tree :as otree]
             [logseq.outliner.transaction :as outliner-tx]
             [frontend.db :as db]
             [frontend.db.model :as db-model]
@@ -15,7 +14,9 @@
             [frontend.state :as state]
             [clojure.set :as set]
             [frontend.db.conn :as conn]
-            [frontend.worker.db-listener :as worker-db-listener]))
+            [frontend.worker.db-listener :as worker-db-listener]
+            [logseq.db :as ldb]
+            [logseq.db.frontend.order :as db-order]))
 
 (def test-db test-helper/test-db)
 
@@ -44,10 +45,14 @@
 (defn get-block
   ([id]
    (get-block id false))
-  ([id node?]
-   (cond->> (db/pull test-db '[*] [:block/uuid id])
-     node?
-     (outliner-core/block (db/get-db)))))
+  ([id _node?]
+   (db/entity test-db [:block/uuid id])))
+
+(defn get-children
+  [id]
+  (->> (:block/_parent (d/entity (db/get-db) [:block/uuid id]))
+       ldb/sort-by-order
+       (mapv :block/uuid)))
 
 (defn build-node-tree
   [col]
@@ -87,16 +92,17 @@
 
 (defn- build-blocks
   [tree]
-  (gp-block/with-parent-and-left 1 (build-node-tree tree)))
+  (gp-block/with-parent-and-order 1 (build-node-tree tree)))
 
 (defn transact-tree!
   [tree]
   (let [blocks (build-blocks tree)]
-    (assert (every? (fn [block] (and (:block/parent block) (:block/left block))) blocks) (str "Invalid blocks: " blocks))
-    (db/transact! test-db (concat [{:db/id 1
-                                    :block/uuid 1
-                                    :block/name "Test page"}]
-                                  blocks))))
+    (assert (every? (fn [block] (and (:block/parent block) (:block/order block))) blocks) (str "Invalid blocks: " blocks))
+    (d/transact! (db/get-db test-db false)
+                 (concat [{:db/id 1
+                           :block/uuid 1
+                           :block/name "Test page"}]
+                         blocks))))
 
 (def tree
   [[22 [[2 [[3 [[4]
@@ -117,11 +123,6 @@
   []
   (set (map :v (d/datoms (db/get-db test-db) :avet :block/uuid))))
 
-(defn get-children
-  [id]
-  (->> (otree/-get-children (get-block id true) (db/get-db test-db false))
-       (mapv #(-> % :data :block/uuid))))
-
 (defn- transact-opts
   []
   {:transact-opts {:repo test-db
@@ -293,37 +294,6 @@
       (outliner-core/indent-outdent-blocks! test-db (db/get-db test-db false) [(get-block 13) (get-block 14)] false))
     (is (= [2 12 13 14 16] (get-children 22)))))
 
-(deftest test-fix-top-level-blocks
-  (testing "no need to fix"
-    (let [blocks [{:block/uuid #uuid "62aa668b-e258-445d-aef6-5510054ff495",
-                   :block/properties {},
-                   :block/left #:db{:id 144},
-                   :block/format :markdown,
-                   :block/level 1,
-                   :block/content "a",
-                   :db/id 145,
-                   :block/parent #:db{:id 144},
-                   :block/page #:db{:id 144}}
-                  {:block/uuid #uuid "62aa668d-65d1-440c-849b-a0717f691193",
-                   :block/properties {},
-                   :block/left #:db{:id 145},
-                   :block/format :markdown,
-                   :block/level 1,
-                   :block/content "b",
-                   :db/id 146,
-                   :block/parent #:db{:id 144},
-                   :block/page #:db{:id 144}}
-                  {:block/uuid #uuid "62aa668e-f866-48ee-b8fe-737e101c548d",
-                   :block/properties {},
-                   :block/left #:db{:id 146},
-                   :block/format :markdown,
-                   :block/level 1,
-                   :block/content "c",
-                   :db/id 147,
-                   :block/parent #:db{:id 144},
-                   :block/page #:db{:id 144}}]]
-      (is (= blocks (outliner-core/fix-top-level-blocks blocks))))))
-
 (deftest test-outdent-blocks
   (testing "
   [1 [[2 [[3]
@@ -453,8 +423,7 @@
        (outliner-core/insert-blocks!
         test-db
         (db/get-db test-db false)
-        [{:block/left 1
-          :block/content "test"
+        [{:block/content "test"
           :block/parent 1
           :block/page 1}]
         target-block
@@ -490,43 +459,35 @@
   (testing "blocks with level"
     (is (= (outliner-core/blocks-with-level
             [{:db/id 6,
-              :block/left #:db{:id 3},
               :block/level 3,
               :block/parent #:db{:id 2},
               :block/uuid 6}
              {:db/id 9,
-              :block/left #:db{:id 6},
               :block/level 3,
               :block/parent #:db{:id 2},
               :block/uuid 9}])
            [{:db/id 6,
-             :block/left #:db{:id 3},
              :block/level 1,
              :block/parent #:db{:id 2},
              :block/uuid 6}
             {:db/id 9,
-             :block/left #:db{:id 6},
              :block/level 1,
              :block/parent #:db{:id 2},
              :block/uuid 9}]))
     (is (= (outliner-core/blocks-with-level
             [{:db/id 6,
-              :block/left #:db{:id 3},
               :block/level 3,
               :block/parent #:db{:id 2},
               :block/uuid 6}
              {:db/id 9,
-              :block/left #:db{:id 6},
               :block/level 4,
               :block/parent #:db{:id 6},
               :block/uuid 9}])
            [{:db/id 6,
-             :block/left #:db{:id 3},
              :block/level 1,
              :block/parent #:db{:id 2},
              :block/uuid 6}
             {:db/id 9,
-             :block/left #:db{:id 6},
              :block/level 2,
              :block/parent #:db{:id 6},
              :block/uuid 9}]))))
@@ -661,8 +622,8 @@ tags:: tag1, tag2
     (if (seq datoms)
       (let [id (:e (gen/generate (gen/elements datoms)))
             block (db/pull test-db '[*] id)]
-        (assert (and (:block/left block) (:block/parent block))
-                (str "No left or parent for block: " block))
+        (assert (:block/parent block)
+                (str "No parent for block: " block))
         block)
       (do
         (transact-random-tree!)

+ 3 - 5
src/test/frontend/test/generators.cljs

@@ -12,7 +12,6 @@
                 true
                 (concat '[:where
                           [?block :block/parent]
-                          [?block :block/left]
                           [?block :block/uuid ?block-uuid]])
                 page-uuid
                 (concat '[[?block :block/page ?page]
@@ -25,17 +24,16 @@
       (gen/elements coll)
       (gen/return nil))))
 
-(defn gen-available-parent-left-pair
-  "generate [<parent-uuid> <left-uuid>]"
+(defn gen-available-parent
+  "generate [<parent-uuid>]"
   [db & {:keys [page-uuid]}]
-  (let [query (cond-> '[:find ?parent-uuid ?left-uuid]
+  (let [query (cond-> '[:find ?parent-uuid]
                 page-uuid
                 (concat '[:in $ ?page-uuid])
                 true
                 (concat '[:where
                           [?b :block/uuid]
                           [?b :block/parent ?parent]
-                          [?b :block/left ?left]
                           [?parent :block/uuid ?parent-uuid]
                           [?left :block/uuid ?left-uuid]])
                 page-uuid

+ 4 - 3
src/test/frontend/test/helper.cljs

@@ -14,7 +14,8 @@
             [logseq.graph-parser.text :as text]
             [logseq.db.sqlite.create-graph :as sqlite-create-graph]
             [frontend.config :as config]
-            [frontend.worker.pipeline :as worker-pipeline]))
+            [frontend.worker.pipeline :as worker-pipeline]
+            [logseq.db.frontend.order :as db-order]))
 
 (def node? (exists? js/process))
 
@@ -225,14 +226,14 @@ This can be called in synchronous contexts as no async fns should be invoked"
       {:block/uuid first-block-uuid
        :block/page page-id
        :block/parent page-id
-       :block/left page-id
+       :block/order (db-order/gen-key nil)
        :block/content "block 1"
        :block/format :markdown}
       ;; second block
       {:block/uuid second-block-uuid
        :block/page page-id
        :block/parent page-id
-       :block/left [:block/uuid first-block-uuid]
+       :block/order (db-order/gen-key nil)
        :block/content "block 2"
        :block/format :markdown}]
      (map sqlite-util/block-with-timestamps))))

+ 2 - 9
src/test/frontend/worker/handler/page/rename_test.cljs

@@ -4,7 +4,6 @@
             [datascript.core :as d]
             [frontend.handler.page :as page-handler]
             [frontend.db :as db]
-            [frontend.worker.db.fix :as db-fix]
             [frontend.worker.handler.page.file-based.rename :as worker-page-rename]
             [frontend.handler.editor :as editor-handler]))
 
@@ -47,9 +46,7 @@
       ;; Old page deleted
       (is (nil? e1))
       ;; Blocks from both pages have been merged
-      (is (= (count (:block/_page e2)) (+ 1 (dec (count init-data)))))
-      ;; Ensure there's no conflicts
-      (is (empty? (db-fix/get-conflicts (db/get-db) (:db/id e2)))))))
+      (is (= (count (:block/_page e2)) (+ 1 (dec (count init-data))))))))
 
 (deftest merge-with-empty-page
   (page-handler/create! "Existing page" {:redirect? false :create-first-block? false})
@@ -60,9 +57,7 @@
       ;; Old page deleted
     (is (nil? e1))
       ;; Blocks from both pages have been merged
-    (is (= (count (:block/_page e2)) (dec (count init-data))))
-      ;; Ensure there's no conflicts
-    (is (empty? (db-fix/get-conflicts (db/get-db) (:db/id e2))))))
+    (is (= (count (:block/_page e2)) (dec (count init-data))))))
 
 (deftest merge-existing-pages-should-update-ref-ids
   (testing "Merge existing page"
@@ -76,7 +71,5 @@
       (is (nil? e1))
       ;; Blocks from both pages have been merged
       (is (= (count (:block/_page e2)) (+ 1 (dec (count init-data)))))
-      ;; Ensure there's no conflicts
-      (is (empty? (db-fix/get-conflicts (db/get-db) (:db/id e2))))
       ;; Content updated
       (is (= "Block 1 [[Existing page]]" (:block/content (db/entity [:block/uuid fbid])))))))

+ 1 - 1
src/test/frontend/worker/undo_redo_test.cljs

@@ -44,7 +44,7 @@
    (fn [[parent left]]
      (and (not= self-uuid left)
           (not= self-uuid parent)))
-   (gen/frequency [[9 (t.gen/gen-available-parent-left-pair db {:page-uuid page-uuid})]
+   (gen/frequency [[9 (t.gen/gen-available-parent db {:page-uuid page-uuid})]
                    [1 (gen/vector gen-non-exist-block-uuid 2)]])))
 
 (defn- gen-move-block-op

+ 1 - 3
src/test/logseq/api_test.cljs

@@ -21,12 +21,10 @@
                    {:db/id 10002
                     :block/uuid #uuid "adae3006-f03e-4814-a1f5-f17f15b86556"
                     :block/parent 10001
-                    :block/left 10001
                     :block/content "3"}
                    {:db/id 10003
                     :block/uuid #uuid "0c3053c3-2dab-4769-badd-14ce16d8ba8d"
                     :block/parent 10002
-                    :block/left 10002
                     :block/content "4"}])
 
     (is (= (:content (bean/->clj (api-block/get_block 10000 #js {}))) "1"))
@@ -35,7 +33,7 @@
     (is (= {:id 10001, :content "2", :uuid "d9b7b45f-267f-4794-9569-f43d1ce77172", :children [["uuid" "adae3006-f03e-4814-a1f5-f17f15b86556"]]}
            (select-keys (js->clj (api-block/get_block 10001 #js {:includeChildren false}) :keywordize-keys true)
                         [:id :content :uuid :children])))
-    (is (= {:content "2", :uuid "d9b7b45f-267f-4794-9569-f43d1ce77172", :id 10001, :children [{:content "3", :left {:id 10001}, :parent {:id 10001}, :uuid "adae3006-f03e-4814-a1f5-f17f15b86556", :id 10002, :level 1, :children [{:content "4", :left {:id 10002}, :parent {:id 10002}, :uuid "0c3053c3-2dab-4769-badd-14ce16d8ba8d", :id 10003, :level 2, :children []}]}]}
+    (is (= {:content "2", :uuid "d9b7b45f-267f-4794-9569-f43d1ce77172", :id 10001, :children [{:content "3", :parent {:id 10001}, :uuid "adae3006-f03e-4814-a1f5-f17f15b86556", :id 10002, :level 1, :children [{:content "4", :parent {:id 10002}, :uuid "0c3053c3-2dab-4769-badd-14ce16d8ba8d", :id 10003, :level 2, :children []}]}]}
            (js->clj (api-block/get_block 10001 #js {:includeChildren true}) :keywordize-keys true)))))
 
 #_(cljs.test/run-tests)