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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -129,6 +129,10 @@ Interpretation:
129
129
  insertion
130
130
  - `resolveMention` restores editor-only label or presentation data from the
131
131
  stored mention identity
132
+ - readonly conversation surfaces pass the same providers to
133
+ `RichTextReadonlyContent.triggerProviders`; it calls `resolveMention` to
134
+ restore presentation such as app icons while keeping serialized Markdown
135
+ limited to durable identity and scope
132
136
  - group ids, labels, totals, and cursors are candidate-panel metadata only;
133
137
  they must not be copied into mention identity, href, or persisted scope
134
138
 
@@ -152,6 +156,53 @@ Markdown serialization includes only `providerId`, `entityId`, `label`, and
152
156
  short `scope` fields. It does not serialize `presentation`, `href`, `kind`,
153
157
  `version`, or arbitrary metadata.
154
158
 
159
+ ## Mention Service
160
+
161
+ `RichTextMentionService` is the host-agnostic owner of provider lookup, query,
162
+ reverse resolution, presentation caching, invalidation, and subscriptions.
163
+ Create one service at the host or app root and reuse it across composer,
164
+ readonly conversation, preview, and AgentGUI surfaces:
165
+
166
+ ```tsx
167
+ import {
168
+ RichTextMentionServiceProvider,
169
+ RichTextReadonlyContent,
170
+ RichTextTriggerEditor
171
+ } from "@tutti-os/ui-rich-text/editor";
172
+ import { createRichTextMentionService } from "@tutti-os/ui-rich-text/service";
173
+
174
+ const mentionService = createRichTextMentionService({ providers });
175
+
176
+ <RichTextMentionServiceProvider service={mentionService}>
177
+ <RichTextTriggerEditor value={draft} onChange={setDraft} />
178
+ <RichTextReadonlyContent value={savedMarkdown} />
179
+ </RichTextMentionServiceProvider>;
180
+
181
+ // Dispose when the owning host/workspace root is destroyed.
182
+ mentionService.dispose();
183
+ ```
184
+
185
+ Non-React consumers use the same instance directly through `query`, `resolve`,
186
+ `getSnapshot`, `invalidate`, and `subscribe`. The React Provider never creates
187
+ a network client or a global singleton; it only injects an existing service.
188
+
189
+ Resolution precedence is fixed: an explicit `mentionService` prop wins, then
190
+ the nearest `RichTextMentionServiceProvider`, then the deprecated
191
+ `triggerProviders` compatibility path. If no resolver exists, persisted label
192
+ text and the semantic `MentionPill` icon remain available.
193
+
194
+ The service uses a normalized `providerId + entityId + canonical scope` key.
195
+ Labels and presentation URLs are deliberately excluded. Ready entries are
196
+ cached for 5 minutes, missing entries for 30 seconds, and errors retry after 5
197
+ seconds. The cache is capped at 1000 identities, concurrent resolution is
198
+ single-flight, and stale ready presentation remains visible during refresh.
199
+ Explicit invalidation may target all entries, a provider, a workspace, or an
200
+ entity.
201
+
202
+ Never persist a resolved snapshot or copy `presentation` into Markdown. Icon
203
+ URLs and runtime cache state stay in memory; durable mention content remains
204
+ limited to provider identity, entity identity, fallback label, and short scope.
205
+
155
206
  Helpers now exported:
156
207
 
157
208
  - `createRichTextMentionPlugin`
@@ -195,14 +246,18 @@ Tutti workspace apps that already receive mention candidates from
195
246
  `@tutti-os/workspace-external-core/rich-text`:
196
247
 
197
248
  ```ts
198
- import { createTuttiExternalAtRichTextTriggerProviders } from "@tutti-os/workspace-external-core/rich-text";
249
+ import { createTuttiExternalRichTextMentionService } from "@tutti-os/workspace-external-core/rich-text";
199
250
 
200
- const providers = createTuttiExternalAtRichTextTriggerProviders({
201
- bridge: window.tuttiExternal,
251
+ const mentionService = createTuttiExternalRichTextMentionService({
252
+ getBridge: () => window.tuttiExternal,
202
253
  providerIds: ["workspace-app", "agent-session"]
203
254
  });
204
255
  ```
205
256
 
257
+ The older trigger-provider factory remains available for compatibility, but a
258
+ root-owned service is the preferred integration because editor and readonly
259
+ surfaces share resolution, caching, and invalidation automatically.
260
+
206
261
  Use a custom `RichTextTriggerProvider` only for app-local mention sources or for
207
262
  apps that do not use the Tutti external bridge.
208
263
 
