feat(workspace): bring an extracted note back into the bullet

A bullet that is only a link to a note (as left by /extract) gets a
"Notiz zurückholen" button. POST /documents/{id}/inline returns the
linked note's blocks with fresh ids and trashes the note; the editor
puts the title back as text and inserts the blocks as an own edit, so
Strg+Z restores the link and the note. Only own notes that no other
note links to. Release 0.52.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-11-01 20:13:53 +01:00
1 parent 75aea4a7d4
commit 39a04082fc
15 files changed
+293 -11

No files matched your search

+34 -4
View File
@@ -24,14 +24,16 @@ func (s *Server) restructureActor(r *http.Request, clientID string) collab.Actor
}
func restructureStatus(err error) int {
var stillLinked *storage.StillLinkedError
switch {
case errors.Is(err, storage.ErrBranchLocked):
case errors.Is(err, storage.ErrBranchLocked), errors.As(err, &stillLinked):
return http.StatusConflict
case errors.Is(err, storage.ErrBlockNotFound), errors.Is(err, storage.ErrDocNotFound):
case errors.Is(err, storage.ErrBlockNotFound), errors.Is(err, storage.ErrDocNotFound), errors.Is(err, storage.ErrLinkedMissing):
return http.StatusNotFound
case errors.Is(err, storage.ErrMergeSelf), errors.Is(err, storage.ErrDocInTrash), errors.Is(err, storage.ErrAlreadyNote):
case errors.Is(err, storage.ErrMergeSelf), errors.Is(err, storage.ErrDocInTrash), errors.Is(err, storage.ErrAlreadyNote),
errors.Is(err, storage.ErrNotNoteLink):
return http.StatusBadRequest
case errors.Is(err, storage.ErrMergeNotOwner):
case errors.Is(err, storage.ErrMergeNotOwner), errors.Is(err, storage.ErrInlineOwner):
return http.StatusForbidden
}
return http.StatusInternalServerError
@@ -80,6 +82,34 @@ func (s *Server) handleUndoExtract(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]bool{"undone": true})
}
// handleInlineNote: POST /documents/{id}/inline {blockId} – the way back from extracting: returns the
// linked note's blocks for the bullet and puts that note into the trash (undo: POST /documents/{note}/restore).
func (s *Server) handleInlineNote(w http.ResponseWriter, r *http.Request) {
userID := auth.GetUserID(r.Context())
docID := chi.URLParam(r, "id")
if !storage.CanEditDoc(s.store.DocRole(userID, docID)) {
writeError(w, http.StatusForbidden, "Keine Schreibrechte für diese Notiz")
return
}
var req struct {
BlockID string `json:"blockId"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.BlockID == "" {
writeError(w, http.StatusBadRequest, "blockId fehlt")
return
}
res, err := s.store.InlineNote(userID, docID, req.BlockID)
if err != nil {
writeError(w, restructureStatus(err), err.Error())
return
}
users, _ := s.store.GetAuthorizedDocUsers(res.SourceID)
for _, uid := range users {
s.wsHub.BroadcastToUser(uid, "DOC_DELETED", map[string]string{"id": res.SourceID})
}
writeJSON(w, http.StatusOK, res)
}
// handleMergeDocuments: POST /documents/{id}/merge {sourceId, clientId} appends the source to this note.
func (s *Server) handleMergeDocuments(w http.ResponseWriter, r *http.Request) {
userID := auth.GetUserID(r.Context())