diff --git a/ROADMAP.md b/ROADMAP.md index abe319eba..70fff77fb 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -151,6 +151,7 @@ Imported Word 97–2003 `.doc`/`.dot` files now ask for a supported DOCX target | 2026-10-17 | Equation arrays centre a row that has no `&` among rows that do, as Word does, so case equations like Word's "First Property of Radicals" no longer push the aligned row right (#2617). | | 2026-10-16 | English dialects (#1281, #1045): English (UK), (Australia), (Canada) and the other Englishes are proofed in their own spelling — *colour* is flagged in a US document and *color* in a British one, with the right form suggested first — from our own list of American/British spellings, so nothing to install. Review › Language lists them, the .docx `w:lang` keeps them, and an English interface on a British (or Canadian, Australian…) system starts new documents in that English | | 2026-10-15 | Triple borders (tables, paragraphs, the `###` AutoFormat line) draw as three lines on screen and in PDF, across the line for vertical borders too; they used to draw as double lines (#1740) | +| 2026-10-17 | The document's right-click menu offers the ribbon's Paste menu, item for item (#2583): Paste (the clipboard's formatting kept, Word's Keep Source Formatting), Keep Text Only, Merge Formatting and Paste Special…, where it had a single Paste. Same commands and shortcuts, with the system clipboard | | 2026-10-14 | DOCX row table border exceptions render and survive saving. Explicit cell borders win; a row exception does not change other rows. | | 2026-10-14 | Tab stops edited on the ruler: the corner selector picks Left/Center/Right/Decimal/Bar, a click sets a stop (Alt skips the snap), dragging moves it with a guide line and position tooltip, dragging off the ruler clears it; markers draw per kind and mirror for RTL; one `para.tabs` call and one undo step per gesture | | 2026-10-12 | Fields keep `w:dirty` through DOCX saves (#877): a begin marker flagged by a generator that couldn't compute a result (pandoc-style TOC placeholders) keeps the flag on open/save — as a buffered field or a range field — so Word still refreshes it on open; computing a result (Update Field, Update Table) clears it, like Word's F9 | @@ -601,6 +602,7 @@ Footer page-table height and body wrapping are corrected. Other pagination gaps | 2026-10-17 | minor | Progress log: Scale to paper size on File › Print, #2108 | | 2026-10-17 | trivial | Non-overlapping body floating tables clear header tables; overlapping tables keep their requested position | | 2026-10-15 | trivial | Progress log: caret inside ligatures (#1974) | +| 2026-10-17 | trivial | Progress log: the ribbon's paste choices in the context menu (#2583) | | 2026-10-14 | trivial | Progress log: row table border exceptions | | 2026-10-12 | minor | Czech proofing review: short-word splits rank after deletion, NOSUGGEST excludes suggestions while accepting typed forms, affix comments apply only to headers, ratios keep spaces and repeated-word messages use Czech quotes; synthetic engine end-to-end and latency checks | | 2026-10-12 | minor | Czech spelling from the installed Hunspell dictionary, runtime-table suggestions and generic checks; Czech interface writes cs-CZ documents; no Czech hyphenation patterns bundled; estimates unchanged | diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 5f3b60797..eb76066de 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -3167,7 +3167,12 @@ fn context_menu(app: &mut WordApp, ui: &mut Ui) { } item(ui, app, "Cut", "edit.cut", json!({})); item(ui, app, "Copy", "edit.copy", json!({})); + // How to paste, chosen before pasting (#2583): the ribbon's Paste menu, item for item. Paste + // keeps the clipboard's formatting (Word's Keep Source Formatting). item(ui, app, "Paste", "edit.paste", json!({})); + item(ui, app, "Keep Text Only", "edit.pasteText", json!({})); + item(ui, app, "Merge Formatting", "edit.pasteMerge", json!({})); + item(ui, app, "Paste Special…", "edit.pasteSpecial", json!({})); ui.separator(); if let Some(syn) = app.canvas.context_synonyms.clone().and_then(|v| v.as_array().cloned()).filter(|a| !a.is_empty()) { ui.menu_button(tl!("Synonyms"), |ui| { @@ -4859,6 +4864,81 @@ mod tests { } } + /// #2583: the context menu offers the ribbon's Paste menu, item for item, and each pastes its + /// own way: Paste keeps the copied bold and colour (Word's Keep Source Formatting), Merge + /// Formatting keeps only the bold, Keep Text Only neither; Paste Special… opens its dialog. + #[test] + fn context_menu_offers_the_ribbons_paste_items() { + use egui_kittest::kittest::Queryable; + use wordcraft_doc::props::{Rgb, TextColor}; + let red = Some(TextColor::Rgb(Rgb(0xC0, 0, 0))); + let items = [ + ("Paste", "edit.paste"), + ("Keep Text Only", "edit.pasteText"), + ("Merge Formatting", "edit.pasteMerge"), + ("Paste Special…", "edit.pasteSpecial"), + ]; + for (choice, pasted) in [ + ("Paste", Some((true, red))), + ("Merge Formatting", Some((true, None))), + ("Keep Text Only", Some((false, None))), + ("Paste Special…", None), + ] { + let mut a = WordApp::new(wordcraft_engine::Session::new(wordcraft_doc::Document::from_text("Bold\nTarget")), Default::default()); + a.session + .doc + .format_range(&Pos::body(0, 0), &Pos::body(0, 4), &|c| { + c.bold = Some(true); + c.color = red; + }) + .unwrap(); + a.session.relayout(); + a.run("select.text", json!({"text": "Bold"})).unwrap(); + a.run("edit.copy", json!({})).unwrap(); + let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1440.0, 900.0)).build_ui_state( + |ui, app: &mut WordApp| { + let ctx = ui.ctx().clone(); + app.logic(&ctx); + app.ui(ui); + }, + a, + ); + for _ in 0..6 { + h.step(); + } + let c = h.state_mut().session.layout().caret(&Pos::body(1, 6)).expect("laid out"); + let at = page_to_screen(h.state_mut(), c.page, c.x, c.top + c.height / 2.0).expect("on screen"); + h.event(egui::Event::PointerMoved(at)); + for pressed in [true, false] { + h.event(egui::Event::PointerButton { pos: at, button: egui::PointerButton::Secondary, pressed, modifiers: Default::default() }); + h.step(); + } + h.step(); + // Each item reads as its name and its shortcut (none for Merge Formatting). + let labels: Vec = items.iter().map(|(name, id)| format!("{name} {}", crate::widgets::shortcut_text(h.state(), id))).collect(); + for l in &labels { + assert!(h.query_by_label(l).is_some(), "{choice}: {l:?} is offered"); + } + let k = items.iter().position(|(name, _)| *name == choice).unwrap(); + h.get_by_label(&labels[k]).click(); + for _ in 0..3 { + h.step(); + } + match pasted { + Some((bold, colour)) => { + let doc = &h.state().session.doc; + assert_eq!(doc.plain_text(StoryRef::Body), "Bold\nTargetBold", "{choice}: pasted at the end of the second paragraph"); + let p = doc.para_at(&Pos::body(1, 0)).expect("the target paragraph"); + assert_eq!(p.props_of_char(7).bold == Some(true), bold, "{choice}: bold"); + assert_eq!(p.props_of_char(7).color, colour, "{choice}: colour"); + } + None => { + assert!(matches!(h.state().dialog, Some(crate::dialogs::Dialog::PasteSpecial { .. })), "{choice}: the dialog opens"); + } + } + } + } + /// #996: on macOS a Control-click is the right-click (the trackpad's and the one-button /// mouse's way to it), so it opens the same menu and leaves the caret where the menu needs it. #[cfg(target_os = "macos")] diff --git a/docs/ui-parity.md b/docs/ui-parity.md index ea703adef..71a0da3fa 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -121,6 +121,7 @@ Footnote and endnote editing keeps body and note text at normal opacity, on ligh | 2026-10-15 | trivial | Text Highlight Color: No Color becomes the Highlight button's choice, as in Word, so the next click on the button removes highlighting instead of applying the colour picked before; the ribbon and the mini toolbar share one menu (#2105) | | 2026-10-16 | trivial | Body and active note text keep normal opacity while editing footnotes or endnotes (#2545); header/footer dimming is unchanged | | 2026-10-16 | trivial | Right-clicking a picture, shape, text box or chart offers Insert Caption…; a picture in a table gets a Figure caption in its cell. Page Setup has Headers and footers check boxes (Different Odd & Even Pages, Different First Page), applied on OK only if changed (#1230) | +| 2026-10-17 | trivial | Context menu: Paste, Keep Text Only, Merge Formatting and Paste Special… replace the single Paste, the same items as Home › Paste's menu (#2583). Word's context menu shows its Paste Options as a row of icons; here they are labelled items with their shortcuts, as on the ribbon | | 2026-10-13 | trivial | New/Modify Table Style: No Border on a part that inherits its lines hides them; emptying the font box makes the part's font inherited again (#1288) | | 2026-10-17 | minor | Insert › Drop Cap is a menu (None, Dropped, In margin, Drop Cap Options…) with a Drop Cap dialog (position, font, lines 1–10, distance 0–22 in), #2601; Modal dialogs 56 → 57 | | 2026-10-14 | trivial | Tab stops edited on the ruler: corner kind selector, click to set, drag to move or off to clear, per-kind markers mirrored for RTL; each gesture one `para.tabs` call and one undo step; Rulers 85% → 90% |