fix(ui): text typed into a new text box counts as unsaved work (#4441)

* fix(ui): text typed into a new text box counts as unsaved work

Closing a tab or quitting while the only unsaved thing was text in a new Add text box skipped the save prompt and lost the text. The box now counts like fill text does: only when it holds non-blank text.

* fix(ui): saving on close keeps text typed into a new text box
This commit is contained in:
aiongg
2026-10-24 14:25:44 -04:00
committed by GitHub
parent d445410807
commit a7801dc84e
+56
View File
@@ -38,6 +38,11 @@ pub enum SaveTarget {
As,
}
/// Text typed into a new text box that isn't added to the page yet.
fn new_text_typed(v: &crate::DocView) -> bool {
v.content.draft.as_ref().is_some_and(|d| d.index.is_none() && !d.text.trim().is_empty())
}
impl PdfCraftApp {
/// Apply an edit to the active document. Returns `true` on success; failures are shown.
pub fn apply_edit(&mut self, edit: Edit) -> bool {
@@ -636,6 +641,7 @@ impl PdfCraftApp {
|| v.content.blocked().is_some()
|| v.forms.focus.as_ref().is_some_and(|f| crate::forms_ui::draft_edit(f, &doc.form).is_some())
|| v.fill_text.as_ref().is_some_and(|t| !t.text.trim().is_empty())
|| new_text_typed(v)
|| v.line_editor.as_ref().map_or_else(|| false, |ed| ed.has_unsaved_text())
}
@@ -1694,6 +1700,56 @@ mod revert_draft_tests {
assert!(app.unsaved_flag.load(Ordering::Relaxed));
}
/// Text typed into a new text box is lost on close unless the save prompt appears; an empty
/// box has nothing to lose.
#[test]
fn text_typed_into_a_new_text_box_counts_as_unsaved_work() {
use crate::content_ui::{TextDraft, default_style};
let mut app = app();
let draft = |text: &str| TextDraft { page: 0, index: None, rect: [0.0; 4], text: text.into(), style: default_style(), focus: true };
assert!(!app.has_unsaved_work(0));
app.views[0].content.draft = Some(draft(" \n"));
assert!(!app.has_unsaved_work(0));
app.views[0].content.draft = Some(draft("Typed"));
assert!(!app.session.get(app.views[0].id).unwrap().dirty);
assert!(app.has_unsaved_work(0));
}
/// Close ▸ Save, and Quit ▸ Save, write the text typed into a new text box before the tab
/// closes.
#[test]
fn saving_on_close_keeps_text_typed_into_a_new_text_box() {
use crate::content_ui::{TextDraft, default_style};
use pdfcraft_engine::AddedContent;
for quit in [false, true] {
let dir = std::env::temp_dir().join(format!("pdfcraft-ui-new-text-{}-{quit}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("source.pdf").to_string_lossy().into_owned();
let bytes = include_bytes!("../tests/data/form.pdf").to_vec();
std::fs::write(&path, &bytes).unwrap();
let mut app = PdfCraftApp::new();
app.open_bytes("source.pdf", Some(path.clone()), bytes).unwrap();
let rect = [72.0, 72.0, 272.0, 92.0];
app.views[0].content.draft = Some(TextDraft { page: 0, index: None, rect, text: "Typed".into(), style: default_style(), focus: true });
let ctx = egui::Context::default();
if quit {
assert!(!app.request_quit(&ctx));
} else {
app.request_close_tab(0);
}
assert!(app.close_request.is_some());
app.resolve_close(&ctx, Some(true));
assert!(app.views.is_empty(), "the tab closes once saved");
let mut reopened = PdfCraftApp::new();
reopened.open_bytes("source.pdf", Some(path.clone()), std::fs::read(&path).unwrap()).unwrap();
let doc = reopened.session.get(reopened.views[0].id).unwrap();
assert!(doc.added().iter().any(|a| matches!(&a.content, AddedContent::Text(t) if t.text == "Typed")), "quit: {quit}");
let _ = std::fs::remove_dir_all(&dir);
}
}
#[test]
fn successful_revert_clears_focused_or_queued_drafts_only_on_its_document() {
for queued in [false, true] {