@brainfish-ai/editor 0.16.2 → 0.16.4

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.
Files changed (40) hide show
  1. package/dist/{index-BJKQPlcf.js → index-4Mg03NZ0.js} +2 -2
  2. package/dist/{index-BIHNHMyg.js → index-B44gf6lc.js} +2 -2
  3. package/dist/{index-CFr33LuW.js → index-B72hrXZp.js} +2 -2
  4. package/dist/{index-BQa2bres.js → index-BFdVwXxe.js} +2 -2
  5. package/dist/{index-DJWghCuI.js → index-BL3BrmJG.js} +518 -174
  6. package/dist/{index-DztBKea2.js → index-BdcQa21o.js} +2 -2
  7. package/dist/{index-DveiglG-.js → index-BiT1CaUK.js} +3 -3
  8. package/dist/{index-COD_xliD.js → index-BkpCLtjh.js} +4 -4
  9. package/dist/{index-D7noxe1h.js → index-BuU-LxbL.js} +3 -3
  10. package/dist/{index-DnpXm_wT.js → index-BzH29qwV.js} +4 -4
  11. package/dist/{index-C-bL3Cy9.js → index-C7heuyel.js} +3 -3
  12. package/dist/{index-dKic9ff-.js → index-CIzWSWLU.js} +1 -1
  13. package/dist/{index-BBMUH80b.js → index-Ce_sp8_S.js} +2 -2
  14. package/dist/{index-DsUx-yKw.js → index-Ck4GUp6S.js} +2 -2
  15. package/dist/{index-Dv14TLjz.js → index-CvcXtLfn.js} +3 -3
  16. package/dist/{index-Dk6UNQWp.js → index-DH5dBgli.js} +4 -4
  17. package/dist/{index-mEwvGwcX.js → index-DKpql_7V.js} +2 -2
  18. package/dist/{index-CsaeJbB5.js → index-DOSUqw2E.js} +2 -2
  19. package/dist/{index-CgzQ1jj0.js → index-DRSHBn8E.js} +2 -2
  20. package/dist/{index-paWgasj8.js → index-D_DH6CLj.js} +3 -3
  21. package/dist/{index-B4R1rT37.js → index-DnZLLzeC.js} +2 -2
  22. package/dist/{index-CyTfufcP.js → index-F6dIPhGZ.js} +2 -2
  23. package/dist/{index-LyNcRvuh.js → index-ImG4tGZ8.js} +2 -2
  24. package/dist/{index-DnbjsrWF.js → index-MGiiJ6ZI.js} +2 -2
  25. package/dist/index.js +1 -1
  26. package/dist/markdown-source-editor/MarkdownSourceEditor.d.ts +2 -0
  27. package/dist/plugins/emoji/suggest.d.ts +3 -0
  28. package/dist/plugins/mentions/core/types.d.ts +7 -1
  29. package/dist/plugins/mentions/integration/slash-factory.d.ts +5 -0
  30. package/dist/plugins/mentions/integration/slash-keydown.d.ts +1 -0
  31. package/dist/plugins/mentions/ui/MentionSlashView.d.ts +1 -0
  32. package/dist/plugins/mentions/ui/filterMentionSlashRows.d.ts +16 -0
  33. package/dist/plugins/mentions/ui/loadMentionSlashMenu.d.ts +9 -0
  34. package/dist/plugins/mentions/ui/mentionSlashMenuState.d.ts +57 -0
  35. package/dist/plugins/mentions/ui/runtime.d.ts +5 -0
  36. package/dist/plugins/mentions/ui/useMentionSlashMenu.d.ts +8 -11
  37. package/dist/stories/mentionBreakFixtures.d.ts +11 -0
  38. package/dist/stories/storybook-utils.d.ts +4 -0
  39. package/dist/types.d.ts +8 -0
  40. package/package.json +3 -3
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs, Fragment as Fragment$2 } from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
- import React__default, { createContext, useRef, useState, useMemo, useContext, useCallback, useLayoutEffect, useEffect, useId, Suspense, lazy } from 'react';
3
+ import React__default, { createContext, useRef, useState, useMemo, useContext, useCallback, useLayoutEffect, useEffect, useReducer, useId, Suspense, lazy } from 'react';
4
4
  import clsx from 'clsx';
5
5
  import { BrainfishColors } from '@brainfish-ai/components/colors';
6
6
  import * as Y from 'yjs';
@@ -8,7 +8,7 @@ import { UndoManager, Item as Item$1, ContentType, Text as Text$2, XmlElement, e
8
8
  import { flushSync, createPortal } from 'react-dom';
9
9
  import katex from 'katex';
10
10
  import { renderToStaticMarkup } from 'react-dom/server';
11
- import { Plus, Trash, SquaresFour, Warning, Info, Star, ArrowSquareOut, File as File$1, MagicWand, YoutubeLogo, Browser, X, Video, Paperclip, DownloadSimple, TextHOne, TextHSix, TextHFive, TextHFour, TextHThree, TextHTwo, Highlighter, ListBullets, ListNumbers, Link as Link$1, Eye, CodeSimple } from '@phosphor-icons/react';
11
+ import { Plus, Trash, SquaresFour, Warning, CaretLeft, CaretRight, ArrowSquareOut, Info, Star, File as File$1, MagicWand, YoutubeLogo, Browser, X, Video, Paperclip, DownloadSimple, TextHOne, TextHSix, TextHFive, TextHFour, TextHThree, TextHTwo, Highlighter, ListBullets, ListNumbers, Link as Link$1, Eye, CodeSimple } from '@phosphor-icons/react';
12
12
  import { Button } from '@brainfish-ai/components/ui/button';
13
13
  import { Input } from '@brainfish-ai/components/ui/input';
14
14
  import { ButtonGroup } from '@brainfish-ai/components/ui/button-group';
@@ -16,7 +16,7 @@ import { SimpleSelect } from '@brainfish-ai/components/select';
16
16
  import { Tooltip, TooltipTrigger, TooltipContent } from '@brainfish-ai/components/ui/tooltip';
17
17
  import { Command, CommandInput, CommandList, CommandGroup, CommandItem } from '@brainfish-ai/components/ui/command';
18
18
  import { Popover, PopoverTrigger, PopoverContent } from '@brainfish-ai/components/ui/popover';
19
- import { Card, CardHeader, CardTitle, CardContent, CardFooter } from '@brainfish-ai/components/ui/card';
19
+ import { Card, CardContent, CardFooter } from '@brainfish-ai/components/ui/card';
20
20
  import { Textarea } from '@brainfish-ai/components/ui/textarea';
21
21
  import { Tabs, TabsList, TabsTrigger, TabsContent } from '@brainfish-ai/components/ui/tabs';
22
22
 
@@ -41745,9 +41745,11 @@ function configureMentionStringify(crepe) {
41745
41745
  }
41746
41746
 
41747
41747
  const emptyRef = { current: [] };
41748
+ const emptyFooterButtonClickRef = { current: void 0 };
41748
41749
  const MentionsRuntimeContext = createContext({
41749
41750
  providersRef: emptyRef,
41750
- providers: []
41751
+ providers: [],
41752
+ onFooterButtonClickRef: emptyFooterButtonClickRef
41751
41753
  });
41752
41754
  function useMentionsRuntime() {
41753
41755
  return useContext(MentionsRuntimeContext);
@@ -41760,6 +41762,10 @@ function findMentionProvider(providers, type) {
41760
41762
  return providers.find((provider) => provider.type === type);
41761
41763
  }
41762
41764
 
41765
+ function pillIcon(provider, refId) {
41766
+ const match = provider?.candidates?.find((row) => row.id === refId);
41767
+ return match?.icon ?? provider?.typeIcon;
41768
+ }
41763
41769
  const MENTION_PILL_CLASS = "bf-mention-pill";
41764
41770
  function selectAdjacentMention(view, event) {
41765
41771
  if (event.shiftKey || event.metaKey || event.ctrlKey || event.altKey) return false;
@@ -41829,7 +41835,7 @@ const MentionNodeView = () => {
41829
41835
  attrs: flattenMentionAttrs(parsed)
41830
41836
  })
41831
41837
  );
41832
- const TypeIcon = provider?.typeIcon;
41838
+ const TypeIcon = pillIcon(provider, provider ? parsed.extra[provider.refAttr] : void 0);
41833
41839
  const canPreview = Boolean(provider?.onClick);
41834
41840
  const [open, setOpen] = useState(false);
41835
41841
  const [content, setContent] = useState(null);
@@ -41858,6 +41864,7 @@ const MentionNodeView = () => {
41858
41864
  className: clsx(
41859
41865
  MENTION_PILL_CLASS,
41860
41866
  "inline-flex relative top-0.5 items-center gap-1 rounded-full px-2 py-0.5 h-6 text-sm font-medium align-baseline",
41867
+ "max-w-64 min-w-0",
41861
41868
  { "bg-purple-800 text-purple-100 ring-purple-300": !invalid },
41862
41869
  { "bg-warning text-warning border border-warning ring-yellow-500": invalid },
41863
41870
  { "!ring-2": selected }
@@ -41878,8 +41885,16 @@ const MentionNodeView = () => {
41878
41885
  moveCaretOffMention(view, pos, node.nodeSize, event.key);
41879
41886
  },
41880
41887
  children: [
41881
- invalid ? /* @__PURE__ */ jsx(Warning, { size: 16, weight: "regular", "data-testid": "mention-pill-warning" }) : TypeIcon ? /* @__PURE__ */ jsx(TypeIcon, { size: 16, weight: "fill", "data-testid": "mention-pill-type-icon" }) : null,
41882
- parsed.label || parsed.type
41888
+ invalid ? /* @__PURE__ */ jsx(Warning, { size: 16, weight: "regular", className: "shrink-0", "data-testid": "mention-pill-warning" }) : TypeIcon ? /* @__PURE__ */ jsx(
41889
+ TypeIcon,
41890
+ {
41891
+ size: 16,
41892
+ weight: provider?.iconWeight ?? "regular",
41893
+ className: "shrink-0",
41894
+ "data-testid": "mention-pill-type-icon"
41895
+ }
41896
+ ) : null,
41897
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", title: parsed.label || parsed.type, children: parsed.label || parsed.type })
41883
41898
  ]
41884
41899
  }
41885
41900
  ) }),
