* feat(edit): remove all links command and context menu (#539) - Expose 'edit.remove_links' and 'edit.remove_all_links' commands to remove all links across the document or on specified pages. - Add right-click context menu options for links: Properties, Delete, and Remove all links in both Link tool and Select tool modes. - Extend automation tool 'links_remove' with optional 'pages' parameter and removal count. - Add notification feedback in UI for link deletion. - Mark 'edit.remove-all-links' as shipped in Parity with evidence tests. - Add unit and integration tests covering automation, engine, and UI undo/redo. * Remove all links: one command id, catalog rows, link menu only on a link - Drop the duplicate edit.remove_all_links command (same label as the existing edit.remove_links, so it showed twice); edit.remove_links now runs remove_all_links with its toast. - Add the toast strings (No links to remove, Removed 1 link, Removed {n} links) to all 20 catalogs. - The canvas context menu shows the link items only when the right-click hit a link (LinkView::context, set per right-click), so a link left selected no longer takes over the comments menu, and the Link tool no longer replaces the comments menu away from links. Regression test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Restore the PR's links_remove pages test (lost with the workdir conflict) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e8e275cbd4
commit
7d419a6c73
|
|
@ -7,7 +7,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Greek UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "f158bd9cc2557593365171f28f76fc38b95e7ce07faf2ff8b651d99a540f82a6"
|
||||
sha256 = "74d52e8de164f17e2f54905dbb53e547b41ecd892c5402f89088a42c205892f7"
|
||||
|
||||
# ATTRIBUTION.toml: one entry per asset (AGENTS.md §1). The source of truth for provenance and licences.
|
||||
# ATTRIBUTION.md is generated from this file: `cargo xtask assets --write`. Checked by `cargo xtask assets` (part of `xtask ci`).
|
||||
|
|
@ -30,7 +30,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Simplified Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "26c2f0ead5f4dd87d946ba11b4c2b8d442e87d54b2ec8dc15f7abfe187eccc3f"
|
||||
sha256 = "a0c2b7210671794d9c4b9f1eed1990ce6e34f91fac54f330b55a5d163487c417"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/zh-hant.tsv"
|
||||
|
|
@ -41,7 +41,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Traditional Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "658f1c4a24029fd767cabecb8f8f3be2eacc0d09f710eae0e7aceb03cc628f32"
|
||||
sha256 = "c155ca42d4fe98076aa69bcb45571b034e5c685323024e3652a7056990dec60d"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/cs.tsv"
|
||||
|
|
@ -52,7 +52,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Czech UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "640636e260b8bc726d40d6d7101698640de5028df0d67935a16741491fb3cb5f"
|
||||
sha256 = "8d8e221cec2447eeaf2de1e4c22a0ad3bbb2056df445c9a4aab9cc641408abb6"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/pt-br.tsv"
|
||||
|
|
@ -63,7 +63,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Brazilian Portuguese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "bf92cc2ebf7a1a7ae1f7c4fd270ca7643530bc6f8b38d5cba4da3594498f2889"
|
||||
sha256 = "851e4c262d5cc48c0556845403fdea3c8909c86d3c83175ac56e66da6c3df2c2"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/es.tsv"
|
||||
|
|
@ -74,7 +74,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Spanish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "7c12f6bf221a654ce22e77fc723e621daf8dd52ceafd6bff075b7b070d484aa2"
|
||||
sha256 = "47be4a80633789d29ed0e5d70f8b4f7fc5e2c5af2181076c4773f7808620e598"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/fr.tsv"
|
||||
|
|
@ -85,7 +85,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "French UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "3d16bf1a86f7d8108ec64569e18db00eafba174092f27ca93c8281ffb93a8c39"
|
||||
sha256 = "c9792fd691ab7b8d1d575b31787813d45aacb6ac53af252dffeaab2d0b5f867b"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/de.tsv"
|
||||
|
|
@ -96,7 +96,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "German UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "64cfe3444b8c0cf1bc91938567b52ed52fccd9124e2a567f8ac2d710510b7c30"
|
||||
sha256 = "97faaaf19582d9c3d34325a14e0db1b1b5bdf6b49151e8dda5d98e7a80049f12"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/te.tsv"
|
||||
|
|
@ -107,7 +107,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Telugu UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "7e1c2cebada9fd1d065d10496e77e6c0ae4b08ef9145292b1ce216fe476036ba"
|
||||
sha256 = "85e2ba7e083a629c493dd88911614e81ecb26be5166f2859a6d840a410a0399e"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/it.tsv"
|
||||
|
|
@ -118,7 +118,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Italian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "4ed39c62cf09008372d5f079458bb7ab80ed62786c58e8193ded3d35e8fea521"
|
||||
sha256 = "4a40714c86b012d5e4810b6279824a6a576d7f5a8202a5932dd2a7de7b23bcdd"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/pl.tsv"
|
||||
|
|
@ -129,7 +129,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Polish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "92ceee115cfb37b94dd7c5aad674896d3529b1f5637b1fd9529458b7d91ab8df"
|
||||
sha256 = "fc907f19f2273c5a3fd69544207c757d1e43c97f371e0bebefea409a266af640"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ru.tsv"
|
||||
|
|
@ -140,7 +140,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Russian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "1e1524a817f7ee2b53aa7d48e0d755173578060d2fae9fa3d9c7ecd18ec3b0f0"
|
||||
sha256 = "dc674b7b60359e26fe994594ee57160772f35997550888fc8b41065d9c248023"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/hu.tsv"
|
||||
|
|
@ -151,7 +151,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Hungarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "faee788eefd418d93caded400a81ea33a5ec5ea20ae62bf36f344a694027c7b5"
|
||||
sha256 = "f081f92d60e4b3ed75ecef178698170346965b0f36d49e2c46ec6bbd0de37f7e"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/bg.tsv"
|
||||
|
|
@ -162,7 +162,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Bulgarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "d88ae2bc2ad294ef9daa783768feb9827c3a90bf60f0c022cf714ddd8d5e2606"
|
||||
sha256 = "ec0a0349c33e5f22a4c27367d55454a4946f1c7288f65f7a8b1e4fc417191ebe"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/kk.tsv"
|
||||
|
|
@ -173,7 +173,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Kazakh UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "203245e46fdddd99a6e9c2fac1b05db6f6e3a3347369a2d0dc5846dcd0293c32"
|
||||
sha256 = "b8e71686440249c9247bf99089d828a172e08cc5e4a85939af05ddb60589518b"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ja.tsv"
|
||||
|
|
@ -184,7 +184,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Japanese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "d9e8acae91cb85b22abd2f8e7d285709d200f8b9b60e09370e3c1919328607c0"
|
||||
sha256 = "0ea5c0904da3166e252746803c96f9f72a69242e151328f7b8b454052db12dd7"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/uk.tsv"
|
||||
|
|
@ -195,7 +195,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Ukrainian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "bcb35726d63d4f154df5a389d3be46b53b6de9836e79d79b5fb13cd5aa9c5157"
|
||||
sha256 = "380e545b11a8e3325362acb6cb99f57203262baa2487665d57a2edbd504aeafb"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ar.tsv"
|
||||
|
|
@ -206,7 +206,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Arabic UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "7550018b4737b4ee689d38c31b5ea2093e26a18ccd717661b80a68778a0fb652"
|
||||
sha256 = "369fc4f6c0fe1501cfb4cb310e0007a054287ee5e3b813617b783782c6759bfd"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/tr.tsv"
|
||||
|
|
@ -217,7 +217,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Turkish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "45580772203b30915881c8945d33e6edc6363d7b5583fc0eff5acface163b37b"
|
||||
sha256 = "cd346d40188a768468adc7631150e93527a52c1693669425c8569bd9ba548b60"
|
||||
|
||||
[[asset]]
|
||||
path = "assets/icons/accessibility.svg"
|
||||
|
|
@ -3328,4 +3328,4 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Slovene UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "a9c8fd94278a41acda913e7d3242da627701207a1ac187f2bf2b5a9fddcf6aa8"
|
||||
sha256 = "72bb3bac963553ad79268dc3b7ef02d25f03d71fb79043a0c6df3bd38d984b32"
|
||||
|
|
|
|||
|
|
@ -188,8 +188,15 @@ impl Automation {
|
|||
}
|
||||
|
||||
pub(crate) fn links_remove(&mut self, a: &Args) -> Result<Value> {
|
||||
let before = self.doc(a)?.links.len();
|
||||
let mut out = self.apply(a, Edit::RemoveLinks { pages: None })?;
|
||||
let pages = match a.opt_ints("pages")? {
|
||||
Some(_) => Some(self.pages(a, "pages")?),
|
||||
None => None,
|
||||
};
|
||||
let before = match &pages {
|
||||
Some(ps) => self.doc(a)?.links.iter().filter(|l| ps.contains(&l.page)).count(),
|
||||
None => self.doc(a)?.links.len(),
|
||||
};
|
||||
let mut out = self.apply(a, Edit::RemoveLinks { pages })?;
|
||||
out["removed"] = json!(before);
|
||||
Ok(out)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -581,10 +581,10 @@ pub fn tools() -> Vec<ToolDef> {
|
|||
t("links_from_urls", "Create links from URLs", "Find web addresses (http://, https://, www.) in the text of every page and make them clickable links. Returns the URLs. Undoable.")
|
||||
.cmd("edit.links_from_urls")
|
||||
.with(schema(json!({ "doc": doc() }), &["doc"])),
|
||||
t("links_remove", "Remove all links", "Remove every link in the document. Undoable.")
|
||||
t("links_remove", "Remove all links", "Remove every link in the document (or on the given pages). Undoable.")
|
||||
.destructive()
|
||||
.cmd("edit.remove_links")
|
||||
.with(schema(json!({ "doc": doc() }), &["doc"])),
|
||||
.with(schema(json!({ "doc": doc(), "pages": pages("whose links to remove (default: all)") }), &["doc"])),
|
||||
t("measure_distance", "Measure distance", "Add an undoable two-point distance annotation using the scale of the first point's viewport.")
|
||||
.cmd("measure.distance").with(measure_schema()),
|
||||
t("measure_perimeter", "Measure perimeter", "Add an undoable connected-line length annotation. To include a closing edge, repeat the first point at the end.")
|
||||
|
|
|
|||
|
|
@ -3161,9 +3161,14 @@ fn links_through_tools() {
|
|||
let list = ok(&mut a, "link_list", json!({ "doc": doc }));
|
||||
assert!(list["links"].as_array().unwrap().iter().any(|l| l["url"] == "https://pdfcraft.dev"));
|
||||
ok(&mut a, "link_delete", json!({ "doc": doc, "page": 1, "index": added["index"] }));
|
||||
let r = ok(&mut a, "links_remove", json!({ "doc": doc }));
|
||||
let r = ok(&mut a, "links_remove", json!({ "doc": doc, "pages": [1] }));
|
||||
assert_eq!(r["removed"], 2);
|
||||
assert_eq!(ok(&mut a, "link_list", json!({ "doc": doc }))["count"], 0);
|
||||
// Add one more link and test links_remove across all pages
|
||||
ok(&mut a, "link_add", json!({ "doc": doc, "page": 1, "rect": [72, 300, 200, 320], "url": "https://example.org" }));
|
||||
let r2 = ok(&mut a, "links_remove", json!({ "doc": doc }));
|
||||
assert_eq!(r2["removed"], 1);
|
||||
assert_eq!(ok(&mut a, "link_list", json!({ "doc": doc }))["count"], 0);
|
||||
assert!(matches!(a.call("link_add", &json!({ "doc": doc, "page": 1, "rect": [0, 0, 50, 20] })), Err(ToolError::InvalidArgs(_))));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2047,6 +2047,12 @@ fn links_from_urls_and_link_edits() {
|
|||
assert_eq!(d.links.len(), 1);
|
||||
assert_eq!(d.info.links.len(), 1, "the viewer follows it");
|
||||
assert!(s.find_urls(id).is_empty(), "already linked");
|
||||
// Remove all links and undo.
|
||||
s.apply(id, Edit::RemoveLinks { pages: None }).unwrap();
|
||||
assert!(s.get(id).unwrap().links.is_empty());
|
||||
assert_eq!(s.get(id).unwrap().can_undo(), Some("Remove all links"));
|
||||
s.undo(id).unwrap();
|
||||
assert_eq!(s.get(id).unwrap().links.len(), 1);
|
||||
// Link Properties and delete.
|
||||
let l = s.get(id).unwrap().links[0].clone();
|
||||
s.apply(id, Edit::SetLink { page: 0, index: l.index, rect: None, action: Some(LinkAction::Page(0)), style: None }).unwrap();
|
||||
|
|
|
|||
|
|
@ -2375,6 +2375,10 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
|
|||
let mut current_overlap = -1.0f32;
|
||||
let mut current_height = f32::INFINITY;
|
||||
let pointer = ui.input(|i| i.pointer.hover_pos());
|
||||
if resp.secondary_clicked() {
|
||||
// Set again below when the right-click lands on a link.
|
||||
view.links.context = None;
|
||||
}
|
||||
for &i in &visible_pages {
|
||||
let r = snap_to_pixels(rects[i].translate(origin.to_vec2()), ppp);
|
||||
if !r.intersects(visible.expand(400.0)) {
|
||||
|
|
@ -2671,6 +2675,14 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
|
|||
text_used || image_used
|
||||
};
|
||||
let on_link = tool == QuickTool::Link && can_modify && crate::link_ui::page_input(ui, &resp, &xf, i, info, &doc_links, view);
|
||||
if tool == QuickTool::Select
|
||||
&& resp.secondary_clicked()
|
||||
&& can_modify
|
||||
&& let Some(p) = pointer.filter(|p| xf.rect.contains(*p))
|
||||
&& let Some(l) = doc.links.iter().find(|l| l.page == i && crate::link_ui::screen_rect(&xf, info, i, l.rect).expand(2.0).contains(p))
|
||||
{
|
||||
view.links.context = Some((i, l.index));
|
||||
}
|
||||
let consumed = on_objects || on_edit_text || on_link || on_content || boxing || on_field || comments::page_input(ui, &resp, &pcx, view);
|
||||
|
||||
let preview_target =
|
||||
|
|
@ -2999,6 +3011,26 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
|
|||
}
|
||||
return;
|
||||
}
|
||||
if let Some((page, index)) = view.links.context {
|
||||
if ui.button(tl!("Properties…")).clicked() {
|
||||
view.links.open_existing = Some((page, index));
|
||||
ui.close();
|
||||
}
|
||||
if ui.add_enabled(can_modify, egui::Button::new(tl!("Delete"))).clicked() {
|
||||
view.pending_edit = Some(Edit::DeleteLink { page, index });
|
||||
view.links.selected = None;
|
||||
view.links.context = None;
|
||||
ui.close();
|
||||
}
|
||||
ui.separator();
|
||||
if ui.add_enabled(can_modify, egui::Button::new(tl!("Remove all links"))).clicked() {
|
||||
view.pending_edit = Some(Edit::RemoveLinks { pages: None });
|
||||
view.links.selected = None;
|
||||
view.links.context = None;
|
||||
ui.close();
|
||||
}
|
||||
return;
|
||||
}
|
||||
canvas_action = comments::context_menu(ui, view, info, prefs, allowed);
|
||||
});
|
||||
(wanted, visible_now)
|
||||
|
|
|
|||
|
|
@ -408,9 +408,7 @@ impl PdfCraftApp {
|
|||
self.notify_tr("Drag a rectangle to create a link; double-click a link to edit it");
|
||||
}
|
||||
"edit.links_from_urls" => self.links_from_urls(),
|
||||
"edit.remove_links" => {
|
||||
self.apply_edit(Edit::RemoveLinks { pages: None });
|
||||
}
|
||||
"edit.remove_links" => self.remove_all_links(),
|
||||
"redact.mark" => {
|
||||
self.quick_tool = crate::QuickTool::Redact;
|
||||
self.left = crate::LeftPanel::Tool("redact");
|
||||
|
|
|
|||
|
|
@ -225,6 +225,9 @@
|
|||
Add or edit links إضافة روابط أو تحريرها
|
||||
Create links from URLs إنشاء روابط من عناوين URL
|
||||
Remove all links إزالة كل الروابط
|
||||
No links to remove لا توجد روابط لإزالتها
|
||||
Removed 1 link تمت إزالة رابط واحد
|
||||
Removed {n} links الروابط التي أزيلت: {n}
|
||||
Add Bates numbering… إضافة ترقيم بيتس…
|
||||
Add image… إضافة صورة…
|
||||
Redact text and images حجب النص والصور
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
|
|
@ -1172,6 +1172,9 @@
|
|||
Redact pages… Заличи страници…
|
||||
Redaction properties… Свойства на заличаването…
|
||||
Remove all links Премахни всички връзки
|
||||
No links to remove Няма връзки за премахване
|
||||
Removed 1 link Премахната е 1 връзка
|
||||
Removed {n} links Премахнати връзки: {n}
|
||||
Remove background Премахни фона
|
||||
Remove header & footer Премахни горния и долния колонтитул
|
||||
Remove hidden information… Премахни скритата информация…
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 8 and column 16.
|
|
|
@ -342,6 +342,9 @@ key Space Mezerník
|
|||
Add or edit links Přidat nebo upravit odkazy
|
||||
Create links from URLs Vytvořit odkazy z adres URL
|
||||
Remove all links Odebrat všechny odkazy
|
||||
No links to remove Nejsou žádné odkazy k odebrání
|
||||
Removed 1 link Odebrán 1 odkaz
|
||||
Removed {n} links Odebrané odkazy: {n}
|
||||
Add Bates numbering… Přidat Batesovo číslování…
|
||||
Add image… Přidat obrázek…
|
||||
Redact text and images Začernit text a obrázky
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
|
|
@ -198,6 +198,9 @@
|
|||
Add or edit links Links hinzufügen oder bearbeiten
|
||||
Create links from URLs Links aus URLs erstellen
|
||||
Remove all links Alle Links entfernen
|
||||
No links to remove Keine Links zum Entfernen
|
||||
Removed 1 link 1 Link entfernt
|
||||
Removed {n} links {n} Links entfernt
|
||||
Add Bates numbering… Bates-Nummerierung hinzufügen…
|
||||
Add image… Bild hinzufügen…
|
||||
Redact text and images Text und Bilder schwärzen
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 51.
|
|
|
@ -2147,6 +2147,9 @@
|
|||
Add or edit links Προσθήκη ή επεξεργασία συνδέσμων
|
||||
Create links from URLs Δημιουργία συνδέσμων από URL
|
||||
Remove all links Αφαίρεση όλων των συνδέσμων
|
||||
No links to remove Δεν υπάρχουν σύνδεσμοι για κατάργηση
|
||||
Removed 1 link Καταργήθηκε 1 σύνδεσμος
|
||||
Removed {n} links Σύνδεσμοι που καταργήθηκαν: {n}
|
||||
Add Bates numbering… Προσθήκη αρίθμησης Bates…
|
||||
Add image… Προσθήκη εικόνας…
|
||||
Redact text and images Οριστική απόκρυψη κειμένου και εικόνων
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 5.
|
|
|
@ -221,6 +221,9 @@
|
|||
Add or edit links Añadir o editar enlaces
|
||||
Create links from URLs Crear enlaces a partir de URL
|
||||
Remove all links Quitar todos los enlaces
|
||||
No links to remove No hay vínculos que quitar
|
||||
Removed 1 link Se quitó 1 vínculo
|
||||
Removed {n} links Se quitaron {n} vínculos
|
||||
Add Bates numbering… Añadir numeración Bates…
|
||||
Add image… Añadir imagen…
|
||||
Redact text and images Censurar texto e imágenes
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 60.
|
|
|
@ -198,6 +198,9 @@
|
|||
Add or edit links Ajouter ou modifier des liens
|
||||
Create links from URLs Créer des liens à partir des URL
|
||||
Remove all links Retirer tous les liens
|
||||
No links to remove Aucun lien à retirer
|
||||
Removed 1 link 1 lien retiré
|
||||
Removed {n} links Liens retirés : {n}
|
||||
Add Bates numbering… Ajouter une numérotation Bates…
|
||||
Add image… Ajouter une image…
|
||||
Redact text and images Biffer du texte et des images
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 39.
|
|
|
@ -196,6 +196,9 @@
|
|||
Add or edit links Hivatkozások hozzáadása vagy szerkesztése
|
||||
Create links from URLs Hivatkozások létrehozása URL-ekből
|
||||
Remove all links Összes hivatkozás eltávolítása
|
||||
No links to remove Nincs eltávolítható hivatkozás
|
||||
Removed 1 link 1 hivatkozás eltávolítva
|
||||
Removed {n} links {n} hivatkozás eltávolítva
|
||||
Add Bates numbering… Bates-számozás hozzáadása…
|
||||
Add image… Kép hozzáadása…
|
||||
Redact text and images Szöveg és képek kitakarása
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 7 and column 44.
|
|
|
@ -222,6 +222,9 @@
|
|||
Add or edit links Aggiungi o modifica collegamenti
|
||||
Create links from URLs Crea collegamenti dagli URL
|
||||
Remove all links Rimuovi tutti i collegamenti
|
||||
No links to remove Nessun collegamento da rimuovere
|
||||
Removed 1 link 1 collegamento rimosso
|
||||
Removed {n} links {n} collegamenti rimossi
|
||||
Add Bates numbering… Aggiungi numerazione Bates…
|
||||
Add image… Aggiungi immagine…
|
||||
Redact text and images Oscura testo e immagini
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
|
|
@ -225,6 +225,9 @@
|
|||
Add or edit links リンクを追加・編集
|
||||
Create links from URLs URL からリンクを作成
|
||||
Remove all links すべてのリンクを削除
|
||||
No links to remove 削除するリンクはありません
|
||||
Removed 1 link リンクを 1 件削除しました
|
||||
Removed {n} links リンクを {n} 件削除しました
|
||||
Add Bates numbering… ベイツ番号を追加…
|
||||
Add image… 画像を追加…
|
||||
Redact text and images テキストと画像を墨消し
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
|
|
@ -186,6 +186,9 @@
|
|||
Add or edit links Сілтемелерді қосу немесе түзету
|
||||
Create links from URLs URL-ден сілтемелер жасау
|
||||
Remove all links Барлық сілтемелерді алып тастау
|
||||
No links to remove Жоятын сілтеме жоқ
|
||||
Removed 1 link 1 сілтеме жойылды
|
||||
Removed {n} links Жойылған сілтемелер: {n}
|
||||
Add Bates numbering… Bates нөмірлеуін қосу…
|
||||
Add image… Сурет қосу…
|
||||
Redact text and images Мәтін мен суреттерді жасыру
|
||||
|
|
|
|||
|
|
|
@ -221,6 +221,9 @@
|
|||
Add or edit links Dodaj lub edytuj łącza
|
||||
Create links from URLs Utwórz łącza z adresów URL
|
||||
Remove all links Usuń wszystkie łącza
|
||||
No links to remove Brak łączy do usunięcia
|
||||
Removed 1 link Usunięto 1 łącze
|
||||
Removed {n} links Usunięte łącza: {n}
|
||||
Add Bates numbering… Dodaj numerację Batesa…
|
||||
Add image… Dodaj obraz…
|
||||
Redact text and images Utajnij tekst i obrazy
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
|
|
@ -1116,6 +1116,9 @@
|
|||
Redact pages… Ocultar páginas…
|
||||
Redaction properties… Propriedades da ocultação…
|
||||
Remove all links Remover todos os links
|
||||
No links to remove Não há links para remover
|
||||
Removed 1 link 1 link removido
|
||||
Removed {n} links {n} links removidos
|
||||
Remove background Remover plano de fundo
|
||||
Remove header & footer Remover cabeçalho e rodapé
|
||||
Remove hidden information… Remover informações ocultas…
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
|
|
@ -218,6 +218,9 @@
|
|||
Add or edit links Добавить или изменить ссылки
|
||||
Create links from URLs Создать ссылки из URL
|
||||
Remove all links Удалить все ссылки
|
||||
No links to remove Нет ссылок для удаления
|
||||
Removed 1 link Удалена 1 ссылка
|
||||
Removed {n} links Удалено ссылок: {n}
|
||||
Add Bates numbering… Добавить нумерацию Бейтса…
|
||||
Add image… Добавить изображение…
|
||||
Redact text and images Скрыть текст и изображения
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
|
|
@ -1429,6 +1429,9 @@
|
|||
Reliable character encoding is provided Zagotovljeno je zanesljivo kodiranje znakov
|
||||
Remove Odstrani
|
||||
Remove all links Odstrani vse povezave
|
||||
No links to remove Ni povezav za odstranitev
|
||||
Removed 1 link Odstranjena je 1 povezava
|
||||
Removed {n} links Odstranjene povezave: {n}
|
||||
Remove background Odstrani ozadje
|
||||
Remove checkmark Odstrani kljukico
|
||||
Remove from Recent Odstrani iz nedavnih
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
|
|
@ -202,6 +202,9 @@ comment menu Edit సవరించు
|
|||
Add or edit links లింక్లను జోడించు లేదా సవరించు
|
||||
Create links from URLs URLల నుండి లింక్లను సృష్టించు
|
||||
Remove all links అన్ని లింక్లను తీసివేయి
|
||||
No links to remove తీసివేయడానికి లింక్లు లేవు
|
||||
Removed 1 link 1 లింక్ తీసివేయబడింది
|
||||
Removed {n} links తీసివేసిన లింక్లు: {n}
|
||||
Add Bates numbering… బేట్స్ నంబరింగ్ జోడించు…
|
||||
Add image… చిత్రాన్ని జోడించు…
|
||||
Redact text and images టెక్స్ట్ను, చిత్రాలను రెడాక్ట్ చేయి
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 13 and column 16.
|
|
|
@ -183,6 +183,9 @@
|
|||
Add or edit links Bağlantı ekle veya düzenle
|
||||
Create links from URLs URL'lerden bağlantı oluştur
|
||||
Remove all links Tüm bağlantıları kaldır
|
||||
No links to remove Kaldırılacak bağlantı yok
|
||||
Removed 1 link 1 bağlantı kaldırıldı
|
||||
Removed {n} links {n} bağlantı kaldırıldı
|
||||
Add Bates numbering… Bates numaralandırması ekle…
|
||||
Add image… Resim ekle…
|
||||
Redact text and images Metin ve resimleri karart
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 7.
|
|
|
@ -223,6 +223,9 @@
|
|||
Add or edit links Додати або редагувати посилання
|
||||
Create links from URLs Створити посилання з URL
|
||||
Remove all links Видалити всі посилання
|
||||
No links to remove Немає посилань для вилучення
|
||||
Removed 1 link Вилучено 1 посилання
|
||||
Removed {n} links Вилучено посилань: {n}
|
||||
Add Bates numbering… Додати нумерацію Бейтса…
|
||||
Add image… Додати зображення…
|
||||
Redact text and images Вилучити текст і зображення
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
|
|
@ -1179,6 +1179,9 @@
|
|||
Redact pages… 密文页面…
|
||||
Redaction properties… 密文属性…
|
||||
Remove all links 删除所有链接
|
||||
No links to remove 没有可删除的链接
|
||||
Removed 1 link 已删除 1 个链接
|
||||
Removed {n} links 已删除 {n} 个链接
|
||||
Remove background 删除背景
|
||||
Remove header & footer 删除页眉和页脚
|
||||
Remove hidden information… 删除隐藏信息…
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
|
|
@ -1123,6 +1123,9 @@
|
|||
Redact pages… 遮蔽頁面…
|
||||
Redaction properties… 遮蔽內容設定…
|
||||
Remove all links 移除所有連結
|
||||
No links to remove 沒有可移除的連結
|
||||
Removed 1 link 已移除 1 個連結
|
||||
Removed {n} links 已移除 {n} 個連結
|
||||
Remove background 移除背景
|
||||
Remove header & footer 移除頁首與頁尾
|
||||
Remove hidden information… 移除隱藏資訊…
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
|
|
@ -17,6 +17,10 @@ pub struct LinkView {
|
|||
/// A link rectangle being drawn: (page, start).
|
||||
pub drag: Option<(usize, Pos2)>,
|
||||
pub selected: Option<(usize, usize)>,
|
||||
/// The link the last right-click on a page hit (page, index), if any: the canvas
|
||||
/// context menu shows the link items only for it, so a right-click elsewhere (on a
|
||||
/// comment, say) keeps its own menu even while a link stays selected.
|
||||
pub context: Option<(usize, usize)>,
|
||||
/// Ask the app to open Link Properties for a new rect (page, user rect) or an existing
|
||||
/// link (page, index).
|
||||
pub open_new: Option<(usize, [f64; 4])>,
|
||||
|
|
@ -41,7 +45,7 @@ pub struct LinkDraft {
|
|||
pub style: LinkStyle,
|
||||
}
|
||||
|
||||
fn screen_rect(xf: &PageXform, info: &DocInfo, page: usize, r: [f64; 4]) -> Rect {
|
||||
pub(crate) fn screen_rect(xf: &PageXform, info: &DocInfo, page: usize, r: [f64; 4]) -> Rect {
|
||||
xf.user_rect(info, page, [r[0] as f32, r[1] as f32, r[2] as f32, r[3] as f32])
|
||||
}
|
||||
|
||||
|
|
@ -80,6 +84,13 @@ pub(crate) fn page_input(
|
|||
let Some(p) = pointer.filter(|p| xf.rect.contains(*p)) else { return false };
|
||||
let hit = links.iter().filter(|l| l.page == page).find(|l| screen_rect(xf, info, page, l.rect).expand(2.0).contains(p));
|
||||
ui.ctx().set_cursor_icon(if hit.is_some() { egui::CursorIcon::PointingHand } else { egui::CursorIcon::Crosshair });
|
||||
if resp.secondary_clicked()
|
||||
&& let Some(l) = hit
|
||||
{
|
||||
lv.selected = Some((page, l.index));
|
||||
lv.context = Some((page, l.index));
|
||||
return true;
|
||||
}
|
||||
if resp.clicked() || resp.double_clicked() {
|
||||
let now = ui.input(|i| i.time);
|
||||
// A second click on the same link soon after the first is a double-click.
|
||||
|
|
@ -188,6 +199,23 @@ impl PdfCraftApp {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove every link in the active document.
|
||||
pub fn remove_all_links(&mut self) {
|
||||
let Some((_, id)) = self.active_ids() else { return };
|
||||
let count = self.session.get(id).map_or(0, |d| d.links.len());
|
||||
if count == 0 {
|
||||
self.notify_tr("No links to remove");
|
||||
return;
|
||||
}
|
||||
if self.apply_edit(Edit::RemoveLinks { pages: None }) {
|
||||
if count == 1 {
|
||||
self.notify_tr("Removed 1 link");
|
||||
} else {
|
||||
self.notify_fmt("Removed {n} links", &[("n", &count.to_string())]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Link Properties. Returns (apply, cancel).
|
||||
|
|
|
|||
|
|
@ -83,3 +83,65 @@ fn drawing_editing_and_deleting_links() {
|
|||
h.run_steps(3);
|
||||
assert!(links(&h).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removing_all_links_and_undo() {
|
||||
let mut h = harness();
|
||||
assert!(h.state_mut().execute("edit.link"));
|
||||
let (a, b) = (at(&h, 40.0, 300.0), at(&h, 160.0, 320.0));
|
||||
drag(&mut h, a, b);
|
||||
h.state_mut().link_draft.as_mut().unwrap().url = "https://example.org".into();
|
||||
h.get_by_label("OK").click();
|
||||
h.run_steps(3);
|
||||
assert_eq!(links(&h).len(), 1);
|
||||
|
||||
// Execute remove all links command
|
||||
assert!(h.state_mut().execute("edit.remove_links"));
|
||||
h.run_steps(3);
|
||||
assert_eq!(links(&h).len(), 0);
|
||||
|
||||
// Undo restores the link
|
||||
h.state_mut().undo();
|
||||
h.run_steps(3);
|
||||
assert_eq!(links(&h).len(), 1);
|
||||
}
|
||||
|
||||
fn right_click(h: &mut Harness<'static, PdfCraftApp>, p: Pos2) {
|
||||
h.hover_at(p);
|
||||
h.run_steps(1);
|
||||
h.event(egui::Event::PointerButton { pos: p, button: egui::PointerButton::Secondary, pressed: true, modifiers: Default::default() });
|
||||
h.event(egui::Event::PointerButton { pos: p, button: egui::PointerButton::Secondary, pressed: false, modifiers: Default::default() });
|
||||
h.run_steps(3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_link_context_menu_shows_only_for_a_right_click_on_a_link() {
|
||||
let mut h = harness();
|
||||
assert!(h.state_mut().execute("edit.link"));
|
||||
let (a, b) = (at(&h, 40.0, 100.0), at(&h, 160.0, 120.0));
|
||||
drag(&mut h, a, b);
|
||||
h.state_mut().link_draft.as_mut().unwrap().url = "https://example.org".into();
|
||||
h.get_by_label("OK").click();
|
||||
h.run_steps(3);
|
||||
let (on_link, away) = (at(&h, 100.0, 110.0), at(&h, 250.0, 200.0));
|
||||
// Select the link with the Link tool, then right-click away from it: the menu there is
|
||||
// not the link's.
|
||||
h.hover_at(on_link);
|
||||
h.run_steps(1);
|
||||
h.event(egui::Event::PointerButton { pos: on_link, button: egui::PointerButton::Primary, pressed: true, modifiers: Default::default() });
|
||||
h.event(egui::Event::PointerButton { pos: on_link, button: egui::PointerButton::Primary, pressed: false, modifiers: Default::default() });
|
||||
h.run_steps(3);
|
||||
assert!(h.state().views[0].links.selected.is_some());
|
||||
right_click(&mut h, away);
|
||||
assert!(h.query_by_label("Remove all links").is_none(), "a right-click away from the link got the link menu");
|
||||
h.key_press(egui::Key::Escape);
|
||||
h.run_steps(3);
|
||||
// A right-click on the link gets it, with the Select tool too.
|
||||
h.state_mut().quick_tool = pdfcraft_ui_egui::QuickTool::Select;
|
||||
h.run_steps(2);
|
||||
right_click(&mut h, on_link);
|
||||
h.get_by_label("Remove all links").click();
|
||||
h.run_steps(3);
|
||||
assert!(links(&h).is_empty());
|
||||
assert_eq!(h.state().session.get(h.state().views[0].id).unwrap().can_undo(), Some("Remove all links"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2211,11 +2211,17 @@ title = "Remove all links"
|
|||
area = "C"
|
||||
tier = "P2"
|
||||
milestone = "M7"
|
||||
status = "planned"
|
||||
commands = []
|
||||
tools = []
|
||||
evidence = []
|
||||
notes = ""
|
||||
status = "shipped"
|
||||
commands = ["edit.remove_links"]
|
||||
tools = ["links_remove"]
|
||||
evidence = [
|
||||
"crates/engine/src/tests.rs::links_from_urls_and_link_edits",
|
||||
"crates/automation/tests/automation.rs::links_through_tools",
|
||||
"crates/annot/src/tests.rs::links_are_added_edited_listed_and_removed",
|
||||
"crates/ui-egui/tests/ui_group/pdf_links.rs::removing_all_links_and_undo",
|
||||
"crates/ui-egui/tests/ui_group/pdf_links.rs::the_link_context_menu_shows_only_for_a_right_click_on_a_link",
|
||||
]
|
||||
notes = "removes all link annotations across the document or on specified pages (links_remove pages); a right-click on a link offers Properties, Delete and Remove all links; undoable, with a toast giving the count"
|
||||
|
||||
[[feature]]
|
||||
id = "edit.action-goto"
|
||||
|
|
|
|||
Loading…
Reference in New Issue