Преглед изворни кода

refactor(undo): update ns frontend.worker.undo-redo (wip)

rcmerci пре 1 година
родитељ
комит
a7c8451ce4

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

@@ -929,9 +929,10 @@
        (remove nil?)))))
 
 (defn ^:api delete-block
-  "Delete block from the tree."
+  "FIXME: why expose this fn? there's already a public fn `delete-blocks!`
+  Delete block from the tree."
   [repo conn txs-state node {:keys [children? children-check? date-formatter]
-                        :or {children-check? true}}]
+                             :or {children-check? true}}]
   (if (and children-check?
            (not children?)
            (first (:block/_parent (d/entity @conn [:block/uuid (:block/uuid (get-data node))]))))

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

@@ -1,4 +1,5 @@
 (ns frontend.worker.db-listener
+  "Db listeners for worker-db."
   (:require [datascript.core :as d]))
 
 

+ 5 - 1
src/main/frontend/worker/state.cljs

@@ -8,11 +8,15 @@
                        :db/latest-transact-time {}
                        :worker/context {}
 
+                       ;; FIXME: this name :config is too general
                        :config {}
                        :git/current-repo nil
                        :rtc/batch-processing? false
                        :rtc/remote-batch-txs nil
-                       :rtc/downloading-graph? false}))
+                       :rtc/downloading-graph? false
+
+                       :undo/repo->undo-stack (atom {})
+                       :undo/repo->redo-stack (atom {})}))
 
 (defonce *rtc-ws-url (atom nil))
 

+ 151 - 27
src/main/frontend/worker/undo_redo.cljs

@@ -1,10 +1,13 @@
 (ns frontend.worker.undo-redo
   "undo/redo related fns and op-schema"
-  (:require [frontend.worker.db-listener :as db-listener]
-            [datascript.core :as d]
-            [malli.util :as mu]
-            [malli.core :as m]))
-
+  (:require [datascript.core :as d]
+            [frontend.worker.db-listener :as db-listener]
+            [frontend.worker.state :as worker-state]
+            [logseq.common.config :as common-config]
+            [logseq.outliner.core :as outliner-core]
+            [logseq.outliner.transaction :as outliner-tx]
+            [malli.core :as m]
+            [malli.util :as mu]))
 
 (def undo-op-schema
   (mu/closed-schema
@@ -40,10 +43,10 @@
       [:map
        [:block-uuid :uuid]
        [:block-origin-content {:optional true} :string]
-      ;; TODO: add more attrs
+       ;; TODO: add more attrs
        ]]]]))
 
-(def undo-op-validator (m/validator undo-op-schema))
+(def undo-ops-validator (m/validator [:sequential undo-op-schema]))
 
 (defn reverse-op
   [db op]
@@ -83,7 +86,122 @@
          (cond-> {:block-uuid block-uuid}
            block-origin-content (assoc :block-origin-content block-origin-content))]))))
 
