fix(workspace): extract replaces the branch in the editor, with undo
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m2s

The server only creates the new note and returns the link; the editor
swaps the branch for it as an ordinary own edit through the sync engine.
No more clash with unsent typing (conflict copies), the link shows at
once also with polling, and Strg+Z restores the branch. Undoing (toast or
Strg+Z) puts the new note into the trash via POST /extract/undo. The
sidebar gets the new note right away and opens its folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-10-30 09:18:25 +01:00
1 parent 02ce554c02
commit 362dd3b4c6
10 files changed
+163 -71

No files matched your search

+23 -23
View File
@@ -92,16 +92,17 @@ func foreignLeaseIn(ids []string, a collab.Actor, leases collab.LeaseView) bool
return false
}
// ExtractBranch moves a bullet's children into a new note named after the bullet, in the same folder,
// shared like the original; the bullet itself becomes a [[link]] to it. Returns the new note.
func (s *Store) ExtractBranch(userID, docID, blockID string, a collab.Actor, leases collab.LeaseView) (*domain.Document, error) {
// ExtractBranch creates a new note from a bullet's children, named after the bullet, in the same folder
// and shared like the original. The original note is not changed here: the editor replaces the branch
// by the returned link itself, as an ordinary own edit (no clash with unsent typing, undo works).
func (s *Store) ExtractBranch(userID, docID, blockID string, a collab.Actor, leases collab.LeaseView) (*domain.Document, string, error) {
source, err := s.GetDocumentByID(userID, docID)
if err != nil {
return nil, err
return nil, "", err
}
state, err := s.readCollabDoc(docID)
if err != nil {
return nil, err
return nil, "", err
}
flat := state.Flatten()
start := -1
@@ -112,7 +113,7 @@ func (s *Store) ExtractBranch(userID, docID, blockID string, a collab.Actor, lea
}
}
if start < 0 {
return nil, ErrBlockNotFound
return nil, "", ErrBlockNotFound
}
root := flat[start]
var children []domain.DocumentBlock
@@ -122,37 +123,36 @@ func (s *Store) ExtractBranch(userID, docID, blockID string, a collab.Actor, lea
ids = append(ids, flat[i].ID)
}
if foreignLeaseIn(ids, a, leases) {
return nil, ErrBranchLocked
return nil, "", ErrBranchLocked
}
base, tags := branchTitle(root.Content)
title := s.uniqueTitle(userID, base)
created, err := s.CreateDocument(userID, "", title, "📄", source.FolderPath, children, nil, false)
if err != nil {
return nil, err
return nil, "", err
}
s.copyShares(userID, source, created.ID)
link := "[[" + title + "]]"
if tags != "" {
link += " " + tags
}
_, _, err = s.applyDocChange(docID, a, leases, func(d *collab.Doc) []collab.Op {
b, ok := d.Blocks[blockID]
if !ok || b.Deleted {
return nil
}
ops := []collab.Op{{OpID: domain.NewID("op"), Kind: collab.OpSetContent, BlockID: blockID, BaseVersion: b.Version, Content: &link}}
for _, c := range d.Children(blockID) {
ops = append(ops, collab.Op{OpID: domain.NewID("op"), Kind: collab.OpDelete, BlockID: c.ID})
}
return ops
})
return created, link, nil
}
// UndoExtract puts a note created by ExtractBranch into the trash (only its creator may).
func (s *Store) UndoExtract(userID, docID string) error {
doc, err := s.GetDocumentByID(userID, docID)
if err != nil {
_ = s.hardDeleteDocument(created.ID)
return nil, err
return err
}
return created, nil
if !doc.IsOwner {
return ErrMergeNotOwner
}
if doc.IsArchived {
return nil
}
return s.trashDocument(userID, docID)
}
// copyShares gives the new note the direct and team shares of the source; when someone else owns the