@get-bb/plugin-sdk 0.5.31 → 0.6.9
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 +434 -253
- package/bundled-types/bb-plugin-sdk-internal-composer-handle.d.ts +457 -0
- package/bundled-types/bb-plugin-sdk-internal-host-policy.d.ts +26 -9
- package/bundled-types/bb-plugin-sdk-provider-bridge-acp.d.ts +2 -2
- package/bundled-types/bb-plugin-sdk-provider-bridge-testing.d.ts +9 -12
- package/bundled-types/bb-plugin-sdk-provider-bridge.d.ts +11 -10
- package/bundled-types/bb-plugin-sdk-testing-app.d.ts +32 -10
- package/bundled-types/bb-plugin-sdk-testing.d.ts +9 -4
- package/bundled-types/bb-plugin-sdk.d.ts +469 -261
- 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/host-policy.js +0 -1
- package/dist/internal/plugin-app-collector.js +42 -27
- package/dist/provider-bridge-acp.js +14 -60
- package/dist/provider-bridge-testing.js +12 -61
- package/dist/provider-bridge.js +12 -55
- package/dist/testing/app.js +801 -139
- package/dist/testing/index.js +6 -3
- package/package.json +8 -2
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"
|
|
@@ -707,7 +1233,12 @@ function collectPluginAppRegistrations(definition, onComposerCustomizationReject
|
|
|
707
1233
|
"description",
|
|
708
1234
|
registration.description
|
|
709
1235
|
);
|
|
1236
|
+
const page = registration.experimental_page;
|
|
1237
|
+
if (page !== void 0 && page !== "mobile") {
|
|
1238
|
+
throw new Error(`${kind}: experimental_page must be "mobile"`);
|
|
1239
|
+
}
|
|
710
1240
|
collected.settingsSections.push({
|
|
1241
|
+
...page !== void 0 ? { experimental_page: page } : {},
|
|
711
1242
|
id,
|
|
712
1243
|
...title !== void 0 ? { title } : {},
|
|
713
1244
|
...description !== void 0 ? { description } : {},
|
|
@@ -1079,6 +1610,34 @@ function collectPluginAppRegistrations(definition, onComposerCustomizationReject
|
|
|
1079
1610
|
|
|
1080
1611
|
// src/testing/app.tsx
|
|
1081
1612
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
1613
|
+
function testComposerKey(scope) {
|
|
1614
|
+
switch (scope.kind) {
|
|
1615
|
+
case "thread":
|
|
1616
|
+
return `test-composer:thread:${scope.threadId}`;
|
|
1617
|
+
case "queued-message":
|
|
1618
|
+
return `test-composer:queued-message:${scope.threadId}:${scope.queuedMessageId}`;
|
|
1619
|
+
case "new-thread":
|
|
1620
|
+
return `test-composer:new-thread:${scope.projectId ?? ""}`;
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
function testComposerMentionText(mention) {
|
|
1624
|
+
switch (mention.kind) {
|
|
1625
|
+
case "thread":
|
|
1626
|
+
return `@thread:${mention.threadId}`;
|
|
1627
|
+
case "project":
|
|
1628
|
+
return `@project:${mention.projectId}`;
|
|
1629
|
+
case "section":
|
|
1630
|
+
return `@section:${mention.sectionId}`;
|
|
1631
|
+
case "command":
|
|
1632
|
+
return `${mention.trigger}${mention.name}`;
|
|
1633
|
+
case "plugin":
|
|
1634
|
+
return `@${mention.label}`;
|
|
1635
|
+
case "path": {
|
|
1636
|
+
const path = mention.source === "thread-storage" ? `thread-storage:${mention.path}` : mention.path;
|
|
1637
|
+
return `@${path}${mention.entryKind === "directory" && !path.endsWith("/") ? "/" : ""}`;
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1082
1641
|
function createSdkFakeNode(provided, path, calls) {
|
|
1083
1642
|
const callable = function sdkFakeNode() {
|
|
1084
1643
|
};
|
|
@@ -1339,7 +1898,16 @@ function TestProviderModelPicker({
|
|
|
1339
1898
|
className
|
|
1340
1899
|
}) {
|
|
1341
1900
|
const [draft, setDraft] = useState(value);
|
|
1342
|
-
|
|
1901
|
+
const { providerId, model, reasoningLevel, serviceTier } = value;
|
|
1902
|
+
useEffect(
|
|
1903
|
+
() => setDraft({
|
|
1904
|
+
providerId,
|
|
1905
|
+
model,
|
|
1906
|
+
reasoningLevel,
|
|
1907
|
+
...serviceTier === void 0 ? {} : { serviceTier }
|
|
1908
|
+
}),
|
|
1909
|
+
[providerId, model, reasoningLevel, serviceTier]
|
|
1910
|
+
);
|
|
1343
1911
|
const reasoningLevels = [
|
|
1344
1912
|
"none",
|
|
1345
1913
|
"low",
|
|
@@ -1387,11 +1955,11 @@ function TestProviderModelPicker({
|
|
|
1387
1955
|
"aria-label": "Reasoning level",
|
|
1388
1956
|
value: draft.reasoningLevel,
|
|
1389
1957
|
onChange: (event) => {
|
|
1390
|
-
const
|
|
1958
|
+
const reasoningLevel2 = reasoningLevels.find(
|
|
1391
1959
|
(candidate) => candidate === event.target.value
|
|
1392
1960
|
);
|
|
1393
|
-
if (
|
|
1394
|
-
setDraft((current) => ({ ...current, reasoningLevel }));
|
|
1961
|
+
if (reasoningLevel2 === void 0) return;
|
|
1962
|
+
setDraft((current) => ({ ...current, reasoningLevel: reasoningLevel2 }));
|
|
1395
1963
|
}
|
|
1396
1964
|
}
|
|
1397
1965
|
),
|
|
@@ -1401,13 +1969,13 @@ function TestProviderModelPicker({
|
|
|
1401
1969
|
"aria-label": "Service tier",
|
|
1402
1970
|
value: draft.serviceTier ?? "",
|
|
1403
1971
|
onChange: (event) => setDraft((current) => {
|
|
1404
|
-
const
|
|
1405
|
-
if (
|
|
1972
|
+
const serviceTier2 = event.target.value;
|
|
1973
|
+
if (serviceTier2 !== "fast" && serviceTier2 !== "default") {
|
|
1406
1974
|
const next = { ...current };
|
|
1407
1975
|
delete next.serviceTier;
|
|
1408
1976
|
return next;
|
|
1409
1977
|
}
|
|
1410
|
-
return { ...current, serviceTier };
|
|
1978
|
+
return { ...current, serviceTier: serviceTier2 };
|
|
1411
1979
|
}),
|
|
1412
1980
|
children: [
|
|
1413
1981
|
/* @__PURE__ */ jsx("option", { value: "", children: "Unsupported" }),
|
|
@@ -1610,19 +2178,21 @@ var testPluginSdkApp = {
|
|
|
1610
2178
|
},
|
|
1611
2179
|
useComposer() {
|
|
1612
2180
|
const composer = useSlotEnv("useComposer").composer;
|
|
1613
|
-
|
|
2181
|
+
useSyncExternalStore(
|
|
1614
2182
|
composer.subscribe,
|
|
1615
2183
|
composer.getVersionSnapshot,
|
|
1616
2184
|
composer.getVersionSnapshot
|
|
1617
2185
|
);
|
|
1618
|
-
return
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
2186
|
+
return composer.api;
|
|
2187
|
+
},
|
|
2188
|
+
useComposers() {
|
|
2189
|
+
const composer = useSlotEnv("useComposers").composer;
|
|
2190
|
+
useSyncExternalStore(
|
|
2191
|
+
composer.subscribe,
|
|
2192
|
+
composer.getVersionSnapshot,
|
|
2193
|
+
composer.getVersionSnapshot
|
|
1625
2194
|
);
|
|
2195
|
+
return composer.apiList;
|
|
1626
2196
|
},
|
|
1627
2197
|
ThreadChat: TestThreadChat,
|
|
1628
2198
|
Markdown: TestMarkdown,
|
|
@@ -1760,13 +2330,13 @@ var testPluginSdkApp = {
|
|
|
1760
2330
|
const attachmentCount = composer.getAttachmentCount();
|
|
1761
2331
|
return {
|
|
1762
2332
|
scope: composer.getScope(),
|
|
1763
|
-
layout:
|
|
2333
|
+
layout: composer.getLayout(),
|
|
1764
2334
|
draft: {
|
|
1765
2335
|
text,
|
|
1766
2336
|
isEmpty: isComposerDraftEmpty(text, attachmentCount),
|
|
1767
2337
|
attachmentCount
|
|
1768
2338
|
},
|
|
1769
|
-
run:
|
|
2339
|
+
run: composer.getRun()
|
|
1770
2340
|
};
|
|
1771
2341
|
}, [composer, version]);
|
|
1772
2342
|
}
|
|
@@ -2171,26 +2741,76 @@ function renderSlot(registration, props, options = {}) {
|
|
|
2171
2741
|
const projectId = options.context?.projectId ?? null;
|
|
2172
2742
|
const threadId = options.context?.threadId ?? null;
|
|
2173
2743
|
let composerScope = options.composer?.scope ?? (threadId !== null ? { kind: "thread", threadId } : { kind: "new-thread", projectId });
|
|
2744
|
+
let composerSelection = composerScope.kind === "queued-message" ? null : {
|
|
2745
|
+
...composerScope.kind === "new-thread" && projectId !== null ? { projectId } : {},
|
|
2746
|
+
...options.composer?.selection
|
|
2747
|
+
};
|
|
2174
2748
|
let composerText = options.composer?.text ?? "";
|
|
2175
|
-
|
|
2749
|
+
let composerMentions = [
|
|
2750
|
+
...options.composer?.mentions ?? []
|
|
2751
|
+
];
|
|
2752
|
+
let composerAttachments = [...options.composer?.attachments ?? []];
|
|
2753
|
+
let composerAttachmentCount = options.composer?.attachmentCount ?? composerAttachments.length;
|
|
2754
|
+
const composerLayout = options.composer?.layout ?? "expanded";
|
|
2755
|
+
const composerIsRunning = options.composer?.isRunning ?? false;
|
|
2756
|
+
const composerIsSubmitting = options.composer?.isSubmitting ?? false;
|
|
2757
|
+
const composerPluginId = options.pluginId ?? "test-plugin";
|
|
2176
2758
|
let composerVersion = 0;
|
|
2177
2759
|
const composerListeners = /* @__PURE__ */ new Set();
|
|
2178
2760
|
const notifyComposerListeners = () => {
|
|
2179
2761
|
composerVersion += 1;
|
|
2180
2762
|
for (const listener of composerListeners) listener();
|
|
2181
2763
|
};
|
|
2764
|
+
const commitComposerDraft = (nextText, nextMentions) => {
|
|
2765
|
+
if (nextText === composerText && nextMentions === composerMentions) return;
|
|
2766
|
+
composerText = nextText;
|
|
2767
|
+
composerMentions = nextMentions;
|
|
2768
|
+
notifyComposerListeners();
|
|
2769
|
+
};
|
|
2182
2770
|
const commitComposerText = (next) => {
|
|
2183
2771
|
if (next === composerText) return;
|
|
2184
|
-
|
|
2185
|
-
|
|
2772
|
+
commitComposerDraft(
|
|
2773
|
+
next,
|
|
2774
|
+
reconcileComposerMentions(composerText, next, composerMentions)
|
|
2775
|
+
);
|
|
2776
|
+
};
|
|
2777
|
+
const composerBlockedReason = () => {
|
|
2778
|
+
if (options.composer?.submittingBlockedReason !== void 0) {
|
|
2779
|
+
return options.composer.submittingBlockedReason;
|
|
2780
|
+
}
|
|
2781
|
+
if (composerIsSubmitting) return "Submitting...";
|
|
2782
|
+
return composerText.trim() === "" && composerAttachmentCount === 0 ? "Type a message first." : null;
|
|
2783
|
+
};
|
|
2784
|
+
let composerDraftCache = null;
|
|
2785
|
+
const composerDraft = () => {
|
|
2786
|
+
if (composerDraftCache?.version !== composerVersion) {
|
|
2787
|
+
composerDraftCache = {
|
|
2788
|
+
version: composerVersion,
|
|
2789
|
+
draft: {
|
|
2790
|
+
text: composerText,
|
|
2791
|
+
mentions: composerMentions,
|
|
2792
|
+
attachments: composerAttachments
|
|
2793
|
+
}
|
|
2794
|
+
};
|
|
2795
|
+
}
|
|
2796
|
+
return composerDraftCache.draft;
|
|
2186
2797
|
};
|
|
2187
2798
|
const composerLog = {
|
|
2188
2799
|
get text() {
|
|
2189
2800
|
return composerText;
|
|
2190
2801
|
},
|
|
2802
|
+
get draft() {
|
|
2803
|
+
return composerHandle.draft;
|
|
2804
|
+
},
|
|
2805
|
+
get key() {
|
|
2806
|
+
return testComposerKey(composerScope);
|
|
2807
|
+
},
|
|
2191
2808
|
get scope() {
|
|
2192
2809
|
return composerScope;
|
|
2193
2810
|
},
|
|
2811
|
+
get selection() {
|
|
2812
|
+
return composerSelection;
|
|
2813
|
+
},
|
|
2194
2814
|
get attachmentCount() {
|
|
2195
2815
|
return composerAttachmentCount;
|
|
2196
2816
|
},
|
|
@@ -2205,26 +2825,88 @@ function renderSlot(registration, props, options = {}) {
|
|
|
2205
2825
|
selections: []
|
|
2206
2826
|
};
|
|
2207
2827
|
const composerOwnership = { active: true };
|
|
2828
|
+
const composerIsAvailable = () => composerOwnership.active || composerScope.kind !== "queued-message";
|
|
2208
2829
|
const submissionListeners = /* @__PURE__ */ new Set();
|
|
2209
|
-
const
|
|
2830
|
+
const composerTarget = {
|
|
2831
|
+
get key() {
|
|
2832
|
+
return testComposerKey(composerScope);
|
|
2833
|
+
},
|
|
2834
|
+
get scope() {
|
|
2835
|
+
return composerScope;
|
|
2836
|
+
},
|
|
2837
|
+
getDraft: composerDraft,
|
|
2210
2838
|
getAttachmentCount: () => composerAttachmentCount,
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2839
|
+
getSelection: () => composerSelection,
|
|
2840
|
+
setDraft: (next) => {
|
|
2841
|
+
if (next.attachments !== void 0) {
|
|
2842
|
+
composerAttachments = [...next.attachments];
|
|
2843
|
+
composerAttachmentCount = composerAttachments.length;
|
|
2844
|
+
}
|
|
2845
|
+
commitComposerDraft(next.text, [...next.mentions]);
|
|
2217
2846
|
},
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
}
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2847
|
+
addQuote(text) {
|
|
2848
|
+
const trimmed = text.replace(/\r\n|\r/gu, "\n").trim();
|
|
2849
|
+
if (trimmed === "") return;
|
|
2850
|
+
const block = trimmed.split("\n").map((line) => line.length > 0 ? `> ${line}` : ">").join("\n");
|
|
2851
|
+
commitComposerText(
|
|
2852
|
+
composerText === "" ? `${block}
|
|
2853
|
+
` : `${composerText}
|
|
2854
|
+
${block}
|
|
2855
|
+
`
|
|
2856
|
+
);
|
|
2857
|
+
composerLog.quotes.push(text);
|
|
2858
|
+
},
|
|
2859
|
+
getEditorState: () => {
|
|
2860
|
+
const reason = composerBlockedReason();
|
|
2861
|
+
return {
|
|
2862
|
+
layout: composerLayout,
|
|
2863
|
+
isRunning: composerIsRunning,
|
|
2864
|
+
isSubmitting: composerIsSubmitting,
|
|
2865
|
+
isSubmittingBlocked: reason !== null,
|
|
2866
|
+
submittingBlockedReason: reason,
|
|
2867
|
+
isAttaching: false,
|
|
2868
|
+
attachmentError: null
|
|
2869
|
+
};
|
|
2870
|
+
},
|
|
2871
|
+
subscribeEditorState: () => () => {
|
|
2872
|
+
},
|
|
2873
|
+
insertAtCursor(value, block) {
|
|
2874
|
+
composerTarget.setDraft(
|
|
2875
|
+
appendComposerDraft(composerDraft(), value, block)
|
|
2876
|
+
);
|
|
2877
|
+
return true;
|
|
2878
|
+
},
|
|
2879
|
+
isAvailable: composerIsAvailable,
|
|
2880
|
+
focus() {
|
|
2881
|
+
composerLog.focusCount += 1;
|
|
2882
|
+
},
|
|
2883
|
+
async submit(submitOptions) {
|
|
2884
|
+
composerLog.submits.push(submitOptions);
|
|
2885
|
+
commitComposerDraft("", []);
|
|
2886
|
+
for (const listener of submissionListeners) listener();
|
|
2887
|
+
},
|
|
2888
|
+
async setSelection(selection) {
|
|
2889
|
+
if (composerScope.kind === "queued-message") {
|
|
2890
|
+
throw new Error("This composer has no pickers to set.");
|
|
2891
|
+
}
|
|
2892
|
+
const {
|
|
2893
|
+
projectId: _projectId,
|
|
2894
|
+
environment: _environment,
|
|
2895
|
+
...rest
|
|
2896
|
+
} = selection;
|
|
2897
|
+
const accepted = composerScope.kind === "thread" ? rest : { ...selection };
|
|
2898
|
+
composerLog.selections.push(accepted);
|
|
2899
|
+
composerSelection = { ...composerSelection, ...accepted };
|
|
2900
|
+
notifyComposerListeners();
|
|
2901
|
+
return composerSelection;
|
|
2902
|
+
}
|
|
2903
|
+
};
|
|
2904
|
+
const composerHandle = createComposerHandleBinding(
|
|
2905
|
+
testComposerKey(composerScope),
|
|
2906
|
+
{
|
|
2907
|
+
pluginId: composerPluginId,
|
|
2908
|
+
target: composerTarget,
|
|
2909
|
+
mentionText: testComposerMentionText,
|
|
2228
2910
|
setTextEffect(effect) {
|
|
2229
2911
|
if (!composerOwnership.active) return;
|
|
2230
2912
|
composerLog.textEffect = effect;
|
|
@@ -2235,76 +2917,56 @@ function renderSlot(registration, props, options = {}) {
|
|
|
2235
2917
|
composerLog.inputLocked = locked;
|
|
2236
2918
|
composerLog.inputLockCalls.push(locked);
|
|
2237
2919
|
},
|
|
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) {
|
|
2920
|
+
onSubmitted(listener) {
|
|
2253
2921
|
submissionListeners.add(listener);
|
|
2254
2922
|
return () => {
|
|
2255
2923
|
submissionListeners.delete(listener);
|
|
2256
2924
|
};
|
|
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
2925
|
}
|
|
2307
2926
|
}
|
|
2927
|
+
).handle;
|
|
2928
|
+
const forgetLoggedMention = ({
|
|
2929
|
+
provider,
|
|
2930
|
+
id
|
|
2931
|
+
}) => {
|
|
2932
|
+
for (let index = composerLog.mentions.length - 1; index >= 0; index -= 1) {
|
|
2933
|
+
const mention = composerLog.mentions[index];
|
|
2934
|
+
if (mention?.provider === provider && mention.id === id) {
|
|
2935
|
+
composerLog.mentions.splice(index, 1);
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
};
|
|
2939
|
+
const { insertMention, removeMention, experimental_removeMention } = composerHandle;
|
|
2940
|
+
Object.assign(composerHandle, {
|
|
2941
|
+
insertMention(mention) {
|
|
2942
|
+
insertMention(mention);
|
|
2943
|
+
composerLog.mentions.push(mention);
|
|
2944
|
+
},
|
|
2945
|
+
removeMention(mention) {
|
|
2946
|
+
removeMention(mention);
|
|
2947
|
+
forgetLoggedMention(mention);
|
|
2948
|
+
},
|
|
2949
|
+
experimental_removeMention(mention) {
|
|
2950
|
+
experimental_removeMention(mention);
|
|
2951
|
+
forgetLoggedMention(mention);
|
|
2952
|
+
}
|
|
2953
|
+
});
|
|
2954
|
+
const composer = {
|
|
2955
|
+
api: composerHandle,
|
|
2956
|
+
apiList: [composerHandle],
|
|
2957
|
+
getAttachmentCount: () => composerAttachmentCount,
|
|
2958
|
+
getLayout: () => composerLayout,
|
|
2959
|
+
getRun: () => ({
|
|
2960
|
+
isRunning: composerIsRunning,
|
|
2961
|
+
isSubmitting: composerIsSubmitting
|
|
2962
|
+
}),
|
|
2963
|
+
getScope: () => composerScope,
|
|
2964
|
+
getText: () => composerText,
|
|
2965
|
+
getVersionSnapshot: () => composerVersion,
|
|
2966
|
+
subscribe(listener) {
|
|
2967
|
+
composerListeners.add(listener);
|
|
2968
|
+
return () => composerListeners.delete(listener);
|
|
2969
|
+
}
|
|
2308
2970
|
};
|
|
2309
2971
|
const env = {
|
|
2310
2972
|
rpcClient,
|
|
@@ -2382,7 +3044,7 @@ ${block}
|
|
|
2382
3044
|
const setRealtimeConnectionState = async (state) => {
|
|
2383
3045
|
await act(async () => realtimeConnection.setState(state));
|
|
2384
3046
|
};
|
|
2385
|
-
const
|
|
3047
|
+
const setComposerText2 = async (text) => {
|
|
2386
3048
|
await act(async () => commitComposerText(text));
|
|
2387
3049
|
};
|
|
2388
3050
|
const setComposerScope = async (scope) => {
|
|
@@ -2402,7 +3064,7 @@ ${block}
|
|
|
2402
3064
|
rpcCalls,
|
|
2403
3065
|
emitRealtime,
|
|
2404
3066
|
setRealtimeConnectionState,
|
|
2405
|
-
setComposerText,
|
|
3067
|
+
setComposerText: setComposerText2,
|
|
2406
3068
|
setComposerScope,
|
|
2407
3069
|
navigateCalls,
|
|
2408
3070
|
experimental_fixedTabOpenCalls,
|
|
@@ -2413,7 +3075,7 @@ ${block}
|
|
|
2413
3075
|
behavior: {
|
|
2414
3076
|
emitRealtime,
|
|
2415
3077
|
setRealtimeConnectionState,
|
|
2416
|
-
setComposerText,
|
|
3078
|
+
setComposerText: setComposerText2,
|
|
2417
3079
|
setComposerScope
|
|
2418
3080
|
},
|
|
2419
3081
|
inspection: {
|