- TipTap 2 → 3: Extension from @tiptap/core, Placeholder from @tiptap/extensions, TaskList/TaskItem from @tiptap/extension-list, link: false in StarterKit (now bundles Link), @tiptap/core added - marked 15 → 17: heading renderer updated to tokens/parseInline API - Pinia 2 → 3, Vue Router 4 → 5 (no code changes required) - Vite 6 → 7, @vitejs/plugin-vue 5 → 6, vue-tsc 2 → 3 - TypeScript 5.6 → 5.9: fixed Uint8Array<ArrayBuffer> strictness in push.ts, removed unused bodyEl ref in NoteViewerView.vue - .npmrc: legacy-peer-deps=true for TipTap v3 peer dep resolution TipTap 3 new capabilities now available: static renderer (createStaticRenderer), MarkViews, @tiptap/extensions package. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
70 lines
2.6 KiB
TypeScript
70 lines
2.6 KiB
TypeScript
import { Extension } from "@tiptap/core";
|
|
import { PluginKey } from "@tiptap/pm/state";
|
|
import Suggestion from "@tiptap/suggestion";
|
|
import { createSuggestionRenderer } from "./suggestionRenderer";
|
|
import type { SuggestionItem } from "@/components/SuggestionDropdown.vue";
|
|
import type { Editor } from "@tiptap/vue-3";
|
|
|
|
const slashCommandsKey = new PluginKey("slashCommands");
|
|
|
|
interface SlashCommand {
|
|
label: string;
|
|
value: string;
|
|
keywords: string[];
|
|
}
|
|
|
|
const COMMANDS: SlashCommand[] = [
|
|
{ label: "Heading 1", value: "h1", keywords: ["heading", "h1", "title"] },
|
|
{ label: "Heading 2", value: "h2", keywords: ["heading", "h2", "subtitle"] },
|
|
{ label: "Heading 3", value: "h3", keywords: ["heading", "h3"] },
|
|
{ label: "Bullet List", value: "ul", keywords: ["bullet", "list", "ul", "unordered"] },
|
|
{ label: "Numbered List", value: "ol", keywords: ["numbered", "ordered", "ol", "number"] },
|
|
{ label: "Task List", value: "task", keywords: ["task", "todo", "checkbox", "check"] },
|
|
{ label: "Code Block", value: "code", keywords: ["code", "block", "pre", "codeblock"] },
|
|
{ label: "Blockquote", value: "quote", keywords: ["quote", "blockquote"] },
|
|
];
|
|
|
|
function applyCommand(editor: Editor, value: string) {
|
|
const chain = editor.chain().focus();
|
|
switch (value) {
|
|
case "h1": chain.toggleHeading({ level: 1 }).run(); break;
|
|
case "h2": chain.toggleHeading({ level: 2 }).run(); break;
|
|
case "h3": chain.toggleHeading({ level: 3 }).run(); break;
|
|
case "ul": chain.toggleBulletList().run(); break;
|
|
case "ol": chain.toggleOrderedList().run(); break;
|
|
case "task": chain.toggleTaskList().run(); break;
|
|
case "code": chain.toggleCodeBlock().run(); break;
|
|
case "quote": chain.toggleBlockquote().run(); break;
|
|
}
|
|
}
|
|
|
|
export const SlashCommands = Extension.create({
|
|
name: "slashCommands",
|
|
|
|
addProseMirrorPlugins() {
|
|
return [
|
|
Suggestion<SuggestionItem>({
|
|
editor: this.editor,
|
|
pluginKey: slashCommandsKey,
|
|
char: "/",
|
|
allowSpaces: false,
|
|
items: ({ query }): SuggestionItem[] => {
|
|
const q = query.toLowerCase();
|
|
return COMMANDS
|
|
.filter((cmd) =>
|
|
!q ||
|
|
cmd.label.toLowerCase().includes(q) ||
|
|
cmd.keywords.some((k) => k.includes(q))
|
|
)
|
|
.map((cmd) => ({ label: cmd.label, value: cmd.value }));
|
|
},
|
|
command: ({ editor, range, props: item }) => {
|
|
editor.chain().focus().deleteRange(range).run();
|
|
applyCommand(editor as Editor, item.value);
|
|
},
|
|
render: createSuggestionRenderer,
|
|
}),
|
|
];
|
|
},
|
|
});
|