diff --git a/CHANGELOG.md b/CHANGELOG.md index a08bb65..e24996b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ - AutoCorrect while typing (LibreOffice's Tools ▸ AutoCorrect Options, one checkbox per rule): typographic quotes per document language (`"…"` / `„…“`), its dash matrix (`A - B` → `A – B`, `A--B` → `A—B`), its replacement table (`-->` → `→`, `(C)` → `©`, `...` → `…`), capitalize the first letter of a sentence — with an abbreviation exception list — and TWo INitial CApitals. URL recognition and auto-list ride the same switches - Word completion (the same dialog's own section, as LibreOffice keeps it): every word of at least eight letters is remembered as it is typed, and typing its first three letters offers the rest in grey after the caret — Enter takes the offer, Esc drops it. It is offered while typing only, so an Enter meant to split a paragraph never completes a word instead, and the offer stays outside the document until it is accepted. Minimum length, an appended space and clearing the collected words are all settable - AutoText (LibreOffice's Tools ▸ AutoText, Word's Insert ▸ Quick Parts): keep a selected block of text under a name and a shortcut, then insert it from the library or by typing the shortcut and pressing F3 — LibreOffice's own key. An entry keeps its formatting, and the library belongs to the app rather than to a document, as it does in both products +- Ruby annotations (Insert ▸ Phonetic guide — LibreOffice's Format ▸ Asian Phonetic Guide, Word's Phonetic Guide): the reading printed over its base text, the selected text proposed as the base. Round-trips as ODF `text:ruby` with its ruby-family style and as Word's `w:ruby` run — verified through LibreOffice both ways, including our `.docx` converted to `.odt` by it - Automatic hyphenation for the whole document (Layout ▸ Hyphenation): the browser hyphenates in the document's own language, which shortens a justified paragraph the way LibreOffice does. Round-trips as ODF `fo:hyphenate` on the base style — where LibreOffice keeps it, in its *text* properties — and Word's `w:autoHyphenation` - Manual line breaks (Shift+Enter) @@ -163,7 +164,7 @@ The gap against Word/LibreOffice, most valuable first. Reviewed 2026-08-14. - Find & Replace by font, size or colour — the weight/slant/underline and paragraph-style half is implemented, but LibreOffice's Format dialog is the whole character dialog - Multi-document management: one document is open at a time, so there is no window list and no side-by-side compare - Thesaurus and grammar check -- Vertical writing modes (ODF `tb-rl`) and Asian typography (ruby) +- Vertical writing modes (ODF `tb-rl`), and a ruby annotation's own alignment and position — both products' defaults are what we write - Password-protected ODT/DOCX; digital signatures - Split view: two views of one document, scrolled independently - A committed corpus of documents we author ourselves, so CI can test against real files diff --git a/src/lib/components/RubyDialog.svelte b/src/lib/components/RubyDialog.svelte new file mode 100644 index 0000000..3875718 --- /dev/null +++ b/src/lib/components/RubyDialog.svelte @@ -0,0 +1,134 @@ + + + (open = false)} + onclick={(e) => e.target === dialogEl && (open = false)} + aria-label={t().ruby.title} +> + + {t().ruby.title} + + + {t().ruby.base} + + + + {t().ruby.reading} + e.key === 'Enter' && insert()} /> + + + {#if base.trim()} + {base}{reading} + {/if} + + + + (open = false)}>{t().common.cancel} + {t().common.insert} + + + + + diff --git a/src/lib/components/ribbon/icons.ts b/src/lib/components/ribbon/icons.ts index f720e7b..5cd88f8 100644 --- a/src/lib/components/ribbon/icons.ts +++ b/src/lib/components/ribbon/icons.ts @@ -137,6 +137,8 @@ export const STROKED = { footer: ['M3.5 1.75h9v12.5h-9z', 'M5.4 4h5.2M5.4 6.1h5.2M5.4 8.2h3.1'], pageNumber: ['M3.25 1.75h9.5v12.5h-9.5z', 'M6.9 6.4 8 5.3v5.2', 'M6.75 10.5h2.5'], pageBreak: ['M2 2.5h12M2 13.5h12', 'M8 5.5v5.2', 'M6.2 8.9 8 10.7l1.8-1.8'], + // A big glyph with its small reading over it: what the annotation prints. + ruby: ['M4.5 13.2V6.6M4.5 6.6h4.2M8.7 6.6v6.6', 'M11 4.6h3.2M12.6 4.6v-2.4'], // A page with one saved block picked out of its text: the stored entry. quickParts: ['M3 1.75h10v12.5H3z', 'M5.2 4.4h5.6v3H5.2z', 'M5.2 9.9h5.6M5.2 12h3.4'], } as const; diff --git a/src/lib/components/ribbon/tabs/InsertTab.svelte b/src/lib/components/ribbon/tabs/InsertTab.svelte index 850eea4..e09d2c1 100644 --- a/src/lib/components/ribbon/tabs/InsertTab.svelte +++ b/src/lib/components/ribbon/tabs/InsertTab.svelte @@ -9,6 +9,7 @@ import BookmarkDialog from '../../BookmarkDialog.svelte'; import CrossRefDialog from '../../CrossRefDialog.svelte'; import FormulaDialog from '../../FormulaDialog.svelte'; + import RubyDialog from '../../RubyDialog.svelte'; import { captionClicks, anchored, clickOutside, isMenuOpen, toggleMenu, closeMenu } from '../menu.svelte'; import { OPEN_LINK_DIALOG_EVENT } from '../../../editor/extensions/link'; import { OPEN_BOOKMARK_DIALOG_EVENT, bookmarkNames, findBookmark } from '../../../editor/extensions/bookmark'; @@ -37,6 +38,8 @@ onAutoText?: () => void; } = $props(); + let rubyOpen = $state(false); + let hasSelection = $derived(tick >= 0 && !!editor && !editor.state.selection.empty); let isLink = $derived(tick >= 0 && !!editor?.isActive('link')); let bmNames = $derived(tick >= 0 && editor && !hfActive ? bookmarkNames(editor.state.doc) : []); @@ -264,6 +267,14 @@ disabled={!editor || !onAutoText} onclick={() => onAutoText?.()} /> + (rubyOpen = true)} + /> +
{base}{reading}