@@ -41917,6 +41932,30 @@ function configureMentionNodeView(ctx, nodeViewFactory) {
41917
41932
  ]);
41918
41933
  }
41919
41934
 
41935
+ const ALL_MENTION_GROUP = "all";
41936
+ function filterMentionSlashRows(rows, activeGroup) {
41937
+ if (activeGroup === ALL_MENTION_GROUP) return rows;
41938
+ return rows.filter((row) => row.groupLabel === activeGroup);
41939
+ }
41940
+ function resolveMentionSlashGroup(rows, activeGroup) {
41941
+ if (activeGroup === ALL_MENTION_GROUP) return ALL_MENTION_GROUP;
41942
+ return rows.some((row) => row.groupLabel === activeGroup) ? activeGroup : ALL_MENTION_GROUP;
41943
+ }
41944
+ function mentionSlashTabIds(rows) {
41945
+ const labels = [ALL_MENTION_GROUP];
41946
+ for (const row of rows) {
41947
+ if (!labels.includes(row.groupLabel)) labels.push(row.groupLabel);
41948
+ }
41949
+ return labels;
41950
+ }
41951
+ function cycleMentionSlashGroup(tabIds, current, direction) {
41952
+ if (!tabIds.length) return ALL_MENTION_GROUP;
41953
+ const resolved = tabIds.includes(current) ? current : ALL_MENTION_GROUP;
41954
+ const index = tabIds.indexOf(resolved);
41955
+ const delta = direction === "next" ? 1 : -1;
41956
+ return tabIds[(index + delta + tabIds.length) % tabIds.length] ?? ALL_MENTION_GROUP;
41957
+ }
41958
+
41920
41959
  //#region src/slash-plugin.ts
