Browse Source

refactor: build tx-data in db-worker instead of the UI thread

So the tx-data will be built on top of the full db instead of the
partial db from the UI thread, it also reduces the risk to use
outdated data since RTC can transact data from the worker.
Tienson Qin 1 year ago
parent
commit
4e1ca386d4

+ 6 - 31
src/main/frontend/db_worker.cljs

@@ -25,8 +25,7 @@
             [frontend.worker.util :as worker-util]
             [frontend.worker.handler.page.rename :as worker-page-rename]
             [frontend.worker.handler.page :as worker-page]
-            [logseq.outliner.core :as outliner-core]
-            [logseq.outliner.transaction :as outliner-tx]))
+            [frontend.worker.outliner-op :as worker-outliner-op]))
 
 (defonce *sqlite worker-state/*sqlite)
 (defonce *sqlite-conns worker-state/*sqlite-conns)
@@ -470,36 +469,12 @@
      (let [result (worker-page/delete! repo conn page-name nil {})]
        (bean/->js {:result result}))))
 
-  ;; block ops
-
-  (delete-blocks
-   [this repo block-ids opts-str]
-   (when-let [conn (worker-state/get-datascript-conn repo)]
-     (let [block-ids (remove nil? block-ids)]
-       (when (seq block-ids)
-         (let [opts (if opts-str
-                      (edn/read-string opts-str)
-                      {})
-               blocks (map #(d/entity @conn %) block-ids)]
-           (outliner-tx/transact!
-             {:local-tx? true           ; keep this transaction in undo/redo history
-             :outliner-op :delete-blocks
-             :transact-opts {:repo repo
-                             :conn conn}}
-            (outliner-core/delete-blocks! repo conn
-                                          (worker-state/get-date-formatter repo)
-                                          blocks
-                                          opts)))))
-     nil))
-
-  (move-blocks-up-down
-   [this repo block-ids up?]
+  (apply-outliner-ops
+   [this repo ops-str opts-str]
    (when-let [conn (worker-state/get-datascript-conn repo)]
-     (let [block-ids (remove nil? block-ids)]
-       (when (seq block-ids)
-         (let [blocks (map #(d/entity @conn %) block-ids)
-               _ (outliner-core/move-blocks-up-down! repo conn blocks up?)]
-           )))))
+     (let [ops (edn/read-string ops-str)
+           opts (edn/read-string opts-str)]
+       (worker-outliner-op/apply-ops! repo conn ops opts))))
 
   (file-writes-finished?
    [this repo]

+ 5 - 6
src/main/frontend/handler/block.cljs

@@ -8,6 +8,7 @@
    [frontend.mobile.haptics :as haptics]
    [logseq.outliner.core :as outliner-core]
    [frontend.modules.outliner.ui :as ui-outliner-tx]
+   [frontend.modules.outliner.op :as outliner-op]
    [frontend.state :as state]
    [frontend.util :as util]
    [frontend.util.drawer :as drawer]
@@ -334,12 +335,10 @@
        {:outliner-op :move-blocks
         :real-outliner-op :indent-outdent}
        (when save-current-block (save-current-block))
-       (outliner-core/indent-outdent-blocks! (state/get-current-repo)
-                                             (db/get-db false)
-                                             (get-top-level-blocks blocks)
-                                             indent?
-                                             {:parent-original (get-first-block-original)
-                                              :logical-outdenting? (state/logical-outdenting?)})))))
+       (outliner-op/indent-outdent-blocks! (get-top-level-blocks blocks)
+                                           indent?
+                                           {:parent-original (get-first-block-original)
+                                            :logical-outdenting? (state/logical-outdenting?)})))))
 
 (def *swipe (atom nil))
 

+ 11 - 13
src/main/frontend/handler/common/page.cljs

@@ -21,7 +21,7 @@
             [frontend.db.conn :as conn]
             [datascript.core :as d]
             [frontend.modules.outliner.ui :as ui-outliner-tx]
-            [logseq.outliner.core :as outliner-core]))
+            [frontend.modules.outliner.op :as outliner-op]))
 
 (defn build-hidden-page-tx-data
   [page-name]
@@ -126,28 +126,26 @@
 (defn <favorite-page!-v2
   [page-block-uuid]
   {:pre [(uuid? page-block-uuid)]}
-  (let [repo (state/get-current-repo)
-        favorites-page (d/entity (conn/get-db) [:block/name favorites-page-name])
+  (let [favorites-page (d/entity (conn/get-db) [:block/name favorites-page-name])
         favorites-page-tx-data (build-hidden-page-tx-data "favorites")]
     (when (d/entity (conn/get-db) [:block/uuid page-block-uuid])
       (p/do!
        (when-not favorites-page (ldb/transact! nil [favorites-page-tx-data]))
        (ui-outliner-tx/transact!
         {:outliner-op :insert-blocks}
-        (outliner-core/insert-blocks! repo (conn/get-db false) [{:block/link [:block/uuid page-block-uuid]
-                                                                 :block/content ""
-                                                                 :block/format :markdown}]
-                                      (d/entity (conn/get-db) [:block/name favorites-page-name])
-                                      {}))))))
+        (outliner-op/insert-blocks! [{:block/link [:block/uuid page-block-uuid]
+                                      :block/content ""
+                                      :block/format :markdown}]
+                                    (d/entity (conn/get-db) [:block/name favorites-page-name])
+                                    {}))))))
 
 (defn <unfavorite-page!-v2
   [page-block-uuid]
   {:pre [(uuid? page-block-uuid)]}
-  (let [repo (state/get-current-repo)]
-    (when-let [block (find-block-in-favorites-page page-block-uuid)]
-      (ui-outliner-tx/transact!
-       {:outliner-op :delete-blocks}
-       (outliner-core/delete-blocks! repo (conn/get-db false) (state/get-date-formatter) [block] {})))))
+  (when-let [block (find-block-in-favorites-page page-block-uuid)]
+    (ui-outliner-tx/transact!
+     {:outliner-op :delete-blocks}
+     (outliner-op/delete-blocks! [block] {}))))
 
 
 ;; favorites fns end ================

+ 3 - 3
src/main/frontend/handler/db_based/editor.cljs

@@ -14,8 +14,8 @@
             [frontend.handler.property :as property-handler]
             [frontend.handler.property.util :as pu]
             [frontend.handler.repo-config :as repo-config-handler]
-            [logseq.outliner.core :as outliner-core]
             [frontend.modules.outliner.ui :as ui-outliner-tx]
+            [frontend.modules.outliner.op :as outliner-op]
             [frontend.schema.handler.repo-config :as repo-config-schema]
             [promesa.core :as p]
             [logseq.db.frontend.content :as db-content]))
@@ -152,6 +152,6 @@
   [repo block-ids heading]
   (ui-outliner-tx/transact!
    {:outliner-op :save-block}
-   (doseq [block-tx (keep #(set-heading-aux! % heading) block-ids)]
-     (outliner-core/save-block! repo (db/get-db false) (state/get-date-formatter) block-tx))
+   (doseq [block (keep #(set-heading-aux! % heading) block-ids)]
+     (outliner-op/save-block! block))
    (property-handler/batch-set-block-property! repo block-ids :heading heading)))

+ 6 - 6
src/main/frontend/handler/dnd.cljs

@@ -2,9 +2,10 @@
   "Provides fns for drag and drop"
   (:require [frontend.handler.editor :as editor-handler]
             [frontend.handler.property :as property-handler]
-            [logseq.outliner.core :as outliner-core]
             [logseq.outliner.tree :as otree]
+            [logseq.outliner.core :as outliner-core]
             [frontend.modules.outliner.ui :as ui-outliner-tx]
+            [frontend.modules.outliner.op :as outliner-op]
             [logseq.common.util.block-ref :as block-ref]
             [frontend.state :as state]
             [frontend.db :as db]
@@ -12,8 +13,7 @@
 
 (defn move-blocks
   [^js event blocks target-block original-block move-to]
-  (let [repo (state/get-current-repo)
-        blocks' (map #(db/pull (:db/id %)) blocks)
+  (let [blocks' (map #(db/pull (:db/id %)) blocks)
         first-block (first blocks')
         top? (= move-to :top)
         nested? (= move-to :nested)
@@ -53,10 +53,10 @@
                     (otree/-get-left-id target-node conn))]
              (if first-child?
                (when-let [parent (otree/-get-parent target-node conn)]
-                 (outliner-core/move-blocks! repo conn blocks' (:data parent) false))
+                 (outliner-op/move-blocks! blocks' (:data parent) false))
                (when-let [before-node (otree/-get-left target-node conn)]
-                 (outliner-core/move-blocks! repo conn blocks' (:data before-node) true))))
-           (outliner-core/move-blocks! repo conn blocks' target-block (not nested?)))))
+                 (outliner-op/move-blocks! blocks' (:data before-node) true))))
+           (outliner-op/move-blocks! blocks' target-block (not nested?)))))
 
       :else
       nil)))

+ 49 - 64
src/main/frontend/handler/editor.cljs

@@ -33,6 +33,7 @@
             [frontend.handler.file-based.editor :as file-editor-handler]
             [frontend.mobile.util :as mobile-util]
             [logseq.outliner.core :as outliner-core]
+            [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]
@@ -66,7 +67,8 @@
             [promesa.core :as p]
             [rum.core :as rum]
             [frontend.handler.db-based.property :as db-property-handler]
-            [frontend.fs.capacitor-fs :as capacitor-fs]))
+            [frontend.fs.capacitor-fs :as capacitor-fs]
+            [clojure.edn :as edn]))
 
 ;; FIXME: should support multiple images concurrently uploading
 
@@ -78,9 +80,7 @@
 
 (defn- outliner-save-block!
   [block]
-  (let [repo (state/get-current-repo)
-        conn (db/get-db false)]
-    (outliner-core/save-block! repo conn (state/get-date-formatter) block)))
+  (outliner-op/save-block! block))
 
 (defn get-block-own-order-list-type
   [block]
@@ -345,15 +345,13 @@
 
                    :else
                    (not has-children?))]
-    (p/do!
-     (ui-outliner-tx/transact!
-      {:outliner-op :insert-blocks}
-       (save-current-block! {:current-block current-block})
-       (outliner-core/insert-blocks! (state/get-current-repo) (db/get-db false)
-                                    [new-block] current-block {:sibling? sibling?
-                                                               :keep-uuid? keep-uuid?
-                                                               :ordered-list? ordered-list?
-                                                               :replace-empty-target? replace-empty-target?})))))
+    (ui-outliner-tx/transact!
+     {:outliner-op :insert-blocks}
+     (save-current-block! {:current-block current-block})
+     (outliner-op/insert-blocks! [new-block] current-block {:sibling? sibling?
+                                                            :keep-uuid? keep-uuid?
+                                                            :ordered-list? ordered-list?
+                                                            :replace-empty-target? replace-empty-target?}))))
 
 
 (defn- block-self-alone-when-insert?
@@ -554,10 +552,10 @@
                                                            :ordered-list? ordered-list?
                                                            :replace-empty-target? replace-empty-target?})
              (when edit-block?
-              (if (and replace-empty-target?
-                       (string/blank? (:block/content last-block)))
-                (edit-block! last-block :max nil)
-                (edit-block! new-block :max nil)))
+               (if (and replace-empty-target?
+                        (string/blank? (:block/content last-block)))
+                 (edit-block! last-block :max nil)
+                 (edit-block! new-block :max nil)))
              new-block)))))))
 
 (defn insert-first-page-block-if-not-exists!
@@ -687,15 +685,12 @@
     (when block
       (let [blocks (block-handler/get-top-level-blocks [block])]
         (state/set-state! :ui/deleting-block uuid)
-        (p/do!
-         (ui-outliner-tx/transact!
-          {:outliner-op :delete-blocks}
-          (outliner-core/delete-blocks! repo (db/get-db false)
-                                        (state/get-date-formatter)
-                                        blocks
-                                        (merge
-                                         delete-opts
-                                         {:children? children?}))))))))
+        (ui-outliner-tx/transact!
+         {:outliner-op :delete-blocks}
+         (outliner-op/delete-blocks! blocks
+                                     (merge
+                                      delete-opts
+                                      {:children? children?})))))))
 
 (defn- move-to-prev-block
   [repo sibling-block format _id value]
@@ -846,8 +841,7 @@
       (p/do!
        (ui-outliner-tx/transact!
         {:outliner-op :delete-blocks}
-        (when-let [^Object worker @state/*db-worker]
-          (.delete-blocks worker repo (clj->js (map :db/id blocks')) nil)))
+         (outliner-op/delete-blocks! blocks' nil))
        (when sibling-block
          (move-to-prev-block repo sibling-block
                              (:block/format block)
@@ -1740,14 +1734,12 @@
   [up?]
   (fn [event]
     (util/stop event)
-    (let [repo (state/get-current-repo)
-          edit-block-id (:block/uuid (state/get-edit-block))
+    (let [edit-block-id (:block/uuid (state/get-edit-block))
           move-nodes (fn [blocks]
                        (let [blocks' (block-handler/get-top-level-blocks blocks)
                              result (ui-outliner-tx/transact!
                                      {:outliner-op :move-blocks}
-                                      (when-let [^Object worker @state/*db-worker]
-                                        (.move-blocks-up-down worker repo (clj->js (map :db/id blocks')) up?)))]
+                                      (outliner-op/move-blocks-up-down! blocks' up?))]
                          (when-let [block-node (util/get-first-block-by-id (:block/uuid (first blocks)))]
                            (.scrollIntoView block-node #js {:behavior "smooth" :block "nearest"}))
                          result))]
@@ -2046,7 +2038,7 @@
         page (if (:block/name block) block
                  (when target-block (:block/page (db/entity (:db/id target-block)))))
         empty-target? (if (true? skip-empty-target?) false
-                        (string/blank? (:block/content target-block)))
+                          (string/blank? (:block/content target-block)))
         paste-nested-blocks? (nested-blocks blocks)
         target-block-has-children? (db/has-children? (:block/uuid target-block))
         replace-empty-target? (and empty-target?
@@ -2074,23 +2066,21 @@
        {:outliner-op :save-block}
        (outliner-save-block! editing-block)))
 
-    (p/let [*insert-result (atom nil)
-            _ (ui-outliner-tx/transact!
-               {:outliner-op :insert-blocks
-                :additional-tx revert-cut-txs}
-               (when target-block'
-                 (let [format (or (:block/format target-block') (state/get-preferred-format))
-                       repo (state/get-current-repo)
-                       blocks' (map (fn [block]
-                                      (paste-block-cleanup repo block page exclude-properties format content-update-fn keep-uuid?))
-                                    blocks)
-                       result (outliner-core/insert-blocks! repo (db/get-db false) blocks' target-block' {:sibling? sibling?
-                                                                                                          :outliner-op :paste
-                                                                                                          :replace-empty-target? replace-empty-target?
-                                                                                                          :keep-uuid? keep-uuid?})]
-                   (reset! *insert-result result))))]
+    (p/let [result (ui-outliner-tx/transact!
+                    {:outliner-op :insert-blocks
+                     :additional-tx revert-cut-txs}
+                    (when target-block'
+                      (let [format (or (:block/format target-block') (state/get-preferred-format))
+                            repo (state/get-current-repo)
+                            blocks' (map (fn [block]
+                                           (paste-block-cleanup repo block page exclude-properties format content-update-fn keep-uuid?))
+                                         blocks)]
+                        (outliner-op/insert-blocks! blocks' target-block' {:sibling? sibling?
+                                                                           :outliner-op :paste
+                                                                           :replace-empty-target? replace-empty-target?
+                                                                           :keep-uuid? keep-uuid?}))))]
       (state/set-block-op-type! nil)
-      (when-let [result @*insert-result] (edit-last-block-after-inserted! result)))))
+      (when result (edit-last-block-after-inserted! (edn/read-string result))))))
 
 (defn- block-tree->blocks
   "keep-uuid? - maintain the existing :uuid in tree vec"
@@ -2190,18 +2180,14 @@
                                :else
                                true)]
                (try
-                 (let [*result (atom nil)]
-                   (p/do!
-                    (ui-outliner-tx/transact!
-                     {:outliner-op :insert-blocks
-                      :created-from-journal-template? journal?}
-                     (when-not (string/blank? (state/get-edit-content))
-                       (save-current-block!))
-                     (let [result (outliner-core/insert-blocks! repo (db/get-db false) blocks'
-                                                                target
-                                                                (assoc opts :sibling? sibling?'))]
-                       (reset! *result result)))
-                    (some-> @*result edit-last-block-after-inserted!)))
+                 (p/let [result (ui-outliner-tx/transact!
+                                 {:outliner-op :insert-blocks
+                                  :created-from-journal-template? journal?}
+                                 (when-not (string/blank? (state/get-edit-content))
+                                   (save-current-block!))
+                                 (outliner-op/insert-blocks! blocks' target
+                                                             (assoc opts :sibling? sibling?')))]
+                   (when result (edit-last-block-after-inserted! (edn/read-string result))))
 
                  (catch :default ^js/Error e
                    (notification/show!
@@ -2266,9 +2252,8 @@
        :real-outliner-op :indent-outdent}
       (save-current-block!)
       (when target
-        (outliner-core/move-blocks! (state/get-current-repo) (db/get-db false)
-                                    (block-handler/get-top-level-blocks [block])
-                                    target true)))
+        (outliner-op/move-blocks! (block-handler/get-top-level-blocks [block])
+                                  target true)))
      (when original-block
        (util/schedule #(edit-block! block pos nil))))))
 

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

@@ -6,8 +6,8 @@
             [frontend.format.block :as block]
             [frontend.db :as db]
             [frontend.format.mldoc :as mldoc]
-            [logseq.outliner.core :as outliner-core]
             [frontend.state :as state]
+            [frontend.modules.outliner.op :as outliner-op]
             [frontend.modules.outliner.ui :as ui-outliner-tx]
             [frontend.util :as util]
             [frontend.util.clock :as clock]
@@ -195,9 +195,7 @@
    {:outliner-op :save-block}
    (doseq [block-id block-ids]
      (when-let [block (set-heading-aux! block-id heading)]
-       (outliner-core/save-block! (state/get-current-repo) (db/get-db false)
-                                  (state/get-date-formatter)
-                                  block)))))
+       (outliner-op/save-block! block)))))
 
 (defn set-blocks-id!
   "Persist block uuid to file if the uuid is valid, and it's not persisted in file.

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

@@ -2,7 +2,7 @@
   "Page property fns for file graphs"
   (:require [clojure.string :as string]
             [frontend.db :as db]
-            [logseq.outliner.core :as outliner-core]
+            [frontend.modules.outliner.op :as outliner-op]
             [frontend.modules.outliner.ui :as ui-outliner-tx]
             [frontend.state :as state]
             [frontend.util :as util]))
@@ -85,4 +85,4 @@
             (ui-outliner-tx/transact!
              {:outliner-op :insert-blocks
               :additional-tx page-properties-tx}
-             (outliner-core/insert-blocks! repo (db/get-db false) block page {:sibling? false}))))))))
+             (outliner-op/insert-blocks! block page {:sibling? false}))))))))

+ 2 - 4
src/main/frontend/handler/file_based/property.cljs

@@ -3,7 +3,7 @@
   (:require [frontend.db :as db]
             [frontend.handler.block :as block-handler]
             [frontend.handler.file-based.property.util :as property-util]
-            [logseq.outliner.core :as outliner-core]
+            [frontend.modules.outliner.op :as outliner-op]
             [frontend.modules.outliner.ui :as ui-outliner-tx]
             [frontend.state :as state]
             [logseq.common.util :as common-util]
@@ -55,9 +55,7 @@
                          :block/properties-order property-ks
                          :block/properties-text-values properties-text-values
                          :block/content content}]
-              (outliner-core/save-block! (state/get-current-repo) (db/get-db false)
-                                         (state/get-date-formatter)
-                                         block))))))
+              (outliner-op/save-block! block))))))
      (let [block-id (ffirst col)
            block-id (if (string? block-id) (uuid block-id) block-id)
            input-pos (or (state/get-edit-pos) :max)]

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

@@ -41,7 +41,7 @@
             [frontend.db.conn :as conn]
             [logseq.db :as ldb]
             [frontend.modules.outliner.ui :as ui-outliner-tx]
-            [logseq.outliner.core :as outliner-core]))
+            [frontend.modules.outliner.op :as outliner-op]))
 
 (def create! page-common-handler/create!)
 (def <create! page-common-handler/<create!)
@@ -139,8 +139,7 @@
 
 (defn <reorder-favorites!
   [favorites]
-  (let [repo (state/get-current-repo)
-        conn (conn/get-db false)]
+  (let [conn (conn/get-db false)]
     (when-let [favorites-page-entity (d/entity @conn [:block/name page-common-handler/favorites-page-name])]
       (let [favorite-page-block-db-id-coll
             (keep (fn [page-name]
@@ -150,13 +149,12 @@
             current-blocks (ldb/sort-by-left (ldb/get-page-blocks @conn page-common-handler/favorites-page-name {})
                                              favorites-page-entity)]
         (p/do!
-          (ui-outliner-tx/transact!
-              {}
-              (doseq [[page-block-db-id block] (zipmap favorite-page-block-db-id-coll current-blocks)]
-                (when (not= page-block-db-id (:db/id (:block/link block)))
-                  (outliner-core/save-block! repo conn (state/get-date-formatter)
-                                             (assoc block :block/link page-block-db-id)))))
-          (state/update-favorites-updated!))))))
+         (ui-outliner-tx/transact!
+          {}
+          (doseq [[page-block-db-id block] (zipmap favorite-page-block-db-id-coll current-blocks)]
+            (when (not= page-block-db-id (:db/id (:block/link block)))
+              (outliner-op/save-block! (assoc block :block/link page-block-db-id)))))
+         (state/update-favorites-updated!))))))
 
 (defn has-more-journals?
   []

+ 69 - 0
src/main/frontend/modules/outliner/op.cljs

@@ -0,0 +1,69 @@
+(ns frontend.modules.outliner.op
+  "Build outliner ops")
+
+(def ^:private ^:dynamic *outliner-ops*
+  "Stores outliner ops that are generated by the following calls"
+  nil)
+
+(defn- op-transact!
+  [fn-var & args]
+  {:pre [(var? fn-var)]}
+  (when (nil? *outliner-ops*)
+    (throw (js/Error. (str (:name (meta fn-var)) " is not used in (transact! ...)"))))
+  (let [result (apply @fn-var args)]
+    (conj! *outliner-ops* result)
+    result))
+
+(defn save-block
+  [block]
+  [:save-block [block]])
+
+(defn insert-blocks
+  [blocks target-block opts]
+  (let [id (:db/id target-block)]
+    [:insert-blocks [blocks id opts]]))
+
+(defn delete-blocks
+  [blocks opts]
+  (let [ids (map :db/id blocks)]
+    [:delete-blocks [ids opts]]))
+
+(defn move-blocks
+  [blocks target-block sibling?]
+  (let [ids (map :db/id blocks)
+        target-id (:db/id target-block)]
+    [:move-blocks [ids target-id sibling?]]))
+
+(defn move-blocks-up-down
+  [blocks up?]
+  (let [ids (map :db/id blocks)]
+    [:move-blocks-up-down [ids up?]]))
+
+(defn indent-outdent-blocks
+  [blocks indent? & {:as opts}]
+  (let [ids (map :db/id blocks)]
+    [:indent-outdent-blocks [ids indent? opts]]))
+
+(defn save-block!
+  [block]
+  (op-transact! #'save-block block))
+
+(defn insert-blocks!
+  [blocks target-block opts]
+  (op-transact! #'insert-blocks blocks target-block opts))
+
+(defn delete-blocks!
+  [blocks opts]
+  (op-transact! #'delete-blocks blocks opts))
+
+(defn move-blocks!
+  [blocks target-block sibling?]
+  (op-transact! #'move-blocks blocks target-block sibling?))
+
+(defn move-blocks-up-down!
+  [blocks up?]
+  (op-transact! #'move-blocks-up-down blocks up?))
+
+(defn indent-outdent-blocks!
+  [blocks indent? & {:as opts}]
+  (op-transact! #'indent-outdent-blocks blocks indent? opts))

+ 11 - 20
src/main/frontend/modules/outliner/ui.cljc

@@ -1,28 +1,19 @@
 (ns frontend.modules.outliner.ui
-  #?(:cljs (:require-macros [logseq.outliner.transaction]))
   #?(:cljs (:require-macros [frontend.modules.outliner.ui]))
   #?(:cljs (:require [frontend.state :as state]
-                     [frontend.config :as config]
                      [frontend.db :as db])))
 
-#?(:cljs
-   (do
-     (defn unlinked-graph?
-       []
-       (let [repo (state/get-current-repo)]
-         (contains? (:file/unlinked-dirs @state/state)
-                    (config/get-repo-dir repo))))
-
-     (def set-state-fn state/set-state!)))
-
 (defmacro transact!
   [opts & body]
   `(when (db/request-finished?)
-     (let [transact-opts# {:repo (state/get-current-repo)
-                           :conn (db/get-db false)
-                           :unlinked-graph? frontend.modules.outliner.ui/unlinked-graph?
-                           :set-state-fn frontend.modules.outliner.ui/set-state-fn}]
-       (when (nil? @(:history/tx-before-editor-cursor @state/state))
-         (state/set-state! :history/tx-before-editor-cursor (state/get-current-edit-block-and-position)))
-       (logseq.outliner.transaction/transact! (assoc ~opts :transact-opts transact-opts#)
-                                              ~@body))))
+     (when (nil? @(:history/tx-before-editor-cursor @state/state))
+       (state/set-state! :history/tx-before-editor-cursor (state/get-current-edit-block-and-position)))
+     (let [ops# frontend.modules.outliner.op/*outliner-ops*]
+       (if ops#
+         (do ~@body)                    ; nested transact!
+         (binding [frontend.modules.outliner.op/*outliner-ops* (transient [])]
+           ~@body
+           (let [r# (persistent! frontend.modules.outliner.op/*outliner-ops*)
+                 worker# @state/*db-worker]
+             (when (and worker# (seq r#))
+               (.apply-outliner-ops ^Object worker# (state/get-current-repo) (pr-str r#) (pr-str ~opts)))))))))

+ 53 - 0
src/main/frontend/worker/outliner_op.cljs

@@ -0,0 +1,53 @@
+(ns frontend.worker.outliner-op
+  "Transact outliner ops from UI"
+  (:require [logseq.outliner.transaction :as outliner-tx]
+            [logseq.outliner.core :as outliner-core]
+            [frontend.worker.state :as worker-state]
+            [datascript.core :as d]
+            [promesa.core :as p]))
+
+(defn apply-ops!
+  [repo conn ops opts]
+  (let [opts' (assoc opts
+                     :transact-opts {:repo repo :conn conn}
+                     :local-tx? true)
+        date-formatter (worker-state/get-date-formatter repo)
+        *insert-result (atom nil)]
+    (p/do!
+     (outliner-tx/transact!
+      opts'
+      (doseq [[op args] ops]
+        (case op
+          :save-block
+          (apply outliner-core/save-block! repo conn date-formatter args)
+
+          :insert-blocks
+          (let [[blocks target-block-id opts] args]
+            (when-let [target-block (d/entity @conn target-block-id)]
+              (let [result (outliner-core/insert-blocks! repo conn blocks target-block opts)]
+                (reset! *insert-result result))))
+
+          :delete-blocks
+          (let [[block-ids opts] args
+                blocks (keep #(d/entity @conn %) block-ids)]
+            (outliner-core/delete-blocks! repo conn date-formatter blocks opts))
+
+          :move-blocks
+          (let [[block-ids target-block-id sibling?] args
+                blocks (keep #(d/entity @conn %) block-ids)
+                target-block (d/entity @conn target-block-id)]
+            (when (and target-block (seq blocks))
+              (outliner-core/move-blocks! repo conn blocks target-block sibling?)))
+
+          :move-blocks-up-down
+          (let [[block-ids up?] args
+                blocks (keep #(d/entity @conn %) block-ids)]
+            (when (seq blocks)
+              (outliner-core/move-blocks-up-down! repo conn blocks up?)))
+
+          :indent-outdent-blocks
+          (let [[block-ids indent? opts] args
+                blocks (keep #(d/entity @conn %) block-ids)]
+            (when (seq blocks)
+              (outliner-core/indent-outdent-blocks! repo conn blocks indent? opts))))))
+     (pr-str @*insert-result))))