mirror of
https://github.com/logseq/logseq.git
synced 2026-05-05 19:36:35 +00:00
fix(undo): sort&merge undo-ops
because maybe some ops depend-on others, e.g. insert parent first then its children.
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
[frontend.worker.db-listener :as db-listener]
|
||||
[frontend.worker.state :as worker-state]
|
||||
[logseq.common.config :as common-config]
|
||||
[logseq.common.util :as common-util]
|
||||
[logseq.outliner.core :as outliner-core]
|
||||
[logseq.outliner.transaction :as outliner-tx]
|
||||
[malli.core :as m]
|
||||
@@ -27,9 +28,9 @@ so when undo, it will undo [<op0> <op1> <op2>] instead of [<op1> <op2>]")
|
||||
"boundary of one or more undo-ops.
|
||||
when one undo/redo will operate on all ops between two ::boundary")
|
||||
|
||||
(sr/defkeyword ::insert-block
|
||||
"when a block is inserted, generate a ::insert-block undo-op.
|
||||
when undo this op, the related block will be removed.")
|
||||
(sr/defkeyword ::insert-blocks
|
||||
"when some blocks are inserted, generate a ::insert-blocks undo-op.
|
||||
when undo this op, the related blocks will be removed.")
|
||||
|
||||
(sr/defkeyword ::move-block
|
||||
"when a block is moved, generate a ::move-block undo-op.")
|
||||
@@ -54,10 +55,10 @@ when undo this op, this original entity-map will be transacted back into db")
|
||||
[:multi {:dispatch first}
|
||||
[::boundary
|
||||
[:cat :keyword]]
|
||||
[::insert-block
|
||||
[::insert-blocks
|
||||
[:cat :keyword
|
||||
[:map
|
||||
[:block-uuid :uuid]]]]
|
||||
[:block-uuids [:sequential :uuid]]]]]
|
||||
[::move-block
|
||||
[:cat :keyword
|
||||
[:map
|
||||
@@ -113,25 +114,29 @@ when undo this op, this original entity-map will be transacted back into db")
|
||||
(seq (:block/tags m)) (update :block/tags (partial mapv :block/uuid)))))
|
||||
|
||||
(defn- reverse-op
|
||||
"return ops"
|
||||
[db op]
|
||||
(let [block-uuid (:block-uuid (second op))]
|
||||
(case (first op)
|
||||
::boundary op
|
||||
::boundary [op]
|
||||
|
||||
::insert-block
|
||||
[::remove-block
|
||||
{:block-uuid block-uuid
|
||||
:block-entity-map (->block-entity-map db [:block/uuid block-uuid])}]
|
||||
::insert-blocks
|
||||
(mapv
|
||||
(fn [block-uuid]
|
||||
[::remove-block
|
||||
{:block-uuid block-uuid
|
||||
:block-entity-map (->block-entity-map db [:block/uuid block-uuid])}])
|
||||
(:block-uuids (second op)))
|
||||
|
||||
::move-block
|
||||
(let [b (d/entity db [:block/uuid block-uuid])]
|
||||
[::move-block
|
||||
{:block-uuid block-uuid
|
||||
:block-origin-left (:block/uuid (:block/left b))
|
||||
:block-origin-parent (:block/uuid (:block/parent b))}])
|
||||
[[::move-block
|
||||
{:block-uuid block-uuid
|
||||
:block-origin-left (:block/uuid (:block/left b))
|
||||
:block-origin-parent (:block/uuid (:block/parent b))}]])
|
||||
|
||||
::remove-block
|
||||
[::insert-block {:block-uuid block-uuid}]
|
||||
[[::insert-blocks {:block-uuids [block-uuid]}]]
|
||||
|
||||
::update-block
|
||||
(let [value-keys (set (keys (second op)))
|
||||
@@ -142,11 +147,11 @@ when undo this op, this original entity-map will be transacted back into db")
|
||||
(mapv :block/uuid (:block/tags block-entity)))
|
||||
block-origin-collapsed (when (contains? value-keys :block-origin-collapsed)
|
||||
(boolean (:block/collapsed? block-entity)))]
|
||||
[::update-block
|
||||
(cond-> {:block-uuid block-uuid}
|
||||
(some? block-origin-content) (assoc :block-origin-content block-origin-content)
|
||||
(some? block-origin-tags) (assoc :block-origin-tags block-origin-tags)
|
||||
(some? block-origin-collapsed) (assoc :block-origin-collapsed block-origin-collapsed))]))))
|
||||
[[::update-block
|
||||
(cond-> {:block-uuid block-uuid}
|
||||
(some? block-origin-content) (assoc :block-origin-content block-origin-content)
|
||||
(some? block-origin-tags) (assoc :block-origin-tags block-origin-tags)
|
||||
(some? block-origin-collapsed) (assoc :block-origin-collapsed block-origin-collapsed))]]))))
|
||||
|
||||
(def ^:private apply-conj-vec (partial apply (fnil conj [])))
|
||||
|
||||
@@ -248,27 +253,46 @@ when undo this op, this original entity-map will be transacted back into db")
|
||||
(assoc :block/updated-at (:block/updated-at block-entity-map))
|
||||
|
||||
(seq (:block/tags block-entity-map))
|
||||
(assoc :block/tags (mapv (partial vector :block/uuid)
|
||||
(:block/tags block-entity-map))))]
|
||||
(assoc :block/tags (some->> (:block/tags block-entity-map)
|
||||
(map (partial vector :block/uuid))
|
||||
(d/pull-many @conn [:db/id])
|
||||
(keep :db/id))))]
|
||||
left-entity {:sibling? sibling? :keep-uuid? true}))
|
||||
(conj [:push-undo-redo])))))))
|
||||
|
||||
(defmethod reverse-apply-op ::insert-block
|
||||
(defn- sort-block-entities
|
||||
"return nil when there are other children existing"
|
||||
[block-entities]
|
||||
(let [sorted-block-entities (common-util/sort-coll-by-dependency
|
||||
:block/uuid (comp :block/uuid :block/parent) block-entities)
|
||||
block-uuid-set (set (map :block/uuid sorted-block-entities))]
|
||||
(when-not
|
||||
(some ;; check no other children
|
||||
(fn [ent]
|
||||
(not-empty (set/difference (set (map :block/uuid (:block/_parent ent))) block-uuid-set)))
|
||||
sorted-block-entities)
|
||||
|
||||
sorted-block-entities)))
|
||||
|
||||
(defmethod reverse-apply-op ::insert-blocks
|
||||
[op conn repo]
|
||||
(let [[_ {:keys [block-uuid]}] op]
|
||||
(when-let [block-entity (d/entity @conn [:block/uuid block-uuid])]
|
||||
(when (empty? (:block/_parent block-entity)) ;if have children, skip
|
||||
(some->>
|
||||
(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}))
|
||||
(conj [:push-undo-redo]))))))
|
||||
(let [[_ {:keys [block-uuids]}] op]
|
||||
(when-let [block-entities (->> block-uuids
|
||||
(keep #(d/entity @conn [:block/uuid %]))
|
||||
sort-block-entities
|
||||
reverse
|
||||
not-empty)]
|
||||
(some->>
|
||||
(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-entities
|
||||
{:children? false}))
|
||||
(conj [:push-undo-redo])))))
|
||||
|
||||
(defmethod reverse-apply-op ::move-block
|
||||
[op conn repo]
|
||||
@@ -314,18 +338,35 @@ when undo this op, this original entity-map will be transacted back into db")
|
||||
|
||||
(when r2 [:push-undo-redo r2]))))))
|
||||
|
||||
(defn- sort&merge-ops
|
||||
[ops]
|
||||
(let [groups (group-by first ops)
|
||||
remove-ops (groups ::remove-block)
|
||||
insert-ops (groups ::insert-blocks)
|
||||
other-ops (apply concat (vals (dissoc groups ::remove-block ::insert-blocks)))
|
||||
sorted-remove-ops (reverse
|
||||
(common-util/sort-coll-by-dependency (comp :block-uuid second)
|
||||
(comp :block/left :block-entity-map second)
|
||||
remove-ops))
|
||||
insert-op (some->> (seq insert-ops)
|
||||
(mapcat (fn [op] (:block-uuids (second op))))
|
||||
(hash-map :block-uuids)
|
||||
(vector ::insert-blocks))]
|
||||
(cond-> (concat sorted-remove-ops other-ops)
|
||||
insert-op (conj insert-op))))
|
||||
|
||||
(defn undo
|
||||
[repo page-block-uuid conn]
|
||||
(if-let [ops (not-empty (pop-undo-ops repo page-block-uuid))]
|
||||
(let [redo-ops-to-push (transient [])]
|
||||
(batch-tx/with-batch-tx-mode conn
|
||||
(doseq [op ops]
|
||||
(let [rev-op (reverse-op @conn op)
|
||||
(let [rev-ops (reverse-op @conn op)
|
||||
r (reverse-apply-op op conn repo)]
|
||||
(when (= :push-undo-redo (first r))
|
||||
(some-> *undo-redo-info-for-test* (reset! {:op op :tx (second r)}))
|
||||
(conj! redo-ops-to-push rev-op)))))
|
||||
(when-let [rev-ops (not-empty (persistent! redo-ops-to-push))]
|
||||
(apply conj! redo-ops-to-push rev-ops)))))
|
||||
(when-let [rev-ops (not-empty (sort&merge-ops (persistent! redo-ops-to-push)))]
|
||||
(push-redo-ops repo page-block-uuid (cons boundary rev-ops)))
|
||||
nil)
|
||||
|
||||
@@ -339,12 +380,12 @@ when undo this op, this original entity-map will be transacted back into db")
|
||||
(let [undo-ops-to-push (transient [])]
|
||||
(batch-tx/with-batch-tx-mode conn
|
||||
(doseq [op ops]
|
||||
(let [rev-op (reverse-op @conn op)
|
||||
(let [rev-ops (reverse-op @conn op)
|
||||
r (reverse-apply-op op conn repo)]
|
||||
(when (= :push-undo-redo (first r))
|
||||
(some-> *undo-redo-info-for-test* (reset! {:op op :tx (second r)}))
|
||||
(conj! undo-ops-to-push rev-op)))))
|
||||
(when-let [rev-ops (not-empty (persistent! undo-ops-to-push))]
|
||||
(apply conj! undo-ops-to-push rev-ops)))))
|
||||
(when-let [rev-ops (not-empty (sort&merge-ops (persistent! undo-ops-to-push)))]
|
||||
(push-undo-ops repo page-block-uuid (cons boundary rev-ops)))
|
||||
nil)
|
||||
|
||||
@@ -375,7 +416,7 @@ when undo this op, this original entity-map will be transacted back into db")
|
||||
|
||||
(and add1? block-uuid
|
||||
(normal-block? entity-after))
|
||||
[[::insert-block {:block-uuid (:block/uuid entity-after)}]]
|
||||
[[::insert-blocks {:block-uuids [(:block/uuid entity-after)]}]]
|
||||
|
||||
(and (or add3? add4?)
|
||||
(normal-block? entity-after))
|
||||
@@ -432,7 +473,8 @@ when undo this op, this original entity-map will be transacted back into db")
|
||||
(defn- generate-undo-ops
|
||||
[repo db-before db-after same-entity-datoms-coll id->attr->datom gen-boundary-op?]
|
||||
(when-let [page-block-uuid (find-page-block-uuid db-before db-after same-entity-datoms-coll)]
|
||||
(let [ops (mapcat (partial entity-datoms=>ops db-before db-after id->attr->datom) same-entity-datoms-coll)]
|
||||
(let [ops (mapcat (partial entity-datoms=>ops db-before db-after id->attr->datom) same-entity-datoms-coll)
|
||||
ops (sort&merge-ops ops)]
|
||||
(when (seq ops)
|
||||
(push-undo-ops repo page-block-uuid (if gen-boundary-op? (cons boundary ops) ops))))))
|
||||
|
||||
@@ -468,4 +510,5 @@ when undo this op, this original entity-map will be transacted back into db")
|
||||
:n n})))
|
||||
|
||||
(remove-watch (:undo/repo->pege-block-uuid->undo-ops @worker-state/*state) :xxx)
|
||||
(remove-watch (:undo/repo->pege-block-uuid->redo-ops @worker-state/*state) :xxx))
|
||||
(remove-watch (:undo/repo->pege-block-uuid->redo-ops @worker-state/*state) :xxx)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user