import { Extension } from "@tiptap/core"; import Suggestion from "@tiptap/suggestion"; import { ref, h } from "vue"; export const SlashCommand = Extension.create({ name: "slash-command", addOptions() { return { suggestion: { char: "/", startOfLine: true, command: ({ editor, range, props }) => { // props.type => 'paragraph' | 'heading' | 'todo' if (props.type === "paragraph") { editor .chain() .focus() .deleteRange(range) .insertContent({ type: "paragraph", content: [{ type: "text", text: "" }], }) .run(); } if (props.type === "heading") { editor .chain() .focus() .deleteRange(range) .setNode("heading", { level: 2 }) .run(); } if (props.type === "todo") { editor .chain() .focus() .deleteRange(range) .insertContent({ type: "paragraph", content: [{ type: "text", text: "☑️ Todo " }], }) .run(); } }, items: ({ query }) => { const all = [ { title: "Text", type: "paragraph" }, { title: "Heading", type: "heading" }, { title: "Todo", type: "todo" }, ]; return all.filter((item) => item.title.toLowerCase().startsWith(query.toLowerCase()) ); }, render: () => { let component; let popup; return { onStart: (props) => { component = ref({ items: props.items, command: props.command, selected: 0, }); popup = document.createElement("div"); popup.style.position = "absolute"; popup.style.background = "white"; popup.style.border = "1px solid #ddd"; popup.style.borderRadius = "6px"; popup.style.padding = "4px 0"; popup.style.zIndex = 100; document.body.appendChild(popup); update(); function update() { popup.innerHTML = ""; component.value.items.forEach((item, i) => { const div = document.createElement("div"); div.textContent = item.title; div.style.padding = "4px 12px"; div.style.cursor = "pointer"; div.style.background = i === component.value.selected ? "#f0f0f0" : "white"; div.onclick = () => component.value.command({ editor: props.editor, range: props.range, props: item, }); popup.appendChild(div); }); } component.value.update = update; }, onUpdate: (props) => { component.value.items = props.items; component.value.update(); }, onKeyDown: (props) => { const event = props.event; if (event.key === "ArrowDown") { component.value.selected = (component.value.selected + 1) % component.value.items.length; component.value.update(); return true; } if (event.key === "ArrowUp") { component.value.selected = (component.value.selected - 1 + component.value.items.length) % component.value.items.length; component.value.update(); return true; } if (event.key === "Enter") { component.value.command({ editor: props.editor, range: props.range, props: component.value.items[component.value.selected], }); return true; } return false; }, onExit: () => { popup.remove(); }, }; }, }, }; }, addProseMirrorPlugins() { return [Suggestion(this.options.suggestion)]; }, });