-(def entity-map-pull-pattern
+
+(def ^:private apply-conj-vec (partial apply (fnil conj [])))
+
+(defn- push-undo-ops
+  [repo ops]
+  (swap! (:undo/repo->undo-stack @worker-state/*state) update repo apply-conj-vec ops))
+
+(defn- pop-undo-op
+  [repo]
+  (let [repo->undo-stack (:undo/repo->undo-stack @worker-state/*state)]
+    (when-let [peek-op (peek (@repo->undo-stack repo))]
+      (swap! repo->undo-stack update repo pop)
+      peek-op)))
+
+(defn- push-redo-ops
+  [repo ops]
+  (swap! (:undo/repo->redo-stack @worker-state/*state) update repo apply-conj-vec ops))
+
+(defn- pop-redo-op
+  [repo]
+  (let [repo->redo-stack (:undo/repo->redo-stack @worker-state/*state)]
+    (when-let [peek-op (peek (@repo->redo-stack repo))]
+      (swap! repo->redo-stack update repo pop)
+      peek-op)))
+
+
+(defmulti reverse-apply-op (fn [op _conn _repo] (first op)))
+(defmethod reverse-apply-op :remove-block
+  [op conn repo]
+  (let [[_ {:keys [block-uuid block-entity-map]}] op]
+    (when-let [left-entity (d/entity @conn [:block/uuid (:block/left block-entity-map)])]
+      (let [sibling? (not= (:block/left block-entity-map) (:block/parent block-entity-map))]
+        (outliner-tx/transact!
+         {:gen-undo-op? false
+          :outliner-op :insert-blocks
+          :transact-opts {:repo repo
+                          :conn conn}}
+         (outliner-core/insert-blocks! repo conn
+                                       [(cond-> {:block/uuid block-uuid
+                                                 :block/content (:block/content block-entity-map)
+                                                 :block/created-at (:block/created-at block-entity-map)
+                                                 :block/updated-at (:block/updated-at block-entity-map)
+                                                 :block/format :markdown}
+                                          (seq (:block/tags block-entity-map))
+                                          (assoc :block/tags (mapv (partial vector :block/uuid)
+                                                                   (:block/tags block-entity-map))))]
+                                       left-entity {:sibling? sibling? :keep-uuid? true}))
+        :push-undo-redo
+        ))))
+
+(defmethod reverse-apply-op :insert-block
+  [op conn repo]
+  (let [[_ {:keys [block-uuid]}] op]
+    (when-let [block-entity (d/entity @conn [:block/uuid block-uuid])]
+      (when (empty? (seq (:block/_parent block-entity))) ;if have children, skip
+        (outliner-tx/transact!
+         {:gen-undo-op? false
+          :outliner-op :delete-blocks
+          :transact-opts {:repo repo
+                          :conn conn}}
+         (outliner-core/delete-blocks! repo conn
+                                       (common-config/get-date-formatter (worker-state/get-config repo))
+                                       [block-entity]
+                                       {:children? false}))
+        :push-undo-redo))))
+
+(defmethod reverse-apply-op :move-block
+  [op conn repo]
+  (let [[_ {:keys [block-uuid block-origin-left block-origin-parent]}] op]
+    (when-let [block-entity (d/entity @conn [:block/uuid block-uuid])]
+      (when-let [left-entity (d/entity @conn [:block/uuid block-origin-left])]
+        (let [sibling? (not= block-origin-left block-origin-parent)]
+          (outliner-tx/transact!
+           {:gen-undo-op? false
+            :outliner-op :move-blocks
+            :transact-opts {:repo repo
+                            :conn conn}}
+           (outliner-core/move-blocks! repo conn [block-entity] left-entity sibling?))
+          :push-undo-redo)))))
+
+(defmethod reverse-apply-op :update-block
+  [op conn repo]
+  (let [[_ {:keys [block-uuid block-origin-content]}] op]
+    (when-let [block-entity (d/entity @conn [:block/uuid block-uuid])]
+      (let [new-block (assoc block-entity :block/content block-origin-content)]
+        (outliner-tx/transact!
+         {:gen-undo-op? false
+          :outliner-op :save-block
+          :transact-opts {:repo repo
+                          :conn conn}}
+         (outliner-core/save-block! repo conn
+                                    (common-config/get-date-formatter (worker-state/get-config repo))
+                                    new-block))
+        :push-undo-redo))))
+
+
+(defn undo
+  [repo]
+  (when-let [op (pop-undo-op repo)]
+    (let [conn (worker-state/get-datascript-conn repo)
+          rev-op (reverse-op @conn op)]
+      (when (= :push-undo-redo (reverse-apply-op op conn repo))
+        (push-redo-ops repo [rev-op])))))
+
+(defn redo
+  [repo]
+  (when-let [op (pop-redo-op repo)]
+    (let [conn (worker-state/get-datascript-conn repo)
+          rev-op (reverse-op @conn op)]
+      (when (= :push-undo-redo (reverse-apply-op op conn repo))
+        (push-undo-ops repo [rev-op])))))
+
+
+;;; listen db changes and push undo-ops
+
+(def ^:private entity-map-pull-pattern
   [:block/uuid
    {:block/left [:block/uuid]}
    {:block/parent [:block/uuid]}
@@ -102,16 +220,14 @@
       (update m :block/tags (partial mapv :block/uuid))
       m)))
 
-(defn normal-block?
+(defn- normal-block?
   [entity]
   (and (:block/parent entity)
        (:block/left entity)))
 
 
-(defn entity-datoms=>op
+(defn- entity-datoms=>ops
   [db-before db-after id->attr->datom entity-datoms]
-  {:post [(or (nil? %)
-              (undo-op-validator %))]}
   (when-let [e (ffirst entity-datoms)]
     (let [attr->datom (id->attr->datom e)]
       (when (seq attr->datom)
@@ -124,31 +240,37 @@
           (cond
             (and (not add1?) block-uuid
                  (normal-block? entity-before))
-            [:remove-block
-             {:block-uuid (:block/uuid entity-before)
-              :block-entity-map (->block-entity-map db-before e)}]
+            [[:remove-block
+              {:block-uuid (:block/uuid entity-before)
+               :block-entity-map (->block-entity-map db-before e)}]]
 
             (and add1? block-uuid
                  (normal-block? entity-after))
-            [:insert-block {:block-uuid (:block/uuid entity-after)}]
+            [[:insert-block {:block-uuid (:block/uuid entity-after)}]]
 
             (and (or add3? add4?)
                  (normal-block? entity-after))
-            [:move-block
-             {:block-uuid (:block/uuid entity-after)
-              :block-origin-left (:block/uuid (:block/left entity-before))
-              :block-origin-parent (:block/uuid (:block/parent entity-before))}]
+            (cond-> [[:move-block
+                      {:block-uuid (:block/uuid entity-after)
+                       :block-origin-left (:block/uuid (:block/left entity-before))
+                       :block-origin-parent (:block/uuid (:block/parent entity-before))}]]
+              (and add2? block-content)
+              (conj [:update-block
+                     {:block-uuid (:block/uuid entity-after)
+                      :block-origin-content (:block/content entity-before)}]))
 
             (and add2? block-content
                  (normal-block? entity-after))
-            [:update-block
-             {:block-uuid (:block/uuid entity-after)
-              :block-origin-content (:block/content entity-before)}]))))))
+            [[:update-block
+              {:block-uuid (:block/uuid entity-after)
+               :block-origin-content (:block/content entity-before)}]]))))))
 
-(defn generate-undo-ops
-  [_repo db-before db-after same-entity-datoms-coll id->attr->datom]
-  (let [ops (keep (partial entity-datoms=>op db-before db-after id->attr->datom) same-entity-datoms-coll)]
-    (prn ::debug-undo-ops ops)))
+(defn- generate-undo-ops
+  [repo db-before db-after same-entity-datoms-coll id->attr->datom]
+  (let [ops (mapcat (partial entity-datoms=>ops db-before db-after id->attr->datom) same-entity-datoms-coll)]
+    (assert (undo-ops-validator ops) ops)
+    (when (seq ops)
+      (push-undo-ops repo ops))))
 
 
 (defmethod db-listener/listen-db-changes :gen-undo-ops
@@ -156,3 +278,5 @@
              repo id->attr->datom same-entity-datoms-coll]}]
   (when (:gen-undo-op? tx-meta true)
     (generate-undo-ops repo db-before db-after same-entity-datoms-coll id->attr->datom)))
+
+;;; listen db changes and push undo-ops (ends)

+ 2 - 0
src/test/frontend/worker/undo_redo_test.cljs

@@ -8,4 +8,6 @@
   ;; TODO: add tests for undo-redo
   undo-redo/undo-op-schema
   undo-redo/reverse-op
+  undo-redo/undo
+  undo-redo/redo
   )