fix(cli): remove block --id can remove built-in nodes

files, empty-placeholder and import-tx idents could all be deleted.
Ents with :kv/value would error unintentionally with
'No protocol method INode.-del defined for type null:'.
Now these all gracefully error
This commit is contained in:
Gabriel Horner
2026-04-13 09:58:57 -04:00
parent 28cd63907f
commit 22c7736751
2 changed files with 38 additions and 4 deletions

View File

@@ -11,6 +11,7 @@
[logseq.db :as ldb]
[logseq.db.common.order :as db-order]
[logseq.db.frontend.class :as db-class]
[logseq.db.frontend.malli-schema :as db-malli-schema]
[logseq.db.frontend.schema :as db-schema]
[logseq.db.sqlite.create-graph :as sqlite-create-graph]
[logseq.outliner.datascript :as ds]
@@ -836,15 +837,18 @@
(let [top-level-blocks (filter-top-level-blocks db blocks)
non-consecutive? (and (> (count top-level-blocks) 1) (seq (ldb/get-non-consecutive-blocks db top-level-blocks)))
top-level-blocks* (get-top-level-blocks top-level-blocks non-consecutive?)
top-level-blocks (remove :logseq.property/built-in? top-level-blocks*)
undeletable? (fn [b] (or (:logseq.property/built-in? b)
(:file/path b)
(some-> (:db/ident b) db-malli-schema/internal-ident?)))
top-level-blocks (remove undeletable? top-level-blocks*)
txs-state (ds/new-outliner-txs-state)
block-ids (map (fn [b] [:block/uuid (:block/uuid b)]) top-level-blocks)
start-block (first top-level-blocks)
end-block (last top-level-blocks)
delete-one-block? (or (= 1 (count top-level-blocks)) (= start-block end-block))]
;; Validate before `when` since top-level-blocks will be empty when deleting one built-in block
(when (seq (filter :logseq.property/built-in? top-level-blocks*))
;; Validate before `when` since top-level-blocks will be empty when deleting one built-in/internal block
(when (seq (filter undeletable? top-level-blocks*))
(throw (ex-info "Built-in nodes can't be deleted"
{:type :notification
:payload {:message "Built-in nodes can't be deleted"

View File

@@ -3,7 +3,8 @@
[datascript.core :as d]
[logseq.db :as ldb]
[logseq.db.test.helper :as db-test]
[logseq.outliner.core :as outliner-core]))
[logseq.outliner.core :as outliner-core]
[logseq.common.config :as common-config]))
(deftest test-delete-block-with-default-property
(testing "Delete block with default property hard retracts the block subtree"
@@ -50,3 +51,32 @@
child' (db-test/find-block-by-content @conn "child")]
(is (nil? parent'))
(is (nil? child')))))
(deftest delete-blocks-rejects-built-in-entities
(let [conn (db-test/create-conn)]
(testing "built-in page is rejected"
(let [recycle-page (ldb/get-page @conn common-config/recycle-page-name)]
(is (true? (:logseq.property/built-in? recycle-page)))
(is (thrown-with-msg? js/Error #"Built-in nodes can't be deleted"
(db-test/silence-stderr (outliner-core/delete-blocks! conn [recycle-page] {}))))))
(testing "built-in idents that are not a class or property like empty-placeholder are rejected"
(let [placeholder (d/entity @conn :logseq.property/empty-placeholder)]
(is (some? (:block/uuid placeholder)))
(is (thrown-with-msg? js/Error #"Built-in nodes can't be deleted"
(db-test/silence-stderr (outliner-core/delete-blocks! conn [placeholder] {}))))))
(testing "file entity is rejected"
(let [file (->> (d/datoms @conn :avet :file/path)
first
:e
(d/entity @conn))]
(is (some? (:file/path file)))
(is (thrown-with-msg? js/Error #"Built-in nodes can't be deleted"
(db-test/silence-stderr (outliner-core/delete-blocks! conn [file] {}))))))
(testing "KV entity is rejected"
(let [kv (d/entity @conn :logseq.kv/db-type)]
(is (some? (:db/id kv)))
(is (thrown-with-msg? js/Error #"Built-in nodes can't be deleted"
(db-test/silence-stderr (outliner-core/delete-blocks! conn [kv] {}))))))))