mirror of
https://github.com/aphrody-labs/wordcraft.git
synced 2026-10-14 03:29:04 +00:00
Bind Word's documented default shortcuts that were missing to existing commands, plus a few small new commands: Reset Paragraph Formatting (Ctrl+Q), Reset Character Formatting (Ctrl+Space, which used to clear all formatting), em dash, copyright, registered, trademark and ellipsis, DATE / TIME / PAGE fields (Alt+Shift+D/T/P), Print Layout / Outline / Draft / Split views, Word Count (Ctrl+Shift+G), Bookmark, Alt+F7, Ctrl+Page Up/Down find browsing, Shift+F12 / Ctrl+F12 / Ctrl+Shift+F12 / Ctrl+F2 / Ctrl+F4, Mark Index Entry, Mark Citation and the mail merge keys. Where macOS claims Word's key (Cmd+Q quits, Cmd+Space is Spotlight, Cmd+Shift+G is Find Previous, Option+letter types symbols), the binding follows Word for Mac or is left off there. F4 Repeat, Q and F2 now reach the registry from the keyboard, and an Alt shortcut no longer also types its key's character. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
29 files changed
+278
-42
No files matched your search
@@ -131,6 +131,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm
|
||||
|
||||
| Date | What landed |
|
||||
|---|---|
|
||||
| 2026-10-11 | Word's common keyboard shortcuts (#330): 101 → 125 of ~250 |
|
||||
| 2026-10-11 | Turkish (`tr`) interface: all 1,307 catalog entries, `tr-TR`/`tr-CY` system locales and a saved language choice; command search keeps Turkish dotted and dotless I apart (`italik` finds İtalik); locale, catalog, search and bundled-font tests; no Turkish proofing resources |
|
||||
| 2026-10-11 | Citations and bibliography in all 12 of Word's styles: Harvard, ISO 690 (author-date and numerical), Turabian, GB/T 7714, GOST (name and title sort) and SIST02 join APA, MLA, Chicago and IEEE; numbered styles count in order of first citation (#383) |
|
||||
| 2026-10-11 | Interface size setting (#475): File › Options › General scales the interface 80–200 % on top of the system display scale, kept between runs; `ui.interfaceSize` for agents |
|
||||
@@ -150,6 +151,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-11 | trivial | Word's common keyboard shortcuts (#330): 125 of ~250 |
|
||||
| 2026-10-11 | trivial | Collapsed duplicate rows left by parallel merges |
|
||||
| 2026-10-11 | trivial | Turkish interface (`tr`): progress log and shipped languages |
|
||||
| 2026-10-11 | trivial | Progress log: eight more bibliography styles (#383) |
|
||||
|
||||
@@ -331,7 +331,8 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
s.doc.insert_object(&b, InlineObject::Field { instr: format!("XE \"{}\"", entry.replace('"', "")), result: String::new(), locked: false }, &CharProps { hidden: Some(true), ..Default::default() })?;
|
||||
Ok(json!({"entry": entry}))
|
||||
})
|
||||
.params(r#"{"entry"?: string}"#),
|
||||
.params(r#"{"entry"?: string}"#)
|
||||
.key(super::non_mac("Alt+Shift+X")),
|
||||
CommandSpec::new("references.index", "Insert Index", "References › Index", |s, _| {
|
||||
generated_list(s, "INDEX", "Index")?;
|
||||
sel_result(s)
|
||||
@@ -349,7 +350,8 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
s.doc.insert_object(&b, InlineObject::Field { instr: format!("TA \\l \"{}\"", entry.replace('"', "")), result: String::new(), locked: false }, &CharProps { hidden: Some(true), ..Default::default() })?;
|
||||
Ok(json!({"entry": entry}))
|
||||
})
|
||||
.params(r#"{"entry"?: string}"#),
|
||||
.params(r#"{"entry"?: string}"#)
|
||||
.key(super::non_mac("Alt+Shift+I")),
|
||||
CommandSpec::new("references.tableOfAuthorities", "Insert Table of Authorities", "References › Table of Authorities", |s, _| {
|
||||
generated_list(s, "TOA", "Table of Authorities")?;
|
||||
sel_result(s)
|
||||
|
||||
@@ -144,7 +144,7 @@ pub(crate) fn dispatch(s: &mut Session, id: &str, mutates: bool, run: fn(&mut Se
|
||||
delete(s, &segs)?;
|
||||
super::sel_result(s)
|
||||
}
|
||||
"format.changeCase" | "format.clear" => per_row(s, run, v, &segs),
|
||||
"format.changeCase" | "format.clear" | "format.resetChar" => per_row(s, run, v, &segs),
|
||||
_ if id.starts_with("format.") || id.starts_with("para.") || id == "select.column" => run(s, v),
|
||||
_ if mutates && (id.starts_with("text.") || id.starts_with("edit.paste")) => {
|
||||
delete(s, &segs)?;
|
||||
|
||||
@@ -64,8 +64,11 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
CommandSpec::new("edit.advancedFind", "Advanced Find", "Home › Editing › Find", advanced_find)
|
||||
.params(r#"{"text"?: string, "matchCase"?: bool, "wholeWord"?: bool, "regex"?: bool, "in"?: "main|selection", "highlight"?: bool}"#)
|
||||
.pure(),
|
||||
CommandSpec::new("edit.findNext", "Find Next", "Home › Editing › Find", |s, _| step(s, 1)).key("Mod+G / F3").pure(),
|
||||
CommandSpec::new("edit.findPrevious", "Find Previous", "Home › Editing › Find", |s, _| step(s, -1)).key("Mod+Shift+G").pure(),
|
||||
CommandSpec::new("edit.findNext", "Find Next", "Home › Editing › Find", |s, _| step(s, 1)).key("Mod+G / F3 / Mod+PageDown").pure(),
|
||||
CommandSpec::new("edit.findPrevious", "Find Previous", "Home › Editing › Find", |s, _| step(s, -1))
|
||||
// Elsewhere Ctrl+Shift+G is Word Count; Ctrl+Page Up/Down browse the find results.
|
||||
.key(super::mac_or("Mod+Shift+G / Mod+PageUp", "Mod+PageUp"))
|
||||
.pure(),
|
||||
CommandSpec::new("edit.replace", "Replace", "Home › Editing", replace).key("Mod+H").params(r#"{"text": string, "with": string}"#),
|
||||
CommandSpec::new("edit.replaceAll", "Replace All", "Home › Editing › Replace", replace_all)
|
||||
.params(r#"{"text": string, "with": string, "matchCase"?: bool, "wholeWord"?: bool, "regex"?: bool}"#),
|
||||
|
||||
@@ -30,11 +30,11 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
vec![
|
||||
CommandSpec::new("file.new", "New", "File", new).key("Mod+N").params(r#"{"template"?: "blank|sample|letter|resume|report"}"#).pure(),
|
||||
CommandSpec::new("file.open", "Open", "File", open)
|
||||
.key("Mod+O")
|
||||
.key("Mod+O / Mod+F12")
|
||||
.params(r#"{"path": string, "data"?: base64 (the file's bytes; `path` then only names it), "password"?: string (for a password-protected Word document)}"#)
|
||||
.pure(),
|
||||
CommandSpec::new("file.save", "Save", "File", save)
|
||||
.key("Mod+S")
|
||||
.key("Mod+S / Shift+F12")
|
||||
.params(r#"{"path"?: string, "password"?: string (encrypt this and later saves with it, as file.encrypt does)}"#)
|
||||
.pure(),
|
||||
CommandSpec::new("file.saveAs", "Save As", "File", save_as).key("F12").params(r#"{"path": string, "password"?: string}"#).pure(),
|
||||
@@ -58,13 +58,13 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
s.ui_requests.push(json!({"open": "print"}));
|
||||
sel_result(s)
|
||||
})
|
||||
.key("Mod+P")
|
||||
.key("Mod+P / Mod+Shift+F12 / Mod+F2")
|
||||
.pure(),
|
||||
CommandSpec::new("file.close", "Close", "File", |s, _| {
|
||||
s.ui_requests.push(json!({"close": true}));
|
||||
sel_result(s)
|
||||
})
|
||||
.key("Mod+W")
|
||||
.key("Mod+W / Mod+F4")
|
||||
.pure(),
|
||||
CommandSpec::new("file.properties", "Properties", "File › Info", properties)
|
||||
.params(r#"{"title"?, "subject"?, "author"?, "keywords"?, "comments"?, "category"?, "custom"?: {name: string | null (removes)}}"#),
|
||||
|
||||
@@ -101,9 +101,12 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
CommandSpec::new("format.growFont1", "Grow Font 1 Point", "Home › Font", |s, _| nudge_size(s, 1.0)).key("Mod+]"),
|
||||
CommandSpec::new("format.shrinkFont1", "Shrink Font 1 Point", "Home › Font", |s, _| nudge_size(s, -1.0)).key("Mod+["),
|
||||
CommandSpec::new("format.color", "Font Color", "Home › Font", color).params(r#"{"color": "RRGGBB" | "auto"}"#),
|
||||
CommandSpec::new("format.highlight", "Text Highlight Color", "Home › Font", highlight).params(
|
||||
r#"{"color": "yellow|brightGreen|turquoise|pink|blue|red|darkBlue|teal|green|violet|darkRed|darkYellow|gray50|gray25|black|none"}"#,
|
||||
),
|
||||
CommandSpec::new("format.highlight", "Text Highlight Color", "Home › Font", highlight)
|
||||
.params(
|
||||
r#"{"color": "yellow|brightGreen|turquoise|pink|blue|red|darkBlue|teal|green|violet|darkRed|darkYellow|gray50|gray25|black|none"}"#,
|
||||
)
|
||||
// ⌘⌥H hides other apps on macOS.
|
||||
.key(super::non_mac("Mod+Alt+H")),
|
||||
CommandSpec::new("format.shading", "Character Shading", "Home › Font", |s, v| {
|
||||
let c = p::str(v, "color").and_then(Rgb::parse);
|
||||
apply(s, &|x| x.shading = c)
|
||||
@@ -112,7 +115,9 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
CommandSpec::new("format.changeCase", "Change Case", "Home › Font", change_case)
|
||||
.key("Shift+F3")
|
||||
.params(r#"{"mode"?: "sentence|lower|upper|title|toggle"}"#),
|
||||
CommandSpec::new("format.clear", "Clear All Formatting", "Home › Font", clear).key("Mod+Space"),
|
||||
CommandSpec::new("format.clear", "Clear All Formatting", "Home › Font", clear),
|
||||
// Word's Ctrl+Space; ⌘Space is Spotlight, so Word for Mac uses ⌃Space.
|
||||
CommandSpec::new("format.resetChar", "Reset Character Formatting", "Home › Font", reset_char).key(super::mac_or("Ctrl+Space", "Mod+Space")),
|
||||
CommandSpec::new("format.spacing", "Character Spacing", "Home › Font › Font › Advanced", |s, v| {
|
||||
let x = p::req_f32(v, "points")?.clamp(-100.0, 100.0);
|
||||
apply(s, &|c| c.spacing = Some(x))
|
||||
@@ -454,6 +459,15 @@ fn clear(s: &mut Session, _: &Value) -> CmdResult {
|
||||
sel_result(s)
|
||||
}
|
||||
|
||||
/// Remove direct character formatting, keeping the paragraph and any character style.
|
||||
fn reset_char(s: &mut Session, _: &Value) -> CmdResult {
|
||||
match target(s) {
|
||||
Some((a, b)) => s.doc.format_range(&a, &b, &|c| *c = CharProps { style: c.style.clone(), ..c.cleared() })?,
|
||||
None => s.pending = Some(CharProps::default()),
|
||||
}
|
||||
sel_result(s)
|
||||
}
|
||||
|
||||
fn char_style(s: &mut Session, v: &Value) -> CmdResult {
|
||||
let st = p::req_str(v, "style")?;
|
||||
let id = s.doc.styles.find(st).map(|x| x.id.clone()).ok_or_else(|| CmdError::Params(format!("no style `{st}`")))?;
|
||||
|
||||
@@ -41,7 +41,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
c.style = None;
|
||||
}
|
||||
})),
|
||||
CommandSpec::new("insert.bookmark", "Bookmark", "Insert › Links", bookmark).params(r#"{"name": string}"#),
|
||||
CommandSpec::new("insert.bookmark", "Bookmark", "Insert › Links", bookmark).params(r#"{"name": string}"#).key("Mod+Shift+F5"),
|
||||
CommandSpec::new("insert.header", "Header", "Insert › Header & Footer", |s, v| header_footer(s, v, true)).params(r#"{"text"?: string, "preset"?: "blank|blankThree|title"}"#),
|
||||
CommandSpec::new("insert.footer", "Footer", "Insert › Header & Footer", |s, v| header_footer(s, v, false)).params(r#"{"text"?: string, "preset"?: "blank|blankThree|pageNumber"}"#),
|
||||
CommandSpec::new("insert.pageNumber", "Page Number", "Insert › Header & Footer", page_number).params(r#"{"position"?: "top|bottom|current", "align"?: "left|center|right", "format"?: "x of y"}"#),
|
||||
@@ -71,6 +71,13 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
CommandSpec::new("insert.dateTime", "Date & Time", "Insert › Text", date_time).params(r#"{"format"?: "M/d/yyyy", "update"?: bool}"#),
|
||||
CommandSpec::new("insert.symbol", "Symbol", "Insert › Symbols", symbol).params(r#"{"char": string, "font"?: family (the symbol's own font)}"#),
|
||||
CommandSpec::new("insert.field", "Field", "Insert › Text › Quick Parts", field).key("Mod+F9").params(r#"{"instr": string, "result"?: string}"#),
|
||||
// Word's Alt+Shift+D / T / P (Control+Shift+… in Word for Mac): DATE, TIME and PAGE fields.
|
||||
CommandSpec::new("insert.dateField", "Insert Date Field", "Insert › Text › Quick Parts", |s, _| field(s, &json!({"instr": "DATE"})))
|
||||
.key(super::mac_or("Ctrl+Shift+D", "Alt+Shift+D")),
|
||||
CommandSpec::new("insert.timeField", "Insert Time Field", "Insert › Text › Quick Parts", |s, _| field(s, &json!({"instr": "TIME"})))
|
||||
.key(super::mac_or("Ctrl+Shift+T", "Alt+Shift+T")),
|
||||
CommandSpec::new("insert.pageField", "Insert Page Field", "Insert › Text › Quick Parts", |s, _| field(s, &json!({"instr": "PAGE"})))
|
||||
.key(super::mac_or("Ctrl+Shift+P", "Alt+Shift+P")),
|
||||
CommandSpec::new("insert.dropCap", "Drop Cap", "Insert › Text", |s, v| {
|
||||
let lines = p::u64(v, "lines").unwrap_or(3).min(10) as u8;
|
||||
super::para::fmt(s, &|p| p.drop_cap = Some(lines))
|
||||
|
||||
@@ -56,13 +56,15 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
Ok(json!({"headers": s.merge.headers, "rows": s.merge.rows}))
|
||||
})
|
||||
.params(r#"{"rows"?: [{field: value}] | [[value]], "fields"?: [string]}"#)
|
||||
.key(super::non_mac("Alt+Shift+E"))
|
||||
.when(has_list)
|
||||
.pure(),
|
||||
CommandSpec::new("mailings.insertField", "Insert Merge Field", "Mailings › Write & Insert Fields", |s, v| {
|
||||
let f = p::req_str(v, "field")?.to_string();
|
||||
insert_field(s, &format!("MERGEFIELD {}", quote(&f)), &format!("«{f}»"))
|
||||
})
|
||||
.params(r#"{"field": string}"#),
|
||||
.params(r#"{"field": string}"#)
|
||||
.key(super::non_mac("Alt+Shift+F")),
|
||||
CommandSpec::new("mailings.addressBlock", "Address Block", "Mailings › Write & Insert Fields", |s, _| {
|
||||
insert_field(s, "ADDRESSBLOCK", "«AddressBlock»")
|
||||
}),
|
||||
@@ -102,6 +104,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
refresh(s);
|
||||
Ok(json!({"preview": s.merge.preview, "record": s.merge.record + 1}))
|
||||
})
|
||||
.key(super::non_mac("Alt+Shift+K"))
|
||||
.when(has_recipients),
|
||||
CommandSpec::new("mailings.next", "Next Record", "Mailings › Preview Results", |s, _| step(s, 1)).when(has_recipients),
|
||||
CommandSpec::new("mailings.previous", "Previous Record", "Mailings › Preview Results", |s, _| step(s, -1)).when(has_recipients),
|
||||
@@ -131,6 +134,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
.pure(),
|
||||
CommandSpec::new("mailings.finish", "Finish & Merge", "Mailings › Finish", finish)
|
||||
.params(r#"{"path"?: string (save the merged document), "from"?: n, "to"?: n}"#)
|
||||
.key(super::non_mac("Alt+Shift+N"))
|
||||
.when(has_recipients),
|
||||
CommandSpec::new("mailings.envelopes", "Envelopes", "Mailings › Create", envelopes)
|
||||
.params(r#"{"delivery": string, "return"?: string, "size"?: "Envelope #10|Envelope DL"}"#),
|
||||
|
||||
@@ -66,6 +66,17 @@ pub fn registry() -> Registry {
|
||||
Registry::new(v)
|
||||
}
|
||||
|
||||
/// A shortcut that differs on macOS, where Word for Mac moves it (⌘Q quits, ⌘Space is
|
||||
/// Spotlight, ⌘⌥H hides other apps…): `mac` there, `other` everywhere else.
|
||||
pub const fn mac_or(mac: &'static str, other: &'static str) -> &'static str {
|
||||
if cfg!(target_os = "macos") { mac } else { other }
|
||||
}
|
||||
|
||||
/// A shortcut with no macOS equivalent (Option+letter types the character there).
|
||||
pub const fn non_mac(k: &'static str) -> &'static str {
|
||||
mac_or("", k)
|
||||
}
|
||||
|
||||
/// Parse a position from JSON (`{"story":"body","path":[0],"off":3}` or `{"block":0,"off":3}`).
|
||||
pub fn parse_pos(v: &Value) -> Option<Pos> {
|
||||
if let Ok(p) = serde_json::from_value::<Pos>(v.clone()) {
|
||||
|
||||
@@ -94,6 +94,14 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
let has = cur(s).space_after > 0.0;
|
||||
fmt(s, &|p| p.space_after = Some(if has { 0.0 } else { 8.0 }))
|
||||
}),
|
||||
// Back to the paragraph style: direct indents, spacing, alignment… go; the style, list
|
||||
// membership and drop cap stay. ⌘Q quits on macOS, so Word for Mac uses ⌃Q.
|
||||
CommandSpec::new("para.reset", "Reset Paragraph Formatting", "Home › Paragraph", |s, _| {
|
||||
fmt(s, &|p| {
|
||||
*p = ParaProps { style: p.style.take(), numbering: p.numbering.take(), drop_cap: p.drop_cap.take(), ..Default::default() }
|
||||
})
|
||||
})
|
||||
.key(super::mac_or("Ctrl+Q", "Mod+Q")),
|
||||
CommandSpec::new("para.style", "Apply Style", "Home › Styles", apply_style).params(r#"{"style": string (name or id)}"#),
|
||||
CommandSpec::new("para.normal", "Normal Style", "Home › Styles", |s, _| apply_style(s, &json!({"style": "Normal"}))).key("Mod+Shift+N"),
|
||||
CommandSpec::new("para.heading1", "Heading 1", "Home › Styles", |s, _| apply_style(s, &json!({"style": "Heading1"}))).key("Mod+Alt+1"),
|
||||
|
||||
@@ -46,9 +46,13 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
r#"{"comments"?: bool, "ink"?: bool, "insertionsDeletions"?: bool, "formatting"?: bool, "balloons"?: "revisions|inline|commentsAndFormatting", "insertMark"?: "underline|doubleUnderline|bold|italic|strikethrough|colorOnly|none", "insertColor"?: "byAuthor" | "RRGGBB", "deleteMark"?: "strikethrough|doubleStrikethrough|hidden|caret|hash|underline|colorOnly", "deleteColor"?: "byAuthor" | "RRGGBB", "changedLines"?: "outside|left|right|none", "changedLinesColor"?: "auto" | "RRGGBB", "trackFormatting"?: bool} → the options (omit all to read them)"#,
|
||||
)
|
||||
.pure(),
|
||||
CommandSpec::new("review.wordCount", "Word Count", "Review › Proofing", word_count).params(r#"{"includeTextBoxes"?: bool}"#).pure(),
|
||||
CommandSpec::new("review.wordCount", "Word Count", "Review › Proofing", word_count)
|
||||
.params(r#"{"includeTextBoxes"?: bool}"#)
|
||||
// ⌘⇧G is Find Previous on macOS.
|
||||
.key(super::non_mac("Mod+Shift+G"))
|
||||
.pure(),
|
||||
CommandSpec::new("review.changes", "Reviewing Pane", "Review › Tracking", list_changes).pure(),
|
||||
CommandSpec::new("review.spelling", "Spelling & Grammar", "Review › Proofing", next_issue).key("F7").pure(),
|
||||
CommandSpec::new("review.spelling", "Spelling & Grammar", "Review › Proofing", next_issue).key("F7 / Alt+F7").pure(),
|
||||
CommandSpec::new("review.issues", "Proofing Issues", "Review › Proofing", all_issues).pure(),
|
||||
CommandSpec::new("review.suggestions", "Spelling Suggestions", "Review › Proofing", suggestions).params(r#"{"pos"?: Pos}"#).pure(),
|
||||
CommandSpec::new("review.addToDictionary", "Add to Dictionary", "Review › Proofing", |s, v| {
|
||||
|
||||
@@ -5,7 +5,7 @@ use wordcraft_doc::para::{COLUMN_BREAK, NB_HYPHEN, NBSP, PAGE_BREAK, SOFT_HYPHEN
|
||||
use wordcraft_doc::props::{Border, BorderStyle, NumRef};
|
||||
use wordcraft_doc::{Block, ListKind, Pos};
|
||||
|
||||
use super::{delete_selection, sel_result, split_para, type_text};
|
||||
use super::{delete_selection, non_mac, sel_result, split_para, type_text};
|
||||
use crate::{CmdError, CmdResult, CommandSpec, Selection, Session, p};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
@@ -24,6 +24,12 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
CommandSpec::new("text.nbsp", "Nonbreaking Space", "Insert › Symbols", |s, _| ins_char(s, NBSP)).key("Mod+Shift+Space"),
|
||||
CommandSpec::new("text.nbHyphen", "Nonbreaking Hyphen", "Insert › Symbols", |s, _| ins_char(s, NB_HYPHEN)).key("Mod+Shift+-"),
|
||||
CommandSpec::new("text.optionalHyphen", "Optional Hyphen", "Insert › Symbols", |s, _| ins_char(s, SOFT_HYPHEN)).key("Mod+-"),
|
||||
CommandSpec::new("text.emDash", "Em Dash", "Insert › Symbols", |s, _| ins_char(s, '—')).key("Mod+Alt+-"),
|
||||
// On macOS Option+G / R / 2 / ; type these directly, so Word for Mac has no Cmd shortcut.
|
||||
CommandSpec::new("text.copyright", "Copyright Sign", "Insert › Symbols", |s, _| ins_char(s, '©')).key(non_mac("Mod+Alt+C")),
|
||||
CommandSpec::new("text.registered", "Registered Sign", "Insert › Symbols", |s, _| ins_char(s, '®')).key(non_mac("Mod+Alt+R")),
|
||||
CommandSpec::new("text.trademark", "Trademark Sign", "Insert › Symbols", |s, _| ins_char(s, '™')).key(non_mac("Mod+Alt+T")),
|
||||
CommandSpec::new("text.ellipsis", "Ellipsis", "Insert › Symbols", |s, _| ins_char(s, '…')).key(non_mac("Mod+Alt+.")),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -21,14 +21,15 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
})
|
||||
.key("Mod+Shift+8 / Mod+8")
|
||||
.pure(),
|
||||
CommandSpec::new("view.printLayout", "Print Layout", "View › Views", |s, _| mode(s, ViewMode::Print, false)).pure(),
|
||||
CommandSpec::new("view.printLayout", "Print Layout", "View › Views", |s, _| mode(s, ViewMode::Print, false)).key("Mod+Alt+P").pure(),
|
||||
CommandSpec::new("view.webLayout", "Web Layout", "View › Views", |s, _| mode(s, ViewMode::Web, false)).pure(),
|
||||
CommandSpec::new("view.draft", "Draft", "View › Views", |s, _| mode(s, ViewMode::Draft, false)).pure(),
|
||||
CommandSpec::new("view.draft", "Draft", "View › Views", |s, _| mode(s, ViewMode::Draft, false)).key("Mod+Alt+N").pure(),
|
||||
CommandSpec::new("view.outline", "Outline", "View › Views", |s, _| {
|
||||
mode(s, ViewMode::Draft, false)?;
|
||||
s.view.nav_pane = true;
|
||||
sel_result(s)
|
||||
})
|
||||
.key("Mod+Alt+O")
|
||||
.pure(),
|
||||
CommandSpec::new("view.readMode", "Read Mode", "View › Views", |s, _| mode(s, ViewMode::Print, true)).pure(),
|
||||
CommandSpec::new("view.focus", "Focus", "View › Immersive", |s, v| toggle(s, v, |x| &mut x.focus_mode)).pure(),
|
||||
@@ -71,6 +72,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
s.ui_requests.push(json!({"toggle": "split"}));
|
||||
sel_result(s)
|
||||
})
|
||||
.key("Mod+Alt+S")
|
||||
.pure(),
|
||||
CommandSpec::new("view.state", "View State", "View", |s, _| serde_json::to_value(&s.view).map_err(|e| CmdError::Failed(e.to_string())))
|
||||
.pure(),
|
||||
|
||||
@@ -73,6 +73,58 @@ fn every_shortcut_runs_one_command() {
|
||||
assert_eq!(reg.by_shortcut("Shift+F3").map(|c| c.id), Some("format.changeCase"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn word_default_shortcuts_resolve() {
|
||||
// #330: Word's documented default shortcuts reach the matching commands, with Word for
|
||||
// Mac's keys where macOS takes Word's (⌘Q quits, ⌘Space is Spotlight, ⌘⇧G is Find Previous).
|
||||
let reg = cmd::registry();
|
||||
let id = |k: &str| reg.by_shortcut(k).map(|c| c.id);
|
||||
assert_eq!(id("Mod+Alt+1"), Some("para.heading1"));
|
||||
assert_eq!(id("Mod+Alt+P"), Some("view.printLayout"));
|
||||
assert_eq!(id("Mod+Alt+O"), Some("view.outline"));
|
||||
assert_eq!(id("Mod+Alt+N"), Some("view.draft"));
|
||||
assert_eq!(id("Mod+Alt+S"), Some("view.split"));
|
||||
assert_eq!(id("Mod+Alt+-"), Some("text.emDash"));
|
||||
assert_eq!(id("Mod+Shift+F5"), Some("insert.bookmark"));
|
||||
assert_eq!(id("Alt+F7"), Some("review.spelling"));
|
||||
assert_eq!(id("Shift+F12"), Some("file.save"));
|
||||
assert_eq!(id(cmd::mac_or("Ctrl+Q", "Mod+Q")), Some("para.reset"));
|
||||
assert_eq!(id(cmd::mac_or("Ctrl+Space", "Mod+Space")), Some("format.resetChar"));
|
||||
assert_eq!(id(cmd::mac_or("Ctrl+Shift+D", "Alt+Shift+D")), Some("insert.dateField"));
|
||||
if cfg!(target_os = "macos") {
|
||||
assert_eq!(id("Mod+Shift+G"), Some("edit.findPrevious"));
|
||||
} else {
|
||||
assert_eq!(id("Mod+Shift+G"), Some("review.wordCount"));
|
||||
assert_eq!(id("Mod+Alt+C"), Some("text.copyright"));
|
||||
assert_eq!(id("Mod+Alt+H"), Some("format.highlight"));
|
||||
}
|
||||
assert_eq!(id("Mod+PageUp"), Some("edit.findPrevious"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_shortcuts_remove_direct_formatting_but_keep_styles() {
|
||||
let mut s = s();
|
||||
run(&mut s, "text.insert", json!({"text": "Hello world"}));
|
||||
run(&mut s, "para.heading1", json!({}));
|
||||
run(&mut s, "para.indent", json!({}));
|
||||
run(&mut s, "para.alignCenter", json!({}));
|
||||
run(&mut s, "para.reset", json!({}));
|
||||
let props = s.doc.para_at(&Pos::body(0, 0)).unwrap().props.clone();
|
||||
assert_eq!(props.style.as_deref(), Some("Heading1"), "Ctrl+Q keeps the paragraph style");
|
||||
assert_eq!((props.indent_left, props.align), (None, None), "Ctrl+Q drops direct paragraph formatting");
|
||||
|
||||
run(&mut s, "select.all", json!({}));
|
||||
run(&mut s, "format.bold", json!({}));
|
||||
run(&mut s, "format.resetChar", json!({}));
|
||||
let p = s.doc.para_at(&Pos::body(0, 0)).unwrap();
|
||||
assert_eq!(p.props_of_char(1).bold, None, "Ctrl+Space drops direct character formatting");
|
||||
assert_eq!(p.props.style.as_deref(), Some("Heading1"), "Ctrl+Space leaves the paragraph alone");
|
||||
|
||||
run(&mut s, "caret.docEnd", json!({}));
|
||||
run(&mut s, "text.emDash", json!({}));
|
||||
assert_eq!(text(&s), "Hello world—");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typing_enter_undo() {
|
||||
let mut s = s();
|
||||
|
||||
@@ -73,6 +73,9 @@ pub struct CanvasState {
|
||||
pub table_tool: Option<crate::table_pen::TableTool>,
|
||||
/// The Draw Table stroke (or eraser press) in progress.
|
||||
pub(crate) table_stroke: Option<crate::table_pen::PenStroke>,
|
||||
/// The last key ran an Alt shortcut: the text its key sends (it may arrive a frame later)
|
||||
/// isn't typed. The next key press clears it.
|
||||
pub(crate) swallow_text: bool,
|
||||
}
|
||||
|
||||
impl CanvasState {
|
||||
@@ -109,6 +112,7 @@ impl Default for CanvasState {
|
||||
context_menu_open: false,
|
||||
mini_anchor: None,
|
||||
pasted: false,
|
||||
swallow_text: false,
|
||||
wheel: Default::default(),
|
||||
scroll_offset: egui::Vec2::ZERO,
|
||||
scroll_max: egui::Vec2::ZERO,
|
||||
|
||||
@@ -549,6 +549,16 @@
|
||||
Open Abrir
|
||||
OpenDocument Text (*.odt) Texto de OpenDocument (*.odt)
|
||||
Optional Hyphen Guion opcional
|
||||
Copyright Sign Signo de copyright
|
||||
Ellipsis Puntos suspensivos
|
||||
Em Dash Raya
|
||||
Insert Date Field Insertar campo de fecha
|
||||
Insert Page Field Insertar campo de página
|
||||
Insert Time Field Insertar campo de hora
|
||||
Registered Sign Signo de marca registrada
|
||||
Reset Character Formatting Restablecer formato de caracteres
|
||||
Reset Paragraph Formatting Restablecer formato de párrafo
|
||||
Trademark Sign Signo de marca comercial
|
||||
Options Opciones
|
||||
Orientation Orientación
|
||||
Outline Esquema
|
||||
@@ -1155,8 +1165,6 @@
|
||||
Document Information Información del documento
|
||||
Double Closing Quote Comilla doble de cierre
|
||||
Double Opening Quote Comilla doble de apertura
|
||||
Ellipsis Puntos suspensivos
|
||||
Em Dash Raya
|
||||
En Dash Guion medio
|
||||
Em Space Espacio eme
|
||||
En Space Espacio ene
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
@@ -1323,6 +1323,14 @@
|
||||
Type the field's name and its switches, for example PAGE \\* roman. Sisesta välja nimi ja lülitid, näiteks PAGE \\* roman.
|
||||
Width and spacing Laius ja vahe
|
||||
from Unicode (hex) Unicode (kuueteistkümnend)
|
||||
Copyright Sign Autoriõiguse märk
|
||||
Insert Date Field Lisa kuupäevaväli
|
||||
Insert Page Field Lisa leheküljeväli
|
||||
Insert Time Field Lisa kellaajaväli
|
||||
Registered Sign Registreeritud kaubamärgi märk
|
||||
Reset Character Formatting Lähtesta märgivorming
|
||||
Reset Paragraph Formatting Lähtesta lõiguvorming
|
||||
Trademark Sign Kaubamärgi märk
|
||||
Formatted Vormindatud
|
||||
Interface size: Kasutajaliidese suurus:
|
||||
Makes the ribbon, menus and dialogs larger or smaller, on top of your system's display scaling. Document zoom is separate. Muudab menüülindi, menüüd ja dialoogid suuremaks või väiksemaks, lisaks süsteemi ekraani skaleerimisele. Dokumendi suum on eraldi.
|
||||
|
Can't render this file because it contains an unexpected character in line 421 and column 13.
|
@@ -539,6 +539,16 @@
|
||||
Open 開く
|
||||
OpenDocument Text (*.odt) OpenDocument テキスト (*.odt)
|
||||
Optional Hyphen 任意指定のハイフン
|
||||
Copyright Sign 著作権記号
|
||||
Ellipsis 省略記号
|
||||
Em Dash エム ダッシュ
|
||||
Insert Date Field 日付フィールドの挿入
|
||||
Insert Page Field ページ フィールドの挿入
|
||||
Insert Time Field 時刻フィールドの挿入
|
||||
Registered Sign 登録商標記号
|
||||
Reset Character Formatting 文字書式のリセット
|
||||
Reset Paragraph Formatting 段落書式のリセット
|
||||
Trademark Sign 商標記号
|
||||
Options オプション
|
||||
Orientation 印刷の向き
|
||||
Outline アウトライン
|
||||
@@ -1139,8 +1149,6 @@
|
||||
Document Information 文書の情報
|
||||
Double Closing Quote 二重引用符 (閉じ)
|
||||
Double Opening Quote 二重引用符 (開き)
|
||||
Ellipsis 省略記号
|
||||
Em Dash em ダッシュ
|
||||
En Dash en ダッシュ
|
||||
Em Space em スペース
|
||||
En Space en スペース
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 496 and column 13.
|
@@ -539,6 +539,16 @@
|
||||
Open Abrir
|
||||
OpenDocument Text (*.odt) Texto OpenDocument (*.odt)
|
||||
Optional Hyphen Hífen opcional
|
||||
Copyright Sign Sinal de copyright
|
||||
Ellipsis Reticências
|
||||
Em Dash Travessão
|
||||
Insert Date Field Inserir campo de data
|
||||
Insert Page Field Inserir campo de página
|
||||
Insert Time Field Inserir campo de hora
|
||||
Registered Sign Sinal de marca registrada
|
||||
Reset Character Formatting Redefinir formatação de caracteres
|
||||
Reset Paragraph Formatting Redefinir formatação de parágrafo
|
||||
Trademark Sign Sinal de marca comercial
|
||||
Options Opções
|
||||
Orientation Orientação
|
||||
Outline Estrutura de tópicos
|
||||
@@ -1151,8 +1161,6 @@
|
||||
Document Information Informações do documento
|
||||
Double Closing Quote Aspas duplas de fechamento
|
||||
Double Opening Quote Aspas duplas de abertura
|
||||
Ellipsis Reticências
|
||||
Em Dash Travessão
|
||||
En Dash Meia-risca
|
||||
Em Space Espaço eme
|
||||
En Space Espaço ene
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
@@ -587,6 +587,16 @@
|
||||
Open Otvori
|
||||
OpenDocument Text (*.odt) OpenDocument tekst (*.odt)
|
||||
Optional Hyphen Opcionalna crtica
|
||||
Copyright Sign Znak autorskog prava
|
||||
Ellipsis Tri tačke
|
||||
Em Dash Duga crta
|
||||
Insert Date Field Umetni polje datuma
|
||||
Insert Page Field Umetni polje strane
|
||||
Insert Time Field Umetni polje vremena
|
||||
Registered Sign Znak registrovanog žiga
|
||||
Reset Character Formatting Poništi formatiranje znakova
|
||||
Reset Paragraph Formatting Poništi formatiranje pasusa
|
||||
Trademark Sign Znak žiga
|
||||
Options Opcije
|
||||
Orientation Orijentacija
|
||||
Outline Kontura
|
||||
@@ -1254,8 +1264,6 @@
|
||||
Document Information Informacije o dokumentu
|
||||
Double Closing Quote Zatvoreni dvostruki navodnik
|
||||
Double Opening Quote Otvoreni dvostruki navodnik
|
||||
Ellipsis Tri tačke
|
||||
Em Dash Duga crta
|
||||
En Dash Srednja crta
|
||||
Em Space Em razmak
|
||||
En Space En razmak
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
@@ -591,6 +591,16 @@
|
||||
Open Отвори
|
||||
OpenDocument Text (*.odt) OpenDocument текст (*.odt)
|
||||
Optional Hyphen Опционална цртица
|
||||
Copyright Sign Знак ауторског права
|
||||
Ellipsis Три тачке
|
||||
Em Dash Дуга црта
|
||||
Insert Date Field Уметни поље датума
|
||||
Insert Page Field Уметни поље стране
|
||||
Insert Time Field Уметни поље времена
|
||||
Registered Sign Знак регистрованог жига
|
||||
Reset Character Formatting Поништи форматирање знакова
|
||||
Reset Paragraph Formatting Поништи форматирање пасуса
|
||||
Trademark Sign Знак жига
|
||||
Options Опције
|
||||
Orientation Оријентација
|
||||
Outline Контура
|
||||
@@ -1258,8 +1268,6 @@
|
||||
Document Information Информације о документу
|
||||
Double Closing Quote Затворени двоструки наводник
|
||||
Double Opening Quote Отворени двоструки наводник
|
||||
Ellipsis Три тачке
|
||||
Em Dash Дуга црта
|
||||
En Dash Средња црта
|
||||
Em Space Em размак
|
||||
En Space En размак
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 11 and column 25.
|
@@ -553,6 +553,16 @@
|
||||
Open Відкрити
|
||||
OpenDocument Text (*.odt) Текст OpenDocument (*.odt)
|
||||
Optional Hyphen М'який перенос
|
||||
Copyright Sign Знак авторського права
|
||||
Ellipsis Три крапки
|
||||
Em Dash Довге тире
|
||||
Insert Date Field Вставити поле дати
|
||||
Insert Page Field Вставити поле сторінки
|
||||
Insert Time Field Вставити поле часу
|
||||
Registered Sign Знак зареєстрованої торгової марки
|
||||
Reset Character Formatting Скинути форматування символів
|
||||
Reset Paragraph Formatting Скинути форматування абзацу
|
||||
Trademark Sign Знак торгової марки
|
||||
Options Параметри
|
||||
Orientation Орієнтація
|
||||
Outline План
|
||||
@@ -1151,8 +1161,6 @@
|
||||
Document Information Відомості про документ
|
||||
Double Closing Quote Закривні подвійні лапки
|
||||
Double Opening Quote Відкривні подвійні лапки
|
||||
Ellipsis Три крапки
|
||||
Em Dash Довге тире
|
||||
En Dash Коротке тире
|
||||
Em Space Широкий пробіл
|
||||
En Space Пробіл середньої ширини
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 31.
|
@@ -550,6 +550,16 @@
|
||||
Open 打开
|
||||
OpenDocument Text (*.odt) OpenDocument 文本 (*.odt)
|
||||
Optional Hyphen 可选连字符
|
||||
Copyright Sign 版权符号
|
||||
Ellipsis 省略号
|
||||
Em Dash 长破折号
|
||||
Insert Date Field 插入日期域
|
||||
Insert Page Field 插入页码域
|
||||
Insert Time Field 插入时间域
|
||||
Registered Sign 注册商标符号
|
||||
Reset Character Formatting 重置字符格式
|
||||
Reset Paragraph Formatting 重置段落格式
|
||||
Trademark Sign 商标符号
|
||||
Options 选项
|
||||
Orientation 纸张方向
|
||||
Outline 大纲
|
||||
@@ -1150,8 +1160,6 @@
|
||||
Document Information 文档信息
|
||||
Double Closing Quote 右双引号
|
||||
Double Opening Quote 左双引号
|
||||
Ellipsis 省略号
|
||||
Em Dash 长破折号
|
||||
En Dash 短破折号
|
||||
Em Space 全角空格
|
||||
En Space 半角空格
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
@@ -540,6 +540,16 @@
|
||||
Open 開啟
|
||||
OpenDocument Text (*.odt) OpenDocument 文字 (*.odt)
|
||||
Optional Hyphen 選擇性連字號
|
||||
Copyright Sign 著作權符號
|
||||
Ellipsis 刪節號
|
||||
Em Dash 長破折號
|
||||
Insert Date Field 插入日期欄位
|
||||
Insert Page Field 插入頁碼欄位
|
||||
Insert Time Field 插入時間欄位
|
||||
Registered Sign 註冊商標符號
|
||||
Reset Character Formatting 重設字元格式
|
||||
Reset Paragraph Formatting 重設段落格式
|
||||
Trademark Sign 商標符號
|
||||
Options 選項
|
||||
Orientation 方向
|
||||
Outline 大綱
|
||||
@@ -1140,8 +1150,6 @@
|
||||
Document Information 文件資訊
|
||||
Double Closing Quote 右雙引號
|
||||
Double Opening Quote 左雙引號
|
||||
Ellipsis 刪節號
|
||||
Em Dash 長破折號
|
||||
En Dash 短破折號
|
||||
Em Space 全形空格
|
||||
En Space 半形空格
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 497 and column 13.
|
@@ -35,7 +35,9 @@ pub fn key_name(k: Key) -> Option<&'static str> {
|
||||
Key::Num5 => "5",
|
||||
Key::Num8 => "8",
|
||||
Key::Num9 => "9",
|
||||
Key::F2 => "F2",
|
||||
Key::F3 => "F3",
|
||||
Key::F4 => "F4",
|
||||
Key::F5 => "F5",
|
||||
Key::F7 => "F7",
|
||||
Key::F8 => "F8",
|
||||
@@ -57,6 +59,7 @@ pub fn key_name(k: Key) -> Option<&'static str> {
|
||||
Key::N => "N",
|
||||
Key::O => "O",
|
||||
Key::P => "P",
|
||||
Key::Q => "Q",
|
||||
Key::R => "R",
|
||||
Key::S => "S",
|
||||
Key::T => "T",
|
||||
@@ -148,6 +151,12 @@ pub fn canvas_events(app: &mut WordApp, ctx: &egui::Context) {
|
||||
}
|
||||
let events = ctx.input(|i| i.events.clone());
|
||||
for e in events {
|
||||
// An Alt shortcut (Alt+Shift+D, Alt+F7…) may also send its key's text; it isn't typed.
|
||||
match &e {
|
||||
egui::Event::Key { pressed: true, .. } => app.canvas.swallow_text = false,
|
||||
egui::Event::Text(_) if std::mem::take(&mut app.canvas.swallow_text) => continue,
|
||||
_ => {}
|
||||
}
|
||||
if let egui::Event::Paste(t) = &e {
|
||||
app.canvas.pasted = true;
|
||||
// Copied picture files arrive as their paths: insert the pictures (#45).
|
||||
@@ -260,7 +269,7 @@ pub fn canvas_events(app: &mut WordApp, ctx: &egui::Context) {
|
||||
let _ = app.run("insert.closeHeader", json!({}));
|
||||
continue;
|
||||
}
|
||||
dispatch(app, key, modifiers);
|
||||
app.canvas.swallow_text = dispatch(app, key, modifiers) && modifiers.alt;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -1134,6 +1134,9 @@ fn input_dialog(id: &str, params: &Value) -> Option<&'static str> {
|
||||
// Define New Multilevel List and Track Changes Options without settings show their dialogs.
|
||||
"list.define" if params.get("levels").is_none() => Some("defineList"),
|
||||
"review.trackingOptions" if params.as_object().is_none_or(|m| m.is_empty()) => Some("trackChangesOptions"),
|
||||
"insert.bookmark" if !has("name") => Some("bookmark"),
|
||||
// Word Count from the keyboard or command search shows its dialog, as the ribbon does.
|
||||
"review.wordCount" if params.as_object().is_none_or(|m| m.is_empty()) => Some("wordCount"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -1755,6 +1758,36 @@ mod tests {
|
||||
assert_eq!(reported.get(), Some(true), "the host was told before the frame ended");
|
||||
}
|
||||
|
||||
/// #330: Word's shortcuts work from the keyboard — Ctrl+Alt+1 (⌘⌥1) applies Heading 1, and
|
||||
/// an Alt shortcut doesn't also type the character its key would produce.
|
||||
#[test]
|
||||
fn word_shortcuts_run_from_the_keyboard() {
|
||||
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);
|
||||
},
|
||||
app(),
|
||||
);
|
||||
for _ in 0..6 {
|
||||
h.step();
|
||||
}
|
||||
let key = |key, modifiers| egui::Event::Key { key, physical_key: None, pressed: true, repeat: false, modifiers };
|
||||
h.event(egui::Event::Text("Kilns".into()));
|
||||
h.step();
|
||||
h.event(key(egui::Key::Num1, egui::Modifiers::COMMAND | egui::Modifiers::ALT));
|
||||
h.step();
|
||||
let style =
|
||||
|h: &egui_kittest::Harness<WordApp>| h.state().session.doc.para_at(&wordcraft_doc::Pos::body(0, 0)).and_then(|p| p.props.style.clone());
|
||||
assert_eq!(style(&h).as_deref(), Some("Heading1"));
|
||||
// Alt+F7 (next misspelling) can come with key text on some platforms; none is typed.
|
||||
h.event(key(egui::Key::F7, egui::Modifiers::ALT));
|
||||
h.event(egui::Event::Text("x".into()));
|
||||
h.step();
|
||||
assert_eq!(h.state().session.doc.plain_text(wordcraft_doc::StoryRef::Body), "Kilns");
|
||||
}
|
||||
|
||||
/// Save writes the document first, then carries on with what the user asked for.
|
||||
#[test]
|
||||
fn save_then_continue() {
|
||||
|
||||
+2
-1
@@ -34,7 +34,7 @@ first (`gh pr list`), and update this file and the parity doc when a gap closes.
|
||||
| 21 | **Mail merge data sources**: no Excel, Outlook/Contacts, email merge; CSV and typed lists only (#247) | | | Office administrators | 6–10 | [target-app-parity.md](target-app-parity.md) |
|
||||
| 22 | **Ecosystem**: no VBA execution (macros preserved only), no Office add-ins, no cloud storage, no real-time co-authoring | | `.docm` keeps macros (#172) | Enterprise users with macro templates; teams | 40–80 + owner decisions | [target-app-parity.md](target-app-parity.md) |
|
||||
| 23 | **PDF Reflow** (open a PDF as an editable document, Word for Windows) | | | Users who edit PDFs they receive | 30–50 | [file-format-parity.md](file-format-parity.md) |
|
||||
| 24 | **Customize Ribbon / Keyboard**, Quick Access Toolbar customization; ~100 of Word's ~250 shortcuts | | [ui-parity.md](ui-parity.md) | Power users | 10–16 | [ui-parity.md](ui-parity.md) |
|
||||
| 24 | **Customize Ribbon / Keyboard**, Quick Access Toolbar customization; ~125 of Word's ~250 shortcuts (#330) | | [ui-parity.md](ui-parity.md) | Power users | 10–16 | [ui-parity.md](ui-parity.md) |
|
||||
| 25 | **In-app AI assistant** (Copilot-style draft, rewrite, summarize) | | Chat PR #178 open | Users expecting Copilot | 15–30 + owner (provider) | [target-app-parity.md](target-app-parity.md) |
|
||||
| 26 | **Minor formats**: Flat OPC XML, Word 2003 XML, MHT, Works/WordPerfect | | | Rare | 20–35 | [file-format-parity.md](file-format-parity.md) |
|
||||
| 27 | **View windows**: Side by Side, Synchronous Scrolling, Arrange All, Switch Windows | | [parity-checklist.md](parity-checklist.md) (View 25/29) | Comparing documents | 3–5 | [ui-parity.md](ui-parity.md) |
|
||||
@@ -64,6 +64,7 @@ The same gaps grouped the way the parity documents are, for agents working in on
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-10 | trivial | #24: 125 of ~250 keyboard shortcuts after #330 |
|
||||
| 2026-10-11 | trivial | Collapsed duplicate rows left by parallel merges |
|
||||
| 2026-10-11 | trivial | All 12 bibliography styles landed (#383); removed from References depth |
|
||||
| 2026-10-11 | trivial | Formatting revisions landed (#41); gap #5 narrowed to move tracking and content controls, 8–12 h |
|
||||
|
||||
@@ -103,7 +103,7 @@ h), pictures and tables (12–18 h), spelling depth (8–12 h), polish (10–20
|
||||
|
||||
| Discount | Mainstream | Essentials | Evidence |
|
||||
|---|---|---|---|
|
||||
| Interaction fidelity | ×0.93 | — | Typing pinned to Word (`tests_typing.rs`), ribbon, keytips, mini-toolbar; but 16 modal dialogs against Word's ~100, 101 of ~250 shortcuts, no table column drag on the canvas (#217, #49), picture/shape handling complaints (#82, #142), lines wrap differently without an Aptos-metric font |
|
||||
| Interaction fidelity | ×0.93 | — | Typing pinned to Word (`tests_typing.rs`), ribbon, keytips, mini-toolbar; but 16 modal dialogs against Word's ~100, 125 of ~250 shortcuts, no table column drag on the canvas (#217, #49), picture/shape handling complaints (#82, #142), lines wrap differently without an Aptos-metric font |
|
||||
| Discoverability and UI clarity | — | ×0.95 | Word-style ribbon a Word user already knows, command search, keytips; some newer pane strings and tooltips terse or untranslated |
|
||||
| Stability on real machines | ×0.90 | ×0.90 (launch and install) | Never-crash standard, hostile-param fuzzing, panic guard; but open startup crash on Intel UHD (#170), freezes (#77, #59, #31), Windows writes fail without admin (#218), installer conflicts with other Crafting Apps (#64, #73, #106, #51); 5 days of field history |
|
||||
| Exchanging files with Word users | ×0.90 | ×0.92 (opening files people send) | Word opens our .docx; `.doc` import; but no real-world corpus test, charts/SmartArt/OLE dropped silently, move tracking lost, pagination differs (Aptos, no column balancing or footnote continuation). Simple documents, which are most of what a casual user receives, come through well; double-click open on macOS is fixed on main but not yet released (#223, #279) |
|
||||
@@ -190,7 +190,7 @@ Word. Their weighted sum is the **full ready for real work** number.
|
||||
| Features (depth, table above) | 30% | 68% | 210–335 | this file | Weighted table above. Hours: the area table sums to 290–460 h; the Page layout, Footnotes, RTL and East Asian rows (~50–85 h) are counted under Layout and the Proofing row (25–45 h) under Localization, so they aren't counted twice |
|
||||
| File formats (DOCX fidelity first) | 20% | 60% | 185–300 | [file-format-parity.md](file-format-parity.md) | DOCX opens in Word and round-trips our own tests, but no real-world corpus test yet; charts, SmartArt, OLE, content controls (unwrapped), move tracking and encrypted files are lost or refused |
|
||||
| Layout and pagination fidelity | 15% | 55% | 90–145 | [layout-parity.md](layout-parity.md) | Aptos has no metric-matched substitute so lines break differently from Word; no column balancing, footnote continuation or document grid; compatibility modes beyond 15 partial |
|
||||
| UI/UX fidelity | 10% | 65% | 65–100 | [ui-parity.md](ui-parity.md) | Ribbon, keytips, mini-toolbar, Backstage, 101 shortcuts; 16 modal dialogs against Word's ~100; no ribbon/keyboard customization |
|
||||
| UI/UX fidelity | 10% | 65% | 65–100 | [ui-parity.md](ui-parity.md) | Ribbon, keytips, mini-toolbar, Backstage, 125 shortcuts; 16 modal dialogs against Word's ~100; no ribbon/keyboard customization |
|
||||
| Stability | 10% | 50% | 25–45 | [gaps.md](gaps.md) | Never-crash standard and hostile-param fuzzing in place, but open reports of a startup crash on Intel UHD (#170), freezes (#77, #59, #31) and Windows write failure without admin (#218); no soak test; 5 days of field history |
|
||||
| Performance | 5% | 75% | 10–20 | [layout-parity.md](layout-parity.md) | 61 ms cold layout and 1.4 ms relayout on a 188-page document (measured 2026-10-06, not re-run); font picker freezes fixed in #233; no large real-world documents measured |
|
||||
| Localization | 4% | 25% | 80–130 + native review | [localization-parity.md](localization-parity.md) | 9 UI languages (Word 30); 4 of the 12 key languages partly done; English-only proofing |
|
||||
@@ -249,6 +249,7 @@ can be used without committing Word output.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-11 | trivial | Shortcut count 101 → 125 (#330) |
|
||||
| 2026-10-11 | trivial | Equation fixes: removing numbers keeps every row (#444), caret moves keep imported OMML (#446), build-up on a caret move is an undoable edit (#447), LaTeX `\text{}` keeps its spaces (#448); no percentage change |
|
||||
| 2026-10-11 | trivial | English spelling (#520): common modern words ("desktop", "download", "email"…) recognised via our supplement list; Proofing row updated, no percentage change |
|
||||
| 2026-10-11 | trivial | References: all 12 of Word's bibliography styles (eight more: Harvard, ISO 690 ×2, Turabian, GB/T 7714, GOST ×2, SIST02) (#383); no percentage change |
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
# UI parity with Microsoft Word
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** major (first version; ribbon, dialogs, shortcuts and on-canvas interaction measured from `crates/ui-egui` and the command registry) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** minor (Word's common default keyboard shortcuts bound, #330: 101 → 125 commands; previously major: first version; ribbon, dialogs, shortcuts and on-canvas interaction measured from `crates/ui-egui` and the command registry) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
|
||||
|
||||
How WordCraft looks and feels next to Word: ribbon, dialogs, panes, keyboard, mouse and touch.
|
||||
Typing behaviour has its own checklist ([`typing-parity.md`](typing-parity.md)). All assets are our
|
||||
@@ -15,7 +15,7 @@ own (`AGENTS.md`: no Microsoft iconography, ever).
|
||||
| Mini-toolbar on selection | ✅ (#43) | ✅ | 80% | 1–2 |
|
||||
| Quick Access Toolbar | Undo, Redo, Repeat, AutoSave | customizable | 50% | 2–3 |
|
||||
| Customize Ribbon / Customize Keyboard | ❌ | ✅ | 0% | 6–10 |
|
||||
| Keyboard shortcuts | **101** bound in `CommandSpec::key` (measured) | ~250 default shortcuts | ~60% | 4–6 |
|
||||
| Keyboard shortcuts | **125** commands bound in `CommandSpec::key`, 144 key combinations (measured on Linux/Windows); styles, spacing, symbols, fields, views, mail merge (#330). Missing: field-code toggles (Shift+F9, Alt+F9), unlink/lock fields, en dash on the numeric keypad, move/promote paragraph, Go Back | ~250 default shortcuts | ~70% | 2–4 |
|
||||
| Modal dialogs | **35** (`Dialog` enum: Font, Paragraph, Tabs, Borders and Shading, Find, Go To, Insert Table, Table Properties, Page Setup, Link, Bookmark, Word Count, Zoom, Watermark, New Style, Modify Style, Manage Styles, Table Style, Commands, Paste Special, About, Save Changes, Password, Encrypt with Password, Define New Multilevel List, Track Changes Options; Columns, Symbol with Special Characters, Field (#321); mail merge Recipients, Insert Merge Field, Find Recipient, Rules, Match Fields, Check for Errors) plus panes and menus for the rest | ~100 (Caption, Index, TOC options, Citation, Compare, Protect, Language, Hyphenation, Line Numbers, Envelopes, Labels, AutoCorrect, Options panes…) | ~40% | 20–30 |
|
||||
| Task panes (Navigation, Styles, Comments, Reviewing, Format Picture/Shape, Thesaurus, Accessibility, Clipboard, Selection) | Navigation, Styles, Comments, Thesaurus, Accessibility, Read Aloud, Zotero; Style Inspector (#236); Clipboard pane (#264) open | all | 70% | 4–6 |
|
||||
| Context menus (right-click) | text, spelling suggestions; objects and tables partial | rich per context | 55% | 3–5 |
|
||||
@@ -37,6 +37,7 @@ own (`AGENTS.md`: no Microsoft iconography, ever).
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-11 | minor | Keyboard shortcuts 101 → 125 commands (#330): Reset Paragraph/Character Formatting, symbols, DATE/TIME/PAGE fields, view switches, Word Count, mail merge, F4 Repeat now reachable |
|
||||
| 2026-10-11 | trivial | Collapsed duplicate rows left by parallel merges |
|
||||
| 2026-10-11 | trivial | Dialogs keep their top edge in place when switching tabs (Paragraph, Font, Borders and Shading…): only the bottom edge moves (#507) |
|
||||
| 2026-10-11 | trivial | Interface size setting in File › Options › General (80–200 %), kept between runs and applied on top of the system's display scale; `ui.interfaceSize` for agents (#475) |
|
||||
|
||||
Reference in new issue
Block a user