41921
41960
  function slashFactory(id) {
41922
41961
  const slashSpec = $ctx({}, `${id}_SLASH_SPEC`);
@@ -42712,7 +42751,10 @@ const mentionSlashApi = $ctx(
42712
42751
  isOpen: () => false,
42713
42752
  nav: () => {
42714
42753
  },
42754
+ navGroup: () => {
42755
+ },
42715
42756
  pick: () => false,
42757
+ focusFooterButton: () => false,
42716
42758
  dismiss: () => {
42717
42759
  }
42718
42760
  },
@@ -42781,34 +42823,135 @@ function firstSlashMessage(providers, kind) {
42781
42823
  return null;
42782
42824
  }
42783
42825
 
42826
+ async function loadMentionSlashMenu(providers, query, signal) {
42827
+ const decision = decideSlashFetch(providers);
42828
+ if (decision.kind === "loading") return { type: "retry" };
42829
+ if (decision.kind === "not-connected") {
42830
+ return { type: "not-connected", content: firstSlashMessage(providers, "not-connected") };
42831
+ }
42832
+ try {
42833
+ const settled = await Promise.allSettled(
42834
+ decision.ready.map(async (provider) => {
42835
+ const items = query ? await provider.search({ query, signal }) : await provider.list({ limit: 5, signal });
42836
+ return items.map(
42837
+ (item) => ({
42838
+ ...item,
42839
+ type: item.type || provider.type,
42840
+ groupLabel: provider.groupLabel,
42841
+ refAttr: provider.refAttr,
42842
+ provider
42843
+ })
42844
+ );
42845
+ })
42846
+ );
42847
+ if (signal.aborted) return { type: "retry" };
42848
+ const outcome = decideSlashFetchResult(settled);
42849
+ if (outcome === "error") {
42850
+ return { type: "error", content: firstSlashMessage(providers, "error") };
42851
+ }
42852
+ return {
42853
+ type: "loaded",
42854
+ rows: settled.flatMap((result) => result.status === "fulfilled" ? result.value : []),
42855
+ status: outcome
42856
+ };
42857
+ } catch (error) {
42858
+ if (signal.aborted) return { type: "retry" };
42859
+ if (error instanceof DOMException && error.name === "AbortError") return { type: "retry" };
42860
+ return { type: "error", content: firstSlashMessage(providers, "error") };
42861
+ }
42862
+ }
42863
+
42864
+ const initialMentionSlashMenuState = {
42865
+ open: false,
42866
+ query: "",
42867
+ status: "loading",
42868
+ slashContent: null,
42869
+ rows: [],
42870
+ selectedIndex: 0,
42871
+ activeGroup: ALL_MENTION_GROUP
42872
+ };
42873
+ function mentionSlashMenuReducer(state, action) {
42874
+ switch (action.type) {
42875
+ case "show": {
42876
+ const queryChanged = state.query !== action.query;
42877
+ if (state.open && !queryChanged) return state;
42878
+ return {
42879
+ ...state,
42880
+ open: true,
42881
+ query: action.query,
42882
+ ...queryChanged ? {
42883
+ activeGroup: ALL_MENTION_GROUP,
42884
+ selectedIndex: 0,
42885
+ status: "loading",
42886
+ rows: [],
42887
+ slashContent: null
42888
+ } : {}
42889
+ };
42890
+ }
42891
+ case "hide":
42892
+ return initialMentionSlashMenuState;
42893
+ case "loading":
42894
+ return { ...state, status: "loading", slashContent: null, rows: [] };
42895
+ case "not-connected":
42896
+ return { ...state, status: "not-connected", slashContent: action.content, rows: [] };
42897
+ case "error":
42898
+ return { ...state, status: "error", slashContent: action.content, rows: [] };
42899
+ case "loaded":
42900
+ return {
42901
+ ...state,
42902
+ rows: action.rows,
42903
+ status: action.status,
42904
+ slashContent: null,
42905
+ selectedIndex: 0,
42906
+ activeGroup: resolveMentionSlashGroup(action.rows, state.activeGroup)
42907
+ };
42908
+ case "select":
42909
+ return { ...state, selectedIndex: action.index };
42910
+ case "setGroup":
42911
+ return { ...state, activeGroup: action.group, selectedIndex: 0 };
42912
+ case "nav": {
42913
+ const visible = visibleMentionSlashRows(state);
42914
+ if (!visible.length) return state;
42915
+ const next = action.direction === "next" ? (state.selectedIndex + 1) % visible.length : (state.selectedIndex - 1 + visible.length) % visible.length;
42916
+ return { ...state, selectedIndex: next };
42917
+ }
42918
+ case "navGroup":
42919
+ return {
42920
+ ...state,
42921
+ activeGroup: cycleMentionSlashGroup(
42922
+ mentionSlashTabIds(state.rows),
42923
+ resolveMentionSlashGroup(state.rows, state.activeGroup),
42924
+ action.direction
42925
+ ),
42926
+ selectedIndex: 0
42927
+ };
42928
+ }
42929
+ }
42930
+ function visibleMentionSlashRows(state) {
42931
+ return filterMentionSlashRows(state.rows, resolveMentionSlashGroup(state.rows, state.activeGroup));
42932
+ }
42933
+ function resolvedMentionSlashGroup(state) {
42934
+ return resolveMentionSlashGroup(state.rows, state.activeGroup);
42935
+ }
42936
+
42784
42937
  const SEARCH_DEBOUNCE_MS = 200;
42785
42938
  const AVAILABILITY_RECHECK_MS = 200;
42786
42939
  function useMentionSlashMenu() {
42787
42940
  const { view } = usePluginViewContext();
42788
42941
  const { getInstance, loading } = useEditorAction();
42789
- const { providersRef } = useMentionsRuntime();
42942
+ const { providersRef, onFooterButtonClickRef } = useMentionsRuntime();
42790
42943
  const contentRef = useRef(null);
42944
+ const listRef = useRef(null);
42791
42945
  const providerRef = useRef(null);
42792
42946
  const viewRef = useRef(view);
42793
42947
  viewRef.current = view;
42794
- const listRef = useRef(null);
42795
- const [open, setOpen] = useState(false);
42796
- const [query, setQuery] = useState("");
42797
- const [status, setStatus] = useState("loading");
42798
- const [slashContent, setSlashContent] = useState(null);
42799
- const [rows, setRows] = useState([]);
42800
- const [selectedIndex, setSelectedIndex] = useState(0);
42801
- const [availabilityTick, setAvailabilityTick] = useState(0);
42802
42948
  const fromToRef = useRef({ from: 0, to: 0 });
42803
- const rowsRef = useRef(rows);
42804
- rowsRef.current = rows;
42805
- const selectedIndexRef = useRef(selectedIndex);
42806
- selectedIndexRef.current = selectedIndex;
42807
- const statusRef = useRef(status);
42808
- statusRef.current = status;
42809
- const openRef = useRef(open);
42810
- openRef.current = open;
42811
42949
  const hostRef = useRef(null);
42950
+ const [state, dispatch] = useReducer(mentionSlashMenuReducer, initialMentionSlashMenuState);
42951
+ const stateRef = useRef(state);
42952
+ stateRef.current = state;
42953
+ const visibleRows = visibleMentionSlashRows(state);
42954
+ const activeGroup = resolvedMentionSlashGroup(state);
42812
42955
  useLayoutEffect(() => {
42813
42956
  const el = contentRef.current;
42814
42957
  if (el?.parentElement && el.parentElement !== document.body) {
@@ -42823,10 +42966,11 @@ function useMentionSlashMenu() {
42823
42966
  };
42824
42967
  }, []);
42825
42968
  const pickRef = useRef(() => false);
42826
- pickRef.current = () => {
42827
- const row = rowsRef.current[selectedIndexRef.current];
42969
+ pickRef.current = (index) => {
42970
+ const current = stateRef.current;
42971
+ const row = visibleMentionSlashRows(current)[index ?? current.selectedIndex];
42828
42972
  const editorView = viewRef.current;
42829
- if (statusRef.current !== "ready") return false;
42973
+ if (current.status !== "ready") return false;
42830
42974
  if (!row || !editorView || !editorView.editable) return false;
42831
42975
  const inserted = insertMentionInView(editorView, fromToRef.current.from, fromToRef.current.to, row, row.provider);
42832
42976
  if (inserted) providerRef.current?.hide();
@@ -42842,141 +42986,187 @@ function useMentionSlashMenu() {
42842
42986
  root: document.body,
42843
42987
  floatingUIOptions: { strategy: "fixed" },
42844
42988
  shouldShow(editorView) {
42989
+ if (contentRef.current?.contains(document.activeElement)) return true;
42845
42990
  if (!editorView.editable) return false;
42846
42991
  if (!providersRef.current.length) return false;
42847
42992
  const trigger = findMentionTrigger(editorView.state);
42848
42993
  if (!trigger) return false;
42849
42994
  fromToRef.current = { from: trigger.from, to: trigger.to };
42850
- queueMicrotask(() => {
42851
- setQuery(trigger.query);
42852
- setOpen(true);
42853
- });
42995
+ queueMicrotask(() => dispatch({ type: "show", query: trigger.query }));
42854
42996
  return true;
42855
42997
  }
42856
42998
  });
42857
42999
  slashProvider.onHide = () => {
42858
- queueMicrotask(() => {
42859
- setOpen(false);
42860
- setRows([]);
42861
- setSlashContent(null);
42862
- setStatus("loading");
42863
- });
43000
+ queueMicrotask(() => dispatch({ type: "hide" }));
42864
43001
  };
42865
43002
  providerRef.current = slashProvider;
42866
43003
  const onNativeMouseDown = (event) => {
42867
43004
  if (!viewRef.current?.editable) return;
42868
- const option = event.target instanceof Element ? event.target.closest("[data-mention-option-index]") : null;
43005
+ const target = event.target instanceof Element ? event.target : null;
43006
+ const tabNav = target?.closest("[data-mention-tab-nav]");
43007
+ if (tabNav) {
43008
+ event.preventDefault();
43009
+ event.stopPropagation();
43010
+ const direction = tabNav.getAttribute("data-mention-tab-nav");
43011
+ if (direction === "next" || direction === "prev") dispatch({ type: "navGroup", direction });
43012
+ return;
43013
+ }
43014
+ const tab = target?.closest("[data-mention-group]");
43015
+ if (tab) {
43016
+ event.preventDefault();
43017
+ event.stopPropagation();
43018
+ const group = tab.getAttribute("data-mention-group");
43019
+ if (group) dispatch({ type: "setGroup", group });
43020
+ return;
43021
+ }
43022
+ const option = target?.closest("[data-mention-option-index]");
42869
43023
  if (!option) return;
42870
43024
  event.preventDefault();
42871
43025
  event.stopPropagation();
42872
43026
  const index = Number(option.getAttribute("data-mention-option-index"));
42873
43027
  if (!Number.isFinite(index)) return;
42874
- selectedIndexRef.current = index;
42875
- setSelectedIndex(index);
42876
- pickRef.current();
43028
+ dispatch({ type: "select", index });
43029
+ pickRef.current(index);
43030
+ };
43031
+ const onNativeKeyDown = (event) => {
43032
+ const target = event.target instanceof Element ? event.target : null;
43033
+ if (!target?.closest('[data-testid="mention-slash-footer-button"]')) return;
43034
+ if (event.key === "Escape") {
43035
+ event.preventDefault();
43036
+ event.stopPropagation();
43037
+ viewRef.current?.focus();
43038
+ providerRef.current?.hide();
43039
+ return;
43040
+ }
43041
+ if (event.key === "Tab") {
43042
+ event.preventDefault();
43043
+ viewRef.current?.focus();
43044
+ }
43045
+ };
43046
+ const onNativeClick = (event) => {
43047
+ const target = event.target instanceof Element ? event.target : null;
43048
+ if (!target?.closest('[data-testid="mention-slash-footer-button"]')) return;
43049
+ onFooterButtonClickRef.current?.();
42877
43050
  };
42878
43051
  content.addEventListener("mousedown", onNativeMouseDown);
43052
+ content.addEventListener("keydown", onNativeKeyDown);
43053
+ content.addEventListener("click", onNativeClick);
42879
43054
  return () => {
42880
43055
  content.removeEventListener("mousedown", onNativeMouseDown);
43056
+ content.removeEventListener("keydown", onNativeKeyDown);
43057
+ content.removeEventListener("click", onNativeClick);
42881
43058
  slashProvider.destroy();
42882
43059
  providerRef.current = null;
42883
43060
  };
42884
- }, [view, providersRef]);
43061
+ }, [view, providersRef, onFooterButtonClickRef]);
43062
+ const prevEditorStateRef = useRef(void 0);
42885
43063
  useEffect(() => {
42886
43064
  if (!view || !providerRef.current) return;
42887
- providerRef.current.update(view);
43065
+ providerRef.current.update(view, prevEditorStateRef.current);
43066
+ prevEditorStateRef.current = view.state;
42888
43067
  });
