Full document, spreadsheet, slideshow, and diagram tooling
1import { Extension } from '@tiptap/core';
2
3interface LineSpacingOptions {
4 types: string[];
5}
6
7/**
8 * LineSpacing extension — adds lineHeight attribute to paragraph and heading nodes.
9 *
10 * Presets: 1, 1.15, 1.5, 2, 2.5, 3
11 * Default: null (inherits CSS default line-height)
12 *
13 * Usage:
14 * editor.chain().focus().setLineSpacing('1.5').run()
15 * editor.chain().focus().unsetLineSpacing().run()
16 */
17export const LINE_SPACING_PRESETS: string[] = ['1', '1.15', '1.5', '2', '2.5', '3'];
18
19export const LineSpacing = Extension.create<LineSpacingOptions>({
20 name: 'lineSpacing',
21
22 addOptions() {
23 return {
24 types: ['paragraph', 'heading'],
25 };
26 },
27
28 addGlobalAttributes() {
29 return [
30 {
31 types: this.options.types,
32 attributes: {
33 lineHeight: {
34 default: null,
35 parseHTML: (element: HTMLElement) => {
36 const lh = element.style.lineHeight;
37 if (!lh) return null;
38 const num = parseFloat(lh);
39 if (isNaN(num)) return null;
40 return String(num);
41 },
42 renderHTML: (attributes: Record<string, string | null>) => {
43 if (!attributes.lineHeight) return {};
44 return { style: `line-height: ${attributes.lineHeight}` };
45 },
46 },
47 },
48 },
49 ];
50 },
51
52 addCommands() {
53 return {
54 setLineSpacing: (lineHeight: string) => ({ tr, state, dispatch }) => {
55 const { from, to } = state.selection;
56 let changed = false;
57 state.doc.nodesBetween(from, to, (node, pos) => {
58 if (this.options.types.includes(node.type.name)) {
59 if (dispatch) {
60 tr.setNodeMarkup(pos, undefined, {
61 ...node.attrs,
62 lineHeight,
63 });
64 }
65 changed = true;
66 }
67 });
68 return changed;
69 },
70 unsetLineSpacing: () => ({ tr, state, dispatch }) => {
71 const { from, to } = state.selection;
72 let changed = false;
73 state.doc.nodesBetween(from, to, (node, pos) => {
74 if (this.options.types.includes(node.type.name)) {
75 if (dispatch) {
76 tr.setNodeMarkup(pos, undefined, {
77 ...node.attrs,
78 lineHeight: null,
79 });
80 }
81 changed = true;
82 }
83 });
84 return changed;
85 },
86 };
87 },
88});