enhance(recycle): permanently delete recycled roots with sync-safe replay

This commit is contained in:
Tienson Qin
2026-04-09 04:01:48 +08:00
parent 9362dbc847
commit 801620b901
10 changed files with 185 additions and 9 deletions

View File

@@ -4,9 +4,12 @@
[datascript.core :as d]
[frontend.components.block :as component-block]
[frontend.db :as db]
[frontend.db-mixins :as db-mixins]
[frontend.db.react :as react]
[frontend.handler.editor :as editor-handler]
[frontend.handler.page :as page-handler]
[frontend.state :as state]
[frontend.util :as util]
[logseq.db :as ldb]
[logseq.shui.ui :as shui]
[rum.core :as rum]))
@@ -36,6 +39,20 @@
(map #(d/entity db %))
(sort-by :logseq.property/deleted-at #(compare %2 %1))))
(defn- sub-deleted-root-ids
[]
(when-let [repo (state/get-current-repo)]
(some-> (react/q repo
[:frontend.worker.react/recycle-roots]
{:query-fn (fn [db _]
(->> (d/q '[:find [?e ...]
:where
[?e :logseq.property/deleted-at]]
db)
vec))}
nil)
util/react)))
(defn- group-title
[db root]
(if (ldb/page? root)
@@ -59,7 +76,11 @@
(defn- deleted-root-header
[db root]
(let [user (deleted-by db root)
deleted-at (:logseq.property/deleted-at root)]
deleted-at (:logseq.property/deleted-at root)
root-uuid (:block/uuid root)
delete-message (str "Permanently delete this "
(if (ldb/page? root) "page" "block")
" from Recycle? This cannot be undone.")]
[:div.flex.items-center.justify-between.gap-4.text-xs.text-muted-foreground
[:div.flex.items-center.gap-1.min-w-0.flex-1
(deleted-by-avatar user)
@@ -68,12 +89,20 @@
(str (if (ldb/page? root) "Page" "Block")
" deleted "
(.toLocaleString (js/Date. deleted-at)))]]]
(shui/button
{:variant :ghost
:size :xs
:class "!py-0 !px-1 h-4"
:on-click #(page-handler/restore-recycled! (:block/uuid root))}
"Restore")]))
[:div.flex.items-center.gap-1
(shui/button
{:variant :ghost
:size :xs
:class "!py-0 !px-1 h-4"
:on-click #(page-handler/restore-recycled! root-uuid)}
"Restore")
(shui/button
{:variant :ghost
:size :xs
:class "!py-0 !px-1 h-4 hover:text-destructive"
:on-click #(when (js/confirm delete-message)
(page-handler/delete-recycled-permanently! root-uuid))}
"Delete permanently")]]))
(defn- deleted-root-outliner
[root]
@@ -88,10 +117,17 @@
:id (str (:block/uuid root))}
root))
(rum/defc recycle-page
(rum/defc recycle-page < rum/reactive db-mixins/query
[_page]
(let [db* (db/get-db)
groups (->> (deleted-roots db*)
root-ids (or (sub-deleted-root-ids)
[])
roots (if (seq root-ids)
(->> root-ids
(keep #(d/entity db* %))
(sort-by :logseq.property/deleted-at #(compare %2 %1)))
(deleted-roots db*))
groups (->> roots
(group-by #(group-title db* %))
(sort-by (fn [[_ roots]]
(:logseq.property/deleted-at (first roots)))

View File

@@ -57,6 +57,16 @@
(outliner-op/transact! tx-data nil))
true))))
(defn delete-recycled-permanently!
[root-uuid]
(when-let [root (db/entity [:block/uuid root-uuid])]
(when-let [tx-data (seq (outliner-recycle/permanently-delete-tx-data (db/get-db) root))]
(p/do!
(ui-outliner-tx/transact!
{:outliner-op :recycle-delete-permanently}
(outliner-op/recycle-delete-permanently! root-uuid))
true))))
(defn <unfavorite-page!
[page-name]
(p/do!

View File

@@ -163,3 +163,8 @@
([page-uuid opts]
(op-transact!
[:delete-page [page-uuid (current-user-delete-opts opts)]])))
(defn recycle-delete-permanently!
[root-uuid]
(op-transact!
[:recycle-delete-permanently [root-uuid]]))

View File

@@ -19,6 +19,8 @@
(s/def ::objects (s/tuple #(= ::objects %) int?))
;; get block reactions
(s/def ::block-reactions (s/tuple #(= ::block-reactions %) int?))
;; recycle roots list
(s/def ::recycle-roots (s/tuple #(= ::recycle-roots %)))
;; custom react-query
(s/def ::custom any?)
@@ -27,6 +29,7 @@
:refs ::refs
:objects ::objects
:block-reactions ::block-reactions
:recycle-roots ::recycle-roots
:custom ::custom))
(s/def ::affected-keys (s/coll-of ::react-query-keys))
@@ -71,6 +74,9 @@
(= :logseq.property.reaction/target (:a datom))) tx-data)
(map :v)
(distinct))
recycle-roots? (some (fn [datom]
(= :logseq.property/deleted-at (:a datom)))
tx-data)
other-blocks (->> (filter (fn [datom] (= "block" (namespace (:a datom)))) tx-data)
(map :e))
blocks (-> (concat blocks other-blocks) distinct)
@@ -114,6 +120,9 @@
(when tag [::objects tag]))
tags)
(when recycle-roots?
[[::recycle-roots]])
(when journals?
[[::journals]]))]
(->>

View File

@@ -800,6 +800,27 @@
(ldb/transact! conn tx-data
{:outliner-op :restore-recycled}))
:recycle-delete-permanently
(let [[root-id] args
root-ref (cond
(and (vector? root-id)
(= :block/uuid (first root-id)))
root-id
(uuid? root-id)
[:block/uuid root-id]
:else
root-id)
root (d/entity @conn root-ref)
tx-data (when root
(seq (outliner-recycle/permanently-delete-tx-data @conn root)))]
;; Keep replay idempotent under concurrent edits where the recycled root may
;; already be permanently removed by a preceding remote tx.
(when (seq tx-data)
(ldb/transact! conn tx-data
{:outliner-op :recycle-delete-permanently})))
:set-block-property
(let [[block-eid property-id v] args
block-eid' (or (replay-entity-id-value @conn block-eid)