/** * Custom TipTap extension for line height. * TipTap doesn't ship this officially so we build it ourselves. */ import { Extension } from "@tiptap/react"; import "@tiptap/extension-text-style"; declare module "@tiptap/core" { interface Commands { lineHeight: { setLineHeight: (lineHeight: string) => ReturnType; unsetLineHeight: () => ReturnType; }; } } export const LineHeight = Extension.create({ name: "lineHeight", addOptions() { return { types: ["paragraph", "heading"], defaultLineHeight: "normal", }; }, addGlobalAttributes() { return [ { types: this.options.types, attributes: { lineHeight: { default: null, parseHTML: (element) => element.style.lineHeight || null, renderHTML: (attributes) => { if (!attributes.lineHeight) return {}; return { style: `line-height: ${attributes.lineHeight}` }; }, }, }, }, ]; }, addCommands() { return { setLineHeight: (lineHeight: string) => ({ tr, state, dispatch }) => { const { selection } = state; tr = tr.setSelection(selection); const { from, to } = selection; state.doc.nodesBetween(from, to, (node, pos) => { if (this.options.types.includes(node.type.name)) { tr = tr.setNodeMarkup(pos, undefined, { ...node.attrs, lineHeight, }); } }); if (dispatch) dispatch(tr); return true; }, unsetLineHeight: () => ({ tr, state, dispatch }) => { const { selection } = state; tr = tr.setSelection(selection); const { from, to } = selection; state.doc.nodesBetween(from, to, (node, pos) => { if (this.options.types.includes(node.type.name)) { tr = tr.setNodeMarkup(pos, undefined, { ...node.attrs, lineHeight: null, }); } }); if (dispatch) dispatch(tr); return true; }, }; }, });