import TiptapHeading, { HeadingOptions as TiptapHeadingOptions, } from "@tiptap/extension-heading"; import { mergeAttributes } from "@tiptap/react"; import { Decoration, DecorationSet } from "@tiptap/pm/view"; import { Plugin } from "@tiptap/pm/state"; import { copyToClipboard } from "../utils"; const copyIcon = ``; const successIcon = ``; export const Heading = TiptapHeading.extend({ // @ts-ignore addProseMirrorPlugins() { return [ new Plugin({ props: { decorations(state) { const decorations: Decoration[] = []; const { doc } = state; doc.descendants((node, pos) => { if (node.type.name === "heading" && node.content.size > 1) { const deco = Decoration.widget( pos + node.nodeSize - 1, () => { const icon = document.createElement("span"); icon.classList.add("link-btn"); icon.innerHTML = " "; icon.contentEditable = "false"; const linkBtnContent = document.createElement("span"); linkBtnContent.classList.add("link-btn-content"); linkBtnContent.innerHTML = copyIcon; icon.appendChild(linkBtnContent); icon.addEventListener("mousedown", (e) => e.preventDefault(), ); icon.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); const id = node.attrs.id; const baseUrl = window.location.href.split('#')[0]; const url = `${baseUrl}#${id}`; copyToClipboard(url); linkBtnContent.innerHTML = successIcon; setTimeout( () => (linkBtnContent.innerHTML = copyIcon), 2000, ); }); return icon; }, { side: 1 }, // render after node content ); decorations.push(deco); } }); return DecorationSet.create(doc, decorations); }, }, }), ]; }, renderHTML({ node, HTMLAttributes }) { const hasLevel = this.options.levels.includes(node.attrs.level); const level = hasLevel ? node.attrs.level : this.options.levels[0]; return [ `h${level}`, mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { id: node.attrs.id, }), 0, ]; }, });