@@ -0,0 +1,45 @@
1
+ import { a as RichTextMentionIdentity, k as RichTextMentionResolved } from './mention-DJq2OJ4K.js';
2
+ import { RichTextTriggerProvider, RichTextTriggerConfig, RichTextTriggerQueryInput, RichTextTriggerQueryMatch } from './types/index.js';
3
+
4
+ declare const RICH_TEXT_MENTION_READY_TTL_MS = 300000;
5
+ declare const RICH_TEXT_MENTION_MISSING_TTL_MS = 30000;
6
+ declare const RICH_TEXT_MENTION_ERROR_RETRY_MS = 5000;
7
+ declare const RICH_TEXT_MENTION_CACHE_CAPACITY = 1000;
8
+ type RichTextMentionResolutionState = "idle" | "loading" | "ready" | "missing" | "error";
9
+ interface RichTextMentionSnapshot {
10
+ state: RichTextMentionResolutionState;
11
+ resolved?: RichTextMentionResolved;
12
+ updatedAtUnixMs?: number;
13
+ }
14
+ interface RichTextMentionInvalidationSelector {
15
+ providerId?: string;
16
+ workspaceId?: string;
17
+ entityId?: string;
18
+ }
19
+ type RichTextMentionDiagnosticEventName = "mention_query_completed" | "mention_resolve_completed" | "mention_cache_invalidated";
20
+ interface RichTextMentionDiagnosticEvent {
21
+ name: RichTextMentionDiagnosticEventName;
22
+ providerId: string;
23
+ outcome: "success" | "missing" | "error";
24
+ cacheStatus: "hit" | "miss" | "stale" | "invalidated";
25
+ durationMs: number;
26
+ }
27
+ interface CreateRichTextMentionServiceInput {
28
+ providers: readonly RichTextTriggerProvider[];
29
+ now?: () => number;
30
+ diagnostics?: (event: RichTextMentionDiagnosticEvent) => void;
31
+ }
32
+ interface RichTextMentionService {
33
+ listProviders(): readonly RichTextTriggerProvider[];
34
+ getProvider(providerId: string): RichTextTriggerProvider | undefined;
35
+ listTriggerConfigs(): readonly RichTextTriggerConfig[];
36
+ query(input: RichTextTriggerQueryInput): Promise<readonly RichTextTriggerQueryMatch[]>;
37
+ resolve(identity: RichTextMentionIdentity): Promise<RichTextMentionSnapshot>;
38
+ getSnapshot(identity: RichTextMentionIdentity): RichTextMentionSnapshot;
39
+ invalidate(selector?: RichTextMentionInvalidationSelector): void;
40
+ subscribe(listener: () => void, identity?: RichTextMentionIdentity): () => void;
41
+ dispose(): void;
42
+ }
43
+ declare function createRichTextMentionService(input: CreateRichTextMentionServiceInput): RichTextMentionService;
44
+
45
+ export { type CreateRichTextMentionServiceInput as C, RICH_TEXT_MENTION_CACHE_CAPACITY as R, RICH_TEXT_MENTION_ERROR_RETRY_MS as a, RICH_TEXT_MENTION_MISSING_TTL_MS as b, RICH_TEXT_MENTION_READY_TTL_MS as c, type RichTextMentionDiagnosticEvent as d, type RichTextMentionDiagnosticEventName as e, type RichTextMentionInvalidationSelector as f, type RichTextMentionResolutionState as g, type RichTextMentionService as h, type RichTextMentionSnapshot as i, createRichTextMentionService as j };
@@ -0,0 +1,113 @@
1
+ // src/plugins/triggerRegistry.ts
2
+ function normalizeProviderId(providerId) {
3
+ return providerId.trim();
4
+ }
5
+ function normalizeProviderTrigger(provider) {
6
+ return provider.trigger;
7
+ }
8
+ function normalizeProviderBoundary(provider) {
9
+ return provider.boundary ?? "punctuation";
10
+ }
11
+ function normalizeProviderTriggerConfig(provider) {
12
+ return {
13
+ trigger: normalizeProviderTrigger(provider),
14
+ boundary: normalizeProviderBoundary(provider)
15
+ };
16
+ }
17
+ function normalizeOptionalIconUrl(value) {
18
+ const trimmed = value?.trim();
19
+ return trimmed ? trimmed : void 0;
20
+ }
21
+ function resolveInsertResultIconUrl(insertResult) {
22
+ if (insertResult.kind !== "mention") {
23
+ return void 0;
24
+ }
25
+ return normalizeOptionalIconUrl(insertResult.mention.presentation?.iconUrl);
26
+ }
27
+ async function resolveItemIconUrl(provider, item, insertResult) {
28
+ if (provider.getItemIconUrl) {
29
+ try {
30
+ const iconUrl = normalizeOptionalIconUrl(
31
+ await Promise.resolve(provider.getItemIconUrl(item))
32
+ );
33
+ if (iconUrl) {
34
+ return iconUrl;
35
+ }
36
+ } catch {
37
+ return resolveInsertResultIconUrl(insertResult);
38
+ }
39
+ }
40
+ return resolveInsertResultIconUrl(insertResult);
41
+ }
42
+ function createRichTextTriggerRegistry(providers) {
43
+ const providerMap = /* @__PURE__ */ new Map();
44
+ const triggerConfigKeys = /* @__PURE__ */ new Set();
45
+ const triggerConfigs = [];
46
+ for (const provider of providers) {
47
+ const providerId = normalizeProviderId(provider.id);
48
+ if (!providerId) {
49
+ throw new Error("Rich text trigger provider id is required.");
50
+ }
51
+ if (providerMap.has(providerId)) {
52
+ throw new Error(`Duplicate rich text trigger provider id: ${providerId}`);
53
+ }
54
+ providerMap.set(providerId, provider);
55
+ const triggerConfig = normalizeProviderTriggerConfig(provider);
56
+ const triggerConfigKey = `${triggerConfig.trigger}:${triggerConfig.boundary}`;
57
+ if (!triggerConfigKeys.has(triggerConfigKey)) {
58
+ triggerConfigKeys.add(triggerConfigKey);
59
+ triggerConfigs.push(triggerConfig);
60
+ }
61
+ }
62
+ async function query(input) {
63
+ if (input.abortSignal?.aborted) {
64
+ return [];
65
+ }
66
+ const matches = await Promise.all(
67
+ [...providerMap.values()].filter(
68
+ (provider) => normalizeProviderTrigger(provider) === input.trigger
69
+ ).map(async (provider) => {
70
+ if (input.abortSignal?.aborted) {
71
+ return [];
72
+ }
73
+ const items = await provider.query(input);
74
+ if (input.abortSignal?.aborted) {
75
+ return [];
76
+ }
77
+ return Promise.all(
78
+ items.map(async (item) => {
79
+ const insertResult = provider.toInsertResult(item);
80
+ return {
81
+ providerId: provider.id,
82
+ trigger: input.trigger,
83
+ key: provider.getItemKey(item),
84
+ label: provider.getItemLabel(item),
85
+ subtitle: provider.getItemSubtitle?.(item) || void 0,
86
+ iconUrl: await resolveItemIconUrl(provider, item, insertResult),
87
+ keywords: provider.getItemKeywords?.(item),
88
+ item,
89
+ insertResult
90
+ };
91
+ })
92
+ );
93
+ })
94
+ );
95
+ const flatMatches = matches.flat();
96
+ const limit = input.maxResults;
97
+ if (typeof limit === "number" && limit >= 0) {
98
+ return flatMatches.slice(0, limit);
99
+ }
100
+ return flatMatches;
101
+ }
102
+ return {
103
+ listProviders: () => [...providerMap.values()],
104
+ getProvider: (providerId) => providerMap.get(normalizeProviderId(providerId)),
105
+ listTriggerConfigs: () => [...triggerConfigs],
106
+ query
107
+ };
108
+ }
109
+
110
+ export {
111
+ createRichTextTriggerRegistry
112
+ };
113
+ //# sourceMappingURL=chunk-3UTSSMV7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugins/triggerRegistry.ts"],"sourcesContent":["import type {\n RichTextTrigger,\n RichTextTriggerBoundary,\n RichTextTriggerConfig,\n RichTextTriggerInsertResult,\n RichTextTriggerProvider,\n RichTextTriggerQueryInput,\n RichTextTriggerQueryMatch,\n RichTextTriggerRegistry\n} from \"../types/trigger.ts\";\n\nfunction normalizeProviderId(providerId: string): string {\n return providerId.trim();\n}\n\nfunction normalizeProviderTrigger(\n provider: RichTextTriggerProvider\n): RichTextTrigger {\n return provider.trigger;\n}\n\nfunction normalizeProviderBoundary(\n provider: RichTextTriggerProvider\n): RichTextTriggerBoundary {\n return provider.boundary ?? \"punctuation\";\n}\n\nfunction normalizeProviderTriggerConfig(\n provider: RichTextTriggerProvider\n): RichTextTriggerConfig {\n return {\n trigger: normalizeProviderTrigger(provider),\n boundary: normalizeProviderBoundary(provider)\n };\n}\n\nfunction normalizeOptionalIconUrl(\n value: string | null | undefined\n): string | undefined {\n const trimmed = value?.trim();\n return trimmed ? trimmed : undefined;\n}\n\nfunction resolveInsertResultIconUrl(\n insertResult: RichTextTriggerInsertResult\n): string | undefined {\n if (insertResult.kind !== \"mention\") {\n return undefined;\n }\n return normalizeOptionalIconUrl(insertResult.mention.presentation?.iconUrl);\n}\n\nasync function resolveItemIconUrl<TItem>(\n provider: RichTextTriggerProvider<TItem>,\n item: TItem,\n insertResult: RichTextTriggerInsertResult\n): Promise<string | undefined> {\n if (provider.getItemIconUrl) {\n try {\n const iconUrl = normalizeOptionalIconUrl(\n await Promise.resolve(provider.getItemIconUrl(item))\n );\n if (iconUrl) {\n return iconUrl;\n }\n } catch {\n return resolveInsertResultIconUrl(insertResult);\n }\n }\n return resolveInsertResultIconUrl(insertResult);\n}\n\nexport function createRichTextTriggerRegistry(\n providers: readonly RichTextTriggerProvider[]\n): RichTextTriggerRegistry {\n const providerMap = new Map<string, RichTextTriggerProvider>();\n const triggerConfigKeys = new Set<string>();\n const triggerConfigs: RichTextTriggerConfig[] = [];\n\n for (const provider of providers) {\n const providerId = normalizeProviderId(provider.id);\n if (!providerId) {\n throw new Error(\"Rich text trigger provider id is required.\");\n }\n if (providerMap.has(providerId)) {\n throw new Error(`Duplicate rich text trigger provider id: ${providerId}`);\n }\n providerMap.set(providerId, provider);\n const triggerConfig = normalizeProviderTriggerConfig(provider);\n const triggerConfigKey = `${triggerConfig.trigger}:${triggerConfig.boundary}`;\n if (!triggerConfigKeys.has(triggerConfigKey)) {\n triggerConfigKeys.add(triggerConfigKey);\n triggerConfigs.push(triggerConfig);\n }\n }\n\n async function query(\n input: RichTextTriggerQueryInput\n ): Promise<readonly RichTextTriggerQueryMatch[]> {\n if (input.abortSignal?.aborted) {\n return [];\n }\n\n const matches = await Promise.all(\n [...providerMap.values()]\n .filter(\n (provider) => normalizeProviderTrigger(provider) === input.trigger\n )\n .map(async (provider) => {\n if (input.abortSignal?.aborted) {\n return [];\n }\n const items = await provider.query(input);\n if (input.abortSignal?.aborted) {\n return [];\n }\n return Promise.all(\n items.map(async (item) => {\n const insertResult = provider.toInsertResult(item);\n return {\n providerId: provider.id,\n trigger: input.trigger,\n key: provider.getItemKey(item),\n label: provider.getItemLabel(item),\n subtitle: provider.getItemSubtitle?.(item) || undefined,\n iconUrl: await resolveItemIconUrl(provider, item, insertResult),\n keywords: provider.getItemKeywords?.(item),\n item,\n insertResult\n };\n })\n );\n })\n );\n\n const flatMatches = matches.flat();\n const limit = input.maxResults;\n if (typeof limit === \"number\" && limit >= 0) {\n return flatMatches.slice(0, limit);\n }\n return flatMatches;\n }\n\n return {\n listProviders: () => [...providerMap.values()],\n getProvider: (providerId: string) =>\n providerMap.get(normalizeProviderId(providerId)),\n listTriggerConfigs: () => [...triggerConfigs],\n query\n };\n}\n"],"mappings":";AAWA,SAAS,oBAAoB,YAA4B;AACvD,SAAO,WAAW,KAAK;AACzB;AAEA,SAAS,yBACP,UACiB;AACjB,SAAO,SAAS;AAClB;AAEA,SAAS,0BACP,UACyB;AACzB,SAAO,SAAS,YAAY;AAC9B;AAEA,SAAS,+BACP,UACuB;AACvB,SAAO;AAAA,IACL,SAAS,yBAAyB,QAAQ;AAAA,IAC1C,UAAU,0BAA0B,QAAQ;AAAA,EAC9C;AACF;AAEA,SAAS,yBACP,OACoB;AACpB,QAAM,UAAU,OAAO,KAAK;AAC5B,SAAO,UAAU,UAAU;AAC7B;AAEA,SAAS,2BACP,cACoB;AACpB,MAAI,aAAa,SAAS,WAAW;AACnC,WAAO;AAAA,EACT;AACA,SAAO,yBAAyB,aAAa,QAAQ,cAAc,OAAO;AAC5E;AAEA,eAAe,mBACb,UACA,MACA,cAC6B;AAC7B,MAAI,SAAS,gBAAgB;AAC3B,QAAI;AACF,YAAM,UAAU;AAAA,QACd,MAAM,QAAQ,QAAQ,SAAS,eAAe,IAAI,CAAC;AAAA,MACrD;AACA,UAAI,SAAS;AACX,eAAO;AAAA,MACT;AAAA,IACF,QAAQ;AACN,aAAO,2BAA2B,YAAY;AAAA,IAChD;AAAA,EACF;AACA,SAAO,2BAA2B,YAAY;AAChD;AAEO,SAAS,8BACd,WACyB;AACzB,QAAM,cAAc,oBAAI,IAAqC;AAC7D,QAAM,oBAAoB,oBAAI,IAAY;AAC1C,QAAM,iBAA0C,CAAC;AAEjD,aAAW,YAAY,WAAW;AAChC,UAAM,aAAa,oBAAoB,SAAS,EAAE;AAClD,QAAI,CAAC,YAAY;AACf,YAAM,IAAI,MAAM,4CAA4C;AAAA,IAC9D;AACA,QAAI,YAAY,IAAI,UAAU,GAAG;AAC/B,YAAM,IAAI,MAAM,4CAA4C,UAAU,EAAE;AAAA,IAC1E;AACA,gBAAY,IAAI,YAAY,QAAQ;AACpC,UAAM,gBAAgB,+BAA+B,QAAQ;AAC7D,UAAM,mBAAmB,GAAG,cAAc,OAAO,IAAI,cAAc,QAAQ;AAC3E,QAAI,CAAC,kBAAkB,IAAI,gBAAgB,GAAG;AAC5C,wBAAkB,IAAI,gBAAgB;AACtC,qBAAe,KAAK,aAAa;AAAA,IACnC;AAAA,EACF;AAEA,iBAAe,MACb,OAC+C;AAC/C,QAAI,MAAM,aAAa,SAAS;AAC9B,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,UAAU,MAAM,QAAQ;AAAA,MAC5B,CAAC,GAAG,YAAY,OAAO,CAAC,EACrB;AAAA,QACC,CAAC,aAAa,yBAAyB,QAAQ,MAAM,MAAM;AAAA,MAC7D,EACC,IAAI,OAAO,aAAa;AACvB,YAAI,MAAM,aAAa,SAAS;AAC9B,iBAAO,CAAC;AAAA,QACV;AACA,cAAM,QAAQ,MAAM,SAAS,MAAM,KAAK;AACxC,YAAI,MAAM,aAAa,SAAS;AAC9B,iBAAO,CAAC;AAAA,QACV;AACA,eAAO,QAAQ;AAAA,UACb,MAAM,IAAI,OAAO,SAAS;AACxB,kBAAM,eAAe,SAAS,eAAe,IAAI;AACjD,mBAAO;AAAA,cACL,YAAY,SAAS;AAAA,cACrB,SAAS,MAAM;AAAA,cACf,KAAK,SAAS,WAAW,IAAI;AAAA,cAC7B,OAAO,SAAS,aAAa,IAAI;AAAA,cACjC,UAAU,SAAS,kBAAkB,IAAI,KAAK;AAAA,cAC9C,SAAS,MAAM,mBAAmB,UAAU,MAAM,YAAY;AAAA,cAC9D,UAAU,SAAS,kBAAkB,IAAI;AAAA,cACzC;AAAA,cACA;AAAA,YACF;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF,CAAC;AAAA,IACL;AAEA,UAAM,cAAc,QAAQ,KAAK;AACjC,UAAM,QAAQ,MAAM;AACpB,QAAI,OAAO,UAAU,YAAY,SAAS,GAAG;AAC3C,aAAO,YAAY,MAAM,GAAG,KAAK;AAAA,IACnC;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,eAAe,MAAM,CAAC,GAAG,YAAY,OAAO,CAAC;AAAA,IAC7C,aAAa,CAAC,eACZ,YAAY,IAAI,oBAAoB,UAAU,CAAC;AAAA,IACjD,oBAAoB,MAAM,CAAC,GAAG,cAAc;AAAA,IAC5C;AAAA,EACF;AACF;","names":[]}
@@ -169,115 +169,6 @@ function createRichTextTriggerProvider(provider) {
169
169
  };
