Type menu in InDesign's order; Character ⌘T, Paragraph ⌥⌘T and Current Page Number ⌥⇧⌘N

Chris Nielsen's "Numbering Page Sections" (2:09) reaches Insert Special
Character › Markers › Current Page Number through InDesign's Type menu:
Font, Size, Character, Paragraph, Tabs, Glyphs, Story, the style panels,
Create Outlines, Find Font, Change Case, Type on a Path, Notes, Track
Changes, the footnote and endnote items, Hyperlinks & Cross-References,
Text Variables, Bulleted & Numbered Lists, the insert submenus, Fill with
Placeholder Text and Show Hidden Characters. DesignCraft's began with Find/
Replace Font and Create Outlines and held alignment, Bold/Italic and point
size steps InDesign keeps out of its menus.

- Size ▸ (6–72 pt), Character, Paragraph, Glyphs, Character Styles,
  Paragraph Styles (InDesign's shortcuts: ⌘T, ⌥⌘T, ⌥⇧F11, ⇧⌘F11, ⌘F11),
  then InDesign's order. Notes ▸ holds New Note and the Notes panel.
- Current Page Number carries ⌥⇧⌘N (app.insertCurrentPageNumber).
- Alignment, Bold/Italic/Underline/All Caps, point size steps and
  Paragraph Rules leave the menu; their shortcuts and panel buttons stay.
- DesignCraft's math and CJK items follow after Show Hidden Characters.
- Labels "Bulleted & Numbered Lists" and "Show Hidden Characters", with
  translations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
moppeeer
2026-10-14 12:38:39 +02:00
co-authored by Claude Opus 5.5
parent 43a46c04bf
commit f4aa93f17d
8 changed files with 192 additions and 49 deletions
+1
View File
@@ -128,6 +128,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m
Newest first.
- **2026-10-14** · The Type menu follows InDesign's order (Size, Character ⌘T, Paragraph ⌥⌘T, Glyphs, the style panels, Notes, footnotes and endnotes, then the insert submenus and Show Hidden Characters); Current Page Number has ⌥⇧⌘N. No readiness percentage change.
- **2026-10-12** · Text threads by hand: click a selected text frame's in or out port to load
the text cursor, then click a frame to thread it, click empty space for a column-wide frame or
drag one (it snaps like frame drawing); Shift-click autoflows, Alt-click stays loaded on the
+11
View File
@@ -55,6 +55,17 @@ const TABLE: &[(&str, [&str; 5])] = &[
("Cut", ["Ausschneiden", "Couper", "Cortar", "カット", "剪切"]),
("Copy", ["Kopieren", "Copier", "Copiar", "コピー", "复制"]),
("Paste", ["Einfügen", "Coller", "Pegar", "ペースト", "粘贴"]),
(
"Bulleted & Numbered Lists",
[
"Aufzählungszeichen und nummerierte Listen",
"Listes à puces et numérotées",
"Listas con viñetas y numeradas",
"箇条書き",
"项目符号列表和编号列表",
],
),
("Current Page Number", ["Aktuelle Seitenzahl", "Numéro de page actuelle", "Número de página actual", "現在のページ番号", "当前页码"]),
("Reverse Path", ["Pfadrichtung umkehren", "Inverser le tracé", "Invertir trazado", "パスの方向反転", "反转路径"]),
("Set Anchor", ["Ankerpunkt festlegen", "Définir le point d’ancrage", "Definir punto de ancla", "アンカーポイントを設定", "设置锚点"]),
("Clear", ["Löschen", "Effacer", "Borrar", "消去", "清除"]),
+2
View File
@@ -30,6 +30,8 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "قص"),
("Copy", "نسخ"),
("Paste", "لصق"),
("Bulleted & Numbered Lists", "القوائم ذات التعداد النقطي والرقمي"),
("Current Page Number", "رقم الصفحة الحالية"),
("Reverse Path", "عكس المسار"),
("Set Anchor", "تعيين نقطة الارتساء"),
("Clear", "مسح"),
+2
View File
@@ -28,6 +28,8 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "Taglia"),
("Copy", "Copia"),
("Paste", "Incolla"),
("Bulleted & Numbered Lists", "Elenchi puntati e numerati"),
("Current Page Number", "Numero di pagina corrente"),
("Reverse Path", "Inverti tracciato"),
("Set Anchor", "Imposta ancoraggio"),
("Clear", "Cancella"),
+1
View File
@@ -30,6 +30,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "カット"),
("Copy", "コピー"),
("Paste", "ペースト"),
("Bulleted & Numbered Lists", "箇条書き"),
("Reverse Path", "パスの方向反転"),
("Set Anchor", "アンカーポイントを設定"),
("Clear", "消去"),
+5
View File
@@ -31,6 +31,11 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "Recortar"),
("Copy", "Copiar"),
("Paste", "Colar"),
("Bulleted & Numbered Lists", "Listas com marcadores e numeradas"),
("Show Hidden Characters", "Mostrar caracteres ocultos"),
("Current Page Number", "Número da página atual"),
("New Note", "Nova nota"),
("Insert Endnote", "Inserir nota de fim"),
("Reverse Path", "Inverter demarcador"),
("Set Anchor", "Definir ponto de ancoragem"),
("Clear", "Limpar"),
+2
View File
@@ -31,6 +31,8 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "Вирізати"),
("Copy", "Копіювати"),
("Paste", "Вставити"),
("Bulleted & Numbered Lists", "Марковані й нумеровані списки"),
("Current Page Number", "Номер поточної сторінки"),
("Reverse Path", "Розвернути контур"),
("Set Anchor", "Задати прив’язку"),
("Clear", "Очистити"),
+168 -49
View File
@@ -75,6 +75,12 @@ pub const UI_COMMANDS: &[(&str, &str, Option<&str>, &str)] = &[
("app.deletePage", "Delete Page", None, "{} — deletes the page in view"),
("app.duplicateSpread", "Duplicate Spread", None, "{} — duplicates the spread in view (pages and items)"),
("app.tablePanel", "Table Panel", Some("Shift+F9"), "{}"),
("app.characterPanel", "Character", Some("Cmd+T"), "{} — the Character panel"),
("app.paragraphPanel", "Paragraph", Some("Cmd+Alt+T"), "{} — the Paragraph panel"),
("app.glyphsPanel", "Glyphs", Some("Alt+Shift+F11"), "{} — the Glyphs panel"),
("app.characterStylesPanel", "Character Styles", Some("Cmd+Shift+F11"), "{} — the Character Styles panel"),
("app.paragraphStylesPanel", "Paragraph Styles", Some("Cmd+F11"), "{} — the Paragraph Styles panel"),
("app.insertCurrentPageNumber", "Current Page Number", Some("Cmd+Alt+Shift+N"), "{} — the Current Page Number marker at the insertion point"),
("app.storyEditor", "Edit in Story Editor", Some("Cmd+Y"), "{story?}"),
("view.zoomIn", "Zoom In", Some("Cmd+="), "{}"),
("view.zoomOut", "Zoom Out", Some("Cmd+-"), "{}"),
@@ -120,7 +126,7 @@ pub const UI_COMMANDS: &[(&str, &str, Option<&str>, &str)] = &[
),
("view.baselineGrid", "Show/Hide Baseline Grid", Some("Cmd+Alt+'"), "{}"),
("view.textThreads", "Show/Hide Text Threads", Some("Cmd+Alt+Y"), "{}"),
("view.hiddenCharacters", "Show/Hide Hidden Characters", Some("Cmd+Alt+I"), "{}"),
("view.hiddenCharacters", "Show Hidden Characters", Some("Cmd+Alt+I"), "{}"),
("view.taggedFrames", "Show/Hide Tagged Frames", None, "{} — XML-tagged frames outlined in their tag colour"),
("window.hidePanels", "Show/Hide Panels", Some("Tab"), "{} — every panel, the Tools panel included"),
("window.hidePanelsExceptTools", "Show/Hide Panels Except Tools", Some("Shift+Tab"), "{}"),
@@ -330,18 +336,76 @@ pub const MENUS: &[(&str, &[&str])] = &[
(
"Type",
&[
"ui:app.findFontDialog",
">Size",
"cmd:type.char|6 pt|{\"attrs\": {\"size\": 6}}",
"cmd:type.char|7 pt|{\"attrs\": {\"size\": 7}}",
"cmd:type.char|8 pt|{\"attrs\": {\"size\": 8}}",
"cmd:type.char|9 pt|{\"attrs\": {\"size\": 9}}",
"cmd:type.char|10 pt|{\"attrs\": {\"size\": 10}}",
"cmd:type.char|11 pt|{\"attrs\": {\"size\": 11}}",
"cmd:type.char|12 pt|{\"attrs\": {\"size\": 12}}",
"cmd:type.char|14 pt|{\"attrs\": {\"size\": 14}}",
"cmd:type.char|18 pt|{\"attrs\": {\"size\": 18}}",
"cmd:type.char|24 pt|{\"attrs\": {\"size\": 24}}",
"cmd:type.char|30 pt|{\"attrs\": {\"size\": 30}}",
"cmd:type.char|36 pt|{\"attrs\": {\"size\": 36}}",
"cmd:type.char|48 pt|{\"attrs\": {\"size\": 48}}",
"cmd:type.char|60 pt|{\"attrs\": {\"size\": 60}}",
"cmd:type.char|72 pt|{\"attrs\": {\"size\": 72}}",
"<",
"-",
"ui:app.characterPanel",
"ui:app.paragraphPanel",
"ui:app.glyphsPanel",
"-",
"ui:app.characterStylesPanel",
"ui:app.paragraphStylesPanel",
"-",
"cmd:type.createOutlines",
">Bulleted and Numbered Lists",
"cmd:list.define",
"ui:app.findFontDialog",
">Change Case",
"cmd:type.changeCase|UPPERCASE|{\"case\": \"upper\"}",
"cmd:type.changeCase|lowercase|{\"case\": \"lower\"}",
"cmd:type.changeCase|Title Case|{\"case\": \"title\"}",
"cmd:type.changeCase|Sentence case|{\"case\": \"sentence\"}",
"<",
"-",
">Type on a Path",
"cmd:type.pathOptions",
"cmd:type.pathOptions|Delete Type from Path|{\"delete\": true}",
"<",
"ui:window.panel|Glyphs|{\"panel\": \"glyphs\"}",
"cmd:type.fillWithPlaceholder",
"-",
">Notes",
"cmd:note.new",
"ui:window.panel|Notes|{\"panel\": \"notes\"}",
"<",
">Track Changes",
"cmd:changes.track",
"cmd:changes.acceptAll",
"cmd:changes.rejectAll",
"<",
"-",
"cmd:footnote.insert",
"ui:app.footnoteOptionsDialog",
"cmd:endnote.insert",
"cmd:footnote.goToReference",
"-",
">Hyperlinks & Cross-References",
"cmd:hyperlink.create",
"cmd:anchor.create",
"-",
"ui:app.insertXrefDialog",
"cmd:xref.defineFormat",
"<",
">Text Variables",
"cmd:variables.define",
"cmd:variables.insert",
"<",
"-",
">Bulleted & Numbered Lists",
"cmd:list.define",
"<",
"-",
">Insert Special Character",
">Symbols",
"cmd:text.insert|Bullet Character|{\"text\": \"\u{2022}\", \"raw\": true}",
@@ -353,7 +417,7 @@ pub const MENUS: &[(&str, &[&str])] = &[
"cmd:text.insert|Trademark Symbol|{\"text\": \"\u{2122}\", \"raw\": true}",
"<",
">Markers",
"cmd:text.insert|Current Page Number|{\"text\": \"\u{e000}\", \"raw\": true}",
"ui:app.insertCurrentPageNumber",
"cmd:text.insert|Next Page Number|{\"text\": \"\u{e007}\", \"raw\": true}",
"cmd:text.insert|Previous Page Number|{\"text\": \"\u{e008}\", \"raw\": true}",
"cmd:text.insert|Section Marker|{\"text\": \"\u{e001}\", \"raw\": true}",
@@ -400,44 +464,10 @@ pub const MENUS: &[(&str, &[&str])] = &[
"cmd:text.insert|Paragraph Return|{\"text\": \"\\n\", \"raw\": true}",
"cmd:text.insert|Forced Line Break|{\"text\": \"\u{2028}\", \"raw\": true}",
"<",
"-",
">Change Case",
"cmd:type.changeCase|UPPERCASE|{\"case\": \"upper\"}",
"cmd:type.changeCase|lowercase|{\"case\": \"lower\"}",
"cmd:type.changeCase|Title Case|{\"case\": \"title\"}",
"cmd:type.changeCase|Sentence case|{\"case\": \"sentence\"}",
"<",
"-",
"cmd:type.alignLeft",
"cmd:type.alignCenter",
"cmd:type.alignRight",
"cmd:type.justify",
"ui:app.paragraphRulesDialog",
"-",
"cmd:type.bold",
"cmd:type.italic",
"cmd:type.underline",
"cmd:type.allCaps",
"-",
"cmd:type.sizeUp",
"cmd:type.sizeDown",
"cmd:type.fillWithPlaceholder",
"-",
"ui:view.hiddenCharacters",
"-",
">Hyperlinks & Cross-References",
"cmd:hyperlink.create",
"cmd:anchor.create",
"-",
"ui:app.insertXrefDialog",
"cmd:xref.defineFormat",
"<",
">Text Variables",
"cmd:variables.define",
"cmd:variables.insert",
"<",
"-",
"cmd:footnote.insert",
"cmd:endnote.insert",
"cmd:math.insert",
">Story Direction",
"cmd:type.storyDirection|Horizontal|{\"vertical\": false}",
@@ -447,13 +477,6 @@ pub const MENUS: &[(&str, &[&str])] = &[
"ui:app.rubyDialog",
"cmd:type.kenten",
"cmd:type.warichu",
">Track Changes",
"cmd:changes.track",
"cmd:changes.acceptAll",
"cmd:changes.rejectAll",
"<",
"ui:app.footnoteOptionsDialog",
"cmd:footnote.goToReference",
],
),
(
@@ -1264,6 +1287,17 @@ pub fn run_ui(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value,
}
Ok(Value::Null)
}
"app.characterPanel" | "app.paragraphPanel" | "app.glyphsPanel" | "app.characterStylesPanel" | "app.paragraphStylesPanel" => {
let panel = match id {
"app.characterPanel" => "character",
"app.paragraphPanel" => "paragraph",
"app.glyphsPanel" => "glyphs",
"app.characterStylesPanel" => "characterStyles",
_ => "paragraphStyles",
};
run_ui(app, "window.panel", &json!({"panel": panel})).unwrap_or(Ok(Value::Null))
}
"app.insertCurrentPageNumber" => app.run("text.insert", json!({"text": "\u{e000}", "raw": true})),
"window.panel" => {
let panel = p.get("panel").and_then(Value::as_str).unwrap_or("properties").to_string();
if crate::dock::DOCK_TABS.iter().any(|(id, _, _)| *id == panel) {
@@ -3511,6 +3545,91 @@ mod tests {
assert!(all.iter().any(|(l, id, p)| l == "Fill Frame Proportionally" && id == "object.fit" && p["mode"] == "fillProportionally"));
}
#[test]
fn the_type_menu_follows_indesigns_order() {
// InDesign's Type menu, as Chris Nielsen opens it in "Numbering Page Sections" (2:09).
let ty = menu_tree().into_iter().find(|(m, _)| *m == "Type").unwrap().1;
let labels: Vec<String> = ty
.iter()
.map(|i| match i {
Item::Sep => "-".to_string(),
Item::Sub(n, _) => format!("{n} >"),
Item::Cmd { label, .. } => label.clone(),
})
.collect();
let expected = [
"Size >",
"-",
"Character",
"Paragraph",
"Glyphs",
"-",
"Character Styles",
"Paragraph Styles",
"-",
"Create Outlines",
"Find/Replace Font…",
"Change Case >",
"-",
"Type on a Path >",
"-",
"Notes >",
"Track Changes >",
"-",
"Insert Footnote",
"Document Footnote Options…",
"Insert Endnote",
];
assert_eq!(&labels[..expected.len()], expected, "{labels:?}");
let pos = |l: &str| labels.iter().position(|x| x == l).unwrap_or_else(|| panic!("{l} in {labels:?}"));
assert!(pos("Hyperlinks & Cross-References >") < pos("Text Variables >"));
assert!(pos("Text Variables >") < pos("Bulleted & Numbered Lists >"));
assert!(pos("Bulleted & Numbered Lists >") < pos("Insert Special Character >"));
assert!(pos("Insert Special Character >") < pos("Insert White Space >"));
assert!(pos("Insert Break Character >") < pos("Fill with Placeholder Text"));
assert!(pos("Fill with Placeholder Text") < pos("Show Hidden Characters"));
// Alignment, styles and point sizes keep their shortcuts, out of the menu as in InDesign.
for gone in ["Align Left", "Bold", "Increase Point Size", "Paragraph Rules…"] {
assert!(!labels.iter().any(|l| l == gone), "{gone}");
}
let app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
let all = effective_shortcuts(&app);
for (id, sc) in [
("type.alignLeft", "Cmd+Shift+L"),
("app.characterPanel", "Cmd+T"),
("app.paragraphPanel", "Cmd+Alt+T"),
("app.insertCurrentPageNumber", "Cmd+Alt+Shift+N"),
] {
assert_eq!(all.iter().find(|(i, _)| i == id).map(|(_, s)| s.as_str()), Some(sc), "{id}");
}
// Markers › Current Page Number carries ⌥⇧⌘N.
let markers = ty
.iter()
.find_map(|i| match i {
Item::Sub(n, c) if n == "Insert Special Character" => {
c.iter().find_map(|i| if let Item::Sub(n, c) = i { (n == "Markers").then(|| c.clone()) } else { None })
}
_ => None,
})
.unwrap();
assert!(matches!(&markers[0], Item::Cmd { label, shortcut: Some("Cmd+Alt+Shift+N"), .. } if label == "Current Page Number"));
}
#[test]
fn type_panel_items_open_their_panels_and_the_marker_shortcut_inserts_a_page_number() {
let mut app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
app.run("file.new", json!({})).unwrap();
activate(&mut app, "app.characterPanel", &Value::Null);
assert_eq!(app.ui.open_panel.as_deref(), Some("character"));
activate(&mut app, "app.paragraphStylesPanel", &Value::Null);
assert_eq!(app.ui.open_panel.as_deref(), Some("paragraphStyles"));
let story = app.run("frame.create", json!({"rect": [36, 36, 200, 100], "content": "text", "text": "Page "})).unwrap()["story"].clone();
app.run("text.select", json!({"story": story, "anchor": 5, "focus": 5})).unwrap();
activate(&mut app, "app.insertCurrentPageNumber", &Value::Null);
let d = &app.session.doc().unwrap().doc;
assert_eq!(d.story(designcraft_doc::StoryId(story.as_u64().unwrap())).unwrap().text, "Page \u{e000}");
}
#[test]
fn custom_workspaces_save_apply_and_delete() {
let mut app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());