M4: cut, copy and paste bookmark subtrees (#3994)

The Bookmarks panel's item menu gets Cut, Copy and Paste, and Bookmark
options gets Paste bookmarks (to the end of the outline, which is how you
paste into a document without one). The clipboard lives in the app
(`PdfCraftApp::bookmark_clipboard`), like the Organize Pages clipboard, so
it works across tabs and on the web build.

- Copy reads the subtree with `outline_tree`; a copy cut at the listing
  depth says so.
- Cut copies first and deletes only if the copy succeeded: one undo step.
- Paste is one `ImportBookmarks` edit after the bookmark it was asked on,
  at its level. A bookmark whose page the document lacks keeps its place
  without a destination, and a notice counts them (plural forms through
  `trn`).

Parity: new `organize.bookmark-clipboard` entry with what is missing
(shortcuts, system clipboard, multi-select, page-ordered insertion).

Test: cut_copy_and_paste_a_bookmark_subtree_within_and_between_documents.
Reverting copy, cut, paste, the paste's parent, the one-step undo or the
notice each fails it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
This commit is contained in:
KenM76andClaude Opus 5.5 committed 2026-11-04 05:04:49 -05:00
1 parent 5bbcb7a218
commit c16c6b5716
7 files changed
+162

No files matched your search

+12
View File
@@ -3433,6 +3433,18 @@ tools = ["bookmark_export", "bookmark_import", "bookmark_copy"]
evidence = ["crates/organize/src/tests.rs::an_exported_outline_imports_into_another_document_unchanged", "crates/organize/src/tests.rs::a_page_the_target_lacks_keeps_its_bookmark_without_a_destination", "crates/organize/src/tests.rs::an_import_appends_inserts_or_replaces", "crates/organize/src/tests.rs::a_bad_node_anywhere_refuses_the_whole_import", "crates/automation/tests/it/automation.rs::bookmarks_export_import_and_copy_between_documents", "crates/automation/tests/it/automation.rs::a_bad_bookmark_tree_is_refused_whole_and_names_the_node"]
notes = "#6018, headless only. The bookmark tree (or one bookmark's subtree) goes out as JSON with titles, nesting, open state, colour, bold/italic and destinations as page plus view in PDF user space, and comes back unchanged: into another document, under any bookmark, at any position, or in place of its outline, as one undoable step; bookmark_copy does the same between open documents by page number. A bookmark whose page the target lacks keeps its place without a destination and is reported, never dropped. Not yet: a panel command and preview, CSV/Markdown/pdftk formats, remapping by named destination (named destinations travel resolved to their page), actions other than go-to-page, structure links (/SE)"
[[feature]]
id = "organize.bookmark-clipboard"
title = "Cut, copy and paste bookmarks"
area = "D"
tier = "P2"
milestone = "M4"
status = "shipped"
commands = []
tools = ["bookmark_copy", "bookmark_export", "bookmark_import", "bookmark_delete"]
evidence = ["crates/ui-egui/tests/it/bookmarks.rs::cut_copy_and_paste_a_bookmark_subtree_within_and_between_documents", "crates/automation/tests/it/automation.rs::bookmarks_export_import_and_copy_between_documents"]
notes = "#3994 (the cut/copy/paste part). Cut, Copy and Paste in a bookmark's menu, and Paste bookmarks in Bookmark options, carry a bookmark with its whole subtree within a document or into another one, each as one undo step. A destination is a page index: a bookmark whose page the target lacks keeps its place without a destination and the toast counts them. Headless, bookmark_copy copies between open documents directly (there is no clipboard). Not yet: keyboard shortcuts in the panel, the system clipboard (the bookmarks stay inside the app), several bookmarks at once, copying out of a document that cannot be edited (the bookmark menu is hidden there), page-ordered insertion (the other half of #3994), actions other than go-to-page"
[[feature]]
id = "organize.attachments-add"
title = "Add file attachments"