@relevaince/mentions 0.2.1 → 0.3.0
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/README.md +41 -13
- package/dist/index.d.mts +34 -3
- package/dist/index.d.ts +34 -3
- package/dist/index.js +182 -33
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +182 -34
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { useEditor } from "@tiptap/react";
|
|
|
8
8
|
import StarterKit from "@tiptap/starter-kit";
|
|
9
9
|
import Placeholder from "@tiptap/extension-placeholder";
|
|
10
10
|
import { Extension as Extension2 } from "@tiptap/core";
|
|
11
|
+
import { Plugin as Plugin2, PluginKey as PluginKey2 } from "@tiptap/pm/state";
|
|
11
12
|
|
|
12
13
|
// src/core/mentionExtension.ts
|
|
13
14
|
import { mergeAttributes, Node } from "@tiptap/core";
|
|
@@ -40,6 +41,11 @@ var MentionNode = Node.create({
|
|
|
40
41
|
default: null,
|
|
41
42
|
parseHTML: (element) => element.getAttribute("data-type"),
|
|
42
43
|
renderHTML: (attributes) => ({ "data-type": attributes.entityType })
|
|
44
|
+
},
|
|
45
|
+
rootLabel: {
|
|
46
|
+
default: null,
|
|
47
|
+
parseHTML: (element) => element.getAttribute("data-root-label"),
|
|
48
|
+
renderHTML: (attributes) => attributes.rootLabel ? { "data-root-label": attributes.rootLabel } : {}
|
|
43
49
|
}
|
|
44
50
|
};
|
|
45
51
|
},
|
|
@@ -50,13 +56,14 @@ var MentionNode = Node.create({
|
|
|
50
56
|
const entityType = node.attrs.entityType;
|
|
51
57
|
const label = node.attrs.label;
|
|
52
58
|
const prefix = DEFAULT_PREFIXES[entityType] ?? "@";
|
|
59
|
+
const display = `${prefix}${label}`;
|
|
53
60
|
return [
|
|
54
61
|
"span",
|
|
55
62
|
mergeAttributes(HTMLAttributes, {
|
|
56
63
|
"data-mention": "",
|
|
57
64
|
class: "mention-chip"
|
|
58
65
|
}),
|
|
59
|
-
|
|
66
|
+
display
|
|
60
67
|
];
|
|
61
68
|
},
|
|
62
69
|
renderText({ node }) {
|
|
@@ -108,6 +115,7 @@ var suggestionPluginKey = new PluginKey("mentionSuggestion");
|
|
|
108
115
|
function createSuggestionExtension(triggers, callbacksRef) {
|
|
109
116
|
return Extension.create({
|
|
110
117
|
name: "mentionSuggestion",
|
|
118
|
+
priority: 200,
|
|
111
119
|
addProseMirrorPlugins() {
|
|
112
120
|
const editor = this.editor;
|
|
113
121
|
let active = false;
|
|
@@ -136,7 +144,8 @@ function createSuggestionExtension(triggers, callbacksRef) {
|
|
|
136
144
|
attrs: {
|
|
137
145
|
id: attrs.id,
|
|
138
146
|
label: attrs.label,
|
|
139
|
-
entityType: attrs.entityType
|
|
147
|
+
entityType: attrs.entityType,
|
|
148
|
+
rootLabel: attrs.rootLabel ?? null
|
|
140
149
|
}
|
|
141
150
|
},
|
|
142
151
|
{ type: "text", text: " " }
|
|
@@ -238,7 +247,10 @@ function serializeParagraph(node) {
|
|
|
238
247
|
if (!node.content) return "";
|
|
239
248
|
return node.content.map((child) => {
|
|
240
249
|
if (child.type === "mention") {
|
|
241
|
-
const { id, label } = child.attrs ?? {};
|
|
250
|
+
const { id, label, rootLabel } = child.attrs ?? {};
|
|
251
|
+
if (rootLabel != null && rootLabel !== "") {
|
|
252
|
+
return `@${rootLabel}[${label}](${id})`;
|
|
253
|
+
}
|
|
242
254
|
return `@[${label}](${id})`;
|
|
243
255
|
}
|
|
244
256
|
return child.text ?? "";
|
|
@@ -284,7 +296,7 @@ function extractParagraphText(node) {
|
|
|
284
296
|
}
|
|
285
297
|
|
|
286
298
|
// src/core/markdownParser.ts
|
|
287
|
-
var MENTION_RE =
|
|
299
|
+
var MENTION_RE = /@(?:([^\[]+)\[)?\[([^\]]+)\]\((?:([^:)]+):)?([^)]+)\)/g;
|
|
288
300
|
function parseFromMarkdown(markdown) {
|
|
289
301
|
const lines = markdown.split("\n");
|
|
290
302
|
const content = lines.map((line) => ({
|
|
@@ -300,9 +312,10 @@ function parseLine(line) {
|
|
|
300
312
|
let match;
|
|
301
313
|
while ((match = MENTION_RE.exec(line)) !== null) {
|
|
302
314
|
const fullMatch = match[0];
|
|
303
|
-
const
|
|
304
|
-
const
|
|
305
|
-
const
|
|
315
|
+
const rootLabel = match[1] ?? null;
|
|
316
|
+
const label = match[2];
|
|
317
|
+
const entityType = match[3] ?? "unknown";
|
|
318
|
+
const id = match[4];
|
|
306
319
|
if (match.index > lastIndex) {
|
|
307
320
|
nodes.push({
|
|
308
321
|
type: "text",
|
|
@@ -314,7 +327,8 @@ function parseLine(line) {
|
|
|
314
327
|
attrs: {
|
|
315
328
|
id,
|
|
316
329
|
label,
|
|
317
|
-
entityType
|
|
330
|
+
entityType,
|
|
331
|
+
rootLabel
|
|
318
332
|
}
|
|
319
333
|
});
|
|
320
334
|
lastIndex = match.index + fullMatch.length;
|
|
@@ -330,6 +344,13 @@ function parseLine(line) {
|
|
|
330
344
|
}
|
|
331
345
|
return nodes;
|
|
332
346
|
}
|
|
347
|
+
function extractFromMarkdown(markdown) {
|
|
348
|
+
const doc = parseFromMarkdown(markdown);
|
|
349
|
+
return {
|
|
350
|
+
tokens: extractTokens(doc),
|
|
351
|
+
plainText: extractPlainText(doc)
|
|
352
|
+
};
|
|
353
|
+
}
|
|
333
354
|
|
|
334
355
|
// src/hooks/useMentionsEditor.ts
|
|
335
356
|
function buildOutput(editor) {
|
|
@@ -343,6 +364,7 @@ function buildOutput(editor) {
|
|
|
343
364
|
function createSubmitExtension(onSubmitRef, clearOnSubmitRef) {
|
|
344
365
|
return Extension2.create({
|
|
345
366
|
name: "submitShortcut",
|
|
367
|
+
priority: 150,
|
|
346
368
|
addKeyboardShortcuts() {
|
|
347
369
|
return {
|
|
348
370
|
"Mod-Enter": () => {
|
|
@@ -358,22 +380,35 @@ function createSubmitExtension(onSubmitRef, clearOnSubmitRef) {
|
|
|
358
380
|
}
|
|
359
381
|
});
|
|
360
382
|
}
|
|
383
|
+
var enterSubmitPluginKey = new PluginKey2("enterSubmit");
|
|
361
384
|
function createEnterExtension(onSubmitRef, clearOnSubmitRef) {
|
|
362
385
|
return Extension2.create({
|
|
363
386
|
name: "enterSubmit",
|
|
364
|
-
priority:
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
387
|
+
priority: 150,
|
|
388
|
+
addProseMirrorPlugins() {
|
|
389
|
+
const editor = this.editor;
|
|
390
|
+
return [
|
|
391
|
+
new Plugin2({
|
|
392
|
+
key: enterSubmitPluginKey,
|
|
393
|
+
props: {
|
|
394
|
+
handleKeyDown(_view, event) {
|
|
395
|
+
if (event.key !== "Enter") return false;
|
|
396
|
+
if (event.shiftKey) {
|
|
397
|
+
editor.commands.splitBlock();
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
if (event.metaKey || event.ctrlKey) return false;
|
|
401
|
+
if (onSubmitRef.current) {
|
|
402
|
+
onSubmitRef.current(buildOutput(editor));
|
|
403
|
+
if (clearOnSubmitRef.current) {
|
|
404
|
+
editor.commands.clearContent(true);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return true;
|
|
372
408
|
}
|
|
373
409
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
};
|
|
410
|
+
})
|
|
411
|
+
];
|
|
377
412
|
}
|
|
378
413
|
});
|
|
379
414
|
}
|
|
@@ -426,10 +461,12 @@ function useMentionsEditor({
|
|
|
426
461
|
bulletList: false,
|
|
427
462
|
orderedList: false,
|
|
428
463
|
listItem: false,
|
|
429
|
-
horizontalRule: false
|
|
464
|
+
horizontalRule: false,
|
|
465
|
+
hardBreak: false
|
|
430
466
|
}),
|
|
431
467
|
Placeholder.configure({
|
|
432
|
-
placeholder: placeholder ?? "Type a message..."
|
|
468
|
+
placeholder: placeholder ?? "Type a message...",
|
|
469
|
+
showOnlyCurrent: false
|
|
433
470
|
}),
|
|
434
471
|
MentionNode,
|
|
435
472
|
suggestionExtension,
|
|
@@ -497,10 +534,17 @@ function useSuggestion(providers) {
|
|
|
497
534
|
);
|
|
498
535
|
const providerRef = useRef2(null);
|
|
499
536
|
const fetchItems = useCallback2(
|
|
500
|
-
async (provider, query, parent) => {
|
|
537
|
+
async (provider, query, parent, useSearchAll) => {
|
|
501
538
|
setUIState((prev) => ({ ...prev, loading: true, state: "loading" }));
|
|
502
539
|
try {
|
|
503
|
-
|
|
540
|
+
let items;
|
|
541
|
+
if (useSearchAll && provider.searchAll) {
|
|
542
|
+
items = await provider.searchAll(query);
|
|
543
|
+
} else if (parent && provider.getChildren) {
|
|
544
|
+
items = await provider.getChildren(parent, query);
|
|
545
|
+
} else {
|
|
546
|
+
items = await provider.getRootItems(query);
|
|
547
|
+
}
|
|
504
548
|
setUIState((prev) => ({
|
|
505
549
|
...prev,
|
|
506
550
|
items,
|
|
@@ -537,7 +581,11 @@ function useSuggestion(providers) {
|
|
|
537
581
|
trigger: props.trigger,
|
|
538
582
|
query: props.query
|
|
539
583
|
});
|
|
540
|
-
|
|
584
|
+
if (props.query.trim() && provider.searchAll) {
|
|
585
|
+
fetchItems(provider, props.query, void 0, true);
|
|
586
|
+
} else {
|
|
587
|
+
fetchItems(provider, props.query);
|
|
588
|
+
}
|
|
541
589
|
},
|
|
542
590
|
[fetchItems]
|
|
543
591
|
);
|
|
@@ -546,12 +594,25 @@ function useSuggestion(providers) {
|
|
|
546
594
|
const provider = providerRef.current;
|
|
547
595
|
if (!provider) return;
|
|
548
596
|
commandRef.current = props.command;
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
597
|
+
const current = stateRef.current;
|
|
598
|
+
if (current.breadcrumbs.length > 0) {
|
|
599
|
+
setUIState((prev) => ({
|
|
600
|
+
...prev,
|
|
601
|
+
breadcrumbs: [],
|
|
602
|
+
clientRect: props.clientRect,
|
|
603
|
+
query: props.query,
|
|
604
|
+
activeIndex: 0
|
|
605
|
+
}));
|
|
606
|
+
} else {
|
|
607
|
+
setUIState((prev) => ({
|
|
608
|
+
...prev,
|
|
609
|
+
clientRect: props.clientRect,
|
|
610
|
+
query: props.query
|
|
611
|
+
}));
|
|
612
|
+
}
|
|
613
|
+
if (props.query.trim() && provider.searchAll) {
|
|
614
|
+
fetchItems(provider, props.query, void 0, true);
|
|
615
|
+
} else {
|
|
555
616
|
fetchItems(provider, props.query);
|
|
556
617
|
}
|
|
557
618
|
},
|
|
@@ -593,10 +654,12 @@ function useSuggestion(providers) {
|
|
|
593
654
|
return;
|
|
594
655
|
}
|
|
595
656
|
if (commandRef.current) {
|
|
657
|
+
const rootLabel = current.breadcrumbs.length > 0 ? current.breadcrumbs[0].label : selected.rootLabel ?? null;
|
|
596
658
|
commandRef.current({
|
|
597
659
|
id: selected.id,
|
|
598
660
|
label: selected.label,
|
|
599
|
-
entityType: selected.type
|
|
661
|
+
entityType: selected.type,
|
|
662
|
+
rootLabel
|
|
600
663
|
});
|
|
601
664
|
}
|
|
602
665
|
},
|
|
@@ -625,6 +688,18 @@ function useSuggestion(providers) {
|
|
|
625
688
|
const close = useCallback2(() => {
|
|
626
689
|
setUIState(IDLE_STATE);
|
|
627
690
|
}, []);
|
|
691
|
+
const searchNested = useCallback2(
|
|
692
|
+
(query) => {
|
|
693
|
+
const provider = providerRef.current;
|
|
694
|
+
if (!provider) return;
|
|
695
|
+
const current = stateRef.current;
|
|
696
|
+
const parent = current.breadcrumbs[current.breadcrumbs.length - 1];
|
|
697
|
+
if (parent) {
|
|
698
|
+
fetchItems(provider, query, parent);
|
|
699
|
+
}
|
|
700
|
+
},
|
|
701
|
+
[fetchItems]
|
|
702
|
+
);
|
|
628
703
|
const onKeyDown = useCallback2(
|
|
629
704
|
({ event }) => {
|
|
630
705
|
const current = stateRef.current;
|
|
@@ -684,13 +759,14 @@ function useSuggestion(providers) {
|
|
|
684
759
|
navigateDown,
|
|
685
760
|
select,
|
|
686
761
|
goBack,
|
|
687
|
-
close
|
|
762
|
+
close,
|
|
763
|
+
searchNested
|
|
688
764
|
};
|
|
689
765
|
return { uiState, actions, callbacksRef };
|
|
690
766
|
}
|
|
691
767
|
|
|
692
768
|
// src/components/SuggestionList.tsx
|
|
693
|
-
import { useEffect as useEffect2, useRef as useRef3 } from "react";
|
|
769
|
+
import { useEffect as useEffect2, useRef as useRef3, useState as useState2 } from "react";
|
|
694
770
|
|
|
695
771
|
// src/utils/ariaHelpers.ts
|
|
696
772
|
function comboboxAttrs(expanded, listboxId) {
|
|
@@ -730,10 +806,29 @@ function SuggestionList({
|
|
|
730
806
|
onSelect,
|
|
731
807
|
onHover,
|
|
732
808
|
onGoBack,
|
|
809
|
+
onSearchNested,
|
|
810
|
+
onNavigateUp,
|
|
811
|
+
onNavigateDown,
|
|
812
|
+
onClose,
|
|
733
813
|
renderItem
|
|
734
814
|
}) {
|
|
735
815
|
const listRef = useRef3(null);
|
|
816
|
+
const searchInputRef = useRef3(null);
|
|
736
817
|
const depth = breadcrumbs.length;
|
|
818
|
+
const [nestedQuery, setNestedQuery] = useState2("");
|
|
819
|
+
const breadcrumbKey = breadcrumbs.map((b) => b.id).join("/");
|
|
820
|
+
const prevBreadcrumbKey = useRef3(breadcrumbKey);
|
|
821
|
+
useEffect2(() => {
|
|
822
|
+
if (prevBreadcrumbKey.current !== breadcrumbKey) {
|
|
823
|
+
setNestedQuery("");
|
|
824
|
+
prevBreadcrumbKey.current = breadcrumbKey;
|
|
825
|
+
}
|
|
826
|
+
}, [breadcrumbKey]);
|
|
827
|
+
useEffect2(() => {
|
|
828
|
+
if (breadcrumbs.length > 0 && searchInputRef.current) {
|
|
829
|
+
requestAnimationFrame(() => searchInputRef.current?.focus());
|
|
830
|
+
}
|
|
831
|
+
}, [breadcrumbKey, breadcrumbs.length]);
|
|
737
832
|
useEffect2(() => {
|
|
738
833
|
if (!listRef.current) return;
|
|
739
834
|
const active = listRef.current.querySelector('[aria-selected="true"]');
|
|
@@ -741,6 +836,35 @@ function SuggestionList({
|
|
|
741
836
|
}, [activeIndex]);
|
|
742
837
|
const style = usePopoverPosition(clientRect);
|
|
743
838
|
if (items.length === 0 && !loading) return null;
|
|
839
|
+
const handleSearchKeyDown = (e) => {
|
|
840
|
+
switch (e.key) {
|
|
841
|
+
case "ArrowDown":
|
|
842
|
+
e.preventDefault();
|
|
843
|
+
onNavigateDown?.();
|
|
844
|
+
break;
|
|
845
|
+
case "ArrowUp":
|
|
846
|
+
e.preventDefault();
|
|
847
|
+
onNavigateUp?.();
|
|
848
|
+
break;
|
|
849
|
+
case "Enter": {
|
|
850
|
+
e.preventDefault();
|
|
851
|
+
e.stopPropagation();
|
|
852
|
+
const selectedItem = items[activeIndex];
|
|
853
|
+
if (selectedItem) onSelect(selectedItem);
|
|
854
|
+
break;
|
|
855
|
+
}
|
|
856
|
+
case "Escape":
|
|
857
|
+
e.preventDefault();
|
|
858
|
+
onClose?.();
|
|
859
|
+
break;
|
|
860
|
+
case "Backspace":
|
|
861
|
+
if (nestedQuery === "") {
|
|
862
|
+
e.preventDefault();
|
|
863
|
+
onGoBack();
|
|
864
|
+
}
|
|
865
|
+
break;
|
|
866
|
+
}
|
|
867
|
+
};
|
|
744
868
|
return /* @__PURE__ */ jsxs(
|
|
745
869
|
"div",
|
|
746
870
|
{
|
|
@@ -765,6 +889,24 @@ function SuggestionList({
|
|
|
765
889
|
crumb.label
|
|
766
890
|
] }, crumb.id))
|
|
767
891
|
] }),
|
|
892
|
+
breadcrumbs.length > 0 && /* @__PURE__ */ jsx("div", { "data-suggestion-search": "", children: /* @__PURE__ */ jsx(
|
|
893
|
+
"input",
|
|
894
|
+
{
|
|
895
|
+
ref: searchInputRef,
|
|
896
|
+
type: "text",
|
|
897
|
+
"data-suggestion-search-input": "",
|
|
898
|
+
placeholder: "Search...",
|
|
899
|
+
value: nestedQuery,
|
|
900
|
+
onChange: (e) => {
|
|
901
|
+
const q = e.target.value;
|
|
902
|
+
setNestedQuery(q);
|
|
903
|
+
onSearchNested?.(q);
|
|
904
|
+
},
|
|
905
|
+
onKeyDown: handleSearchKeyDown,
|
|
906
|
+
autoComplete: "off",
|
|
907
|
+
spellCheck: false
|
|
908
|
+
}
|
|
909
|
+
) }),
|
|
768
910
|
loading && /* @__PURE__ */ jsx("div", { "data-suggestion-loading": "", children: "Loading..." }),
|
|
769
911
|
!loading && /* @__PURE__ */ jsx("div", { ...listboxAttrs(LISTBOX_ID, `${trigger ?? ""} suggestions`), children: items.map((item, index) => {
|
|
770
912
|
const isActive = index === activeIndex;
|
|
@@ -847,7 +989,8 @@ var MentionsInput = forwardRef(
|
|
|
847
989
|
[clear, setContent, focus]
|
|
848
990
|
);
|
|
849
991
|
const isExpanded = uiState.state !== "idle";
|
|
850
|
-
const handleHover = useCallback3((
|
|
992
|
+
const handleHover = useCallback3((index) => {
|
|
993
|
+
void index;
|
|
851
994
|
}, []);
|
|
852
995
|
return /* @__PURE__ */ jsxs2(
|
|
853
996
|
"div",
|
|
@@ -871,6 +1014,10 @@ var MentionsInput = forwardRef(
|
|
|
871
1014
|
onSelect: (item) => actions.select(item),
|
|
872
1015
|
onHover: handleHover,
|
|
873
1016
|
onGoBack: actions.goBack,
|
|
1017
|
+
onSearchNested: actions.searchNested,
|
|
1018
|
+
onNavigateUp: actions.navigateUp,
|
|
1019
|
+
onNavigateDown: actions.navigateDown,
|
|
1020
|
+
onClose: actions.close,
|
|
874
1021
|
renderItem
|
|
875
1022
|
}
|
|
876
1023
|
)
|
|
@@ -881,6 +1028,7 @@ var MentionsInput = forwardRef(
|
|
|
881
1028
|
);
|
|
882
1029
|
export {
|
|
883
1030
|
MentionsInput,
|
|
1031
|
+
extractFromMarkdown,
|
|
884
1032
|
parseFromMarkdown,
|
|
885
1033
|
serializeToMarkdown
|
|
886
1034
|
};
|