170
170
  }
171
171
 
172
- // src/plugins/triggerRegistry.ts
173
- function normalizeProviderId(providerId) {
174
- return providerId.trim();
175
- }
176
- function normalizeProviderTrigger(provider) {
177
- return provider.trigger;
178
- }
179
- function normalizeProviderBoundary(provider) {
180
- return provider.boundary ?? "punctuation";
181
- }
182
- function normalizeProviderTriggerConfig(provider) {
183
- return {
184
- trigger: normalizeProviderTrigger(provider),
185
- boundary: normalizeProviderBoundary(provider)
186
- };
187
- }
188
- function normalizeOptionalIconUrl(value) {
189
- const trimmed = value?.trim();
190
- return trimmed ? trimmed : void 0;
191
- }
192
- function resolveInsertResultIconUrl(insertResult) {
193
- if (insertResult.kind !== "mention") {
194
- return void 0;
195
- }
196
- return normalizeOptionalIconUrl(insertResult.mention.presentation?.iconUrl);
197
- }
198
- async function resolveItemIconUrl(provider, item, insertResult) {
199
- if (provider.getItemIconUrl) {
200
- try {
201
- const iconUrl = normalizeOptionalIconUrl(
202
- await Promise.resolve(provider.getItemIconUrl(item))
203
- );
204
- if (iconUrl) {
205
- return iconUrl;
206
- }
207
- } catch {
208
- return resolveInsertResultIconUrl(insertResult);
209
- }
210
- }
211
- return resolveInsertResultIconUrl(insertResult);
212
- }
213
- function createRichTextTriggerRegistry(providers) {
214
- const providerMap = /* @__PURE__ */ new Map();
215
- const triggerConfigKeys = /* @__PURE__ */ new Set();
216
- const triggerConfigs = [];
217
- for (const provider of providers) {
218
- const providerId = normalizeProviderId(provider.id);
219
- if (!providerId) {
220
- throw new Error("Rich text trigger provider id is required.");
221
- }
222
- if (providerMap.has(providerId)) {
223
- throw new Error(`Duplicate rich text trigger provider id: ${providerId}`);
224
- }
225
- providerMap.set(providerId, provider);
226
- const triggerConfig = normalizeProviderTriggerConfig(provider);
227
- const triggerConfigKey = `${triggerConfig.trigger}:${triggerConfig.boundary}`;
228
- if (!triggerConfigKeys.has(triggerConfigKey)) {
229
- triggerConfigKeys.add(triggerConfigKey);
230
- triggerConfigs.push(triggerConfig);
231
- }
232
- }
233
- async function query(input) {
234
- if (input.abortSignal?.aborted) {
235
- return [];
236
- }
237
- const matches = await Promise.all(
238
- [...providerMap.values()].filter(
239
- (provider) => normalizeProviderTrigger(provider) === input.trigger
240
- ).map(async (provider) => {
241
- if (input.abortSignal?.aborted) {
242
- return [];
243
- }
244
- const items = await provider.query(input);
245
- if (input.abortSignal?.aborted) {
246
- return [];
247
- }
248
- return Promise.all(
249
- items.map(async (item) => {
250
- const insertResult = provider.toInsertResult(item);
251
- return {
252
- providerId: provider.id,
253
- trigger: input.trigger,
254
- key: provider.getItemKey(item),
255
- label: provider.getItemLabel(item),
256
- subtitle: provider.getItemSubtitle?.(item) || void 0,
257
- iconUrl: await resolveItemIconUrl(provider, item, insertResult),
258
- keywords: provider.getItemKeywords?.(item),
259
- item,
260
- insertResult
261
- };
262
- })
263
- );
264
- })
265
- );
266
- const flatMatches = matches.flat();
267
- const limit = input.maxResults;
268
- if (typeof limit === "number" && limit >= 0) {
269
- return flatMatches.slice(0, limit);
270
- }
271
- return flatMatches;
272
- }
273
- return {
274
- listProviders: () => [...providerMap.values()],
275
- getProvider: (providerId) => providerMap.get(normalizeProviderId(providerId)),
276
- listTriggerConfigs: () => [...triggerConfigs],
277
- query
278
- };
279
- }
280
-
281
172
  // src/plugins/mentionRegistry.ts
