@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/app.js
CHANGED
|
@@ -90,6 +90,7 @@ var experimental_useFixedTabTarget = runtimeFunction(
|
|
|
90
90
|
"experimental_useFixedTabTarget"
|
|
91
91
|
);
|
|
92
92
|
var useComposer = runtimeFunction("useComposer");
|
|
93
|
+
var useComposers = runtimeFunction("useComposers");
|
|
93
94
|
var useComposerView = runtimeFunction("useComposerView");
|
|
94
95
|
var experimental_useSidebarThreads = runtimeFunction(
|
|
95
96
|
"experimental_useSidebarThreads"
|
|
@@ -171,6 +172,7 @@ export {
|
|
|
171
172
|
useBbNavigate,
|
|
172
173
|
useComposer,
|
|
173
174
|
useComposerView,
|
|
175
|
+
useComposers,
|
|
174
176
|
useEnvironmentProviders,
|
|
175
177
|
useRealtime,
|
|
176
178
|
useRealtimeConnectionState,
|
|
@@ -123,6 +123,30 @@ function parseContributionArray(kind, value, onRejected, parse) {
|
|
|
123
123
|
}
|
|
124
124
|
return parsed;
|
|
125
125
|
}
|
|
126
|
+
function parseMenuItem(entryKind, value) {
|
|
127
|
+
const entry = value;
|
|
128
|
+
const id = requireSlotId(entryKind, entry?.id);
|
|
129
|
+
const icon = requireOptionalString(entryKind, "icon", entry?.icon);
|
|
130
|
+
const description = requireOptionalString(
|
|
131
|
+
entryKind,
|
|
132
|
+
"description",
|
|
133
|
+
entry?.description
|
|
134
|
+
);
|
|
135
|
+
const disabled = entry?.disabled;
|
|
136
|
+
if (disabled !== void 0 && typeof disabled !== "boolean" && typeof disabled !== "function") {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`${entryKind}: "disabled" must be a boolean or function when set`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
id,
|
|
143
|
+
label: requireNonEmptyString(entryKind, "label", entry?.label),
|
|
144
|
+
...icon !== void 0 ? { icon } : {},
|
|
145
|
+
...description !== void 0 ? { description } : {},
|
|
146
|
+
...disabled !== void 0 ? { disabled } : {},
|
|
147
|
+
run: requireFunction(entryKind, "run", entry?.run)
|
|
148
|
+
};
|
|
149
|
+
}
|
|
126
150
|
function parseRegions(kind, registration, onRejected) {
|
|
127
151
|
const actions = parseContributionArray(`${kind}.actions`, registration.actions, onRejected, (entryKind, value) => {
|
|
128
152
|
const entry = value;
|
|
@@ -150,32 +174,13 @@ function parseRegions(kind, registration, onRejected) {
|
|
|
150
174
|
`${kind}.plusMenu`,
|
|
151
175
|
registration.plusMenu,
|
|
152
176
|
onRejected,
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
entry?.description
|
|
161
|
-
);
|
|
162
|
-
const disabled = entry?.disabled;
|
|
163
|
-
if (disabled !== void 0 && typeof disabled !== "boolean" && typeof disabled !== "function") {
|
|
164
|
-
throw new Error(
|
|
165
|
-
`${entryKind}: "disabled" must be a boolean or function when set`
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
return {
|
|
169
|
-
id,
|
|
170
|
-
label: requireNonEmptyString(entryKind, "label", entry?.label),
|
|
171
|
-
...icon !== void 0 ? { icon } : {},
|
|
172
|
-
...description !== void 0 ? { description } : {},
|
|
173
|
-
...disabled !== void 0 ? {
|
|
174
|
-
disabled
|
|
175
|
-
} : {},
|
|
176
|
-
run: requireFunction(entryKind, "run", entry?.run)
|
|
177
|
-
};
|
|
178
|
-
}
|
|
177
|
+
parseMenuItem
|
|
178
|
+
);
|
|
179
|
+
const sendMenu = parseContributionArray(
|
|
180
|
+
`${kind}.sendMenu`,
|
|
181
|
+
registration.sendMenu,
|
|
182
|
+
onRejected,
|
|
183
|
+
parseMenuItem
|
|
179
184
|
);
|
|
180
185
|
let richText;
|
|
181
186
|
if (registration.richText !== void 0) {
|
|
@@ -218,6 +223,7 @@ function parseRegions(kind, registration, onRejected) {
|
|
|
218
223
|
...actions !== void 0 ? { actions } : {},
|
|
219
224
|
...banners !== void 0 ? { banners } : {},
|
|
220
225
|
...plusMenu !== void 0 ? { plusMenu } : {},
|
|
226
|
+
...sendMenu !== void 0 ? { sendMenu } : {},
|
|
221
227
|
...richText !== void 0 ? { richText } : {}
|
|
222
228
|
};
|
|
223
229
|
}
|
|
@@ -242,7 +248,11 @@ function collectComposerCustomization(registration, seenIds, onRejected) {
|
|
|
242
248
|
requireUniqueId(kind, seenIds, id);
|
|
243
249
|
return {
|
|
244
250
|
id,
|
|
245
|
-
...scopes !== void 0 ? {
|
|
251
|
+
...scopes !== void 0 ? {
|
|
252
|
+
scopes: scopes.filter(
|
|
253
|
+
(scope) => scope !== "side-chat"
|
|
254
|
+
)
|
|
255
|
+
} : {},
|
|
246
256
|
...parseRegions(`${kind}(${id})`, raw ?? {}, onRejected)
|
|
247
257
|
};
|
|
248
258
|
} catch (error) {
|
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
// src/internal/composer-draft-transforms.ts
|
|
2
|
+
function removeComposerMentions(draft, matches) {
|
|
3
|
+
const removed = draft.mentions.filter(matches).sort((a, b) => a.from - b.from);
|
|
4
|
+
if (removed.length === 0) return draft;
|
|
5
|
+
let text = "";
|
|
6
|
+
let cursor = 0;
|
|
7
|
+
for (const mention of removed) {
|
|
8
|
+
text += draft.text.slice(cursor, mention.from);
|
|
9
|
+
cursor = mention.to;
|
|
10
|
+
}
|
|
11
|
+
const removedSet = new Set(removed);
|
|
12
|
+
const mentions = draft.mentions.filter((mention) => !removedSet.has(mention)).map((mention) => {
|
|
13
|
+
const offset = removed.reduce(
|
|
14
|
+
(sum, item) => sum + (item.to <= mention.from ? item.to - item.from : 0),
|
|
15
|
+
0
|
|
16
|
+
);
|
|
17
|
+
return offset === 0 ? mention : { ...mention, from: mention.from - offset, to: mention.to - offset };
|
|
18
|
+
});
|
|
19
|
+
return { ...draft, text: text + draft.text.slice(cursor), mentions };
|
|
20
|
+
}
|
|
21
|
+
function setComposerText(draft, text) {
|
|
22
|
+
if (text === draft.text) return draft;
|
|
23
|
+
return {
|
|
24
|
+
...draft,
|
|
25
|
+
text,
|
|
26
|
+
mentions: reconcileComposerMentions(draft.text, text, draft.mentions)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function reconcileComposerMentions(currentText, nextText, mentions) {
|
|
30
|
+
if (currentText === nextText) return [...mentions];
|
|
31
|
+
let unchangedPrefixLength = 0;
|
|
32
|
+
const maximumPrefixLength = Math.min(currentText.length, nextText.length);
|
|
33
|
+
while (unchangedPrefixLength < maximumPrefixLength && currentText[unchangedPrefixLength] === nextText[unchangedPrefixLength]) {
|
|
34
|
+
unchangedPrefixLength += 1;
|
|
35
|
+
}
|
|
36
|
+
let unchangedSuffixLength = 0;
|
|
37
|
+
while (unchangedSuffixLength < currentText.length - unchangedPrefixLength && unchangedSuffixLength < nextText.length - unchangedPrefixLength && currentText[currentText.length - unchangedSuffixLength - 1] === nextText[nextText.length - unchangedSuffixLength - 1]) {
|
|
38
|
+
unchangedSuffixLength += 1;
|
|
39
|
+
}
|
|
40
|
+
const replacedCurrentEnd = currentText.length - unchangedSuffixLength;
|
|
41
|
+
const replacementDelta = nextText.length - currentText.length;
|
|
42
|
+
return mentions.flatMap((mention) => {
|
|
43
|
+
if (mention.to <= unchangedPrefixLength) return [mention];
|
|
44
|
+
if (mention.from >= replacedCurrentEnd) {
|
|
45
|
+
return [
|
|
46
|
+
{
|
|
47
|
+
...mention,
|
|
48
|
+
from: mention.from + replacementDelta,
|
|
49
|
+
to: mention.to + replacementDelta
|
|
50
|
+
}
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
return [];
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/internal/composer-draft-actions.ts
|
|
58
|
+
import { z } from "zod";
|
|
59
|
+
var range = {
|
|
60
|
+
from: z.number().int().nonnegative(),
|
|
61
|
+
to: z.number().int().nonnegative(),
|
|
62
|
+
label: z.string()
|
|
63
|
+
};
|
|
64
|
+
var mentionSchema = z.discriminatedUnion("kind", [
|
|
65
|
+
z.object({
|
|
66
|
+
...range,
|
|
67
|
+
kind: z.literal("thread"),
|
|
68
|
+
threadId: z.string(),
|
|
69
|
+
projectId: z.string().optional()
|
|
70
|
+
}),
|
|
71
|
+
z.object({ ...range, kind: z.literal("project"), projectId: z.string() }),
|
|
72
|
+
z.object({ ...range, kind: z.literal("section"), sectionId: z.string() }),
|
|
73
|
+
z.object({
|
|
74
|
+
...range,
|
|
75
|
+
kind: z.literal("path"),
|
|
76
|
+
path: z.string(),
|
|
77
|
+
source: z.enum(["workspace", "thread-storage"]),
|
|
78
|
+
entryKind: z.enum(["file", "directory"])
|
|
79
|
+
}),
|
|
80
|
+
z.object({
|
|
81
|
+
...range,
|
|
82
|
+
kind: z.literal("command"),
|
|
83
|
+
trigger: z.enum(["/", "$"]),
|
|
84
|
+
name: z.string(),
|
|
85
|
+
source: z.enum(["skill", "command"]),
|
|
86
|
+
origin: z.enum(["builtin", "project", "user"]),
|
|
87
|
+
argumentHint: z.string().nullable()
|
|
88
|
+
}),
|
|
89
|
+
z.object({
|
|
90
|
+
...range,
|
|
91
|
+
kind: z.literal("plugin"),
|
|
92
|
+
pluginId: z.string(),
|
|
93
|
+
provider: z.string().min(1).refine((value) => !value.includes(":")),
|
|
94
|
+
id: z.string(),
|
|
95
|
+
icon: z.string().nullable().optional()
|
|
96
|
+
})
|
|
97
|
+
]);
|
|
98
|
+
var attachmentsSchema = z.array(
|
|
99
|
+
z.object({
|
|
100
|
+
type: z.enum(["localImage", "localFile"]),
|
|
101
|
+
path: z.string().min(1),
|
|
102
|
+
name: z.string(),
|
|
103
|
+
mimeType: z.string().optional(),
|
|
104
|
+
sizeBytes: z.number().nonnegative()
|
|
105
|
+
})
|
|
106
|
+
);
|
|
107
|
+
var replacementSchema = z.object({
|
|
108
|
+
text: z.string(),
|
|
109
|
+
mentions: z.array(mentionSchema),
|
|
110
|
+
attachments: attachmentsSchema.optional()
|
|
111
|
+
}).superRefine((draft, context) => {
|
|
112
|
+
let end = 0;
|
|
113
|
+
for (const mention of [...draft.mentions].sort((a, b) => a.from - b.from)) {
|
|
114
|
+
if (mention.from < end || mention.to <= mention.from || mention.to > draft.text.length) {
|
|
115
|
+
context.addIssue({
|
|
116
|
+
code: "custom",
|
|
117
|
+
message: "Mention ranges must be non-overlapping and inside the draft text."
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
end = mention.to;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
function createComposerDraftActions(target) {
|
|
124
|
+
let cached = null;
|
|
125
|
+
const snapshotOf = (current) => {
|
|
126
|
+
const content = current.getDraft();
|
|
127
|
+
if (cached?.content === content) return cached.snapshot;
|
|
128
|
+
const snapshot = Object.freeze({
|
|
129
|
+
text: content.text,
|
|
130
|
+
mentions: Object.freeze(
|
|
131
|
+
content.mentions.map((mention) => Object.freeze({ ...mention }))
|
|
132
|
+
),
|
|
133
|
+
attachments: Object.freeze(
|
|
134
|
+
content.attachments.map(
|
|
135
|
+
(attachment) => Object.freeze({ ...attachment })
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
});
|
|
139
|
+
cached = { content, snapshot };
|
|
140
|
+
return snapshot;
|
|
141
|
+
};
|
|
142
|
+
const requireAvailable = (current) => {
|
|
143
|
+
if (!current.isAvailable())
|
|
144
|
+
throw new Error("This composer is no longer available.");
|
|
145
|
+
};
|
|
146
|
+
return {
|
|
147
|
+
get draft() {
|
|
148
|
+
return snapshotOf(target());
|
|
149
|
+
},
|
|
150
|
+
replace(next) {
|
|
151
|
+
const current = target();
|
|
152
|
+
requireAvailable(current);
|
|
153
|
+
const snapshot = snapshotOf(current);
|
|
154
|
+
const result = typeof next === "function" ? next(snapshot) : next;
|
|
155
|
+
requireAvailable(current);
|
|
156
|
+
if (result === snapshot) return;
|
|
157
|
+
const parsed = replacementSchema.safeParse(result);
|
|
158
|
+
if (!parsed.success)
|
|
159
|
+
throw new Error(
|
|
160
|
+
"Invalid composer draft: text, mention ranges, or attachments are invalid."
|
|
161
|
+
);
|
|
162
|
+
current.setDraft(parsed.data);
|
|
163
|
+
},
|
|
164
|
+
focus() {
|
|
165
|
+
const current = target();
|
|
166
|
+
requireAvailable(current);
|
|
167
|
+
current.focus();
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/internal/composer-handle.ts
|
|
173
|
+
var UNAVAILABLE_MESSAGE = "This composer is no longer available.";
|
|
174
|
+
var OFF_SCREEN_MESSAGE = "This composer isn't on screen.";
|
|
175
|
+
var OFF_SCREEN_EDITOR_STATE = {
|
|
176
|
+
layout: "expanded",
|
|
177
|
+
isRunning: false,
|
|
178
|
+
isSubmitting: false,
|
|
179
|
+
isSubmittingBlocked: true,
|
|
180
|
+
submittingBlockedReason: OFF_SCREEN_MESSAGE,
|
|
181
|
+
isAttaching: false,
|
|
182
|
+
attachmentError: null
|
|
183
|
+
};
|
|
184
|
+
var warnedAliases = /* @__PURE__ */ new Set();
|
|
185
|
+
function warnDeprecatedComposerMember(oldName, newName) {
|
|
186
|
+
if (warnedAliases.has(oldName)) return;
|
|
187
|
+
warnedAliases.add(oldName);
|
|
188
|
+
console.warn(
|
|
189
|
+
`useComposer().${oldName} is deprecated; use useComposer().${newName}.`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
function appendComposerDraft(current, value, block) {
|
|
193
|
+
const base = block ? current.text.replace(/\s+$/u, "") : current.text;
|
|
194
|
+
const separator = block && base.length > 0 ? "\n\n" : "";
|
|
195
|
+
const offset = base.length + separator.length;
|
|
196
|
+
return {
|
|
197
|
+
text: `${base}${separator}${value.text}`,
|
|
198
|
+
mentions: [
|
|
199
|
+
...current.mentions.filter((mention) => mention.to <= base.length),
|
|
200
|
+
...value.mentions.map((mention) => ({
|
|
201
|
+
...mention,
|
|
202
|
+
from: mention.from + offset,
|
|
203
|
+
to: mention.to + offset
|
|
204
|
+
}))
|
|
205
|
+
]
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function validProviderId(provider) {
|
|
209
|
+
const trimmed = provider.trim();
|
|
210
|
+
return trimmed.length === 0 || trimmed.includes(":") ? null : trimmed;
|
|
211
|
+
}
|
|
212
|
+
function insertValue(parts, controller) {
|
|
213
|
+
const list = Array.isArray(parts) ? parts : [parts];
|
|
214
|
+
let text = "";
|
|
215
|
+
const mentions = [];
|
|
216
|
+
for (const part of list) {
|
|
217
|
+
if (typeof part === "string") {
|
|
218
|
+
text += part;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
let mention;
|
|
222
|
+
if ("kind" in part) {
|
|
223
|
+
mention = part;
|
|
224
|
+
} else {
|
|
225
|
+
const provider = validProviderId(part.provider);
|
|
226
|
+
if (provider === null) {
|
|
227
|
+
throw new Error(`Invalid mention provider id "${part.provider}".`);
|
|
228
|
+
}
|
|
229
|
+
mention = {
|
|
230
|
+
from: 0,
|
|
231
|
+
to: 0,
|
|
232
|
+
label: part.label.trim() || part.id,
|
|
233
|
+
kind: "plugin",
|
|
234
|
+
pluginId: controller.pluginId,
|
|
235
|
+
provider,
|
|
236
|
+
id: part.id,
|
|
237
|
+
icon: null
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
const serialized = controller.mentionText(mention);
|
|
241
|
+
mentions.push({
|
|
242
|
+
...mention,
|
|
243
|
+
from: text.length,
|
|
244
|
+
to: text.length + serialized.length
|
|
245
|
+
});
|
|
246
|
+
text += serialized;
|
|
247
|
+
}
|
|
248
|
+
return { text, mentions };
|
|
249
|
+
}
|
|
250
|
+
function isDraftEmpty(draft, attachmentCount) {
|
|
251
|
+
return draft.text.trim().length === 0 && draft.mentions.length === 0 && attachmentCount === 0;
|
|
252
|
+
}
|
|
253
|
+
var legacyDrafts = /* @__PURE__ */ new WeakMap();
|
|
254
|
+
function withLegacyDraftFields(draft, attachmentCount) {
|
|
255
|
+
const cached = legacyDrafts.get(draft);
|
|
256
|
+
if (cached) return cached;
|
|
257
|
+
const snapshot = { ...draft };
|
|
258
|
+
Object.defineProperties(snapshot, {
|
|
259
|
+
isEmpty: { value: isDraftEmpty(draft, attachmentCount), enumerable: false },
|
|
260
|
+
attachmentCount: { value: attachmentCount, enumerable: false }
|
|
261
|
+
});
|
|
262
|
+
Object.freeze(snapshot);
|
|
263
|
+
legacyDrafts.set(draft, snapshot);
|
|
264
|
+
return snapshot;
|
|
265
|
+
}
|
|
266
|
+
function waitForUploads(target) {
|
|
267
|
+
return new Promise((resolve) => {
|
|
268
|
+
if (!target.getEditorState().isAttaching) {
|
|
269
|
+
resolve();
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const unsubscribe = target.subscribeEditorState(() => {
|
|
273
|
+
if (target.getEditorState().isAttaching) return;
|
|
274
|
+
unsubscribe();
|
|
275
|
+
resolve();
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
function createComposerHandleBinding(key, initial) {
|
|
280
|
+
let controller = initial;
|
|
281
|
+
const target = () => controller.target;
|
|
282
|
+
const editorState = () => target().getEditorState();
|
|
283
|
+
const requireAvailable = () => {
|
|
284
|
+
if (!target().isAvailable()) throw new Error(UNAVAILABLE_MESSAGE);
|
|
285
|
+
};
|
|
286
|
+
const legacyAvailable = (method) => {
|
|
287
|
+
if (target().isAvailable()) return true;
|
|
288
|
+
console.warn(
|
|
289
|
+
`[plugin:${controller.pluginId}] useComposer().${method}: ${UNAVAILABLE_MESSAGE}`
|
|
290
|
+
);
|
|
291
|
+
return false;
|
|
292
|
+
};
|
|
293
|
+
const replaceText = (nextText) => {
|
|
294
|
+
const current = target().getDraft();
|
|
295
|
+
if (nextText === current.text) return;
|
|
296
|
+
target().setDraft(setComposerText(current, nextText));
|
|
297
|
+
};
|
|
298
|
+
const insertMention = (mention) => {
|
|
299
|
+
warnDeprecatedComposerMember("insertMention", "insert");
|
|
300
|
+
const provider = validProviderId(mention.provider);
|
|
301
|
+
if (provider === null) {
|
|
302
|
+
console.warn(
|
|
303
|
+
`[plugin:${controller.pluginId}] useComposer().insertMention: invalid provider id "${mention.provider}"`
|
|
304
|
+
);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (!legacyAvailable("insertMention")) return;
|
|
308
|
+
const label = mention.label.trim() || mention.id;
|
|
309
|
+
const current = target().getDraft();
|
|
310
|
+
const separator = current.text.length === 0 || /\s$/u.test(current.text) ? "" : " ";
|
|
311
|
+
const from = current.text.length + separator.length;
|
|
312
|
+
target().setDraft({
|
|
313
|
+
text: `${current.text}${separator}${label} `,
|
|
314
|
+
mentions: [
|
|
315
|
+
...current.mentions,
|
|
316
|
+
{
|
|
317
|
+
from,
|
|
318
|
+
to: from + label.length,
|
|
319
|
+
label,
|
|
320
|
+
kind: "plugin",
|
|
321
|
+
pluginId: controller.pluginId,
|
|
322
|
+
provider,
|
|
323
|
+
id: mention.id,
|
|
324
|
+
icon: null
|
|
325
|
+
}
|
|
326
|
+
]
|
|
327
|
+
});
|
|
328
|
+
target().focus();
|
|
329
|
+
};
|
|
330
|
+
const removeMentionFrom = (mention) => {
|
|
331
|
+
const current = target().getDraft();
|
|
332
|
+
const next = removeComposerMentions(
|
|
333
|
+
current,
|
|
334
|
+
(item) => item.kind === "plugin" && item.pluginId === controller.pluginId && item.provider === mention.provider && item.id === mention.id
|
|
335
|
+
);
|
|
336
|
+
if (next !== current) target().setDraft(next);
|
|
337
|
+
};
|
|
338
|
+
const removeMention = (mention) => {
|
|
339
|
+
warnDeprecatedComposerMember("removeMention", "replace");
|
|
340
|
+
requireAvailable();
|
|
341
|
+
removeMentionFrom(mention);
|
|
342
|
+
};
|
|
343
|
+
const insert = (parts, options = {}) => {
|
|
344
|
+
requireAvailable();
|
|
345
|
+
const value = insertValue(parts, controller);
|
|
346
|
+
const block = options.block === true;
|
|
347
|
+
if ((options.at ?? "cursor") === "cursor") {
|
|
348
|
+
if (!target().insertAtCursor(value, block)) {
|
|
349
|
+
throw new Error(OFF_SCREEN_MESSAGE);
|
|
350
|
+
}
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
target().setDraft(appendComposerDraft(target().getDraft(), value, block));
|
|
354
|
+
};
|
|
355
|
+
const submit = async (options) => {
|
|
356
|
+
requireAvailable();
|
|
357
|
+
const hostSubmit = target().submit;
|
|
358
|
+
if (hostSubmit === void 0) {
|
|
359
|
+
throw new Error("This composer cannot submit programmatically.");
|
|
360
|
+
}
|
|
361
|
+
if (options.sendAt !== void 0 && (!Number.isFinite(options.sendAt) || options.sendAt <= Date.now())) {
|
|
362
|
+
throw new Error("Pick a time in the future.");
|
|
363
|
+
}
|
|
364
|
+
const errorBefore = editorState().attachmentError;
|
|
365
|
+
await waitForUploads(target());
|
|
366
|
+
const state = editorState();
|
|
367
|
+
if (state.attachmentError !== null && state.attachmentError !== errorBefore) {
|
|
368
|
+
throw new Error(state.attachmentError);
|
|
369
|
+
}
|
|
370
|
+
if (state.isSubmittingBlocked) {
|
|
371
|
+
throw new Error(
|
|
372
|
+
state.submittingBlockedReason ?? "This composer can't submit right now."
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
await hostSubmit(
|
|
376
|
+
options,
|
|
377
|
+
options.experimental_data === void 0 ? void 0 : { pluginId: controller.pluginId, data: options.experimental_data }
|
|
378
|
+
);
|
|
379
|
+
};
|
|
380
|
+
const setSelection = async (selection) => {
|
|
381
|
+
requireAvailable();
|
|
382
|
+
const hostSetSelection = target().setSelection;
|
|
383
|
+
if (hostSetSelection === void 0) {
|
|
384
|
+
throw new Error("This composer has no pickers to set.");
|
|
385
|
+
}
|
|
386
|
+
return hostSetSelection(selection);
|
|
387
|
+
};
|
|
388
|
+
const submittedListeners = /* @__PURE__ */ new Set();
|
|
389
|
+
let submittedSubscription = null;
|
|
390
|
+
const syncSubmittedSubscription = () => {
|
|
391
|
+
const scope = submittedListeners.size === 0 ? null : JSON.stringify(target().scope);
|
|
392
|
+
if (submittedSubscription?.scope === scope) return;
|
|
393
|
+
submittedSubscription?.unsubscribe();
|
|
394
|
+
submittedSubscription = scope === null ? null : {
|
|
395
|
+
scope,
|
|
396
|
+
unsubscribe: controller.onSubmitted(() => {
|
|
397
|
+
for (const listener of [...submittedListeners]) {
|
|
398
|
+
try {
|
|
399
|
+
listener();
|
|
400
|
+
} catch (error) {
|
|
401
|
+
console.error("Composer submission listener failed", error);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
})
|
|
405
|
+
};
|
|
406
|
+
};
|
|
407
|
+
const onSubmitted = (listener) => {
|
|
408
|
+
const entry = () => listener();
|
|
409
|
+
submittedListeners.add(entry);
|
|
410
|
+
syncSubmittedSubscription();
|
|
411
|
+
return () => {
|
|
412
|
+
submittedListeners.delete(entry);
|
|
413
|
+
syncSubmittedSubscription();
|
|
414
|
+
};
|
|
415
|
+
};
|
|
416
|
+
const draftActions = createComposerDraftActions(target);
|
|
417
|
+
const handle = {
|
|
418
|
+
replace: draftActions.replace,
|
|
419
|
+
get scope() {
|
|
420
|
+
return target().scope;
|
|
421
|
+
},
|
|
422
|
+
get key() {
|
|
423
|
+
return target().key;
|
|
424
|
+
},
|
|
425
|
+
get layout() {
|
|
426
|
+
return editorState().layout;
|
|
427
|
+
},
|
|
428
|
+
get isRunning() {
|
|
429
|
+
return editorState().isRunning;
|
|
430
|
+
},
|
|
431
|
+
get isSubmitting() {
|
|
432
|
+
return editorState().isSubmitting;
|
|
433
|
+
},
|
|
434
|
+
get isSubmittingBlocked() {
|
|
435
|
+
return editorState().isSubmittingBlocked;
|
|
436
|
+
},
|
|
437
|
+
get submittingBlockedReason() {
|
|
438
|
+
return editorState().submittingBlockedReason;
|
|
439
|
+
},
|
|
440
|
+
get isEmpty() {
|
|
441
|
+
return isDraftEmpty(target().getDraft(), target().getAttachmentCount());
|
|
442
|
+
},
|
|
443
|
+
get attachmentCount() {
|
|
444
|
+
return target().getAttachmentCount();
|
|
445
|
+
},
|
|
446
|
+
get text() {
|
|
447
|
+
return target().getDraft().text;
|
|
448
|
+
},
|
|
449
|
+
get draft() {
|
|
450
|
+
return withLegacyDraftFields(
|
|
451
|
+
draftActions.draft,
|
|
452
|
+
target().getAttachmentCount()
|
|
453
|
+
);
|
|
454
|
+
},
|
|
455
|
+
get selection() {
|
|
456
|
+
return target().getSelection();
|
|
457
|
+
},
|
|
458
|
+
setText: (next) => {
|
|
459
|
+
warnDeprecatedComposerMember("setText", "replace");
|
|
460
|
+
if (legacyAvailable("setText")) replaceText(next);
|
|
461
|
+
},
|
|
462
|
+
updateText: (updater) => {
|
|
463
|
+
warnDeprecatedComposerMember("updateText", "replace");
|
|
464
|
+
if (legacyAvailable("updateText")) {
|
|
465
|
+
replaceText(updater(target().getDraft().text));
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
clear: () => {
|
|
469
|
+
warnDeprecatedComposerMember("clear", "replace");
|
|
470
|
+
if (legacyAvailable("clear")) replaceText("");
|
|
471
|
+
},
|
|
472
|
+
insert,
|
|
473
|
+
setTextEffect: (effect) => controller.setTextEffect(effect),
|
|
474
|
+
setInputLock: (locked) => controller.setInputLock(locked),
|
|
475
|
+
addQuote: (text) => {
|
|
476
|
+
warnDeprecatedComposerMember("addQuote", "replace");
|
|
477
|
+
if (!legacyAvailable("addQuote")) return;
|
|
478
|
+
target().addQuote(text);
|
|
479
|
+
target().focus();
|
|
480
|
+
},
|
|
481
|
+
insertMention,
|
|
482
|
+
removeMention,
|
|
483
|
+
onSubmitted,
|
|
484
|
+
focus: () => target().focus(),
|
|
485
|
+
submit,
|
|
486
|
+
setSelection,
|
|
487
|
+
experimental_removeMention: (mention) => {
|
|
488
|
+
warnDeprecatedComposerMember("experimental_removeMention", "replace");
|
|
489
|
+
if (!legacyAvailable("experimental_removeMention")) return;
|
|
490
|
+
removeMentionFrom(mention);
|
|
491
|
+
},
|
|
492
|
+
experimental_onSubmitted: (listener) => {
|
|
493
|
+
warnDeprecatedComposerMember("experimental_onSubmitted", "onSubmitted");
|
|
494
|
+
return onSubmitted(listener);
|
|
495
|
+
},
|
|
496
|
+
experimental_submit: (options) => {
|
|
497
|
+
warnDeprecatedComposerMember("experimental_submit", "submit");
|
|
498
|
+
return submit(options);
|
|
499
|
+
},
|
|
500
|
+
experimental_setSelection: (selection) => {
|
|
501
|
+
warnDeprecatedComposerMember("experimental_setSelection", "setSelection");
|
|
502
|
+
return setSelection(selection);
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
Object.defineProperties(handle, {
|
|
506
|
+
run: {
|
|
507
|
+
enumerable: false,
|
|
508
|
+
get: () => {
|
|
509
|
+
const state = editorState();
|
|
510
|
+
return { isRunning: state.isRunning, isSubmitting: state.isSubmitting };
|
|
511
|
+
}
|
|
512
|
+
},
|
|
513
|
+
setThreadRowStatus: { enumerable: false, value: () => {
|
|
514
|
+
} }
|
|
515
|
+
});
|
|
516
|
+
return {
|
|
517
|
+
key,
|
|
518
|
+
handle,
|
|
519
|
+
update(next) {
|
|
520
|
+
controller = next;
|
|
521
|
+
syncSubmittedSubscription();
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
export {
|
|
526
|
+
OFF_SCREEN_EDITOR_STATE,
|
|
527
|
+
appendComposerDraft,
|
|
528
|
+
createComposerDraftActions,
|
|
529
|
+
createComposerHandleBinding,
|
|
530
|
+
reconcileComposerMentions
|
|
531
|
+
};
|