fix: stabilize uuid-based outliner ops and undo/redo replay

This commit is contained in:
Tienson Qin
2026-04-14 22:09:37 +08:00
parent cfe00a5e71
commit fc76192631
20 changed files with 925 additions and 276 deletions

View File

@@ -664,7 +664,7 @@
(let [prev-order (db-order/get-prev-order (db/get-db) nil (:db/id over))]
(db-order/gen-key prev-order over-order)))]
(db/transact! (state/get-current-repo)
[{:db/id (:db/id active)
[{:block/uuid (:block/uuid active)
:block/order new-order}
(outliner-core/block-with-updated-at
{:db/id (:db/id block)})]

View File

@@ -36,6 +36,10 @@
(when (contains? #{:logseq.property/status :logseq.property/priority} (:db/ident property))
(state/pub-event! [:init/commands])))
(defn- ->block-lookup-id
[block]
[:block/uuid (:block/uuid block)])
(defn- <upsert-closed-value!
"Create new closed value and returns its block UUID."
[property item]
@@ -64,7 +68,7 @@
(if-let [ent (:logseq.property/description property)]
(db/transact! (state/get-current-repo)
[(outliner-core/block-with-updated-at
{:db/id (:db/id ent) :block/title description})]
{:block/uuid (:block/uuid ent) :block/title description})]
{:outliner-op :save-block})
(when-not (string/blank? description)
(db-property-handler/set-block-property!
@@ -121,8 +125,11 @@
(if (= value :no-tag)
(toggle-fn)
(p/let [result (<create-class-if-not-exists! value)
value' (or result value)
tx-data [[(if select? :db/add :db/retract) (:db/id property) :logseq.property/classes [:block/uuid value']]]
class-uuid (or result value)
tx-data [[(if select? :db/add :db/retract)
(->block-lookup-id property)
:logseq.property/classes
[:block/uuid class-uuid]]]
_ (db/transact! (state/get-current-repo) tx-data {:outliner-op :update-property})]
(when-not multiple-choices? (toggle-fn)))))}]
@@ -468,10 +475,10 @@
(db-order/gen-key prev-order over-order)))]
(db/transact! (state/get-current-repo)
[{:db/id (:db/id active)
[{:block/uuid (:block/uuid active)
:block/order new-order}
(outliner-core/block-with-updated-at
{:db/id (:db/id property)})]
{:block/uuid (:block/uuid property)})]
{:outliner-op :save-block})))})]
(shui/dropdown-menu-separator)])

View File