282
173
  function normalizePluginId(pluginId) {
283
174
  return pluginId.trim();
@@ -352,7 +243,6 @@ export {
352
243
  createRichTextMentionInsertResult,
353
244
  renderRichTextTriggerInsertResult,
354
245
  createRichTextTriggerProvider,
355
- createRichTextTriggerRegistry,
356
246
  createRichTextMentionRegistry
357
247
  };
358
- //# sourceMappingURL=chunk-RUX5KKX3.js.map
248
+ //# sourceMappingURL=chunk-CLNWVUDR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugins/mention.ts","../src/plugins/trigger.ts","../src/plugins/mentionRegistry.ts"],"sourcesContent":["import type {\n RichTextMentionAttrs,\n RichTextMentionInsert,\n RichTextMentionPresentation,\n RichTextMentionPlugin,\n RichTextResolvedMention,\n RichTextResolvedMentionView\n} from \"../types/mention.ts\";\n\nconst richTextMentionTrigger = \"@\";\n\nfunction normalizeOptionalString(value?: string | null): string | undefined {\n const trimmed = value?.trim();\n return trimmed ? trimmed : undefined;\n}\n\nfunction normalizeMentionLabel(value: string): string {\n return value.trim().replace(/^@+/, \"\").trim();\n}\n\nfunction normalizeStringRecord(\n values?: Readonly<Record<string, string>> | null\n): Readonly<Record<string, string>> | undefined {\n if (!values) {\n return undefined;\n }\n\n const nextEntries = Object.entries(values)\n .map(([key, value]) => [key.trim(), value.trim()] as const)\n .filter(([key, value]) => key.length > 0 && value.length > 0);\n\n if (nextEntries.length === 0) {\n return undefined;\n }\n\n return Object.freeze(Object.fromEntries(nextEntries));\n}\n\nfunction normalizeMentionPresentation(\n presentation?: RichTextMentionPresentation | null\n): RichTextMentionPresentation | undefined {\n if (!presentation) {\n return undefined;\n }\n\n const normalized: RichTextMentionPresentation = {};\n for (const key of richTextMentionPresentationKeys) {\n const value = normalizeOptionalString(presentation[key]);\n if (value) {\n normalized[key] = value;\n }\n }\n\n return Object.keys(normalized).length > 0\n ? Object.freeze(normalized)\n : undefined;\n}\n\nconst richTextMentionPresentationKeys = [\n \"agentProviderId\",\n \"agentIconUrl\",\n \"iconUrl\",\n \"thumbnailUrl\",\n \"subtitle\",\n \"description\",\n \"participant\",\n \"status\",\n \"statusDataStatus\",\n \"statusLabel\",\n \"statusPulse\",\n \"userAvatarPlaceholderUrl\"\n] as const satisfies readonly (keyof RichTextMentionPresentation)[];\n\nexport function createRichTextMentionAttrs(\n providerId: string,\n mention: RichTextMentionInsert\n): RichTextMentionAttrs {\n const normalizedProviderId = providerId.trim();\n const entityId = mention.entityId.trim();\n const label = normalizeMentionLabel(mention.label);\n\n if (!normalizedProviderId) {\n throw new Error(\"Rich text mention provider id is required.\");\n }\n if (!entityId) {\n throw new Error(\"Rich text mention entityId is required.\");\n }\n if (!label) {\n throw new Error(\"Rich text mention label is required.\");\n }\n\n const attrs: RichTextMentionAttrs = {\n trigger: richTextMentionTrigger,\n providerId: normalizedProviderId,\n entityId,\n label\n };\n const scope = normalizeStringRecord(mention.scope);\n const presentation = normalizeMentionPresentation(mention.presentation);\n if (scope) {\n attrs.scope = scope;\n }\n if (presentation) {\n attrs.presentation = presentation;\n }\n return attrs;\n}\n\nexport function isRichTextMentionAttrs(\n value: unknown\n): value is RichTextMentionAttrs {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as Partial<RichTextMentionAttrs>;\n return (\n candidate.trigger === richTextMentionTrigger &&\n typeof candidate.providerId === \"string\" &&\n candidate.providerId.trim().length > 0 &&\n typeof candidate.entityId === \"string\" &&\n candidate.entityId.trim().length > 0 &&\n typeof candidate.label === \"string\" &&\n candidate.label.trim().length > 0\n );\n}\n\nexport function getRichTextMentionDisplayText(\n attrs: RichTextMentionAttrs\n): string {\n const label = normalizeMentionLabel(attrs.label);\n return label ? `@${label}` : \"\";\n}\n\nexport function resolveRichTextMentionView(\n mention: RichTextMentionAttrs,\n resolved?: RichTextResolvedMention | null\n): RichTextResolvedMentionView {\n const state = resolved?.state ?? \"active\";\n const label = normalizeMentionLabel(resolved?.label ?? mention.label);\n const tooltip = normalizeOptionalString(resolved?.tooltip);\n\n return {\n state,\n label,\n tooltip,\n presentation: normalizeMentionPresentation(\n resolved?.presentation ?? mention.presentation\n ),\n entity: resolved?.entity,\n interactive: state === \"active\"\n };\n}\n\nexport function createRichTextMentionPlugin<TItem, TResolved = unknown>(\n plugin: RichTextMentionPlugin<TItem, TResolved>\n): RichTextMentionPlugin<TItem, TResolved> {\n const id = plugin.id.trim();\n if (!id) {\n throw new Error(\"Rich text mention plugin id is required.\");\n }\n\n return {\n ...plugin,\n id,\n trigger: richTextMentionTrigger\n };\n}\n","import { createRichTextMentionAttrs } from \"./mention.ts\";\nimport {\n createRichTextMentionMarkdown,\n createRichTextLinkMarkdown\n} from \"../core/richTextDocument.ts\";\nimport type {\n RichTextTriggerProvider,\n RichTextMarkdownLinkInsertResult,\n RichTextMentionTriggerInsertResult,\n RichTextTriggerInsertResult,\n RichTextTextInsertResult\n} from \"../types/trigger.ts\";\n\nfunction normalizeRequiredString(value: string, fieldName: string): string {\n const trimmed = value.trim();\n if (!trimmed) {\n throw new Error(`Rich text ${fieldName} is required.`);\n }\n return trimmed;\n}\n\nexport function createRichTextTextInsertResult(\n text: string\n): RichTextTextInsertResult {\n return {\n kind: \"text\",\n text\n };\n}\n\nexport function createRichTextMarkdownLinkInsertResult(\n label: string,\n href: string\n): RichTextMarkdownLinkInsertResult {\n return {\n kind: \"markdown-link\",\n label: normalizeRequiredString(label, \"insert label\"),\n href: normalizeRequiredString(href, \"insert href\")\n };\n}\n\nexport function createRichTextMentionInsertResult(\n mention: RichTextMentionTriggerInsertResult[\"mention\"]\n): RichTextMentionTriggerInsertResult {\n return {\n kind: \"mention\",\n mention\n };\n}\n\nexport function renderRichTextTriggerInsertResult(\n providerId: string,\n result: RichTextTriggerInsertResult\n): string {\n switch (result.kind) {\n case \"mention\":\n return createRichTextMentionMarkdown(\n createRichTextMentionAttrs(providerId, result.mention)\n );\n case \"markdown-link\":\n return createRichTextLinkMarkdown({\n name: result.label,\n path: result.href,\n kind: result.href.endsWith(\"/\") ? \"folder\" : \"file\"\n });\n case \"text\":\n return result.text;\n default:\n return \"\";\n }\n}\n\nexport function createRichTextTriggerProvider<TItem>(\n provider: RichTextTriggerProvider<TItem>\n): RichTextTriggerProvider<TItem> {\n const id = normalizeRequiredString(provider.id, \"provider id\");\n\n return {\n ...provider,\n id\n };\n}\n","import {\n createRichTextMentionAttrs,\n resolveRichTextMentionView\n} from \"./mention.ts\";\nimport type {\n RichTextMentionPlugin,\n RichTextMentionQueryInput,\n RichTextMentionQueryMatch,\n RichTextMentionRegistry,\n RichTextMentionResolveInput,\n RichTextResolvedMentionView\n} from \"../types/mention.ts\";\n\nfunction normalizePluginId(pluginId: string): string {\n return pluginId.trim();\n}\n\nexport function createRichTextMentionRegistry(\n plugins: readonly RichTextMentionPlugin[]\n): RichTextMentionRegistry {\n const pluginMap = new Map<string, RichTextMentionPlugin>();\n\n for (const plugin of plugins) {\n const pluginId = normalizePluginId(plugin.id);\n if (!pluginId) {\n throw new Error(\"Rich text mention plugin id is required.\");\n }\n if (pluginMap.has(pluginId)) {\n throw new Error(`Duplicate rich text mention plugin id: ${pluginId}`);\n }\n pluginMap.set(pluginId, plugin);\n }\n\n async function query(\n input: RichTextMentionQueryInput\n ): Promise<readonly RichTextMentionQueryMatch[]> {\n const matches = await Promise.all(\n [...pluginMap.values()].map(async (plugin) => {\n const items = await plugin.query(input);\n return items.map<RichTextMentionQueryMatch>((item) => ({\n pluginId: plugin.id,\n key: plugin.getItemKey(item),\n label: plugin.getItemLabel(item),\n subtitle: plugin.getItemSubtitle?.(item) || undefined,\n keywords: plugin.getItemKeywords?.(item),\n item,\n mention: createRichTextMentionAttrs(plugin.id, plugin.toMention(item))\n }));\n })\n );\n\n const flatMatches = matches.flat();\n const limit = input.maxResults;\n if (typeof limit === \"number\" && limit >= 0) {\n return flatMatches.slice(0, limit);\n }\n return flatMatches;\n }\n\n async function resolve(\n input: RichTextMentionResolveInput\n ): Promise<RichTextResolvedMentionView> {\n const plugin = pluginMap.get(input.mention.providerId);\n if (!plugin) {\n return resolveRichTextMentionView(input.mention, {\n state: \"missing\"\n });\n }\n\n if (!plugin.resolveMention) {\n return resolveRichTextMentionView(input.mention, {\n state: \"active\"\n });\n }\n\n return resolveRichTextMentionView(\n input.mention,\n await plugin.resolveMention(input)\n );\n }\n\n return {\n listPlugins: () => [...pluginMap.values()],\n getPlugin: (pluginId: string) => pluginMap.get(normalizePluginId(pluginId)),\n query,\n resolve\n };\n}\n"],"mappings":"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","names":[]}