SurfSense/surfsense_web/components/ui/slash-node.tsx

215 lines
5.2 KiB
TypeScript
Raw Normal View History

2026-02-17 12:47:39 +05:30
"use client";
2026-02-17 12:47:39 +05:30
import { SlashInputPlugin } from "@platejs/slash-command/react";
import {
2026-02-17 12:47:39 +05:30
ChevronRightIcon,
Code2Icon,
FileCodeIcon,
Heading1Icon,
Heading2Icon,
Heading3Icon,
InfoIcon,
ListIcon,
ListOrderedIcon,
MinusIcon,
PilcrowIcon,
QuoteIcon,
RadicalIcon,
SquareIcon,
TableIcon,
} from "lucide-react";
import { KEYS } from "platejs";
2026-02-20 22:44:56 -08:00
import type { PlateElementProps } from "platejs/react";
2026-02-17 12:47:39 +05:30
import { PlateElement, useEditorRef } from "platejs/react";
2026-02-20 22:44:56 -08:00
import type * as React from "react";
import { insertBlock, insertInlineElement } from "@/components/editor/transforms";
import {
2026-02-17 12:47:39 +05:30
InlineCombobox,
InlineComboboxContent,
InlineComboboxEmpty,
InlineComboboxGroup,
InlineComboboxGroupLabel,
InlineComboboxInput,
InlineComboboxItem,
} from "@/components/ui/inline-combobox";
interface SlashCommandItem {
2026-02-17 12:47:39 +05:30
icon: React.ReactNode;
keywords: string[];
label: string;
value: string;
onSelect: (editor: any) => void;
}
const slashCommandGroups: { heading: string; items: SlashCommandItem[] }[] = [
2026-02-17 12:47:39 +05:30
{
heading: "Basic Blocks",
items: [
{
icon: <PilcrowIcon />,
keywords: ["paragraph", "text", "plain"],
label: "Text",
value: "text",
onSelect: (editor) => insertBlock(editor, KEYS.p),
},
{
icon: <Heading1Icon />,
keywords: ["title", "h1", "heading"],
label: "Heading 1",
value: "heading1",
onSelect: (editor) => insertBlock(editor, "h1"),
},
{
icon: <Heading2Icon />,
keywords: ["subtitle", "h2", "heading"],
label: "Heading 2",
value: "heading2",
onSelect: (editor) => insertBlock(editor, "h2"),
},
{
icon: <Heading3Icon />,
keywords: ["subtitle", "h3", "heading"],
label: "Heading 3",
value: "heading3",
onSelect: (editor) => insertBlock(editor, "h3"),
},
{
icon: <QuoteIcon />,
keywords: ["citation", "blockquote"],
label: "Quote",
value: "quote",
onSelect: (editor) => insertBlock(editor, KEYS.blockquote),
},
{
icon: <MinusIcon />,
keywords: ["divider", "separator", "line"],
label: "Divider",
value: "divider",
onSelect: (editor) => insertBlock(editor, KEYS.hr),
},
],
},
{
heading: "Lists",
items: [
{
icon: <ListIcon />,
keywords: ["unordered", "ul", "bullet"],
label: "Bulleted list",
value: "bulleted-list",
onSelect: (editor) => insertBlock(editor, KEYS.ul),
},
{
icon: <ListOrderedIcon />,
keywords: ["ordered", "ol", "numbered"],
label: "Numbered list",
value: "numbered-list",
onSelect: (editor) => insertBlock(editor, KEYS.ol),
},
{
icon: <SquareIcon />,
keywords: ["checklist", "task", "checkbox", "todo"],
label: "To-do list",
value: "todo-list",
onSelect: (editor) => insertBlock(editor, KEYS.listTodo),
},
],
},
{
heading: "Advanced",
items: [
{
icon: <TableIcon />,
keywords: ["table", "grid"],
label: "Table",
value: "table",
onSelect: (editor) => insertBlock(editor, KEYS.table),
},
{
icon: <FileCodeIcon />,
keywords: ["code", "codeblock", "snippet"],
label: "Code block",
value: "code-block",
onSelect: (editor) => insertBlock(editor, KEYS.codeBlock),
},
{
icon: <InfoIcon />,
keywords: ["callout", "note", "info", "warning", "tip"],
label: "Callout",
value: "callout",
onSelect: (editor) => insertBlock(editor, KEYS.callout),
},
{
icon: <ChevronRightIcon />,
keywords: ["toggle", "collapsible", "expand"],
label: "Toggle",
value: "toggle",
onSelect: (editor) => insertBlock(editor, KEYS.toggle),
},
{
icon: <RadicalIcon />,
keywords: ["equation", "math", "formula", "latex"],
label: "Equation",
value: "equation",
onSelect: (editor) => insertInlineElement(editor, KEYS.equation),
},
],
},
{
heading: "Inline",
items: [
{
icon: <Code2Icon />,
keywords: ["link", "url", "href"],
label: "Link",
value: "link",
onSelect: (editor) => insertInlineElement(editor, KEYS.link),
},
],
},
];
2026-02-17 12:47:39 +05:30
export function SlashInputElement({ children, ...props }: PlateElementProps) {
const editor = useEditorRef();
2026-02-17 12:47:39 +05:30
return (
<PlateElement {...props} as="span">
<InlineCombobox element={props.element} trigger="/">
<InlineComboboxInput />
2026-02-17 12:47:39 +05:30
<InlineComboboxContent className="dark:bg-neutral-800 dark:border dark:border-neutral-700">
<InlineComboboxEmpty>No results found.</InlineComboboxEmpty>
2026-02-17 12:47:39 +05:30
{slashCommandGroups.map(({ heading, items }) => (
<InlineComboboxGroup key={heading}>
<InlineComboboxGroupLabel>{heading}</InlineComboboxGroupLabel>
2026-02-17 12:47:39 +05:30
{items.map(({ icon, keywords, label, value, onSelect }) => (
<InlineComboboxItem
key={value}
className="flex items-center gap-3 px-2 py-1.5"
keywords={keywords}
label={label}
value={value}
group={heading}
onClick={() => {
onSelect(editor);
editor.tf.focus();
}}
>
<span className="flex size-5 items-center justify-center text-muted-foreground">
{icon}
</span>
{label}
</InlineComboboxItem>
))}
</InlineComboboxGroup>
))}
</InlineComboboxContent>
</InlineCombobox>
2026-02-17 12:47:39 +05:30
{children}
</PlateElement>
);
}