import { type Component, createSignal, For, createEffect } from "solid-js"; import { Type, Code, Quote, Heading1, Heading2, Heading3, List, ListTodo, FileText, AppWindow, Minus, Grid3X3, Trash2 } from "lucide-solid"; import { cn } from "@/lib/utils"; export interface CommandItem { title: string; description: string; aliases?: string[]; icon: any; command: (props: { editor: any; range: any }) => void; } export const getSuggestionItems = ({ query }: { query: string }): CommandItem[] => { return [ { title: "Text", description: "Just start typing with plain text.", icon: Type, command: ({ editor, range }: { editor: any, range: any }) => { editor.chain().focus().deleteRange(range).setNode("paragraph").run(); }, }, { title: "Heading 1", description: "Big section heading.", aliases: ["h1"], icon: Heading1, command: ({ editor, range }: { editor: any, range: any }) => { editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run(); }, }, { title: "Heading 2", description: "Medium section heading.", aliases: ["h2"], icon: Heading2, command: ({ editor, range }: { editor: any, range: any }) => { editor.chain().focus().deleteRange(range).setNode("heading", { level: 2 }).run(); }, }, { title: "Heading 3", description: "Small section heading.", aliases: ["h3"], icon: Heading3, command: ({ editor, range }: { editor: any, range: any }) => { editor.chain().focus().deleteRange(range).setNode("heading", { level: 3 }).run(); }, }, { title: "Bullet List", description: "Create a simple bulleted list.", icon: List, command: ({ editor, range }: { editor: any, range: any }) => { editor.chain().focus().deleteRange(range).toggleBulletList().run(); }, }, { title: "Checklist", description: "Insert a checklist.", icon: ListTodo, command: ({ editor, range }: { editor: any, range: any }) => { editor.chain().focus().deleteRange(range).toggleTaskList().run(); }, }, { title: "Multi Checklist", description: "Insert a row with multiple checkboxes.", aliases: ["multi", "checkboxes", "multi-checklist"], icon: ListTodo, command: ({ editor, range }: { editor: any, range: any }) => { editor.chain().focus().deleteRange(range).toggleMultiTaskList().run(); }, }, { title: "Code Block", description: "Capture a code snippet.", icon: Code, command: ({ editor, range }: { editor: any, range: any }) => { editor.chain().focus().deleteRange(range).toggleCodeBlock().run(); }, }, { title: "Blockquote", description: "Capture a quote.", icon: Quote, command: ({ editor, range }: { editor: any, range: any }) => { editor.chain().focus().deleteRange(range).toggleBlockquote().run(); }, }, { title: "Upload", description: "Upload an image, video, or document.", aliases: ["image", "video", "file", "upload", "pdf", "movie"], icon: FileText, command: ({ editor, range }: { editor: any, range: any }) => { editor.chain().focus().deleteRange(range).uploadMedia().run(); }, }, { title: "Job File", description: "Embed a job file from Prism.", aliases: ["job", "jobfile", "prism"], icon: AppWindow, command: ({ editor, range }: { editor: any, range: any }) => { editor.chain().focus().deleteRange(range).insertFileViewer().run(); }, }, { title: "Divider", description: "Insert a horizontal rule.", icon: Minus, command: ({ editor, range }: { editor: any, range: any }) => { editor.chain().focus().deleteRange(range).setHorizontalRule().run(); }, }, { title: "Table", description: "Insert a 3x3 table.", aliases: ["grid", "table"], icon: Grid3X3, command: ({ editor, range }: { editor: any, range: any }) => { editor.chain().focus().deleteRange(range).insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(); }, }, { title: "Delete Table", description: "Delete the entire table.", icon: Trash2, command: ({ editor, range }: { editor: any, range: any }) => { editor.chain().focus().deleteRange(range).deleteTable().run(); }, }, ].filter(item => { const lowerQuery = query.toLowerCase(); return item.title.toLowerCase().startsWith(lowerQuery) || item.aliases?.some(alias => alias.toLowerCase().startsWith(lowerQuery)); }); }; export const SlashMenu: Component<{ items: CommandItem[]; command: (item: CommandItem) => void; editor: any; ref: (handlers: { onKeyDown: (e: KeyboardEvent) => boolean }) => void; }> = (props) => { const [selectedIndex, setSelectedIndex] = createSignal(0); // Auto-select the first item when the list changes (filtering) createEffect(() => { // We track props.items. When it changes, we reset. props.items; setSelectedIndex(0); }); const selectItem = (index: number) => { const item = props.items[index]; if (item) { props.command(item); } }; // Expose handlers to the parent via the ref prop props.ref({ onKeyDown: (e: KeyboardEvent) => { if (e.key === "ArrowUp") { setSelectedIndex((prev) => (prev + props.items.length - 1) % props.items.length); return true; } if (e.key === "ArrowDown") { setSelectedIndex((prev) => (prev + 1) % props.items.length); return true; } if (e.key === "Enter") { selectItem(selectedIndex()); return true; } return false; } }); return (
e.stopPropagation()} onWheel={(e) => e.stopPropagation()} >
{(item, index) => ( )} {props.items.length === 0 && (
No results found
)}
); };