Files
kandev/apps/web/components/task/chat/tiptap-slash-command-extension.tsx
T
2026-07-02 16:45:01 +01:00

93 lines
2.4 KiB
TypeScript

"use client";
import { mergeAttributes, Node } from "@tiptap/core";
import { NodeViewWrapper, ReactNodeViewRenderer, type ReactNodeViewProps } from "@tiptap/react";
import { IconSlash } from "@tabler/icons-react";
import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip";
import {
formatSlashCommandDisplayLabel,
formatSlashCommandLabel,
slashCommandAttrsFromElement,
slashCommandHtmlAttributes,
} from "./tiptap-slash-command-utils";
export const SlashCommandNode = Node.create({
name: "slashCommand",
group: "inline",
inline: true,
selectable: false,
atom: true,
addAttributes() {
return {
id: { default: null, rendered: false },
label: { default: null, rendered: false },
commandName: { default: null, rendered: false },
description: { default: null, rendered: false },
};
},
parseHTML() {
return [
{
tag: "span[data-slash-command]",
getAttrs: (element) => slashCommandAttrsFromElement(element),
},
];
},
renderHTML({ node, HTMLAttributes }) {
const label = formatSlashCommandLabel(node.attrs);
return [
"span",
mergeAttributes(
{ "data-slash-command": "" },
HTMLAttributes,
slashCommandHtmlAttributes(node.attrs),
),
label,
];
},
renderText({ node }) {
return formatSlashCommandLabel(node.attrs);
},
addNodeView() {
return ReactNodeViewRenderer(SlashCommandChipView);
},
});
function SlashCommandChipView({ node }: ReactNodeViewProps) {
const label = formatSlashCommandDisplayLabel(node.attrs);
const description = typeof node.attrs.description === "string" ? node.attrs.description : "";
const chip = (
<span
contentEditable={false}
data-testid="slash-command-chip"
className="inline-flex max-w-[180px] items-center gap-1 rounded bg-primary/10 px-1.5 py-0.5 text-xs font-medium text-primary ring-1 ring-inset ring-primary/25 align-baseline"
>
<IconSlash className="h-3 w-3 shrink-0" />
<span className="truncate">{label}</span>
</span>
);
if (!description) {
return (
<NodeViewWrapper as="span" className="inline">
{chip}
</NodeViewWrapper>
);
}
return (
<NodeViewWrapper as="span" className="inline">
<Tooltip>
<TooltipTrigger asChild>{chip}</TooltipTrigger>
<TooltipContent side="top">{description}</TooltipContent>
</Tooltip>
</NodeViewWrapper>
);
}