feat: the index reads like the one a word processor generates

Its leader is real dots now, not a border, so it scales with the font and
reaches the PDF as text. Title and entries follow the document's Heading 1
and default style, which is what the index styles of Word and LO are based on.
This commit is contained in:
Steffen Becker committed 2026-08-08 09:33:25 +00:00
1 parent 34c529e3c4
commit 5549d625f7
3 files changed
+14 -6

No files matched your search

@@ -16,6 +16,8 @@ const FALLBACK_PAGE_HEIGHT = 1123;
// The heading above the entries. An imported TOC keeps the one its file used
// ("Inhalt", "Sommaire", …); a freshly inserted one takes this.
const TITLE = 'Table of Contents';
// Enough leader dots to cross the widest gap a page can offer; the row clips the rest.
const LEADER_DOTS = 200;
declare module '@tiptap/core' {
interface Commands<ReturnType> {
@@ -183,6 +185,9 @@ class TocView {
text.textContent = e.text;
const leader = document.createElement('span');
leader.className = 'toc-leader';
// Real dots, as a word processor fills the gap: they scale with the font and
// reach the PDF as text. The row clips whatever the gap has no room for.
leader.textContent = '.'.repeat(LEADER_DOTS);
const page = document.createElement('span');
page.className = 'toc-page';
page.textContent = String(e.page);
+4 -1
View File
@@ -270,7 +270,10 @@ export function styleCss(sheet: StyleSheet): string {
const attr = `[data-style="${style.name.replace(/"/g, '\\"')}"]`;
const selectors = [`.paper .tiptap ${attr}`];
if (style.outlineLevel) selectors.push(`.paper .tiptap h${style.outlineLevel}:not([data-style])`);
if (style.name === DEFAULT_STYLE) selectors.push('.paper .tiptap p:not([data-style])');
if (style.outlineLevel === 1) selectors.push('.paper .tiptap .toc-title'); // the index heads its list like any chapter
// The generated index carries no style name, and a word processor bases its own
// index styles on the default one — so it follows the document's body text.
if (style.name === DEFAULT_STYLE) selectors.push('.paper .tiptap p:not([data-style])', '.paper .tiptap .toc');
rules.push(`${selectors.join(',\n')} {\n ${decls.join(';\n ')};\n}`);
// A list marker inherits the item's own font, never its paragraph's, so the item
+5 -5
View File
@@ -214,13 +214,13 @@
text-overflow: ellipsis;
flex: 0 1 auto;
}
/* Dotted leader filling the gap; nudged up to sit on the text baseline. */
/* Leader dots filling the gap: the row grants the span the space that is left and
clips the dots that do not fit, so the page number stays put. */
.paper .tiptap .toc-leader {
flex: 1 1 auto;
flex: 1 1 0; /* basis 0: the dots are decoration, they must not size the row */
min-width: 1em;
margin: 0 4px;
border-bottom: 1px dotted currentColor;
transform: translateY(-0.25em);
overflow: hidden;
white-space: nowrap;
}
.paper .tiptap .toc-page {
flex: 0 0 auto;