authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-02 21:37:14-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-03 01:50:33-07:00
loga4a0ccf055b14aa599b027e96a679054fea6b5a2
tree2b104b885f46f98cc0eb5991d1b8a67bdaa4e801
parent9361b752ce57b06d0f55af85dc5f515f794ea8f2
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

fix: menu items, commands and buttons drop their trailing ellipses

Clover: no ellipses in action titles, on desktop, web or iOS. The command table, context menus, dialog buttons and iOS menus name actions without them; "…" stays only on progress text and truncated text. Assisted-by: claude-opus-5.5

31 files changed, 100 insertions(+), 102 deletions(-)

apps/ios/Snowbound/Arranging.swift+2-2
...@@ -342,7 +342,7 @@ extension PagesViewController: UITableViewDragDelegate, UITableViewDropDelegate...@@ -342,7 +342,7 @@ extension PagesViewController: UITableViewDragDelegate, UITableViewDropDelegate
342 if indentable(rows, at) { add("Make Subpage") { [weak self] in self?.indent(id, by: 1) } }342 if indentable(rows, at) { add("Make Subpage") { [weak self] in self?.indent(id, by: 1) } }
343 if rows[at].level > 1 { add("Promote Subpage") { [weak self] in self?.indent(id, by: -1) } }343 if rows[at].level > 1 { add("Promote Subpage") { [weak self] in self?.indent(id, by: -1) } }
344 if !moveActions(item).isEmpty {344 if !moveActions(item).isEmpty {
345 add("Move to Section…") { [weak self] in345 add("Move to Section") { [weak self] in
346 guard let self, let row = items.firstIndex(where: { $0.id == id }),346 guard let self, let row = items.firstIndex(where: { $0.id == id }),
347 let cell = tableView.cellForRow(at: IndexPath(row: row, section: 0))347 let cell = tableView.cellForRow(at: IndexPath(row: row, section: 0))
348 else { return }348 else { return }
...@@ -571,7 +571,7 @@ extension NotebooksViewController: UICollectionViewDragDelegate, UICollectionVie...@@ -571,7 +571,7 @@ extension NotebooksViewController: UICollectionViewDragDelegate, UICollectionVie
571 if at < order.count - 1 { add("Move Down") { [weak self] in self?.shift(path, of: notebook, by: 1) } }571 if at < order.count - 1 { add("Move Down") { [weak self] in self?.shift(path, of: notebook, by: 1) } }
572 }572 }
573 if !notebook.destinations(of: path).isEmpty {573 if !notebook.destinations(of: path).isEmpty {
574 add("Move…") { [weak self, weak cell] in574 add("Move") { [weak self, weak cell] in
575 guard let self, let cell else { return }575 guard let self, let cell else { return }
576 sheet("Move", moveActions(path, of: notebook), from: cell, in: self)576 sheet("Move", moveActions(path, of: notebook), from: cell, in: self)
577 }577 }
apps/ios/Snowbound/Lists.swift+7-7
...@@ -94,10 +94,10 @@ final class NotebooksViewController: UICollectionViewController, UIDocumentPicke...@@ -94,10 +94,10 @@ final class NotebooksViewController: UICollectionViewController, UIDocumentPicke
94 })94 })
95 var actions: [UIMenuElement] = [95 var actions: [UIMenuElement] = [
96 UIMenu(title: "New Notebook", options: .displayInline, children: places),96 UIMenu(title: "New Notebook", options: .displayInline, children: places),
97 UIAction(title: "Open from Server…", image: UIImage(systemName: "server.rack")) { [weak self] _ in97 UIAction(title: "Open from Server", image: UIImage(systemName: "server.rack")) { [weak self] _ in
98 self?.connect()98 self?.connect()
99 },99 },
100 UIAction(title: "Open Folder…", image: UIImage(systemName: "folder")) { [weak self] _ in100 UIAction(title: "Open Folder", image: UIImage(systemName: "folder")) { [weak self] _ in
101 self?.openFolder()101 self?.openFolder()
102 },102 },
103 ]103 ]
...@@ -114,7 +114,7 @@ final class NotebooksViewController: UICollectionViewController, UIDocumentPicke...@@ -114,7 +114,7 @@ final class NotebooksViewController: UICollectionViewController, UIDocumentPicke
114 let more = UIMenu(children: [114 let more = UIMenu(children: [
115 UIDeferredMenuElement.uncached { [weak self] provide in115 UIDeferredMenuElement.uncached { [weak self] provide in
116 var actions: [UIMenuElement] = [116 var actions: [UIMenuElement] = [
117 UIAction(title: "Sync Status…", image: UIImage(systemName: "arrow.triangle.2.circlepath")) {117 UIAction(title: "Sync Status", image: UIImage(systemName: "arrow.triangle.2.circlepath")) {
118 [weak self] _ in self.map(SyncViewController.present)118 [weak self] _ in self.map(SyncViewController.present)
119 }119 }
120 ]120 ]
...@@ -125,7 +125,7 @@ final class NotebooksViewController: UICollectionViewController, UIDocumentPicke...@@ -125,7 +125,7 @@ final class NotebooksViewController: UICollectionViewController, UIDocumentPicke
125 })125 })
126 }126 }
127 actions.append(127 actions.append(
128 UIAction(title: "Personalize…", image: UIImage(systemName: "person.crop.circle")) { [weak self] _ in128 UIAction(title: "Personalize", image: UIImage(systemName: "person.crop.circle")) { [weak self] _ in
129 guard let self else { return }129 guard let self else { return }
130 Author.ask(from: self) {}130 Author.ask(from: self) {}
131 })131 })
...@@ -385,7 +385,7 @@ final class NotebooksViewController: UICollectionViewController, UIDocumentPicke...@@ -385,7 +385,7 @@ final class NotebooksViewController: UICollectionViewController, UIDocumentPicke
385 return [UIMenu(options: .displayInline, children: [details, now])]385 return [UIMenu(options: .displayInline, children: [details, now])]
386 } ?? []386 } ?? []
387 let actions = [387 let actions = [
388 UIAction(title: "New Section…", image: UIImage(systemName: "plus.rectangle.portrait")) {388 UIAction(title: "New Section", image: UIImage(systemName: "plus.rectangle.portrait")) {
389 [weak self] _ in self?.newSection(in: notebook)389 [weak self] _ in self?.newSection(in: notebook)
390 },390 },
391 UIAction(title: "Tags Summary", image: UIImage(systemName: "tag")) { [weak self] _ in391 UIAction(title: "Tags Summary", image: UIImage(systemName: "tag")) { [weak self] _ in
...@@ -403,13 +403,13 @@ final class NotebooksViewController: UICollectionViewController, UIDocumentPicke...@@ -403,13 +403,13 @@ final class NotebooksViewController: UICollectionViewController, UIDocumentPicke
403 return UIMenu(children: status + actions + [close])403 return UIMenu(children: status + actions + [close])
404 }404 }
405 let file = [405 let file = [
406 UIAction(title: "Rename…", image: UIImage(systemName: "pencil")) { [weak self] _ in406 UIAction(title: "Rename", image: UIImage(systemName: "pencil")) { [weak self] _ in
407 self?.rename(folder, of: notebook)407 self?.rename(folder, of: notebook)
408 },408 },
409 UIAction(title: "Duplicate", image: UIImage(systemName: "plus.square.on.square")) { [weak self] _ in409 UIAction(title: "Duplicate", image: UIImage(systemName: "plus.square.on.square")) { [weak self] _ in
410 self?.duplicate(folder, of: notebook)410 self?.duplicate(folder, of: notebook)
411 },411 },
412 UIAction(title: "Share…", image: UIImage(systemName: "square.and.arrow.up")) { [weak self] _ in412 UIAction(title: "Share", image: UIImage(systemName: "square.and.arrow.up")) { [weak self] _ in
413 self?.share(folder, from: notebook)413 self?.share(folder, from: notebook)
414 },414 },
415 UIAction(title: "Show in Files", image: UIImage(systemName: "folder")) { _ in415 UIAction(title: "Show in Files", image: UIImage(systemName: "folder")) { _ in
apps/ios/Snowbound/PageViewController.swift+3-3
...@@ -202,7 +202,7 @@ final class PageViewController: UIViewController, PHPickerViewControllerDelegate...@@ -202,7 +202,7 @@ final class PageViewController: UIViewController, PHPickerViewControllerDelegate
202 ])202 ])
203 return (Prototype.reading ? [readingAction()] : []) + [203 return (Prototype.reading ? [readingAction()] : []) + [
204 insert,204 insert,
205 UIAction(title: "Page Background…", image: UIImage(systemName: "paintpalette"), attributes: editable) {205 UIAction(title: "Page Background", image: UIImage(systemName: "paintpalette"), attributes: editable) {
206 [weak self] _ in self?.showPaper()206 [weak self] _ in self?.showPaper()
207 },207 },
208 themeMenu(),208 themeMenu(),
...@@ -211,10 +211,10 @@ final class PageViewController: UIViewController, PHPickerViewControllerDelegate...@@ -211,10 +211,10 @@ final class PageViewController: UIViewController, PHPickerViewControllerDelegate
211 scene?.showTags(of: section.notebook, from: self)211 scene?.showTags(of: section.notebook, from: self)
212 },212 },
213 UIMenu(options: .displayInline, children: [213 UIMenu(options: .displayInline, children: [
214 UIAction(title: "Print…", image: UIImage(systemName: "printer")) { [weak self] _ in214 UIAction(title: "Print", image: UIImage(systemName: "printer")) { [weak self] _ in
215 self?.printPage(export: false)215 self?.printPage(export: false)
216 },216 },
217 UIAction(title: "Export as PDF…", image: UIImage(systemName: "doc.richtext")) { [weak self] _ in217 UIAction(title: "Export as PDF", image: UIImage(systemName: "doc.richtext")) { [weak self] _ in
218 self?.printPage(export: true)218 self?.printPage(export: true)
219 },219 },
220 ]),220 ]),
apps/ios/Snowbound/Prototypes.swift+2-2
...@@ -52,8 +52,8 @@ extension Prototype {...@@ -52,8 +52,8 @@ extension Prototype {
52 open.title = "Open Existing Notebook"52 open.title = "Open Existing Notebook"
53 welcome.secondaryButton = open53 welcome.secondaryButton = open
54 var elsewhere = [54 var elsewhere = [
55 UIAction(title: "Open Folder…", image: UIImage(systemName: "folder")) { _ in openFolder() },55 UIAction(title: "Open Folder", image: UIImage(systemName: "folder")) { _ in openFolder() },
56 UIAction(title: "Connect to Server…", image: UIImage(systemName: "server.rack")) { _ in connect() },56 UIAction(title: "Connect to Server", image: UIImage(systemName: "server.rack")) { _ in connect() },
57 ]57 ]
58 if !ICloud.signedIn {58 if !ICloud.signedIn {
59 elsewhere.append(59 elsewhere.append(
arc/platforms.md+2-2
...@@ -52,7 +52,7 @@ keyboard, the toolbar and the macOS menu bar all run commands from it....@@ -52,7 +52,7 @@ keyboard, the toolbar and the macOS menu bar all run commands from it.
52 frame with the window's new size instead of stretching the last one.52 frame with the window's new size instead of stretching the last one.
53- A notebook on a mounted SMB share is opened through the embedded SMB client,53- A notebook on a mounted SMB share is opened through the embedded SMB client,
54 signed in with the password the keychain keeps for that mount (see54 signed in with the password the keychain keeps for that mount (see
55 [sync](sync.md) for why the mount itself isn't enough). Open Notebook from Server… reaches one55 [sync](sync.md) for why the mount itself isn't enough). Open Notebook from Server reaches one
56 by address instead, and keeps a password it asks to remember as the Finder does, an56 by address instead, and keeps a password it asks to remember as the Finder does, an
57 SMB internet password.57 SMB internet password.
58- The menu bar (`menubar.rs`) is laid out as OneNote for Mac's. Its items are58- The menu bar (`menubar.rs`) is laid out as OneNote for Mac's. Its items are
...@@ -147,7 +147,7 @@ keyboard, the toolbar and the macOS menu bar all run commands from it....@@ -147,7 +147,7 @@ keyboard, the toolbar and the macOS menu bar all run commands from it.
147 other writers) and takes OneNote's coordination bytes with byte-range147 other writers) and takes OneNote's coordination bytes with byte-range
148 locks, so both apps can have a section open on one machine.148 locks, so both apps can have a section open on one machine.
149 ReadDirectoryChangesW reports changes below a notebook, including other149 ReadDirectoryChangesW reports changes below a notebook, including other
150 clients' on a share. Open Notebook from Server… still uses the embedded150 clients' on a share. Open Notebook from Server still uses the embedded
151 client, its passwords in the Credential Manager.151 client, its passwords in the Credential Manager.
152- The common file dialogs, task dialogs, the date and time picker, and the152- The common file dialogs, task dialogs, the date and time picker, and the
153 Spell Checking API (from Windows 8) are the system's; audio plays and153 Spell Checking API (from Windows 8) are the system's; audio plays and
arc/sync.md+1-1
...@@ -192,7 +192,7 @@ client with the account the system keeps for that mount. It falls back to the...@@ -192,7 +192,7 @@ client with the account the system keeps for that mount. It falls back to the
192mount only when it can't sign in that way.192mount only when it can't sign in that way.
193193
194A notebook can also be opened from its server's address, with no mount at all (File ▸194A notebook can also be opened from its server's address, with no mount at all (File ▸
195Open Notebook from Server…), which is how Mac OS X 10.6 reaches a server that no longer195Open Notebook from Server), which is how Mac OS X 10.6 reaches a server that no longer
196speaks SMB1, the only version its Finder has. The notebook is kept by that address196speaks SMB1, the only version its Finder has. The notebook is kept by that address
197(`smb://[domain;]user@server/share/folder`), never with a password; the password lives in197(`smb://[domain;]user@server/share/folder`), never with a password; the password lives in
198the keychain (the Secret Service on Linux) when the user asks to remember it, and198the keychain (the Secret Service on Linux) when the user asks to remember it, and
corpus/page-versions/README.md+1-1
...@@ -47,7 +47,7 @@ Versions is chosen. An open version is read-only under a yellow bar (RGB 255, 23...@@ -47,7 +47,7 @@ Versions is chosen. An open version is read-only under a yellow bar (RGB 255, 23
47"This is an earlier version of the page. It will be deleted over time. Click here to restore47"This is an earlier version of the page. It will be deleted over time. Click here to restore
48or delete this version." What the version changed since the one before it is banded green48or delete this version." What the version changed since the one before it is banded green
49(RGB 214, 255, 214). The bar's menu (`shots/02-version-menu.png`): Restore Version, Delete49(RGB 214, 255, 214). The bar's menu (`shots/02-version-menu.png`): Restore Version, Delete
50Version, Copy Page To…; Delete All Versions in Section, Section Group, Notebook; Disable50Version, Copy Page To; Delete All Versions in Section, Section Group, Notebook; Disable
51History for This Notebook; Hide Page Versions. Share also has Hide Authors, Recent Edits and51History for This Notebook; Hide Page Versions. Share also has Hide Authors, Recent Edits and
52Find by Author (`shots/14`–`16`).52Find by Author (`shots/14`–`16`).
5353
corpus/section-color/README.md+1-1
...@@ -6,7 +6,7 @@ ends with Section Color, sixteen named colours and None (`section-color-menu.png...@@ -6,7 +6,7 @@ ends with Section Color, sixteen named colours and None (`section-color-menu.png
6appends a revision to the section's own file setting its metadata's `0x14001cbe`6appends a revision to the section's own file setting its metadata's `0x14001cbe`
7(COLORREF; `teal.one`), and None sets `0xFFFFFFFF` (`none.one`); the table of contents is7(COLORREF; `teal.one`), and None sets `0xFFFFFFFF` (`none.one`); the table of contents is
8left byte for byte. Notebook Properties (`notebook-properties.png`, from the notebook's8left byte for byte. Notebook Properties (`notebook-properties.png`, from the notebook's
9Rename… or Properties…) offers the same sixteen without None (`notebook-colors.png`) and9Rename or Properties) offers the same sixteen without None (`notebook-colors.png`) and
10appends a revision to the root `Open Notebook.onetoc2` setting its root's `0x14001cbe`10appends a revision to the root `Open Notebook.onetoc2` setting its root's `0x14001cbe`
11(`before.onetoc2`, `after.onetoc2`). Its Display name is written to neither file: OneNote11(`before.onetoc2`, `after.onetoc2`). Its Display name is written to neither file: OneNote
12keeps it in its local `OneNoteOfflineCache.onecache`, so the hierarchy's `nickname`12keeps it in its local `OneNoteOfflineCache.onecache`, so the hierarchy's `nickname`
crates/snowbound/linux/README.md+1-1
...@@ -11,7 +11,7 @@ cd snowbound-linux-x86_64...@@ -11,7 +11,7 @@ cd snowbound-linux-x86_64
11./bin/snowbound11./bin/snowbound
12```12```
1313
14Choose **Open Notebook…** in the notebook list and pick the notebook folder,14Choose **Open Notebook** in the notebook list and pick the notebook folder,
15the one holding the `.one` section files and `Open Notebook.onetoc2`.15the one holding the `.one` section files and `Open Notebook.onetoc2`.
16Snowbound remembers the notebooks you open. `--notebook FOLDER` opens one16Snowbound remembers the notebooks you open. `--notebook FOLDER` opens one
17straight away.17straight away.
crates/snowbound/src/background.rs+1-1
...@@ -161,7 +161,7 @@ pub fn menu(...@@ -161,7 +161,7 @@ pub fn menu(
161 }161 }
162 match cells[index] {162 match cells[index] {
163 Cell::Color(None) | Cell::Art(None) | Cell::Rules(None) => label(ui, "None"),163 Cell::Color(None) | Cell::Art(None) | Cell::Rules(None) => label(ui, "None"),
164 Cell::ShowAll => label(ui, "Show All…"),164 Cell::ShowAll => label(ui, "Show All"),
165 Cell::Color(Some(color)) => {165 Cell::Color(Some(color)) => {
166 ui.leaf(166 ui.leaf(
167 "swatch",167 "swatch",
crates/snowbound/src/commands.rs+20-20
...@@ -260,12 +260,12 @@ const NONE: &[Chord] = &[];...@@ -260,12 +260,12 @@ const NONE: &[Chord] = &[];
260260
261/// OneNote 2010's chords off macOS; there, AppKit's and OneNote for Mac's.261/// OneNote 2010's chords off macOS; there, AppKit's and OneNote for Mac's.
262pub const COMMANDS: &[Command] = &[262pub const COMMANDS: &[Command] = &[
263 row!(Id::Settings, "Settings…", &[cmd(',')], &[cmd(',')]),263 row!(Id::Settings, "Settings", &[cmd(',')], &[cmd(',')]),
264 row!(Id::NewNotebook, "New Notebook…", NONE, NONE),264 row!(Id::NewNotebook, "New Notebook", NONE, NONE),
265 row!(Id::OpenNotebook, "Open Notebook…", &[cmd('o')], &[cmd('o')]),265 row!(Id::OpenNotebook, "Open Notebook", &[cmd('o')], &[cmd('o')]),
266 row!(Id::OpenFromServer, "Open Notebook from Server…", NONE, NONE),266 row!(Id::OpenFromServer, "Open Notebook from Server", NONE, NONE),
267 row!(Id::OpenShared, "Open Shared Notebook…", NONE, NONE),267 row!(Id::OpenShared, "Open Shared Notebook", NONE, NONE),
268 row!(Id::LiveShare, "Live Share…", NONE, NONE),268 row!(Id::LiveShare, "Live Share", NONE, NONE),
269 row!(Id::CloseNotebook, "Close This Notebook", NONE, NONE),269 row!(Id::CloseNotebook, "Close This Notebook", NONE, NONE),
270 row!(Id::NewSection, "New Section", &[cmd('t')], &[cmd('t')]),270 row!(Id::NewSection, "New Section", &[cmd('t')], &[cmd('t')]),
271 row!(Id::NewSectionGroup, "New Section Group", NONE, NONE),271 row!(Id::NewSectionGroup, "New Section Group", NONE, NONE),
...@@ -280,7 +280,7 @@ pub const COMMANDS: &[Command] = &[...@@ -280,7 +280,7 @@ pub const COMMANDS: &[Command] = &[
280 row!(Id::CopyPageLink, "Copy Link to Page", NONE, NONE),280 row!(Id::CopyPageLink, "Copy Link to Page", NONE, NONE),
281 row!(281 row!(
282 Id::PasswordProtect,282 Id::PasswordProtect,
283 "Password Protect This Section…",283 "Password Protect This Section",
284 NONE,284 NONE,
285 NONE285 NONE
286 ),286 ),
...@@ -302,7 +302,7 @@ pub const COMMANDS: &[Command] = &[...@@ -302,7 +302,7 @@ pub const COMMANDS: &[Command] = &[
302 ),302 ),
303 row!(303 row!(
304 Id::SaveAs,304 Id::SaveAs,
305 "Save As…",305 "Save As",
306 &[cmd('s').shift()],306 &[cmd('s').shift()],
307 &[cmd('s').shift()]307 &[cmd('s').shift()]
308 ),308 ),
...@@ -318,11 +318,11 @@ pub const COMMANDS: &[Command] = &[...@@ -318,11 +318,11 @@ pub const COMMANDS: &[Command] = &[
318 NONE318 NONE
319 ),319 ),
320 row!(Id::NextUnread, "Next Unread", NONE, NONE),320 row!(Id::NextUnread, "Next Unread", NONE, NONE),
321 row!(Id::ExportPdf, "Export as PDF…", NONE, NONE),321 row!(Id::ExportPdf, "Export as PDF", NONE, NONE),
322 // Go to takes OneNote's Ctrl+P, and Pause its Ctrl+Alt+P.322 // Go to takes OneNote's Ctrl+P, and Pause its Ctrl+Alt+P.
323 row!(323 row!(
324 Id::Print,324 Id::Print,
325 "Print…",325 "Print",
326 &[cmd('p').option().shift()],326 &[cmd('p').option().shift()],
327 &[cmd('p').option().shift()]327 &[cmd('p').option().shift()]
328 ),328 ),
...@@ -351,10 +351,10 @@ pub const COMMANDS: &[Command] = &[...@@ -351,10 +351,10 @@ pub const COMMANDS: &[Command] = &[
351 &[cmd('f').option()],351 &[cmd('f').option()],
352 &[key('o').option()],352 &[key('o').option()],
353 ),353 ),
354 row!(Id::GoTo, "Go to Page or Section…", &[cmd('p')], &[cmd('p')]),354 row!(Id::GoTo, "Go to Page or Section", &[cmd('p')], &[cmd('p')]),
355 row!(355 row!(
356 Id::CommandPalette,356 Id::CommandPalette,
357 "Command Palette…",357 "Command Palette",
358 &[cmd('p').shift()],358 &[cmd('p').shift()],
359 &[cmd('p').shift()]359 &[cmd('p').shift()]
360 ),360 ),
...@@ -393,7 +393,7 @@ pub const COMMANDS: &[Command] = &[...@@ -393,7 +393,7 @@ pub const COMMANDS: &[Command] = &[
393 row!(Id::MarkdownShortcuts, "Markdown Shortcuts", NONE, NONE),393 row!(Id::MarkdownShortcuts, "Markdown Shortcuts", NONE, NONE),
394 row!(394 row!(
395 Id::Spelling,395 Id::Spelling,
396 "Spelling…",396 "Spelling",
397 &[named(NamedKey::F7)],397 &[named(NamedKey::F7)],
398 &[named(NamedKey::F7)]398 &[named(NamedKey::F7)]
399 ),399 ),
...@@ -406,10 +406,10 @@ pub const COMMANDS: &[Command] = &[...@@ -406,10 +406,10 @@ pub const COMMANDS: &[Command] = &[
406 ),406 ),
407 row!(Id::PageColor, "Page Color", NONE, NONE),407 row!(Id::PageColor, "Page Color", NONE, NONE),
408 row!(Id::Table, "Table", NONE, NONE),408 row!(Id::Table, "Table", NONE, NONE),
409 row!(Id::Picture, "Picture…", NONE, NONE),409 row!(Id::Picture, "Picture", NONE, NONE),
410 row!(Id::ScreenClipping, "Screen Clipping", NONE, NONE),410 row!(Id::ScreenClipping, "Screen Clipping", NONE, NONE),
411 row!(Id::Attachment, "Attach File…", NONE, NONE),411 row!(Id::Attachment, "Attach File", NONE, NONE),
412 row!(Id::Link, "Link…", &[cmd('k')], &[cmd('k')]),412 row!(Id::Link, "Link", &[cmd('k')], &[cmd('k')]),
413 row!(Id::InsertSpace, "Insert Space", NONE, NONE),413 row!(Id::InsertSpace, "Insert Space", NONE, NONE),
414 row!(414 row!(
415 Id::Equation,415 Id::Equation,
...@@ -465,7 +465,7 @@ pub const COMMANDS: &[Command] = &[...@@ -465,7 +465,7 @@ pub const COMMANDS: &[Command] = &[
465 NONE,465 NONE,
466 NONE466 NONE
467 ),467 ),
468 row!(Id::Transport(Transport::SeekTo), "Seek To…", NONE, NONE),468 row!(Id::Transport(Transport::SeekTo), "Seek To", NONE, NONE),
469 row!(469 row!(
470 Id::Transport(Transport::SeePlayback),470 Id::Transport(Transport::SeePlayback),
471 "See Playback",471 "See Playback",
...@@ -596,8 +596,8 @@ pub const COMMANDS: &[Command] = &[...@@ -596,8 +596,8 @@ pub const COMMANDS: &[Command] = &[
596 row!(Id::Style(8), "Quote", NONE, NONE),596 row!(Id::Style(8), "Quote", NONE, NONE),
597 row!(Id::Style(9), "Code", NONE, NONE),597 row!(Id::Style(9), "Code", NONE, NONE),
598 row!(Id::Style(10), "Normal", NONE, NONE),598 row!(Id::Style(10), "Normal", NONE, NONE),
599 row!(Id::Themes, "Customize Theme…", NONE, NONE),599 row!(Id::Themes, "Customize Theme", NONE, NONE),
600 row!(Id::CustomizeTags, "Customize Tags…", NONE, NONE),600 row!(Id::CustomizeTags, "Customize Tags", NONE, NONE),
601 row!(601 row!(
602 Id::RemoveTags,602 Id::RemoveTags,
603 "Remove Tag",603 "Remove Tag",
...@@ -608,7 +608,7 @@ pub const COMMANDS: &[Command] = &[...@@ -608,7 +608,7 @@ pub const COMMANDS: &[Command] = &[
608 row!(Id::ToDoList, "Make To-Do List", NONE, NONE),608 row!(Id::ToDoList, "Make To-Do List", NONE, NONE),
609 row!(Id::BulletedList, "Make Bulleted List", NONE, NONE),609 row!(Id::BulletedList, "Make Bulleted List", NONE, NONE),
610 row!(Id::Help, "Snowbound Help", NONE, NONE),610 row!(Id::Help, "Snowbound Help", NONE, NONE),
611 row!(Id::CheckForUpdates, "Check for Updates…", NONE, NONE),611 row!(Id::CheckForUpdates, "Check for Updates", NONE, NONE),
612];612];
613613
614/// The most tags a list holds: MS-ONE's action types 0 to 99 number them.614/// The most tags a list holds: MS-ONE's action types 0 to 99 number them.
crates/snowbound/src/history.rs+1-1
...@@ -105,7 +105,7 @@ pub fn menu(...@@ -105,7 +105,7 @@ pub fn menu(
105 drawn("Delete Version", art::DELETE),105 drawn("Delete Version", art::DELETE),
106 ui::popup::Item {106 ui::popup::Item {
107 disabled: sections.is_empty(),107 disabled: sections.is_empty(),
108 ..drawn("Copy Page To…", art::COPY)108 ..drawn("Copy Page To", art::COPY)
109 },109 },
110 ui::popup::Item {110 ui::popup::Item {
111 separated: true,111 separated: true,
crates/snowbound/src/keys.rs+1-1
...@@ -78,7 +78,7 @@ fn label(chord: Chord) -> String {...@@ -78,7 +78,7 @@ fn label(chord: Chord) -> String {
78fn title(id: Command) -> String {78fn title(id: Command) -> String {
79 match id {79 match id {
80 Command::Tag(place) => format!("Tag {}", place + 1),80 Command::Tag(place) => format!("Tag {}", place + 1),
81 id => crate::title(id).to_owned(),81 id => commands::command(id).title.to_owned(),
82 }82 }
83}83}
8484
crates/snowbound/src/link.rs+8-8
...@@ -303,7 +303,7 @@ impl State {...@@ -303,7 +303,7 @@ impl State {
303 item("Ignore"),303 item("Ignore"),
304 Item {304 Item {
305 separated: true,305 separated: true,
306 ..drawn("Spelling…", art::SPELLING)306 ..drawn("Spelling", art::SPELLING)
307 },307 },
308 ],308 ],
309 Some(correction) => {309 Some(correction) => {
...@@ -326,7 +326,7 @@ impl State {...@@ -326,7 +326,7 @@ impl State {
326 drawn("Add to Dictionary", art::ADD_TO_DICTIONARY),326 drawn("Add to Dictionary", art::ADD_TO_DICTIONARY),
327 Item {327 Item {
328 separated: true,328 separated: true,
329 ..drawn("Spelling…", art::SPELLING)329 ..drawn("Spelling", art::SPELLING)
330 },330 },
331 ]);331 ]);
332 items332 items
...@@ -344,7 +344,7 @@ impl State {...@@ -344,7 +344,7 @@ impl State {
344 .collect::<Vec<_>>();344 .collect::<Vec<_>>();
345 items.extend(if context.attachment.is_some() {345 items.extend(if context.attachment.is_some() {
346 // OneNote 2010's commands for the file itself; its clipboard holds text alone.346 // OneNote 2010's commands for the file itself; its clipboard holds text alone.
347 vec![drawn("Open", art::OPEN), drawn("Save As…", art::SAVE)]347 vec![drawn("Open", art::OPEN), drawn("Save As", art::SAVE)]
348 } else {348 } else {
349 let mut items = vec![349 let mut items = vec![
350 Item {350 Item {
...@@ -362,7 +362,7 @@ impl State {...@@ -362,7 +362,7 @@ impl State {
362 Some(_) => items.extend([362 Some(_) => items.extend([
363 Item {363 Item {
364 separated: true,364 separated: true,
365 ..drawn("Edit Link…", art::LINK)365 ..drawn("Edit Link", art::LINK)
366 },366 },
367 drawn("Copy Link to Paragraph", art::COPY_LINK),367 drawn("Copy Link to Paragraph", art::COPY_LINK),
368 Item {368 Item {
...@@ -375,7 +375,7 @@ impl State {...@@ -375,7 +375,7 @@ impl State {
375 None => items.extend([375 None => items.extend([
376 Item {376 Item {
377 separated: true,377 separated: true,
378 ..drawn("Link…", art::LINK)378 ..drawn("Link", art::LINK)
379 },379 },
380 drawn("Copy Link to Paragraph", art::COPY_LINK),380 drawn("Copy Link to Paragraph", art::COPY_LINK),
381 ]),381 ]),
...@@ -414,7 +414,7 @@ impl State {...@@ -414,7 +414,7 @@ impl State {
414 self.view.correct(&correction, &chosen_text)?414 self.view.correct(&correction, &chosen_text)?
415 }415 }
416 "Delete Repeated Word" => self.view.correct(&correction, "")?,416 "Delete Repeated Word" => self.view.correct(&correction, "")?,
417 "Spelling…" => {417 "Spelling" => {
418 self.open_spelling_pane();418 self.open_spelling_pane();
419 return Ok(());419 return Ok(());
420 }420 }
...@@ -441,7 +441,7 @@ impl State {...@@ -441,7 +441,7 @@ impl State {
441 }441 }
442 let response = match chosen_text.as_str() {442 let response = match chosen_text.as_str() {
443 "Open" => return self.open_attachment(&context.attachment.unwrap()),443 "Open" => return self.open_attachment(&context.attachment.unwrap()),
444 "Save As…" => return self.save_attachment(&context.attachment.unwrap()),444 "Save As" => return self.save_attachment(&context.attachment.unwrap()),
445 "Cut" => self.view.copy(true)?,445 "Cut" => self.view.copy(true)?,
446 "Copy" => self.view.copy(false)?,446 "Copy" => self.view.copy(false)?,
447 "Paste" => {447 "Paste" => {
...@@ -449,7 +449,7 @@ impl State {...@@ -449,7 +449,7 @@ impl State {
449 .push(Command::Page(canvas::interaction::Request::Paste));449 .push(Command::Page(canvas::interaction::Request::Paste));
450 return Ok(());450 return Ok(());
451 }451 }
452 "Edit Link…" | "Link…" => {452 "Edit Link" | "Link" => {
453 self.open_link_dialog();453 self.open_link_dialog();
454 return Ok(());454 return Ok(());
455 }455 }
crates/snowbound/src/main.rs+15-13
...@@ -2413,7 +2413,7 @@ impl State {...@@ -2413,7 +2413,7 @@ impl State {
2413 } else if ui::shell::split_button(2413 } else if ui::shell::split_button(
2414 ui,2414 ui,
2415 part,2415 part,
2416 title(id),2416 commands::command(id).title,
2417 icon,2417 icon,
2418 bar,2418 bar,
2419 None,2419 None,
...@@ -2496,7 +2496,7 @@ impl State {...@@ -2496,7 +2496,7 @@ impl State {
2496 } else if ui::shell::split_button(2496 } else if ui::shell::split_button(
2497 ui,2497 ui,
2498 part,2498 part,
2499 title(id),2499 commands::command(id).title,
2500 icon,2500 icon,
2501 None,2501 None,
2502 status.checked,2502 status.checked,
...@@ -2798,7 +2798,7 @@ impl State {...@@ -2798,7 +2798,7 @@ impl State {
2798 if ui::shell::split_button(2798 if ui::shell::split_button(
2799 ui,2799 ui,
2800 "pen",2800 "pen",
2801 title(Cmd::Pen),2801 commands::command(Cmd::Pen).title,
2802 art::PEN,2802 art::PEN,
2803 bar,2803 bar,
2804 status.checked,2804 status.checked,
...@@ -4724,7 +4724,7 @@ fn conflict_bar(ui: &mut Ui, bar: Bar, sections: &[&str], steps: [bool; 2]) -> O...@@ -4724,7 +4724,7 @@ fn conflict_bar(ui: &mut Ui, bar: Bar, sections: &[&str], steps: [bool; 2]) -> O
4724 ..Default::default()4724 ..Default::default()
4725 },4725 },
4726 ui::popup::Item {4726 ui::popup::Item {
4727 text: "Copy Page To…",4727 text: "Copy Page To",
4728 icon: Some(art::COPY),4728 icon: Some(art::COPY),
4729 disabled: sections.is_empty(),4729 disabled: sections.is_empty(),
4730 ..Default::default()4730 ..Default::default()
...@@ -5349,7 +5349,9 @@ fn dropdown(...@@ -5349,7 +5349,9 @@ fn dropdown(
5349 Head::Split(id) if status_of(id).enabled => {5349 Head::Split(id) if status_of(id).enabled => {
5350 let icon = artwork(id).unwrap_or_default();5350 let icon = artwork(id).unwrap_or_default();
5351 let on = status_of(id).checked;5351 let on = status_of(id).checked;
5352 if ui::shell::split_button(ui, part, title(id), icon, None, on, menu).clicked {5352 if ui::shell::split_button(ui, part, commands::command(id).title, icon, None, on, menu)
5353 .clicked
5354 {
5353 choice = Some(Choice::Command(id));5355 choice = Some(Choice::Command(id));
5354 }5356 }
5355 tip(ui, id);5357 tip(ui, id);
...@@ -5368,7 +5370,7 @@ fn dropdown(...@@ -5368,7 +5370,7 @@ fn dropdown(
5368 Head::Split(id) => {5370 Head::Split(id) => {
5369 let icon = artwork(id).unwrap_or_default();5371 let icon = artwork(id).unwrap_or_default();
5370 let anchor = ui::shell::menu_button(ui, part, icon, None, menu);5372 let anchor = ui::shell::menu_button(ui, part, icon, None, menu);
5371 name(ui, button, title(id));5373 name(ui, button, commands::command(id).title);
5372 anchor5374 anchor
5373 }5375 }
5374 Head::Tag(_, tag) => {5376 Head::Tag(_, tag) => {
...@@ -5422,7 +5424,7 @@ fn dropdown(...@@ -5422,7 +5424,7 @@ fn dropdown(
5422 let options: Vec<_> = entries5424 let options: Vec<_> = entries
5423 .iter()5425 .iter()
5424 .map(|entry| match *entry {5426 .map(|entry| match *entry {
5425 Entry::Open(_, id) if runs(id) => format!("{} Options", title(id)),5427 Entry::Open(_, id) if runs(id) => format!("{} Options", commands::command(id).title),
5426 _ => String::new(),5428 _ => String::new(),
5427 })5429 })
5428 .collect();5430 .collect();
...@@ -5723,12 +5725,12 @@ fn tag_tip(ui: &mut Ui, place: usize, tag: &canvas::editor::NoteTag) {...@@ -5723,12 +5725,12 @@ fn tag_tip(ui: &mut Ui, place: usize, tag: &canvas::editor::NoteTag) {
57235725
5724/// A tooltip naming command `id` and its chord on the box built last.5726/// A tooltip naming command `id` and its chord on the box built last.
5725fn tip(ui: &mut Ui, id: commands::Id) {5727fn tip(ui: &mut Ui, id: commands::Id) {
5726 ui::popup::tooltip(ui, title(id), &commands::shortcut(id), None);5728 ui::popup::tooltip(
5727}5729 ui,
57285730 commands::command(id).title,
5729/// Command `id`'s title without the menu's ellipsis, naming the command, not a dialog it opens.5731 &commands::shortcut(id),
5730fn title(id: commands::Id) -> &'static str {5732 None,
5731 commands::command(id).title.trim_end_matches('…')5733 );
5732}5734}
57335735
5734/// Whether each toolbar group stands on a face of its own.5736/// Whether each toolbar group stands on a face of its own.
crates/snowbound/src/menubar.rs+1-1
...@@ -102,7 +102,7 @@ const MENUS: &[Item] = &[...@@ -102,7 +102,7 @@ const MENUS: &[Item] = &[
102 C(Id::Search),102 C(Id::Search),
103 C(Id::SearchResults),103 C(Id::SearchResults),
104 S,104 S,
105 Item::System("Start Dictation…", "startDictation:", None),105 Item::System("Start Dictation", "startDictation:", None),
106 Item::System(106 Item::System(
107 "Emoji & Symbols",107 "Emoji & Symbols",
108 "orderFrontCharacterPalette:",108 "orderFrontCharacterPalette:",
crates/snowbound/src/menus.rs+9-9
...@@ -342,7 +342,7 @@ impl State {...@@ -342,7 +342,7 @@ impl State {
342 item(Action::Copy, "Copy To", nowhere(), false),342 item(Action::Copy, "Copy To", nowhere(), false),
343 ],343 ],
344 Target::Section { path, .. } if crate::recycle::binned(path) => vec![344 Target::Section { path, .. } if crate::recycle::binned(path) => vec![
345 item(Action::SaveAs, "Save As…", false, false),345 item(Action::SaveAs, "Save As", false, false),
346 item(Action::Move, "Restore To", nowhere(), false),346 item(Action::Move, "Restore To", nowhere(), false),
347 item(Action::EmptyRecycleBin, "Empty Recycle Bin", false, true),347 item(Action::EmptyRecycleBin, "Empty Recycle Bin", false, true),
348 item(Action::Colors, "Section Color", false, true),348 item(Action::Colors, "Section Color", false, true),
...@@ -360,7 +360,7 @@ impl State {...@@ -360,7 +360,7 @@ impl State {
360 item(Action::NewPage, "New Page", false, true),360 item(Action::NewPage, "New Page", false, true),
361 // OneNote offers this beside New Page, which is a plain + here.361 // OneNote offers this beside New Page, which is a plain + here.
362 item(Action::NewSubpage, "New Subpage", false, false),362 item(Action::NewSubpage, "New Subpage", false, false),
363 item(Action::Theme, "Theme…", false, true),363 item(Action::Theme, "Theme", false, true),
364 ];364 ];
365 // Levels and versions are known once the section is open.365 // Levels and versions are known once the section is open.
366 let Some(session) = self.session.as_ref().filter(|_| self.open(library, path))366 let Some(session) = self.session.as_ref().filter(|_| self.open(library, path))
...@@ -400,7 +400,7 @@ impl State {...@@ -400,7 +400,7 @@ impl State {
400 let section = matches!(target, Target::Section { .. });400 let section = matches!(target, Target::Section { .. });
401 let mut actions = vec![item(Action::Rename, "Rename", false, false)];401 let mut actions = vec![item(Action::Rename, "Rename", false, false)];
402 if section {402 if section {
403 actions.push(item(Action::SaveAs, "Save As…", false, false));403 actions.push(item(Action::SaveAs, "Save As", false, false));
404 }404 }
405 actions.extend([405 actions.extend([
406 item(Action::Delete, "Delete", false, false),406 item(Action::Delete, "Delete", false, false),
...@@ -412,7 +412,7 @@ impl State {...@@ -412,7 +412,7 @@ impl State {
412 actions.extend([412 actions.extend([
413 item(413 item(
414 Action::Password,414 Action::Password,
415 "Password Protect This Section…",415 "Password Protect This Section",
416 library.catalog().is_none(),416 library.catalog().is_none(),
417 true,417 true,
418 ),418 ),
...@@ -423,7 +423,7 @@ impl State {...@@ -423,7 +423,7 @@ impl State {
423 section_file(library, path).is_none(),423 section_file(library, path).is_none(),
424 false,424 false,
425 ),425 ),
426 item(Action::Theme, "Theme…", false, false),426 item(Action::Theme, "Theme", false, false),
427 ]);427 ]);
428 }428 }
429 actions429 actions
...@@ -433,10 +433,10 @@ impl State {...@@ -433,10 +433,10 @@ impl State {
433 let last = self.notebooks.len().saturating_sub(1);433 let last = self.notebooks.len().saturating_sub(1);
434 let unread = self.unread_notebook(library);434 let unread = self.unread_notebook(library);
435 vec![435 vec![
436 item(Action::Rename, "Rename…", false, false),436 item(Action::Rename, "Rename", false, false),
437 item(437 item(
438 Action::SaveAs,438 Action::SaveAs,
439 "Save As…",439 "Save As",
440 library.catalog().is_none(),440 library.catalog().is_none(),
441 false,441 false,
442 ),442 ),
...@@ -493,7 +493,7 @@ impl State {...@@ -493,7 +493,7 @@ impl State {
493 library.folder().is_none(),493 library.folder().is_none(),
494 true,494 true,
495 ),495 ),
496 item(Action::Theme, "Theme…", false, false),496 item(Action::Theme, "Theme", false, false),
497 item(497 item(
498 Action::RecycleBin,498 Action::RecycleBin,
499 "Notebook Recycle Bin",499 "Notebook Recycle Bin",
...@@ -502,7 +502,7 @@ impl State {...@@ -502,7 +502,7 @@ impl State {
502 ),502 ),
503 item(503 item(
504 Action::Properties,504 Action::Properties,
505 "Properties…",505 "Properties",
506 library.catalog().is_none(),506 library.catalog().is_none(),
507 false,507 false,
508 ),508 ),
crates/snowbound/src/options.rs+1-1
...@@ -992,7 +992,7 @@ fn installed(state: &mut State, _: &mut Options) {...@@ -992,7 +992,7 @@ fn installed(state: &mut State, _: &mut Options) {
992 let ui = &mut state.ui;992 let ui = &mut state.ui;
993 if crate::desktop::uninstallable() {993 if crate::desktop::uninstallable() {
994 path(ui, &crate::desktop::binary().unwrap_or_default());994 path(ui, &crate::desktop::binary().unwrap_or_default());
995 if ui::button(ui, "uninstall", "Uninstall…").clicked {995 if ui::button(ui, "uninstall", "Uninstall").clicked {
996 crate::desktop::uninstall(&state.proxy);996 crate::desktop::uninstall(&state.proxy);
997 }997 }
998 } else if crate::desktop::installable() {998 } else if crate::desktop::installable() {
crates/snowbound/src/print.rs+1-5
...@@ -257,11 +257,7 @@ impl State {...@@ -257,11 +257,7 @@ impl State {
257 });257 });
258 crate::buttons(ui);258 crate::buttons(ui);
259 let cancel = ui::button(ui, "cancel", "Cancel").clicked;259 let cancel = ui::button(ui, "cancel", "Cancel").clicked;
260 let action = if dialog.export {260 let action = if dialog.export { "Export" } else { "Print" };
261 "Export…"
262 } else {
263 "Print…"
264 };
265 let go = ui::button(ui, "go", action).clicked || entered;261 let go = ui::button(ui, "go", action).clicked || entered;
266 ui.close();262 ui.close();
267 ui.close();263 ui.close();
crates/snowbound/src/protection.rs+5-5
...@@ -442,9 +442,9 @@ impl State {...@@ -442,9 +442,9 @@ impl State {
442 };442 };
443 text(ui, "status", &status, false);443 text(ui, "status", &status, false);
444 buttons = if protected {444 buttons = if protected {
445 &["Change Password…", "Remove Password…", "Lock All", "Close"]445 &["Change Password", "Remove Password", "Lock All", "Close"]
446 } else {446 } else {
447 &["Set Password…", "Lock All", "Close"]447 &["Set Password", "Lock All", "Close"]
448 };448 };
449 }449 }
450 Dialog::Set([value, confirm], refused) => {450 Dialog::Set([value, confirm], refused) => {
...@@ -527,9 +527,9 @@ impl State {...@@ -527,9 +527,9 @@ impl State {
527 self.ui.close_popup(id());527 self.ui.close_popup(id());
528 return self.lock_all();528 return self.lock_all();
529 }529 }
530 (Some("Set Password…"), _) => Some(Dialog::Set(Default::default(), None)),530 (Some("Set Password"), _) => Some(Dialog::Set(Default::default(), None)),
531 (Some("Change Password…"), _) => Some(Dialog::Change(Default::default(), None)),531 (Some("Change Password"), _) => Some(Dialog::Change(Default::default(), None)),
532 (Some("Remove Password…"), _) => Some(Dialog::Remove(Zeroizing::default(), false)),532 (Some("Remove Password"), _) => Some(Dialog::Remove(Zeroizing::default(), false)),
533 (Some(_), Dialog::Unlock(value, _)) => match library.unlock(&path, &value) {533 (Some(_), Dialog::Unlock(value, _)) => match library.unlock(&path, &value) {
534 Ok(()) => {534 Ok(()) => {
535 self.commands535 self.commands
crates/snowbound/src/recording.rs+1-1
...@@ -495,7 +495,7 @@ impl State {...@@ -495,7 +495,7 @@ impl State {
495 let title =495 let title =
496 crate::commands::command(crate::commands::Id::Transport(Transport::SeekTo))496 crate::commands::command(crate::commands::Id::Transport(Transport::SeekTo))
497 .title;497 .title;
498 node.set_label(title.trim_end_matches('…'));498 node.set_label(title);
499 node.set_value(clock_text);499 node.set_value(clock_text);
500 }500 }
501 if clicked {501 if clicked {
crates/snowbound/src/save_as.rs+1-1
...@@ -135,7 +135,7 @@ impl State {...@@ -135,7 +135,7 @@ impl State {
135 }135 }
136 crate::buttons(ui);136 crate::buttons(ui);
137 let cancel = ui::button(ui, "cancel", "Cancel").clicked;137 let cancel = ui::button(ui, "cancel", "Cancel").clicked;
138 let go = ui::button(ui, "go", "Save As…").clicked || entered;138 let go = ui::button(ui, "go", "Save As").clicked || entered;
139 ui.close();139 ui.close();
140 ui.close();140 ui.close();
141 if !go && !cancel {141 if !go && !cancel {
crates/snowbound/src/sidebar.rs+3-3
...@@ -279,7 +279,7 @@ fn sidebar(...@@ -279,7 +279,7 @@ fn sidebar(
279 (279 (
280 "shared",280 "shared",
281 Leading::Icon(art::LINK),281 Leading::Icon(art::LINK),
282 "Open Shared…",282 "Open Shared",
283 Action::OpenShared,283 Action::OpenShared,
284 ),284 ),
285 (285 (
...@@ -1267,13 +1267,13 @@ impl crate::State {...@@ -1267,13 +1267,13 @@ impl crate::State {
1267 (1267 (
1268 "shared",1268 "shared",
1269 Leading::Icon(art::LINK),1269 Leading::Icon(art::LINK),
1270 "Open Shared Notebook…",1270 "Open Shared Notebook",
1271 crate::Command::OpenShared,1271 crate::Command::OpenShared,
1272 ),1272 ),
1273 (1273 (
1274 "server",1274 "server",
1275 Leading::Icon(art::SERVER),1275 Leading::Icon(art::SERVER),
1276 "Open Notebook from Server…",1276 "Open Notebook from Server",
1277 crate::Command::OpenFromServer(None),1277 crate::Command::OpenFromServer(None),
1278 ),1278 ),
1279 ]1279 ]
crates/snowbound/src/symbol.rs+1-1
...@@ -101,7 +101,7 @@ pub fn gallery(ui: &mut Ui, id: Id, anchor: Anchor, symbols: &[char]) -> Option<...@@ -101,7 +101,7 @@ pub fn gallery(ui: &mut Ui, id: Id, anchor: Anchor, symbols: &[char]) -> Option<
101 "more",101 "more",
102 Spec {102 Spec {
103 size: [fill(), fill()],103 size: [fill(), fill()],
104 text: Some("More Symbols…"),104 text: Some("More Symbols"),
105 icon: Some(art::SYMBOL),105 icon: Some(art::SYMBOL),
106 gap: 6.0,106 gap: 6.0,
107 ..Spec::default()107 ..Spec::default()
crates/snowbound/src/sync.rs+1-1
...@@ -739,7 +739,7 @@ fn build(ui: &mut Ui, facts: &Facts) -> Picked {...@@ -739,7 +739,7 @@ fn build(ui: &mut Ui, facts: &Facts) -> Picked {
739 &format!("Using the system’s connection. Snowbound couldn’t sign in: {notice}"),739 &format!("Using the system’s connection. Snowbound couldn’t sign in: {notice}"),
740 theme.text_dim,740 theme.text_dim,
741 );741 );
742 picked.sign_in = ui::button(ui, "sign-in", "Sign In…").clicked;742 picked.sign_in = ui::button(ui, "sign-in", "Sign In").clicked;
743 }743 }
744 ui.close();744 ui.close();
745 }745 }
crates/snowbound/src/tags.rs+3-3
...@@ -515,11 +515,11 @@ impl State {...@@ -515,11 +515,11 @@ impl State {
515 let new = button(515 let new = button(
516 ui,516 ui,
517 "new",517 "new",
518 "New Tag…",518 "New Tag",
519 list.tags.len() < commands::TAGS && list.editor.is_none(),519 list.tags.len() < commands::TAGS && list.editor.is_none(),
520 );520 );
521 modify = modify.or_else(|| {521 modify = modify.or_else(|| {
522 button(ui, "modify", "Modify Tag…", list.selected.is_some())522 button(ui, "modify", "Modify Tag", list.selected.is_some())
523 .then_some(list.selected)523 .then_some(list.selected)
524 .flatten()524 .flatten()
525 });525 });
...@@ -1035,7 +1035,7 @@ fn gallery(ui: &mut Ui, id: Id, anchor: Anchor, snowbound: bool) -> Option<Pick>...@@ -1035,7 +1035,7 @@ fn gallery(ui: &mut Ui, id: Id, anchor: Anchor, snowbound: bool) -> Option<Pick>
1035 ..Spec::default()1035 ..Spec::default()
1036 },1036 },
1037 );1037 );
1038 if ui::button(ui, "picture", "Custom Image…").clicked {1038 if ui::button(ui, "picture", "Custom Image").clicked {
1039 picked = Some(Pick::Picture);1039 picked = Some(Pick::Picture);
1040 }1040 }
1041 }1041 }
crates/snowbound/src/themes.rs+1-1
...@@ -807,7 +807,7 @@ pub(crate) fn gallery(...@@ -807,7 +807,7 @@ pub(crate) fn gallery(
807 "customize",807 "customize",
808 Spec {808 Spec {
809 size: [fill(), fill()],809 size: [fill(), fill()],
810 text: Some("Customize…"),810 text: Some("Customize"),
811 ..Spec::default()811 ..Spec::default()
812 },812 },
813 );813 );
crates/snowbound/src/unpack.rs+1-1
...@@ -154,7 +154,7 @@ impl State {...@@ -154,7 +154,7 @@ impl State {
154 ..Spec::default()154 ..Spec::default()
155 },155 },
156 );156 );
157 let browse = ui::button(ui, "browse", "Browse…").clicked;157 let browse = ui::button(ui, "browse", "Browse").clicked;
158 let root = dialog.folder.join(dialog.name.trim());158 let root = dialog.folder.join(dialog.name.trim());
159 let full = format!("Full path: {}", root.display());159 let full = format!("Full path: {}", root.display());
160 ui.leaf(160 ui.leaf(
crates/snowbound/tests/replay.rs+2-2
...@@ -452,7 +452,7 @@ fn a_version_copied_into_its_section_is_listed_and_found() {...@@ -452,7 +452,7 @@ fn a_version_copied_into_its_section_is_listed_and_found() {
452 let notebook =452 let notebook =
453 Path::new(env!("CARGO_MANIFEST_DIR")).join("../../corpus/page-versions/candidate-restore");453 Path::new(env!("CARGO_MANIFEST_DIR")).join("../../corpus/page-versions/candidate-restore");
454 let mut steps = version_menu();454 let mut steps = version_menu();
455 // Copy Page To…, then the section itself.455 // Copy Page To, then the section itself.
456 steps.extend(["key Down", "key Down", "key Down", "key Enter", "settle"].map(String::from));456 steps.extend(["key Down", "key Down", "key Down", "key Enter", "settle"].map(String::from));
457 steps.extend(["key Down", "key Enter", "accessibility copied"].map(String::from));457 steps.extend(["key Down", "key Enter", "accessibility copied"].map(String::from));
458 steps.extend(search("Second author", "found"));458 steps.extend(search("Second author", "found"));
...@@ -674,7 +674,7 @@ fn open_shared_notebook_is_offered_with_notebooks_open() {...@@ -674,7 +674,7 @@ fn open_shared_notebook_is_offered_with_notebooks_open() {
674 let [sidebar, dialog] = replay_with(&scratch, Some(&notebook), &steps, &variables)674 let [sidebar, dialog] = replay_with(&scratch, Some(&notebook), &steps, &variables)
675 .try_into()675 .try_into()
676 .unwrap();676 .unwrap();
677 assert!(sidebar.contains(r#""Open Shared…""#), "{sidebar}");677 assert!(sidebar.contains(r#""Open Shared""#), "{sidebar}");
678 assert!(678 assert!(
679 dialog.contains(r#"Dialog "Open Shared Notebook""#),679 dialog.contains(r#"Dialog "Open Shared Notebook""#),
680 "{dialog}"680 "{dialog}"
tools/RELEASE.md+2-2
...@@ -230,7 +230,7 @@ frames from the symbol table....@@ -230,7 +230,7 @@ frames from the symbol table.
230230
231`update.rs` runs one thread. With automatic checks on (Options, General) and a231`update.rs` runs one thread. With automatic checks on (Options, General) and a
232published build, it checks shortly after launch and then daily, skipping while232published build, it checks shortly after launch and then daily, skipping while
233Work Offline is on; Check for Updates… (the app menu on macOS, the command233Work Offline is on; Check for Updates (the app menu on macOS, the command
234palette elsewhere) checks at once and reports what it found. A check reads234palette elsewhere) checks at once and reports what it found. A check reads
235`latest.json`, then the named build's `build.json` and signature, then235`latest.json`, then the named build's `build.json` and signature, then
236`history.json` and the `build.json` of each build since its own, and236`history.json` and the `build.json` of each build since its own, and
...@@ -241,7 +241,7 @@ the app unpacked into `.Snowbound.app.update` next to the bundle on macOS, the...@@ -241,7 +241,7 @@ the app unpacked into `.Snowbound.app.update` next to the bundle on macOS, the
241executable into `.snowbound.update` next to it on Linux (`.snowbound.exe.update` on241executable into `.snowbound.update` next to it on Linux (`.snowbound.exe.update` on
242Windows). The sync status icon gets a dot, and its popup says what the update242Windows). The sync status icon gets a dot, and its popup says what the update
243changes, unfolding to the titles, and offers Build Folder and Restart to243changes, unfolding to the titles, and offers Build Folder and Restart to
244Update; Check for Updates… lists them in its dialog.244Update; Check for Updates lists them in its dialog.
245245
246Restart to Update quits the app and starts the old executable with246Restart to Update quits the app and starts the old executable with
247`--finish-update`, which waits for the app to exit, renames the old bundle (or247`--finish-update`, which waits for the app to exit, renames the old bundle (or
tools/canvas/README.md+1-1
...@@ -50,7 +50,7 @@ snowbound title bar · toolbar · section frame · page tabs · the page as a...@@ -50,7 +50,7 @@ snowbound title bar · toolbar · section frame · page tabs · the page as a
5050
51`--notebook FOLDER` opens a notebook laid out as OneNote lays one out. The title bar shows the application's icon and the notebook's name; below it two rows of toolbar buttons follow the Home ribbon's Basic Text group, the first nine tags and zoom (only zoom acts yet). Readable top-level sections are tabs in the notebook's order, each leaning over the next at 45° with a faint highlight along its top, and the open one on top, casting a soft shadow and framing the page and the page tabs on the right in its colour, which shades slightly down the window; a tab that opens rises to meet the frame as its outline fades in. The frame's top corners and the page's corners are rounded concentric with the window's. The open page's tab is the page's colour and joins it with rounded inside corners; subpages are indented. The interface follows the system's light or dark appearance, and in the dark one the page's paper darkens and text left at OneNote's automatic colour turns light (except on a highlight), resolved when painting so nothing re-lays out; explicit colours, highlights, pictures and ink keep theirs, except that the background art of OneNote 2010's page templates, recognised by the picture's hash, paints from bundled vector recreations drawn for the paper. Sections take their stored colour's hue at a saturation and lightness chosen for each appearance. The section's colour is also the interface accent, easing to the next section's when another opens. The search box, New Page and the page-tab toggle sit above the page tabs. Command-F searches the page titles; Enter opens the first match and Escape clears it. A page that also changed on another computer shows Keep mine / Keep theirs above it. `--section FILE TITLE` opens one section the same way at that page. Section files must be regular files; discovery rejects symbolic links.51`--notebook FOLDER` opens a notebook laid out as OneNote lays one out. The title bar shows the application's icon and the notebook's name; below it two rows of toolbar buttons follow the Home ribbon's Basic Text group, the first nine tags and zoom (only zoom acts yet). Readable top-level sections are tabs in the notebook's order, each leaning over the next at 45° with a faint highlight along its top, and the open one on top, casting a soft shadow and framing the page and the page tabs on the right in its colour, which shades slightly down the window; a tab that opens rises to meet the frame as its outline fades in. The frame's top corners and the page's corners are rounded concentric with the window's. The open page's tab is the page's colour and joins it with rounded inside corners; subpages are indented. The interface follows the system's light or dark appearance, and in the dark one the page's paper darkens and text left at OneNote's automatic colour turns light (except on a highlight), resolved when painting so nothing re-lays out; explicit colours, highlights, pictures and ink keep theirs, except that the background art of OneNote 2010's page templates, recognised by the picture's hash, paints from bundled vector recreations drawn for the paper. Sections take their stored colour's hue at a saturation and lightness chosen for each appearance. The section's colour is also the interface accent, easing to the next section's when another opens. The search box, New Page and the page-tab toggle sit above the page tabs. Command-F searches the page titles; Enter opens the first match and Escape clears it. A page that also changed on another computer shows Keep mine / Keep theirs above it. `--section FILE TITLE` opens one section the same way at that page. Section files must be regular files; discovery rejects symbolic links.
5252
53Launched without a file, the app reopens the notebooks open when it last closed, at the one shown last, or shows "No notebooks open" with New Notebook and Open Existing. Settings (the open notebooks, the sidebar, recent fonts and the user name edits are stored under, which Settings… (⌘,, Ctrl+, on Linux, or Options in the sidebar) changes and which starts as the account's full name) are JSON in `~/Library/Application Support/Snowbound/settings.json` or `$XDG_CONFIG_HOME/snowbound`; `--settings FILE` uses another file, `--notebook FOLDER` adds a notebook to them, and `--screenshot` never writes them. Open Existing takes a notebook folder, its `Open Notebook.onetoc2` or a section file, which opens in the notebook its folders make up. A notebook on a mounted SMB share opens through the embedded SMB client, signed in with the password the system keychain holds, and through the mount only when that fails. The notebook button left of the section tabs opens OneNote 2010's navigation bar: the open notebooks with their sections and section groups, the open section marked; dragging a row reorders a folder or, onto a group, moves into it, and right-clicking a row, a section tab or a page tab offers OneNote's commands (Rename, Delete to the notebook's recycle bin, New Section, New Section Group, Close This Notebook; Delete, New Page, Make Subpage, Promote Subpage). Page tabs drag to reorder. The `+` above the page tabs adds a page at the end of the section, titled with its date and time as OneNote titles one, and puts the caret in its title; until its body is typed in, drawn on or clicked, templates offer our recreations of OneNote 2010's art (Ivy first), its page colours and Informal Meeting Notes' content, written as ops (`corpus/notebook-management`).53Launched without a file, the app reopens the notebooks open when it last closed, at the one shown last, or shows "No notebooks open" with New Notebook and Open Existing. Settings (the open notebooks, the sidebar, recent fonts and the user name edits are stored under, which Settings (⌘,, Ctrl+, on Linux, or Options in the sidebar) changes and which starts as the account's full name) are JSON in `~/Library/Application Support/Snowbound/settings.json` or `$XDG_CONFIG_HOME/snowbound`; `--settings FILE` uses another file, `--notebook FOLDER` adds a notebook to them, and `--screenshot` never writes them. Open Existing takes a notebook folder, its `Open Notebook.onetoc2` or a section file, which opens in the notebook its folders make up. A notebook on a mounted SMB share opens through the embedded SMB client, signed in with the password the system keychain holds, and through the mount only when that fails. The notebook button left of the section tabs opens OneNote 2010's navigation bar: the open notebooks with their sections and section groups, the open section marked; dragging a row reorders a folder or, onto a group, moves into it, and right-clicking a row, a section tab or a page tab offers OneNote's commands (Rename, Delete to the notebook's recycle bin, New Section, New Section Group, Close This Notebook; Delete, New Page, Make Subpage, Promote Subpage). Page tabs drag to reorder. The `+` above the page tabs adds a page at the end of the section, titled with its date and time as OneNote titles one, and puts the caret in its title; until its body is typed in, drawn on or clicked, templates offer our recreations of OneNote 2010's art (Ivy first), its page colours and Informal Meeting Notes' content, written as ops (`corpus/notebook-management`).
5454
55As in OneNote 2010, Notebook Recycle Bin (File menu, a notebook's context menu, or the palette) shows the notebook's recycle bin as its own row of tabs, read-only under a bar: a page's context menu restores it to a section, keeping its identity, copies it out, or deletes it for good, a section's restores it to a folder, and Empty Recycle Bin deletes what it holds (`corpus/recycle-bin-view`). Pages another author changed since they were viewed are bold in the page list, and so are their section's tab and row and the groups and notebook holding them, until left or marked read (Mark as Read, Ctrl+Q off macOS; Mark Notebook as Read; Next Unread; Show Unread Changes in This Notebook); what was read is kept in the cache folder's `read.json`, as OneNote keeps it in its cache. Save As (⇧⌘S) writes the page or section as a OneNote section, the notebook as a OneNote package, or any of them as a PDF; opening a `.onepkg` asks, as Unpack Notebook does, for its name, colour and folder (`corpus/notebook-package`).55As in OneNote 2010, Notebook Recycle Bin (File menu, a notebook's context menu, or the palette) shows the notebook's recycle bin as its own row of tabs, read-only under a bar: a page's context menu restores it to a section, keeping its identity, copies it out, or deletes it for good, a section's restores it to a folder, and Empty Recycle Bin deletes what it holds (`corpus/recycle-bin-view`). Pages another author changed since they were viewed are bold in the page list, and so are their section's tab and row and the groups and notebook holding them, until left or marked read (Mark as Read, Ctrl+Q off macOS; Mark Notebook as Read; Next Unread; Show Unread Changes in This Notebook); what was read is kept in the cache folder's `read.json`, as OneNote keeps it in its cache. Save As (⇧⌘S) writes the page or section as a OneNote section, the notebook as a OneNote package, or any of them as a PDF; opening a `.onepkg` asks, as Unpack Notebook does, for its name, colour and folder (`corpus/notebook-package`).
5656