@relevaince/mentions 0.2.2 → 0.3.1

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/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";
@@ -114,6 +115,7 @@ var suggestionPluginKey = new PluginKey("mentionSuggestion");
114
115
  function createSuggestionExtension(triggers, callbacksRef) {
115
116
  return Extension.create({
116
117
  name: "mentionSuggestion",
118
+ priority: 200,
117
119
  addProseMirrorPlugins() {
118
120
  const editor = this.editor;
119
121
  let active = false;
@@ -297,10 +299,10 @@ function extractParagraphText(node) {
297
299
  var MENTION_RE = /@(?:([^\[]+)\[)?\[([^\]]+)\]\((?:([^:)]+):)?([^)]+)\)/g;
298
300
  function parseFromMarkdown(markdown) {
299
301
  const lines = markdown.split("\n");
300
- const content = lines.map((line) => ({
301
- type: "paragraph",
302
- content: parseLine(line)
303
- }));
302
+ const content = lines.map((line) => {
303
+ const children = parseLine(line);
304
+ return children.length > 0 ? { type: "paragraph", content: children } : { type: "paragraph" };
305
+ });
304
306
  return { type: "doc", content };
305
307
  }
306
308
  function parseLine(line) {
@@ -337,11 +339,15 @@ function parseLine(line) {
337
339
  text: line.slice(lastIndex)
338
340
  });
339
341
  }
340
- if (nodes.length === 0) {
341
- nodes.push({ type: "text", text: "" });
342
- }
343
342
  return nodes;
344
343
  }
344
+ function extractFromMarkdown(markdown) {
345
+ const doc = parseFromMarkdown(markdown);
346
+ return {
347
+ tokens: extractTokens(doc),
348
+ plainText: extractPlainText(doc)
349
+ };
350
+ }
345
351
 
346
352
  // src/hooks/useMentionsEditor.ts
347
353
  function buildOutput(editor) {
@@ -355,6 +361,7 @@ function buildOutput(editor) {
355
361
  function createSubmitExtension(onSubmitRef, clearOnSubmitRef) {
356
362
  return Extension2.create({
357
363
  name: "submitShortcut",
364
+ priority: 150,
358
365
  addKeyboardShortcuts() {
359
366
  return {
360
367
  "Mod-Enter": () => {
@@ -370,22 +377,35 @@ function createSubmitExtension(onSubmitRef, clearOnSubmitRef) {
370
377
  }
371
378
  });
372
379
  }
380
+ var enterSubmitPluginKey = new PluginKey2("enterSubmit");
373
381
  function createEnterExtension(onSubmitRef, clearOnSubmitRef) {
374
382
  return Extension2.create({
375
383
  name: "enterSubmit",
376
- priority: 50,
377
- addKeyboardShortcuts() {
378
- return {
379
- Enter: () => {
380
- if (onSubmitRef.current) {
381
- onSubmitRef.current(buildOutput(this.editor));
382
- if (clearOnSubmitRef.current) {
383
- this.editor.commands.clearContent(true);
384
+ priority: 150,
385
+ addProseMirrorPlugins() {
386
+ const editor = this.editor;
387
+ return [
388
+ new Plugin2({
389
+ key: enterSubmitPluginKey,
390
+ props: {
391
+ handleKeyDown(_view, event) {
392
+ if (event.key !== "Enter") return false;
393
+ if (event.shiftKey) {
394
+ editor.commands.splitBlock();
395
+ return true;
396
+ }
397
+ if (event.metaKey || event.ctrlKey) return false;
398
+ if (onSubmitRef.current) {
399
+ onSubmitRef.current(buildOutput(editor));
400
+ if (clearOnSubmitRef.current) {
401
+ editor.commands.clearContent(true);
402
+ }
403
+ }
404
+ return true;
384
405
  }
385
406
  }
386
- return true;
387
- }
388
- };
407
+ })
408
+ ];
389
409
  }
390
410
  });
391
411
  }
@@ -438,10 +458,12 @@ function useMentionsEditor({
438
458
  bulletList: false,
439
459
  orderedList: false,
440
460
  listItem: false,
441
- horizontalRule: false
461
+ horizontalRule: false,
462
+ hardBreak: false
442
463
  }),
443
464
  Placeholder.configure({
444
- placeholder: placeholder ?? "Type a message..."
465
+ placeholder: placeholder ?? "Type a message...",
466
+ showOnlyCurrent: false
445
467
  }),
446
468
  MentionNode,
447
469
  suggestionExtension,
@@ -509,10 +531,17 @@ function useSuggestion(providers) {
509
531
  );
510
532
  const providerRef = useRef2(null);
511
533
  const fetchItems = useCallback2(
512
- async (provider, query, parent) => {
534
+ async (provider, query, parent, useSearchAll) => {
513
535
  setUIState((prev) => ({ ...prev, loading: true, state: "loading" }));
514
536
  try {
515
- const items = parent && provider.getChildren ? await provider.getChildren(parent, query) : await provider.getRootItems(query);
537
+ let items;
538
+ if (useSearchAll && provider.searchAll) {
539
+ items = await provider.searchAll(query);
540
+ } else if (parent && provider.getChildren) {
541
+ items = await provider.getChildren(parent, query);
542
+ } else {
543
+ items = await provider.getRootItems(query);
544
+ }
516
545
  setUIState((prev) => ({
517
546
  ...prev,
518
547
  items,
@@ -549,7 +578,11 @@ function useSuggestion(providers) {
549
578
  trigger: props.trigger,
550
579
  query: props.query
551
580
  });
552
- fetchItems(provider, props.query);
581
+ if (props.query.trim() && provider.searchAll) {
582
+ fetchItems(provider, props.query, void 0, true);
583
+ } else {
584
+ fetchItems(provider, props.query);
585
+ }
553
586
  },
554
587
  [fetchItems]
555
588
  );
@@ -558,12 +591,25 @@ function useSuggestion(providers) {
558
591
  const provider = providerRef.current;
559
592
  if (!provider) return;
560
593
  commandRef.current = props.command;
561
- setUIState((prev) => ({
562
- ...prev,
563
- clientRect: props.clientRect,
564
- query: props.query
565
- }));
566
- if (stateRef.current.breadcrumbs.length === 0) {
594
+ const current = stateRef.current;
595
+ if (current.breadcrumbs.length > 0) {
596
+ setUIState((prev) => ({
597
+ ...prev,
598
+ breadcrumbs: [],
599
+ clientRect: props.clientRect,
600
+ query: props.query,
601
+ activeIndex: 0
602
+ }));
603
+ } else {
604
+ setUIState((prev) => ({
605
+ ...prev,
606
+ clientRect: props.clientRect,
607
+ query: props.query
608
+ }));
609
+ }
610
+ if (props.query.trim() && provider.searchAll) {
611
+ fetchItems(provider, props.query, void 0, true);
612
+ } else {
567
613
  fetchItems(provider, props.query);
568
614
  }
569
615
  },
@@ -605,7 +651,7 @@ function useSuggestion(providers) {
605
651
  return;
606
652
  }
607
653
  if (commandRef.current) {
608
- const rootLabel = current.breadcrumbs.length > 0 ? current.breadcrumbs[0].label : null;
654
+ const rootLabel = current.breadcrumbs.length > 0 ? current.breadcrumbs[0].label : selected.rootLabel ?? null;
609
655
  commandRef.current({
610
656
  id: selected.id,
611
657
  label: selected.label,
@@ -639,6 +685,18 @@ function useSuggestion(providers) {
639
685
  const close = useCallback2(() => {
640
686
  setUIState(IDLE_STATE);
641
687
  }, []);
688
+ const searchNested = useCallback2(
689
+ (query) => {
690
+ const provider = providerRef.current;
691
+ if (!provider) return;
692
+ const current = stateRef.current;
693
+ const parent = current.breadcrumbs[current.breadcrumbs.length - 1];
694
+ if (parent) {
695
+ fetchItems(provider, query, parent);
696
+ }
697
+ },
698
+ [fetchItems]
699
+ );
642
700
  const onKeyDown = useCallback2(
643
701
  ({ event }) => {
644
702
  const current = stateRef.current;
@@ -698,13 +756,14 @@ function useSuggestion(providers) {
698
756
  navigateDown,
699
757
  select,
700
758
  goBack,
701
- close
759
+ close,
760
+ searchNested
702
761
  };
703
762
  return { uiState, actions, callbacksRef };
704
763
  }
705
764
 
706
765
  // src/components/SuggestionList.tsx
707
- import { useEffect as useEffect2, useRef as useRef3 } from "react";
766
+ import { useEffect as useEffect2, useRef as useRef3, useState as useState2 } from "react";
708
767
 
709
768
  // src/utils/ariaHelpers.ts
710
769
  function comboboxAttrs(expanded, listboxId) {
@@ -744,10 +803,29 @@ function SuggestionList({
744
803
  onSelect,
745
804
  onHover,
746
805
  onGoBack,
806
+ onSearchNested,
807
+ onNavigateUp,
808
+ onNavigateDown,
809
+ onClose,
747
810
  renderItem
748
811
  }) {
749
812
  const listRef = useRef3(null);
813
+ const searchInputRef = useRef3(null);
750
814
  const depth = breadcrumbs.length;
815
+ const [nestedQuery, setNestedQuery] = useState2("");
816
+ const breadcrumbKey = breadcrumbs.map((b) => b.id).join("/");
817
+ const prevBreadcrumbKey = useRef3(breadcrumbKey);
818
+ useEffect2(() => {
819
+ if (prevBreadcrumbKey.current !== breadcrumbKey) {
820
+ setNestedQuery("");
821
+ prevBreadcrumbKey.current = breadcrumbKey;
822
+ }
823
+ }, [breadcrumbKey]);
824
+ useEffect2(() => {
825
+ if (breadcrumbs.length > 0 && searchInputRef.current) {
826
+ requestAnimationFrame(() => searchInputRef.current?.focus());
827
+ }
828
+ }, [breadcrumbKey, breadcrumbs.length]);
751
829
  useEffect2(() => {
752
830
  if (!listRef.current) return;
753
831
  const active = listRef.current.querySelector('[aria-selected="true"]');
@@ -755,6 +833,35 @@ function SuggestionList({
755
833
  }, [activeIndex]);
756
834
  const style = usePopoverPosition(clientRect);
757
835
  if (items.length === 0 && !loading) return null;
836
+ const handleSearchKeyDown = (e) => {
837
+ switch (e.key) {
838
+ case "ArrowDown":
839
+ e.preventDefault();
840
+ onNavigateDown?.();
841
+ break;
842
+ case "ArrowUp":
843
+ e.preventDefault();
844
+ onNavigateUp?.();
845
+ break;
846
+ case "Enter": {
847
+ e.preventDefault();
848
+ e.stopPropagation();
849
+ const selectedItem = items[activeIndex];
850
+ if (selectedItem) onSelect(selectedItem);
851
+ break;
852
+ }
853
+ case "Escape":
854
+ e.preventDefault();
855
+ onClose?.();
856
+ break;
857
+ case "Backspace":
858
+ if (nestedQuery === "") {
859
+ e.preventDefault();
860
+ onGoBack();
861
+ }
862
+ break;
863
+ }
864
+ };
758
865
  return /* @__PURE__ */ jsxs(
759
866
  "div",
760
867
  {
@@ -779,6 +886,24 @@ function SuggestionList({
779
886
  crumb.label
780
887
  ] }, crumb.id))
781
888
  ] }),
889
+ breadcrumbs.length > 0 && /* @__PURE__ */ jsx("div", { "data-suggestion-search": "", children: /* @__PURE__ */ jsx(
890
+ "input",
891
+ {
892
+ ref: searchInputRef,
893
+ type: "text",
894
+ "data-suggestion-search-input": "",
895
+ placeholder: "Search...",
896
+ value: nestedQuery,
897
+ onChange: (e) => {
898
+ const q = e.target.value;
899
+ setNestedQuery(q);
900
+ onSearchNested?.(q);
901
+ },
902
+ onKeyDown: handleSearchKeyDown,
903
+ autoComplete: "off",
904
+ spellCheck: false
905
+ }
906
+ ) }),
782
907
  loading && /* @__PURE__ */ jsx("div", { "data-suggestion-loading": "", children: "Loading..." }),
783
908
  !loading && /* @__PURE__ */ jsx("div", { ...listboxAttrs(LISTBOX_ID, `${trigger ?? ""} suggestions`), children: items.map((item, index) => {
784
909
  const isActive = index === activeIndex;
@@ -861,7 +986,8 @@ var MentionsInput = forwardRef(
861
986
  [clear, setContent, focus]
862
987
  );
863
988
  const isExpanded = uiState.state !== "idle";
864
- const handleHover = useCallback3((_index) => {
989
+ const handleHover = useCallback3((index) => {
990
+ void index;
865
991
  }, []);
866
992
  return /* @__PURE__ */ jsxs2(
867
993
  "div",
@@ -885,6 +1011,10 @@ var MentionsInput = forwardRef(
885
1011
  onSelect: (item) => actions.select(item),
886
1012
  onHover: handleHover,
887
1013
  onGoBack: actions.goBack,
1014
+ onSearchNested: actions.searchNested,
1015
+ onNavigateUp: actions.navigateUp,
1016
+ onNavigateDown: actions.navigateDown,
1017
+ onClose: actions.close,
888
1018
  renderItem
889
1019
  }
890
1020
  )
@@ -895,6 +1025,7 @@ var MentionsInput = forwardRef(
895
1025
  );
896
1026
  export {
897
1027
  MentionsInput,
1028
+ extractFromMarkdown,
898
1029
  parseFromMarkdown,
899
1030
  serializeToMarkdown
900
1031
  };