@tutti-os/ui-rich-text 0.0.11 → 0.0.13
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 +50 -57
- package/dist/at-panel/index.css +190 -0
- package/dist/at-panel/index.css.map +1 -0
- package/dist/at-panel/index.d.ts +63 -0
- package/dist/at-panel/index.js +1165 -0
- package/dist/at-panel/index.js.map +1 -0
- package/dist/at-panel/model.d.ts +386 -0
- package/dist/at-panel/model.js +39 -0
- package/dist/at-panel/model.js.map +1 -0
- package/dist/chunk-E4PWFY3N.js +264 -0
- package/dist/chunk-E4PWFY3N.js.map +1 -0
- package/dist/{chunk-K5POY2YJ.js → chunk-QK6NMNYN.js} +124 -46
- package/dist/chunk-QK6NMNYN.js.map +1 -0
- package/dist/{chunk-52PIIFZA.js → chunk-QOVFZUZN.js} +58 -49
- package/dist/chunk-QOVFZUZN.js.map +1 -0
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.js +1 -1
- package/dist/editor/index.d.ts +30 -13
- package/dist/editor/index.js +501 -171
- package/dist/editor/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{mention-QICvf4wE.d.ts → mention-CgUcsO8r.d.ts} +35 -19
- package/dist/plugins/index.d.ts +8 -8
- package/dist/plugins/index.js +8 -8
- package/dist/types/index.d.ts +36 -24
- package/package.json +17 -3
- package/dist/chunk-52PIIFZA.js.map +0 -1
- package/dist/chunk-K5POY2YJ.js.map +0 -1
package/dist/editor/index.js
CHANGED
|
@@ -2,12 +2,12 @@ import {
|
|
|
2
2
|
createDefaultRichTextI18nRuntime
|
|
3
3
|
} from "../chunk-VQHCWUBH.js";
|
|
4
4
|
import {
|
|
5
|
-
createRichTextAtRegistry,
|
|
6
5
|
createRichTextMentionAttrs,
|
|
6
|
+
createRichTextTriggerRegistry,
|
|
7
7
|
getRichTextMentionDisplayText,
|
|
8
|
-
|
|
8
|
+
renderRichTextTriggerInsertResult,
|
|
9
9
|
resolveRichTextMentionView
|
|
10
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-QK6NMNYN.js";
|
|
11
11
|
import {
|
|
12
12
|
findRichTextMarkdownLinks,
|
|
13
13
|
isRichTextMentionHref,
|
|
@@ -18,9 +18,9 @@ import {
|
|
|
18
18
|
parseRichTextMentionHref,
|
|
19
19
|
serializeRichTextDocumentToContent,
|
|
20
20
|
workspaceReferenceNodeName
|
|
21
|
-
} from "../chunk-
|
|
21
|
+
} from "../chunk-QOVFZUZN.js";
|
|
22
22
|
|
|
23
|
-
// src/editor/
|
|
23
|
+
// src/editor/RichTextTriggerEditor.tsx
|
|
24
24
|
import {
|
|
25
25
|
useEffect as useEffect2,
|
|
26
26
|
useLayoutEffect,
|
|
@@ -36,12 +36,18 @@ import { EditorContent, useEditor } from "@tiptap/react";
|
|
|
36
36
|
import { ViewportMenuSurface } from "@tutti-os/ui-system/components";
|
|
37
37
|
import { cn } from "@tutti-os/ui-system/utils";
|
|
38
38
|
|
|
39
|
-
// src/editor/
|
|
40
|
-
function
|
|
39
|
+
// src/editor/richTextTriggerQuery.ts
|
|
40
|
+
function isRichTextTriggerPrefixBoundary(character, boundary) {
|
|
41
|
+
if (boundary === "whitespace") {
|
|
42
|
+
return /\s/.test(character);
|
|
43
|
+
}
|
|
41
44
|
return /[\s,;:!?<>{}|\\'"`~()[\]]/.test(character);
|
|
42
45
|
}
|
|
43
|
-
function
|
|
46
|
+
function findRichTextTriggerQuery(value, caret, triggerConfigs) {
|
|
44
47
|
const cursor = Math.max(0, Math.min(caret, value.length));
|
|
48
|
+
if (triggerConfigs.length === 0) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
45
51
|
let segmentStart = cursor;
|
|
46
52
|
while (segmentStart > 0) {
|
|
47
53
|
const previous = value[segmentStart - 1] ?? "";
|
|
@@ -51,9 +57,18 @@ function findRichTextAtQuery(value, caret) {
|
|
|
51
57
|
segmentStart -= 1;
|
|
52
58
|
}
|
|
53
59
|
const segment = value.slice(segmentStart, cursor);
|
|
54
|
-
for (let index = segment.
|
|
60
|
+
for (let index = segment.length - 1; index >= 0; index -= 1) {
|
|
61
|
+
const trigger = segment[index];
|
|
62
|
+
const matchingConfigs = triggerConfigs.filter(
|
|
63
|
+
(config) => config.trigger === trigger
|
|
64
|
+
);
|
|
65
|
+
if (matchingConfigs.length === 0) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
55
68
|
const previous = segment[index - 1] ?? "";
|
|
56
|
-
if (index > 0 && !
|
|
69
|
+
if (index > 0 && !matchingConfigs.some(
|
|
70
|
+
(config) => isRichTextTriggerPrefixBoundary(previous, config.boundary)
|
|
71
|
+
)) {
|
|
57
72
|
continue;
|
|
58
73
|
}
|
|
59
74
|
const candidate = segment.slice(index);
|
|
@@ -63,12 +78,13 @@ function findRichTextAtQuery(value, caret) {
|
|
|
63
78
|
return {
|
|
64
79
|
from: segmentStart + index,
|
|
65
80
|
to: cursor,
|
|
81
|
+
trigger,
|
|
66
82
|
keyword: candidate.slice(1)
|
|
67
83
|
};
|
|
68
84
|
}
|
|
69
85
|
return null;
|
|
70
86
|
}
|
|
71
|
-
async function
|
|
87
|
+
async function queryRichTextTriggerMatches(registry, input) {
|
|
72
88
|
try {
|
|
73
89
|
return await registry.query(input);
|
|
74
90
|
} catch {
|
|
@@ -86,16 +102,74 @@ function isRichTextImeComposing(event) {
|
|
|
86
102
|
return keyCode === 229 || which === 229;
|
|
87
103
|
}
|
|
88
104
|
|
|
89
|
-
// src/editor/
|
|
105
|
+
// src/editor/richTextTriggerText.ts
|
|
90
106
|
var defaultRichTextI18n = createDefaultRichTextI18nRuntime();
|
|
91
|
-
function
|
|
107
|
+
function resolveRichTextTriggerText(overrides, removeDecorationAriaLabel, i18n = defaultRichTextI18n) {
|
|
92
108
|
return {
|
|
93
109
|
loadingLabel: overrides?.loadingLabel?.trim() || i18n.t("richTextAt.loading"),
|
|
94
110
|
noMatchesLabel: overrides?.noMatchesLabel?.trim() || i18n.t("richTextAt.noMatches"),
|
|
95
111
|
removeReferenceActionLabel: removeDecorationAriaLabel?.trim() || overrides?.removeReferenceActionLabel?.trim() || i18n.t("richTextAt.removeReferenceActionLabel")
|
|
96
112
|
};
|
|
97
113
|
}
|
|
98
|
-
var
|
|
114
|
+
var defaultRichTextTriggerText = resolveRichTextTriggerText();
|
|
115
|
+
|
|
116
|
+
// src/editor/RichTextTriggerMenuItem.tsx
|
|
117
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
118
|
+
function RichTextTriggerMenuItem({
|
|
119
|
+
label,
|
|
120
|
+
selected,
|
|
121
|
+
subtitle,
|
|
122
|
+
thumbnailUrl,
|
|
123
|
+
onSelect
|
|
124
|
+
}) {
|
|
125
|
+
return /* @__PURE__ */ jsxs(
|
|
126
|
+
"button",
|
|
127
|
+
{
|
|
128
|
+
"aria-selected": selected,
|
|
129
|
+
className: [
|
|
130
|
+
"flex w-full cursor-pointer items-center gap-2 rounded-md border-0 bg-transparent px-2.5 py-2 text-left text-[var(--text-primary)] outline-0 transition-colors duration-100",
|
|
131
|
+
selected ? "bg-[var(--transparency-block)]" : "",
|
|
132
|
+
"hover:bg-[var(--transparency-block)]"
|
|
133
|
+
].join(" "),
|
|
134
|
+
type: "button",
|
|
135
|
+
onMouseDown: (event) => {
|
|
136
|
+
event.preventDefault();
|
|
137
|
+
onSelect();
|
|
138
|
+
},
|
|
139
|
+
children: [
|
|
140
|
+
/* @__PURE__ */ jsx(RichTextTriggerMenuThumbnail, { thumbnailUrl }),
|
|
141
|
+
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-auto flex-col items-start gap-0.5", children: [
|
|
142
|
+
/* @__PURE__ */ jsx("span", { className: "w-full overflow-hidden text-ellipsis whitespace-nowrap text-[13px] leading-5 font-semibold", children: label }),
|
|
143
|
+
subtitle ? /* @__PURE__ */ jsx("span", { className: "w-full overflow-hidden text-ellipsis whitespace-nowrap text-[11px] leading-4 text-[var(--text-secondary)]", children: subtitle }) : null
|
|
144
|
+
] })
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
function RichTextTriggerMenuThumbnail({
|
|
150
|
+
thumbnailUrl
|
|
151
|
+
}) {
|
|
152
|
+
const normalizedThumbnailUrl = thumbnailUrl?.trim() ?? "";
|
|
153
|
+
return /* @__PURE__ */ jsx(
|
|
154
|
+
"span",
|
|
155
|
+
{
|
|
156
|
+
"aria-hidden": "true",
|
|
157
|
+
className: "inline-grid size-4 flex-none place-items-center overflow-hidden rounded bg-[var(--bg-block,var(--transparency-block))]",
|
|
158
|
+
"data-rich-text-trigger-thumbnail": "true",
|
|
159
|
+
children: normalizedThumbnailUrl ? /* @__PURE__ */ jsx(
|
|
160
|
+
"img",
|
|
161
|
+
{
|
|
162
|
+
alt: "",
|
|
163
|
+
className: "block size-full object-cover object-center",
|
|
164
|
+
decoding: "async",
|
|
165
|
+
draggable: false,
|
|
166
|
+
loading: "lazy",
|
|
167
|
+
src: normalizedThumbnailUrl
|
|
168
|
+
}
|
|
169
|
+
) : /* @__PURE__ */ jsx("span", { className: "block size-3 rounded-[3px] bg-[var(--transparency-block)]" })
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
}
|
|
99
173
|
|
|
100
174
|
// src/extensions/mentionReference.ts
|
|
101
175
|
import { mergeAttributes, Node } from "@tiptap/core";
|
|
@@ -103,26 +177,60 @@ import { ReactNodeViewRenderer } from "@tiptap/react";
|
|
|
103
177
|
|
|
104
178
|
// src/extensions/MentionReferenceNodeView.tsx
|
|
105
179
|
import { NodeViewWrapper } from "@tiptap/react";
|
|
106
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
180
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
181
|
+
function readMentionPresentationUrl(presentation) {
|
|
182
|
+
if (!presentation || typeof presentation !== "object") {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
const value = presentation.iconUrl ?? presentation.thumbnailUrl;
|
|
186
|
+
if (typeof value !== "string") {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
const trimmed = value.trim();
|
|
190
|
+
return trimmed || null;
|
|
191
|
+
}
|
|
107
192
|
function MentionReferenceNodeView({
|
|
108
193
|
node,
|
|
109
194
|
selected
|
|
110
195
|
}) {
|
|
111
|
-
const label = typeof node.attrs.label === "string" ? node.attrs.label.trim() : "";
|
|
112
|
-
|
|
196
|
+
const label = typeof node.attrs.label === "string" ? node.attrs.label.trim().replace(/^@+/, "").trim() : "";
|
|
197
|
+
const iconUrl = readMentionPresentationUrl(node.attrs.presentation);
|
|
198
|
+
return /* @__PURE__ */ jsx2(
|
|
113
199
|
NodeViewWrapper,
|
|
114
200
|
{
|
|
115
201
|
as: "span",
|
|
116
|
-
className:
|
|
202
|
+
className: selected ? "is-selected" : void 0,
|
|
117
203
|
contentEditable: false,
|
|
118
204
|
"data-rich-text-mention-reference": "true",
|
|
119
|
-
children: /* @__PURE__ */
|
|
205
|
+
children: /* @__PURE__ */ jsxs2(
|
|
120
206
|
"span",
|
|
121
207
|
{
|
|
122
|
-
className:
|
|
208
|
+
className: [
|
|
209
|
+
"inline-flex max-w-full items-center gap-1 overflow-hidden rounded-md px-[7px] py-0.5 text-[13px] leading-[18px] font-semibold align-baseline text-[var(--text-primary)]",
|
|
210
|
+
selected ? "bg-[var(--background-fronted)] shadow-[var(--shadow-soft,0_1px_2px_rgb(0_0_0/8%))]" : "bg-[var(--transparency-block)]"
|
|
211
|
+
].join(" "),
|
|
212
|
+
"data-rich-text-mention-chip": "true",
|
|
123
213
|
children: [
|
|
124
|
-
|
|
125
|
-
|
|
214
|
+
iconUrl ? /* @__PURE__ */ jsx2(
|
|
215
|
+
"span",
|
|
216
|
+
{
|
|
217
|
+
"aria-hidden": "true",
|
|
218
|
+
className: "inline-grid size-4 flex-none place-items-center overflow-hidden rounded",
|
|
219
|
+
"data-rich-text-mention-icon": "true",
|
|
220
|
+
children: /* @__PURE__ */ jsx2(
|
|
221
|
+
"img",
|
|
222
|
+
{
|
|
223
|
+
alt: "",
|
|
224
|
+
className: "block size-full object-cover object-center",
|
|
225
|
+
decoding: "async",
|
|
226
|
+
draggable: false,
|
|
227
|
+
loading: "lazy",
|
|
228
|
+
src: iconUrl
|
|
229
|
+
}
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
) : null,
|
|
233
|
+
label ? iconUrl ? label : `@${label}` : ""
|
|
126
234
|
]
|
|
127
235
|
}
|
|
128
236
|
)
|
|
@@ -142,26 +250,20 @@ var MentionReference = Node.create({
|
|
|
142
250
|
entityId: {
|
|
143
251
|
default: ""
|
|
144
252
|
},
|
|
145
|
-
href: {
|
|
146
|
-
default: null
|
|
147
|
-
},
|
|
148
|
-
kind: {
|
|
149
|
-
default: null
|
|
150
|
-
},
|
|
151
253
|
label: {
|
|
152
254
|
default: ""
|
|
153
255
|
},
|
|
154
|
-
|
|
256
|
+
presentation: {
|
|
155
257
|
default: null
|
|
156
258
|
},
|
|
157
|
-
|
|
259
|
+
providerId: {
|
|
158
260
|
default: ""
|
|
159
261
|
},
|
|
262
|
+
scope: {
|
|
263
|
+
default: null
|
|
264
|
+
},
|
|
160
265
|
trigger: {
|
|
161
266
|
default: "@"
|
|
162
|
-
},
|
|
163
|
-
version: {
|
|
164
|
-
default: null
|
|
165
267
|
}
|
|
166
268
|
};
|
|
167
269
|
},
|
|
@@ -170,13 +272,14 @@ var MentionReference = Node.create({
|
|
|
170
272
|
},
|
|
171
273
|
renderHTML({ HTMLAttributes }) {
|
|
172
274
|
const label = typeof HTMLAttributes.label === "string" ? HTMLAttributes.label : "";
|
|
275
|
+
const displayLabel = label.trim().replace(/^@+/, "").trim();
|
|
173
276
|
return [
|
|
174
277
|
"span",
|
|
175
278
|
mergeAttributes(HTMLAttributes, {
|
|
176
279
|
"data-rich-text-mention-reference": "true",
|
|
177
280
|
class: "inline-flex max-w-full items-center overflow-hidden rounded-md bg-transparency-block px-1.5 py-0.5 align-baseline text-[13px] font-medium text-[var(--text-primary)]"
|
|
178
281
|
}),
|
|
179
|
-
`@${
|
|
282
|
+
displayLabel ? `@${displayLabel}` : ""
|
|
180
283
|
];
|
|
181
284
|
},
|
|
182
285
|
addNodeView() {
|
|
@@ -239,7 +342,7 @@ function isPathRoot(path) {
|
|
|
239
342
|
}
|
|
240
343
|
|
|
241
344
|
// src/extensions/WorkspaceReferenceNodeView.tsx
|
|
242
|
-
import { jsx as
|
|
345
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
243
346
|
var richTextWorkspaceReferencePillClassName = "max-w-[18rem]";
|
|
244
347
|
function WorkspaceReferenceNodeView({
|
|
245
348
|
deleteNode,
|
|
@@ -255,7 +358,7 @@ function WorkspaceReferenceNodeView({
|
|
|
255
358
|
const label = typeof attrs.label === "string" ? attrs.label : "";
|
|
256
359
|
const path = typeof attrs.path === "string" ? attrs.path : "";
|
|
257
360
|
const presentation = getWorkspaceReferencePresentation(label, path);
|
|
258
|
-
const removeActionAriaLabel = typeof extensionOptions.removeActionAriaLabel === "string" ? extensionOptions.removeActionAriaLabel :
|
|
361
|
+
const removeActionAriaLabel = typeof extensionOptions.removeActionAriaLabel === "string" ? extensionOptions.removeActionAriaLabel : defaultRichTextTriggerText.removeReferenceActionLabel;
|
|
259
362
|
useEffect(() => {
|
|
260
363
|
const syncEditable = () => {
|
|
261
364
|
setIsEditable(editor.isEditable);
|
|
@@ -276,15 +379,15 @@ function WorkspaceReferenceNodeView({
|
|
|
276
379
|
}
|
|
277
380
|
deleteNode();
|
|
278
381
|
};
|
|
279
|
-
return /* @__PURE__ */
|
|
382
|
+
return /* @__PURE__ */ jsx3(
|
|
280
383
|
NodeViewWrapper2,
|
|
281
384
|
{
|
|
282
385
|
as: "span",
|
|
283
386
|
className: `inline-flex max-w-full align-baseline ${selected ? "is-selected" : ""}`,
|
|
284
387
|
contentEditable: false,
|
|
285
388
|
"data-rich-text-workspace-reference": "true",
|
|
286
|
-
children: /* @__PURE__ */
|
|
287
|
-
/* @__PURE__ */
|
|
389
|
+
children: /* @__PURE__ */ jsxs3(Tooltip, { children: [
|
|
390
|
+
/* @__PURE__ */ jsx3(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx3(
|
|
288
391
|
MentionPill,
|
|
289
392
|
{
|
|
290
393
|
className: richTextWorkspaceReferencePillClassName,
|
|
@@ -298,7 +401,7 @@ function WorkspaceReferenceNodeView({
|
|
|
298
401
|
} : void 0
|
|
299
402
|
}
|
|
300
403
|
) }),
|
|
301
|
-
/* @__PURE__ */
|
|
404
|
+
/* @__PURE__ */ jsx3(
|
|
302
405
|
TooltipContent,
|
|
303
406
|
{
|
|
304
407
|
className: "max-w-md whitespace-normal break-all",
|
|
@@ -316,7 +419,7 @@ var WorkspaceReference = Node2.create({
|
|
|
316
419
|
name: workspaceReferenceNodeName,
|
|
317
420
|
addOptions() {
|
|
318
421
|
return {
|
|
319
|
-
removeActionAriaLabel:
|
|
422
|
+
removeActionAriaLabel: defaultRichTextTriggerText.removeReferenceActionLabel
|
|
320
423
|
};
|
|
321
424
|
},
|
|
322
425
|
group: "inline",
|
|
@@ -381,12 +484,12 @@ var WorkspaceReference = Node2.create({
|
|
|
381
484
|
}
|
|
382
485
|
});
|
|
383
486
|
|
|
384
|
-
// src/editor/
|
|
385
|
-
import { jsx as
|
|
386
|
-
function
|
|
487
|
+
// src/editor/RichTextTriggerEditor.tsx
|
|
488
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
489
|
+
function RichTextTriggerEditor({
|
|
387
490
|
value,
|
|
388
491
|
onChange,
|
|
389
|
-
|
|
492
|
+
triggerProviders = [],
|
|
390
493
|
placeholder,
|
|
391
494
|
disabled = false,
|
|
392
495
|
className,
|
|
@@ -398,11 +501,12 @@ function RichTextAtEditor({
|
|
|
398
501
|
i18n,
|
|
399
502
|
textOverrides,
|
|
400
503
|
overlay,
|
|
401
|
-
focusSignal
|
|
504
|
+
focusSignal,
|
|
505
|
+
menuZIndex
|
|
402
506
|
}) {
|
|
403
507
|
const menuOffset = 6;
|
|
404
508
|
const normalizedValue = normalizeRichTextContent(value);
|
|
405
|
-
const text =
|
|
509
|
+
const text = resolveRichTextTriggerText(
|
|
406
510
|
textOverrides,
|
|
407
511
|
removeDecorationAriaLabel,
|
|
408
512
|
i18n
|
|
@@ -410,14 +514,23 @@ function RichTextAtEditor({
|
|
|
410
514
|
const latestOnChangeRef = useRef(onChange);
|
|
411
515
|
const lastSerializedValueRef = useRef(normalizedValue);
|
|
412
516
|
const lastFocusSignalRef = useRef(focusSignal);
|
|
517
|
+
const mentionHydrationRequestRef = useRef(0);
|
|
413
518
|
const containerRef = useRef(null);
|
|
414
519
|
const registry = useMemo(
|
|
415
|
-
() =>
|
|
416
|
-
[
|
|
520
|
+
() => createRichTextTriggerRegistry(triggerProviders),
|
|
521
|
+
[triggerProviders]
|
|
522
|
+
);
|
|
523
|
+
const activeTriggerConfigs = useMemo(
|
|
524
|
+
() => registry.listTriggerConfigs(),
|
|
525
|
+
[registry]
|
|
417
526
|
);
|
|
418
527
|
const [isFocused, setIsFocused] = useState2(false);
|
|
419
|
-
const [query, setQuery] = useState2(
|
|
420
|
-
|
|
528
|
+
const [query, setQuery] = useState2(
|
|
529
|
+
null
|
|
530
|
+
);
|
|
531
|
+
const [matches, setMatches] = useState2(
|
|
532
|
+
[]
|
|
533
|
+
);
|
|
421
534
|
const [activeIndex, setActiveIndex] = useState2(0);
|
|
422
535
|
const [isLoading, setIsLoading] = useState2(false);
|
|
423
536
|
const [menuPoint, setMenuPoint] = useState2(
|
|
@@ -449,6 +562,7 @@ function RichTextAtEditor({
|
|
|
449
562
|
onBlur() {
|
|
450
563
|
window.setTimeout(() => {
|
|
451
564
|
setIsFocused(false);
|
|
565
|
+
setQuery(null);
|
|
452
566
|
setMatches([]);
|
|
453
567
|
setActiveIndex(0);
|
|
454
568
|
setIsLoading(false);
|
|
@@ -486,6 +600,33 @@ function RichTextAtEditor({
|
|
|
486
600
|
);
|
|
487
601
|
lastSerializedValueRef.current = normalizedValue;
|
|
488
602
|
}, [editor, normalizedValue]);
|
|
603
|
+
useEffect2(() => {
|
|
604
|
+
if (!editor) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
const requestId = mentionHydrationRequestRef.current + 1;
|
|
608
|
+
mentionHydrationRequestRef.current = requestId;
|
|
609
|
+
const mentions = collectHydratableMentionNodes(editor);
|
|
610
|
+
if (mentions.length === 0) {
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
for (const mention of mentions) {
|
|
614
|
+
const provider = registry.getProvider(mention.attrs.providerId);
|
|
615
|
+
if (!provider?.resolveMention) {
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
void Promise.resolve(provider.resolveMention(mention.attrs)).then((resolved) => {
|
|
619
|
+
if (!resolved || mentionHydrationRequestRef.current !== requestId || editor.isDestroyed) {
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
applyResolvedMentionAttrs(editor, mention.pos, mention.attrs, {
|
|
623
|
+
label: resolved.label,
|
|
624
|
+
presentation: resolved.presentation
|
|
625
|
+
});
|
|
626
|
+
}).catch(() => {
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
}, [editor, registry, normalizedValue]);
|
|
489
630
|
useEffect2(() => {
|
|
490
631
|
if (!editor) {
|
|
491
632
|
return;
|
|
@@ -510,11 +651,26 @@ function RichTextAtEditor({
|
|
|
510
651
|
return;
|
|
511
652
|
}
|
|
512
653
|
const updateQueryState = () => {
|
|
513
|
-
const nextQuery =
|
|
654
|
+
const nextQuery = findEditorAtQuery(editor, activeTriggerConfigs);
|
|
655
|
+
logRichTextTriggerDebug("query-state", {
|
|
656
|
+
activeTriggerConfigs,
|
|
657
|
+
focused: editor.isFocused,
|
|
658
|
+
query: nextQuery,
|
|
659
|
+
selection: {
|
|
660
|
+
empty: editor.state.selection.empty,
|
|
661
|
+
from: editor.state.selection.from,
|
|
662
|
+
to: editor.state.selection.to
|
|
663
|
+
},
|
|
664
|
+
textBeforeCursor: readEditorTextBeforeCursor(editor)
|
|
665
|
+
});
|
|
514
666
|
setQuery(nextQuery);
|
|
515
667
|
};
|
|
516
668
|
const updateFocus = () => {
|
|
517
|
-
|
|
669
|
+
const nextFocused = editor.isFocused;
|
|
670
|
+
logRichTextTriggerDebug("focus-state", {
|
|
671
|
+
focused: nextFocused
|
|
672
|
+
});
|
|
673
|
+
setIsFocused(nextFocused);
|
|
518
674
|
updateQueryState();
|
|
519
675
|
};
|
|
520
676
|
updateQueryState();
|
|
@@ -528,15 +684,20 @@ function RichTextAtEditor({
|
|
|
528
684
|
editor.off("focus", updateFocus);
|
|
529
685
|
editor.off("blur", updateFocus);
|
|
530
686
|
};
|
|
531
|
-
}, [
|
|
687
|
+
}, [activeTriggerConfigs, editor]);
|
|
532
688
|
useEffect2(() => {
|
|
533
|
-
if (!editor || !query ||
|
|
689
|
+
if (!editor || !query || activeTriggerConfigs.length === 0) {
|
|
534
690
|
setMatches([]);
|
|
535
691
|
setActiveIndex(0);
|
|
536
692
|
setIsLoading(false);
|
|
537
693
|
return;
|
|
538
694
|
}
|
|
539
695
|
if (query.keyword.length < minQueryLength) {
|
|
696
|
+
logRichTextTriggerDebug("query-skipped", {
|
|
697
|
+
minQueryLength,
|
|
698
|
+
query,
|
|
699
|
+
reason: "keyword-too-short"
|
|
700
|
+
});
|
|
540
701
|
setMatches([]);
|
|
541
702
|
setActiveIndex(0);
|
|
542
703
|
setIsLoading(false);
|
|
@@ -544,10 +705,16 @@ function RichTextAtEditor({
|
|
|
544
705
|
}
|
|
545
706
|
const abortController = new AbortController();
|
|
546
707
|
setIsLoading(true);
|
|
547
|
-
|
|
708
|
+
logRichTextTriggerDebug("query-start", {
|
|
709
|
+
maxResults,
|
|
710
|
+
minQueryLength,
|
|
711
|
+
query
|
|
712
|
+
});
|
|
713
|
+
void queryRichTextTriggerMatches(registry, {
|
|
548
714
|
abortSignal: abortController.signal,
|
|
549
715
|
keyword: query.keyword,
|
|
550
716
|
maxResults,
|
|
717
|
+
trigger: query.trigger,
|
|
551
718
|
context: {
|
|
552
719
|
blockText: editor.state.selection.$from.parent.textBetween(
|
|
553
720
|
0,
|
|
@@ -561,6 +728,16 @@ function RichTextAtEditor({
|
|
|
561
728
|
if (abortController.signal.aborted) {
|
|
562
729
|
return;
|
|
563
730
|
}
|
|
731
|
+
logRichTextTriggerDebug("query-result", {
|
|
732
|
+
matchCount: nextMatches.length,
|
|
733
|
+
matches: nextMatches.map((match) => ({
|
|
734
|
+
key: match.key,
|
|
735
|
+
label: match.label,
|
|
736
|
+
providerId: match.providerId,
|
|
737
|
+
trigger: match.trigger
|
|
738
|
+
})),
|
|
739
|
+
query
|
|
740
|
+
});
|
|
564
741
|
setMatches(nextMatches);
|
|
565
742
|
setActiveIndex(
|
|
566
743
|
(current) => nextMatches.length === 0 ? 0 : Math.max(0, Math.min(current, nextMatches.length - 1))
|
|
@@ -575,24 +752,38 @@ function RichTextAtEditor({
|
|
|
575
752
|
};
|
|
576
753
|
}, [
|
|
577
754
|
editor,
|
|
578
|
-
isFocused,
|
|
579
755
|
maxResults,
|
|
580
756
|
minQueryLength,
|
|
581
|
-
|
|
757
|
+
activeTriggerConfigs.length,
|
|
582
758
|
query,
|
|
583
759
|
registry
|
|
584
760
|
]);
|
|
585
761
|
useLayoutEffect(() => {
|
|
586
|
-
if (!editor || !query
|
|
762
|
+
if (!editor || !query) {
|
|
763
|
+
logRichTextTriggerDebug("menu-point-cleared", {
|
|
764
|
+
hasEditor: Boolean(editor),
|
|
765
|
+
query
|
|
766
|
+
});
|
|
587
767
|
setMenuPoint(null);
|
|
588
768
|
return;
|
|
589
769
|
}
|
|
590
770
|
const updateMenuPoint = () => {
|
|
591
771
|
const coords = editor.view.coordsAtPos(editor.state.selection.from);
|
|
592
|
-
|
|
772
|
+
const nextMenuPoint = {
|
|
593
773
|
x: coords.left,
|
|
594
774
|
y: coords.bottom + menuOffset
|
|
775
|
+
};
|
|
776
|
+
logRichTextTriggerDebug("menu-point", {
|
|
777
|
+
coords: {
|
|
778
|
+
bottom: coords.bottom,
|
|
779
|
+
left: coords.left,
|
|
780
|
+
right: coords.right,
|
|
781
|
+
top: coords.top
|
|
782
|
+
},
|
|
783
|
+
menuPoint: nextMenuPoint,
|
|
784
|
+
query
|
|
595
785
|
});
|
|
786
|
+
setMenuPoint(nextMenuPoint);
|
|
596
787
|
};
|
|
597
788
|
updateMenuPoint();
|
|
598
789
|
window.addEventListener("resize", updateMenuPoint);
|
|
@@ -604,9 +795,31 @@ function RichTextAtEditor({
|
|
|
604
795
|
window.removeEventListener("resize", updateMenuPoint);
|
|
605
796
|
window.removeEventListener("scroll", updateMenuPoint, true);
|
|
606
797
|
};
|
|
607
|
-
}, [editor,
|
|
608
|
-
const
|
|
798
|
+
}, [editor, menuOffset, query]);
|
|
799
|
+
const canQueryTrigger = !!query && activeTriggerConfigs.length > 0 && query.keyword.length >= minQueryLength;
|
|
800
|
+
const isMenuOpen = canQueryTrigger && (isFocused || !!menuPoint);
|
|
609
801
|
const isEmpty = !editor || serializeRichTextDocumentToContent(editor.getJSON()).trim().length === 0;
|
|
802
|
+
useEffect2(() => {
|
|
803
|
+
logRichTextTriggerDebug("menu-state", {
|
|
804
|
+
activeIndex,
|
|
805
|
+
canQueryTrigger,
|
|
806
|
+
isFocused,
|
|
807
|
+
isLoading,
|
|
808
|
+
isMenuOpen,
|
|
809
|
+
matchesLength: matches.length,
|
|
810
|
+
menuPoint,
|
|
811
|
+
query
|
|
812
|
+
});
|
|
813
|
+
}, [
|
|
814
|
+
activeIndex,
|
|
815
|
+
canQueryTrigger,
|
|
816
|
+
isFocused,
|
|
817
|
+
isLoading,
|
|
818
|
+
isMenuOpen,
|
|
819
|
+
matches.length,
|
|
820
|
+
menuPoint,
|
|
821
|
+
query
|
|
822
|
+
]);
|
|
610
823
|
const applyMatch = (match) => {
|
|
611
824
|
if (!editor || !query) {
|
|
612
825
|
return;
|
|
@@ -628,7 +841,18 @@ function RichTextAtEditor({
|
|
|
628
841
|
if (isRichTextImeComposing(event)) {
|
|
629
842
|
return;
|
|
630
843
|
}
|
|
631
|
-
if (!isMenuOpen
|
|
844
|
+
if (!isMenuOpen) {
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
if (event.key === "Escape") {
|
|
848
|
+
event.preventDefault();
|
|
849
|
+
setMatches([]);
|
|
850
|
+
setActiveIndex(0);
|
|
851
|
+
setIsLoading(false);
|
|
852
|
+
setMenuPoint(null);
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
if (matches.length === 0) {
|
|
632
856
|
return;
|
|
633
857
|
}
|
|
634
858
|
if (event.key === "ArrowDown") {
|
|
@@ -652,22 +876,15 @@ function RichTextAtEditor({
|
|
|
652
876
|
applyMatch(match);
|
|
653
877
|
return;
|
|
654
878
|
}
|
|
655
|
-
if (event.key === "Escape") {
|
|
656
|
-
event.preventDefault();
|
|
657
|
-
setMatches([]);
|
|
658
|
-
setActiveIndex(0);
|
|
659
|
-
setIsLoading(false);
|
|
660
|
-
setMenuPoint(null);
|
|
661
|
-
}
|
|
662
879
|
};
|
|
663
|
-
return /* @__PURE__ */
|
|
880
|
+
return /* @__PURE__ */ jsxs4(
|
|
664
881
|
"div",
|
|
665
882
|
{
|
|
666
883
|
className: cn("relative min-w-0 w-full", className),
|
|
667
884
|
ref: containerRef,
|
|
668
885
|
children: [
|
|
669
|
-
/* @__PURE__ */
|
|
670
|
-
isEmpty && placeholder ? /* @__PURE__ */
|
|
886
|
+
/* @__PURE__ */ jsx4("div", { className: "w-full min-w-0", onKeyDownCapture: handleKeyDown, children: /* @__PURE__ */ jsx4(EditorContent, { editor }) }),
|
|
887
|
+
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(
|
|
671
888
|
"div",
|
|
672
889
|
{
|
|
673
890
|
className: cn(
|
|
@@ -679,7 +896,7 @@ function RichTextAtEditor({
|
|
|
679
896
|
}
|
|
680
897
|
) }) : null,
|
|
681
898
|
overlay,
|
|
682
|
-
isMenuOpen && menuPoint ? /* @__PURE__ */
|
|
899
|
+
isMenuOpen && menuPoint ? /* @__PURE__ */ jsx4(
|
|
683
900
|
ViewportMenuSurface,
|
|
684
901
|
{
|
|
685
902
|
open: true,
|
|
@@ -694,33 +911,47 @@ function RichTextAtEditor({
|
|
|
694
911
|
height: 256
|
|
695
912
|
}
|
|
696
913
|
},
|
|
697
|
-
|
|
698
|
-
|
|
914
|
+
style: menuZIndex === void 0 ? void 0 : { zIndex: menuZIndex },
|
|
915
|
+
children: matches.length > 0 ? matches.map((match, index) => /* @__PURE__ */ jsx4(
|
|
916
|
+
RichTextTriggerMenuItem,
|
|
699
917
|
{
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
)
|
|
705
|
-
type: "button",
|
|
706
|
-
onMouseDown: (event) => {
|
|
707
|
-
event.preventDefault();
|
|
708
|
-
applyMatch(match);
|
|
709
|
-
},
|
|
710
|
-
children: [
|
|
711
|
-
/* @__PURE__ */ jsx3("div", { className: "text-[13px] leading-5 font-medium", children: match.label }),
|
|
712
|
-
match.subtitle ? /* @__PURE__ */ jsx3("div", { className: "text-[11px] leading-4 text-[var(--text-secondary)]", children: match.subtitle }) : null
|
|
713
|
-
]
|
|
918
|
+
label: match.label,
|
|
919
|
+
selected: index === activeIndex,
|
|
920
|
+
subtitle: match.subtitle,
|
|
921
|
+
thumbnailUrl: match.thumbnailUrl,
|
|
922
|
+
onSelect: () => applyMatch(match)
|
|
714
923
|
},
|
|
715
924
|
`${match.providerId}:${match.key}`
|
|
716
|
-
)) : /* @__PURE__ */
|
|
925
|
+
)) : /* @__PURE__ */ jsx4("div", { className: "px-3 py-2 text-[11px] leading-4 text-[var(--text-secondary)]", children: isLoading ? text.loadingLabel : text.noMatchesLabel })
|
|
717
926
|
}
|
|
718
927
|
) : null
|
|
719
928
|
]
|
|
720
929
|
}
|
|
721
930
|
);
|
|
722
931
|
}
|
|
723
|
-
function findEditorAtQuery(editor) {
|
|
932
|
+
function findEditorAtQuery(editor, triggers) {
|
|
933
|
+
const query = findEditorTriggerQuery(editor, triggers);
|
|
934
|
+
if (!query) {
|
|
935
|
+
return null;
|
|
936
|
+
}
|
|
937
|
+
return query;
|
|
938
|
+
}
|
|
939
|
+
function readEditorTextBeforeCursor(editor) {
|
|
940
|
+
const { selection } = editor.state;
|
|
941
|
+
if (!selection.empty) {
|
|
942
|
+
return null;
|
|
943
|
+
}
|
|
944
|
+
const { $from } = selection;
|
|
945
|
+
if (!$from.parent.isTextblock) {
|
|
946
|
+
return null;
|
|
947
|
+
}
|
|
948
|
+
return $from.parent.textBetween(0, $from.parentOffset, "\n", "\uFFFC");
|
|
949
|
+
}
|
|
950
|
+
function logRichTextTriggerDebug(event, payload) {
|
|
951
|
+
console.info(`[tutti-rich-text-trigger] ${event}`, payload);
|
|
952
|
+
window.__tuttiRichTextDebugLog?.(event, payload);
|
|
953
|
+
}
|
|
954
|
+
function findEditorTriggerQuery(editor, triggers) {
|
|
724
955
|
const { selection } = editor.state;
|
|
725
956
|
if (!selection.empty) {
|
|
726
957
|
return null;
|
|
@@ -735,7 +966,11 @@ function findEditorAtQuery(editor) {
|
|
|
735
966
|
"\n",
|
|
736
967
|
"\uFFFC"
|
|
737
968
|
);
|
|
738
|
-
const query =
|
|
969
|
+
const query = findRichTextTriggerQuery(
|
|
970
|
+
textBeforeCursor,
|
|
971
|
+
textBeforeCursor.length,
|
|
972
|
+
triggers
|
|
973
|
+
);
|
|
739
974
|
if (!query) {
|
|
740
975
|
return null;
|
|
741
976
|
}
|
|
@@ -743,6 +978,7 @@ function findEditorAtQuery(editor) {
|
|
|
743
978
|
return {
|
|
744
979
|
from: selection.from - distanceFromQueryStart,
|
|
745
980
|
keyword: query.keyword,
|
|
981
|
+
trigger: query.trigger,
|
|
746
982
|
to: selection.from
|
|
747
983
|
};
|
|
748
984
|
}
|
|
@@ -770,8 +1006,95 @@ function renderInsertResultAsEditorContent(providerId, result) {
|
|
|
770
1006
|
return null;
|
|
771
1007
|
}
|
|
772
1008
|
}
|
|
1009
|
+
function collectHydratableMentionNodes(editor) {
|
|
1010
|
+
const mentions = [];
|
|
1011
|
+
editor.state.doc.descendants((node, pos) => {
|
|
1012
|
+
if (node.type.name !== mentionReferenceNodeName) {
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
const attrs = node.attrs;
|
|
1016
|
+
if (attrs.trigger !== "@" || typeof attrs.providerId !== "string" || !attrs.providerId.trim() || typeof attrs.entityId !== "string" || !attrs.entityId.trim() || typeof attrs.label !== "string" || !attrs.label.trim()) {
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
mentions.push({
|
|
1020
|
+
pos,
|
|
1021
|
+
attrs: {
|
|
1022
|
+
trigger: "@",
|
|
1023
|
+
providerId: attrs.providerId.trim(),
|
|
1024
|
+
entityId: attrs.entityId.trim(),
|
|
1025
|
+
label: attrs.label.trim().replace(/^@+/, "").trim(),
|
|
1026
|
+
scope: normalizeMentionStringRecord(attrs.scope),
|
|
1027
|
+
presentation: normalizeMentionPresentation(attrs.presentation)
|
|
1028
|
+
}
|
|
1029
|
+
});
|
|
1030
|
+
});
|
|
1031
|
+
return mentions;
|
|
1032
|
+
}
|
|
1033
|
+
function applyResolvedMentionAttrs(editor, pos, currentAttrs, resolved) {
|
|
1034
|
+
const node = editor.state.doc.nodeAt(pos);
|
|
1035
|
+
if (!node || node.type.name !== mentionReferenceNodeName) {
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
const attrs = node.attrs;
|
|
1039
|
+
if (attrs.providerId !== currentAttrs.providerId || attrs.entityId !== currentAttrs.entityId || JSON.stringify(normalizeMentionStringRecord(attrs.scope) ?? {}) !== JSON.stringify(currentAttrs.scope ?? {})) {
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
const nextLabel = resolved.label?.trim().replace(/^@+/, "").trim();
|
|
1043
|
+
const nextPresentation = normalizeMentionPresentation(resolved.presentation);
|
|
1044
|
+
const nextAttrs = {
|
|
1045
|
+
trigger: "@",
|
|
1046
|
+
providerId: currentAttrs.providerId,
|
|
1047
|
+
entityId: currentAttrs.entityId,
|
|
1048
|
+
label: nextLabel || currentAttrs.label,
|
|
1049
|
+
scope: currentAttrs.scope,
|
|
1050
|
+
presentation: nextPresentation ?? currentAttrs.presentation
|
|
1051
|
+
};
|
|
1052
|
+
if (attrs.label === nextAttrs.label && JSON.stringify(normalizeMentionPresentation(attrs.presentation) ?? {}) === JSON.stringify(nextAttrs.presentation ?? {})) {
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
const transaction = editor.state.tr.setNodeMarkup(pos, void 0, nextAttrs);
|
|
1056
|
+
transaction.setMeta("addToHistory", false);
|
|
1057
|
+
transaction.setMeta("preventUpdate", true);
|
|
1058
|
+
editor.view.dispatch(transaction);
|
|
1059
|
+
}
|
|
1060
|
+
function normalizeMentionStringRecord(value) {
|
|
1061
|
+
if (!value || typeof value !== "object") {
|
|
1062
|
+
return void 0;
|
|
1063
|
+
}
|
|
1064
|
+
const entries = Object.entries(value).map(
|
|
1065
|
+
([key, entryValue]) => [
|
|
1066
|
+
key.trim(),
|
|
1067
|
+
typeof entryValue === "string" ? entryValue.trim() : ""
|
|
1068
|
+
]
|
|
1069
|
+
).filter(([key, entryValue]) => key.length > 0 && entryValue.length > 0).sort(([left], [right]) => left.localeCompare(right));
|
|
1070
|
+
return entries.length > 0 ? Object.freeze(Object.fromEntries(entries)) : void 0;
|
|
1071
|
+
}
|
|
1072
|
+
function normalizeMentionPresentation(value) {
|
|
1073
|
+
if (!value || typeof value !== "object") {
|
|
1074
|
+
return void 0;
|
|
1075
|
+
}
|
|
1076
|
+
const source = value;
|
|
1077
|
+
const next = {};
|
|
1078
|
+
for (const key of [
|
|
1079
|
+
"iconUrl",
|
|
1080
|
+
"thumbnailUrl",
|
|
1081
|
+
"subtitle",
|
|
1082
|
+
"description",
|
|
1083
|
+
"status"
|
|
1084
|
+
]) {
|
|
1085
|
+
const fieldValue = source[key];
|
|
1086
|
+
if (typeof fieldValue !== "string") {
|
|
1087
|
+
continue;
|
|
1088
|
+
}
|
|
1089
|
+
const trimmed = fieldValue.trim();
|
|
1090
|
+
if (trimmed) {
|
|
1091
|
+
next[key] = trimmed;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
return Object.keys(next).length > 0 ? Object.freeze(next) : void 0;
|
|
1095
|
+
}
|
|
773
1096
|
|
|
774
|
-
// src/editor/
|
|
1097
|
+
// src/editor/RichTextTriggerTextarea.tsx
|
|
775
1098
|
import {
|
|
776
1099
|
useEffect as useEffect3,
|
|
777
1100
|
useLayoutEffect as useLayoutEffect2,
|
|
@@ -910,7 +1233,7 @@ function getTextareaPresentationStyle(textarea) {
|
|
|
910
1233
|
// src/editor/richTextTextareaDecorations.tsx
|
|
911
1234
|
import { MentionPill as MentionPill2 } from "@tutti-os/ui-system/components";
|
|
912
1235
|
import { cn as cn2 } from "@tutti-os/ui-system/utils";
|
|
913
|
-
import { jsx as
|
|
1236
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
914
1237
|
var richTextWorkspaceReferencePillClassName2 = "max-w-[18rem]";
|
|
915
1238
|
function RichTextTextareaDecoratedContent({
|
|
916
1239
|
onClickSegment,
|
|
@@ -921,7 +1244,7 @@ function RichTextTextareaDecoratedContent({
|
|
|
921
1244
|
segments,
|
|
922
1245
|
textareaStyle
|
|
923
1246
|
}) {
|
|
924
|
-
return /* @__PURE__ */
|
|
1247
|
+
return /* @__PURE__ */ jsx5("div", { className: "pointer-events-none absolute inset-0 z-10 overflow-hidden", children: /* @__PURE__ */ jsx5(
|
|
925
1248
|
"div",
|
|
926
1249
|
{
|
|
927
1250
|
className: "min-h-full min-w-full",
|
|
@@ -931,11 +1254,11 @@ function RichTextTextareaDecoratedContent({
|
|
|
931
1254
|
},
|
|
932
1255
|
children: segments.map((segment, index) => {
|
|
933
1256
|
if (segment.type === "text") {
|
|
934
|
-
return /* @__PURE__ */
|
|
1257
|
+
return /* @__PURE__ */ jsx5("span", { children: segment.text }, index);
|
|
935
1258
|
}
|
|
936
|
-
return /* @__PURE__ */
|
|
937
|
-
/* @__PURE__ */
|
|
938
|
-
/* @__PURE__ */
|
|
1259
|
+
return /* @__PURE__ */ jsxs5("span", { className: "relative", children: [
|
|
1260
|
+
/* @__PURE__ */ jsx5("span", { "aria-hidden": "true", className: "text-transparent", children: segment.text }),
|
|
1261
|
+
/* @__PURE__ */ jsx5(
|
|
939
1262
|
MentionPill2,
|
|
940
1263
|
{
|
|
941
1264
|
className: cn2(
|
|
@@ -953,7 +1276,7 @@ function RichTextTextareaDecoratedContent({
|
|
|
953
1276
|
onRemoveSegment(segment);
|
|
954
1277
|
}
|
|
955
1278
|
},
|
|
956
|
-
summary: /* @__PURE__ */
|
|
1279
|
+
summary: /* @__PURE__ */ jsx5(
|
|
957
1280
|
"span",
|
|
958
1281
|
{
|
|
959
1282
|
className: cn2(
|
|
@@ -1077,12 +1400,12 @@ function getTextareaCaretViewportPoint(textarea, selectionStart) {
|
|
|
1077
1400
|
}
|
|
1078
1401
|
}
|
|
1079
1402
|
|
|
1080
|
-
// src/editor/
|
|
1081
|
-
import { jsx as
|
|
1082
|
-
function
|
|
1403
|
+
// src/editor/RichTextTriggerTextarea.tsx
|
|
1404
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1405
|
+
function RichTextTriggerTextarea({
|
|
1083
1406
|
value,
|
|
1084
1407
|
onChange,
|
|
1085
|
-
|
|
1408
|
+
triggerProviders = [],
|
|
1086
1409
|
placeholder,
|
|
1087
1410
|
disabled = false,
|
|
1088
1411
|
className,
|
|
@@ -1096,14 +1419,16 @@ function RichTextAtTextarea({
|
|
|
1096
1419
|
overlay
|
|
1097
1420
|
}) {
|
|
1098
1421
|
const menuOffset = 6;
|
|
1099
|
-
const text =
|
|
1422
|
+
const text = resolveRichTextTriggerText(
|
|
1100
1423
|
textOverrides,
|
|
1101
1424
|
removeDecorationAriaLabel,
|
|
1102
1425
|
i18n
|
|
1103
1426
|
);
|
|
1104
1427
|
const textareaRef = useRef2(null);
|
|
1105
1428
|
const [selectionStart, setSelectionStart] = useState3(0);
|
|
1106
|
-
const [matches, setMatches] = useState3(
|
|
1429
|
+
const [matches, setMatches] = useState3(
|
|
1430
|
+
[]
|
|
1431
|
+
);
|
|
1107
1432
|
const [activeIndex, setActiveIndex] = useState3(0);
|
|
1108
1433
|
const [isLoading, setIsLoading] = useState3(false);
|
|
1109
1434
|
const [isFocused, setIsFocused] = useState3(false);
|
|
@@ -1114,16 +1439,23 @@ function RichTextAtTextarea({
|
|
|
1114
1439
|
const [textareaPresentationStyle, setTextareaPresentationStyle] = useState3(null);
|
|
1115
1440
|
const pendingSelectionRef = useRef2(null);
|
|
1116
1441
|
const registry = useMemo2(
|
|
1117
|
-
() =>
|
|
1118
|
-
[
|
|
1442
|
+
() => createRichTextTriggerRegistry(triggerProviders),
|
|
1443
|
+
[triggerProviders]
|
|
1444
|
+
);
|
|
1445
|
+
const activeTriggerConfigs = useMemo2(
|
|
1446
|
+
() => registry.listTriggerConfigs(),
|
|
1447
|
+
[registry]
|
|
1119
1448
|
);
|
|
1120
1449
|
const decorationSegments = useMemo2(
|
|
1121
1450
|
() => buildRichTextTextareaDecorationSegments(value),
|
|
1122
1451
|
[value]
|
|
1123
1452
|
);
|
|
1124
1453
|
const hasDecorations = hasRichTextTextareaDecorations(decorationSegments);
|
|
1125
|
-
const query =
|
|
1126
|
-
|
|
1454
|
+
const query = useMemo2(
|
|
1455
|
+
() => isFocused ? findRichTextTriggerQuery(value, selectionStart, activeTriggerConfigs) : null,
|
|
1456
|
+
[activeTriggerConfigs, isFocused, selectionStart, value]
|
|
1457
|
+
);
|
|
1458
|
+
const shouldQuery = query !== null && query.keyword.length >= minQueryLength && activeTriggerConfigs.length > 0;
|
|
1127
1459
|
const isMenuOpen = isFocused && shouldQuery && (isLoading || matches.length > 0);
|
|
1128
1460
|
useEffect3(() => {
|
|
1129
1461
|
const nextSelection = pendingSelectionRef.current;
|
|
@@ -1142,10 +1474,11 @@ function RichTextAtTextarea({
|
|
|
1142
1474
|
}
|
|
1143
1475
|
const abortController = new AbortController();
|
|
1144
1476
|
setIsLoading(true);
|
|
1145
|
-
void
|
|
1477
|
+
void queryRichTextTriggerMatches(registry, {
|
|
1146
1478
|
abortSignal: abortController.signal,
|
|
1147
1479
|
keyword: query.keyword,
|
|
1148
1480
|
maxResults,
|
|
1481
|
+
trigger: query.trigger,
|
|
1149
1482
|
context: {
|
|
1150
1483
|
documentText: value,
|
|
1151
1484
|
blockText: value
|
|
@@ -1273,14 +1606,15 @@ function RichTextAtTextarea({
|
|
|
1273
1606
|
});
|
|
1274
1607
|
};
|
|
1275
1608
|
const applyMatch = (match) => {
|
|
1276
|
-
const currentQuery =
|
|
1609
|
+
const currentQuery = findRichTextTriggerQuery(
|
|
1277
1610
|
value,
|
|
1278
|
-
textareaRef.current?.selectionStart ?? selectionStart
|
|
1611
|
+
textareaRef.current?.selectionStart ?? selectionStart,
|
|
1612
|
+
activeTriggerConfigs
|
|
1279
1613
|
);
|
|
1280
1614
|
if (!currentQuery) {
|
|
1281
1615
|
return;
|
|
1282
1616
|
}
|
|
1283
|
-
const insertedValue =
|
|
1617
|
+
const insertedValue = renderRichTextTriggerInsertResult(
|
|
1284
1618
|
match.providerId,
|
|
1285
1619
|
match.insertResult
|
|
1286
1620
|
);
|
|
@@ -1323,8 +1657,8 @@ function RichTextAtTextarea({
|
|
|
1323
1657
|
closeMenu();
|
|
1324
1658
|
}
|
|
1325
1659
|
};
|
|
1326
|
-
return /* @__PURE__ */
|
|
1327
|
-
hasDecorations && textareaPresentationStyle ? /* @__PURE__ */
|
|
1660
|
+
return /* @__PURE__ */ jsxs6("div", { className: cn3("relative min-w-0 w-full", className), children: [
|
|
1661
|
+
hasDecorations && textareaPresentationStyle ? /* @__PURE__ */ jsx6(
|
|
1328
1662
|
RichTextTextareaDecoratedContent,
|
|
1329
1663
|
{
|
|
1330
1664
|
onClickSegment: handleClickDecoration,
|
|
@@ -1336,7 +1670,7 @@ function RichTextAtTextarea({
|
|
|
1336
1670
|
textareaStyle: textareaPresentationStyle
|
|
1337
1671
|
}
|
|
1338
1672
|
) : null,
|
|
1339
|
-
/* @__PURE__ */
|
|
1673
|
+
/* @__PURE__ */ jsx6(
|
|
1340
1674
|
"textarea",
|
|
1341
1675
|
{
|
|
1342
1676
|
ref: textareaRef,
|
|
@@ -1407,7 +1741,7 @@ function RichTextAtTextarea({
|
|
|
1407
1741
|
}
|
|
1408
1742
|
),
|
|
1409
1743
|
overlay,
|
|
1410
|
-
isMenuOpen && menuPoint ? /* @__PURE__ */
|
|
1744
|
+
isMenuOpen && menuPoint ? /* @__PURE__ */ jsx6(
|
|
1411
1745
|
ViewportMenuSurface2,
|
|
1412
1746
|
{
|
|
1413
1747
|
open: true,
|
|
@@ -1423,33 +1757,24 @@ function RichTextAtTextarea({
|
|
|
1423
1757
|
height: 256
|
|
1424
1758
|
}
|
|
1425
1759
|
},
|
|
1426
|
-
children: matches.length > 0 ? matches.map((match, index) => /* @__PURE__ */
|
|
1427
|
-
|
|
1760
|
+
children: matches.length > 0 ? matches.map((match, index) => /* @__PURE__ */ jsx6(
|
|
1761
|
+
RichTextTriggerMenuItem,
|
|
1428
1762
|
{
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
)
|
|
1434
|
-
type: "button",
|
|
1435
|
-
onMouseDown: (event) => {
|
|
1436
|
-
event.preventDefault();
|
|
1437
|
-
applyMatch(match);
|
|
1438
|
-
},
|
|
1439
|
-
children: [
|
|
1440
|
-
/* @__PURE__ */ jsx5("div", { className: "text-[13px] leading-5 font-medium", children: match.label }),
|
|
1441
|
-
match.subtitle ? /* @__PURE__ */ jsx5("div", { className: "text-[11px] leading-4 text-[var(--text-secondary)]", children: match.subtitle }) : null
|
|
1442
|
-
]
|
|
1763
|
+
label: match.label,
|
|
1764
|
+
selected: index === activeIndex,
|
|
1765
|
+
subtitle: match.subtitle,
|
|
1766
|
+
thumbnailUrl: match.thumbnailUrl,
|
|
1767
|
+
onSelect: () => applyMatch(match)
|
|
1443
1768
|
},
|
|
1444
1769
|
`${match.providerId}:${match.key}`
|
|
1445
|
-
)) : /* @__PURE__ */
|
|
1770
|
+
)) : /* @__PURE__ */ jsx6("div", { className: "px-3 py-2 text-[11px] leading-4 text-[var(--text-secondary)]", children: isLoading ? text.loadingLabel : text.noMatchesLabel })
|
|
1446
1771
|
}
|
|
1447
1772
|
) : null
|
|
1448
1773
|
] });
|
|
1449
1774
|
}
|
|
1450
1775
|
|
|
1451
1776
|
// src/editor/RichTextMentionReadonly.tsx
|
|
1452
|
-
import { jsx as
|
|
1777
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
1453
1778
|
var baseStyle = {
|
|
1454
1779
|
alignItems: "center",
|
|
1455
1780
|
border: "1px solid transparent",
|
|
@@ -1526,7 +1851,7 @@ function RichTextMentionReadonly({
|
|
|
1526
1851
|
`tutti-rich-text-mention--${view.state}`,
|
|
1527
1852
|
className
|
|
1528
1853
|
),
|
|
1529
|
-
"data-
|
|
1854
|
+
"data-provider-id": mention.providerId,
|
|
1530
1855
|
"data-state": view.state,
|
|
1531
1856
|
style: {
|
|
1532
1857
|
...baseStyle,
|
|
@@ -1534,21 +1859,8 @@ function RichTextMentionReadonly({
|
|
|
1534
1859
|
},
|
|
1535
1860
|
title: elementTitle
|
|
1536
1861
|
};
|
|
1537
|
-
if (view.interactive && view.href) {
|
|
1538
|
-
return /* @__PURE__ */ jsx6(
|
|
1539
|
-
"a",
|
|
1540
|
-
{
|
|
1541
|
-
...sharedProps,
|
|
1542
|
-
href: view.href,
|
|
1543
|
-
onClick: (event) => {
|
|
1544
|
-
handleClick(event);
|
|
1545
|
-
},
|
|
1546
|
-
children: label
|
|
1547
|
-
}
|
|
1548
|
-
);
|
|
1549
|
-
}
|
|
1550
1862
|
if (view.interactive && onClick) {
|
|
1551
|
-
return /* @__PURE__ */
|
|
1863
|
+
return /* @__PURE__ */ jsx7(
|
|
1552
1864
|
"button",
|
|
1553
1865
|
{
|
|
1554
1866
|
...sharedProps,
|
|
@@ -1565,7 +1877,7 @@ function RichTextMentionReadonly({
|
|
|
1565
1877
|
}
|
|
1566
1878
|
);
|
|
1567
1879
|
}
|
|
1568
|
-
return /* @__PURE__ */
|
|
1880
|
+
return /* @__PURE__ */ jsx7("span", { ...sharedProps, children: label });
|
|
1569
1881
|
}
|
|
1570
1882
|
|
|
1571
1883
|
// src/editor/RichTextReadonlyContent.tsx
|
|
@@ -1605,13 +1917,14 @@ function buildRichTextReadonlyInlineSegments(content) {
|
|
|
1605
1917
|
}
|
|
1606
1918
|
|
|
1607
1919
|
// src/editor/RichTextReadonlyContent.tsx
|
|
1608
|
-
import { jsx as
|
|
1920
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1609
1921
|
var externalHrefPattern = /^(?:[a-z]+:)?\/\//i;
|
|
1610
1922
|
var richTextWorkspaceReferencePillClassName3 = "max-w-[18rem]";
|
|
1611
1923
|
function RichTextReadonlyContent({
|
|
1612
1924
|
value,
|
|
1613
1925
|
className,
|
|
1614
1926
|
paragraphClassName,
|
|
1927
|
+
onMentionAction,
|
|
1615
1928
|
onOpenWorkspaceReference
|
|
1616
1929
|
}) {
|
|
1617
1930
|
const normalizedValue = normalizeRichTextContent(value).trim();
|
|
@@ -1619,29 +1932,34 @@ function RichTextReadonlyContent({
|
|
|
1619
1932
|
return null;
|
|
1620
1933
|
}
|
|
1621
1934
|
const paragraphs = normalizedValue.split(/\n{2,}/).map((paragraph) => paragraph.trim()).filter(Boolean);
|
|
1622
|
-
return /* @__PURE__ */
|
|
1935
|
+
return /* @__PURE__ */ jsx8("div", { className: cn4("space-y-4", className), children: paragraphs.map((paragraph, paragraphIndex) => /* @__PURE__ */ jsx8(
|
|
1623
1936
|
"p",
|
|
1624
1937
|
{
|
|
1625
1938
|
className: cn4("whitespace-pre-wrap", paragraphClassName),
|
|
1626
|
-
children: renderReadonlyInlineMarkdown(
|
|
1939
|
+
children: renderReadonlyInlineMarkdown(
|
|
1940
|
+
paragraph,
|
|
1941
|
+
onOpenWorkspaceReference,
|
|
1942
|
+
onMentionAction
|
|
1943
|
+
)
|
|
1627
1944
|
},
|
|
1628
1945
|
`${paragraphIndex}:${paragraph}`
|
|
1629
1946
|
)) });
|
|
1630
1947
|
}
|
|
1631
|
-
function renderReadonlyInlineMarkdown(content, onOpenWorkspaceReference) {
|
|
1948
|
+
function renderReadonlyInlineMarkdown(content, onOpenWorkspaceReference, onMentionAction) {
|
|
1632
1949
|
const parts = [];
|
|
1633
1950
|
const segments = buildRichTextReadonlyInlineSegments(content);
|
|
1634
1951
|
segments.forEach((segment, index) => {
|
|
1635
1952
|
if (segment.type === "text") {
|
|
1636
|
-
parts.push(/* @__PURE__ */
|
|
1953
|
+
parts.push(/* @__PURE__ */ jsx8("span", { children: segment.text }, `text:${index}`));
|
|
1637
1954
|
return;
|
|
1638
1955
|
}
|
|
1639
1956
|
parts.push(
|
|
1640
|
-
/* @__PURE__ */
|
|
1957
|
+
/* @__PURE__ */ jsx8(
|
|
1641
1958
|
RichTextReadonlyInlineLink,
|
|
1642
1959
|
{
|
|
1643
1960
|
href: segment.href,
|
|
1644
1961
|
label: segment.label,
|
|
1962
|
+
onMentionAction,
|
|
1645
1963
|
onOpenWorkspaceReference
|
|
1646
1964
|
},
|
|
1647
1965
|
`link:${index}:${segment.href}`
|
|
@@ -1653,18 +1971,27 @@ function renderReadonlyInlineMarkdown(content, onOpenWorkspaceReference) {
|
|
|
1653
1971
|
function RichTextReadonlyInlineLink({
|
|
1654
1972
|
href,
|
|
1655
1973
|
label,
|
|
1974
|
+
onMentionAction,
|
|
1656
1975
|
onOpenWorkspaceReference
|
|
1657
1976
|
}) {
|
|
1658
1977
|
const trimmedHref = href.trim();
|
|
1659
1978
|
const mention = parseRichTextMentionHref(trimmedHref, label);
|
|
1660
1979
|
if (mention) {
|
|
1661
|
-
return /* @__PURE__ */
|
|
1980
|
+
return /* @__PURE__ */ jsx8(
|
|
1981
|
+
RichTextMentionReadonly,
|
|
1982
|
+
{
|
|
1983
|
+
mention,
|
|
1984
|
+
onClick: onMentionAction ? ({ mention: mention2 }) => {
|
|
1985
|
+
void onMentionAction(mention2);
|
|
1986
|
+
} : void 0
|
|
1987
|
+
}
|
|
1988
|
+
);
|
|
1662
1989
|
}
|
|
1663
1990
|
if (!trimmedHref) {
|
|
1664
|
-
return /* @__PURE__ */
|
|
1991
|
+
return /* @__PURE__ */ jsx8("span", { children: label });
|
|
1665
1992
|
}
|
|
1666
1993
|
if (externalHrefPattern.test(trimmedHref) && !isRichTextMentionHref(trimmedHref)) {
|
|
1667
|
-
return /* @__PURE__ */
|
|
1994
|
+
return /* @__PURE__ */ jsx8(
|
|
1668
1995
|
"a",
|
|
1669
1996
|
{
|
|
1670
1997
|
className: "font-medium text-[var(--text-primary)] underline decoration-[var(--border-1)] underline-offset-4 hover:text-[var(--text-primary-hover)]",
|
|
@@ -1677,7 +2004,7 @@ function RichTextReadonlyInlineLink({
|
|
|
1677
2004
|
}
|
|
1678
2005
|
const kind = trimmedHref.endsWith("/") ? "folder" : "file";
|
|
1679
2006
|
const path = normalizeRichTextLinkHref(trimmedHref, kind);
|
|
1680
|
-
return /* @__PURE__ */
|
|
2007
|
+
return /* @__PURE__ */ jsx8(
|
|
1681
2008
|
WorkspaceReferenceReadonly,
|
|
1682
2009
|
{
|
|
1683
2010
|
reference: {
|
|
@@ -1697,7 +2024,7 @@ function WorkspaceReferenceReadonly({
|
|
|
1697
2024
|
reference.label,
|
|
1698
2025
|
reference.path
|
|
1699
2026
|
);
|
|
1700
|
-
const content = /* @__PURE__ */
|
|
2027
|
+
const content = /* @__PURE__ */ jsx8(
|
|
1701
2028
|
MentionPill3,
|
|
1702
2029
|
{
|
|
1703
2030
|
className: richTextWorkspaceReferencePillClassName3,
|
|
@@ -1707,8 +2034,8 @@ function WorkspaceReferenceReadonly({
|
|
|
1707
2034
|
removable: false
|
|
1708
2035
|
}
|
|
1709
2036
|
);
|
|
1710
|
-
return /* @__PURE__ */
|
|
1711
|
-
/* @__PURE__ */
|
|
2037
|
+
return /* @__PURE__ */ jsxs7(Tooltip2, { children: [
|
|
2038
|
+
/* @__PURE__ */ jsx8(TooltipTrigger2, { asChild: true, children: onOpenWorkspaceReference ? /* @__PURE__ */ jsx8(
|
|
1712
2039
|
"button",
|
|
1713
2040
|
{
|
|
1714
2041
|
className: "inline-flex max-w-full appearance-none items-baseline bg-transparent p-0 text-inherit",
|
|
@@ -1718,8 +2045,8 @@ function WorkspaceReferenceReadonly({
|
|
|
1718
2045
|
},
|
|
1719
2046
|
children: content
|
|
1720
2047
|
}
|
|
1721
|
-
) : /* @__PURE__ */
|
|
1722
|
-
/* @__PURE__ */
|
|
2048
|
+
) : /* @__PURE__ */ jsx8("span", { className: "inline-flex max-w-full align-baseline", children: content }) }),
|
|
2049
|
+
/* @__PURE__ */ jsx8(
|
|
1723
2050
|
TooltipContent2,
|
|
1724
2051
|
{
|
|
1725
2052
|
className: "max-w-md whitespace-normal break-all",
|
|
@@ -1730,9 +2057,12 @@ function WorkspaceReferenceReadonly({
|
|
|
1730
2057
|
] });
|
|
1731
2058
|
}
|
|
1732
2059
|
export {
|
|
1733
|
-
RichTextAtEditor,
|
|
1734
|
-
RichTextAtTextarea,
|
|
1735
2060
|
RichTextMentionReadonly,
|
|
1736
|
-
RichTextReadonlyContent
|
|
2061
|
+
RichTextReadonlyContent,
|
|
2062
|
+
RichTextTriggerEditor,
|
|
2063
|
+
RichTextTriggerTextarea,
|
|
2064
|
+
findRichTextTriggerQuery,
|
|
2065
|
+
isRichTextTriggerPrefixBoundary,
|
|
2066
|
+
queryRichTextTriggerMatches
|
|
1737
2067
|
};
|
|
1738
2068
|
//# sourceMappingURL=index.js.map
|