conflict-nuxt-4/app/utils/slashExtension.js
2026-02-12 11:24:27 +03:30

142 lines
4.5 KiB
JavaScript
Executable File

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)];
},
});