42889
43068
  useEffect(() => {
42890
- if (!open) return;
43069
+ if (!state.open) return;
42891
43070
  const abort = new AbortController();
42892
43071
  let recheckTimer;
42893
- const run = debounce(async () => {
42894
- const providers = providersRef.current;
42895
- const decision = decideSlashFetch(providers);
42896
- if (decision.kind === "loading") {
42897
- setRows([]);
42898
- setSlashContent(null);
42899
- setStatus("loading");
42900
- recheckTimer = setTimeout(() => setAvailabilityTick((tick) => tick + 1), AVAILABILITY_RECHECK_MS);
43072
+ const load = async () => {
43073
+ const result = await loadMentionSlashMenu(providersRef.current, state.query, abort.signal);
43074
+ if (abort.signal.aborted) return;
43075
+ if (result.type === "retry") {
43076
+ dispatch({ type: "loading" });
43077
+ recheckTimer = setTimeout(() => {
43078
+ void load();
43079
+ }, AVAILABILITY_RECHECK_MS);
42901
43080
  return;
42902
43081
  }
42903
- if (decision.kind === "not-connected") {
42904
- setRows([]);
42905
- setSlashContent(firstSlashMessage(providers, "not-connected"));
42906
- setStatus("not-connected");
42907
- return;
42908
- }
42909
- setSlashContent(null);
42910
- setStatus("loading");
42911
- try {
42912
- const settled = await Promise.allSettled(
42913
- decision.ready.map(async (provider) => {
42914
- const items = query ? await provider.search({ query, signal: abort.signal }) : await provider.list({ limit: 5, signal: abort.signal });
42915
- return items.map((item) => ({
42916
- ...item,
42917
- type: item.type || provider.type,
42918
- groupLabel: provider.groupLabel,
42919
- refAttr: provider.refAttr,
42920
- provider
42921
- }));
42922
- })
42923
- );
42924
- if (abort.signal.aborted) return;
42925
- const outcome = decideSlashFetchResult(settled);
42926
- if (outcome === "error") {
42927
- setRows([]);
42928
- setSlashContent(firstSlashMessage(providers, "error"));
42929
- setStatus("error");
42930
- return;
42931
- }
42932
- const next = settled.flatMap((result) => result.status === "fulfilled" ? result.value : []);
42933
- setRows(next);
42934
- setSelectedIndex(0);
42935
- setSlashContent(null);
42936
- setStatus(outcome);
42937
- } catch (error) {
42938
- if (abort.signal.aborted) return;
42939
- if (error instanceof DOMException && error.name === "AbortError") return;
42940
- setRows([]);
42941
- setSlashContent(firstSlashMessage(providersRef.current, "error"));
42942
- setStatus("error");
42943
- }
42944
- }, SEARCH_DEBOUNCE_MS);
43082
+ dispatch(result);
43083
+ };
43084
+ const run = debounce(load, SEARCH_DEBOUNCE_MS);
42945
43085
  void run();
42946
43086
  return () => {
42947
43087
  abort.abort();
42948
43088
  run.cancel();
42949
43089
  clearTimeout(recheckTimer);
42950
43090
  };
42951
- }, [open, query, providersRef, availabilityTick]);
43091
+ }, [state.open, state.query, providersRef]);
42952
43092
  useEffect(() => {
42953
43093
  const ctx = getInstance()?.ctx;
42954
43094
  if (!ctx) return;
42955
43095
  ctx.set(mentionSlashApi.key, {
42956
- isOpen: () => openRef.current,
42957
- nav: (direction) => {
42958
- const items = rowsRef.current;
42959
- if (!items.length) return;
42960
- setSelectedIndex((index) => direction === "next" ? (index + 1) % items.length : (index - 1 + items.length) % items.length);
42961
- },
43096
+ isOpen: () => stateRef.current.open,
43097
+ nav: (direction) => dispatch({ type: "nav", direction }),
43098
+ navGroup: (direction) => dispatch({ type: "navGroup", direction }),
42962
43099
  pick: () => pickRef.current(),
43100
+ focusFooterButton: () => {
43101
+ const button = contentRef.current?.querySelector('[data-testid="mention-slash-footer-button"]');
43102
+ if (!button) return false;
43103
+ button.focus({ focusVisible: true });
43104
+ return true;
43105
+ },
42963
43106
  dismiss: () => providerRef.current?.hide()
42964
43107
  });
42965
43108
  }, [getInstance, loading]);
42966
43109
  useLayoutEffect(() => {
42967
43110
  listRef.current?.querySelector('[data-selected="true"]')?.scrollIntoView({ block: "nearest" });
42968
- }, [selectedIndex]);
42969
- return { contentRef, listRef, open, query, status, slashContent, rows, selectedIndex };
43111
+ }, [state.selectedIndex]);
43112
+ return {
43113
+ contentRef,
43114
+ listRef,
43115
+ open: state.open,
43116
+ query: state.query,
43117
+ status: state.status,
43118
+ slashContent: state.slashContent,
43119
+ rows: state.rows,
43120
+ visibleRows,
43121
+ selectedIndex: state.selectedIndex,
43122
+ activeGroup,
43123
+ setActiveGroup: (group) => dispatch({ type: "setGroup", group })
43124
+ };
42970
43125
  }
42971
43126
 
