// extensions/customMarks.ts import { Mark, markInputRule, markPasteRule, mergeAttributes, } from "@tiptap/core"; // Define available size options const sizeOptions = { xs: "text-xs", sm: "text-sm", base: "text-base", lg: "text-lg", xl: "text-xl", } as const; export type SizeOption = keyof typeof sizeOptions; export interface FontSizeOptions { HTMLAttributes: Record; } declare module "@tiptap/core" { interface Commands { fontSize: { setFontSize: (attributes: { size: SizeOption }) => ReturnType; toggleFontSize: (attributes: { size: SizeOption }) => ReturnType; unsetFontSize: () => ReturnType; }; } } export const fontSizeInputRegex = /(?:^|\s)(~((?:[^~]+))~)$/; export const fontSizePasteRegex = /(?:^|\s)(~((?:[^~]+))~)/g; export const FontSize = Mark.create({ name: "fontSize", addOptions() { return { HTMLAttributes: {}, }; }, addAttributes() { return { size: { default: "base", parseHTML: (element: HTMLElement) => { const sizeClass = Object.entries(sizeOptions).find( ([, className]) => element.classList.contains(className), )?.[0]; return sizeClass || "base"; }, renderHTML: (attributes: { size: SizeOption }) => ({ class: sizeOptions[attributes.size], }), }, }; }, parseHTML() { return [ { tag: "span", getAttrs: (node: HTMLElement) => { const hasSizeClass = Object.values(sizeOptions).some( (className) => node.classList.contains(className), ); return hasSizeClass ? {} : null; }, }, ]; }, renderHTML({ HTMLAttributes }) { return [ "span", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0, ]; }, addCommands() { return { setFontSize: (attributes: { size: SizeOption }) => ({ commands }) => { return commands.setMark(this.name, attributes); }, toggleFontSize: (attributes: { size: SizeOption }) => ({ commands }) => { return commands.toggleMark(this.name, attributes); }, unsetFontSize: () => ({ commands }) => { return commands.unsetMark(this.name); }, }; }, addKeyboardShortcuts() { return { "Mod-s": () => this.editor.commands.toggleFontSize({ size: "sm" }), }; }, addInputRules() { return [ markInputRule({ find: fontSizeInputRegex, type: this.type, getAttributes: { size: "sm" }, // Default size for input rule }), ]; }, addPasteRules() { return [ markPasteRule({ find: fontSizePasteRegex, type: this.type, getAttributes: { size: "sm" }, // Default size for paste rule }), ]; }, }); // TextColor remains unchanged export interface TextColorOptions { HTMLAttributes: Record; } declare module "@tiptap/core" { interface Commands { textColor: { setTextColor: (attributes: { color: string }) => ReturnType; toggleTextColor: (attributes: { color: string }) => ReturnType; unsetTextColor: () => ReturnType; }; } } export const TextColor = Mark.create({ name: "textColor", addOptions() { return { HTMLAttributes: {}, }; }, addAttributes() { return { color: { default: null, parseHTML: (element: HTMLElement) => { const colorClass = Array.from(element.classList).find( (cls) => cls.startsWith("text-"), ); return colorClass ? colorClass.replace("text-", "") : null; }, renderHTML: (attributes) => { if (!attributes.color) return {}; // Use !important to override prose styles return { class: `text-${attributes.color} !text-${attributes.color}`, }; }, }, }; }, parseHTML() { return [ { tag: "span", getAttrs: (node: HTMLElement) => { const colorClass = Array.from(node.classList).find((cls) => cls.startsWith("text-"), ); return colorClass ? { color: colorClass.replace("text-", "") } : null; }, }, ]; }, renderHTML({ HTMLAttributes }) { return [ "span", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0, ]; }, addCommands() { return { setTextColor: (attributes: { color: string }) => ({ commands }) => { return commands.setMark(this.name, attributes); }, toggleTextColor: (attributes: { color: string }) => ({ commands }) => { return commands.toggleMark(this.name, attributes); }, unsetTextColor: () => ({ commands }) => { return commands.unsetMark(this.name); }, }; }, addKeyboardShortcuts() { return { "Mod-Shift-c": () => this.editor.commands.toggleTextColor({ color: "gray-500" }), }; }, });