// adapted from @n8body/tiptap-spoiler import { Mark, markInputRule, markPasteRule, mergeAttributes, } from '@tiptap/core' import type { ElementType } from 'react' declare module '@tiptap/core' { interface Commands { spoilerEditor: { setSpoiler: () => ReturnType toggleSpoiler: () => ReturnType unsetSpoiler: () => ReturnType } } } export type SpoilerOptions = { HTMLAttributes: Record spoilerOpenClass: string spoilerCloseClass?: string inputRegex: RegExp pasteRegex: RegExp as: ElementType } const spoilerInputRegex = /(?:^|\s)((?:\|\|)((?:[^||]+))(?:\|\|))$/ const spoilerPasteRegex = /(?:^|\s)((?:\|\|)((?:[^||]+))(?:\|\|))/g export const TiptapSpoiler = Mark.create({ name: 'spoiler', inline: true, group: 'inline', inclusive: false, exitable: true, content: 'inline*', priority: 1001, // higher priority than other formatting so they go inside addOptions() { return { HTMLAttributes: { 'aria-label': 'spoiler' }, spoilerOpenClass: '', spoilerCloseClass: undefined, inputRegex: spoilerInputRegex, pasteRegex: spoilerPasteRegex, as: 'span', editing: false, } }, addCommands() { return { setSpoiler: () => ({ commands }) => commands.setMark(this.name), toggleSpoiler: () => ({ commands }) => commands.toggleMark(this.name), unsetSpoiler: () => ({ commands }) => commands.unsetMark(this.name), } }, addInputRules() { return [ markInputRule({ find: this.options.inputRegex, type: this.type, }), ] }, addPasteRules() { return [ markPasteRule({ find: this.options.pasteRegex, type: this.type, }), ] }, parseHTML() { return [ { tag: 'span', getAttrs: (node) => (node as HTMLElement).ariaLabel?.toLowerCase() === 'spoiler' && null, }, ] }, renderHTML({ HTMLAttributes }) { const elem = document.createElement(this.options.as as string) Object.entries( mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { class: this.options.spoilerCloseClass ?? this.options.spoilerOpenClass, }) ).forEach(([attr, val]) => elem.setAttribute(attr, val)) elem.addEventListener('click', () => { elem.setAttribute('class', this.options.spoilerOpenClass) }) return elem }, })