feat(workspace): trash for notes
Deleting a note (or its folder) moves it to the trash instead of erasing it. Trashed notes leave the tree, search, export and stats and are hidden from everyone they were shared with; the owner sees them read-only with a restore banner. New endpoints list, restore, purge and empty the trash; restoring recreates a deleted folder. Sidebar entry and mobile menu item with counter, list view with two-tap permanent delete. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
344f5e4ab2
commit
f69f74fdc7
15 files changed
+649
-40
No files matched your search
@@ -173,6 +173,10 @@ func (s *Server) Router() http.Handler {
|
||||
r.Put("/api/v1/documents/{id}", s.handleUpdateDocument)
|
||||
r.Put("/api/v1/documents/{id}/move", s.handleMoveDocument)
|
||||
r.Delete("/api/v1/documents/{id}", s.handleDeleteDocument)
|
||||
r.Get("/api/v1/documents/trash", s.handleGetTrash)
|
||||
r.Delete("/api/v1/documents/trash", s.handleEmptyTrash)
|
||||
r.Post("/api/v1/documents/{id}/restore", s.handleRestoreDocument)
|
||||
r.Delete("/api/v1/documents/{id}/permanent", s.handlePurgeDocument)
|
||||
r.Post("/api/v1/documents/{id}/blocks/{blockId}/enhanced-task", s.handleCreateEnhancedTask)
|
||||
|
||||
// Collaborative note editing (operation log)
|
||||
@@ -1163,6 +1167,55 @@ func (s *Server) handleDeleteDocument(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, map[string]bool{"deleted": true})
|
||||
}
|
||||
|
||||
func (s *Server) handleGetTrash(w http.ResponseWriter, r *http.Request) {
|
||||
items, err := s.store.GetTrash(auth.GetUserID(r.Context()))
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, items)
|
||||
}
|
||||
|
||||
func (s *Server) handleRestoreDocument(w http.ResponseWriter, r *http.Request) {
|
||||
userID := auth.GetUserID(r.Context())
|
||||
docID := chi.URLParam(r, "id")
|
||||
if err := s.store.RestoreDocument(userID, docID); err != nil {
|
||||
writeTrashError(w, err)
|
||||
return
|
||||
}
|
||||
// Everyone with access reloads their tree, so the note is back for shared users as well
|
||||
authorized, _ := s.store.GetAuthorizedDocUsers(docID)
|
||||
for _, uid := range append(authorized, userID) {
|
||||
s.wsHub.BroadcastToUser(uid, "WORKSPACE_FOLDERS_UPDATED", map[string]string{"restored": docID})
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]bool{"restored": true})
|
||||
}
|
||||
|
||||
func (s *Server) handlePurgeDocument(w http.ResponseWriter, r *http.Request) {
|
||||
if err := s.store.PurgeDocument(auth.GetUserID(r.Context()), chi.URLParam(r, "id")); err != nil {
|
||||
writeTrashError(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]bool{"deleted": true})
|
||||
}
|
||||
|
||||
func (s *Server) handleEmptyTrash(w http.ResponseWriter, r *http.Request) {
|
||||
ids, err := s.store.EmptyTrash(auth.GetUserID(r.Context()))
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]int{"deleted": len(ids)})
|
||||
}
|
||||
|
||||
func writeTrashError(w http.ResponseWriter, err error) {
|
||||
if errors.Is(err, storage.ErrNotInTrash) {
|
||||
writeError(w, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
func (s *Server) handleCreateEnhancedTask(w http.ResponseWriter, r *http.Request) {
|
||||
userID := auth.GetUserID(r.Context())
|
||||
docID := chi.URLParam(r, "id")
|
||||
|
||||
Reference in new issue
Block a user