UNPKG

@patternslib/pat-tiptap

Version:
198 lines (170 loc) 7.49 kB
import { BasePattern } from "@patternslib/patternslib/src/core/basepattern"; import Parser from "@patternslib/patternslib/src/core/parser"; import registry from "@patternslib/patternslib/src/core/registry"; import dom from "@patternslib/patternslib/src/core/dom"; import events from "@patternslib/patternslib/src/core/events"; import logging from "@patternslib/patternslib/src/core/logging"; import utils from "@patternslib/patternslib/src/core/utils"; export const log = logging.getLogger("tiptap"); export const parser = new Parser("tiptap"); parser.addArgument("collaboration-server", null); parser.addArgument("collaboration-document", null); parser.addArgument("toolbar-external", null); parser.addArgument("image-panel", null); parser.addArgument("embed-panel", null); parser.addArgument("link-panel", null); parser.addArgument("source-panel", null); parser.addArgument("image-menu", null); parser.addArgument("embed-menu", null); parser.addArgument("link-menu", null); parser.addArgument("mentions-menu", null); parser.addArgument("tags-menu", null); parser.addArgument("link-extra-protocols", [], undefined, true); // TODO: Remove with next major version. // BBB - Compatibility aliases parser.addAlias("context-menu-link", "link-menu"); parser.addAlias("context-menu-mentions", "mentions-menu"); parser.addAlias("context-menu-tags", "tags-menu"); class Pattern extends BasePattern { static name = "tiptap"; static trigger = ".pat-tiptap"; parser = parser; current_modal = null; // reference to currently open modal dialog async init() { // Constructor this.toolbar_el = null; const TipTap = (await import("@tiptap/core")).Editor; const ExtDocument = (await import("@tiptap/extension-document")).default; const ExtParagraph = (await import("@tiptap/extension-paragraph")).default; const ExtText = (await import("@tiptap/extension-text")).default; this.focus_handler = (await import("./focus-handler")).focus_handler; this.options = parser.parse(this.el, this.options); // Hide element which will be replaced with tiptap instance this.el.style.display = "none"; // Create container for tiptap const container = document.createElement("div"); container.setAttribute("class", "tiptap-container"); this.el.after(container); // Support for pat-autofocus and autofocus: Set focus depending on textarea's focus setting. const set_focus = this.el.classList.contains("pat-autofocus") || this.el.hasAttribute("autofocus"); const is_form_el = ["TEXTAREA", "INPUT"].includes(this.el.tagName); const getText = () => { // Textarea value getter return is_form_el ? this.el.value : this.el.innerHTML; }; const setText = (text) => { // Textarea value setter if (is_form_el) { this.el.value = text; } else { this.el.innerHTML = text; } this.el.dispatchEvent(events.input_event()); }; const extra_extensions = [ // Allow non-paragraph line-breaks by default. (await import("@tiptap/extension-hard-break")).default.configure(), // Gapcursor for images, tables etc to be able to add content below/above. (await import("@tiptap/extension-gapcursor")).Gapcursor.configure(), // Allways include undo/redo support via keyboard shortcuts. (await import("@tiptap/extension-history")).History.configure(), ]; const placeholder = this.el.getAttribute("placeholder"); if (placeholder) { extra_extensions.push( (await import("@tiptap/extension-placeholder")).Placeholder.configure({ placeholder: placeholder, }) ); } // Mentions extension if (this.options.mentionsMenu) { extra_extensions.push( (await import("./extensions/suggestion")) .factory({ app: this, name: "mention", char: "@", plural: "mentions", }) .configure({ url: this.options.mentionsMenu, }) ); } // Tags extension if (this.options.tagsMenu) { extra_extensions.push( (await import("./extensions/suggestion")) .factory({ app: this, name: "tag", char: "#", plural: "tags", }) .configure({ url: this.options.tagsMenu, }) ); } this.toolbar_el = this.options.toolbarExternal ? document.querySelector(this.options.toolbarExternal) : null; if (this.toolbar_el) { const focus_handler_targets = (await import("./focus-handler")).TARGETS; // prettier-ignore focus_handler_targets.push(this.toolbar_el); // We register the focus handler on itself. this.focus_handler(this.toolbar_el); } const scan_debouncer = utils.debounce((dom) => { registry.scan(dom); }, 500); const toolbar_ext = await import("./toolbar"); this.toolbar = toolbar_ext.init_pre({ app: this }); this.editor = new TipTap({ element: container, extensions: [ ExtDocument, ExtText, ExtParagraph, ...(await toolbar_ext.init_extensions({ app: this })), ...extra_extensions, ], content: getText(), onUpdate() { // Note: ``this`` is the editor instance. setText(this.getHTML()); scan_debouncer(this.view.dom); }, onCreate() { // Initially scan the dom for any Patterns in content. scan_debouncer(this.view.dom); }, onFocus: async () => { // Note: ``this`` is the pattern instance. utils.timeout(1); // short timeout to ensure focus class is set even if tiptap_blur_handler is called concurrently. this.toolbar_el?.classList.add("tiptap-focus"); // Set the current focused pat-tiptap instance on the toolbar element. this.toolbar_el && dom.set_data(this.toolbar_el, "tiptap-instance", this); }, onBlur: () => { // Note: ``this`` is the pattern instance. this.toolbar_el?.classList.remove("tiptap-focus"); }, autofocus: set_focus, }); toolbar_ext.init_post({ app: this }); document.addEventListener("pat-modal-ready", (e) => { // store the reference to the modal dialog // We need the reference to registter some modal functionality or // patterns on it, where we would not easily get access to the // modal DOM structure otherwise. this.current_modal = e.target; }); } } registry.register(Pattern); export default Pattern; export { Pattern };