@tutti-os/ui-rich-text 0.0.124 → 0.0.125

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.
@@ -14,11 +14,10 @@ import {
14
14
  } from "../chunk-AB4LAEBH.js";
15
15
  import {
16
16
  createRichTextMentionAttrs,
17
- createRichTextTriggerRegistry,
18
17
  getRichTextMentionDisplayText,
19
18
  renderRichTextTriggerInsertResult,
20
19
  resolveRichTextMentionView
21
- } from "../chunk-RUX5KKX3.js";
20
+ } from "../chunk-CLNWVUDR.js";
22
21
  import {
23
22
  findRichTextMarkdownLinks,
24
23
  isRichTextMentionHref,
@@ -30,13 +29,18 @@ import {
30
29
  serializeRichTextDocumentToContent,
31
30
  workspaceReferenceNodeName
32
31
  } from "../chunk-RXT3DN3A.js";
32
+ import {
33
+ createRichTextMentionIdentityKey,
34
+ createRichTextMentionService
35
+ } from "../chunk-XN4EGZGR.js";
36
+ import "../chunk-3UTSSMV7.js";
33
37
 
34
38
  // src/editor/RichTextTriggerEditor.tsx
35
39
  import {
36
- useCallback,
37
- useEffect as useEffect2,
40
+ useCallback as useCallback2,
41
+ useEffect as useEffect3,
38
42
  useLayoutEffect,
39
- useMemo,
43
+ useMemo as useMemo2,
40
44
  useRef,
41
45
  useState as useState2
42
46
  } from "react";
@@ -48,6 +52,64 @@ import { EditorContent, useEditor } from "@tiptap/react";
48
52
  import { ViewportMenuSurface } from "@tutti-os/ui-system/components";
49
53
  import { cn } from "@tutti-os/ui-system/utils";
50
54
 
55
+ // src/editor/RichTextMentionServiceProvider.tsx
56
+ import {
57
+ createContext,
58
+ useCallback,
59
+ useContext,
60
+ useEffect,
61
+ useMemo,
62
+ useSyncExternalStore
63
+ } from "react";
64
+ import { jsx } from "react/jsx-runtime";
65
+ var RichTextMentionServiceContext = createContext(null);
66
+ var idleSnapshot = Object.freeze({ state: "idle" });
67
+ function RichTextMentionServiceProvider({
68
+ service,
69
+ children
70
+ }) {
71
+ return /* @__PURE__ */ jsx(RichTextMentionServiceContext.Provider, { value: service, children });
72
+ }
73
+ function useRichTextMentionService(explicitService) {
74
+ const contextService = useContext(RichTextMentionServiceContext);
75
+ return explicitService ?? contextService;
76
+ }
77
+ function useEffectiveRichTextMentionService(input) {
78
+ const contextService = useRichTextMentionService();
79
+ const legacyService = useMemo(
80
+ () => input.mentionService || contextService ? null : createRichTextMentionService({
81
+ providers: input.triggerProviders ?? []
82
+ }),
83
+ [contextService, input.mentionService, input.triggerProviders]
84
+ );
85
+ useEffect(() => () => legacyService?.dispose(), [legacyService]);
86
+ return input.mentionService ?? contextService ?? legacyService;
87
+ }
88
+ function useResolvedRichTextMention(identity, explicitService) {
89
+ const service = useRichTextMentionService(explicitService);
90
+ const identityKey = createRichTextMentionIdentityKey(identity);
91
+ const stableIdentity = useMemo(
92
+ () => identity,
93
+ // Identity presentation fallback changes must still refresh the hook input.
94
+ // eslint-disable-next-line react-hooks/exhaustive-deps
95
+ [identityKey, identity.label]
96
+ );
97
+ const subscribe = useCallback(
98
+ (listener) => service?.subscribe(listener, stableIdentity) ?? (() => {
99
+ }),
100
+ [service, stableIdentity]
101
+ );
102
+ const getSnapshot = useCallback(
103
+ () => service?.getSnapshot(stableIdentity) ?? idleSnapshot,
104
+ [service, stableIdentity]
105
+ );
106
+ const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
107
+ useEffect(() => {
108
+ if (service) void service.resolve(stableIdentity);
109
+ }, [service, stableIdentity]);
110
+ return snapshot;
111
+ }
112
+
51
113
  // src/editor/richTextTriggerQuery.ts
52
114
  function isRichTextTriggerPrefixBoundary(character, boundary) {
53
115
  if (boundary === "whitespace") {
@@ -229,7 +291,7 @@ function resolveTopStartPlacement(cursorRect, menuOffset) {
229
291
 
230
292
  // src/editor/RichTextTriggerMenuItem.tsx
231
293
  import { FileIcon, FolderFilledIcon } from "@tutti-os/ui-system/icons";
232
- import { jsx, jsxs } from "react/jsx-runtime";
294
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
233
295
  function RichTextTriggerMenuItem({
234
296
  iconUrl,
235
297
  label,
@@ -253,7 +315,7 @@ function RichTextTriggerMenuItem({
253
315
  onSelect();
254
316
  },
255
317
  children: [
256
- /* @__PURE__ */ jsx(
318
+ /* @__PURE__ */ jsx2(
257
319
  RichTextTriggerMenuIcon,
258
320
  {
259
321
  iconUrl,
@@ -261,8 +323,8 @@ function RichTextTriggerMenuItem({
261
323
  }
262
324
  ),
263
325
  /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-auto flex-col items-start gap-0.5", children: [
264
- /* @__PURE__ */ jsx("span", { className: "w-full overflow-hidden text-ellipsis whitespace-nowrap text-[13px] leading-5 font-semibold", children: label }),
265
- subtitle ? /* @__PURE__ */ jsx("span", { className: "w-full overflow-hidden text-ellipsis whitespace-nowrap text-[11px] leading-4 text-[var(--text-secondary)]", children: subtitle }) : null
326
+ /* @__PURE__ */ jsx2("span", { className: "w-full overflow-hidden text-ellipsis whitespace-nowrap text-[13px] leading-5 font-semibold", children: label }),
327
+ subtitle ? /* @__PURE__ */ jsx2("span", { className: "w-full overflow-hidden text-ellipsis whitespace-nowrap text-[11px] leading-4 text-[var(--text-secondary)]", children: subtitle }) : null
266
328
  ] })
267
329
  ]
268
330
  }
@@ -275,23 +337,23 @@ function RichTextTriggerMenuIcon({
275
337
  const normalizedIconUrl = iconUrl?.trim() ?? "";
276
338
  if (workspaceReferenceFileKind) {
277
339
  const Icon = workspaceReferenceFileKind === "folder" ? FolderFilledIcon : FileIcon;
278
- return /* @__PURE__ */ jsx(
340
+ return /* @__PURE__ */ jsx2(
279
341
  "span",
280
342
  {
281
343
  "aria-hidden": "true",
282
344
  className: "inline-grid size-4 flex-none place-items-center text-[var(--folder)]",
283
345
  "data-rich-text-trigger-icon": "true",
284
- children: /* @__PURE__ */ jsx(Icon, { className: "size-4" })
346
+ children: /* @__PURE__ */ jsx2(Icon, { className: "size-4" })
285
347
  }
286
348
  );
287
349
  }
288
- return /* @__PURE__ */ jsx(
350
+ return /* @__PURE__ */ jsx2(
289
351
  "span",
290
352
  {
291
353
  "aria-hidden": "true",
292
354
  className: "inline-grid size-4 flex-none place-items-center overflow-hidden rounded bg-[var(--bg-block,var(--transparency-block))]",
293
355
  "data-rich-text-trigger-icon": "true",
294
- children: normalizedIconUrl ? /* @__PURE__ */ jsx(
356
+ children: normalizedIconUrl ? /* @__PURE__ */ jsx2(
295
357
  "img",
296
358
  {
297
359
  alt: "",
@@ -301,7 +363,7 @@ function RichTextTriggerMenuIcon({
301
363
  loading: "lazy",
302
364
  src: normalizedIconUrl
303
365
  }
304
- ) : /* @__PURE__ */ jsx("span", { className: "block size-3 rounded-[3px] bg-[var(--transparency-block)]" })
366
+ ) : /* @__PURE__ */ jsx2("span", { className: "block size-3 rounded-[3px] bg-[var(--transparency-block)]" })
305
367
  }
306
368
  );
307
369
  }
@@ -312,30 +374,19 @@ import { ReactNodeViewRenderer } from "@tiptap/react";
312
374
 
313
375
  // src/extensions/MentionReferenceNodeView.tsx
314
376
  import { NodeViewWrapper } from "@tiptap/react";
315
- import {
316
- MentionPill
317
- } from "@tutti-os/ui-system/components";
318
- import { jsx as jsx2 } from "react/jsx-runtime";
319
- var richTextMentionReferencePillClassName = "max-w-[16rem]";
377
+ import { MentionPill } from "@tutti-os/ui-system/components";
378
+
379
+ // src/extensions/mentionPillPresentation.ts
320
380
  function readStringAttr(value) {
321
381
  return typeof value === "string" ? value.trim() : "";
322
382
  }
323
383
  function readMentionPresentationUrl(presentation) {
324
- if (!presentation || typeof presentation !== "object") {
325
- return null;
326
- }
327
- const value = presentation.iconUrl ?? presentation.thumbnailUrl;
328
- if (typeof value !== "string") {
329
- return null;
330
- }
331
- const trimmed = value.trim();
384
+ const value = presentation?.iconUrl ?? presentation?.thumbnailUrl;
385
+ const trimmed = readStringAttr(value);
332
386
  return trimmed || null;
333
387
  }
334
388
  function readMentionScopeValue(scope, key) {
335
- if (!scope || typeof scope !== "object") {
336
- return "";
337
- }
338
- return readStringAttr(scope[key]);
389
+ return readStringAttr(scope?.[key]);
339
390
  }
340
391
  function resolveMentionPillKind(providerId, scope) {
341
392
  const id = providerId.trim();
@@ -356,24 +407,37 @@ function resolveMentionPillKind(providerId, scope) {
356
407
  }
357
408
  return "issue";
358
409
  }
410
+ function resolveMentionPillIconUrl(input) {
411
+ return readMentionPresentationUrl(input.presentation) || readMentionScopeValue(input.scope, "icon") || null;
412
+ }
413
+
414
+ // src/extensions/MentionReferenceNodeView.tsx
415
+ import { jsx as jsx3 } from "react/jsx-runtime";
416
+ var richTextMentionReferencePillClassName = "max-w-[16rem]";
417
+ function readStringAttr2(value) {
418
+ return typeof value === "string" ? value.trim() : "";
419
+ }
359
420
  function MentionReferenceNodeView({
360
421
  node,
361
422
  selected
362
423
  }) {
363
424
  const label = typeof node.attrs.label === "string" ? node.attrs.label.trim().replace(/^@+/, "").trim() : "";
364
- const iconUrl = readMentionPresentationUrl(node.attrs.presentation) || readMentionScopeValue(node.attrs.scope, "icon");
425
+ const iconUrl = resolveMentionPillIconUrl({
426
+ presentation: node.attrs.presentation,
427
+ scope: node.attrs.scope
428
+ });
365
429
  const kind = resolveMentionPillKind(
366
- readStringAttr(node.attrs.providerId),
430
+ readStringAttr2(node.attrs.providerId),
367
431
  node.attrs.scope
368
432
  );
369
- return /* @__PURE__ */ jsx2(
433
+ return /* @__PURE__ */ jsx3(
370
434
  NodeViewWrapper,
371
435
  {
372
436
  as: "span",
373
437
  className: `inline-flex max-w-full align-baseline${selected ? " is-selected" : ""}`,
374
438
  contentEditable: false,
375
439
  "data-rich-text-mention-reference": "true",
376
- children: /* @__PURE__ */ jsx2(
440
+ children: /* @__PURE__ */ jsx3(
377
441
  MentionPill,
378
442
  {
379
443
  className: richTextMentionReferencePillClassName,
@@ -444,7 +508,7 @@ import { mergeAttributes as mergeAttributes2, Node as Node2 } from "@tiptap/core
444
508
  import { ReactNodeViewRenderer as ReactNodeViewRenderer2 } from "@tiptap/react";
445
509
 
446
510
  // src/extensions/WorkspaceReferenceNodeView.tsx
447
- import { useEffect, useState } from "react";
511
+ import { useEffect as useEffect2, useState } from "react";
448
512
  import { NodeViewWrapper as NodeViewWrapper2 } from "@tiptap/react";
449
513
  import {
450
514
  MentionPill as MentionPill2,
@@ -494,7 +558,7 @@ function isPathRoot(path) {
494
558
  }
495
559
 
496
560
  // src/extensions/WorkspaceReferenceNodeView.tsx
497
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
561
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
498
562
  var richTextWorkspaceReferencePillClassName = "max-w-[18rem]";
499
563
  function WorkspaceReferenceNodeView({
500
564
  deleteNode,
@@ -511,7 +575,7 @@ function WorkspaceReferenceNodeView({
511
575
  const path = typeof attrs.path === "string" ? attrs.path : "";
512
576
  const presentation = getWorkspaceReferencePresentation(label, path);
513
577
  const removeActionAriaLabel = typeof extensionOptions.removeActionAriaLabel === "string" ? extensionOptions.removeActionAriaLabel : defaultRichTextTriggerText.removeReferenceActionLabel;
514
- useEffect(() => {
578
+ useEffect2(() => {
515
579
  const syncEditable = () => {
516
580
  setIsEditable(editor.isEditable);
517
581
  };
@@ -531,7 +595,7 @@ function WorkspaceReferenceNodeView({
531
595
  }
532
596
  deleteNode();
533
597
  };
534
- return /* @__PURE__ */ jsx3(
598
+ return /* @__PURE__ */ jsx4(
535
599
  NodeViewWrapper2,
536
600
  {
537
601
  as: "span",
@@ -539,7 +603,7 @@ function WorkspaceReferenceNodeView({
539
603
  contentEditable: false,
540
604
  "data-rich-text-workspace-reference": "true",
541
605
  children: /* @__PURE__ */ jsxs2(Tooltip, { children: [
542
- /* @__PURE__ */ jsx3(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx3(
606
+ /* @__PURE__ */ jsx4(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx4(
543
607
  MentionPill2,
544
608
  {
545
609
  className: richTextWorkspaceReferencePillClassName,
@@ -554,7 +618,7 @@ function WorkspaceReferenceNodeView({
554
618
  } : void 0
555
619
  }
556
620
  ) }),
557
- /* @__PURE__ */ jsx3(
621
+ /* @__PURE__ */ jsx4(
558
622
  TooltipContent,
559
623
  {
560
624
  className: "max-w-md whitespace-normal break-all",
@@ -638,7 +702,7 @@ var WorkspaceReference = Node2.create({
638
702
  });
639
703
 
640
704
  // src/editor/RichTextTriggerEditor.tsx
641
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
705
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
642
706
  var RICH_TEXT_MENTION_PRESENTATION_KEYS = [
643
707
  "agentProviderId",
644
708
  "agentIconUrl",
@@ -656,7 +720,8 @@ var RICH_TEXT_MENTION_PRESENTATION_KEYS = [
656
720
  function RichTextTriggerEditor({
657
721
  value,
658
722
  onChange,
659
- triggerProviders = [],
723
+ triggerProviders,
724
+ mentionService,
660
725
  placeholder,
661
726
  disabled = false,
662
727
  className,
@@ -687,11 +752,11 @@ function RichTextTriggerEditor({
687
752
  const mentionHydrationRequestRef = useRef(0);
688
753
  const suppressPastedAtQueryRef = useRef(false);
689
754
  const containerRef = useRef(null);
690
- const registry = useMemo(
691
- () => createRichTextTriggerRegistry(triggerProviders),
692
- [triggerProviders]
693
- );
694
- const activeTriggerConfigs = useMemo(
755
+ const registry = useEffectiveRichTextMentionService({
756
+ mentionService,
757
+ triggerProviders
758
+ });
759
+ const activeTriggerConfigs = useMemo2(
695
760
  () => registry.listTriggerConfigs(),
696
761
  [registry]
697
762
  );
@@ -710,7 +775,7 @@ function RichTextTriggerEditor({
710
775
  const [isLoading, setIsLoading] = useState2(false);
711
776
  const [resolvedMenuAnchor, setResolvedMenuAnchor] = useState2(null);
712
777
  latestOnChangeRef.current = onChange;
713
- const resetMenuPlacement = useCallback(() => {
778
+ const resetMenuPlacement = useCallback2(() => {
714
779
  setResolvedMenuAnchor(null);
715
780
  }, []);
716
781
  const editor = useEditor({
@@ -755,7 +820,7 @@ function RichTextTriggerEditor({
755
820
  latestOnChangeRef.current(serialized);
756
821
  }
757
822
  });
758
- useEffect2(() => {
823
+ useEffect3(() => {
759
824
  if (!editor) {
760
825
  return;
761
826
  }
@@ -777,34 +842,32 @@ function RichTextTriggerEditor({
777
842
  );
778
843
  lastSerializedValueRef.current = normalizedValue;
779
844
  }, [editor, normalizedValue]);
780
- useEffect2(() => {
845
+ useEffect3(() => {
781
846
  if (!editor) {
782
847
  return;
783
848
  }
784
- const requestId = mentionHydrationRequestRef.current + 1;
785
- mentionHydrationRequestRef.current = requestId;
786
- const mentions = collectHydratableMentionNodes(editor);
787
- if (mentions.length === 0) {
788
- return;
789
- }
790
- for (const mention of mentions) {
791
- const provider = registry.getProvider(mention.attrs.providerId);
792
- if (!provider?.resolveMention) {
793
- continue;
794
- }
795
- void Promise.resolve(provider.resolveMention(mention.attrs)).then((resolved) => {
796
- if (!resolved || mentionHydrationRequestRef.current !== requestId || editor.isDestroyed) {
797
- return;
798
- }
799
- applyResolvedMentionAttrs(editor, mention.pos, mention.attrs, {
800
- label: resolved.label,
801
- presentation: resolved.presentation
849
+ const hydrateMentions = () => {
850
+ const requestId = mentionHydrationRequestRef.current + 1;
851
+ mentionHydrationRequestRef.current = requestId;
852
+ const mentions = collectHydratableMentionNodes(editor);
853
+ for (const mention of mentions) {
854
+ void registry.resolve(mention.attrs).then((snapshot) => {
855
+ const resolved = snapshot.state === "ready" ? snapshot.resolved : void 0;
856
+ if (!resolved || mentionHydrationRequestRef.current !== requestId || editor.isDestroyed) {
857
+ return;
858
+ }
859
+ applyResolvedMentionAttrs(editor, mention.pos, mention.attrs, {
860
+ label: resolved.label,
861
+ presentation: resolved.presentation
862
+ });
863
+ }).catch(() => {
802
864
  });
803
- }).catch(() => {
804
- });
805
- }
865
+ }
866
+ };
867
+ hydrateMentions();
868
+ return registry.subscribe(hydrateMentions);
806
869
  }, [editor, registry, normalizedValue]);
807
- useEffect2(() => {
870
+ useEffect3(() => {
808
871
  if (!editor) {
809
872
  return;
810
873
  }
@@ -814,7 +877,7 @@ function RichTextTriggerEditor({
814
877
  lastFocusSignalRef.current = focusSignal;
815
878
  editor.commands.focus("end");
816
879
  }, [editor, focusSignal]);
817
- useEffect2(() => {
880
+ useEffect3(() => {
818
881
  if (!editor) {
819
882
  return;
820
883
  }
@@ -823,7 +886,7 @@ function RichTextTriggerEditor({
823
886
  editor.state.tr.setMeta("richTextEditable", !disabled)
824
887
  );
825
888
  }, [disabled, editor]);
826
- useEffect2(() => {
889
+ useEffect3(() => {
827
890
  if (!editor) {
828
891
  return;
829
892
  }
@@ -852,7 +915,7 @@ function RichTextTriggerEditor({
852
915
  editor.off("blur", updateFocus);
853
916
  };
854
917
  }, [activeTriggerConfigs, editor]);
855
- useEffect2(() => {
918
+ useEffect3(() => {
856
919
  if (!editor || !query || activeTriggerConfigs.length === 0) {
857
920
  setMatches([]);
858
921
  setActiveIndex(0);
@@ -909,7 +972,7 @@ function RichTextTriggerEditor({
909
972
  palette,
910
973
  activePaletteCategoryId
911
974
  );
912
- const paletteState = useMemo(
975
+ const paletteState = useMemo2(
913
976
  () => palette ? buildMentionPaletteModelFromTriggerMatches({
914
977
  activeCategoryId: resolvedPaletteCategoryId,
915
978
  categories: palette.categories,
@@ -920,7 +983,7 @@ function RichTextTriggerEditor({
920
983
  }) : null,
921
984
  [isLoading, matches, palette, query?.keyword, resolvedPaletteCategoryId]
922
985
  );
923
- useEffect2(() => {
986
+ useEffect3(() => {
924
987
  const defaultCategoryId = resolveDefaultPaletteCategoryId(palette);
925
988
  if (!defaultCategoryId) {
926
989
  setActivePaletteCategoryId("");
@@ -933,7 +996,7 @@ function RichTextTriggerEditor({
933
996
  }
934
997
  setActivePaletteCategoryId(defaultCategoryId);
935
998
  }, [activePaletteCategoryId, palette]);
936
- useEffect2(() => {
999
+ useEffect3(() => {
937
1000
  if (!paletteState) {
938
1001
  setHighlightedPaletteKey(null);
939
1002
  return;
@@ -1116,8 +1179,8 @@ function RichTextTriggerEditor({
1116
1179
  },
1117
1180
  ref: containerRef,
1118
1181
  children: [
1119
- /* @__PURE__ */ jsx4("div", { className: "w-full min-w-0", onKeyDownCapture: handleKeyDown, children: /* @__PURE__ */ jsx4(EditorContent, { editor }) }),
1120
- isEmpty && placeholder ? /* @__PURE__ */ jsx4("div", { className: "pointer-events-none absolute top-0 right-0 left-0 px-0 py-0 text-[var(--text-placeholder)]", children: /* @__PURE__ */ jsx4(
1182
+ /* @__PURE__ */ jsx5("div", { className: "w-full min-w-0", onKeyDownCapture: handleKeyDown, children: /* @__PURE__ */ jsx5(EditorContent, { editor }) }),
1183
+ isEmpty && placeholder ? /* @__PURE__ */ jsx5("div", { className: "pointer-events-none absolute top-0 right-0 left-0 px-0 py-0 text-[var(--text-placeholder)]", children: /* @__PURE__ */ jsx5(
1121
1184
  "div",
1122
1185
  {
1123
1186
  className: cn(
@@ -1129,7 +1192,7 @@ function RichTextTriggerEditor({
1129
1192
  }
1130
1193
  ) }) : null,
1131
1194
  overlay,
1132
- isMenuOpen && resolvedMenuAnchor ? /* @__PURE__ */ jsx4(
1195
+ isMenuOpen && resolvedMenuAnchor ? /* @__PURE__ */ jsx5(
1133
1196
  ViewportMenuSurface,
1134
1197
  {
1135
1198
  open: true,
@@ -1139,7 +1202,7 @@ function RichTextTriggerEditor({
1139
1202
  ),
1140
1203
  placement: resolveViewportMenuSurfacePlacement(resolvedMenuAnchor),
1141
1204
  style: menuSurfaceStyle,
1142
- children: paletteState && palette ? /* @__PURE__ */ jsx4(
1205
+ children: paletteState && palette ? /* @__PURE__ */ jsx5(
1143
1206
  MentionPaletteFromState,
1144
1207
  {
1145
1208
  state: paletteState,
@@ -1166,7 +1229,7 @@ function RichTextTriggerEditor({
1166
1229
  onSelectItem: applyMatch
1167
1230
  }
1168
1231
  }
1169
- ) : matches.length > 0 ? matches.map((match, index) => /* @__PURE__ */ jsx4(
1232
+ ) : matches.length > 0 ? matches.map((match, index) => /* @__PURE__ */ jsx5(
1170
1233
  RichTextTriggerMenuItem,
1171
1234
  {
1172
1235
  label: match.label,
@@ -1179,7 +1242,7 @@ function RichTextTriggerEditor({
1179
1242
  onSelect: () => applyMatch(match)
1180
1243
  },
1181
1244
  `${match.providerId}:${match.key}`
1182
- )) : /* @__PURE__ */ jsx4("div", { className: "px-3 py-2 text-[11px] leading-4 text-[var(--text-secondary)]", children: isLoading ? text.loadingLabel : text.noMatchesLabel })
1245
+ )) : /* @__PURE__ */ jsx5("div", { className: "px-3 py-2 text-[11px] leading-4 text-[var(--text-secondary)]", children: isLoading ? text.loadingLabel : text.noMatchesLabel })
1183
1246
  }
1184
1247
  ) : null
1185
1248
  ]
@@ -1400,9 +1463,9 @@ function normalizeMentionPresentation(value) {
1400
1463
 
1401
1464
  // src/editor/RichTextTriggerTextarea.tsx
1402
1465
  import {
1403
- useEffect as useEffect3,
1466
+ useEffect as useEffect4,
1404
1467
  useLayoutEffect as useLayoutEffect2,
1405
- useMemo as useMemo2,
1468
+ useMemo as useMemo3,
1406
1469
  useRef as useRef2,
1407
1470
  useState as useState3
1408
1471
  } from "react";
@@ -1536,7 +1599,7 @@ function getTextareaPresentationStyle(textarea) {
1536
1599
  // src/editor/richTextTextareaDecorations.tsx
1537
1600
  import { MentionPill as MentionPill3 } from "@tutti-os/ui-system/components";
1538
1601
  import { cn as cn2 } from "@tutti-os/ui-system/utils";
1539
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1602
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1540
1603
  var richTextWorkspaceReferencePillClassName2 = "max-w-[18rem]";
1541
1604
  function RichTextTextareaDecoratedContent({
1542
1605
  onClickSegment,
@@ -1547,7 +1610,7 @@ function RichTextTextareaDecoratedContent({
1547
1610
  segments,
1548
1611
  textareaStyle
1549
1612
  }) {
1550
- return /* @__PURE__ */ jsx5("div", { className: "pointer-events-none absolute inset-0 z-10 overflow-hidden", children: /* @__PURE__ */ jsx5(
1613
+ return /* @__PURE__ */ jsx6("div", { className: "pointer-events-none absolute inset-0 z-10 overflow-hidden", children: /* @__PURE__ */ jsx6(
1551
1614
  "div",
1552
1615
  {
1553
1616
  className: "min-h-full min-w-full",
@@ -1557,11 +1620,11 @@ function RichTextTextareaDecoratedContent({
1557
1620
  },
1558
1621
  children: segments.map((segment, index) => {
1559
1622
  if (segment.type === "text") {
1560
- return /* @__PURE__ */ jsx5("span", { children: segment.text }, index);
1623
+ return /* @__PURE__ */ jsx6("span", { children: segment.text }, index);
1561
1624
  }
1562
1625
  return /* @__PURE__ */ jsxs4("span", { className: "relative", children: [
1563
- /* @__PURE__ */ jsx5("span", { "aria-hidden": "true", className: "text-transparent", children: segment.text }),
1564
- /* @__PURE__ */ jsx5(
1626
+ /* @__PURE__ */ jsx6("span", { "aria-hidden": "true", className: "text-transparent", children: segment.text }),
1627
+ /* @__PURE__ */ jsx6(
1565
1628
  MentionPill3,
1566
1629
  {
1567
1630
  className: cn2(
@@ -1579,7 +1642,7 @@ function RichTextTextareaDecoratedContent({
1579
1642
  onRemoveSegment(segment);
1580
1643
  }
1581
1644
  },
1582
- summary: /* @__PURE__ */ jsx5(
1645
+ summary: /* @__PURE__ */ jsx6(
1583
1646
  "span",
1584
1647
  {
1585
1648
  className: cn2(
@@ -1704,11 +1767,12 @@ function getTextareaCaretViewportPoint(textarea, selectionStart) {
1704
1767
  }
1705
1768
 
1706
1769
  // src/editor/RichTextTriggerTextarea.tsx
1707
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1770
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1708
1771
  function RichTextTriggerTextarea({
1709
1772
  value,
1710
1773
  onChange,
1711
- triggerProviders = [],
1774
+ triggerProviders,
1775
+ mentionService,
1712
1776
  placeholder,
1713
1777
  disabled = false,
1714
1778
  className,
@@ -1742,27 +1806,27 @@ function RichTextTriggerTextarea({
1742
1806
  const [textareaPresentationStyle, setTextareaPresentationStyle] = useState3(null);
1743
1807
  const pendingSelectionRef = useRef2(null);
1744
1808
  const suppressPastedAtQueryRef = useRef2(false);
1745
- const registry = useMemo2(
1746
- () => createRichTextTriggerRegistry(triggerProviders),
1747
- [triggerProviders]
1748
- );
1749
- const activeTriggerConfigs = useMemo2(
1809
+ const registry = useEffectiveRichTextMentionService({
1810
+ mentionService,
1811
+ triggerProviders
1812
+ });
1813
+ const activeTriggerConfigs = useMemo3(
1750
1814
  () => registry.listTriggerConfigs(),
1751
1815
  [registry]
1752
1816
  );
1753
- const decorationSegments = useMemo2(
1817
+ const decorationSegments = useMemo3(
1754
1818
  () => buildRichTextTextareaDecorationSegments(value),
1755
1819
  [value]
1756
1820
  );
1757
1821
  const hasDecorations = hasRichTextTextareaDecorations(decorationSegments);
1758
- const query = useMemo2(
1822
+ const query = useMemo3(
1759
1823
  () => isFocused ? findRichTextTriggerQuery(value, selectionStart, activeTriggerConfigs) : null,
1760
1824
  [activeTriggerConfigs, isFocused, selectionStart, value]
1761
1825
  );
1762
1826
  const visibleQuery = suppressPastedAtQueryRef.current && query?.trigger === "@" && query.keyword.length > 0 ? null : query;
1763
1827
  const shouldQuery = visibleQuery !== null && visibleQuery.keyword.length >= minQueryLength && activeTriggerConfigs.length > 0;
1764
1828
  const isMenuOpen = isFocused && shouldQuery && (isLoading || matches.length > 0);
1765
- useEffect3(() => {
1829
+ useEffect4(() => {
1766
1830
  const nextSelection = pendingSelectionRef.current;
1767
1831
  if (nextSelection === null || !textareaRef.current) {
1768
1832
  return;
@@ -1770,7 +1834,7 @@ function RichTextTriggerTextarea({
1770
1834
  textareaRef.current.setSelectionRange(nextSelection, nextSelection);
1771
1835
  pendingSelectionRef.current = null;
1772
1836
  }, [value]);
1773
- useEffect3(() => {
1837
+ useEffect4(() => {
1774
1838
  if (!shouldQuery || !visibleQuery) {
1775
1839
  setMatches([]);
1776
1840
  setActiveIndex(0);
@@ -1839,7 +1903,7 @@ function RichTextTriggerTextarea({
1839
1903
  y: caretPoint.y + caretPoint.lineHeight + menuOffset
1840
1904
  });
1841
1905
  }, [isMenuOpen, menuOffset, selectionStart, value, visibleQuery]);
1842
- useEffect3(() => {
1906
+ useEffect4(() => {
1843
1907
  if (!isMenuOpen || !visibleQuery || !textareaRef.current) {
1844
1908
  return;
1845
1909
  }
@@ -1966,7 +2030,7 @@ function RichTextTriggerTextarea({
1966
2030
  }
1967
2031
  };
1968
2032
  return /* @__PURE__ */ jsxs5("div", { className: cn3("relative min-w-0 w-full", className), children: [
1969
- hasDecorations && textareaPresentationStyle ? /* @__PURE__ */ jsx6(
2033
+ hasDecorations && textareaPresentationStyle ? /* @__PURE__ */ jsx7(
1970
2034
  RichTextTextareaDecoratedContent,
1971
2035
  {
1972
2036
  onClickSegment: handleClickDecoration,
@@ -1978,7 +2042,7 @@ function RichTextTriggerTextarea({
1978
2042
  textareaStyle: textareaPresentationStyle
1979
2043
  }
1980
2044
  ) : null,
1981
- /* @__PURE__ */ jsx6(
2045
+ /* @__PURE__ */ jsx7(
1982
2046
  "textarea",
1983
2047
  {
1984
2048
  ref: textareaRef,
@@ -2058,7 +2122,7 @@ function RichTextTriggerTextarea({
2058
2122
  }
2059
2123
  ),
2060
2124
  overlay,
2061
- isMenuOpen && menuPoint ? /* @__PURE__ */ jsx6(
2125
+ isMenuOpen && menuPoint ? /* @__PURE__ */ jsx7(
2062
2126
  ViewportMenuSurface2,
2063
2127
  {
2064
2128
  open: true,
@@ -2074,7 +2138,7 @@ function RichTextTriggerTextarea({
2074
2138
  height: 256
2075
2139
  }
2076
2140
  },
2077
- children: matches.length > 0 ? matches.map((match, index) => /* @__PURE__ */ jsx6(
2141
+ children: matches.length > 0 ? matches.map((match, index) => /* @__PURE__ */ jsx7(
2078
2142
  RichTextTriggerMenuItem,
2079
2143
  {
2080
2144
  label: match.label,
@@ -2084,48 +2148,39 @@ function RichTextTriggerTextarea({
2084
2148
  onSelect: () => applyMatch(match)
2085
2149
  },
2086
2150
  `${match.providerId}:${match.key}`
2087
- )) : /* @__PURE__ */ jsx6("div", { className: "px-3 py-2 text-[11px] leading-4 text-[var(--text-secondary)]", children: isLoading ? text.loadingLabel : text.noMatchesLabel })
2151
+ )) : /* @__PURE__ */ jsx7("div", { className: "px-3 py-2 text-[11px] leading-4 text-[var(--text-secondary)]", children: isLoading ? text.loadingLabel : text.noMatchesLabel })
2088
2152
  }
2089
2153
  ) : null
2090
2154
  ] });
2091
2155
  }
2092
2156
 
2093
2157
  // src/editor/RichTextMentionReadonly.tsx
2094
- import { jsx as jsx7 } from "react/jsx-runtime";
2158
+ import { MentionPill as MentionPill4 } from "@tutti-os/ui-system/components";
2159
+ import { jsx as jsx8 } from "react/jsx-runtime";
2095
2160
  var baseStyle = {
2096
2161
  alignItems: "center",
2097
- border: "1px solid transparent",
2098
- borderRadius: "999px",
2099
2162
  display: "inline-flex",
2100
- fontSize: "0.95em",
2101
- gap: "0.25rem",
2102
- lineHeight: 1.4,
2103
2163
  maxWidth: "100%",
2104
- padding: "0.05rem 0.45rem",
2105
2164
  textDecoration: "none",
2106
2165
  verticalAlign: "baseline",
2107
2166
  whiteSpace: "nowrap"
2108
2167
  };
2109
2168
  var stateStyles = {
2110
2169
  active: {
2111
- background: "var(--tutti-rich-text-mention-active-bg, color-mix(in srgb, currentColor 12%, transparent))",
2112
2170
  color: "var(--tutti-rich-text-mention-active-fg, inherit)",
2113
2171
  cursor: "pointer"
2114
2172
  },
2115
2173
  missing: {
2116
- background: "var(--tutti-rich-text-mention-missing-bg, color-mix(in srgb, currentColor 6%, transparent))",
2117
2174
  color: "var(--tutti-rich-text-mention-missing-fg, color-mix(in srgb, currentColor 48%, transparent))",
2118
2175
  cursor: "default",
2119
2176
  textDecoration: "line-through"
2120
2177
  },
2121
2178
  disabled: {
2122
- background: "var(--tutti-rich-text-mention-disabled-bg, color-mix(in srgb, currentColor 6%, transparent))",
2123
2179
  color: "var(--tutti-rich-text-mention-disabled-fg, color-mix(in srgb, currentColor 58%, transparent))",
2124
2180
  cursor: "not-allowed",
2125
2181
  opacity: 0.88
2126
2182
  },
2127
2183
  loading: {
2128
- background: "var(--tutti-rich-text-mention-loading-bg, color-mix(in srgb, currentColor 8%, transparent))",
2129
2184
  color: "var(--tutti-rich-text-mention-loading-fg, color-mix(in srgb, currentColor 82%, transparent))",
2130
2185
  cursor: "progress",
2131
2186
  opacity: 0.92
@@ -2148,10 +2203,20 @@ function RichTextMentionReadonly({
2148
2203
  mention,
2149
2204
  resolved: view
2150
2205
  };
2151
- const label = renderLabel?.(payload) ?? getRichTextMentionDisplayText({
2152
- ...mention,
2153
- label: view.label
2154
- });
2206
+ const label = renderLabel?.(payload) ?? getRichTextMentionDisplayText({ ...mention, label: view.label });
2207
+ const content = /* @__PURE__ */ jsx8(
2208
+ MentionPill4,
2209
+ {
2210
+ className: "top-0",
2211
+ iconUrl: resolveMentionPillIconUrl({
2212
+ presentation: view.presentation,
2213
+ scope: mention.scope
2214
+ }) ?? void 0,
2215
+ kind: resolveMentionPillKind(mention.providerId, mention.scope),
2216
+ label,
2217
+ removable: false
2218
+ }
2219
+ );
2155
2220
  const elementTitle = title ?? view.tooltip;
2156
2221
  const handleClick = (event) => {
2157
2222
  if (!view.interactive) {
@@ -2177,7 +2242,7 @@ function RichTextMentionReadonly({
2177
2242
  title: elementTitle
2178
2243
  };
2179
2244
  if (view.interactive && onClick) {
2180
- return /* @__PURE__ */ jsx7(
2245
+ return /* @__PURE__ */ jsx8(
2181
2246
  "button",
2182
2247
  {
2183
2248
  ...sharedProps,
@@ -2187,19 +2252,22 @@ function RichTextMentionReadonly({
2187
2252
  style: {
2188
2253
  ...sharedProps.style,
2189
2254
  appearance: "none",
2190
- font: "inherit"
2255
+ background: "transparent",
2256
+ border: 0,
2257
+ font: "inherit",
2258
+ padding: 0
2191
2259
  },
2192
2260
  type: "button",
2193
- children: label
2261
+ children: content
2194
2262
  }
2195
2263
  );
2196
2264
  }
2197
- return /* @__PURE__ */ jsx7("span", { ...sharedProps, children: label });
2265
+ return /* @__PURE__ */ jsx8("span", { ...sharedProps, children: content });
2198
2266
  }
2199
2267
 
2200
2268
  // src/editor/RichTextReadonlyContent.tsx
2201
2269
  import {
2202
- MentionPill as MentionPill4,
2270
+ MentionPill as MentionPill5,
2203
2271
  Tooltip as Tooltip2,
2204
2272
  TooltipContent as TooltipContent2,
2205
2273
  TooltipTrigger as TooltipTrigger2
@@ -2234,27 +2302,34 @@ function buildRichTextReadonlyInlineSegments(content) {
2234
2302
  }
2235
2303
 
2236
2304
  // src/editor/RichTextReadonlyContent.tsx
2237
- import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
2305
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
2238
2306
  var externalHrefPattern = /^(?:[a-z]+:)?\/\//i;
2239
2307
  var richTextWorkspaceReferencePillClassName3 = "max-w-[18rem]";
2240
2308
  function RichTextReadonlyContent({
2241
2309
  value,
2242
2310
  className,
2243
2311
  paragraphClassName,
2312
+ triggerProviders,
2313
+ mentionService,
2244
2314
  onMentionAction,
2245
2315
  onOpenWorkspaceReference
2246
2316
  }) {
2247
2317
  const normalizedValue = normalizeRichTextContent(value).trim();
2318
+ const effectiveMentionService = useEffectiveRichTextMentionService({
2319
+ mentionService,
2320
+ triggerProviders
2321
+ });
2248
2322
  if (!normalizedValue) {
2249
2323
  return null;
2250
2324
  }
2251
2325
  const paragraphs = normalizedValue.split(/\n{2,}/).map((paragraph) => paragraph.trim()).filter(Boolean);
2252
- return /* @__PURE__ */ jsx8("div", { className: cn4("space-y-4", className), children: paragraphs.map((paragraph, paragraphIndex) => /* @__PURE__ */ jsx8(
2326
+ return /* @__PURE__ */ jsx9("div", { className: cn4("space-y-4", className), children: paragraphs.map((paragraph, paragraphIndex) => /* @__PURE__ */ jsx9(
2253
2327
  "p",
2254
2328
  {
2255
2329
  className: cn4("whitespace-pre-wrap", paragraphClassName),
2256
2330
  children: renderReadonlyInlineMarkdown(
2257
2331
  paragraph,
2332
+ effectiveMentionService,
2258
2333
  onOpenWorkspaceReference,
2259
2334
  onMentionAction
2260
2335
  )
@@ -2262,22 +2337,23 @@ function RichTextReadonlyContent({
2262
2337
  `${paragraphIndex}:${paragraph}`
2263
2338
  )) });
2264
2339
  }
2265
- function renderReadonlyInlineMarkdown(content, onOpenWorkspaceReference, onMentionAction) {
2340
+ function renderReadonlyInlineMarkdown(content, mentionService, onOpenWorkspaceReference, onMentionAction) {
2266
2341
  const parts = [];
2267
2342
  const segments = buildRichTextReadonlyInlineSegments(content);
2268
2343
  segments.forEach((segment, index) => {
2269
2344
  if (segment.type === "text") {
2270
- parts.push(/* @__PURE__ */ jsx8("span", { children: segment.text }, `text:${index}`));
2345
+ parts.push(/* @__PURE__ */ jsx9("span", { children: segment.text }, `text:${index}`));
2271
2346
  return;
2272
2347
  }
2273
2348
  parts.push(
2274
- /* @__PURE__ */ jsx8(
2349
+ /* @__PURE__ */ jsx9(
2275
2350
  RichTextReadonlyInlineLink,
2276
2351
  {
2277
2352
  href: segment.href,
2278
2353
  label: segment.label,
2279
2354
  onMentionAction,
2280
- onOpenWorkspaceReference
2355
+ onOpenWorkspaceReference,
2356
+ mentionService
2281
2357
  },
2282
2358
  `link:${index}:${segment.href}`
2283
2359
  )
@@ -2289,26 +2365,28 @@ function RichTextReadonlyInlineLink({
2289
2365
  href,
2290
2366
  label,
2291
2367
  onMentionAction,
2292
- onOpenWorkspaceReference
2368
+ onOpenWorkspaceReference,
2369
+ mentionService
2293
2370
  }) {
2294
2371
  const trimmedHref = href.trim();
2295
2372
  const mention = parseRichTextMentionHref(trimmedHref, label);
2296
2373
  if (mention) {
2297
- return /* @__PURE__ */ jsx8(
2298
- RichTextMentionReadonly,
2374
+ return /* @__PURE__ */ jsx9(
2375
+ HydratedRichTextMentionReadonly,
2299
2376
  {
2300
2377
  mention,
2301
2378
  onClick: onMentionAction ? ({ mention: mention2 }) => {
2302
2379
  void onMentionAction(mention2);
2303
- } : void 0
2380
+ } : void 0,
2381
+ mentionService
2304
2382
  }
2305
2383
  );
2306
2384
  }
2307
2385
  if (!trimmedHref) {
2308
- return /* @__PURE__ */ jsx8("span", { children: label });
2386
+ return /* @__PURE__ */ jsx9("span", { children: label });
2309
2387
  }
2310
2388
  if (externalHrefPattern.test(trimmedHref) && !isRichTextMentionHref(trimmedHref)) {
2311
- return /* @__PURE__ */ jsx8(
2389
+ return /* @__PURE__ */ jsx9(
2312
2390
  "a",
2313
2391
  {
2314
2392
  className: "font-medium text-[var(--text-primary)] underline decoration-[var(--border-1)] underline-offset-4 hover:text-[var(--text-primary-hover)]",
@@ -2321,7 +2399,7 @@ function RichTextReadonlyInlineLink({
2321
2399
  }
2322
2400
  const kind = trimmedHref.endsWith("/") ? "folder" : "file";
2323
2401
  const path = normalizeRichTextLinkHref(trimmedHref, kind);
2324
- return /* @__PURE__ */ jsx8(
2402
+ return /* @__PURE__ */ jsx9(
2325
2403
  WorkspaceReferenceReadonly,
2326
2404
  {
2327
2405
  reference: {
@@ -2333,6 +2411,22 @@ function RichTextReadonlyInlineLink({
2333
2411
  }
2334
2412
  );
2335
2413
  }
2414
+ function HydratedRichTextMentionReadonly({
2415
+ mention,
2416
+ onClick,
2417
+ mentionService
2418
+ }) {
2419
+ const snapshot = useResolvedRichTextMention(mention, mentionService);
2420
+ const resolved = snapshot.state === "ready" ? { state: "active", ...snapshot.resolved } : snapshot.state === "loading" ? { state: "loading" } : snapshot.state === "missing" ? { state: "missing" } : void 0;
2421
+ return /* @__PURE__ */ jsx9(
2422
+ RichTextMentionReadonly,
2423
+ {
2424
+ mention,
2425
+ onClick,
2426
+ resolved
2427
+ }
2428
+ );
2429
+ }
2336
2430
  function WorkspaceReferenceReadonly({
2337
2431
  reference,
2338
2432
  onOpenWorkspaceReference
@@ -2341,8 +2435,8 @@ function WorkspaceReferenceReadonly({
2341
2435
  reference.label,
2342
2436
  reference.path
2343
2437
  );
2344
- const content = /* @__PURE__ */ jsx8(
2345
- MentionPill4,
2438
+ const content = /* @__PURE__ */ jsx9(
2439
+ MentionPill5,
2346
2440
  {
2347
2441
  className: richTextWorkspaceReferencePillClassName3,
2348
2442
  fileKind: reference.kind,
@@ -2352,7 +2446,7 @@ function WorkspaceReferenceReadonly({
2352
2446
  }
2353
2447
  );
2354
2448
  return /* @__PURE__ */ jsxs6(Tooltip2, { children: [
2355
- /* @__PURE__ */ jsx8(TooltipTrigger2, { asChild: true, children: onOpenWorkspaceReference ? /* @__PURE__ */ jsx8(
2449
+ /* @__PURE__ */ jsx9(TooltipTrigger2, { asChild: true, children: onOpenWorkspaceReference ? /* @__PURE__ */ jsx9(
2356
2450
  "button",
2357
2451
  {
2358
2452
  className: "inline-flex max-w-full appearance-none items-baseline bg-transparent p-0 text-inherit",
@@ -2362,8 +2456,8 @@ function WorkspaceReferenceReadonly({
2362
2456
  },
2363
2457
  children: content
2364
2458
  }
2365
- ) : /* @__PURE__ */ jsx8("span", { className: "inline-flex max-w-full align-baseline", children: content }) }),
2366
- /* @__PURE__ */ jsx8(
2459
+ ) : /* @__PURE__ */ jsx9("span", { className: "inline-flex max-w-full align-baseline", children: content }) }),
2460
+ /* @__PURE__ */ jsx9(
2367
2461
  TooltipContent2,
2368
2462
  {
2369
2463
  className: "max-w-md whitespace-normal break-all",
@@ -2375,11 +2469,15 @@ function WorkspaceReferenceReadonly({
2375
2469
  }
2376
2470
  export {
2377
2471
  RichTextMentionReadonly,
2472
+ RichTextMentionServiceProvider,
2378
2473
  RichTextReadonlyContent,
2379
2474
  RichTextTriggerEditor,
2380
2475
  RichTextTriggerTextarea,
2381
2476
  findRichTextTriggerQuery,
2382
2477
  isRichTextTriggerPrefixBoundary,
2383
- queryRichTextTriggerMatches
2478
+ queryRichTextTriggerMatches,
2479
+ useEffectiveRichTextMentionService,
2480
+ useResolvedRichTextMention,
2481
+ useRichTextMentionService
2384
2482
  };
2385
2483
  //# sourceMappingURL=index.js.map