mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 19:27:04 -04:00
Preferences ▸ Identity: the name on new comments, remembered
Comments were signed with the login name (or set_option author), with no way to change it in the app. Preferences gains an Identity section whose name field (labelled for screen readers) sets the author of new comments; it is saved with the settings and restored (an empty name keeps the login-name default; restored names are capped at 200 characters). Japanese labels added. Test: identity.rs. Parity comment.author-identity: shipped. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
b4e1d488de
commit
b315eff24d
5 files changed
+77
-5
No files matched your search
@@ -47,6 +47,8 @@ const JAPANESE: &[(&str, &str)] = &[
|
||||
("Preferences", "環境設定"),
|
||||
("Preferences…", "環境設定…"),
|
||||
("Interface language", "表示言語"),
|
||||
("Identity", "個人情報"),
|
||||
("Name on new comments", "新しい注釈の作成者名"),
|
||||
("Open…", "開く…"),
|
||||
("New blank PDF", "空白の PDF を作成"),
|
||||
("Create PDF from file…", "ファイルから PDF を作成…"),
|
||||
|
||||
@@ -223,7 +223,7 @@ pub(crate) fn document_js_body(ui: &mut egui::Ui, app: &mut PrintCraftApp, t: &T
|
||||
close
|
||||
}
|
||||
|
||||
/// Preferences (the JavaScript category for now). Returns `true` to close.
|
||||
/// Preferences: interface language, identity and JavaScript. Returns `true` to close.
|
||||
pub(crate) fn preferences_body(ui: &mut egui::Ui, app: &mut PrintCraftApp, t: &Tokens) -> bool {
|
||||
ui.label(egui::RichText::new(app.language.tr("Preferences")).font(theme::semibold(18.0)));
|
||||
ui.horizontal(|ui| {
|
||||
@@ -235,6 +235,14 @@ pub(crate) fn preferences_body(ui: &mut egui::Ui, app: &mut PrintCraftApp, t: &T
|
||||
});
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
// Identity: the author of new comments (Acrobat: Preferences ▸ Identity).
|
||||
ui.label(egui::RichText::new(app.language.tr("Identity")).font(theme::semibold(13.0)));
|
||||
ui.horizontal(|ui| {
|
||||
let label = ui.label(app.language.tr("Name on new comments"));
|
||||
ui.add(egui::TextEdit::singleline(&mut app.comment_prefs.author).desired_width(220.0).char_limit(crate::MAX_AUTHOR_CHARS))
|
||||
.labelled_by(label.id);
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
ui.label(egui::RichText::new("JavaScript").font(theme::semibold(13.0)));
|
||||
egui::Frame::new().fill(t.hover).corner_radius(egui::CornerRadius::same(6)).inner_margin(egui::Margin::same(10)).show(ui, |ui| {
|
||||
ui.set_width(ui.available_width());
|
||||
|
||||
@@ -58,6 +58,9 @@ pub use print_ui::{Handling as PrintHandling, PrintDraft, Which as PrintWhich};
|
||||
mod redact_ui;
|
||||
pub use redact_ui::{HiddenDraft, PagesDraft as RedactPagesDraft, RedactPrefs, SearchDraft as RedactSearchDraft};
|
||||
pub mod i18n;
|
||||
|
||||
/// The longest author name kept (Preferences ▸ Identity, restored settings).
|
||||
pub(crate) const MAX_AUTHOR_CHARS: usize = 200;
|
||||
mod protect;
|
||||
mod recovery;
|
||||
pub mod theme;
|
||||
@@ -824,6 +827,7 @@ impl PrintCraftApp {
|
||||
"recent": self.recent,
|
||||
"theme": self.theme,
|
||||
"language": self.language,
|
||||
"author": self.comment_prefs.author,
|
||||
// Drawn signatures keep their original form (older settings read the same).
|
||||
"signature": match &self.signature { Some(fill_sign::SavedSig::Drawn(s)) => Some(s), _ => None },
|
||||
"signature_text": match &self.signature { Some(fill_sign::SavedSig::Typed(t)) => Some(t), _ => None },
|
||||
@@ -853,6 +857,11 @@ impl PrintCraftApp {
|
||||
if let Ok(language) = serde_json::from_value::<i18n::Language>(v["language"].clone()) {
|
||||
self.language = language;
|
||||
}
|
||||
// An empty or missing name keeps the login-name default; settings are untrusted, so the
|
||||
// name is cut to a sane length.
|
||||
if let Some(author) = v["author"].as_str().map(str::trim).filter(|a| !a.is_empty()) {
|
||||
self.comment_prefs.author = author.chars().take(MAX_AUTHOR_CHARS).collect();
|
||||
}
|
||||
if let Ok(s) = serde_json::from_value::<Vec<Vec<[f32; 2]>>>(v["signature"].clone())
|
||||
&& s.iter().all(|st| st.iter().all(|p| p.iter().all(|x| x.is_finite())))
|
||||
{
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
//! Preferences ▸ Identity: the name new comments are signed with.
|
||||
|
||||
use egui::accesskit::Role;
|
||||
use egui::{Key, Modifiers};
|
||||
use egui_kittest::Harness;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use printcraft_ui_egui::PrintCraftApp;
|
||||
|
||||
const PDF: &[u8] = b"%PDF-1.7
|
||||
1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj
|
||||
2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj
|
||||
3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 300 200] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >> endobj
|
||||
4 0 obj << /Length 55 >> stream
|
||||
BT /F1 14 Tf 20 150 Td (The quick brown fox) Tj ET
|
||||
endstream endobj
|
||||
5 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj
|
||||
trailer << /Root 1 0 R >>
|
||||
%%EOF";
|
||||
|
||||
#[test]
|
||||
fn the_identity_name_signs_new_comments_and_is_remembered() {
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(|_cc| {
|
||||
let mut app = PrintCraftApp::new();
|
||||
app.open_bytes("doc.pdf", None, PDF.to_vec()).expect("opens");
|
||||
app.set_option("author", "Tester").unwrap();
|
||||
app.set_option("dialog", "preferences").unwrap();
|
||||
app
|
||||
});
|
||||
h.run_steps(4);
|
||||
h.get_by_label_contains("Name on new comments");
|
||||
// Replace the name in the field.
|
||||
h.get_by_role_and_label(Role::TextInput, "Name on new comments").click();
|
||||
h.run_steps(1);
|
||||
h.key_press_modifiers(Modifiers::COMMAND, Key::A);
|
||||
h.get_by_role_and_label(Role::TextInput, "Name on new comments").type_text("Grace Hopper");
|
||||
h.run_steps(2);
|
||||
assert_eq!(h.state().comment_prefs.author, "Grace Hopper");
|
||||
// New comments carry it.
|
||||
h.state_mut().set_option("dialog", "none").unwrap();
|
||||
h.state_mut().views[0].select_text(0, 4, 8);
|
||||
assert!(h.state_mut().execute("comment.highlight"));
|
||||
h.run_steps(2);
|
||||
let doc = h.state().session.get(h.state().views[0].id).unwrap();
|
||||
assert_eq!(doc.info.annotations.first().and_then(|a| a.author.as_deref()), Some("Grace Hopper"));
|
||||
// It survives a restart; an empty or missing name keeps the default, and a huge one is cut.
|
||||
let mut restored = PrintCraftApp::new();
|
||||
restored.restore(&h.state().persist());
|
||||
assert_eq!(restored.comment_prefs.author, "Grace Hopper");
|
||||
restored.restore(r#"{"author": " "}"#);
|
||||
assert_eq!(restored.comment_prefs.author, "Grace Hopper");
|
||||
restored.restore(&format!(r#"{{"author": "{}"}}"#, "x".repeat(10_000)));
|
||||
assert_eq!(restored.comment_prefs.author.chars().count(), 200);
|
||||
}
|
||||
@@ -3803,11 +3803,11 @@ title = "Author name from identity preferences"
|
||||
area = "E"
|
||||
tier = "P1"
|
||||
milestone = "M5"
|
||||
status = "partial"
|
||||
status = "shipped"
|
||||
commands = []
|
||||
tools = ["comment_add"]
|
||||
evidence = ["crates/ui-egui/tests/comments.rs::dragging_over_text_with_the_highlighter_highlights_it"]
|
||||
notes = "the login name, as Acrobat defaults to; no Identity preferences yet"
|
||||
evidence = ["crates/ui-egui/tests/comments.rs::dragging_over_text_with_the_highlighter_highlights_it", "crates/ui-egui/tests/identity.rs::the_identity_name_signs_new_comments_and_is_remembered"]
|
||||
notes = "Preferences ▸ Identity: the name on new comments (default the login name, as Acrobat), remembered across restarts"
|
||||
|
||||
[[feature]]
|
||||
id = "comment.reply"
|
||||
@@ -9639,7 +9639,7 @@ status = "partial"
|
||||
commands = ["app.preferences"]
|
||||
tools = ["js_enabled"]
|
||||
evidence = ["crates/ui-egui/tests/javascript.rs::document_scripts_and_preferences"]
|
||||
notes = "Preferences dialog with the JavaScript category; other categories still live in their tools."
|
||||
notes = "Preferences dialog with Interface language, Identity and JavaScript; other categories still live in their tools, and it isn't generated from a schema yet."
|
||||
|
||||
[[feature]]
|
||||
id = "misc.prefs-general"
|
||||
|
||||
Reference in new issue
Block a user