@get-bb/plugin-sdk 0.5.30 → 0.6.5
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 +8 -4
- package/bundled-types/bb-plugin-sdk-app.d.ts +264 -384
- package/bundled-types/bb-plugin-sdk-environment-provider.d.ts +0 -84
- package/bundled-types/bb-plugin-sdk-internal-composer-handle.d.ts +457 -0
- package/bundled-types/bb-plugin-sdk-internal-host-policy.d.ts +21 -88
- package/bundled-types/bb-plugin-sdk-testing-app.d.ts +32 -10
- package/bundled-types/bb-plugin-sdk-testing.d.ts +8 -87
- package/bundled-types/bb-plugin-sdk.d.ts +298 -391
- package/dist/app.js +2 -0
- package/dist/internal/composer-customization-validation.js +37 -27
- package/dist/internal/composer-handle.js +531 -0
- package/dist/internal/plugin-app-collector.js +37 -27
- package/dist/testing/app.js +780 -132
- package/dist/testing/index.js +7 -4
- package/package.json +7 -1
package/dist/testing/app.js
CHANGED
|
@@ -16,6 +16,522 @@ function isComposerDraftEmpty(text, attachmentCount) {
|
|
|
16
16
|
return text.trim().length === 0 && attachmentCount === 0;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// src/internal/composer-draft-transforms.ts
|
|
20
|
+
function removeComposerMentions(draft, matches) {
|
|
21
|
+
const removed = draft.mentions.filter(matches).sort((a, b) => a.from - b.from);
|
|
22
|
+
if (removed.length === 0) return draft;
|
|
23
|
+
let text = "";
|
|
24
|
+
let cursor = 0;
|
|
25
|
+
for (const mention of removed) {
|
|
26
|
+
text += draft.text.slice(cursor, mention.from);
|
|
27
|
+
cursor = mention.to;
|
|
28
|
+
}
|
|
29
|
+
const removedSet = new Set(removed);
|
|
30
|
+
const mentions = draft.mentions.filter((mention) => !removedSet.has(mention)).map((mention) => {
|
|
31
|
+
const offset = removed.reduce(
|
|
32
|
+
(sum, item) => sum + (item.to <= mention.from ? item.to - item.from : 0),
|
|
33
|
+
0
|
|
34
|
+
);
|
|
35
|
+
return offset === 0 ? mention : { ...mention, from: mention.from - offset, to: mention.to - offset };
|
|
36
|
+
});
|
|
37
|
+
return { ...draft, text: text + draft.text.slice(cursor), mentions };
|
|
38
|
+
}
|
|
39
|
+
function setComposerText(draft, text) {
|
|
40
|
+
if (text === draft.text) return draft;
|
|
41
|
+
return {
|
|
42
|
+
...draft,
|
|
43
|
+
text,
|
|
44
|
+
mentions: reconcileComposerMentions(draft.text, text, draft.mentions)
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function reconcileComposerMentions(currentText, nextText, mentions) {
|
|
48
|
+
if (currentText === nextText) return [...mentions];
|
|
49
|
+
let unchangedPrefixLength = 0;
|
|
50
|
+
const maximumPrefixLength = Math.min(currentText.length, nextText.length);
|
|
51
|
+
while (unchangedPrefixLength < maximumPrefixLength && currentText[unchangedPrefixLength] === nextText[unchangedPrefixLength]) {
|
|
52
|
+
unchangedPrefixLength += 1;
|
|
53
|
+
}
|
|
54
|
+
let unchangedSuffixLength = 0;
|
|
55
|
+
while (unchangedSuffixLength < currentText.length - unchangedPrefixLength && unchangedSuffixLength < nextText.length - unchangedPrefixLength && currentText[currentText.length - unchangedSuffixLength - 1] === nextText[nextText.length - unchangedSuffixLength - 1]) {
|
|
56
|
+
unchangedSuffixLength += 1;
|
|
57
|
+
}
|
|
58
|
+
const replacedCurrentEnd = currentText.length - unchangedSuffixLength;
|
|
59
|
+
const replacementDelta = nextText.length - currentText.length;
|
|
60
|
+
return mentions.flatMap((mention) => {
|
|
61
|
+
if (mention.to <= unchangedPrefixLength) return [mention];
|
|
62
|
+
if (mention.from >= replacedCurrentEnd) {
|
|
63
|
+
return [
|
|
64
|
+
{
|
|
65
|
+
...mention,
|
|
66
|
+
from: mention.from + replacementDelta,
|
|
67
|
+
to: mention.to + replacementDelta
|
|
68
|
+
}
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
return [];
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/internal/composer-draft-actions.ts
|
|
76
|
+
import { z } from "zod";
|
|
77
|
+
var range = {
|
|
78
|
+
from: z.number().int().nonnegative(),
|
|
79
|
+
to: z.number().int().nonnegative(),
|
|
80
|
+
label: z.string()
|
|
81
|
+
};
|
|
82
|
+
var mentionSchema = z.discriminatedUnion("kind", [
|
|
83
|
+
z.object({
|
|
84
|
+
...range,
|
|
85
|
+
kind: z.literal("thread"),
|
|
86
|
+
threadId: z.string(),
|
|
87
|
+
projectId: z.string().optional()
|
|
88
|
+
}),
|
|
89
|
+
z.object({ ...range, kind: z.literal("project"), projectId: z.string() }),
|
|
90
|
+
z.object({ ...range, kind: z.literal("section"), sectionId: z.string() }),
|
|
91
|
+
z.object({
|
|
92
|
+
...range,
|
|
93
|
+
kind: z.literal("path"),
|
|
94
|
+
path: z.string(),
|
|
95
|
+
source: z.enum(["workspace", "thread-storage"]),
|
|
96
|
+
entryKind: z.enum(["file", "directory"])
|
|
97
|
+
}),
|
|
98
|
+
z.object({
|
|
99
|
+
...range,
|
|
100
|
+
kind: z.literal("command"),
|
|
101
|
+
trigger: z.enum(["/", "$"]),
|
|
102
|
+
name: z.string(),
|
|
103
|
+
source: z.enum(["skill", "command"]),
|
|
104
|
+
origin: z.enum(["builtin", "project", "user"]),
|
|
105
|
+
argumentHint: z.string().nullable()
|
|
106
|
+
}),
|
|
107
|
+
z.object({
|
|
108
|
+
...range,
|
|
109
|
+
kind: z.literal("plugin"),
|
|
110
|
+
pluginId: z.string(),
|
|
111
|
+
provider: z.string().min(1).refine((value) => !value.includes(":")),
|
|
112
|
+
id: z.string(),
|
|
113
|
+
icon: z.string().nullable().optional()
|
|
114
|
+
})
|
|
115
|
+
]);
|
|
116
|
+
var attachmentsSchema = z.array(
|
|
117
|
+
z.object({
|
|
118
|
+
type: z.enum(["localImage", "localFile"]),
|
|
119
|
+
path: z.string().min(1),
|
|
120
|
+
name: z.string(),
|
|
121
|
+
mimeType: z.string().optional(),
|
|
122
|
+
sizeBytes: z.number().nonnegative()
|
|
123
|
+
})
|
|
124
|
+
);
|
|
125
|
+
var replacementSchema = z.object({
|
|
126
|
+
text: z.string(),
|
|
127
|
+
mentions: z.array(mentionSchema),
|
|
128
|
+
attachments: attachmentsSchema.optional()
|
|
129
|
+
}).superRefine((draft, context) => {
|
|
130
|
+
let end = 0;
|
|
131
|
+
for (const mention of [...draft.mentions].sort((a, b) => a.from - b.from)) {
|
|
132
|
+
if (mention.from < end || mention.to <= mention.from || mention.to > draft.text.length) {
|
|
133
|
+
context.addIssue({
|
|
134
|
+
code: "custom",
|
|
135
|
+
message: "Mention ranges must be non-overlapping and inside the draft text."
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
end = mention.to;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
function createComposerDraftActions(target) {
|
|
142
|
+
let cached = null;
|
|
143
|
+
const snapshotOf = (current) => {
|
|
144
|
+
const content = current.getDraft();
|
|
145
|
+
if (cached?.content === content) return cached.snapshot;
|
|
146
|
+
const snapshot = Object.freeze({
|
|
147
|
+
text: content.text,
|
|
148
|
+
mentions: Object.freeze(
|
|
149
|
+
content.mentions.map((mention) => Object.freeze({ ...mention }))
|
|
150
|
+
),
|
|
151
|
+
attachments: Object.freeze(
|
|
152
|
+
content.attachments.map(
|
|
153
|
+
(attachment) => Object.freeze({ ...attachment })
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
});
|
|
157
|
+
cached = { content, snapshot };
|
|
158
|
+
return snapshot;
|
|
159
|
+
};
|
|
160
|
+
const requireAvailable = (current) => {
|
|
161
|
+
if (!current.isAvailable())
|
|
162
|
+
throw new Error("This composer is no longer available.");
|
|
163
|
+
};
|
|
164
|
+
return {
|
|
165
|
+
get draft() {
|
|
166
|
+
return snapshotOf(target());
|
|
167
|
+
},
|
|
168
|
+
replace(next) {
|
|
169
|
+
const current = target();
|
|
170
|
+
requireAvailable(current);
|
|
171
|
+
const snapshot = snapshotOf(current);
|
|
172
|
+
const result = typeof next === "function" ? next(snapshot) : next;
|
|
173
|
+
requireAvailable(current);
|
|
174
|
+
if (result === snapshot) return;
|
|
175
|
+
const parsed = replacementSchema.safeParse(result);
|
|
176
|
+
if (!parsed.success)
|
|
177
|
+
throw new Error(
|
|
178
|
+
"Invalid composer draft: text, mention ranges, or attachments are invalid."
|
|
179
|
+
);
|
|
180
|
+
current.setDraft(parsed.data);
|
|
181
|
+
},
|
|
182
|
+
focus() {
|
|
183
|
+
const current = target();
|
|
184
|
+
requireAvailable(current);
|
|
185
|
+
current.focus();
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/internal/composer-handle.ts
|
|
191
|
+
var UNAVAILABLE_MESSAGE = "This composer is no longer available.";
|
|
192
|
+
var OFF_SCREEN_MESSAGE = "This composer isn't on screen.";
|
|
193
|
+
var warnedAliases = /* @__PURE__ */ new Set();
|
|
194
|
+
function warnDeprecatedComposerMember(oldName, newName) {
|
|
195
|
+
if (warnedAliases.has(oldName)) return;
|
|
196
|
+
warnedAliases.add(oldName);
|
|
197
|
+
console.warn(
|
|
198
|
+
`useComposer().${oldName} is deprecated; use useComposer().${newName}.`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
function appendComposerDraft(current, value, block) {
|
|
202
|
+
const base = block ? current.text.replace(/\s+$/u, "") : current.text;
|
|
203
|
+
const separator = block && base.length > 0 ? "\n\n" : "";
|
|
204
|
+
const offset = base.length + separator.length;
|
|
205
|
+
return {
|
|
206
|
+
text: `${base}${separator}${value.text}`,
|
|
207
|
+
mentions: [
|
|
208
|
+
...current.mentions.filter((mention) => mention.to <= base.length),
|
|
209
|
+
...value.mentions.map((mention) => ({
|
|
210
|
+
...mention,
|
|
211
|
+
from: mention.from + offset,
|
|
212
|
+
to: mention.to + offset
|
|
213
|
+
}))
|
|
214
|
+
]
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function validProviderId(provider) {
|
|
218
|
+
const trimmed = provider.trim();
|
|
219
|
+
return trimmed.length === 0 || trimmed.includes(":") ? null : trimmed;
|
|
220
|
+
}
|
|
221
|
+
function insertValue(parts, controller) {
|
|
222
|
+
const list = Array.isArray(parts) ? parts : [parts];
|
|
223
|
+
let text = "";
|
|
224
|
+
const mentions = [];
|
|
225
|
+
for (const part of list) {
|
|
226
|
+
if (typeof part === "string") {
|
|
227
|
+
text += part;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
let mention;
|
|
231
|
+
if ("kind" in part) {
|
|
232
|
+
mention = part;
|
|
233
|
+
} else {
|
|
234
|
+
const provider = validProviderId(part.provider);
|
|
235
|
+
if (provider === null) {
|
|
236
|
+
throw new Error(`Invalid mention provider id "${part.provider}".`);
|
|
237
|
+
}
|
|
238
|
+
mention = {
|
|
239
|
+
from: 0,
|
|
240
|
+
to: 0,
|
|
241
|
+
label: part.label.trim() || part.id,
|
|
242
|
+
kind: "plugin",
|
|
243
|
+
pluginId: controller.pluginId,
|
|
244
|
+
provider,
|
|
245
|
+
id: part.id,
|
|
246
|
+
icon: null
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
const serialized = controller.mentionText(mention);
|
|
250
|
+
mentions.push({
|
|
251
|
+
...mention,
|
|
252
|
+
from: text.length,
|
|
253
|
+
to: text.length + serialized.length
|
|
254
|
+
});
|
|
255
|
+
text += serialized;
|
|
256
|
+
}
|
|
257
|
+
return { text, mentions };
|
|
258
|
+
}
|
|
259
|
+
function isDraftEmpty(draft, attachmentCount) {
|
|
260
|
+
return draft.text.trim().length === 0 && draft.mentions.length === 0 && attachmentCount === 0;
|
|
261
|
+
}
|
|
262
|
+
var legacyDrafts = /* @__PURE__ */ new WeakMap();
|
|
263
|
+
function withLegacyDraftFields(draft, attachmentCount) {
|
|
264
|
+
const cached = legacyDrafts.get(draft);
|
|
265
|
+
if (cached) return cached;
|
|
266
|
+
const snapshot = { ...draft };
|
|
267
|
+
Object.defineProperties(snapshot, {
|
|
268
|
+
isEmpty: { value: isDraftEmpty(draft, attachmentCount), enumerable: false },
|
|
269
|
+
attachmentCount: { value: attachmentCount, enumerable: false }
|
|
270
|
+
});
|
|
271
|
+
Object.freeze(snapshot);
|
|
272
|
+
legacyDrafts.set(draft, snapshot);
|
|
273
|
+
return snapshot;
|
|
274
|
+
}
|
|
275
|
+
function waitForUploads(target) {
|
|
276
|
+
return new Promise((resolve) => {
|
|
277
|
+
if (!target.getEditorState().isAttaching) {
|
|
278
|
+
resolve();
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const unsubscribe = target.subscribeEditorState(() => {
|
|
282
|
+
if (target.getEditorState().isAttaching) return;
|
|
283
|
+
unsubscribe();
|
|
284
|
+
resolve();
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
function createComposerHandleBinding(key, initial) {
|
|
289
|
+
let controller = initial;
|
|
290
|
+
const target = () => controller.target;
|
|
291
|
+
const editorState = () => target().getEditorState();
|
|
292
|
+
const requireAvailable = () => {
|
|
293
|
+
if (!target().isAvailable()) throw new Error(UNAVAILABLE_MESSAGE);
|
|
294
|
+
};
|
|
295
|
+
const legacyAvailable = (method) => {
|
|
296
|
+
if (target().isAvailable()) return true;
|
|
297
|
+
console.warn(
|
|
298
|
+
`[plugin:${controller.pluginId}] useComposer().${method}: ${UNAVAILABLE_MESSAGE}`
|
|
299
|
+
);
|
|
300
|
+
return false;
|
|
301
|
+
};
|
|
302
|
+
const replaceText = (nextText) => {
|
|
303
|
+
const current = target().getDraft();
|
|
304
|
+
if (nextText === current.text) return;
|
|
305
|
+
target().setDraft(setComposerText(current, nextText));
|
|
306
|
+
};
|
|
307
|
+
const insertMention = (mention) => {
|
|
308
|
+
warnDeprecatedComposerMember("insertMention", "insert");
|
|
309
|
+
const provider = validProviderId(mention.provider);
|
|
310
|
+
if (provider === null) {
|
|
311
|
+
console.warn(
|
|
312
|
+
`[plugin:${controller.pluginId}] useComposer().insertMention: invalid provider id "${mention.provider}"`
|
|
313
|
+
);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (!legacyAvailable("insertMention")) return;
|
|
317
|
+
const label = mention.label.trim() || mention.id;
|
|
318
|
+
const current = target().getDraft();
|
|
319
|
+
const separator = current.text.length === 0 || /\s$/u.test(current.text) ? "" : " ";
|
|
320
|
+
const from = current.text.length + separator.length;
|
|
321
|
+
target().setDraft({
|
|
322
|
+
text: `${current.text}${separator}${label} `,
|
|
323
|
+
mentions: [
|
|
324
|
+
...current.mentions,
|
|
325
|
+
{
|
|
326
|
+
from,
|
|
327
|
+
to: from + label.length,
|
|
328
|
+
label,
|
|
329
|
+
kind: "plugin",
|
|
330
|
+
pluginId: controller.pluginId,
|
|
331
|
+
provider,
|
|
332
|
+
id: mention.id,
|
|
333
|
+
icon: null
|
|
334
|
+
}
|
|
335
|
+
]
|
|
336
|
+
});
|
|
337
|
+
target().focus();
|
|
338
|
+
};
|
|
339
|
+
const removeMentionFrom = (mention) => {
|
|
340
|
+
const current = target().getDraft();
|
|
341
|
+
const next = removeComposerMentions(
|
|
342
|
+
current,
|
|
343
|
+
(item) => item.kind === "plugin" && item.pluginId === controller.pluginId && item.provider === mention.provider && item.id === mention.id
|
|
344
|
+
);
|
|
345
|
+
if (next !== current) target().setDraft(next);
|
|
346
|
+
};
|
|
347
|
+
const removeMention = (mention) => {
|
|
348
|
+
warnDeprecatedComposerMember("removeMention", "replace");
|
|
349
|
+
requireAvailable();
|
|
350
|
+
removeMentionFrom(mention);
|
|
351
|
+
};
|
|
352
|
+
const insert = (parts, options = {}) => {
|
|
353
|
+
requireAvailable();
|
|
354
|
+
const value = insertValue(parts, controller);
|
|
355
|
+
const block = options.block === true;
|
|
356
|
+
if ((options.at ?? "cursor") === "cursor") {
|
|
357
|
+
if (!target().insertAtCursor(value, block)) {
|
|
358
|
+
throw new Error(OFF_SCREEN_MESSAGE);
|
|
359
|
+
}
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
target().setDraft(appendComposerDraft(target().getDraft(), value, block));
|
|
363
|
+
};
|
|
364
|
+
const submit = async (options) => {
|
|
365
|
+
requireAvailable();
|
|
366
|
+
const hostSubmit = target().submit;
|
|
367
|
+
if (hostSubmit === void 0) {
|
|
368
|
+
throw new Error("This composer cannot submit programmatically.");
|
|
369
|
+
}
|
|
370
|
+
if (options.sendAt !== void 0 && (!Number.isFinite(options.sendAt) || options.sendAt <= Date.now())) {
|
|
371
|
+
throw new Error("Pick a time in the future.");
|
|
372
|
+
}
|
|
373
|
+
const errorBefore = editorState().attachmentError;
|
|
374
|
+
await waitForUploads(target());
|
|
375
|
+
const state = editorState();
|
|
376
|
+
if (state.attachmentError !== null && state.attachmentError !== errorBefore) {
|
|
377
|
+
throw new Error(state.attachmentError);
|
|
378
|
+
}
|
|
379
|
+
if (state.isSubmittingBlocked) {
|
|
380
|
+
throw new Error(
|
|
381
|
+
state.submittingBlockedReason ?? "This composer can't submit right now."
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
await hostSubmit(
|
|
385
|
+
options,
|
|
386
|
+
options.experimental_data === void 0 ? void 0 : { pluginId: controller.pluginId, data: options.experimental_data }
|
|
387
|
+
);
|
|
388
|
+
};
|
|
389
|
+
const setSelection = async (selection) => {
|
|
390
|
+
requireAvailable();
|
|
391
|
+
const hostSetSelection = target().setSelection;
|
|
392
|
+
if (hostSetSelection === void 0) {
|
|
393
|
+
throw new Error("This composer has no pickers to set.");
|
|
394
|
+
}
|
|
395
|
+
return hostSetSelection(selection);
|
|
396
|
+
};
|
|
397
|
+
const submittedListeners = /* @__PURE__ */ new Set();
|
|
398
|
+
let submittedSubscription = null;
|
|
399
|
+
const syncSubmittedSubscription = () => {
|
|
400
|
+
const scope = submittedListeners.size === 0 ? null : JSON.stringify(target().scope);
|
|
401
|
+
if (submittedSubscription?.scope === scope) return;
|
|
402
|
+
submittedSubscription?.unsubscribe();
|
|
403
|
+
submittedSubscription = scope === null ? null : {
|
|
404
|
+
scope,
|
|
405
|
+
unsubscribe: controller.onSubmitted(() => {
|
|
406
|
+
for (const listener of [...submittedListeners]) {
|
|
407
|
+
try {
|
|
408
|
+
listener();
|
|
409
|
+
} catch (error) {
|
|
410
|
+
console.error("Composer submission listener failed", error);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
})
|
|
414
|
+
};
|
|
415
|
+
};
|
|
416
|
+
const onSubmitted = (listener) => {
|
|
417
|
+
const entry = () => listener();
|
|
418
|
+
submittedListeners.add(entry);
|
|
419
|
+
syncSubmittedSubscription();
|
|
420
|
+
return () => {
|
|
421
|
+
submittedListeners.delete(entry);
|
|
422
|
+
syncSubmittedSubscription();
|
|
423
|
+
};
|
|
424
|
+
};
|
|
425
|
+
const draftActions = createComposerDraftActions(target);
|
|
426
|
+
const handle = {
|
|
427
|
+
replace: draftActions.replace,
|
|
428
|
+
get scope() {
|
|
429
|
+
return target().scope;
|
|
430
|
+
},
|
|
431
|
+
get key() {
|
|
432
|
+
return target().key;
|
|
433
|
+
},
|
|
434
|
+
get layout() {
|
|
435
|
+
return editorState().layout;
|
|
436
|
+
},
|
|
437
|
+
get isRunning() {
|
|
438
|
+
return editorState().isRunning;
|
|
439
|
+
},
|
|
440
|
+
get isSubmitting() {
|
|
441
|
+
return editorState().isSubmitting;
|
|
442
|
+
},
|
|
443
|
+
get isSubmittingBlocked() {
|
|
444
|
+
return editorState().isSubmittingBlocked;
|
|
445
|
+
},
|
|
446
|
+
get submittingBlockedReason() {
|
|
447
|
+
return editorState().submittingBlockedReason;
|
|
448
|
+
},
|
|
449
|
+
get isEmpty() {
|
|
450
|
+
return isDraftEmpty(target().getDraft(), target().getAttachmentCount());
|
|
451
|
+
},
|
|
452
|
+
get attachmentCount() {
|
|
453
|
+
return target().getAttachmentCount();
|
|
454
|
+
},
|
|
455
|
+
get text() {
|
|
456
|
+
return target().getDraft().text;
|
|
457
|
+
},
|
|
458
|
+
get draft() {
|
|
459
|
+
return withLegacyDraftFields(
|
|
460
|
+
draftActions.draft,
|
|
461
|
+
target().getAttachmentCount()
|
|
462
|
+
);
|
|
463
|
+
},
|
|
464
|
+
get selection() {
|
|
465
|
+
return target().getSelection();
|
|
466
|
+
},
|
|
467
|
+
setText: (next) => {
|
|
468
|
+
warnDeprecatedComposerMember("setText", "replace");
|
|
469
|
+
if (legacyAvailable("setText")) replaceText(next);
|
|
470
|
+
},
|
|
471
|
+
updateText: (updater) => {
|
|
472
|
+
warnDeprecatedComposerMember("updateText", "replace");
|
|
473
|
+
if (legacyAvailable("updateText")) {
|
|
474
|
+
replaceText(updater(target().getDraft().text));
|
|
475
|
+
}
|
|
476
|
+
},
|
|
477
|
+
clear: () => {
|
|
478
|
+
warnDeprecatedComposerMember("clear", "replace");
|
|
479
|
+
if (legacyAvailable("clear")) replaceText("");
|
|
480
|
+
},
|
|
481
|
+
insert,
|
|
482
|
+
setTextEffect: (effect) => controller.setTextEffect(effect),
|
|
483
|
+
setInputLock: (locked) => controller.setInputLock(locked),
|
|
484
|
+
addQuote: (text) => {
|
|
485
|
+
warnDeprecatedComposerMember("addQuote", "replace");
|
|
486
|
+
if (!legacyAvailable("addQuote")) return;
|
|
487
|
+
target().addQuote(text);
|
|
488
|
+
target().focus();
|
|
489
|
+
},
|
|
490
|
+
insertMention,
|
|
491
|
+
removeMention,
|
|
492
|
+
onSubmitted,
|
|
493
|
+
focus: () => target().focus(),
|
|
494
|
+
submit,
|
|
495
|
+
setSelection,
|
|
496
|
+
experimental_removeMention: (mention) => {
|
|
497
|
+
warnDeprecatedComposerMember("experimental_removeMention", "replace");
|
|
498
|
+
if (!legacyAvailable("experimental_removeMention")) return;
|
|
499
|
+
removeMentionFrom(mention);
|
|
500
|
+
},
|
|
501
|
+
experimental_onSubmitted: (listener) => {
|
|
502
|
+
warnDeprecatedComposerMember("experimental_onSubmitted", "onSubmitted");
|
|
503
|
+
return onSubmitted(listener);
|
|
504
|
+
},
|
|
505
|
+
experimental_submit: (options) => {
|
|
506
|
+
warnDeprecatedComposerMember("experimental_submit", "submit");
|
|
507
|
+
return submit(options);
|
|
508
|
+
},
|
|
509
|
+
experimental_setSelection: (selection) => {
|
|
510
|
+
warnDeprecatedComposerMember("experimental_setSelection", "setSelection");
|
|
511
|
+
return setSelection(selection);
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
Object.defineProperties(handle, {
|
|
515
|
+
run: {
|
|
516
|
+
enumerable: false,
|
|
517
|
+
get: () => {
|
|
518
|
+
const state = editorState();
|
|
519
|
+
return { isRunning: state.isRunning, isSubmitting: state.isSubmitting };
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
setThreadRowStatus: { enumerable: false, value: () => {
|
|
523
|
+
} }
|
|
524
|
+
});
|
|
525
|
+
return {
|
|
526
|
+
key,
|
|
527
|
+
handle,
|
|
528
|
+
update(next) {
|
|
529
|
+
controller = next;
|
|
530
|
+
syncSubmittedSubscription();
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
|
|
19
535
|
// src/internal/composer-customization-validation.ts
|
|
20
536
|
var PLUGIN_SLOT_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
21
537
|
var PLUGIN_MESSAGE_DIRECTIVE_ID_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
@@ -141,6 +657,30 @@ function parseContributionArray(kind, value, onRejected, parse) {
|
|
|
141
657
|
}
|
|
142
658
|
return parsed;
|
|
143
659
|
}
|
|
660
|
+
function parseMenuItem(entryKind, value) {
|
|
661
|
+
const entry = value;
|
|
662
|
+
const id = requireSlotId(entryKind, entry?.id);
|
|
663
|
+
const icon = requireOptionalString(entryKind, "icon", entry?.icon);
|
|
664
|
+
const description = requireOptionalString(
|
|
665
|
+
entryKind,
|
|
666
|
+
"description",
|
|
667
|
+
entry?.description
|
|
668
|
+
);
|
|
669
|
+
const disabled = entry?.disabled;
|
|
670
|
+
if (disabled !== void 0 && typeof disabled !== "boolean" && typeof disabled !== "function") {
|
|
671
|
+
throw new Error(
|
|
672
|
+
`${entryKind}: "disabled" must be a boolean or function when set`
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
return {
|
|
676
|
+
id,
|
|
677
|
+
label: requireNonEmptyString(entryKind, "label", entry?.label),
|
|
678
|
+
...icon !== void 0 ? { icon } : {},
|
|
679
|
+
...description !== void 0 ? { description } : {},
|
|
680
|
+
...disabled !== void 0 ? { disabled } : {},
|
|
681
|
+
run: requireFunction(entryKind, "run", entry?.run)
|
|
682
|
+
};
|
|
683
|
+
}
|
|
144
684
|
function parseRegions(kind, registration, onRejected) {
|
|
145
685
|
const actions = parseContributionArray(`${kind}.actions`, registration.actions, onRejected, (entryKind, value) => {
|
|
146
686
|
const entry = value;
|
|
@@ -168,32 +708,13 @@ function parseRegions(kind, registration, onRejected) {
|
|
|
168
708
|
`${kind}.plusMenu`,
|
|
169
709
|
registration.plusMenu,
|
|
170
710
|
onRejected,
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
entry?.description
|
|
179
|
-
);
|
|
180
|
-
const disabled = entry?.disabled;
|
|
181
|
-
if (disabled !== void 0 && typeof disabled !== "boolean" && typeof disabled !== "function") {
|
|
182
|
-
throw new Error(
|
|
183
|
-
`${entryKind}: "disabled" must be a boolean or function when set`
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
return {
|
|
187
|
-
id,
|
|
188
|
-
label: requireNonEmptyString(entryKind, "label", entry?.label),
|
|
189
|
-
...icon !== void 0 ? { icon } : {},
|
|
190
|
-
...description !== void 0 ? { description } : {},
|
|
191
|
-
...disabled !== void 0 ? {
|
|
192
|
-
disabled
|
|
193
|
-
} : {},
|
|
194
|
-
run: requireFunction(entryKind, "run", entry?.run)
|
|
195
|
-
};
|
|
196
|
-
}
|
|
711
|
+
parseMenuItem
|
|
712
|
+
);
|
|
713
|
+
const sendMenu = parseContributionArray(
|
|
714
|
+
`${kind}.sendMenu`,
|
|
715
|
+
registration.sendMenu,
|
|
716
|
+
onRejected,
|
|
717
|
+
parseMenuItem
|
|
197
718
|
);
|
|
198
719
|
let richText;
|
|
199
720
|
if (registration.richText !== void 0) {
|
|
@@ -236,6 +757,7 @@ function parseRegions(kind, registration, onRejected) {
|
|
|
236
757
|
...actions !== void 0 ? { actions } : {},
|
|
237
758
|
...banners !== void 0 ? { banners } : {},
|
|
238
759
|
...plusMenu !== void 0 ? { plusMenu } : {},
|
|
760
|
+
...sendMenu !== void 0 ? { sendMenu } : {},
|
|
239
761
|
...richText !== void 0 ? { richText } : {}
|
|
240
762
|
};
|
|
241
763
|
}
|
|
@@ -260,7 +782,11 @@ function collectComposerCustomization(registration, seenIds, onRejected) {
|
|
|
260
782
|
requireUniqueId(kind, seenIds, id);
|
|
261
783
|
return {
|
|
262
784
|
id,
|
|
263
|
-
...scopes !== void 0 ? {
|
|
785
|
+
...scopes !== void 0 ? {
|
|
786
|
+
scopes: scopes.filter(
|
|
787
|
+
(scope) => scope !== "side-chat"
|
|
788
|
+
)
|
|
789
|
+
} : {},
|
|
264
790
|
...parseRegions(`${kind}(${id})`, raw ?? {}, onRejected)
|
|
265
791
|
};
|
|
266
792
|
} catch (error) {
|
|
@@ -401,7 +927,7 @@ function normalizeExperimentalFileOpenOptions(value) {
|
|
|
401
927
|
}
|
|
402
928
|
|
|
403
929
|
// src/internal/plugin-app-collector.ts
|
|
404
|
-
import { z } from "zod";
|
|
930
|
+
import { z as z2 } from "zod";
|
|
405
931
|
var sidebarFooterCommandSequence = 0;
|
|
406
932
|
var SIDEBAR_FOOTER_ACTION_KEYS = /* @__PURE__ */ new Set([
|
|
407
933
|
"id",
|
|
@@ -552,8 +1078,8 @@ function rejectStaleNavPanelKeys(kind, registration) {
|
|
|
552
1078
|
}
|
|
553
1079
|
}
|
|
554
1080
|
}
|
|
555
|
-
var commandShortcutSchema =
|
|
556
|
-
key:
|
|
1081
|
+
var commandShortcutSchema = z2.object({
|
|
1082
|
+
key: z2.string().min(1).max(32).refine(
|
|
557
1083
|
(key) => ![
|
|
558
1084
|
"Alt",
|
|
559
1085
|
"Control",
|
|
@@ -564,11 +1090,11 @@ var commandShortcutSchema = z.object({
|
|
|
564
1090
|
"Unidentified"
|
|
565
1091
|
].includes(key)
|
|
566
1092
|
),
|
|
567
|
-
mod:
|
|
568
|
-
meta:
|
|
569
|
-
control:
|
|
570
|
-
alt:
|
|
571
|
-
shift:
|
|
1093
|
+
mod: z2.boolean().default(false),
|
|
1094
|
+
meta: z2.boolean().default(false),
|
|
1095
|
+
control: z2.boolean().default(false),
|
|
1096
|
+
alt: z2.boolean().default(false),
|
|
1097
|
+
shift: z2.boolean().default(false)
|
|
572
1098
|
}).strict().refine(
|
|
573
1099
|
(shortcut) => shortcut.mod || shortcut.meta || shortcut.control || shortcut.alt || /^F(?:[1-9]|1[0-9]|2[0-4])$/u.test(shortcut.key),
|
|
574
1100
|
"Use Command, Control, or Alt with a key, or a function key"
|
|
@@ -1079,6 +1605,34 @@ function collectPluginAppRegistrations(definition, onComposerCustomizationReject
|
|
|
1079
1605
|
|
|
1080
1606
|
// src/testing/app.tsx
|
|
1081
1607
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
1608
|
+
function testComposerKey(scope) {
|
|
1609
|
+
switch (scope.kind) {
|
|
1610
|
+
case "thread":
|
|
1611
|
+
return `test-composer:thread:${scope.threadId}`;
|
|
1612
|
+
case "queued-message":
|
|
1613
|
+
return `test-composer:queued-message:${scope.threadId}:${scope.queuedMessageId}`;
|
|
1614
|
+
case "new-thread":
|
|
1615
|
+
return `test-composer:new-thread:${scope.projectId ?? ""}`;
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
function testComposerMentionText(mention) {
|
|
1619
|
+
switch (mention.kind) {
|
|
1620
|
+
case "thread":
|
|
1621
|
+
return `@thread:${mention.threadId}`;
|
|
1622
|
+
case "project":
|
|
1623
|
+
return `@project:${mention.projectId}`;
|
|
1624
|
+
case "section":
|
|
1625
|
+
return `@section:${mention.sectionId}`;
|
|
1626
|
+
case "command":
|
|
1627
|
+
return `${mention.trigger}${mention.name}`;
|
|
1628
|
+
case "plugin":
|
|
1629
|
+
return `@${mention.label}`;
|
|
1630
|
+
case "path": {
|
|
1631
|
+
const path = mention.source === "thread-storage" ? `thread-storage:${mention.path}` : mention.path;
|
|
1632
|
+
return `@${path}${mention.entryKind === "directory" && !path.endsWith("/") ? "/" : ""}`;
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1082
1636
|
function createSdkFakeNode(provided, path, calls) {
|
|
1083
1637
|
const callable = function sdkFakeNode() {
|
|
1084
1638
|
};
|
|
@@ -1610,19 +2164,21 @@ var testPluginSdkApp = {
|
|
|
1610
2164
|
},
|
|
1611
2165
|
useComposer() {
|
|
1612
2166
|
const composer = useSlotEnv("useComposer").composer;
|
|
1613
|
-
|
|
2167
|
+
useSyncExternalStore(
|
|
1614
2168
|
composer.subscribe,
|
|
1615
2169
|
composer.getVersionSnapshot,
|
|
1616
2170
|
composer.getVersionSnapshot
|
|
1617
2171
|
);
|
|
1618
|
-
return
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
2172
|
+
return composer.api;
|
|
2173
|
+
},
|
|
2174
|
+
useComposers() {
|
|
2175
|
+
const composer = useSlotEnv("useComposers").composer;
|
|
2176
|
+
useSyncExternalStore(
|
|
2177
|
+
composer.subscribe,
|
|
2178
|
+
composer.getVersionSnapshot,
|
|
2179
|
+
composer.getVersionSnapshot
|
|
1625
2180
|
);
|
|
2181
|
+
return composer.apiList;
|
|
1626
2182
|
},
|
|
1627
2183
|
ThreadChat: TestThreadChat,
|
|
1628
2184
|
Markdown: TestMarkdown,
|
|
@@ -1760,13 +2316,13 @@ var testPluginSdkApp = {
|
|
|
1760
2316
|
const attachmentCount = composer.getAttachmentCount();
|
|
1761
2317
|
return {
|
|
1762
2318
|
scope: composer.getScope(),
|
|
1763
|
-
layout:
|
|
2319
|
+
layout: composer.getLayout(),
|
|
1764
2320
|
draft: {
|
|
1765
2321
|
text,
|
|
1766
2322
|
isEmpty: isComposerDraftEmpty(text, attachmentCount),
|
|
1767
2323
|
attachmentCount
|
|
1768
2324
|
},
|
|
1769
|
-
run:
|
|
2325
|
+
run: composer.getRun()
|
|
1770
2326
|
};
|
|
1771
2327
|
}, [composer, version]);
|
|
1772
2328
|
}
|
|
@@ -2171,26 +2727,76 @@ function renderSlot(registration, props, options = {}) {
|
|
|
2171
2727
|
const projectId = options.context?.projectId ?? null;
|
|
2172
2728
|
const threadId = options.context?.threadId ?? null;
|
|
2173
2729
|
let composerScope = options.composer?.scope ?? (threadId !== null ? { kind: "thread", threadId } : { kind: "new-thread", projectId });
|
|
2730
|
+
let composerSelection = composerScope.kind === "queued-message" ? null : {
|
|
2731
|
+
...composerScope.kind === "new-thread" && projectId !== null ? { projectId } : {},
|
|
2732
|
+
...options.composer?.selection
|
|
2733
|
+
};
|
|
2174
2734
|
let composerText = options.composer?.text ?? "";
|
|
2175
|
-
|
|
2735
|
+
let composerMentions = [
|
|
2736
|
+
...options.composer?.mentions ?? []
|
|
2737
|
+
];
|
|
2738
|
+
let composerAttachments = [...options.composer?.attachments ?? []];
|
|
2739
|
+
let composerAttachmentCount = options.composer?.attachmentCount ?? composerAttachments.length;
|
|
2740
|
+
const composerLayout = options.composer?.layout ?? "expanded";
|
|
2741
|
+
const composerIsRunning = options.composer?.isRunning ?? false;
|
|
2742
|
+
const composerIsSubmitting = options.composer?.isSubmitting ?? false;
|
|
2743
|
+
const composerPluginId = options.pluginId ?? "test-plugin";
|
|
2176
2744
|
let composerVersion = 0;
|
|
2177
2745
|
const composerListeners = /* @__PURE__ */ new Set();
|
|
2178
2746
|
const notifyComposerListeners = () => {
|
|
2179
2747
|
composerVersion += 1;
|
|
2180
2748
|
for (const listener of composerListeners) listener();
|
|
2181
2749
|
};
|
|
2750
|
+
const commitComposerDraft = (nextText, nextMentions) => {
|
|
2751
|
+
if (nextText === composerText && nextMentions === composerMentions) return;
|
|
2752
|
+
composerText = nextText;
|
|
2753
|
+
composerMentions = nextMentions;
|
|
2754
|
+
notifyComposerListeners();
|
|
2755
|
+
};
|
|
2182
2756
|
const commitComposerText = (next) => {
|
|
2183
2757
|
if (next === composerText) return;
|
|
2184
|
-
|
|
2185
|
-
|
|
2758
|
+
commitComposerDraft(
|
|
2759
|
+
next,
|
|
2760
|
+
reconcileComposerMentions(composerText, next, composerMentions)
|
|
2761
|
+
);
|
|
2762
|
+
};
|
|
2763
|
+
const composerBlockedReason = () => {
|
|
2764
|
+
if (options.composer?.submittingBlockedReason !== void 0) {
|
|
2765
|
+
return options.composer.submittingBlockedReason;
|
|
2766
|
+
}
|
|
2767
|
+
if (composerIsSubmitting) return "Submitting...";
|
|
2768
|
+
return composerText.trim() === "" && composerAttachmentCount === 0 ? "Type a message first." : null;
|
|
2769
|
+
};
|
|
2770
|
+
let composerDraftCache = null;
|
|
2771
|
+
const composerDraft = () => {
|
|
2772
|
+
if (composerDraftCache?.version !== composerVersion) {
|
|
2773
|
+
composerDraftCache = {
|
|
2774
|
+
version: composerVersion,
|
|
2775
|
+
draft: {
|
|
2776
|
+
text: composerText,
|
|
2777
|
+
mentions: composerMentions,
|
|
2778
|
+
attachments: composerAttachments
|
|
2779
|
+
}
|
|
2780
|
+
};
|
|
2781
|
+
}
|
|
2782
|
+
return composerDraftCache.draft;
|
|
2186
2783
|
};
|
|
2187
2784
|
const composerLog = {
|
|
2188
2785
|
get text() {
|
|
2189
2786
|
return composerText;
|
|
2190
2787
|
},
|
|
2788
|
+
get draft() {
|
|
2789
|
+
return composerHandle.draft;
|
|
2790
|
+
},
|
|
2791
|
+
get key() {
|
|
2792
|
+
return testComposerKey(composerScope);
|
|
2793
|
+
},
|
|
2191
2794
|
get scope() {
|
|
2192
2795
|
return composerScope;
|
|
2193
2796
|
},
|
|
2797
|
+
get selection() {
|
|
2798
|
+
return composerSelection;
|
|
2799
|
+
},
|
|
2194
2800
|
get attachmentCount() {
|
|
2195
2801
|
return composerAttachmentCount;
|
|
2196
2802
|
},
|
|
@@ -2205,26 +2811,88 @@ function renderSlot(registration, props, options = {}) {
|
|
|
2205
2811
|
selections: []
|
|
2206
2812
|
};
|
|
2207
2813
|
const composerOwnership = { active: true };
|
|
2814
|
+
const composerIsAvailable = () => composerOwnership.active || composerScope.kind !== "queued-message";
|
|
2208
2815
|
const submissionListeners = /* @__PURE__ */ new Set();
|
|
2209
|
-
const
|
|
2816
|
+
const composerTarget = {
|
|
2817
|
+
get key() {
|
|
2818
|
+
return testComposerKey(composerScope);
|
|
2819
|
+
},
|
|
2820
|
+
get scope() {
|
|
2821
|
+
return composerScope;
|
|
2822
|
+
},
|
|
2823
|
+
getDraft: composerDraft,
|
|
2210
2824
|
getAttachmentCount: () => composerAttachmentCount,
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2825
|
+
getSelection: () => composerSelection,
|
|
2826
|
+
setDraft: (next) => {
|
|
2827
|
+
if (next.attachments !== void 0) {
|
|
2828
|
+
composerAttachments = [...next.attachments];
|
|
2829
|
+
composerAttachmentCount = composerAttachments.length;
|
|
2830
|
+
}
|
|
2831
|
+
commitComposerDraft(next.text, [...next.mentions]);
|
|
2217
2832
|
},
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
}
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2833
|
+
addQuote(text) {
|
|
2834
|
+
const trimmed = text.replace(/\r\n|\r/gu, "\n").trim();
|
|
2835
|
+
if (trimmed === "") return;
|
|
2836
|
+
const block = trimmed.split("\n").map((line) => line.length > 0 ? `> ${line}` : ">").join("\n");
|
|
2837
|
+
commitComposerText(
|
|
2838
|
+
composerText === "" ? `${block}
|
|
2839
|
+
` : `${composerText}
|
|
2840
|
+
${block}
|
|
2841
|
+
`
|
|
2842
|
+
);
|
|
2843
|
+
composerLog.quotes.push(text);
|
|
2844
|
+
},
|
|
2845
|
+
getEditorState: () => {
|
|
2846
|
+
const reason = composerBlockedReason();
|
|
2847
|
+
return {
|
|
2848
|
+
layout: composerLayout,
|
|
2849
|
+
isRunning: composerIsRunning,
|
|
2850
|
+
isSubmitting: composerIsSubmitting,
|
|
2851
|
+
isSubmittingBlocked: reason !== null,
|
|
2852
|
+
submittingBlockedReason: reason,
|
|
2853
|
+
isAttaching: false,
|
|
2854
|
+
attachmentError: null
|
|
2855
|
+
};
|
|
2856
|
+
},
|
|
2857
|
+
subscribeEditorState: () => () => {
|
|
2858
|
+
},
|
|
2859
|
+
insertAtCursor(value, block) {
|
|
2860
|
+
composerTarget.setDraft(
|
|
2861
|
+
appendComposerDraft(composerDraft(), value, block)
|
|
2862
|
+
);
|
|
2863
|
+
return true;
|
|
2864
|
+
},
|
|
2865
|
+
isAvailable: composerIsAvailable,
|
|
2866
|
+
focus() {
|
|
2867
|
+
composerLog.focusCount += 1;
|
|
2868
|
+
},
|
|
2869
|
+
async submit(submitOptions) {
|
|
2870
|
+
composerLog.submits.push(submitOptions);
|
|
2871
|
+
commitComposerDraft("", []);
|
|
2872
|
+
for (const listener of submissionListeners) listener();
|
|
2873
|
+
},
|
|
2874
|
+
async setSelection(selection) {
|
|
2875
|
+
if (composerScope.kind === "queued-message") {
|
|
2876
|
+
throw new Error("This composer has no pickers to set.");
|
|
2877
|
+
}
|
|
2878
|
+
const {
|
|
2879
|
+
projectId: _projectId,
|
|
2880
|
+
environment: _environment,
|
|
2881
|
+
...rest
|
|
2882
|
+
} = selection;
|
|
2883
|
+
const accepted = composerScope.kind === "thread" ? rest : { ...selection };
|
|
2884
|
+
composerLog.selections.push(accepted);
|
|
2885
|
+
composerSelection = { ...composerSelection, ...accepted };
|
|
2886
|
+
notifyComposerListeners();
|
|
2887
|
+
return composerSelection;
|
|
2888
|
+
}
|
|
2889
|
+
};
|
|
2890
|
+
const composerHandle = createComposerHandleBinding(
|
|
2891
|
+
testComposerKey(composerScope),
|
|
2892
|
+
{
|
|
2893
|
+
pluginId: composerPluginId,
|
|
2894
|
+
target: composerTarget,
|
|
2895
|
+
mentionText: testComposerMentionText,
|
|
2228
2896
|
setTextEffect(effect) {
|
|
2229
2897
|
if (!composerOwnership.active) return;
|
|
2230
2898
|
composerLog.textEffect = effect;
|
|
@@ -2235,76 +2903,56 @@ function renderSlot(registration, props, options = {}) {
|
|
|
2235
2903
|
composerLog.inputLocked = locked;
|
|
2236
2904
|
composerLog.inputLockCalls.push(locked);
|
|
2237
2905
|
},
|
|
2238
|
-
|
|
2239
|
-
const trimmed = text.replace(/\r\n|\r/gu, "\n").trim();
|
|
2240
|
-
if (trimmed !== "") {
|
|
2241
|
-
const block = trimmed.split("\n").map((line) => line.length > 0 ? `> ${line}` : ">").join("\n");
|
|
2242
|
-
commitComposerText(
|
|
2243
|
-
composerText === "" ? `${block}
|
|
2244
|
-
` : `${composerText}
|
|
2245
|
-
${block}
|
|
2246
|
-
`
|
|
2247
|
-
);
|
|
2248
|
-
composerLog.quotes.push(text);
|
|
2249
|
-
}
|
|
2250
|
-
composerLog.focusCount += 1;
|
|
2251
|
-
},
|
|
2252
|
-
experimental_onSubmitted(listener) {
|
|
2906
|
+
onSubmitted(listener) {
|
|
2253
2907
|
submissionListeners.add(listener);
|
|
2254
2908
|
return () => {
|
|
2255
2909
|
submissionListeners.delete(listener);
|
|
2256
2910
|
};
|
|
2257
|
-
},
|
|
2258
|
-
experimental_removeMention({ provider, id }) {
|
|
2259
|
-
for (let index = composerLog.mentions.length - 1; index >= 0; index -= 1) {
|
|
2260
|
-
const mention = composerLog.mentions[index];
|
|
2261
|
-
if (mention?.provider === provider && mention.id === id) {
|
|
2262
|
-
commitComposerText(composerText.replace(mention.label, ""));
|
|
2263
|
-
composerLog.mentions.splice(index, 1);
|
|
2264
|
-
}
|
|
2265
|
-
}
|
|
2266
|
-
},
|
|
2267
|
-
insertMention(mention) {
|
|
2268
|
-
const label = mention.label.trim() || mention.id;
|
|
2269
|
-
const separator = composerText.length === 0 || /\s$/u.test(composerText) ? "" : " ";
|
|
2270
|
-
commitComposerText(`${composerText}${separator}${label} `);
|
|
2271
|
-
composerLog.mentions.push(mention);
|
|
2272
|
-
composerLog.focusCount += 1;
|
|
2273
|
-
},
|
|
2274
|
-
focus() {
|
|
2275
|
-
composerLog.focusCount += 1;
|
|
2276
|
-
},
|
|
2277
|
-
async experimental_submit(options2) {
|
|
2278
|
-
if (!composerOwnership.active) {
|
|
2279
|
-
throw new Error("This composer is no longer active.");
|
|
2280
|
-
}
|
|
2281
|
-
if (composerText.trim() === "") {
|
|
2282
|
-
throw new Error("Type a message before scheduling it.");
|
|
2283
|
-
}
|
|
2284
|
-
if (options2.sendAt !== void 0 && (!Number.isFinite(options2.sendAt) || options2.sendAt <= Date.now())) {
|
|
2285
|
-
throw new Error("Pick a time in the future.");
|
|
2286
|
-
}
|
|
2287
|
-
composerLog.submits.push(options2);
|
|
2288
|
-
commitComposerText("");
|
|
2289
|
-
for (const listener of submissionListeners) listener();
|
|
2290
|
-
},
|
|
2291
|
-
async experimental_setSelection(selection) {
|
|
2292
|
-
if (!composerOwnership.active) {
|
|
2293
|
-
throw new Error("This composer is no longer active.");
|
|
2294
|
-
}
|
|
2295
|
-
if (composerScope.kind === "queued-message" || composerScope.kind === "side-chat") {
|
|
2296
|
-
throw new Error("This composer has no pickers to set.");
|
|
2297
|
-
}
|
|
2298
|
-
const {
|
|
2299
|
-
projectId: _projectId,
|
|
2300
|
-
environment: _environment,
|
|
2301
|
-
...rest
|
|
2302
|
-
} = selection;
|
|
2303
|
-
const accepted = composerScope.kind === "thread" ? rest : { ...selection };
|
|
2304
|
-
composerLog.selections.push(accepted);
|
|
2305
|
-
return accepted;
|
|
2306
2911
|
}
|
|
2307
2912
|
}
|
|
2913
|
+
).handle;
|
|
2914
|
+
const forgetLoggedMention = ({
|
|
2915
|
+
provider,
|
|
2916
|
+
id
|
|
2917
|
+
}) => {
|
|
2918
|
+
for (let index = composerLog.mentions.length - 1; index >= 0; index -= 1) {
|
|
2919
|
+
const mention = composerLog.mentions[index];
|
|
2920
|
+
if (mention?.provider === provider && mention.id === id) {
|
|
2921
|
+
composerLog.mentions.splice(index, 1);
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
};
|
|
2925
|
+
const { insertMention, removeMention, experimental_removeMention } = composerHandle;
|
|
2926
|
+
Object.assign(composerHandle, {
|
|
2927
|
+
insertMention(mention) {
|
|
2928
|
+
insertMention(mention);
|
|
2929
|
+
composerLog.mentions.push(mention);
|
|
2930
|
+
},
|
|
2931
|
+
removeMention(mention) {
|
|
2932
|
+
removeMention(mention);
|
|
2933
|
+
forgetLoggedMention(mention);
|
|
2934
|
+
},
|
|
2935
|
+
experimental_removeMention(mention) {
|
|
2936
|
+
experimental_removeMention(mention);
|
|
2937
|
+
forgetLoggedMention(mention);
|
|
2938
|
+
}
|
|
2939
|
+
});
|
|
2940
|
+
const composer = {
|
|
2941
|
+
api: composerHandle,
|
|
2942
|
+
apiList: [composerHandle],
|
|
2943
|
+
getAttachmentCount: () => composerAttachmentCount,
|
|
2944
|
+
getLayout: () => composerLayout,
|
|
2945
|
+
getRun: () => ({
|
|
2946
|
+
isRunning: composerIsRunning,
|
|
2947
|
+
isSubmitting: composerIsSubmitting
|
|
2948
|
+
}),
|
|
2949
|
+
getScope: () => composerScope,
|
|
2950
|
+
getText: () => composerText,
|
|
2951
|
+
getVersionSnapshot: () => composerVersion,
|
|
2952
|
+
subscribe(listener) {
|
|
2953
|
+
composerListeners.add(listener);
|
|
2954
|
+
return () => composerListeners.delete(listener);
|
|
2955
|
+
}
|
|
2308
2956
|
};
|
|
2309
2957
|
const env = {
|
|
2310
2958
|
rpcClient,
|
|
@@ -2382,7 +3030,7 @@ ${block}
|
|
|
2382
3030
|
const setRealtimeConnectionState = async (state) => {
|
|
2383
3031
|
await act(async () => realtimeConnection.setState(state));
|
|
2384
3032
|
};
|
|
2385
|
-
const
|
|
3033
|
+
const setComposerText2 = async (text) => {
|
|
2386
3034
|
await act(async () => commitComposerText(text));
|
|
2387
3035
|
};
|
|
2388
3036
|
const setComposerScope = async (scope) => {
|
|
@@ -2402,7 +3050,7 @@ ${block}
|
|
|
2402
3050
|
rpcCalls,
|
|
2403
3051
|
emitRealtime,
|
|
2404
3052
|
setRealtimeConnectionState,
|
|
2405
|
-
setComposerText,
|
|
3053
|
+
setComposerText: setComposerText2,
|
|
2406
3054
|
setComposerScope,
|
|
2407
3055
|
navigateCalls,
|
|
2408
3056
|
experimental_fixedTabOpenCalls,
|
|
@@ -2413,7 +3061,7 @@ ${block}
|
|
|
2413
3061
|
behavior: {
|
|
2414
3062
|
emitRealtime,
|
|
2415
3063
|
setRealtimeConnectionState,
|
|
2416
|
-
setComposerText,
|
|
3064
|
+
setComposerText: setComposerText2,
|
|
2417
3065
|
setComposerScope
|
|
2418
3066
|
},
|
|
2419
3067
|
inspection: {
|