42972
43127
  const MentionSlashView = () => {
42973
- const { contentRef, listRef, open, query, status, slashContent, rows, selectedIndex } = useMentionSlashMenu();
42974
- const grouped = groupRows(rows);
42975
- const activeId = rows[selectedIndex] ? `bf-mention-opt-${selectedIndex}` : void 0;
42976
- const selectedRow = rows[selectedIndex];
42977
- const activeGroup = grouped.find((group) => selectedRow != null && group.rows.includes(selectedRow));
42978
- const activeGroupIndex = activeGroup && selectedRow ? activeGroup.rows.indexOf(selectedRow) : 0;
42979
- const activeGroupCount = activeGroup?.rows.length ?? 0;
43128
+ const { footerButtonLabel } = useMentionsRuntime();
43129
+ const {
43130
+ contentRef,
43131
+ listRef,
43132
+ open,
43133
+ query,
43134
+ status,
43135
+ slashContent,
43136
+ rows,
43137
+ visibleRows,
43138
+ selectedIndex,
43139
+ activeGroup,
43140
+ setActiveGroup
43141
+ } = useMentionSlashMenu();
43142
+ const grouped = groupRows(visibleRows);
43143
+ const tabGroups = groupRows(rows);
43144
+ const activeId = visibleRows[selectedIndex] ? `bf-mention-opt-${selectedIndex}` : void 0;
43145
+ const tabsRef = useRef(null);
43146
+ const [canScrollLeft, setCanScrollLeft] = useState(false);
43147
+ const [canScrollRight, setCanScrollRight] = useState(false);
43148
+ const measureTabs = useCallback(() => {
43149
+ const node = tabsRef.current;
43150
+ if (!node) return;
43151
+ setCanScrollLeft(node.scrollLeft > 1);
43152
+ setCanScrollRight(node.scrollLeft + node.clientWidth < node.scrollWidth - 1);
43153
+ }, []);
43154
+ useLayoutEffect(() => {
43155
+ const node = tabsRef.current;
43156
+ if (!node) return;
43157
+ measureTabs();
43158
+ const observer = new ResizeObserver(measureTabs);
43159
+ observer.observe(node);
43160
+ node.addEventListener("scroll", measureTabs, { passive: true });
43161
+ return () => {
43162
+ observer.disconnect();
43163
+ node.removeEventListener("scroll", measureTabs);
43164
+ };
43165
+ }, [tabGroups, open, status, measureTabs]);
43166
+ useLayoutEffect(() => {
43167
+ const tab = tabsRef.current?.querySelector(`[data-mention-group="${CSS.escape(activeGroup)}"]`);
43168
+ tab?.scrollIntoView({ inline: "nearest", block: "nearest", behavior: "smooth" });
43169
+ }, [activeGroup]);
42980
43170
  return /* @__PURE__ */ jsx(
42981
43171
  "div",
42982
43172
  {
@@ -42991,26 +43181,78 @@ const MentionSlashView = () => {
42991
43181
  role: "listbox",
42992
43182
  "aria-label": "Mentions",
42993
43183
  "aria-activedescendant": activeId,
42994
- className: "text-xs p-1.5 w-96",
43184
+ className: "text-xs p-1.5 w-80 max-w-full min-w-0 overflow-hidden",
42995
43185
  children: [
42996
43186
  status === "loading" && /* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-muted-foreground", children: "Loading…" }),
42997
43187
  (status === "error" || status === "not-connected") && /* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-muted-foreground", children: slashContent }),
42998
- status === "empty" && /* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-muted-foreground", children: "No matches" }),
43188
+ status === "empty" && /* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-muted-foreground", children: query.trim() ? "No matches" : "No mentions yet" }),
42999
43189
  status === "ready" && /* @__PURE__ */ jsxs(Fragment$2, { children: [
43000
- /* @__PURE__ */ jsx(CardHeader, { className: "p-2 pt-1.5", children: /* @__PURE__ */ jsxs(CardTitle, { className: "flex justify-between", children: [
43001
- /* @__PURE__ */ jsx("span", { className: "uppercase", children: activeGroup?.label }),
43002
- query ? /* @__PURE__ */ jsxs("span", { "data-testid": "mention-slash-counter", children: [
43003
- activeGroupIndex + 1,
43004
- " of ",
43005
- activeGroupCount
43190
+ /* @__PURE__ */ jsxs("div", { className: "relative min-w-0 max-w-full overflow-hidden border-b border-border", children: [
43191
+ /* @__PURE__ */ jsxs(
43192
+ "div",
43193
+ {
43194
+ ref: tabsRef,
43195
+ className: "flex w-full min-w-0 flex-nowrap overflow-x-auto overflow-y-hidden scroll-smooth [scrollbar-width:none]",
43196
+ children: [
43197
+ /* @__PURE__ */ jsx(
43198
+ GroupTab,
43199
+ {
43200
+ groupId: ALL_MENTION_GROUP,
43201
+ label: "All",
43202
+ count: rows.length,
43203
+ selected: activeGroup === ALL_MENTION_GROUP,
43204
+ onSelect: () => setActiveGroup(ALL_MENTION_GROUP)
43205
+ }
43206
+ ),
43207
+ tabGroups.map((group) => /* @__PURE__ */ jsx(
43208
+ GroupTab,
43209
+ {
43210
+ groupId: group.label,
43211
+ label: group.label,
43212
+ count: group.rows.length,
43213
+ selected: activeGroup === group.label,
43214
+ onSelect: () => setActiveGroup(group.label)
43215
+ },
43216
+ group.label
43217
+ ))
43218
+ ]
43219
+ }
43220
+ ),
43221
+ canScrollLeft ? /* @__PURE__ */ jsxs(Fragment$2, { children: [
43222
+ /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-y-0 left-0 w-8 bg-gradient-to-r from-card to-transparent" }),
43223
+ /* @__PURE__ */ jsx(
43224
+ "button",
43225
+ {
43226
+ type: "button",
43227
+ "aria-label": "Previous mention group",
43228
+ "data-mention-tab-nav": "prev",
43229
+ className: "absolute inset-y-0 left-0 z-10 px-0.5 text-muted-foreground",
43230
+ onMouseDown: (event) => event.preventDefault(),
43231
+ children: /* @__PURE__ */ jsx(CaretLeft, { size: 14 })
43232
+ }
43233
+ )
43234
+ ] }) : null,
43235
+ canScrollRight ? /* @__PURE__ */ jsxs(Fragment$2, { children: [
43236
+ /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-y-0 right-0 w-8 bg-gradient-to-l from-card to-transparent" }),
43237
+ /* @__PURE__ */ jsx(
43238
+ "button",
43239
+ {
43240
+ type: "button",
43241
+ "aria-label": "Next mention group",
43242
+ "data-mention-tab-nav": "next",
43243
+ className: "absolute inset-y-0 right-0 z-10 px-0.5 text-muted-foreground",
43244
+ onMouseDown: (event) => event.preventDefault(),
43245
+ children: /* @__PURE__ */ jsx(CaretRight, { size: 14 })
43246
+ }
43247
+ )
43006
43248
  ] }) : null
43007
- ] }) }),
43249
+ ] }),
43008
43250
  /* @__PURE__ */ jsx(CardContent, { className: "p-0 max-h-72 overflow-y-auto", children: grouped.map((group, groupIndex) => /* @__PURE__ */ jsxs("div", { children: [
43009
- grouped.length > 1 && /* @__PURE__ */ jsx("div", { className: "px-2 py-1 text-xs text-muted-foreground", children: group.label }),
43251
+ activeGroup === ALL_MENTION_GROUP && grouped.length > 1 && /* @__PURE__ */ jsx("div", { className: "px-2 py-1 text-xs text-muted-foreground truncate", title: group.label, children: group.label }),
43010
43252
  group.rows.map((row) => {
43011
- const index = rows.indexOf(row);
43253
+ const index = visibleRows.indexOf(row);
43012
43254
  const selected = index === selectedIndex;
43013
- const TypeIcon = row.provider.typeIcon;
43255
+ const TypeIcon = row.icon ?? row.provider.typeIcon;
43014
43256
  return /* @__PURE__ */ jsxs(
43015
43257
  "div",
43016
43258
  {
@@ -43019,25 +43261,37 @@ const MentionSlashView = () => {
43019
43261
  "aria-selected": selected,
43020
43262
  "data-selected": selected ? "true" : void 0,
43021
43263
  "data-mention-option-index": index,
43022
- className: `cursor-pointer grid grid-cols-[24px_minmax(0,1fr)_auto] gap-[2px_8px] items-center p-2 ${selected ? "bg-dark-200" : ""}`,
43264
+ className: `cursor-pointer flex min-w-0 items-center gap-2 p-2 ${selected ? "bg-dark-200" : ""}`,
43023
43265
  children: [
43266
+ /* @__PURE__ */ jsx(TypeIcon, { size: 16, weight: "fill", className: "shrink-0 text-subtle" }),
43024
43267
  /* @__PURE__ */ jsx(
43025
43268
  "span",
43026
43269
  {
43027
- className: `size-6 rounded-md grid place-items-center bg-purple-800 text-purple-100 ${row.description ? "row-span-2" : ""}`,
43028
- children: /* @__PURE__ */ jsx(TypeIcon, { size: 12, weight: "fill" })
43270
+ className: "min-w-0 flex-1 font-semibold text-sm overflow-hidden text-ellipsis whitespace-nowrap",
43271
+ title: row.label,
43272
+ children: row.label
43029
43273
  }
43030
- ),
43031
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-sm overflow-hidden text-ellipsis whitespace-nowrap", children: row.label }),
43032
- /* @__PURE__ */ jsx("span", { className: "text-xs text-subtlest whitespace-nowrap tabular-nums", children: row.inputCount ? `${row.inputCount} inputs` : null }),
43033
- row.description ? /* @__PURE__ */ jsx("span", { className: "col-start-2 col-span-2 text-xs text-subtlest overflow-hidden text-ellipsis whitespace-nowrap", children: row.description }) : null
43274
+ )
43034
43275
  ]
43035
43276
  },
43036
43277
  `${row.type}-${row.id}-${index}`
43037
43278
  );
43038
43279
  })
43039
43280
  ] }, `${group.label}-${groupIndex}`)) }),
43040
- /* @__PURE__ */ jsx(CardFooter, { className: "p-2 pb-1 border-t border-border", children: /* @__PURE__ */ jsx("span", { className: "text-xs text-subtlest", children: "↑↓ to move · Enter to insert · Esc to close" }) })
43281
+ /* @__PURE__ */ jsx(CardFooter, { className: "p-2 pb-1 border-t border-border min-w-0", children: /* @__PURE__ */ jsxs(
43282
+ Button,
43283
+ {
43284
+ type: "button",
43285
+ variant: "link",
43286
+ size: "sm",
43287
+ className: "h-auto max-w-full min-w-0 p-0 text-lime-700 font-normal whitespace-nowrap",
43288
+ "data-testid": "mention-slash-footer-button",
43289
+ children: [
43290
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: footerButtonLabel ?? "Create a workflow" }),
43291
+ /* @__PURE__ */ jsx(ArrowSquareOut, { size: 14, className: "shrink-0" })
43292
+ ]
43293
+ }
43294
+ ) })
43041
43295
  ] })
43042
43296
  ]
43043
43297
  }
@@ -43045,6 +43299,33 @@ const MentionSlashView = () => {
43045
43299
  }
43046
43300
  );
43047
43301
  };
43302
+ function GroupTab({
43303
+ groupId,
43304
+ label,
43305
+ count,
43306
+ selected,
43307
+ onSelect
43308
+ }) {
43309
+ return /* @__PURE__ */ jsxs(
43310
+ "button",
43311
+ {
43312
+ type: "button",
43313
+ "aria-pressed": selected,
43314
+ "data-mention-group": groupId,
43315
+ "data-testid": `mention-slash-tab-${label}`,
43316
+ title: label,
43317
+ className: `relative shrink-0 scroll-mx-8 px-2 py-1.5 text-sm whitespace-nowrap flex items-center gap-1 ${selected ? "text-foreground font-semibold" : "text-subtlest"}`,
43318
+ onMouseDown: (event) => event.preventDefault(),
43319
+ onClick: onSelect,
43320
+ children: [
43321
+ label,
43322
+ " ",
43323
+ /* @__PURE__ */ jsx("span", { className: "text-subtlest px-1.5 rounded-full bg-dark-200 text-xs tabular-nums shrink-0", children: count }),
43324
+ selected ? /* @__PURE__ */ jsx("span", { className: "absolute inset-x-2 bottom-0 h-0.5 rounded-sm bg-foreground" }) : null
43325
+ ]
43326
+ }
43327
+ );
43328
+ }
43048
43329
  function groupRows(rows) {
43049
43330
  const groups = [];
43050
43331
  for (const row of rows) {
@@ -43072,11 +43353,22 @@ function mentionSlashKeydown(ctx) {
43072
43353
  event.preventDefault();
43073
43354
  api.nav("prev");
43074
43355
  return true;
43356
+ case "ArrowRight":
43357
+ event.preventDefault();
43358
+ api.navGroup("next");
43359
+ return true;
43360
+ case "ArrowLeft":
43361
+ event.preventDefault();
43362
+ api.navGroup("prev");
43363
+ return true;
43075
43364
  case "Enter":
43076
- case "Tab":
43077
43365
  event.preventDefault();
43078
43366
  api.pick();
43079
43367
  return true;
43368
+ case "Tab":
43369
+ event.preventDefault();
43370
+ if (!event.shiftKey) api.focusFooterButton();
43371
+ return true;
43080
43372
  case "Escape":
43081
43373
  event.preventDefault();
43082
43374
  api.dismiss();
@@ -70113,7 +70405,7 @@ function legacy(parser) {
70113
70405
  return new LanguageSupport(StreamLanguage.define(parser));
70114
70406
  }
70115
70407
  function sql(dialectName) {
70116
- return import('./index-DnbjsrWF.js').then(m => m.sql({ dialect: m[dialectName] }));
70408
+ return import('./index-MGiiJ6ZI.js').then(m => m.sql({ dialect: m[dialectName] }));
70117
70409
  }
70118
70410
  /**
70119
70411
  An array of language descriptions for known language packages.
@@ -70124,7 +70416,7 @@ const languages = [
70124
70416
  name: "C",
70125
70417
  extensions: ["c", "h", "ino"],
70126
70418
  load() {
70127
- return import('./index-CsaeJbB5.js').then(m => m.cpp());
70419
+ return import('./index-DOSUqw2E.js').then(m => m.cpp());
70128
70420
  }
70129
70421
  }),
70130
70422
  /*@__PURE__*/LanguageDescription.of({
@@ -70132,7 +70424,7 @@ const languages = [
70132
70424
  alias: ["cpp"],
70133
70425
  extensions: ["cpp", "c++", "cc", "cxx", "hpp", "h++", "hh", "hxx"],
70134
70426
  load() {
70135
- return import('./index-CsaeJbB5.js').then(m => m.cpp());
70427
+ return import('./index-DOSUqw2E.js').then(m => m.cpp());
70136
70428
  }
70137
70429
  }),
70138
70430
  /*@__PURE__*/LanguageDescription.of({
@@ -70145,14 +70437,14 @@ const languages = [
70145
70437
  name: "CSS",
70146
70438
  extensions: ["css"],
70147
70439
  load() {
70148
- return import('./index-mEwvGwcX.js').then(m => m.css());
70440
+ return import('./index-DKpql_7V.js').then(m => m.css());
70149
70441
  }
70150
70442
  }),
70151
70443
  /*@__PURE__*/LanguageDescription.of({
70152
70444
  name: "Go",
70153
70445
  extensions: ["go"],
70154
70446
  load() {
70155
- return import('./index-BQa2bres.js').then(m => m.go());
70447
+ return import('./index-BFdVwXxe.js').then(m => m.go());
70156
70448
  }
70157
70449
  }),
70158
70450
  /*@__PURE__*/LanguageDescription.of({
@@ -70160,14 +70452,14 @@ const languages = [
70160
70452
  alias: ["xhtml"],
70161
70453
  extensions: ["html", "htm", "handlebars", "hbs"],
70162
70454
  load() {
70163
- return import('./index-Dk6UNQWp.js').then(m => m.html());
70455
+ return import('./index-DH5dBgli.js').then(m => m.html());
70164
70456
  }
70165
70457
  }),
70166
70458
  /*@__PURE__*/LanguageDescription.of({
70167
70459
  name: "Java",
70168
70460
  extensions: ["java"],
70169
70461
  load() {
70170
- return import('./index-BBMUH80b.js').then(m => m.java());
70462
+ return import('./index-Ce_sp8_S.js').then(m => m.java());
70171
70463
  }
70172
70464
  }),
70173
70465
  /*@__PURE__*/LanguageDescription.of({
@@ -70175,14 +70467,14 @@ const languages = [
70175
70467
  alias: ["ecmascript", "js", "node"],
70176
70468
  extensions: ["js", "mjs", "cjs"],
70177
70469
  load() {
70178
- return import('./index-BIHNHMyg.js').then(m => m.javascript());
70470
+ return import('./index-B44gf6lc.js').then(m => m.javascript());
70179
70471
  }
70180
70472
  }),
70181
70473
  /*@__PURE__*/LanguageDescription.of({
70182
70474
  name: "Jinja",
70183
70475
  extensions: ["j2", "jinja", "jinja2"],
70184
70476
  load() {
70185
- return import('./index-paWgasj8.js').then(m => m.jinja());
70477
+ return import('./index-D_DH6CLj.js').then(m => m.jinja());
70186
70478
  }
70187
70479
  }),
70188
70480
  /*@__PURE__*/LanguageDescription.of({
@@ -70190,28 +70482,28 @@ const languages = [
70190
70482
  alias: ["json5"],
70191
70483
  extensions: ["json", "map"],
70192
70484
  load() {
70193
- return import('./index-B4R1rT37.js').then(m => m.json());
70485
+ return import('./index-DnZLLzeC.js').then(m => m.json());
70194
70486
  }
70195
70487
  }),
70196
70488
  /*@__PURE__*/LanguageDescription.of({
70197
70489
  name: "JSX",
70198
70490
  extensions: ["jsx"],
70199
70491
  load() {
70200
- return import('./index-BIHNHMyg.js').then(m => m.javascript({ jsx: true }));
70492
+ return import('./index-B44gf6lc.js').then(m => m.javascript({ jsx: true }));
70201
70493
  }
70202
70494
  }),
70203
70495
  /*@__PURE__*/LanguageDescription.of({
70204
70496
  name: "LESS",
70205
70497
  extensions: ["less"],
70206
70498
  load() {
70207
- return import('./index-C-bL3Cy9.js').then(m => m.less());
70499
+ return import('./index-C7heuyel.js').then(m => m.less());
70208
70500
  }
70209
70501
  }),
70210
70502
  /*@__PURE__*/LanguageDescription.of({
70211
70503
  name: "Liquid",
70212
70504
  extensions: ["liquid"],
70213
70505
  load() {
70214
- return import('./index-Dv14TLjz.js').then(m => m.liquid());
70506
+ return import('./index-CvcXtLfn.js').then(m => m.liquid());
70215
70507
  }
70216
70508
  }),
70217
70509
  /*@__PURE__*/LanguageDescription.of({
@@ -70222,7 +70514,7 @@ const languages = [
70222
70514
  name: "Markdown",
70223
70515
  extensions: ["md", "markdown", "mkd"],
70224
70516
  load() {
70225
- return import('./index-LyNcRvuh.js').then(m => m.markdown());
70517
+ return import('./index-ImG4tGZ8.js').then(m => m.markdown());
70226
70518
  }
70227
70519
  }),
70228
70520
  /*@__PURE__*/LanguageDescription.of({
@@ -70237,7 +70529,7 @@ const languages = [
70237
70529
  name: "PHP",
70238
70530
  extensions: ["php", "php3", "php4", "php5", "php7", "phtml"],
70239
70531
  load() {
70240
- return import('./index-DveiglG-.js').then(m => m.php());
70532
+ return import('./index-BiT1CaUK.js').then(m => m.php());
70241
70533
  }
70242
70534
  }),
70243
70535
  /*@__PURE__*/LanguageDescription.of({
@@ -70254,28 +70546,28 @@ const languages = [
70254
70546
  extensions: ["BUILD", "bzl", "py", "pyw"],
70255
70547
  filename: /^(BUCK|BUILD)$/,
70256
70548
  load() {
70257
- return import('./index-DsUx-yKw.js').then(m => m.python());
70549
+ return import('./index-Ck4GUp6S.js').then(m => m.python());
70258
70550
  }
70259
70551
  }),
70260
70552
  /*@__PURE__*/LanguageDescription.of({
70261
70553
  name: "Rust",
70262
70554
  extensions: ["rs"],
70263
70555
  load() {
70264
- return import('./index-CyTfufcP.js').then(m => m.rust());
70556
+ return import('./index-F6dIPhGZ.js').then(m => m.rust());
70265
70557
  }
70266
70558
  }),
70267
70559
  /*@__PURE__*/LanguageDescription.of({
70268
70560
  name: "Sass",
70269
70561
  extensions: ["sass"],
70270
70562
  load() {
70271
- return import('./index-D7noxe1h.js').then(m => m.sass({ indented: true }));
70563
+ return import('./index-BuU-LxbL.js').then(m => m.sass({ indented: true }));
70272
70564
  }
70273
70565
  }),
70274
70566
  /*@__PURE__*/LanguageDescription.of({
70275
70567
  name: "SCSS",
70276
70568
  extensions: ["scss"],
70277
70569
  load() {
70278
- return import('./index-D7noxe1h.js').then(m => m.sass());
70570
+ return import('./index-BuU-LxbL.js').then(m => m.sass());
70279
70571
  }
70280
70572
  }),
70281
70573
  /*@__PURE__*/LanguageDescription.of({
@@ -70291,7 +70583,7 @@ const languages = [
70291
70583
  name: "TSX",
70292
70584
  extensions: ["tsx"],
70293
70585
  load() {
70294
- return import('./index-BIHNHMyg.js').then(m => m.javascript({ jsx: true, typescript: true }));
70586
+ return import('./index-B44gf6lc.js').then(m => m.javascript({ jsx: true, typescript: true }));
70295
70587
  }
70296
70588
  }),
70297
70589
  /*@__PURE__*/LanguageDescription.of({
@@ -70299,14 +70591,14 @@ const languages = [
70299
70591
  alias: ["ts"],
70300
70592
  extensions: ["ts", "mts", "cts"],
70301
70593
  load() {
70302
- return import('./index-BIHNHMyg.js').then(m => m.javascript({ typescript: true }));
70594
+ return import('./index-B44gf6lc.js').then(m => m.javascript({ typescript: true }));
70303
70595
  }
70304
70596
  }),
70305
70597
  /*@__PURE__*/LanguageDescription.of({
70306
70598
  name: "WebAssembly",
70307
70599
  extensions: ["wat", "wast"],
70308
70600
  load() {
70309
- return import('./index-BJKQPlcf.js').then(m => m.wast());
70601
+ return import('./index-4Mg03NZ0.js').then(m => m.wast());
70310
70602
  }
70311
70603
  }),
70312
70604
  /*@__PURE__*/LanguageDescription.of({
@@ -70314,7 +70606,7 @@ const languages = [
70314
70606
  alias: ["rss", "wsdl", "xsd"],
70315
70607
  extensions: ["xml", "xsl", "xsd", "svg"],
70316
70608
  load() {
70317
- return import('./index-CFr33LuW.js').then(m => m.xml());
70609
+ return import('./index-B72hrXZp.js').then(m => m.xml());
70318
70610
  }
70319
70611
  }),
70320
70612
  /*@__PURE__*/LanguageDescription.of({
@@ -70322,7 +70614,7 @@ const languages = [
70322
70614
  alias: ["yml"],
70323
70615
  extensions: ["yaml", "yml"],
70324
70616
  load() {
70325
- return import('./index-DztBKea2.js').then(m => m.yaml());
70617
+ return import('./index-BdcQa21o.js').then(m => m.yaml());
70326
70618
  }
70327
70619
  }),
70328
70620
  // Legacy modes ported from CodeMirror 5
@@ -71111,13 +71403,13 @@ const languages = [
71111
71403
  name: "Vue",
71112
71404
  extensions: ["vue"],
71113
71405
  load() {
71114
- return import('./index-COD_xliD.js').then(m => m.vue());
71406
+ return import('./index-BkpCLtjh.js').then(m => m.vue());
71115
71407
  }
71116
71408
  }),
71117
71409
  /*@__PURE__*/LanguageDescription.of({
71118
71410
  name: "Angular Template",
71119
71411
  load() {
71120
- return import('./index-DnpXm_wT.js').then(m => m.angular());
71412
+ return import('./index-BzH29qwV.js').then(m => m.angular());
71121
71413
  }
71122
71414
  })
71123
71415
  ];
@@ -121927,6 +122219,7 @@ const emojiSuggestProsePlugin = $prose((ctx) => {
121927
122219
  selectedIndex = prev.selectedIndex;
121928
122220
  if (nav === "next") selectedIndex = (selectedIndex + 1) % items.length;
121929
122221
  if (nav === "prev") selectedIndex = (selectedIndex - 1 + items.length) % items.length;
122222
+ if (typeof nav === "number") selectedIndex = Math.min(Math.max(0, nav), items.length - 1);
121930
122223
  }
121931
122224
  return {
121932
122225
  active: true,
@@ -121970,6 +122263,13 @@ const emojiSuggestProsePlugin = $prose((ctx) => {
121970
122263
  }
121971
122264
  });
121972
122265
  });
122266
+ function setEmojiSuggestIndex(view, index) {
122267
+ view.dispatch(view.state.tr.setMeta(emojiSuggestNavMeta, index));
122268
+ }
122269
+ function insertEmojiFromSuggest(ctx, from, to, emoji) {
122270
+ const view = ctx.get(editorViewCtx);
122271
+ insertPicked(view, ctx, from, to, emoji);
122272
+ }
121973
122273
 
121974
122274
  const EMOJI_TOOLTIP_NAME = "emoji-suggest";
121975
122275
  const emojiTooltip = tooltipFactory("BRAINFISH_EMOJI");
@@ -121992,6 +122292,10 @@ function tooltipPosition(view, panel) {
121992
122292
  const top = above ? coords.top - r.top - GAP : coords.bottom - r.top + GAP;
121993
122293
  return { top, left, above };
121994
122294
  }
122295
+ function optionIndex(target) {
122296
+ const raw = target.closest?.("[data-index]")?.getAttribute("data-index");
122297
+ return raw == null ? null : Number(raw);
122298
+ }
121995
122299
  const EmojiTooltipView = () => {
121996
122300
  const { view } = usePluginViewContext();
121997
122301
  const { getInstance } = useEditorAction();
@@ -122017,17 +122321,43 @@ const EmojiTooltipView = () => {
122017
122321
  left: pos.left,
122018
122322
  transform: pos.above ? "translateY(-100%)" : void 0
122019
122323
  },
122020
- children: /* @__PURE__ */ jsx("div", { className: "menu-groups", children: /* @__PURE__ */ jsx("div", { className: "menu-group", children: /* @__PURE__ */ jsx("ul", { children: panel.items.map((item, index) => {
122021
- const selected = index === panel.selectedIndex;
122022
- return /* @__PURE__ */ jsxs("li", { "data-index": index, className: selected ? "hover" : void 0, children: [
122023
- /* @__PURE__ */ jsx("span", { className: "text-lg leading-none", "aria-hidden": true, children: item.emoji }),
122024
- /* @__PURE__ */ jsxs("span", { className: "truncate opacity-80", children: [
122025
- ":",
122026
- item.name,
122027
- ":"
122028
- ] })
122029
- ] }, `${item.name}-${index}`);
122030
- }) }) }) })
122324
+ children: /* @__PURE__ */ jsx("div", { className: "menu-groups", children: /* @__PURE__ */ jsx("div", { className: "menu-group", children: /* @__PURE__ */ jsx(
122325
+ "ul",
122326
+ {
122327
+ role: "listbox",
122328
+ onMouseMove: (event) => {
122329
+ const index = optionIndex(event.target);
122330
+ if (index !== null && index !== panel.selectedIndex && view) setEmojiSuggestIndex(view, index);
122331
+ },
122332
+ onMouseDown: (event) => {
122333
+ event.preventDefault();
122334
+ const index = optionIndex(event.target);
122335
+ const item = index === null ? void 0 : panel.items[index];
122336
+ if (item) insertEmojiFromSuggest(ctx, panel.from, panel.to, item.emoji);
122337
+ },
122338
+ children: panel.items.map((item, index) => {
122339
+ const selected = index === panel.selectedIndex;
122340
+ return /* @__PURE__ */ jsxs(
122341
+ "li",
122342
+ {
122343
+ role: "option",
122344
+ "aria-selected": selected,
122345
+ "data-index": index,
122346
+ className: selected ? "hover" : void 0,
122347
+ children: [
122348
+ /* @__PURE__ */ jsx("span", { className: "text-lg leading-none", "aria-hidden": true, children: item.emoji }),
122349
+ /* @__PURE__ */ jsxs("span", { className: "truncate opacity-80", children: [
122350
+ ":",
122351
+ item.name,
122352
+ ":"
122353
+ ] })
122354
+ ]
122355
+ },
122356
+ `${item.name}-${index}`
122357
+ );
122358
+ })
122359
+ }
122360
+ ) }) })
122031
122361
  }
122032
122362
  );
122033
122363
  };
@@ -124232,6 +124562,7 @@ function setupCollabContentChange(crepe, onContentChangeRef, localEditPendingRef
124232
124562
  });
124233
124563
  }
124234
124564
 
124565
+ const SLASH_MENU_SELECTOR = `.milkdown-slash-menu:not([data-name="${EMOJI_TOOLTIP_NAME}"])`;
124235
124566
  function useSlashMenuPortal(container) {
124236
124567
  const reactId = useId();
124237
124568
  const portalRootId = `bf-slash-menu-portal-${reactId.replace(/:/g, "")}`;
@@ -124276,8 +124607,8 @@ function useSlashMenuPortal(container) {
124276
124607
  return portalRoot;
124277
124608
  };
124278
124609
  const portalSlashMenu = () => {
124279
- const existing = document.getElementById(portalRootId)?.querySelector(".milkdown-slash-menu");
124280
- const menu = container.querySelector(".milkdown-slash-menu") ?? (existing instanceof HTMLElement ? existing : null);
124610
+ const existing = document.getElementById(portalRootId)?.querySelector(SLASH_MENU_SELECTOR);
124611
+ const menu = container.querySelector(SLASH_MENU_SELECTOR) ?? (existing instanceof HTMLElement ? existing : null);
124281
124612
  if (!menu) return;
124282
124613
  const root = getOrCreatePortalRoot();
124283
124614
  if (menu.parentElement !== root) {
@@ -124309,7 +124640,7 @@ function useSlashMenuPortal(container) {
124309
124640
  observer.disconnect();
124310
124641
  window.removeEventListener("scroll", onScrollOrResize, true);
124311
124642
  window.removeEventListener("resize", onScrollOrResize);
124312
- const menu = document.getElementById(portalRootId)?.querySelector(".milkdown-slash-menu");
124643
+ const menu = document.getElementById(portalRootId)?.querySelector(SLASH_MENU_SELECTOR);
124313
124644
  if (menu && originalParent?.isConnected) {
124314
124645
  originalParent.insertBefore(menu, originalNextSibling);
124315
124646
  }
@@ -124319,7 +124650,7 @@ function useSlashMenuPortal(container) {
124319
124650
  }
124320
124651
 
124321
124652
  const ReviewSuggestionsViewport = lazy(
124322
- () => import('./index-CgzQ1jj0.js').then((module) => ({ default: module.ReviewSuggestionsViewport }))
124653
+ () => import('./index-DRSHBn8E.js').then((module) => ({ default: module.ReviewSuggestionsViewport }))
124323
124654
  );
124324
124655
  const MilkdownEditorInner = ({
124325
124656
  readOnly = false,
@@ -124543,7 +124874,18 @@ const MilkdownEditor = (props) => {
124543
124874
  const mentions = props.mentions ?? [];
124544
124875
  const mentionsRef = useRef(mentions);
124545
124876
  mentionsRef.current = mentions;
124546
- const mentionsRuntime = useMemo(() => ({ providersRef: mentionsRef, providers: mentions }), [mentions]);
124877
+ const onFooterButtonClickRef = useRef(props.footerButton?.onClick);
124878
+ onFooterButtonClickRef.current = props.footerButton?.onClick;
124879
+ const footerButtonLabel = props.footerButton?.label;
124880
+ const mentionsRuntime = useMemo(
124881
+ () => ({
124882
+ providersRef: mentionsRef,
124883
+ providers: mentions,
124884
+ onFooterButtonClickRef,
124885
+ footerButtonLabel
124886
+ }),
124887
+ [mentions, footerButtonLabel]
124888
+ );
124547
124889
  return /* @__PURE__ */ jsx(
124548
124890
  VariableContentChannelsContext.Provider,
124549
124891
  {
@@ -124652,7 +124994,8 @@ const MarkdownSourceEditor = React.forwardRef(
124652
124994
  onChange,
124653
124995
  slashMenu = "basic",
124654
124996
  toolbar = "basic",
124655
- mentions
124997
+ mentions,
124998
+ footerButton
124656
124999
  }, ref) {
124657
125000
  const [mode, setMode] = React.useState("markdown");
124658
125001
  const [markdown, setMarkdown] = React.useState(initialMarkdown);
@@ -124720,6 +125063,7 @@ const MarkdownSourceEditor = React.forwardRef(
124720
125063
  toolbar,
124721
125064
  placeholder,
124722
125065
  mentions,
125066
+ footerButton,
124723
125067
  onContentChange: handleEditorChange
124724
125068
  },
124725
125069
  editorKey