@@ -48,7 +48,9 @@
(defn wrap-parse-block
[{:block/keys [title level] :as block}]
(let [block (or (and (:db/id block) (db/entity (:db/id block))) block)
(let [block (or (and (:db/id block) (db/entity (:db/id block)))
(and (:block/uuid block) (db/entity [:block/uuid (:block/uuid block)]))
block)
block (if (nil? title)
block
(let [ast (mldoc/->edn (string/trim title) :markdown)

View File

@@ -52,8 +52,9 @@
(ldb/built-in? page-entity)
(notification/show! "Built-in pages can't be used as tags" :error)
:else
;; FIXME: should move to worker
(let [txs [(db-class/build-new-class (db/get-db)
{:db/id (:db/id page-entity)
{:block/uuid [:block/uuid (:block/uuid page-entity)]
:block/title (:block/title page-entity)
:block/created-at (:block/created-at page-entity)})
[:db/retract (:db/id page-entity) :block/tags :logseq.class/Page]]]

View File

@@ -250,8 +250,7 @@
(defn- save-block-inner!
[block value opts]
(let [block {:db/id (:db/id block)
:block/uuid (:block/uuid block)
(let [block {:block/uuid (:block/uuid block)
:block/title value}
block' (-> (wrap-parse-block block)
;; :block/uuid might be changed when backspace/delete
@@ -1823,7 +1822,7 @@
(content-update-fn (:block/title block))
(:block/title block))]
(merge (apply dissoc block (conj (if-not keep-uuid? [:block/_refs] [])))
{:block/page {:db/id (:db/id page)}
{:block/page {:db/id [:block/uuid (:block/uuid page)]}
:block/title new-content})))
(defn- edit-last-block-after-inserted!
@@ -1842,11 +1841,12 @@
(defn- unrecycle-tx-data
[root]
[[:db/retract (:db/id root) :logseq.property/deleted-at]
[:db/retract (:db/id root) :logseq.property/deleted-by-ref]
[:db/retract (:db/id root) :logseq.property.recycle/original-parent]
[:db/retract (:db/id root) :logseq.property.recycle/original-page]
[:db/retract (:db/id root) :logseq.property.recycle/original-order]])
(let [root-id [:block/uuid (:block/uuid root)]]
[[:db/retract root-id :logseq.property/deleted-at]
[:db/retract root-id :logseq.property/deleted-by-ref]
[:db/retract root-id :logseq.property.recycle/original-parent]
[:db/retract root-id :logseq.property.recycle/original-page]
[:db/retract root-id :logseq.property.recycle/original-order]]))
(defn paste-blocks
"Given a vec of blocks, insert them into the target page.

View File

@@ -21,50 +21,63 @@
(conj! *outliner-ops* result)
result)
(defn- ->block-id
[block-or-id]
(cond
(de/entity? block-or-id)
(:block/uuid block-or-id)
(map? block-or-id)
(:block/uuid block-or-id)
:else
block-or-id))
(defn save-block!
[block & {:as opts}]
(op-transact!
(when-let [block' (if (de/entity? block)
(assoc (.-kv ^js block) :db/id (:db/id block))
(dissoc (.-kv ^js block) :db/id)
block)]
[:save-block [block' opts]])))
(defn insert-blocks!
[blocks target-block opts]
(op-transact!
(let [id (:db/id target-block)]
(let [id (->block-id target-block)]
[:insert-blocks [blocks id opts]])))
(defn apply-template!
[template-id target-block opts]
(op-transact!
(let [id (:db/id target-block)]
[:apply-template [template-id id opts]])))
(let [template-id' (->block-id template-id)
id (->block-id target-block)]
[:apply-template [template-id' id opts]])))
(defn delete-blocks!
[blocks opts]
(op-transact!
(let [ids (map :db/id blocks)]
(let [ids (map ->block-id blocks)]
(when (seq ids)
[:delete-blocks [ids (current-user-delete-opts opts)]]))))
(defn move-blocks!
[blocks target-block opts]
(op-transact!
(let [ids (map :db/id blocks)
target-id (:db/id target-block)]
(let [ids (map ->block-id blocks)
target-id (->block-id target-block)]
[:move-blocks [ids target-id opts]])))
(defn move-blocks-up-down!
[blocks up?]
(op-transact!
(let [ids (map :db/id blocks)]
(let [ids (map ->block-id blocks)]
[:move-blocks-up-down [ids up?]])))
(defn indent-outdent-blocks!
[blocks indent? & {:as opts}]
(op-transact!
(let [ids (map :db/id blocks)]
(let [ids (map ->block-id blocks)]
[:indent-outdent-blocks [ids indent? opts]])))
(defn upsert-property!
@@ -75,47 +88,47 @@
(defn set-block-property!
[block-eid property-id value]
(op-transact!
[:set-block-property [block-eid property-id value]]))
[:set-block-property [(->block-id block-eid) property-id value]]))
(defn remove-block-property!
[block-eid property-id]
(op-transact!
[:remove-block-property [block-eid property-id]]))
[:remove-block-property [(->block-id block-eid) property-id]]))
(defn delete-property-value!
[block-eid property-id property-value]
(op-transact!
[:delete-property-value [block-eid property-id property-value]]))
[:delete-property-value [(->block-id block-eid) property-id property-value]]))
(defn batch-delete-property-value!
[block-eids property-id property-value]
(op-transact!
[:batch-delete-property-value [block-eids property-id property-value]]))
[:batch-delete-property-value [(mapv ->block-id block-eids) property-id property-value]]))
(defn create-property-text-block!
[block-id property-id value opts]
(op-transact!
[:create-property-text-block [block-id property-id value opts]]))
[:create-property-text-block [(->block-id block-id) property-id value opts]]))
(defn batch-set-property!
[block-ids property-id value opts]
(op-transact!
[:batch-set-property [block-ids property-id value opts]]))
[:batch-set-property [(mapv ->block-id block-ids) property-id value opts]]))
(defn batch-remove-property!
[block-ids property-id]
(op-transact!
[:batch-remove-property [block-ids property-id]]))
[:batch-remove-property [(mapv ->block-id block-ids) property-id]]))
(defn class-add-property!
[class-id property-id]
(op-transact!
[:class-add-property [class-id property-id]]))
[:class-add-property [(->block-id class-id) property-id]]))
(defn class-remove-property!
[class-id property-id]
(op-transact!
[:class-remove-property [class-id property-id]]))
[:class-remove-property [(->block-id class-id) property-id]]))
(defn upsert-closed-value!
[property-id closed-value-config]
@@ -125,7 +138,7 @@
(defn delete-closed-value!
[property-id value-block-id]
(op-transact!
[:delete-closed-value [property-id value-block-id]]))
[:delete-closed-value [property-id (->block-id value-block-id)]]))
(defn add-existing-values-to-closed-values!
[property-id values]

View File

@@ -532,13 +532,31 @@
current-sibling
(recur (ldb/get-left-sibling sibling))))))
(defn- rebase-target-ref
[target-id]
(cond
(and (vector? target-id)
(= :block/uuid (first target-id))
(uuid? (second target-id)))
target-id
(uuid? target-id)
[:block/uuid target-id]
(and (map? target-id) (uuid? (:block/uuid target-id)))
[:block/uuid (:block/uuid target-id)]
:else
target-id))
(defn- rebase-resolve-target-and-sibling
[current-db rebase-db-before target-id sibling?]
(let [target (d/entity current-db target-id)
(let [target-ref (rebase-target-ref target-id)
target (d/entity current-db target-ref)
target-before (when rebase-db-before
(d/entity rebase-db-before target-id))
(d/entity rebase-db-before target-ref))
parent-before (when rebase-db-before
(:block/parent (d/entity rebase-db-before target-id)))]
(:block/parent (d/entity rebase-db-before target-ref)))]
(cond
target
[target sibling?]
@@ -552,6 +570,39 @@
:else
nil)))
(defn- template-parent-ref
[parent]
(cond
(and (vector? parent) (= :block/uuid (first parent)))
parent
(uuid? parent)
[:block/uuid parent]
(and (map? parent) (uuid? (:block/uuid parent)))
[:block/uuid (:block/uuid parent)]
:else
parent))
(defn- sanitize-template-block
[current-db rebase-db-before block]
(let [m (into {} block)
block-id (:db/id m)
block-uuid (or (:block/uuid m)
(when (number? block-id)
(or (some-> rebase-db-before (d/entity block-id) :block/uuid)
(some-> (d/entity current-db block-id) :block/uuid)))
(when (and (vector? block-id)
(= :block/uuid (first block-id))
(uuid? (second block-id)))
(second block-id)))]
(cond-> (-> m
(dissoc :db/id :block/order :block/page :block/tx-id)
(update :block/parent template-parent-ref))
(uuid? block-uuid)
(assoc :block/uuid block-uuid))))
(defn- ^:large-vars/cleanup-todo replay-canonical-outliner-op!
[conn [op args] rebase-db-before]
(case op
@@ -588,12 +639,40 @@
(when-not (and template-id' (d/entity @conn template-id') target)
(invalid-rebase-op! op {:args args
:reason :missing-template-or-target-block}))
(outliner-op/apply-ops!
conn
[[:apply-template [template-id'
target-id'
(assoc opts :sibling? sibling?)]]]
{:gen-undo-ops? false}))
(let [template-uuid (:block/uuid (d/entity @conn template-id'))
target-uuid (:block/uuid target)]
(when-not (and (uuid? template-uuid) (uuid? target-uuid))
(invalid-rebase-op! op {:args args
:reason :missing-template-or-target-uuid}))
(let [replace-empty-target? (:replace-empty-target? opts)
template-blocks' (some->> (:template-blocks opts)
(map-indexed
(fn [idx block]
(let [block' (sanitize-template-block @conn rebase-db-before block)
block'' (if (and replace-empty-target?
(zero? idx)
(nil? (:block/uuid block')))
;; Keep replace-empty-target replay consistent with
;; initial apply-template payload where the first
;; block uuid is the target uuid.
(assoc block' :block/uuid target-uuid)
block')]
(when (:block/uuid block'')
block''))))
(remove nil?)
seq
vec)
opts' (cond-> (-> opts
(assoc :sibling? sibling?)
(dissoc :template-blocks))
template-blocks'
(assoc :template-blocks template-blocks'))]
(outliner-op/apply-ops!
conn
[[:apply-template [template-uuid
target-uuid
opts']]]
{:gen-undo-ops? false}))))
:move-blocks
(let [[ids target-id opts] args