diff --git a/deps/db/.carve/ignore b/deps/db/.carve/ignore index fc1f897f1d..a3c2bddbda 100644 --- a/deps/db/.carve/ignore +++ b/deps/db/.carve/ignore @@ -35,6 +35,8 @@ logseq.db.common.view/get-view-data ;; API logseq.db.common.initial-data/with-parent ;; API +logseq.db.common.initial-data/get-block-refs +;; API logseq.db.common.initial-data/get-block-and-children ;; API logseq.db.common.initial-data/get-initial-data diff --git a/deps/db/script/replay_sync_sqlite.cljs b/deps/db/script/replay_sync_sqlite.cljs index d9cd87d858..d835e60acc 100644 --- a/deps/db/script/replay_sync_sqlite.cljs +++ b/deps/db/script/replay_sync_sqlite.cljs @@ -441,6 +441,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 replay-canonical-outliner-op! [conn [op args] rebase-db-before] (case op @@ -473,12 +506,26 @@ (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 [template-blocks' (some->> (:template-blocks opts) + (mapv #(sanitize-template-block @conn rebase-db-before %)) + (filter :block/uuid) + seq) + 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 diff --git a/deps/db/src/logseq/db/common/initial_data.cljs b/deps/db/src/logseq/db/common/initial_data.cljs index 0c125deed2..4996f811d6 100644 --- a/deps/db/src/logseq/db/common/initial_data.cljs +++ b/deps/db/src/logseq/db/common/initial_data.cljs @@ -181,11 +181,6 @@ (and entity-ident (some? (get ref-block entity-ident))))))) -(defn hidden-ref? - "Whether ref-block (for block with the `id`) should be hidden." - [db ref-block id] - ((hidden-ref-pred db id) ref-block)) - (defn get-block-refs [db id] (let [with-alias (->> (get-block-alias db id) diff --git a/deps/outliner/src/logseq/outliner/core.cljs b/deps/outliner/src/logseq/outliner/core.cljs index 850bff5745..f4e4352600 100644 --- a/deps/outliner/src/logseq/outliner/core.cljs +++ b/deps/outliner/src/logseq/outliner/core.cljs @@ -23,32 +23,33 @@ (defn- direct-op-entry [outliner-op args] - (case outliner-op - :save-block - (let [[_conn block opts] args] - [:save-block [block opts]]) + (letfn [(->block-id [block] (:block/uuid block))] + (case outliner-op + :save-block + (let [[_conn block opts] args] + [:save-block [block opts]]) - :insert-blocks - (let [[_conn blocks target-block opts] args] - [:insert-blocks [blocks (:db/id target-block) opts]]) + :insert-blocks + (let [[_conn blocks target-block opts] args] + [:insert-blocks [blocks (->block-id target-block) opts]]) - :delete-blocks - (let [[_conn blocks opts] args] - [:delete-blocks [(mapv :db/id blocks) opts]]) + :delete-blocks + (let [[_conn blocks opts] args] + [:delete-blocks [(mapv ->block-id blocks) opts]]) - :move-blocks - (let [[_conn blocks target-block opts] args] - [:move-blocks [(mapv :db/id blocks) (:db/id target-block) opts]]) + :move-blocks + (let [[_conn blocks target-block opts] args] + [:move-blocks [(mapv ->block-id blocks) (->block-id target-block) opts]]) - :move-blocks-up-down - (let [[_conn blocks up?] args] - [:move-blocks-up-down [(mapv :db/id blocks) up?]]) + :move-blocks-up-down + (let [[_conn blocks up?] args] + [:move-blocks-up-down [(mapv ->block-id blocks) up?]]) - :indent-outdent-blocks - (let [[_conn blocks indent? opts] args] - [:indent-outdent-blocks [(mapv :db/id blocks) indent? opts]]) + :indent-outdent-blocks + (let [[_conn blocks indent? opts] args] + [:indent-outdent-blocks [(mapv ->block-id blocks) indent? opts]]) - nil)) + nil))) (def ^:private block-map (mu/optional-keys @@ -944,8 +945,8 @@ (map :db/id) (set)) move-parents-to-child? (some parents' (map :db/id blocks)) - op-entry [:move-blocks [(mapv :db/id top-level-blocks) - (:db/id target-block) + op-entry [:move-blocks [(mapv :block/uuid top-level-blocks) + (:block/uuid target-block) opts]]] (when-not move-parents-to-child? (ldb/batch-transact-with-temp-conn! diff --git a/deps/outliner/src/logseq/outliner/op.cljs b/deps/outliner/src/logseq/outliner/op.cljs index 70a631b669..5b7f22088a 100644 --- a/deps/outliner/src/logseq/outliner/op.cljs +++ b/deps/outliner/src/logseq/outliner/op.cljs @@ -24,33 +24,33 @@ [:insert-blocks [:catn [:op :keyword] - [:args [:tuple ::blocks ::id ::option]]]] + [:args [:tuple ::blocks ::block-id ::option]]]] [:apply-template [:catn [:op :keyword] - [:args [:tuple ::id ::id ::option]]]] + [:args [:tuple ::block-id ::block-id ::option]]]] [:delete-blocks [:catn [:op :keyword] - [:args [:tuple ::ids ::option]]]] + [:args [:tuple ::block-ids ::option]]]] [:move-blocks [:catn [:op :keyword] - [:args [:tuple ::ids ::id ::option]]]] + [:args [:tuple ::block-ids ::block-id ::option]]]] [:move-blocks-up-down [:catn [:op :keyword] - [:args [:tuple ::ids :boolean]]]] + [:args [:tuple ::block-ids :boolean]]]] [:indent-outdent-blocks [:catn [:op :keyword] - [:args [:tuple ::ids :boolean ::option]]]] + [:args [:tuple ::block-ids :boolean ::option]]]] ;; properties [:upsert-property [:catn [:op :keyword] - [:args [:tuple ::property-id ::schema ::option]]]] + [:args [:tuple ::maybe-property-id ::schema ::option]]]] [:set-block-property [:catn [:op :keyword] @@ -70,7 +70,7 @@ [:create-property-text-block [:catn [:op :keyword] - [:args [:tuple ::block-id ::property-id ::value ::option]]]] + [:args [:tuple ::maybe-block-id ::property-id ::value ::option]]]] [:collapse-expand-block-property [:catn [:op :keyword] @@ -98,7 +98,7 @@ [:delete-closed-value [:catn [:op :keyword] - [:args [:tuple ::property-id ::value]]]] + [:args [:tuple ::property-id ::block-id]]]] [:add-existing-values-to-closed-values [:catn [:op :keyword] @@ -142,22 +142,21 @@ [:args [:tuple ::uuid ::emoji-id ::maybe-uuid]]]]]) (def ^:private ops-schema - [:schema {:registry {::id int? - ::block map? + [:schema {:registry {::block map? ::schema map? - ;; FIXME: use eid integer - ::block-id :any + ::block-id uuid? + ::maybe-block-id [:maybe ::block-id] ::block-ids [:sequential ::block-id] - ::class-id int? + ::class-id ::block-id ::emoji-id string? - ::property-id [:or int? keyword? nil?] + ::property-id qualified-keyword? + ::maybe-property-id [:maybe ::property-id] ::maybe-uuid [:maybe :uuid] ::value :any ::values [:sequential ::value] ::option [:maybe map?] ::import-edn map? ::blocks [:sequential ::block] - ::ids [:sequential ::id] ::uuid uuid? ::title string? ::tx-data [:sequential :any] @@ -217,7 +216,7 @@ (defn- apply-insert-blocks-op! [conn *result [blocks target-block-id opts]] - (when-let [target-block (d/entity @conn target-block-id)] + (when-let [target-block (d/entity @conn [:block/uuid target-block-id])] (let [result (outliner-core/insert-blocks! conn blocks target-block opts)] (reset! *result result)))) @@ -237,9 +236,9 @@ (defn- apply-template-op! [conn *result [template-id target-block-id opts]] - (when-let [target (d/entity @conn target-block-id)] + (when-let [target (d/entity @conn [:block/uuid target-block-id])] (let [blocks (or (some-> (:template-blocks opts) seq vec) - (template-children-blocks @conn template-id)) + (template-children-blocks @conn [:block/uuid template-id])) blocks (outliner-template/resolve-dynamic-template-blocks @conn target blocks)] (when (seq blocks) (let [sibling? (:sibling? opts) @@ -274,25 +273,25 @@ :delete-blocks (let [[block-ids opts] args - blocks (keep #(d/entity @conn %) block-ids)] + blocks (keep #(d/entity @conn [:block/uuid %]) block-ids)] (outliner-core/delete-blocks! conn blocks (merge opts opts'))) :move-blocks (let [[block-ids target-block-id opts] args - blocks (keep #(d/entity @conn %) block-ids) - target-block (d/entity @conn target-block-id)] + blocks (keep #(d/entity @conn [:block/uuid %]) block-ids) + target-block (d/entity @conn [:block/uuid target-block-id])] (when (and target-block (seq blocks)) (outliner-core/move-blocks! conn blocks target-block opts))) :move-blocks-up-down (let [[block-ids up?] args - blocks (keep #(d/entity @conn %) block-ids)] + blocks (keep #(d/entity @conn [:block/uuid %]) block-ids)] (when (seq blocks) (outliner-core/move-blocks-up-down! conn blocks up?))) :indent-outdent-blocks (let [[block-ids indent? opts] args - blocks (keep #(d/entity @conn %) block-ids)] + blocks (keep #(d/entity @conn [:block/uuid %]) block-ids)] (when (seq blocks) (outliner-core/indent-outdent-blocks! conn blocks indent? opts))) @@ -310,7 +309,9 @@ (apply outliner-property/delete-property-value! conn args) :create-property-text-block - (apply outliner-property/create-property-text-block! conn args) + (let [[block-id property-id v opts] args + block-id' (when block-id [:block/uuid block-id])] + (outliner-property/create-property-text-block! conn block-id' property-id v opts)) :batch-set-property (apply outliner-property/batch-set-property! conn args) @@ -322,16 +323,19 @@ (apply outliner-property/batch-delete-property-value! conn args) :class-add-property - (apply outliner-property/class-add-property! conn args) + (let [[class-id property-id] args] + (outliner-property/class-add-property! conn [:block/uuid class-id] property-id)) :class-remove-property - (apply outliner-property/class-remove-property! conn args) + (let [[class-id property-id] args] + (outliner-property/class-remove-property! conn [:block/uuid class-id] property-id)) :upsert-closed-value (apply outliner-property/upsert-closed-value! conn args) :delete-closed-value - (apply outliner-property/delete-closed-value! conn args) + (let [[property-id value-block-id] args] + (outliner-property/delete-closed-value! conn property-id [:block/uuid value-block-id])) :add-existing-values-to-closed-values (apply outliner-property/add-existing-values-to-closed-values! conn args) @@ -377,11 +381,11 @@ :insert-blocks (let [[blocks target-block-id insert-opts] (second op)] (outliner-core/insert-blocks db blocks - (d/entity db target-block-id) + (d/entity db [:block/uuid target-block-id]) insert-opts)) :delete-blocks (let [[block-ids opts] (second op) - blocks (keep #(d/entity db %) block-ids)] + blocks (keep #(d/entity db [:block/uuid %]) block-ids)] (outliner-core/delete-blocks db blocks (merge opts opts')))) additional-tx (:additional-tx opts') full-tx (concat (:tx-data result) additional-tx)] @@ -393,7 +397,7 @@ (let [save-block-tx (:tx-data (apply outliner-core/save-block @conn (second (first ops)))) [blocks target-block-id insert-opts] (second (second ops)) insert-blocks-result (outliner-core/insert-blocks @conn blocks - (d/entity @conn target-block-id) + (d/entity @conn [:block/uuid target-block-id]) insert-opts) additional-tx (:additional-tx opts') full-tx (concat save-block-tx diff --git a/deps/outliner/src/logseq/outliner/op/construct.cljc b/deps/outliner/src/logseq/outliner/op/construct.cljc index 5dcfd68b68..5b7b6853f3 100644 --- a/deps/outliner/src/logseq/outliner/op/construct.cljc +++ b/deps/outliner/src/logseq/outliner/op/construct.cljc @@ -581,7 +581,7 @@ (defn- selected-block-roots [db-before ids] - (let [resolved-entities (mapv #(d/entity db-before %) ids) + (let [resolved-entities (mapv #(block-entity db-before %) ids) unresolved-id? (some nil? resolved-entities) entities (reduce (fn [acc ent] (if (some #(= (:db/id %) (:db/id ent)) acc) @@ -875,6 +875,107 @@ :else ops'))) +(defn- canonical-block-id + [db block-id] + (cond + (uuid? block-id) + block-id + + (and (vector? block-id) + (= :block/uuid (first block-id)) + (uuid? (second block-id))) + (second block-id) + + (and (integer? block-id) + (not (neg? block-id))) + (stable-block-uuid db block-id) + + :else + block-id)) + +(defn- canonical-property-id + [db property-id] + (cond + (qualified-keyword? property-id) + property-id + + (and (integer? property-id) + (not (neg? property-id))) + (or (some-> (d/entity db property-id) :db/ident) + property-id) + + :else + property-id)) + +(defn- normalize-op-entry-ids + [db [op args :as op-entry]] + (let [id (fn [v] (canonical-block-id db v)) + property-id (fn [v] (canonical-property-id db v)) + ids (fn [vs] (mapv id vs))] + (case op + :save-block + (let [[block opts] args] + [op [block opts]]) + + :insert-blocks + [op [(first args) (id (second args)) (nth args 2)]] + + :apply-template + [op [(id (first args)) (id (second args)) (nth args 2)]] + + :delete-blocks + [op [(ids (first args)) (second args)]] + + :move-blocks + [op [(ids (first args)) (id (second args)) (nth args 2)]] + + :move-blocks-up-down + [op [(ids (first args)) (second args)]] + + :indent-outdent-blocks + [op [(ids (first args)) (second args) (nth args 2)]] + + :set-block-property + [op [(id (first args)) (property-id (second args)) (nth args 2)]] + + :remove-block-property + [op [(id (first args)) (property-id (second args))]] + + :delete-property-value + [op [(id (first args)) (property-id (second args)) (nth args 2)]] + + :create-property-text-block + [op [(some-> (first args) id) (property-id (second args)) (nth args 2) (nth args 3)]] + + :batch-set-property + [op [(ids (first args)) (property-id (second args)) (nth args 2) (nth args 3)]] + + :batch-remove-property + [op [(ids (first args)) (property-id (second args))]] + + :batch-delete-property-value + [op [(ids (first args)) (property-id (second args)) (nth args 2)]] + + :class-add-property + [op [(id (first args)) (property-id (second args))]] + + :class-remove-property + [op [(id (first args)) (property-id (second args))]] + + :upsert-property + [op [(some-> (first args) property-id) (second args) (nth args 2)]] + + :upsert-closed-value + [op [(property-id (first args)) (second args)]] + + :delete-closed-value + [op [(property-id (first args)) (id (second args))]] + + :add-existing-values-to-closed-values + [op [(property-id (first args)) (second args)]] + + op-entry))) + (defn- canonicalize-explicit-outliner-ops [db tx-data ops] (let [ops' (normalize-op-entries ops)] @@ -901,16 +1002,25 @@ (let [forward-insert-ops* (atom (->> forward-outliner-ops reverse (filter #(contains? #{:insert-blocks :apply-template} (first %))) - vec))] + vec)) + op->inserted-ids (fn [[op args]] + (let [blocks (case op + :insert-blocks + (first args) + + :apply-template + (get-in args [2 :template-blocks]) + + nil)] + (->> (or blocks []) + (keep (fn [block] + (when-let [block-uuid (:block/uuid block)] + [:block/uuid block-uuid]))) + vec)))] (mapv (fn [[op args :as inverse-op]] (if (and (= :delete-blocks op) (seq @forward-insert-ops*)) - (let [[_ [blocks _target-id _opts]] (first @forward-insert-ops*) - ids (->> blocks - (keep (fn [block] - (when-let [block-uuid (:block/uuid block)] - [:block/uuid block-uuid]))) - vec)] + (let [ids (op->inserted-ids (first @forward-insert-ops*))] (swap! forward-insert-ops* subvec 1) (if (seq ids) [:delete-blocks [ids (second args)]] @@ -1047,23 +1157,31 @@ (let [canonical-forward-outliner-ops (patch-forward-delete-block-op-ids db-before (canonicalize-outliner-ops db-after tx-meta tx-data)) - canonical-forward-outliner-ops (some-> canonical-forward-outliner-ops seq vec) + canonical-forward-outliner-ops (some-> canonical-forward-outliner-ops + seq + vec + (->> (mapv #(normalize-op-entry-ids db-after %)))) _ (assert-no-stale-numeric-ids! db-after canonical-forward-outliner-ops :forward-outliner-ops) forward-outliner-ops canonical-forward-outliner-ops built-inverse-outliner-ops (some-> (build-strict-inverse-outliner-ops db-before db-after tx-data forward-outliner-ops) seq - vec) + vec + (->> (mapv #(normalize-op-entry-ids db-before %)))) _ (assert-no-stale-numeric-ids! db-before built-inverse-outliner-ops :built-inverse-outliner-ops) explicit-inverse-outliner-ops (some-> (canonicalize-explicit-outliner-ops db-after tx-data (:db-sync/inverse-outliner-ops tx-meta)) (patch-inverse-delete-block-ops forward-outliner-ops) seq - vec) + vec + (->> (mapv #(normalize-op-entry-ids db-after %)))) _ (assert-no-stale-numeric-ids! db-after explicit-inverse-outliner-ops :explicit-inverse-outliner-ops) inverse-outliner-ops (cond (and (= :apply-template (:outliner-op tx-meta)) (:undo? tx-meta) (seq (:db-sync/inverse-outliner-ops tx-meta))) - (:db-sync/inverse-outliner-ops tx-meta) + (some-> (:db-sync/inverse-outliner-ops tx-meta) + seq + vec + (->> (mapv #(normalize-op-entry-ids db-before %)))) (has-replace-empty-target-insert-op? forward-outliner-ops) built-inverse-outliner-ops @@ -1076,7 +1194,10 @@ :else explicit-inverse-outliner-ops) - inverse-outliner-ops (some-> inverse-outliner-ops seq vec) + inverse-outliner-ops (some-> inverse-outliner-ops + seq + vec + (->> (mapv #(normalize-op-entry-ids db-before %)))) _ (assert-no-stale-numeric-ids! db-before inverse-outliner-ops :inverse-outliner-ops)] {:forward-outliner-ops forward-outliner-ops :inverse-outliner-ops inverse-outliner-ops})) diff --git a/deps/outliner/src/logseq/outliner/property.cljs b/deps/outliner/src/logseq/outliner/property.cljs index db8572a1bc..1e6b748dcd 100644 --- a/deps/outliner/src/logseq/outliner/property.cljs +++ b/deps/outliner/src/logseq/outliner/property.cljs @@ -670,29 +670,30 @@ (defn batch-delete-property-value! "batch delete value when a property has multiple values" [conn block-eids property-id property-value] - (ldb/batch-transact-with-temp-conn! - conn - {:outliner-op :batch-delete-property-value} - (fn [conn] - (when-let [property (d/entity @conn property-id)] - (when (and (db-property/many? property) - (not (some #(= property-id (:db/ident (d/entity @conn %))) block-eids))) - (when (= property-id :block/tags) - (outliner-validate/validate-tags-property-deletion @conn block-eids property-value)) - (if (= property-id :block/tags) - (let [tx-data (map (fn [id] [:db/retract id property-id property-value]) block-eids)] - (ldb/transact! conn tx-data {:outliner-op :save-block})) - (doseq [block-eid block-eids] - (when-let [block (d/entity @conn block-eid)] - (let [current-val (get block property-id) - fv (first current-val)] - (if (and (= 1 (count current-val)) - (or (= property-value fv) - (= property-value (:db/id fv)))) - (remove-block-property! conn (:db/id block) property-id) - (ldb/transact! conn - [[:db/retract (:db/id block) property-id property-value]] - {:outliner-op :save-block}))))))))))) + (let [block-eids (map ->eid block-eids)] + (ldb/batch-transact-with-temp-conn! + conn + {:outliner-op :batch-delete-property-value} + (fn [conn] + (when-let [property (d/entity @conn property-id)] + (when (and (db-property/many? property) + (not (some #(= property-id (:db/ident (d/entity @conn %))) block-eids))) + (when (= property-id :block/tags) + (outliner-validate/validate-tags-property-deletion @conn block-eids property-value)) + (if (= property-id :block/tags) + (let [tx-data (map (fn [id] [:db/retract id property-id property-value]) block-eids)] + (ldb/transact! conn tx-data {:outliner-op :save-block})) + (doseq [block-eid block-eids] + (when-let [block (d/entity @conn block-eid)] + (let [current-val (get block property-id) + fv (first current-val)] + (if (and (= 1 (count current-val)) + (or (= property-value fv) + (= property-value (:db/id fv)))) + (remove-block-property! conn (:db/id block) property-id) + (ldb/transact! conn + [[:db/retract (:db/id block) property-id property-value]] + {:outliner-op :save-block})))))))))))) (defn delete-property-value! "Delete value if a property has multiple values" @@ -895,7 +896,7 @@ :payload {:message "The choice can't be deleted because it's built-in." :type :warning}})) (let [tx-data (conj (:tx-data (outliner-core/delete-blocks @conn [value-block] {})) - (outliner-core/block-with-updated-at {:db/id property-id}))] + (outliner-core/block-with-updated-at {:db/id (:db/id property)}))] (ldb/transact! conn tx-data {:outliner-op :delete-closed-value})))))) diff --git a/deps/outliner/src/logseq/outliner/template.cljs b/deps/outliner/src/logseq/outliner/template.cljs index 719eab2e79..9b20e5f9af 100644 --- a/deps/outliner/src/logseq/outliner/template.cljs +++ b/deps/outliner/src/logseq/outliner/template.cljs @@ -1,4 +1,5 @@ (ns logseq.outliner.template + "Template with variables" (:require [clojure.string :as string] [datascript.core :as d] [logseq.common.util.page-ref :as page-ref] diff --git a/deps/outliner/test/logseq/outliner/op_construct_test.cljs b/deps/outliner/test/logseq/outliner/op_construct_test.cljs index 5052b5093b..fd25875761 100644 --- a/deps/outliner/test/logseq/outliner/op_construct_test.cljs +++ b/deps/outliner/test/logseq/outliner/op_construct_test.cljs @@ -63,7 +63,7 @@ (get-in forward-outliner-ops [0 1 0 0 :block/uuid]))) (is (= true (get-in forward-outliner-ops [0 1 2 :keep-uuid?]))) (is (some #(and (= :delete-blocks (first %)) - (= [[:block/uuid (:block/uuid empty-target)]] + (= [(:block/uuid empty-target)] (vec (get-in % [1 0])))) (remove nil? inverse-outliner-ops))) (is (not-any? #(= :save-block (first %)) @@ -135,10 +135,10 @@ (op-construct/derive-history-outliner-ops @conn @conn [] tx-meta) insert-op (first inverse-outliner-ops)] (is (= :insert-blocks (first insert-op))) - (is (= [:block/uuid parent-uuid] + (is (= parent-uuid (get-in insert-op [1 1]))) (is (= false (get-in insert-op [1 2 :sibling?]))) - (is (not= [:block/uuid child-uuid] + (is (not= child-uuid (get-in insert-op [1 1])))))))) (deftest derive-history-outliner-ops-delete-blocks-with-stale-id-keeps-id-test @@ -155,7 +155,7 @@ :outliner-ops [[:delete-blocks [[(:db/id child) stale-id] {}]]]} {:keys [forward-outliner-ops]} (op-construct/derive-history-outliner-ops @conn @conn [] tx-meta)] - (is (= [[:delete-blocks [[[:block/uuid child-uuid] stale-id] {}]]] + (is (= [[:delete-blocks [[child-uuid stale-id] {}]]] forward-outliner-ops))))) (deftest derive-history-outliner-ops-delete-blocks-prefers-retracted-tx-data-ids-test @@ -175,7 +175,7 @@ {:keys [forward-outliner-ops]} (op-construct/derive-history-outliner-ops @conn (:db-after tx-report) (:tx-data tx-report) tx-meta)] - (is (= [[:delete-blocks [[[:block/uuid child-uuid]] {}]]] + (is (= [[:delete-blocks [[child-uuid] {}]]] forward-outliner-ops))))) (deftest derive-history-outliner-ops-move-blocks-resolves-target-id-from-tx-data-test @@ -192,7 +192,7 @@ :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] + (is (= target-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 @@ -235,7 +235,7 @@ (let [today-insert-op (some #(when (= :insert-blocks (first %)) %) today-inverse)] (is (some? today-insert-op)) (is (= (:block/uuid today-page) - (second (get-in today-insert-op [1 1])))) + (get-in today-insert-op [1 1]))) (is (= (:block/uuid today-child) (get-in today-insert-op [1 0 0 :block/uuid]))))))) @@ -276,7 +276,7 @@ (:db/id parent) {:sibling? false}]]]})] (is (= :delete-blocks (ffirst inverse-outliner-ops))) - (is (= [[:block/uuid inserted-uuid]] + (is (= [inserted-uuid] (vec (get-in inverse-outliner-ops [0 1 0])))))) (testing ":move-blocks" @@ -287,7 +287,7 @@ (:db/id parent) {:sibling? false}]]]})] (is (= :move-blocks (ffirst inverse-outliner-ops))) - (is (= [[:block/uuid (:block/uuid child-b)]] + (is (= [(:block/uuid child-b)] (get-in inverse-outliner-ops [0 1 0]))))) (testing ":delete-blocks" @@ -348,12 +348,12 @@ :logical-outdenting? nil}]]]} {:keys [forward-outliner-ops inverse-outliner-ops]} (op-construct/derive-history-outliner-ops @conn db-after tx-data tx-meta)] - (is (= [[:indent-outdent-blocks [[[:block/uuid (:block/uuid child-3)]] + (is (= [[:indent-outdent-blocks [[(:block/uuid child-3)] false {:parent-original nil :logical-outdenting? nil}]]] forward-outliner-ops)) - (is (= [[:indent-outdent-blocks [[[:block/uuid (:block/uuid child-3)]] + (is (= [[:indent-outdent-blocks [[(:block/uuid child-3)] true {:parent-original nil :logical-outdenting? nil}]]] @@ -377,12 +377,12 @@ :logical-outdenting? nil}]]]} {:keys [forward-outliner-ops inverse-outliner-ops]} (op-construct/derive-history-outliner-ops @conn db-after tx-data tx-meta)] - (is (= [[:indent-outdent-blocks [[[:block/uuid (:block/uuid child-2)]] + (is (= [[:indent-outdent-blocks [[(:block/uuid child-2)] false {:parent-original nil :logical-outdenting? nil}]]] forward-outliner-ops)) - (is (= [[:indent-outdent-blocks [[[:block/uuid (:block/uuid child-2)]] + (is (= [[:indent-outdent-blocks [[(:block/uuid child-2)] true {:parent-original nil :logical-outdenting? nil}]]] diff --git a/deps/outliner/test/logseq/outliner/op_test.cljs b/deps/outliner/test/logseq/outliner/op_test.cljs index 172dc24f43..e3803ba10a 100644 --- a/deps/outliner/test/logseq/outliner/op_test.cljs +++ b/deps/outliner/test/logseq/outliner/op_test.cljs @@ -47,44 +47,44 @@ [{:page {:block/title "Test"} :blocks [{:block/title "Block"}]}]) block (db-test/find-block-by-content @conn "Block") - block-id (:db/id block)] + block-uuid (:block/uuid block)] (outliner-op/apply-ops! conn [[:upsert-property [:plugin.property._test_plugin/x1 {:logseq.property/type :checkbox :db/cardinality :db.cardinality/one} {:property-name :x1}]] - [:set-block-property [block-id :plugin.property._test_plugin/x1 true]] + [:set-block-property [block-uuid :plugin.property._test_plugin/x1 true]] [:upsert-property [:plugin.property._test_plugin/x2 {:logseq.property/type :url :db/cardinality :db.cardinality/one} {:property-name :x2}]] - [:set-block-property [block-id :plugin.property._test_plugin/x2 "https://logseq.com"]] + [:set-block-property [block-uuid :plugin.property._test_plugin/x2 "https://logseq.com"]] [:upsert-property [:plugin.property._test_plugin/x3 {:logseq.property/type :number :db/cardinality :db.cardinality/one} {:property-name :x3}]] - [:set-block-property [block-id :plugin.property._test_plugin/x3 1]] + [:set-block-property [block-uuid :plugin.property._test_plugin/x3 1]] [:upsert-property [:plugin.property._test_plugin/x4 {:logseq.property/type :number :db/cardinality :db.cardinality/many} {:property-name :x4}]] - [:set-block-property [block-id :plugin.property._test_plugin/x4 1]] + [:set-block-property [block-uuid :plugin.property._test_plugin/x4 1]] [:upsert-property [:plugin.property._test_plugin/x5 {:logseq.property/type :json :db/cardinality :db.cardinality/one} {:property-name :x5}]] - [:set-block-property [block-id :plugin.property._test_plugin/x5 "{\"foo\":\"bar\"}"]] + [:set-block-property [block-uuid :plugin.property._test_plugin/x5 "{\"foo\":\"bar\"}"]] [:upsert-property [:plugin.property._test_plugin/x6 {:logseq.property/type :page :db/cardinality :db.cardinality/one} {:property-name :x6}]] - [:set-block-property [block-id :plugin.property._test_plugin/x6 "Page x"]] + [:set-block-property [block-uuid :plugin.property._test_plugin/x6 "Page x"]] [:upsert-property [:plugin.property._test_plugin/x7 {:logseq.property/type :page :db/cardinality :db.cardinality/many} {:property-name :x7}]] - [:set-block-property [block-id :plugin.property._test_plugin/x7 "Page y"]] - [:set-block-property [block-id :plugin.property._test_plugin/x7 "Page z"]] + [:set-block-property [block-uuid :plugin.property._test_plugin/x7 "Page y"]] + [:set-block-property [block-uuid :plugin.property._test_plugin/x7 "Page z"]] [:upsert-property [:plugin.property._test_plugin/x8 {:logseq.property/type :default :db/cardinality :db.cardinality/one} {:property-name :x8}]] - [:set-block-property [block-id :plugin.property._test_plugin/x8 "some content"]]] + [:set-block-property [block-uuid :plugin.property._test_plugin/x8 "some content"]]] {}) - (let [block' (d/entity @conn block-id)] + (let [block' (d/entity @conn [:block/uuid block-uuid])] (is (true? (:plugin.property._test_plugin/x1 block'))) (is (= "https://logseq.com" (:block/title (:plugin.property._test_plugin/x2 block')))) @@ -142,8 +142,8 @@ (assoc (into {} block) :db/id (:db/id block))) (rest template-blocks))) _ (outliner-op/apply-ops! conn - [[:apply-template [(:db/id template-root) - (:db/id target-block) + [[:apply-template [(:block/uuid template-root) + (:block/uuid target-block) {:template-blocks blocks-to-insert}]]] {}) page-var-block (db-test/find-block-by-content @conn "page is [[Target Page]]") @@ -164,3 +164,33 @@ (is (string? time-value)) (is (not (string/blank? time-value))) (is (not (string/includes? time-value "<%")))))) + +(deftest apply-ops-requires-uuid-block-ids-and-keyword-property-ids-test + (testing "ops reject integer eids and accept UUID/keyword identifiers" + (let [conn (db-test/create-conn-with-blocks + [{:page {:block/title "Test"} + :blocks [{:block/title "Block"}]}]) + block (db-test/find-block-by-content @conn "Block") + block-id (:db/id block) + block-uuid (:block/uuid block) + property-kw :plugin.property._test_plugin/normalized-prop] + (outliner-property/upsert-property! conn property-kw + {:logseq.property/type :checkbox + :db/cardinality :db.cardinality/one} + {:property-name :normalized-prop}) + (let [property-id (:db/id (d/entity @conn property-kw))] + (outliner-op/apply-ops! + conn + [[:set-block-property [block-uuid property-kw true]]] + {}) + (is (true? (property-kw (d/entity @conn [:block/uuid block-uuid])))) + (is (thrown? js/Error + (outliner-op/apply-ops! + conn + [[:set-block-property [block-id property-kw true]]] + {}))) + (is (thrown? js/Error + (outliner-op/apply-ops! + conn + [[:set-block-property [block-uuid property-id true]]] + {}))))))) diff --git a/src/main/frontend/components/property.cljs b/src/main/frontend/components/property.cljs index 0fda669b79..3f4ec3c393 100644 --- a/src/main/frontend/components/property.cljs +++ b/src/main/frontend/components/property.cljs @@ -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)})] diff --git a/src/main/frontend/components/property/config.cljs b/src/main/frontend/components/property/config.cljs index f896b0de2b..927c3e0ea3 100644 --- a/src/main/frontend/components/property/config.cljs +++ b/src/main/frontend/components/property/config.cljs @@ -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- 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)]) diff --git a/src/main/frontend/handler/db_based/editor.cljs b/src/main/frontend/handler/db_based/editor.cljs index cb2b3de6d4..81cb54d72b 100644 --- a/src/main/frontend/handler/db_based/editor.cljs +++ b/src/main/frontend/handler/db_based/editor.cljs @@ -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) diff --git a/src/main/frontend/handler/db_based/page.cljs b/src/main/frontend/handler/db_based/page.cljs index 1d24b0c6a6..435dc90ba6 100644 --- a/src/main/frontend/handler/db_based/page.cljs +++ b/src/main/frontend/handler/db_based/page.cljs @@ -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]]] diff --git a/src/main/frontend/handler/editor.cljs b/src/main/frontend/handler/editor.cljs index f2479cc521..329b47c3f2 100644 --- a/src/main/frontend/handler/editor.cljs +++ b/src/main/frontend/handler/editor.cljs @@ -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. diff --git a/src/main/frontend/modules/outliner/op.cljs b/src/main/frontend/modules/outliner/op.cljs index 5cf49fbfcc..2b413e07fa 100644 --- a/src/main/frontend/modules/outliner/op.cljs +++ b/src/main/frontend/modules/outliner/op.cljs @@ -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] diff --git a/src/main/frontend/worker/sync/apply_txs.cljs b/src/main/frontend/worker/sync/apply_txs.cljs index 701d25061f..8cff5b6546 100644 --- a/src/main/frontend/worker/sync/apply_txs.cljs +++ b/src/main/frontend/worker/sync/apply_txs.cljs @@ -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 diff --git a/src/test/frontend/worker/db_sync_sim_test.cljs b/src/test/frontend/worker/db_sync_sim_test.cljs index fd04f268b1..862d649d9e 100644 --- a/src/test/frontend/worker/db_sync_sim_test.cljs +++ b/src/test/frontend/worker/db_sync_sim_test.cljs @@ -227,6 +227,121 @@ ;; (prn :debug :delete-block! (:db/id block) (:block/uuid block) (:block/title block)) (outliner-core/delete-blocks! conn [block] {}))) +(defn- block-id->uuid + [db block-id] + (cond + (uuid? block-id) + block-id + + (and (vector? block-id) (= :block/uuid (first block-id))) + (second block-id) + + (number? block-id) + (or (some-> (d/entity db block-id) :block/uuid) + block-id) + + :else + block-id)) + +(defn- property-id->ident + [db property-id] + (cond + (qualified-keyword? property-id) + property-id + + (number? property-id) + (or (some-> (d/entity db property-id) :db/ident) + property-id) + + :else + property-id)) + +(defn- normalize-op-block-ids + [db [op args :as op-entry]] + (let [id (fn [v] (block-id->uuid db v)) + property-id (fn [v] (property-id->ident db v)) + ids (fn [vs] (mapv id vs))] + (case op + :save-block + (let [[block opts] args + block' (cond-> block + (and (map? block) + (uuid? (:db/id block))) + ((fn [m] + (cond-> (dissoc m :db/id) + (nil? (:block/uuid m)) + (assoc :block/uuid (:db/id m))))) + (and (map? block) + (nil? (:block/uuid block)) + (number? (:db/id block))) + (assoc :block/uuid (id (:db/id block))))] + [op [block' opts]]) + + :insert-blocks + [op [(first args) (id (second args)) (nth args 2)]] + + :apply-template + [op [(id (first args)) (id (second args)) (nth args 2)]] + + :delete-blocks + [op [(ids (first args)) (second args)]] + + :move-blocks + [op [(ids (first args)) (id (second args)) (nth args 2)]] + + :move-blocks-up-down + [op [(ids (first args)) (second args)]] + + :indent-outdent-blocks + [op [(ids (first args)) (second args) (nth args 2)]] + + :set-block-property + [op [(id (first args)) (property-id (second args)) (nth args 2)]] + + :remove-block-property + [op [(id (first args)) (property-id (second args))]] + + :delete-property-value + [op [(id (first args)) (property-id (second args)) (nth args 2)]] + + :create-property-text-block + [op [(some-> (first args) id) (property-id (second args)) (nth args 2) (nth args 3)]] + + :batch-set-property + [op [(ids (first args)) (property-id (second args)) (nth args 2) (nth args 3)]] + + :batch-remove-property + [op [(ids (first args)) (property-id (second args))]] + + :batch-delete-property-value + [op [(ids (first args)) (property-id (second args)) (nth args 2)]] + + :class-add-property + [op [(id (first args)) (property-id (second args))]] + + :class-remove-property + [op [(id (first args)) (property-id (second args))]] + + :upsert-property + [op [(some-> (first args) property-id) (second args) (nth args 2)]] + + :upsert-closed-value + [op [(property-id (first args)) (second args)]] + + :delete-closed-value + [op [(property-id (first args)) (id (second args))]] + + :add-existing-values-to-closed-values + [op [(property-id (first args)) (second args)]] + + op-entry))) + +(defn- apply-ops! + [conn ops opts] + (outliner-op/apply-ops! conn + (mapv #(normalize-op-block-ids @conn %) ops) + opts)) + (defn- existing-entities [db uuids] (->> uuids @@ -638,7 +753,7 @@ [conn title schema] (or (find-property-by-title @conn title) (do - (outliner-op/apply-ops! + (apply-ops! conn [[:upsert-property [nil schema {:property-name title}]]] {}) @@ -820,7 +935,7 @@ (when block (let [block-uuid (:block/uuid block)] (try - (outliner-op/apply-ops! conn [[:toggle-reaction [block-uuid "+1" nil]]] {}) + (apply-ops! conn [[:toggle-reaction [block-uuid "+1" nil]]] {}) {:op :toggle-reaction :uuid block-uuid :emoji "+1"} @@ -842,7 +957,7 @@ (let [title sim-default-property-title schema sim-default-property-schema existing (find-property-by-title @conn title)] - (outliner-op/apply-ops! + (apply-ops! conn [[:upsert-property [(or (:db/ident existing) nil) schema @@ -867,7 +982,7 @@ (when-let [property (ensure-property! conn sim-default-property-title sim-default-property-schema)] (let [{:keys [value]} (pick-settable-property-input rng conn property "prop-value")] (try - (outliner-op/apply-ops! + (apply-ops! conn [[:set-block-property [(:db/id block) (:db/ident property) value]]] {}) @@ -883,11 +998,11 @@ (when-let [property (ensure-property! conn sim-default-property-title sim-default-property-schema)] (let [{:keys [value]} (pick-settable-property-input rng conn property "remove-prop")] (try - (outliner-op/apply-ops! + (apply-ops! conn [[:set-block-property [(:db/id block) (:db/ident property) value]]] {}) - (outliner-op/apply-ops! + (apply-ops! conn [[:remove-block-property [(:db/id block) (:db/ident property)]]] {}) @@ -899,7 +1014,7 @@ (defn- create-property-text-block-with-uuid! [conn property-id value value-uuid] - (outliner-op/apply-ops! + (apply-ops! conn [[:create-property-text-block [nil property-id value {:new-block-id value-uuid}]]] {}) @@ -931,7 +1046,7 @@ (let [block-ids (mapv :db/id blocks) {:keys [value options]} (pick-settable-property-input rng conn property "batch-prop")] (try - (outliner-op/apply-ops! + (apply-ops! conn [[:batch-set-property [block-ids (:db/ident property) value options]]] {}) @@ -958,11 +1073,11 @@ (let [block-ids (mapv :db/id blocks) {:keys [value options]} (pick-settable-property-input rng conn property "to-remove")] (try - (outliner-op/apply-ops! + (apply-ops! conn [[:batch-set-property [block-ids (:db/ident property) value options]]] {}) - (outliner-op/apply-ops! + (apply-ops! conn [[:batch-remove-property [block-ids (:db/ident property)]]] {}) @@ -976,7 +1091,7 @@ (when-let [class (ensure-class! rng conn)] (when-let [property (ensure-property! conn sim-default-property-title sim-default-property-schema)] (try - (outliner-op/apply-ops! + (apply-ops! conn [[:class-add-property [(:db/id class) (:db/ident property)]]] {}) @@ -990,11 +1105,11 @@ (when-let [class (ensure-class! rng conn)] (when-let [property (ensure-property! conn sim-default-property-title sim-default-property-schema)] (try - (outliner-op/apply-ops! + (apply-ops! conn [[:class-add-property [(:db/id class) (:db/ident property)]]] {}) - (outliner-op/apply-ops! + (apply-ops! conn [[:class-remove-property [(:db/id class) (:db/ident property)]]] {}) @@ -1008,7 +1123,7 @@ (when-let [property (ensure-property! conn sim-default-property-title sim-default-property-schema)] (let [value (str "choice-" (rand-int! rng 1000000))] (try - (outliner-op/apply-ops! + (apply-ops! conn [[:upsert-closed-value [(:db/id property) {:value value}]]] {}) @@ -1022,12 +1137,12 @@ (when-let [property (ensure-property! conn sim-default-property-title sim-default-property-schema)] (let [value (str "delete-choice-" (rand-int! rng 1000000))] (try - (outliner-op/apply-ops! + (apply-ops! conn [[:upsert-closed-value [(:db/id property) {:value value}]]] {}) (when-let [value-block (first (:block/_closed-value-property (d/entity @conn (:db/id property))))] - (outliner-op/apply-ops! + (apply-ops! conn [[:delete-closed-value [(:db/id property) (:db/id value-block)]]] {}) @@ -1054,7 +1169,7 @@ (rng-uuid rng)) uuids (vec (remove nil? [uuid-a uuid-b]))] (when (seq uuids) - (outliner-op/apply-ops! + (apply-ops! conn [[:add-existing-values-to-closed-values [(:db/id property) uuids]]] {}) @@ -1068,11 +1183,11 @@ (when-let [class (ensure-class! rng conn)] (when-let [block (ensure-random-block! rng conn state base-uuid gen-uuid)] (try - (outliner-op/apply-ops! + (apply-ops! conn [[:set-block-property [(:db/id block) :block/tags (:db/id class)]]] {}) - (outliner-op/apply-ops! + (apply-ops! conn [[:delete-property-value [(:db/id block) :block/tags (:db/id class)]]] {}) @@ -1091,11 +1206,11 @@ (when (seq blocks) (let [block-ids (mapv :db/id blocks)] (try - (outliner-op/apply-ops! + (apply-ops! conn [[:batch-set-property [block-ids :block/tags (:db/id class) {}]]] {}) - (outliner-op/apply-ops! + (apply-ops! conn [[:batch-delete-property-value [block-ids :block/tags (:db/id class)]]] {}) @@ -1166,7 +1281,7 @@ (when (seq copied-tree) ;; Simulate "copy + paste tree into empty target block" using ;; replace-empty-target paste semantics. - (outliner-op/apply-ops! + (apply-ops! conn [[:insert-blocks [copied-tree (:db/id target) @@ -1204,7 +1319,7 @@ (when (seq direct-children) ;; Simulate "cut + paste into empty target block" in a single outliner ;; transaction to avoid intermediate sync states. - (outliner-op/apply-ops! + (apply-ops! conn [[:move-blocks [[(:db/id source)] (:db/id target) {:sibling? true}]] [:delete-blocks [[(:db/id target)] {}]]] @@ -1824,33 +1939,33 @@ (let [base-page (d/entity @conn-a [:block/uuid base-uuid]) tx-meta {:client-id "db-sync-sim-client" :local-tx? true}] - (outliner-op/apply-ops! conn-a + (apply-ops! conn-a [[:insert-blocks [[{:block/uuid block-1-uuid :block/title ""}] (:db/id base-page) {:sibling? false :keep-uuid? true}]]] tx-meta) - (outliner-op/apply-ops! conn-a + (apply-ops! conn-a [[:save-block [{:block/uuid block-1-uuid :block/title "1"} nil]]] tx-meta) (let [block-1 (d/entity @conn-a [:block/uuid block-1-uuid])] - (outliner-op/apply-ops! conn-a + (apply-ops! conn-a [[:insert-blocks [[{:block/uuid block-2-uuid :block/title ""}] (:db/id block-1) {:sibling? true :keep-uuid? true}]]] tx-meta)) - (outliner-op/apply-ops! conn-a + (apply-ops! conn-a [[:save-block [{:block/uuid block-2-uuid :block/title "2"} nil]]] tx-meta) (let [block-2 (d/entity @conn-a [:block/uuid block-2-uuid])] - (outliner-op/apply-ops! conn-a + (apply-ops! conn-a [[:indent-outdent-blocks [[(:db/id block-2)] true {}]]] tx-meta)) (loop [undo-count 0] diff --git a/src/test/frontend/worker/db_sync_test.cljs b/src/test/frontend/worker/db_sync_test.cljs index e28179fc3a..b78c42c165 100644 --- a/src/test/frontend/worker/db_sync_test.cljs +++ b/src/test/frontend/worker/db_sync_test.cljs @@ -261,6 +261,121 @@ :a-child-1 (db-test/find-block-by-content @conn "a child 1") :b-child-1 (db-test/find-block-by-content @conn "b child 1")})) +(defn- block-id->uuid + [db block-id] + (cond + (uuid? block-id) + block-id + + (and (vector? block-id) (= :block/uuid (first block-id))) + (second block-id) + + (number? block-id) + (or (some-> (d/entity db block-id) :block/uuid) + block-id) + + :else + block-id)) + +(defn- property-id->ident + [db property-id] + (cond + (qualified-keyword? property-id) + property-id + + (number? property-id) + (or (some-> (d/entity db property-id) :db/ident) + property-id) + + :else + property-id)) + +(defn- normalize-op-block-ids + [db [op args :as op-entry]] + (let [id (fn [v] (block-id->uuid db v)) + property-id (fn [v] (property-id->ident db v)) + ids (fn [vs] (mapv id vs))] + (case op + :save-block + (let [[block opts] args + block' (cond-> block + (and (map? block) + (uuid? (:db/id block))) + ((fn [m] + (cond-> (dissoc m :db/id) + (nil? (:block/uuid m)) + (assoc :block/uuid (:db/id m))))) + (and (map? block) + (nil? (:block/uuid block)) + (number? (:db/id block))) + (assoc :block/uuid (id (:db/id block))))] + [op [block' opts]]) + + :insert-blocks + [op [(first args) (id (second args)) (nth args 2)]] + + :apply-template + [op [(id (first args)) (id (second args)) (nth args 2)]] + + :delete-blocks + [op [(ids (first args)) (second args)]] + + :move-blocks + [op [(ids (first args)) (id (second args)) (nth args 2)]] + + :move-blocks-up-down + [op [(ids (first args)) (second args)]] + + :indent-outdent-blocks + [op [(ids (first args)) (second args) (nth args 2)]] + + :set-block-property + [op [(id (first args)) (property-id (second args)) (nth args 2)]] + + :remove-block-property + [op [(id (first args)) (property-id (second args))]] + + :delete-property-value + [op [(id (first args)) (property-id (second args)) (nth args 2)]] + + :create-property-text-block + [op [(some-> (first args) id) (property-id (second args)) (nth args 2) (nth args 3)]] + + :batch-set-property + [op [(ids (first args)) (property-id (second args)) (nth args 2) (nth args 3)]] + + :batch-remove-property + [op [(ids (first args)) (property-id (second args))]] + + :batch-delete-property-value + [op [(ids (first args)) (property-id (second args)) (nth args 2)]] + + :class-add-property + [op [(id (first args)) (property-id (second args))]] + + :class-remove-property + [op [(id (first args)) (property-id (second args))]] + + :upsert-property + [op [(some-> (first args) property-id) (second args) (nth args 2)]] + + :upsert-closed-value + [op [(property-id (first args)) (second args)]] + + :delete-closed-value + [op [(property-id (first args)) (id (second args))]] + + :add-existing-values-to-closed-values + [op [(property-id (first args)) (second args)]] + + op-entry))) + +(defn- apply-ops! + [conn ops opts] + (outliner-op/apply-ops! conn + (mapv #(normalize-op-block-ids @conn %) ops) + opts)) + (deftest resolve-ws-token-refreshes-when-token-expired-test (async done (let [refresh-calls (atom 0) @@ -1199,7 +1314,7 @@ (let [{:keys [conn client-ops-conn parent]} (setup-parent-child)] (with-datascript-conns conn client-ops-conn (fn [] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:toggle-reaction [(:block/uuid parent) "+1" nil]]] local-tx-meta) (let [pending (#'sync-apply/pending-txs test-repo) @@ -1220,7 +1335,7 @@ (with-datascript-conns conn client-ops-conn (fn [] (worker-page/create! conn "Rename Me" :uuid page-uuid) - (outliner-op/apply-ops! conn + (apply-ops! conn [[:rename-page [page-uuid "Renamed"]]] local-tx-meta) (let [{:keys [forward-outliner-ops]} (last (#'sync-apply/pending-txs test-repo))] @@ -1234,7 +1349,7 @@ (let [{:keys [conn client-ops-conn child2]} (setup-parent-child)] (with-datascript-conns conn client-ops-conn (fn [] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:move-blocks-up-down [[(:db/id child2)] true]]] local-tx-meta) (let [{:keys [forward-outliner-ops]} (first (#'sync-apply/pending-txs test-repo)) @@ -1248,7 +1363,7 @@ (let [{:keys [conn client-ops-conn child2]} (setup-parent-child)] (with-datascript-conns conn client-ops-conn (fn [] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:indent-outdent-blocks [[(:db/id child2)] true {}]]] local-tx-meta) (let [{:keys [forward-outliner-ops]} (first (#'sync-apply/pending-txs test-repo)) @@ -1264,7 +1379,7 @@ tx-meta (assoc local-tx-meta :outliner-op :move-blocks)] (with-datascript-conns conn client-ops-conn (fn [] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:indent-outdent-blocks [[(:db/id child3)] false {:parent-original nil :logical-outdenting? nil}]]] tx-meta) @@ -1272,15 +1387,15 @@ forward-ops (:forward-outliner-ops source-row) inverse-ops (:inverse-outliner-ops source-row)] (is (= :move-blocks (ffirst forward-ops))) - (is (= [[:block/uuid (:block/uuid child3)]] + (is (= [(:block/uuid child3)] (get-in forward-ops [0 1 0]))) - (is (= [:block/uuid (:block/uuid parent)] + (is (= (:block/uuid parent) (get-in forward-ops [0 1 1]))) (is (= true (get-in forward-ops [0 1 2 :sibling?]))) (is (= :move-blocks (ffirst inverse-ops))) - (is (= [[:block/uuid (:block/uuid child3)]] + (is (= [(:block/uuid child3)] (get-in inverse-ops [0 1 0]))) - (is (= [:block/uuid (:block/uuid child2)] + (is (= (:block/uuid child2) (get-in inverse-ops [0 1 1]))) (is (= true (get-in inverse-ops [0 1 2 :sibling?]))))))))) @@ -1290,7 +1405,7 @@ tx-meta (assoc local-tx-meta :outliner-op :move-blocks)] (with-datascript-conns conn client-ops-conn (fn [] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:indent-outdent-blocks [[(:db/id child2)] false {:parent-original nil :logical-outdenting? nil}]]] tx-meta) @@ -1298,15 +1413,15 @@ forward-ops (:forward-outliner-ops source-row) inverse-ops (:inverse-outliner-ops source-row)] (is (= :move-blocks (ffirst forward-ops))) - (is (= [[:block/uuid (:block/uuid child2)]] + (is (= [(:block/uuid child2)] (get-in forward-ops [0 1 0]))) - (is (= [:block/uuid (:block/uuid parent)] + (is (= (:block/uuid parent) (get-in forward-ops [0 1 1]))) (is (= true (get-in forward-ops [0 1 2 :sibling?]))) (is (= :move-blocks (ffirst inverse-ops))) - (is (= [[:block/uuid (:block/uuid child2)]] + (is (= [(:block/uuid child2)] (get-in inverse-ops [0 1 0]))) - (is (= [:block/uuid (:block/uuid child1)] + (is (= (:block/uuid child1) (get-in inverse-ops [0 1 1]))) (is (= true (get-in inverse-ops [0 1 2 :sibling?]))))))))) @@ -1318,7 +1433,7 @@ child3-uuid (:block/uuid child3)] (with-datascript-conns conn client-ops-conn (fn [] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:indent-outdent-blocks [[(:db/id child2)] false {:parent-original nil :logical-outdenting? nil}]]] local-tx-meta) @@ -1348,7 +1463,7 @@ (reset! invalid-payload* {:tx-meta (:tx-meta tx-report) :errors errors}))) (try - (outliner-op/apply-ops! conn + (apply-ops! conn [[:indent-outdent-blocks [[(:db/id child2)] false {:parent-original nil :logical-outdenting? nil}]]] local-tx-meta) @@ -1385,7 +1500,7 @@ child-uuid (:block/uuid child1)] (with-datascript-conns conn client-ops-conn (fn [] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:save-block [{:block/uuid child-uuid :block/title "hello"} nil]]] local-tx-meta) @@ -1410,7 +1525,7 @@ child-uuid (:block/uuid child1)] (with-datascript-conns conn client-ops-conn (fn [] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:save-block [{:block/uuid child-uuid :block/title "hello"} nil]]] local-tx-meta) @@ -1608,7 +1723,7 @@ (let [{:keys [forward-outliner-ops]} (first (#'sync-apply/pending-txs test-repo))] (is (= :save-block (ffirst forward-outliner-ops))) (is (= :indent-outdent-blocks (first (second forward-outliner-ops)))) - (is (= [[:block/uuid block-uuid]] + (is (= [block-uuid] (get-in forward-outliner-ops [1 1 0]))))))))) (deftest apply-history-action-undo-delete-blocks-noops-when-target-missing-test @@ -1854,7 +1969,7 @@ (fn [] (outliner-page/create! conn "Page y" {}) (let [page-y (db-test/find-page-by-title @conn "Page y")] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:batch-set-property [[(:db/id block-1) (:db/id block-2)] property-id @@ -2128,7 +2243,7 @@ requested-uuid (random-uuid)] (with-datascript-conns conn client-ops-conn (fn [] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:insert-blocks [[{:block/title "history insert" :block/uuid requested-uuid}] (:db/id parent) @@ -2141,7 +2256,7 @@ (is (= inserted-uuid (get-in pending [:forward-outliner-ops 0 1 0 0 :block/uuid]))) (is (= inserted-uuid - (second (first (get-in pending [:inverse-outliner-ops 0 1 0]))))) + (get-in pending [:inverse-outliner-ops 0 1 0 0]))) (is (= true (:applied? (#'sync-apply/apply-history-action! test-repo tx-id true {})))) (is (nil? (d/entity @conn [:block/uuid inserted-uuid]))) @@ -2159,7 +2274,7 @@ child-uuid (:block/uuid child1)] (with-datascript-conns conn client-ops-conn (fn [] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:save-block [{:block/uuid child-uuid :block/title "child 1 inline edit"} {}]]] local-tx-meta) @@ -2286,7 +2401,7 @@ (with-datascript-conns conn client-ops-conn (fn [] (let [before-ids (property-page-ids @conn)] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:upsert-property [nil {:logseq.property/type :default} {:property-name property-name}]]] @@ -2334,7 +2449,7 @@ (reset! undo-redo/*apply-history-action! sync-apply/apply-history-action!) (try (d/transact! conn [[:db/add property-id :logseq.property/classes :logseq.class/Root]]) - (outliner-op/apply-ops! conn + (apply-ops! conn [[:upsert-property [property-id {:logseq.property/type :node :db/cardinality :many} @@ -2370,7 +2485,7 @@ right (db-test/find-block-by-content @conn "hello") left-uuid (:block/uuid left) right-uuid (:block/uuid right)] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:delete-blocks [[(:db/id right)] {:deleted-by-uuid (random-uuid)}]] [:save-block [{:block/uuid left-uuid @@ -2399,7 +2514,7 @@ inserted-uuid (random-uuid)] (with-datascript-conns conn client-ops-conn (fn [] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:save-block [{:block/uuid child-uuid :block/title "child 1 edited"} {}]] [:insert-blocks [[{:block/title "inserted after save" @@ -2446,7 +2561,7 @@ :block/parent [:block/uuid parent-uuid]}]] (with-datascript-conns conn client-ops-conn (fn [] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:insert-blocks [copied-blocks (:db/id empty-target) {:sibling? true @@ -2471,7 +2586,7 @@ pasted-uuid (:block/uuid pasted) pasted-child-uuid (:block/uuid (d/entity @conn pasted-child-id))] (is (some #(and (= :delete-blocks (first %)) - (= [[:block/uuid empty-target-uuid]] + (= [empty-target-uuid] (vec (get-in % [1 0])))) (:inverse-outliner-ops pending))) (is (some #(and (= :insert-blocks (first %)) @@ -2498,7 +2613,7 @@ inserted-uuid (random-uuid)] (with-datascript-conns conn client-ops-conn (fn [] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:insert-blocks [[{:block/title "draft" :block/uuid inserted-uuid}] (:db/id parent) @@ -2506,7 +2621,7 @@ local-tx-meta) (let [inserted (db-test/find-block-by-content @conn "draft") inserted-uuid' (:block/uuid inserted)] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:save-block [{:block/uuid inserted-uuid' :block/title "published"} {}]]] local-tx-meta) @@ -2560,7 +2675,7 @@ child-uuid (:block/uuid child1)] (with-datascript-conns conn client-ops-conn (fn [] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:save-block [{:block/uuid child-uuid :block/title "local-2"} {}]]] local-tx-meta) @@ -2644,7 +2759,7 @@ parent-b-uuid (:block/uuid parent-b)] (with-datascript-conns conn client-ops-conn (fn [] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:move-blocks [[(:db/id a-child-1) (:db/id b-child-1)] (:db/id parent-b) @@ -2657,13 +2772,13 @@ (is (some? move-action)) (is (= 2 (count inverse-ops))) (is (some #(and (= :move-blocks (first %)) - (= [[:block/uuid a-child-uuid]] (get-in % [1 0])) - (= [:block/uuid parent-a-uuid] (get-in % [1 1])) + (= [a-child-uuid] (get-in % [1 0])) + (= parent-a-uuid (get-in % [1 1])) (= false (get-in % [1 2 :sibling?]))) inverse-ops)) (is (some #(and (= :move-blocks (first %)) - (= [[:block/uuid b-child-uuid]] (get-in % [1 0])) - (= [:block/uuid parent-b-uuid] (get-in % [1 1])) + (= [b-child-uuid] (get-in % [1 0])) + (= parent-b-uuid (get-in % [1 1])) (= false (get-in % [1 2 :sibling?]))) inverse-ops)))))))) @@ -2676,7 +2791,7 @@ parent-b-uuid (:block/uuid parent-b)] (with-datascript-conns conn client-ops-conn (fn [] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:move-blocks [[(:db/id a-child-1) (:db/id b-child-1)] (:db/id parent-b) @@ -2737,7 +2852,7 @@ {:sibling? false}) (let [{:keys [forward-outliner-ops]} (first (#'sync-apply/pending-txs test-repo))] (is (= :insert-blocks (ffirst forward-outliner-ops))) - (is (= [:block/uuid (:block/uuid parent)] + (is (= (:block/uuid parent) (get-in forward-outliner-ops [0 1 1]))))))))) (deftest rebase-create-page-keeps-page-uuid-test @@ -2746,7 +2861,7 @@ page-title "rebase page uuid"] (with-datascript-conns conn client-ops-conn (fn [] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:create-page [page-title {:redirect? false :split-namespace? true :tags ()}]]] @@ -2773,7 +2888,7 @@ (let [{:keys [conn client-ops-conn parent]} (setup-parent-child)] (with-datascript-conns conn client-ops-conn (fn [] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:insert-blocks [[{:block/title "rebase uuid block" :block/uuid (random-uuid)}] (:db/id parent) @@ -2844,7 +2959,7 @@ (let [{:keys [conn client-ops-conn parent]} (setup-parent-child)] (with-datascript-conns conn client-ops-conn (fn [] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:toggle-reaction [(:block/uuid parent) "+1" nil]]] local-tx-meta) (let [reaction-eid (-> (d/datoms @conn :avet :logseq.property.reaction/target (:db/id parent)) @@ -2852,7 +2967,7 @@ :e) before-count (count (#'sync-apply/pending-txs test-repo))] (is (some? reaction-eid)) - (outliner-op/apply-ops! conn + (apply-ops! conn [[:toggle-reaction [(:block/uuid parent) "+1" nil]]] local-tx-meta) (let [after-count (count (#'sync-apply/pending-txs test-repo))] @@ -2865,7 +2980,7 @@ remote-delete-tx (:tx-data (outliner-core/delete-blocks @conn [parent] {}))] (with-datascript-conns conn client-ops-conn (fn [] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:toggle-reaction [target-uuid "+1" nil]]] local-tx-meta) (let [pending-before (#'sync-apply/pending-txs test-repo) @@ -3290,7 +3405,7 @@ (fn [] (let [page (db-test/find-page-by-title @conn-a "page 1") tag1 (ldb/get-page @conn-a "tag1") - result (outliner-op/apply-ops! + result (apply-ops! conn-a [[:insert-blocks [[{:block/title (common-util/format "[[%s]]" @@ -4003,7 +4118,7 @@ (orig repo tx-report))))] (with-datascript-conns conn client-ops-conn (fn [] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:save-block [{:block/uuid (:block/uuid block) :block/title "test"} nil]]] local-tx-meta) @@ -4527,7 +4642,7 @@ blocks-to-insert (cons (assoc (first template-blocks) :logseq.property/used-template (:db/id template-root)) (rest template-blocks))] - (outliner-op/apply-ops! + (apply-ops! conn [[:apply-template [(:db/id template-root) (:db/id empty-target) @@ -4567,7 +4682,7 @@ seed-page (db-test/find-page-by-title @seed-conn "page 1") page-id (:db/id seed-page) client-ops-conn (new-client-ops-db)] - (outliner-op/apply-ops! + (apply-ops! seed-conn [[:insert-blocks [[{:block/uuid template-root-uuid :block/title "template 1" diff --git a/src/test/frontend/worker/undo_redo_test.cljs b/src/test/frontend/worker/undo_redo_test.cljs index d2c84b4761..d295498c67 100644 --- a/src/test/frontend/worker/undo_redo_test.cljs +++ b/src/test/frontend/worker/undo_redo_test.cljs @@ -108,6 +108,121 @@ :outliner-ops [[:save-block [{:block/uuid block-uuid :block/title title} {}]]]})))) +(defn- block-id->uuid + [db block-id] + (cond + (uuid? block-id) + block-id + + (and (vector? block-id) (= :block/uuid (first block-id))) + (second block-id) + + (number? block-id) + (or (some-> (d/entity db block-id) :block/uuid) + block-id) + + :else + block-id)) + +(defn- property-id->ident + [db property-id] + (cond + (qualified-keyword? property-id) + property-id + + (number? property-id) + (or (some-> (d/entity db property-id) :db/ident) + property-id) + + :else + property-id)) + +(defn- normalize-op-block-ids + [db [op args :as op-entry]] + (let [id (fn [v] (block-id->uuid db v)) + property-id (fn [v] (property-id->ident db v)) + ids (fn [vs] (mapv id vs))] + (case op + :save-block + (let [[block opts] args + block' (cond-> block + (and (map? block) + (uuid? (:db/id block))) + ((fn [m] + (cond-> (dissoc m :db/id) + (nil? (:block/uuid m)) + (assoc :block/uuid (:db/id m))))) + (and (map? block) + (nil? (:block/uuid block)) + (number? (:db/id block))) + (assoc :block/uuid (id (:db/id block))))] + [op [block' opts]]) + + :insert-blocks + [op [(first args) (id (second args)) (nth args 2)]] + + :apply-template + [op [(id (first args)) (id (second args)) (nth args 2)]] + + :delete-blocks + [op [(ids (first args)) (second args)]] + + :move-blocks + [op [(ids (first args)) (id (second args)) (nth args 2)]] + + :move-blocks-up-down + [op [(ids (first args)) (second args)]] + + :indent-outdent-blocks + [op [(ids (first args)) (second args) (nth args 2)]] + + :set-block-property + [op [(id (first args)) (property-id (second args)) (nth args 2)]] + + :remove-block-property + [op [(id (first args)) (property-id (second args))]] + + :delete-property-value + [op [(id (first args)) (property-id (second args)) (nth args 2)]] + + :create-property-text-block + [op [(some-> (first args) id) (property-id (second args)) (nth args 2) (nth args 3)]] + + :batch-set-property + [op [(ids (first args)) (property-id (second args)) (nth args 2) (nth args 3)]] + + :batch-remove-property + [op [(ids (first args)) (property-id (second args))]] + + :batch-delete-property-value + [op [(ids (first args)) (property-id (second args)) (nth args 2)]] + + :class-add-property + [op [(id (first args)) (property-id (second args))]] + + :class-remove-property + [op [(id (first args)) (property-id (second args))]] + + :upsert-property + [op [(some-> (first args) property-id) (second args) (nth args 2)]] + + :upsert-closed-value + [op [(property-id (first args)) (second args)]] + + :delete-closed-value + [op [(property-id (first args)) (id (second args))]] + + :add-existing-values-to-closed-values + [op [(property-id (first args)) (second args)]] + + op-entry))) + +(defn- apply-ops! + [conn ops opts] + (outliner-op/apply-ops! conn + (mapv #(normalize-op-block-ids @conn %) ops) + opts)) + (deftest undo-redo-selection-editor-info-roundtrip-test (testing "undo/redo result keeps block selection editor info when no cursor is recorded" (worker-undo-redo/clear-history! test-repo) @@ -418,7 +533,7 @@ (is (= inserted-uuid (get-in data [:db-sync/forward-outliner-ops 0 1 0 0 :block/uuid]))) (is (= inserted-uuid - (second (first (get-in data [:db-sync/inverse-outliner-ops 0 1 0]))))))))) + (get-in data [:db-sync/inverse-outliner-ops 0 1 0 0]))))))) (deftest undo-works-for-local-graph-test (testing "worker undo/redo works for local changes on local graph" @@ -438,7 +553,7 @@ (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 + (apply-ops! conn [[:set-block-property [[:block/uuid block-uuid] :logseq.property/status :logseq.property/status.todo]]] @@ -462,7 +577,7 @@ (worker-undo-redo/clear-history! test-repo) (let [conn (worker-state/get-datascript-conn test-repo) {:keys [page-uuid]} (seed-page-parent-child!)] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:delete-page [page-uuid {}]]] (local-tx-meta {:client-id "test-client"})) (let [deleted-page (d/entity @conn [:block/uuid page-uuid])] @@ -483,14 +598,14 @@ (testing "undoing delete-page restores hard-retracted class/property pages and today page blocks" (let [conn (worker-state/get-datascript-conn test-repo) class-title "undo class page movie" - [_ class-uuid] (outliner-op/apply-ops! conn + [_ class-uuid] (apply-ops! conn [[:create-page [class-title {:class? true :redirect? false :split-namespace? true :tags ()}]]] (local-tx-meta {:client-id "test-client"})) - _ (outliner-op/apply-ops! conn + _ (apply-ops! conn [[:upsert-property [:user.property/undo-rating {:logseq.property/type :number} {:property-name "undo-rating"}]]] @@ -502,7 +617,7 @@ today-day (:logseq.property.journal/title-format (d/entity @conn :logseq.class/Journal))) - [_ today-page-uuid] (outliner-op/apply-ops! conn + [_ today-page-uuid] (apply-ops! conn [[:create-page [today-title {:today-journal? true :redirect? false @@ -511,7 +626,7 @@ (local-tx-meta {:client-id "test-client"})) today-page-id (:db/id (d/entity @conn [:block/uuid today-page-uuid])) today-child-uuid (random-uuid) - _ (outliner-op/apply-ops! conn + _ (apply-ops! conn [[:insert-blocks [[{:block/uuid today-child-uuid :block/title "today undo child"}] today-page-id @@ -522,7 +637,7 @@ property-ident-before (:db/ident (d/entity @conn [:block/uuid property-uuid]))] (worker-undo-redo/clear-history! test-repo) - (outliner-op/apply-ops! conn + (apply-ops! conn [[:delete-page [class-uuid {}]]] (local-tx-meta {:client-id "test-client"})) (is (nil? (d/entity @conn [:block/uuid class-uuid]))) @@ -531,7 +646,7 @@ (:db/ident (d/entity @conn [:block/uuid class-uuid])))) (worker-undo-redo/clear-history! test-repo) - (outliner-op/apply-ops! conn + (apply-ops! conn [[:delete-page [property-uuid {}]]] (local-tx-meta {:client-id "test-client"})) (is (nil? (d/entity @conn :user.property/undo-rating))) @@ -540,7 +655,7 @@ (:db/ident (d/entity @conn [:block/uuid property-uuid])))) (worker-undo-redo/clear-history! test-repo) - (outliner-op/apply-ops! conn + (apply-ops! conn [[:delete-page [today-page-uuid {}]]] (local-tx-meta {:client-id "test-client"})) (is (some? (d/entity @conn [:block/uuid today-page-uuid]))) @@ -553,7 +668,7 @@ (worker-undo-redo/clear-history! test-repo) (let [conn (worker-state/get-datascript-conn test-repo) page-title "redo create page alpha"] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:create-page [page-title {:redirect? false :split-namespace? true :tags ()}]]] @@ -582,7 +697,7 @@ template-a-uuid (random-uuid) template-b-uuid (random-uuid) empty-target-uuid (random-uuid)] - (outliner-op/apply-ops! + (apply-ops! conn [[:insert-blocks [[{:block/uuid template-root-uuid :block/title "template 1" @@ -597,7 +712,7 @@ {:sibling? false :keep-uuid? true}]]] (local-tx-meta {:client-id "test-client"})) - (outliner-op/apply-ops! + (apply-ops! conn [[:insert-blocks [[{:block/uuid empty-target-uuid :block/title ""}] @@ -613,7 +728,7 @@ blocks-to-insert (cons (assoc (first template-blocks) :logseq.property/used-template (:db/id template-root)) (rest template-blocks))] - (outliner-op/apply-ops! + (apply-ops! conn [[:insert-blocks [blocks-to-insert (:db/id empty-target) @@ -648,7 +763,7 @@ template-a-uuid (random-uuid) template-b-uuid (random-uuid) empty-target-uuid (random-uuid)] - (outliner-op/apply-ops! + (apply-ops! conn [[:insert-blocks [[{:block/uuid template-root-uuid :block/title "template 1" @@ -663,7 +778,7 @@ {:sibling? false :keep-uuid? true}]]] (local-tx-meta {:client-id "test-client"})) - (outliner-op/apply-ops! + (apply-ops! conn [[:insert-blocks [[{:block/uuid empty-target-uuid :block/title ""}] @@ -679,7 +794,7 @@ blocks-to-insert (cons (assoc (first template-blocks) :logseq.property/used-template (:db/id template-root)) (rest template-blocks))] - (outliner-op/apply-ops! + (apply-ops! conn [[:insert-blocks [blocks-to-insert (:db/id empty-target) @@ -718,7 +833,7 @@ empty-target-uuid (random-uuid) inserted-root-uuid (random-uuid) inserted-child-uuid (random-uuid)] - (outliner-op/apply-ops! + (apply-ops! conn [[:insert-blocks [[{:block/uuid empty-target-uuid :block/title ""}] @@ -727,7 +842,7 @@ :keep-uuid? true}]]] (local-tx-meta {:client-id "test-client"})) (let [empty-target (d/entity @conn [:block/uuid empty-target-uuid])] - (outliner-op/apply-ops! + (apply-ops! conn [[:insert-blocks [[{:block/uuid inserted-root-uuid :block/title "insert root"} @@ -770,7 +885,7 @@ template-a-uuid (random-uuid) template-b-uuid (random-uuid) empty-target-uuid (random-uuid)] - (outliner-op/apply-ops! + (apply-ops! conn [[:insert-blocks [[{:block/uuid template-root-uuid :block/title "template 1" @@ -785,7 +900,7 @@ {:sibling? false :keep-uuid? true}]]] (local-tx-meta {:client-id "test-client"})) - (outliner-op/apply-ops! + (apply-ops! conn [[:insert-blocks [[{:block/uuid empty-target-uuid :block/title ""}] @@ -801,7 +916,7 @@ blocks-to-insert (cons (assoc (first template-blocks) :logseq.property/used-template (:db/id template-root)) (rest template-blocks))] - (outliner-op/apply-ops! + (apply-ops! conn [[:apply-template [(:db/id template-root) (:db/id empty-target) @@ -839,7 +954,7 @@ template-a-uuid (random-uuid) template-b-uuid (random-uuid) empty-target-uuid (random-uuid)] - (outliner-op/apply-ops! + (apply-ops! conn [[:insert-blocks [[{:block/uuid template-root-uuid :block/title "template 1" @@ -854,7 +969,7 @@ {:sibling? false :keep-uuid? true}]]] (local-tx-meta {:client-id "test-client"})) - (outliner-op/apply-ops! + (apply-ops! conn [[:insert-blocks [[{:block/uuid empty-target-uuid :block/title ""}] @@ -880,7 +995,7 @@ [?b :block/title "a"]] @conn template-root-uuid))] - (outliner-op/apply-ops! + (apply-ops! conn [[:apply-template [(:db/id template-root) (:db/id empty-target) @@ -913,7 +1028,7 @@ (worker-undo-redo/clear-history! test-repo) (let [conn (worker-state/get-datascript-conn test-repo) {:keys [child-uuid]} (seed-page-parent-child!)] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:save-block [{:block/uuid child-uuid :block/title "saved via apply-ops"} {}]]] (local-tx-meta {:client-id "test-client"})) @@ -980,7 +1095,7 @@ (let [conn (worker-state/get-datascript-conn test-repo) {:keys [child-uuid]} (seed-page-parent-child!)] (doseq [title ["foo" "foo bar"]] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:save-block [{:block/uuid child-uuid :block/title title} {}]]] (local-tx-meta {:client-id "test-client"}))) @@ -997,14 +1112,14 @@ {:keys [child-uuid]} (seed-page-parent-child!)] (doseq [[suffix cardinality] [[:one :one] [:many :many]]] (let [property-id (keyword (str "user.property/p1-undo-redo-" (name suffix)))] - (outliner-op/apply-ops! conn + (apply-ops! conn [[:upsert-property [property-id {:logseq.property/type :default :db/cardinality cardinality} {}]]] (local-tx-meta {:client-id "test-client"})) (worker-undo-redo/clear-history! test-repo) - (outliner-op/apply-ops! conn + (apply-ops! conn [[:set-block-property [[:block/uuid child-uuid] property-id "value-1"]]]