diff --git a/deps/db/src/logseq/db/common/entity_plus.cljc b/deps/db/src/logseq/db/common/entity_plus.cljc index 6a1e06fff5..79265656c9 100644 --- a/deps/db/src/logseq/db/common/entity_plus.cljc +++ b/deps/db/src/logseq/db/common/entity_plus.cljc @@ -44,7 +44,7 @@ (assert (empty? (last (data/diff immutable-db-ident-entities nil-db-ident-entities)))) -(def ^:private lookup-entity @#'entity/lookup-entity) +(def lookup-entity @#'entity/lookup-entity) (def ^:private *seen-immutable-entities (volatile! {})) diff --git a/deps/outliner/src/logseq/outliner/op/construct.cljc b/deps/outliner/src/logseq/outliner/op/construct.cljc index fc869002da..a29f5461b4 100644 --- a/deps/outliner/src/logseq/outliner/op/construct.cljc +++ b/deps/outliner/src/logseq/outliner/op/construct.cljc @@ -8,7 +8,8 @@ [logseq.db :as ldb] [logseq.db.frontend.content :as db-content] [logseq.db.frontend.property :as db-property] - [logseq.db.frontend.property.type :as db-property-type])) + [logseq.db.frontend.property.type :as db-property-type] + [logseq.db.common.entity-plus :as entity-plus])) (def ^:private semantic-outliner-ops #{:save-block @@ -62,6 +63,8 @@ (when-let [id (:block/uuid x)] (:db/id (d/entity db [:block/uuid id]))))] (stable-entity-ref db eid)) + (uuid? x) + [:block/uuid x] (and (integer? x) (not (neg? x))) (if-let [ent (d/entity db x)] (cond @@ -71,6 +74,19 @@ x) :else x)) +(defn- stable-block-ref-with-tx-data + [db tx-data x] + (let [ref (stable-entity-ref db x)] + (if (and (integer? ref) (not (neg? ref))) + (or (some (fn [item] + (when (and (= ref (:e item)) + (= :block/uuid (:a item)) + (uuid? (:v item))) + [:block/uuid (:v item)])) + tx-data) + ref) + ref))) + (defn- sanitize-ref-value [db v] (cond @@ -464,7 +480,7 @@ :move-blocks (let [[ids target-id opts] args] [:move-blocks [(stable-id-coll db ids) - (stable-entity-ref db target-id) + (stable-block-ref-with-tx-data db tx-data target-id) opts]]) :delete-blocks @@ -569,7 +585,7 @@ :delete-closed-value (let [[property-id value-block-id] args] [:delete-closed-value [(stable-entity-ref db property-id) - (stable-entity-ref db value-block-id)]]) + (stable-block-ref-with-tx-data db tx-data value-block-id)]]) [op args])) @@ -598,6 +614,9 @@ (integer? block) (d/entity db block) + (uuid? block) + (d/entity db [:block/uuid block]) + (vector? block) (d/entity db block) @@ -645,8 +664,8 @@ (defn- block-property-value [db block-id property-id] - (when-let [value (some-> (d/entity db block-id) - (get property-id))] + ;; `get` lookup may include derived defaults. + (when-let [value (entity-plus/lookup-entity (d/entity db block-id) property-id)] (property-ref-value db property-id value))) (defn- property-history-refs-from-tx-data diff --git a/deps/outliner/test/logseq/outliner/op_construct_test.cljs b/deps/outliner/test/logseq/outliner/op_construct_test.cljs index d5c4a3427f..e5c0599008 100644 --- a/deps/outliner/test/logseq/outliner/op_construct_test.cljs +++ b/deps/outliner/test/logseq/outliner/op_construct_test.cljs @@ -157,6 +157,40 @@ (is (= [[:delete-blocks [[[:block/uuid child-uuid]] {}]]] forward-outliner-ops))))) +(deftest derive-history-outliner-ops-move-blocks-resolves-target-id-from-tx-data-test + (testing "move-blocks should resolve stale numeric target id using tx-data block uuid" + (let [conn (db-test/create-conn-with-blocks + {:pages-and-blocks + [{:page {:block/title "page"} + :blocks [{:block/title "child"}]}]}) + child (db-test/find-block-by-content @conn "child") + stale-target-id 9999999 + target-uuid (random-uuid) + tx-data [{:e stale-target-id :a :block/uuid :v target-uuid :added false}] + tx-meta {:outliner-op :move-blocks + :outliner-ops [[:move-blocks [[(:db/id child)] stale-target-id {:sibling? true}]]]} + {:keys [forward-outliner-ops]} + (op-construct/derive-history-outliner-ops @conn @conn tx-data tx-meta)] + (is (= [:block/uuid target-uuid] + (get-in forward-outliner-ops [0 1 1])))))) + +(deftest derive-history-outliner-ops-delete-closed-value-resolves-value-id-from-tx-data-test + (testing "delete-closed-value should resolve stale numeric value block id using tx-data block uuid" + (let [conn (db-test/create-conn-with-blocks + {:properties {:status {:logseq.property/type :default}} + :pages-and-blocks []}) + property-page (d/entity @conn :user.property/status) + property-id (:db/id property-page) + stale-value-id 8888888 + value-uuid (random-uuid) + tx-data [{:e stale-value-id :a :block/uuid :v value-uuid :added false}] + tx-meta {:outliner-op :delete-closed-value + :outliner-ops [[:delete-closed-value [property-id stale-value-id]]]} + {:keys [forward-outliner-ops]} + (op-construct/derive-history-outliner-ops @conn @conn tx-data tx-meta)] + (is (= [:block/uuid value-uuid] + (get-in forward-outliner-ops [0 1 1])))))) + (deftest derive-history-outliner-ops-builds-delete-page-inverse-for-class-property-and-today-page-test (testing "delete-page inverse restores hard-retracted class/property/today pages with stable db/ident" (let [today (date-time-util/ms->journal-day (js/Date.)) diff --git a/src/test/frontend/worker/undo_redo_test.cljs b/src/test/frontend/worker/undo_redo_test.cljs index 705d40dd3f..ab5aa47acc 100644 --- a/src/test/frontend/worker/undo_redo_test.cljs +++ b/src/test/frontend/worker/undo_redo_test.cljs @@ -375,6 +375,30 @@ (is (map? redo-result)) (is (= "local-1" (:block/title (d/entity @conn [:block/uuid child-uuid])))))))) +(deftest undo-cycle-todo-removes-task-class-test + (testing "undoing first status set should remove task class and status" + (worker-undo-redo/clear-history! test-repo) + (let [conn (worker-state/get-datascript-conn test-repo) + block-uuid (:block/uuid (db-test/find-block-by-content @conn "task"))] + (outliner-op/apply-ops! conn + [[:set-block-property [[:block/uuid block-uuid] + :logseq.property/status + :logseq.property/status.todo]]] + (local-tx-meta {:client-id "test-client"})) + + (let [block-after-set (d/entity @conn [:block/uuid block-uuid])] + (is (= :logseq.property/status.todo + (some-> (:logseq.property/status block-after-set) :db/ident))) + (is (contains? (set (map :db/ident (:block/tags block-after-set))) + :logseq.class/Task))) + + (is (map? (worker-undo-redo/undo test-repo))) + (let [block-after-undo (d/entity @conn [:block/uuid block-uuid])] + (is (not (contains? (d/pull @conn [:logseq.property/status] [:block/uuid block-uuid]) + :logseq.property/status))) + (is (not (contains? (set (map :db/ident (:block/tags block-after-undo))) + :logseq.class/Task))))))) + (deftest undo-delete-page-restores-page-out-of-recycle-test (testing "undoing delete-page should restore page and clear recycle marker" (worker-undo-redo/clear-history! test-repo)