diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ad22c0..670d46a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ - Zoom feature - font selection - font size selection +- line spacing button ### Not yet implemented - ODT import / open existing file diff --git a/src/lib/editor/ToolbarExpanded.svelte b/src/lib/editor/ToolbarExpanded.svelte index 30e2c5e..48b017d 100644 --- a/src/lib/editor/ToolbarExpanded.svelte +++ b/src/lib/editor/ToolbarExpanded.svelte @@ -75,8 +75,35 @@ return mixed ? '' : (size ?? DEFAULT_FONT_SIZE); }); + // Must match line-height in editor.css (.paper .tiptap) + const DEFAULT_LINE_HEIGHT = '1.5'; + const LINE_HEIGHTS = [ + { value: '1', label: 'Single' }, + { value: '1.15', label: '1.15' }, + { value: '1.5', label: '1.5' }, + { value: '2', label: 'Double' }, + ] as const; + + let currentLineHeight = $derived.by(() => { + if (tick < 0 || !editor) return DEFAULT_LINE_HEIGHT; + const { from, to, empty } = editor.state.selection; + if (empty) { + return editor.state.selection.$head.parent.attrs.lineHeight ?? DEFAULT_LINE_HEIGHT; + } + let lh: string | undefined; + let mixed = false; + editor.state.doc.nodesBetween(from, to, (node) => { + if (mixed || !('lineHeight' in node.attrs)) return; + const h: string = node.attrs.lineHeight ?? DEFAULT_LINE_HEIGHT; + if (lh === undefined) lh = h; + else if (lh !== h) mixed = true; + }); + return mixed ? '' : (lh ?? DEFAULT_LINE_HEIGHT); + }); + let fontOpen = $state(false); let sizeOpen = $state(false); + let lineHeightOpen = $state(false); let sizeInputFocused = $state(false); let sizeInputValue = $state(''); let savedFrom: number | null = null; @@ -92,6 +119,7 @@ if (!editor) return; sizeOpen = false; sizeInputFocused = false; + lineHeightOpen = false; // Save selection before the picker button steals focus. savedFrom = editor.state.selection.from; savedTo = editor.state.selection.to; @@ -129,6 +157,7 @@ if (!editor) return; sizeInputFocused = true; fontOpen = false; + lineHeightOpen = false; savedFrom = editor.state.selection.from; savedTo = editor.state.selection.to; (e.target as HTMLInputElement).select(); @@ -154,6 +183,7 @@ function openSizePicker() { if (!editor) return; fontOpen = false; + lineHeightOpen = false; if (!sizeInputFocused) { savedFrom = editor.state.selection.from; savedTo = editor.state.selection.to; @@ -177,6 +207,28 @@ window.addEventListener('mousedown', handler); return { destroy() { window.removeEventListener('mousedown', handler); } }; } + + function openLineHeightPicker() { + if (!editor) return; + fontOpen = false; + sizeOpen = false; + sizeInputFocused = false; + lineHeightOpen = !lineHeightOpen; + } + + function pickLineHeight(value: string) { + if (!editor) return; + lineHeightOpen = false; + editor.chain().focus().setLineHeight(value).run(); + } + + function lineHeightPickerClickOutside(node: HTMLElement) { + function handler(e: MouseEvent) { + if (!node.contains(e.target as Node)) lineHeightOpen = false; + } + window.addEventListener('mousedown', handler); + return { destroy() { window.removeEventListener('mousedown', handler); } }; + }
@@ -254,6 +306,34 @@
+
+ + {#if lineHeightOpen} +
+ {#each LINE_HEIGHTS as lh} + + {/each} +
+ {/if} +
+ +
+