Move pages to and from the vault
A page, including a journal day, can be moved into the unlocked vault with its id kept. Links to it elsewhere become [[locked:id]], shown as Locked Page until the vault is unlocked, so its title is kept only in the vault. Move out reverses it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c08a8b8718
commit
994927c704
10 files changed
+443
-14
No files matched your search
+115
-3
@@ -591,7 +591,7 @@ func (s *Store) relink(oldTitle, newTitle, except string) error {
|
||||
if m.ID == except || m.Trashed || !slices.Contains(m.links, old) {
|
||||
continue
|
||||
}
|
||||
body := rewriteLinks(m.Body, old, newTitle)
|
||||
body := rewriteLinks(m.Body, old, newTitle, true)
|
||||
if body == m.Body {
|
||||
continue
|
||||
}
|
||||
@@ -606,7 +606,8 @@ func (s *Store) relink(oldTitle, newTitle, except string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func rewriteLinks(body, old, newTitle string) string {
|
||||
// rewriteLinks points the [[old]] links in body at newTitle, keeping their |text unless keepAlias is false.
|
||||
func rewriteLinks(body, old, newTitle string, keepAlias bool) string {
|
||||
lines := strings.Split(body, "\n")
|
||||
fence := false
|
||||
for i, l := range lines {
|
||||
@@ -623,7 +624,7 @@ func rewriteLinks(body, old, newTitle string) string {
|
||||
return x
|
||||
}
|
||||
alias := ""
|
||||
if k := strings.Index(x, "|"); k >= 0 {
|
||||
if k := strings.Index(x, "|"); keepAlias && k >= 0 {
|
||||
alias = x[k : len(x)-2]
|
||||
}
|
||||
return "[[" + newTitle + alias + "]]"
|
||||
@@ -1076,3 +1077,114 @@ func exportFormat(n *Note) []byte {
|
||||
b.WriteString("\n")
|
||||
return []byte(b.String())
|
||||
}
|
||||
|
||||
// ---- moving pages between notebooks (the vault) ----
|
||||
|
||||
// LockedPrefix starts the target of a [[link]] to a page that has been moved to the vault, so that the
|
||||
// ordinary notebook keeps the page's id and never its title.
|
||||
const LockedPrefix = "locked:"
|
||||
|
||||
var ErrExists = errors.New("there is already a page with that id")
|
||||
|
||||
// Import adds a page that was made in another notebook, keeping its id, dates and text. It is placed at
|
||||
// the top level, its title is changed if another page has it, and it is written before anything else
|
||||
// is touched, so a failure leaves the page where it came from.
|
||||
func (s *Store) Import(n Note) (Note, error) {
|
||||
if !ValidID(n.ID) {
|
||||
return Note{}, ErrBadID
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if _, ok := s.by[n.ID]; ok {
|
||||
return Note{}, ErrExists
|
||||
}
|
||||
in := n.copy()
|
||||
in.Parent, in.Trashed = "", false
|
||||
if in.Daily != "" {
|
||||
in.Title = in.Daily
|
||||
} else {
|
||||
in.Title = s.free(in.Title, in.ID)
|
||||
}
|
||||
in.refresh()
|
||||
if err := s.write(&in); err != nil {
|
||||
return Note{}, err
|
||||
}
|
||||
s.by[in.ID] = &in
|
||||
return in.copy(), nil
|
||||
}
|
||||
|
||||
// Discard removes a page with no trash and no questions. It undoes an Import that cannot be completed.
|
||||
func (s *Store) Discard(id string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
n, ok := s.by[id]
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
if err := os.Remove(s.path(n)); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
delete(s.by, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Detach takes a page out of this notebook to be kept somewhere else. Pages inside it move up a level,
|
||||
// and every [[link]] to it, even in the trash, becomes [[locked:id]], so its title is left nowhere.
|
||||
func (s *Store) Detach(id string) (Note, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
n, ok := s.by[id]
|
||||
if !ok || n.Trashed {
|
||||
return Note{}, ErrNotFound
|
||||
}
|
||||
gone := n.copy()
|
||||
for _, c := range s.by {
|
||||
if c.Parent == id {
|
||||
next := c.copy()
|
||||
next.Parent = n.Parent
|
||||
if err := s.write(&next); err != nil {
|
||||
return Note{}, err
|
||||
}
|
||||
*c = next
|
||||
}
|
||||
}
|
||||
if err := s.relinkAll(strings.ToLower(n.Title), LockedPrefix+id, false); err != nil {
|
||||
return Note{}, err
|
||||
}
|
||||
if err := os.Remove(s.path(n)); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return Note{}, err
|
||||
}
|
||||
delete(s.by, id)
|
||||
return gone, nil
|
||||
}
|
||||
|
||||
// Reveal points the [[locked:id]] links of a page that has come back at its title again.
|
||||
func (s *Store) Reveal(id, title string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.relinkAll(LockedPrefix+id, title, true)
|
||||
}
|
||||
|
||||
// relinkAll rewrites the links to old (lower-cased) in every page, trashed or not.
|
||||
func (s *Store) relinkAll(old, newTarget string, keepAlias bool) error {
|
||||
if old == "" || newTarget == "" {
|
||||
return nil
|
||||
}
|
||||
for _, m := range s.by {
|
||||
if !slices.Contains(m.links, old) {
|
||||
continue
|
||||
}
|
||||
body := rewriteLinks(m.Body, old, newTarget, keepAlias)
|
||||
if body == m.Body {
|
||||
continue
|
||||
}
|
||||
next := m.copy()
|
||||
next.set(body)
|
||||
next.Updated = moved(m.Updated)
|
||||
if err := s.write(&next); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = next
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user