@sybilion/uilib 1.3.32 → 1.3.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/ui/Chat/ChatChrome/ChatChrome.js +2 -2
- package/dist/esm/components/ui/Chat/ChatPrompt/ChatPrompt.helpers.js +18 -4
- package/dist/esm/components/ui/Chat/ChatPrompt/ChatPrompt.js +39 -52
- package/dist/esm/components/ui/Chat/ChatPrompt/ChatPrompt.styl.js +2 -2
- package/dist/esm/components/ui/Chat/ChatPrompt/ChatPromptComposer.js +25 -0
- package/dist/esm/components/ui/Chat/ChatPrompt/chatPromptDoc.js +17 -0
- package/dist/esm/components/ui/Chat/ChatPrompt/useChatPromptEditor.js +163 -0
- package/dist/esm/components/ui/Tooltip/Tooltip.js +1 -1
- package/dist/esm/components/ui/Tooltip/Tooltip.styl.js +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/tiptap/slash-mention/SlashSuggestionList.js +53 -0
- package/dist/esm/tiptap/slash-mention/SlashSuggestionList.styl.js +7 -0
- package/dist/esm/tiptap/slash-mention/createSlashMentionExtension.js +151 -0
- package/dist/esm/tiptap/slash-mention/defaultChatSlashItems.js +12 -0
- package/dist/esm/types/src/components/ui/Chat/Chat.types.d.ts +3 -0
- package/dist/esm/types/src/components/ui/Chat/ChatChrome/ChatChrome.d.ts +1 -1
- package/dist/esm/types/src/components/ui/Chat/ChatChrome/ChatChrome.types.d.ts +5 -0
- package/dist/esm/types/src/components/ui/Chat/ChatPrompt/ChatPrompt.d.ts +1 -1
- package/dist/esm/types/src/components/ui/Chat/ChatPrompt/ChatPrompt.helpers.d.ts +6 -0
- package/dist/esm/types/src/components/ui/Chat/ChatPrompt/ChatPromptComposer.d.ts +13 -0
- package/dist/esm/types/src/components/ui/Chat/ChatPrompt/chatPromptDoc.d.ts +3 -0
- package/dist/esm/types/src/components/ui/Chat/ChatPrompt/useChatPromptEditor.d.ts +20 -0
- package/dist/esm/types/src/components/ui/Chat/index.d.ts +1 -0
- package/dist/esm/types/src/components/ui/Input/Input.d.ts +1 -1
- package/dist/esm/types/src/docs/pages/ChatSlashCommandsPage.d.ts +1 -0
- package/dist/esm/types/src/index.d.ts +1 -0
- package/dist/esm/types/src/tiptap/slash-mention/SlashSuggestionList.d.ts +12 -0
- package/dist/esm/types/src/tiptap/slash-mention/createSlashMentionExtension.d.ts +21 -0
- package/dist/esm/types/src/tiptap/slash-mention/defaultChatSlashItems.d.ts +4 -0
- package/dist/esm/types/src/tiptap/slash-mention/index.d.ts +5 -0
- package/dist/esm/types/src/tiptap/slash-mention/types.d.ts +25 -0
- package/package.json +15 -1
- package/src/components/ui/Chat/Chat.types.ts +4 -0
- package/src/components/ui/Chat/ChatChrome/ChatChrome.tsx +4 -0
- package/src/components/ui/Chat/ChatChrome/ChatChrome.types.ts +5 -0
- package/src/components/ui/Chat/ChatPrompt/ChatPrompt.helpers.ts +43 -0
- package/src/components/ui/Chat/ChatPrompt/ChatPrompt.styl +33 -5
- package/src/components/ui/Chat/ChatPrompt/ChatPrompt.styl.d.ts +2 -1
- package/src/components/ui/Chat/ChatPrompt/ChatPrompt.tsx +50 -106
- package/src/components/ui/Chat/ChatPrompt/ChatPromptComposer.tsx +93 -0
- package/src/components/ui/Chat/ChatPrompt/chatPromptDoc.ts +18 -0
- package/src/components/ui/Chat/ChatPrompt/useChatPromptEditor.ts +214 -0
- package/src/components/ui/Chat/index.ts +1 -0
- package/src/components/ui/Tooltip/Tooltip.styl +1 -0
- package/src/components/ui/Tooltip/Tooltip.tsx +1 -1
- package/src/docs/pages/ChatSlashCommandsPage.tsx +139 -0
- package/src/docs/pages/TooltipPage.tsx +1 -1
- package/src/docs/registry.ts +6 -0
- package/src/index.ts +1 -0
- package/src/tiptap/slash-mention/SlashSuggestionList.styl +48 -0
- package/src/tiptap/slash-mention/SlashSuggestionList.styl.d.ts +11 -0
- package/src/tiptap/slash-mention/SlashSuggestionList.tsx +109 -0
- package/src/tiptap/slash-mention/createSlashMentionExtension.ts +217 -0
- package/src/tiptap/slash-mention/defaultChatSlashItems.ts +18 -0
- package/src/tiptap/slash-mention/index.ts +16 -0
- package/src/tiptap/slash-mention/types.ts +29 -0
package/src/docs/registry.ts
CHANGED
|
@@ -109,6 +109,12 @@ export const DOC_REGISTRY: DocEntry[] = [
|
|
|
109
109
|
section: 'Chat',
|
|
110
110
|
load: () => import('./pages/ChatPage'),
|
|
111
111
|
},
|
|
112
|
+
{
|
|
113
|
+
slug: 'chat-slash-commands',
|
|
114
|
+
title: 'Chat slash commands',
|
|
115
|
+
section: 'Chat',
|
|
116
|
+
load: () => import('./pages/ChatSlashCommandsPage'),
|
|
117
|
+
},
|
|
112
118
|
{
|
|
113
119
|
slug: 'chat-user-csv-attachment',
|
|
114
120
|
title: 'Chat user CSV attachment',
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ export { DEFAULT_THEME_ACTIVE_COLOR } from './docs/lib/theme';
|
|
|
11
11
|
export * from './sybilion-auth';
|
|
12
12
|
export * from './types/sybilionDatasetSnapshots';
|
|
13
13
|
export * from './contexts/chat-context';
|
|
14
|
+
export * from './tiptap/slash-mention';
|
|
14
15
|
export * from './types/chat-api.types';
|
|
15
16
|
export * from './components/ui/AnalysesSelector';
|
|
16
17
|
export * from './components/ui/AnalysisLineIcon';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
@import 'lib/theme.styl';
|
|
2
|
+
|
|
3
|
+
.root
|
|
4
|
+
display flex
|
|
5
|
+
flex-direction column
|
|
6
|
+
min-width 220px
|
|
7
|
+
max-width min(340px, 90vw)
|
|
8
|
+
max-height min(260px, 40vh)
|
|
9
|
+
overflow-y auto
|
|
10
|
+
padding var(--p-1)
|
|
11
|
+
margin 0
|
|
12
|
+
list-style none
|
|
13
|
+
background var(--popover, var(--card))
|
|
14
|
+
color var(--foreground)
|
|
15
|
+
border 1px solid var(--border)
|
|
16
|
+
border-radius var(--radius-lg, 8px)
|
|
17
|
+
box-shadow 0 8px 24px rgba(0, 0, 0, 0.12)
|
|
18
|
+
font-size var(--text-sm)
|
|
19
|
+
|
|
20
|
+
:global(.dark) &
|
|
21
|
+
box-shadow 0 8px 24px rgba(0, 0, 0, 0.5)
|
|
22
|
+
|
|
23
|
+
.item
|
|
24
|
+
display flex
|
|
25
|
+
flex-direction column
|
|
26
|
+
align-items flex-start
|
|
27
|
+
width 100%
|
|
28
|
+
padding var(--p-1) var(--p-2)
|
|
29
|
+
margin 0
|
|
30
|
+
border none
|
|
31
|
+
border-radius var(--radius-md, 6px)
|
|
32
|
+
background transparent
|
|
33
|
+
color inherit
|
|
34
|
+
text-align left
|
|
35
|
+
cursor pointer
|
|
36
|
+
|
|
37
|
+
&:hover
|
|
38
|
+
background var(--muted)
|
|
39
|
+
|
|
40
|
+
.itemHighlighted
|
|
41
|
+
background var(--muted)
|
|
42
|
+
|
|
43
|
+
.itemLabel
|
|
44
|
+
font-weight 600
|
|
45
|
+
|
|
46
|
+
.itemDesc
|
|
47
|
+
font-size var(--text-xs)
|
|
48
|
+
color var(--muted-foreground)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// This file is automatically generated.
|
|
2
|
+
// Please do not change this file!
|
|
3
|
+
interface CssExports {
|
|
4
|
+
'item': string;
|
|
5
|
+
'itemDesc': string;
|
|
6
|
+
'itemHighlighted': string;
|
|
7
|
+
'itemLabel': string;
|
|
8
|
+
'root': string;
|
|
9
|
+
}
|
|
10
|
+
export const cssExports: CssExports;
|
|
11
|
+
export default cssExports;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
import type { RefObject } from 'react';
|
|
3
|
+
import { useCallback, useEffect, useImperativeHandle, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import S from './SlashSuggestionList.styl';
|
|
6
|
+
import type { SlashCommandItem } from './types';
|
|
7
|
+
|
|
8
|
+
export type SlashSuggestionListHandle = {
|
|
9
|
+
onKeyboardEvent: (event: KeyboardEvent) => boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type SlashSuggestionListInnerProps = {
|
|
13
|
+
items: SlashCommandItem[];
|
|
14
|
+
command: (item: SlashCommandItem) => void;
|
|
15
|
+
listHandleRef: RefObject<SlashSuggestionListHandle | null>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function SlashSuggestionListInner({
|
|
19
|
+
items,
|
|
20
|
+
command,
|
|
21
|
+
listHandleRef,
|
|
22
|
+
}: SlashSuggestionListInnerProps) {
|
|
23
|
+
const [selected, setSelected] = useState(0);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
setSelected(s => Math.min(s, Math.max(items.length - 1, 0)));
|
|
27
|
+
}, [items.length]);
|
|
28
|
+
|
|
29
|
+
const safeSel = Math.min(selected, Math.max(items.length - 1, 0));
|
|
30
|
+
|
|
31
|
+
const onPick = useCallback(
|
|
32
|
+
(index: number) => {
|
|
33
|
+
const item = items[index];
|
|
34
|
+
if (!item) return;
|
|
35
|
+
command(item);
|
|
36
|
+
},
|
|
37
|
+
[command, items],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const onKeyboardEvent = useCallback(
|
|
41
|
+
(event: KeyboardEvent): boolean => {
|
|
42
|
+
if (items.length === 0) return false;
|
|
43
|
+
if (event.key === 'ArrowDown') {
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
setSelected(s => Math.min(s + 1, items.length - 1));
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
if (event.key === 'ArrowUp') {
|
|
49
|
+
event.preventDefault();
|
|
50
|
+
setSelected(s => Math.max(s - 1, 0));
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
if (event.key === 'Enter') {
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
const max = Math.max(items.length - 1, 0);
|
|
56
|
+
const idx = Math.min(selected, max);
|
|
57
|
+
const item = items[idx];
|
|
58
|
+
if (item) command(item);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
},
|
|
63
|
+
[command, items, selected],
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
useImperativeHandle(listHandleRef, () => ({ onKeyboardEvent }), [
|
|
67
|
+
onKeyboardEvent,
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div className={S.root} role="listbox" aria-label="Slash commands">
|
|
72
|
+
{items.map((item, idx) => (
|
|
73
|
+
<button
|
|
74
|
+
key={`${item.id}-${idx}`}
|
|
75
|
+
type="button"
|
|
76
|
+
role="option"
|
|
77
|
+
aria-selected={idx === safeSel}
|
|
78
|
+
className={cn(S.item, idx === safeSel && S.itemHighlighted)}
|
|
79
|
+
onMouseDown={e => e.preventDefault()}
|
|
80
|
+
onMouseEnter={() => setSelected(idx)}
|
|
81
|
+
onClick={() => onPick(idx)}
|
|
82
|
+
>
|
|
83
|
+
<span className={S.itemLabel}>/{item.label}</span>
|
|
84
|
+
{item.description ? (
|
|
85
|
+
<span className={S.itemDesc}>{item.description}</span>
|
|
86
|
+
) : null}
|
|
87
|
+
</button>
|
|
88
|
+
))}
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Props forwarded from Tiptap suggestion renderer + our wiring. */
|
|
94
|
+
export type SlashSuggestionListProps = {
|
|
95
|
+
items: SlashCommandItem[];
|
|
96
|
+
command: (item: SlashCommandItem) => void;
|
|
97
|
+
listHandleRef: RefObject<SlashSuggestionListHandle | null>;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export function SlashSuggestionList(props: SlashSuggestionListProps) {
|
|
101
|
+
if (props.items.length === 0) return null;
|
|
102
|
+
return (
|
|
103
|
+
<SlashSuggestionListInner
|
|
104
|
+
items={props.items}
|
|
105
|
+
command={props.command}
|
|
106
|
+
listHandleRef={props.listHandleRef}
|
|
107
|
+
/>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import type { MutableRefObject } from 'react';
|
|
2
|
+
|
|
3
|
+
import { type Editor, mergeAttributes } from '@tiptap/core';
|
|
4
|
+
import Mention from '@tiptap/extension-mention';
|
|
5
|
+
import type { EditorState } from '@tiptap/pm/state';
|
|
6
|
+
import { ReactRenderer } from '@tiptap/react';
|
|
7
|
+
import type { SuggestionProps } from '@tiptap/suggestion';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
SlashSuggestionList,
|
|
11
|
+
type SlashSuggestionListHandle,
|
|
12
|
+
} from './SlashSuggestionList';
|
|
13
|
+
import { filterSlashItems } from './defaultChatSlashItems';
|
|
14
|
+
import type {
|
|
15
|
+
CreateSlashMentionExtensionOptions,
|
|
16
|
+
SlashCommandItem,
|
|
17
|
+
} from './types';
|
|
18
|
+
|
|
19
|
+
export function slashMentionSuggestionRender(
|
|
20
|
+
uiRef: MutableRefObject<SlashSuggestionListHandle | null>,
|
|
21
|
+
): {
|
|
22
|
+
onStart?: (
|
|
23
|
+
props: SuggestionProps<SlashCommandItem, SlashCommandItem>,
|
|
24
|
+
) => void;
|
|
25
|
+
onUpdate?: (
|
|
26
|
+
props: SuggestionProps<SlashCommandItem, SlashCommandItem>,
|
|
27
|
+
) => void;
|
|
28
|
+
onExit?: (props: SuggestionProps<SlashCommandItem, SlashCommandItem>) => void;
|
|
29
|
+
onKeyDown?: (p: {
|
|
30
|
+
view: unknown;
|
|
31
|
+
event: KeyboardEvent;
|
|
32
|
+
range: { from: number; to: number };
|
|
33
|
+
}) => boolean;
|
|
34
|
+
} {
|
|
35
|
+
let popup: ReactRenderer<{
|
|
36
|
+
items: SlashCommandItem[];
|
|
37
|
+
command: (item: SlashCommandItem) => void;
|
|
38
|
+
listHandleRef: MutableRefObject<SlashSuggestionListHandle | null>;
|
|
39
|
+
}> | null = null;
|
|
40
|
+
|
|
41
|
+
const place = (
|
|
42
|
+
props: Pick<
|
|
43
|
+
SuggestionProps<SlashCommandItem, SlashCommandItem>,
|
|
44
|
+
'clientRect'
|
|
45
|
+
>,
|
|
46
|
+
) => {
|
|
47
|
+
if (!popup?.element) return;
|
|
48
|
+
const rect = props.clientRect?.();
|
|
49
|
+
if (!rect) return;
|
|
50
|
+
const el = popup.element;
|
|
51
|
+
el.style.left = `${rect.left}px`;
|
|
52
|
+
el.style.top = `${rect.bottom + 4}px`;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
onStart: props => {
|
|
57
|
+
uiRef.current = null;
|
|
58
|
+
popup?.destroy?.();
|
|
59
|
+
popup = new ReactRenderer(SlashSuggestionList, {
|
|
60
|
+
editor: props.editor,
|
|
61
|
+
props: {
|
|
62
|
+
items: props.items,
|
|
63
|
+
command: props.command,
|
|
64
|
+
listHandleRef: uiRef,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
popup.element.style.position = 'fixed';
|
|
68
|
+
popup.element.style.margin = '0';
|
|
69
|
+
popup.element.style.zIndex = '10002';
|
|
70
|
+
document.body.append(popup.element);
|
|
71
|
+
place(props);
|
|
72
|
+
},
|
|
73
|
+
onUpdate: props => {
|
|
74
|
+
if (!popup) return;
|
|
75
|
+
popup.updateProps({
|
|
76
|
+
items: props.items,
|
|
77
|
+
command: props.command,
|
|
78
|
+
listHandleRef: uiRef,
|
|
79
|
+
});
|
|
80
|
+
place(props);
|
|
81
|
+
},
|
|
82
|
+
onExit: () => {
|
|
83
|
+
popup?.destroy();
|
|
84
|
+
popup?.element?.remove?.();
|
|
85
|
+
popup = null;
|
|
86
|
+
uiRef.current = null;
|
|
87
|
+
},
|
|
88
|
+
onKeyDown: ({ event }) =>
|
|
89
|
+
uiRef.current?.onKeyboardEvent(event as KeyboardEvent) ?? false,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function insertDefaultMention(
|
|
94
|
+
editor: Editor,
|
|
95
|
+
range: { from: number; to: number },
|
|
96
|
+
props: SlashCommandItem,
|
|
97
|
+
slashChar: string,
|
|
98
|
+
): void {
|
|
99
|
+
const nodeAfter = editor.view.state.selection.$to.nodeAfter;
|
|
100
|
+
const extend = nodeAfter?.text?.startsWith(' ') ? 1 : 0;
|
|
101
|
+
const adjusted = extend ? { ...range, to: range.to + extend } : range;
|
|
102
|
+
|
|
103
|
+
editor
|
|
104
|
+
.chain()
|
|
105
|
+
.focus()
|
|
106
|
+
.insertContentAt(adjusted, [
|
|
107
|
+
{
|
|
108
|
+
type: 'mention',
|
|
109
|
+
attrs: {
|
|
110
|
+
id: props.id,
|
|
111
|
+
label: props.label,
|
|
112
|
+
mentionSuggestionChar: slashChar,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{ type: 'text', text: ' ' },
|
|
116
|
+
])
|
|
117
|
+
.run();
|
|
118
|
+
|
|
119
|
+
queueMicrotask(() => {
|
|
120
|
+
editor.view.dom.ownerDocument?.defaultView
|
|
121
|
+
?.getSelection?.()
|
|
122
|
+
?.collapseToEnd();
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function allowSlashTrigger({
|
|
127
|
+
state,
|
|
128
|
+
range,
|
|
129
|
+
}: {
|
|
130
|
+
editor: Editor;
|
|
131
|
+
state: EditorState;
|
|
132
|
+
range: { from: number; to: number };
|
|
133
|
+
}) {
|
|
134
|
+
const type = state.schema.nodes['mention'];
|
|
135
|
+
const $from = state.doc.resolve(range.from);
|
|
136
|
+
if (!type || !$from.parent.type.contentMatch.matchType(type)) return false;
|
|
137
|
+
if ($from.parentOffset === 0) return true;
|
|
138
|
+
const before = state.doc.textBetween(range.from - 1, range.from);
|
|
139
|
+
return /\s/.test(before);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export type CreateSlashMentionExtensionConfiguredOptions =
|
|
143
|
+
CreateSlashMentionExtensionOptions & {
|
|
144
|
+
onSuggestionUiActiveChange?: (active: boolean) => void;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export function createSlashMentionExtension({
|
|
148
|
+
items: resolvedItems,
|
|
149
|
+
slashChar = '/',
|
|
150
|
+
pluginKey,
|
|
151
|
+
onItemCommand,
|
|
152
|
+
onSuggestionUiActiveChange,
|
|
153
|
+
}: CreateSlashMentionExtensionConfiguredOptions) {
|
|
154
|
+
const uiRef: MutableRefObject<SlashSuggestionListHandle | null> = {
|
|
155
|
+
current: null,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
return Mention.configure({
|
|
159
|
+
renderText({ node }) {
|
|
160
|
+
return `/${node.attrs.id as string}`;
|
|
161
|
+
},
|
|
162
|
+
renderHTML({ options, node, suggestion }) {
|
|
163
|
+
const suggestionChar = suggestion?.char ?? slashChar;
|
|
164
|
+
const id =
|
|
165
|
+
typeof node.attrs.id === 'string'
|
|
166
|
+
? node.attrs.id
|
|
167
|
+
: String(node.attrs.id ?? '');
|
|
168
|
+
return [
|
|
169
|
+
'span',
|
|
170
|
+
mergeAttributes(
|
|
171
|
+
{
|
|
172
|
+
'data-type': 'mention',
|
|
173
|
+
'data-slash-command': id,
|
|
174
|
+
class: 'slash-mention',
|
|
175
|
+
},
|
|
176
|
+
options.HTMLAttributes,
|
|
177
|
+
),
|
|
178
|
+
`${suggestionChar}${id}`,
|
|
179
|
+
];
|
|
180
|
+
},
|
|
181
|
+
suggestion: {
|
|
182
|
+
char: slashChar,
|
|
183
|
+
pluginKey,
|
|
184
|
+
allowedPrefixes: [' ', '\n', '\t', '\r'],
|
|
185
|
+
allow: props => allowSlashTrigger(props),
|
|
186
|
+
items: ({ query }) =>
|
|
187
|
+
Promise.resolve(filterSlashItems(resolvedItems, query)),
|
|
188
|
+
command: ({ editor, range, props }) => {
|
|
189
|
+
const item = props as SlashCommandItem;
|
|
190
|
+
if (onItemCommand?.({ editor, range, item }) === true) {
|
|
191
|
+
queueMicrotask(() => {
|
|
192
|
+
editor.view.dom.ownerDocument?.defaultView
|
|
193
|
+
?.getSelection?.()
|
|
194
|
+
?.collapseToEnd();
|
|
195
|
+
});
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
insertDefaultMention(editor, range, item, slashChar);
|
|
199
|
+
return null;
|
|
200
|
+
},
|
|
201
|
+
render: () => {
|
|
202
|
+
const menu = slashMentionSuggestionRender(uiRef);
|
|
203
|
+
return {
|
|
204
|
+
...menu,
|
|
205
|
+
onStart: props => {
|
|
206
|
+
onSuggestionUiActiveChange?.(true);
|
|
207
|
+
menu.onStart?.(props);
|
|
208
|
+
},
|
|
209
|
+
onExit: props => {
|
|
210
|
+
menu.onExit?.(props);
|
|
211
|
+
onSuggestionUiActiveChange?.(false);
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SlashCommandItem } from './types';
|
|
2
|
+
|
|
3
|
+
/** Empty default: pass `slashCommandItems` from the app to enable `/` palette. */
|
|
4
|
+
export const DEFAULT_CHAT_SLASH_ITEMS: SlashCommandItem[] = [];
|
|
5
|
+
|
|
6
|
+
export function filterSlashItems(
|
|
7
|
+
items: SlashCommandItem[],
|
|
8
|
+
query: string,
|
|
9
|
+
): SlashCommandItem[] {
|
|
10
|
+
const q = query.trim().toLowerCase();
|
|
11
|
+
if (!q) return items;
|
|
12
|
+
return items.filter(
|
|
13
|
+
item =>
|
|
14
|
+
item.id.toLowerCase().includes(q) ||
|
|
15
|
+
item.label.toLowerCase().includes(q) ||
|
|
16
|
+
(item.description?.toLowerCase().includes(q) ?? false),
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
SlashCommandItem,
|
|
3
|
+
SlashOnItemCommand,
|
|
4
|
+
SlashItemCommandContext,
|
|
5
|
+
CreateSlashMentionExtensionOptions,
|
|
6
|
+
} from './types';
|
|
7
|
+
export {
|
|
8
|
+
DEFAULT_CHAT_SLASH_ITEMS,
|
|
9
|
+
filterSlashItems,
|
|
10
|
+
} from './defaultChatSlashItems';
|
|
11
|
+
export {
|
|
12
|
+
createSlashMentionExtension,
|
|
13
|
+
slashMentionSuggestionRender,
|
|
14
|
+
} from './createSlashMentionExtension';
|
|
15
|
+
export type { CreateSlashMentionExtensionConfiguredOptions } from './createSlashMentionExtension';
|
|
16
|
+
export type { SlashSuggestionListHandle } from './SlashSuggestionList';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Editor, Range } from '@tiptap/core';
|
|
2
|
+
|
|
3
|
+
export type SlashCommandItem = {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type SlashItemCommandContext = {
|
|
10
|
+
editor: Editor;
|
|
11
|
+
range: Range;
|
|
12
|
+
item: SlashCommandItem;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* If provided, run before default mention insertion. Return true to skip inserting a mention node.
|
|
17
|
+
*/
|
|
18
|
+
export type SlashOnItemCommand = (ctx: SlashItemCommandContext) => boolean;
|
|
19
|
+
|
|
20
|
+
export type CreateSlashMentionExtensionOptions = {
|
|
21
|
+
/** Items shown in the slash menu (filtered by query after `/`). */
|
|
22
|
+
items: SlashCommandItem[];
|
|
23
|
+
/** Trigger character; use `/` for slash commands. */
|
|
24
|
+
slashChar?: string;
|
|
25
|
+
/** Optional custom plugin key when multiple slash editors exist. */
|
|
26
|
+
pluginKey?: import('@tiptap/pm/state').PluginKey;
|
|
27
|
+
/** Custom handler (e.g. insert a block node instead of a mention). */
|
|
28
|
+
onItemCommand?: SlashOnItemCommand;
|
|
29
|
+
};
|