import { IconBlockquote, IconCaretRightFilled, IconCheckbox, IconCode, IconH1, IconH2, IconH3, IconInfoCircle, IconList, IconListNumbers, IconMath, IconMathFunction, IconMovie, IconPhoto, IconTable, IconTypography, } from "@tabler/icons-react"; import { CommandProps, SlashMenuGroupedItemsType, } from "@/features/editor/components/slash-menu/types"; import { uploadImageAction } from "@/features/editor/components/image/upload-image-action.tsx"; import { uploadVideoAction } from "@/features/editor/components/video/upload-video-action.tsx"; const CommandGroups: SlashMenuGroupedItemsType = { basic: [ { title: "Text", description: "Just start typing with plain text.", searchTerms: ["p", "paragraph"], icon: IconTypography, command: ({ editor, range }: CommandProps) => { editor .chain() .focus() .deleteRange(range) .toggleNode("paragraph", "paragraph") .run(); }, }, { title: "To-do list", description: "Track tasks with a to-do list.", searchTerms: ["todo", "task", "list", "check", "checkbox"], icon: IconCheckbox, command: ({ editor, range }: CommandProps) => { editor.chain().focus().deleteRange(range).toggleTaskList().run(); }, }, { title: "Heading 1", description: "Big section heading.", searchTerms: ["title", "big", "large"], icon: IconH1, command: ({ editor, range }: CommandProps) => { editor .chain() .focus() .deleteRange(range) .setNode("heading", { level: 1 }) .run(); }, }, { title: "Heading 2", description: "Medium section heading.", searchTerms: ["subtitle", "medium"], icon: IconH2, command: ({ editor, range }: CommandProps) => { editor .chain() .focus() .deleteRange(range) .setNode("heading", { level: 2 }) .run(); }, }, { title: "Heading 3", description: "Small section heading.", searchTerms: ["subtitle", "small"], icon: IconH3, command: ({ editor, range }: CommandProps) => { editor .chain() .focus() .deleteRange(range) .setNode("heading", { level: 3 }) .run(); }, }, { title: "Bullet list", description: "Create a simple bullet list.", searchTerms: ["unordered", "point", "list"], icon: IconList, command: ({ editor, range }: CommandProps) => { editor.chain().focus().deleteRange(range).toggleBulletList().run(); }, }, { title: "Numbered list", description: "Create a list with numbering.", searchTerms: ["numbered", "ordered", "list"], icon: IconListNumbers, command: ({ editor, range }: CommandProps) => { editor.chain().focus().deleteRange(range).toggleOrderedList().run(); }, }, { title: "Quote", description: "Create block quote.", searchTerms: ["blockquote", "quotes"], icon: IconBlockquote, command: ({ editor, range }: CommandProps) => editor.chain().focus().deleteRange(range).toggleBlockquote().run(), }, { title: "Code", description: "Capture a code snippet.", searchTerms: ["codeblock"], icon: IconCode, command: ({ editor, range }: CommandProps) => editor.chain().focus().deleteRange(range).toggleCodeBlock().run(), }, { title: "Image", description: "Upload an image from your computer.", searchTerms: ["photo", "picture", "media"], icon: IconPhoto, command: ({ editor, range }) => { editor.chain().focus().deleteRange(range).run(); const pageId = editor.storage?.pageId; if (!pageId) return; // upload image const input = document.createElement("input"); input.type = "file"; input.accept = "image/*"; input.onchange = async () => { if (input.files?.length) { const file = input.files[0]; const pos = editor.view.state.selection.from; uploadImageAction(file, editor.view, pos, pageId); } }; input.click(); }, }, { title: "Video", description: "Upload an video from your computer.", searchTerms: ["video", "mp4", "media"], icon: IconMovie, command: ({ editor, range }) => { editor.chain().focus().deleteRange(range).run(); const pageId = editor.storage?.pageId; if (!pageId) return; // upload video const input = document.createElement("input"); input.type = "file"; input.accept = "video/*"; input.onchange = async () => { if (input.files?.length) { const file = input.files[0]; const pos = editor.view.state.selection.from; uploadVideoAction(file, editor.view, pos, pageId); } }; input.click(); }, }, { title: "Table", description: "Insert a table.", searchTerms: ["table", "rows", "columns"], icon: IconTable, command: ({ editor, range }: CommandProps) => editor .chain() .focus() .deleteRange(range) .insertTable({ rows: 3, cols: 3, withHeaderRow: false }) .run(), }, { title: "Toggle block", description: "Insert collapsible block.", searchTerms: ["collapsible", "block", "toggle", "details", "expand"], icon: IconCaretRightFilled, command: ({ editor, range }: CommandProps) => editor.chain().focus().deleteRange(range).toggleDetails().run(), }, { title: "Callout", description: "Insert callout notice.", searchTerms: [ "callout", "notice", "panel", "info", "warning", "success", "error", "danger", ], icon: IconInfoCircle, command: ({ editor, range }: CommandProps) => editor.chain().focus().deleteRange(range).toggleCallout().run(), }, { title: "Math inline", description: "Insert inline math equation.", searchTerms: [ "math", "inline", "mathinline", "inlinemath", "inline math", "equation", "katex", "latex", "tex", ], icon: IconMathFunction, command: ({ editor, range }: CommandProps) => editor .chain() .focus() .deleteRange(range) .setMathInline() .setNodeSelection(range.from) .run(), }, { title: "Math block", description: "Insert math equation", searchTerms: [ "math", "block", "mathblock", "block math", "equation", "katex", "latex", "tex", ], icon: IconMath, command: ({ editor, range }: CommandProps) => editor.chain().focus().deleteRange(range).setMathBlock().run(), }, ], }; export const getSuggestionItems = ({ query, }: { query: string; }): SlashMenuGroupedItemsType => { const search = query.toLowerCase(); const filteredGroups: SlashMenuGroupedItemsType = {}; const fuzzyMatch = (query, target) => { let queryIndex = 0; target = target.toLowerCase(); for (let char of target) { if (query[queryIndex] === char) queryIndex++; if (queryIndex === query.length) return true; } return false; }; for (const [group, items] of Object.entries(CommandGroups)) { const filteredItems = items.filter((item) => { return ( fuzzyMatch(search, item.title) || item.description.toLowerCase().includes(search) || (item.searchTerms && item.searchTerms.some((term: string) => term.includes(search))) ); }); if (filteredItems.length) { filteredGroups[group] = filteredItems; } } return filteredGroups; }; export default getSuggestionItems;