@embedpdf/plugin-commands 3.0.0-next.13 → 3.0.0-next.15
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/dist/contract/host.cjs +18 -0
- package/dist/contract/host.cjs.map +1 -0
- package/dist/contract/host.d.cts +3 -0
- package/dist/contract/host.d.ts +3 -0
- package/dist/contract/host.js +13 -0
- package/dist/contract/host.js.map +1 -0
- package/dist/contract-BlOznyrj.cjs +91 -0
- package/dist/contract-BlOznyrj.cjs.map +1 -0
- package/dist/contract-Cj5BTtO7.js +56 -0
- package/dist/contract-Cj5BTtO7.js.map +1 -0
- package/dist/contract-DAV5VR3P.d.cts +295 -0
- package/dist/contract-DAV5VR3P.d.ts +295 -0
- package/dist/contract.cjs +5 -1
- package/dist/contract.d.cts +2 -2
- package/dist/contract.d.ts +2 -2
- package/dist/contract.js +2 -2
- package/dist/host-contract-Cqz-V6kP.d.cts +29 -0
- package/dist/host-contract-DkuusR8r.d.ts +29 -0
- package/dist/index.cjs +463 -130
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -11
- package/dist/index.d.ts +9 -11
- package/dist/index.js +461 -130
- package/dist/index.js.map +1 -1
- package/dist/standard.cjs +243 -0
- package/dist/standard.cjs.map +1 -0
- package/dist/standard.d.cts +17 -0
- package/dist/standard.d.ts +17 -0
- package/dist/standard.js +242 -0
- package/dist/standard.js.map +1 -0
- package/package.json +19 -6
- package/dist/contract-CSIfF6wC.d.cts +0 -137
- package/dist/contract-CSIfF6wC.d.ts +0 -137
- package/dist/contract-DYlaVyVk.cjs +0 -25
- package/dist/contract-DYlaVyVk.cjs.map +0 -1
- package/dist/contract-UFgXphIQ.js +0 -14
- package/dist/contract-UFgXphIQ.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,29 +1,213 @@
|
|
|
1
|
-
import { n as resolvedCommandsEqual, t as
|
|
2
|
-
import {
|
|
1
|
+
import { a as unregisteredCommand, i as unknownCommandWarning, n as boundCommandOf, o as CommandsToken, r as resolvedCommandsEqual, t as COMMANDS_DEFAULTS } from "./contract-Cj5BTtO7.js";
|
|
2
|
+
import { CommandsToken as CommandsToken$1 } from "./contract/host.js";
|
|
3
|
+
import { DocumentsToken, PluginError, definePlugin, isPluginError, memo, memoByKey, toPluginError, toPluginErrorInfo } from "@embedpdf/core";
|
|
3
4
|
import { I18nToken } from "@embedpdf/plugin-i18n/contract";
|
|
4
5
|
import { ShellToken } from "@embedpdf/plugin-shell/contract";
|
|
5
6
|
import { matchShortcut, parseShortcut } from "@embedpdf/core-ui";
|
|
6
|
-
//#region src/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const shortcuts =
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
//#region src/registry.ts
|
|
8
|
+
/** A command definition with its shortcuts parsed once, and how the registry tells a family apart. */
|
|
9
|
+
const registeredCommand = (definition) => {
|
|
10
|
+
const shortcuts = definition.shortcut === void 0 ? [] : [].concat(definition.shortcut);
|
|
11
|
+
return {
|
|
12
|
+
definition,
|
|
12
13
|
shortcuts,
|
|
13
14
|
parsed: shortcuts.map(parseShortcut)
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
const isCommandFamily = (item) => "prefix" in item;
|
|
18
|
+
function configuredCommands(items) {
|
|
19
|
+
const commands = /* @__PURE__ */ new Map();
|
|
20
|
+
const families = [];
|
|
21
|
+
for (const item of items) if (isCommandFamily(item)) families.push(item);
|
|
22
|
+
else commands.set(item.id, registeredCommand(item));
|
|
23
|
+
return {
|
|
24
|
+
commands,
|
|
25
|
+
families
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/services/availability.ts
|
|
30
|
+
/**
|
|
31
|
+
* Whether a category is off: it, or a category it's under, is in the list. Parts are whole
|
|
32
|
+
* dotted segments, so `annotations` covers `annotations.highlight` and not `annotationsExtra`.
|
|
33
|
+
*/
|
|
34
|
+
const isCategoryOff = (category, disabled) => disabled.some((off) => category === off || category.startsWith(`${off}.`));
|
|
35
|
+
/**
|
|
36
|
+
* The label: a language's string under the command's id, else under its `labelKey` (the current
|
|
37
|
+
* language, then the fallback one), else `label`, else the id. So a string for
|
|
38
|
+
* `'document:download'` relabels that one command, whichever key its label is under.
|
|
39
|
+
*/
|
|
40
|
+
const labelOf = (definition, context) => {
|
|
41
|
+
const i18n = context.tryGet(I18nToken);
|
|
42
|
+
if (!i18n) return definition.label ?? definition.labelKey ?? definition.id;
|
|
43
|
+
const fallbackKey = definition.labelKey ? { fallbackKey: definition.labelKey } : {};
|
|
44
|
+
return definition.label === void 0 && definition.labelKey ? i18n.t(definition.id, fallbackKey) : i18n.t(definition.id, {
|
|
45
|
+
...fallbackKey,
|
|
46
|
+
fallback: definition.label ?? definition.id
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Whether a document's session holds a permission: what `ctx.allows` answers for a
|
|
51
|
+
* document-scoped plugin, asked here of the command's document, since the commands are one
|
|
52
|
+
* set for the whole workspace.
|
|
53
|
+
*/
|
|
54
|
+
const sessionHolds = (security, permission) => permission === "annotations:create" ? security.allowsAnnotation("create") : security.allows(permission);
|
|
55
|
+
/** Where a language keeps the sentence for a permission: `commands.notAllowed.doc-text-copy`. */
|
|
56
|
+
const permissionKeyOf = (permission) => `commands.notAllowed.${permission.replace(/[.:]/g, "-")}`;
|
|
57
|
+
/** The general sentence's key: `{label} isn't allowed`. */
|
|
58
|
+
const DEFAULT_NOT_ALLOWED_KEY = "commands.notAllowed.default";
|
|
59
|
+
/** The sentence for a command the session may not run, under `key`, else the general one. */
|
|
60
|
+
const notAllowedMessage = (key, label, context) => {
|
|
61
|
+
const fallback = `${label} isn't allowed`;
|
|
62
|
+
const i18n = context.tryGet(I18nToken);
|
|
63
|
+
if (!i18n) return fallback;
|
|
64
|
+
const params = { label };
|
|
65
|
+
return i18n.t(key, {
|
|
66
|
+
params,
|
|
67
|
+
fallback: i18n.t(DEFAULT_NOT_ALLOWED_KEY, {
|
|
68
|
+
params,
|
|
69
|
+
fallback
|
|
70
|
+
})
|
|
14
71
|
});
|
|
72
|
+
};
|
|
73
|
+
/** The sentence for a command that's turned off. */
|
|
74
|
+
const turnedOffMessage = (label, context) => {
|
|
75
|
+
const i18n = context.tryGet(I18nToken);
|
|
76
|
+
return i18n ? i18n.t("commands.turnedOff", {
|
|
77
|
+
params: { label },
|
|
78
|
+
fallback: "{label} isn't available"
|
|
79
|
+
}) : `${label} isn't available`;
|
|
80
|
+
};
|
|
81
|
+
function createAvailability(ctx, settings) {
|
|
82
|
+
/** Whether a command is turned off: by its id, or through one of its categories. */
|
|
83
|
+
const isTurnedOff = (definition) => {
|
|
84
|
+
const { disabledCategories, disabledCommands } = settings.get();
|
|
85
|
+
return disabledCommands.includes(definition.id) || (definition.categories ?? []).some((category) => isCategoryOff(category, disabledCategories));
|
|
86
|
+
};
|
|
87
|
+
/** Whether a command's `allowed` check says yes; one that throws allows it. */
|
|
88
|
+
const passesCheck = (definition, context) => {
|
|
89
|
+
if (!definition.allowed) return true;
|
|
90
|
+
try {
|
|
91
|
+
return definition.allowed(context);
|
|
92
|
+
} catch {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Why its document's session may not run a command, as the key of the sentence that says
|
|
98
|
+
* so, or `null` when it may: the first permission `requires` names that the session doesn't
|
|
99
|
+
* hold (`commands.notAllowed.doc-print`), else its `allowed` check saying no (its
|
|
100
|
+
* `notAllowedKey`). With no document open there is no session to ask, and nothing is refused.
|
|
101
|
+
*/
|
|
102
|
+
const notAllowedKeyOf = (definition, context) => {
|
|
103
|
+
if (!definition.requires?.length && !definition.allowed) return null;
|
|
104
|
+
const handle = context.documentId === null ? null : ctx.documentHandle(context.documentId);
|
|
105
|
+
if (!handle) return null;
|
|
106
|
+
const missing = definition.requires?.find((permission) => !sessionHolds(handle.security, permission));
|
|
107
|
+
if (missing !== void 0) return permissionKeyOf(missing);
|
|
108
|
+
return passesCheck(definition, context) ? null : definition.notAllowedKey ?? DEFAULT_NOT_ALLOWED_KEY;
|
|
109
|
+
};
|
|
110
|
+
/** Why a command won't run whatever the moment, or `null` when it may run. */
|
|
111
|
+
const refusalOf = (definition, context) => {
|
|
112
|
+
if (isTurnedOff(definition)) return {
|
|
113
|
+
reason: "turned-off",
|
|
114
|
+
message: turnedOffMessage(labelOf(definition, context), context)
|
|
115
|
+
};
|
|
116
|
+
const notAllowed = notAllowedKeyOf(definition, context);
|
|
117
|
+
return notAllowed === null ? null : {
|
|
118
|
+
reason: "not-allowed",
|
|
119
|
+
message: notAllowedMessage(notAllowed, labelOf(definition, context), context)
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
return {
|
|
123
|
+
isTurnedOff,
|
|
124
|
+
notAllowedKeyOf,
|
|
125
|
+
refusalOf
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/write/turning-off.ts
|
|
130
|
+
function createTurningOff(settings, definitionOf, isTurnedOff) {
|
|
131
|
+
const listOf = (list) => settings.get()[list];
|
|
132
|
+
const replace = (list, names) => settings.api.updateSettings(list === "disabledCategories" ? { disabledCategories: [...names] } : { disabledCommands: [...names] });
|
|
133
|
+
const add = (list, name) => {
|
|
134
|
+
const current = listOf(list);
|
|
135
|
+
if (!current.includes(name)) replace(list, [...current, name]);
|
|
136
|
+
};
|
|
137
|
+
const remove = (list, name) => {
|
|
138
|
+
const current = listOf(list);
|
|
139
|
+
if (current.includes(name)) replace(list, current.filter((other) => other !== name));
|
|
140
|
+
};
|
|
141
|
+
return {
|
|
142
|
+
getDisabledCategories: () => listOf("disabledCategories"),
|
|
143
|
+
isCategoryDisabled: (category) => isCategoryOff(category, listOf("disabledCategories")),
|
|
144
|
+
disableCategory: (category) => add("disabledCategories", category),
|
|
145
|
+
enableCategory: (category) => remove("disabledCategories", category),
|
|
146
|
+
setDisabledCategories: (categories) => replace("disabledCategories", categories),
|
|
147
|
+
getDisabledCommands: () => listOf("disabledCommands"),
|
|
148
|
+
isCommandDisabled: (id) => {
|
|
149
|
+
const definition = definitionOf(id);
|
|
150
|
+
return definition ? isTurnedOff(definition) : listOf("disabledCommands").includes(id);
|
|
151
|
+
},
|
|
152
|
+
disableCommand: (id) => add("disabledCommands", id),
|
|
153
|
+
enableCommand: (id) => remove("disabledCommands", id),
|
|
154
|
+
setDisabledCommands: (ids) => replace("disabledCommands", ids)
|
|
155
|
+
};
|
|
15
156
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/controller.ts
|
|
159
|
+
/**
|
|
160
|
+
* The commands controller: the commands (from the settings, added while the
|
|
161
|
+
* app runs, and from families), resolution for a document (derivations over
|
|
162
|
+
* live state, safe to throw), the one way to run a command, turning commands
|
|
163
|
+
* off (by category and one by one), what the session may run (the scope's
|
|
164
|
+
* `requires`, the plugins' checks in `allowed`), and the host-only keystroke
|
|
165
|
+
* matcher.
|
|
166
|
+
*
|
|
167
|
+
* Definitions hold functions, so the commands added while the app runs are a
|
|
168
|
+
* map outside state; every change to it calls `ctx.notify()` so readers read
|
|
169
|
+
* again.
|
|
170
|
+
*/
|
|
171
|
+
const panelTarget = (definition) => definition.panel === void 0 ? null : typeof definition.panel === "string" ? { id: definition.panel } : definition.panel;
|
|
172
|
+
const NOT_REGISTERED = [
|
|
173
|
+
null,
|
|
174
|
+
"",
|
|
175
|
+
void 0,
|
|
176
|
+
void 0,
|
|
177
|
+
false,
|
|
178
|
+
false,
|
|
179
|
+
false,
|
|
180
|
+
void 0
|
|
181
|
+
];
|
|
182
|
+
const NO_NAMES = [];
|
|
183
|
+
const accentOf = (primary, secondary) => primary === void 0 && secondary === void 0 ? void 0 : {
|
|
184
|
+
...primary !== void 0 ? { primary } : {},
|
|
185
|
+
...secondary !== void 0 ? { secondary } : {}
|
|
186
|
+
};
|
|
187
|
+
/** The memo key of a resolution: the command id and the document named, if any. */
|
|
188
|
+
const resolutionKey = (id, documentId) => JSON.stringify([id, documentId ?? null]);
|
|
189
|
+
function createCommandsController(ctx) {
|
|
190
|
+
const settings = ctx.settings();
|
|
191
|
+
/** Commands added while the app runs, in the order they were added. */
|
|
192
|
+
const registered = /* @__PURE__ */ new Map();
|
|
193
|
+
/** Bumped on every change to `registered`: the input of the reads built from it. */
|
|
194
|
+
let registeredVersion = 0;
|
|
195
|
+
const registeredChanged = () => {
|
|
196
|
+
registeredVersion += 1;
|
|
197
|
+
ctx.notify();
|
|
198
|
+
};
|
|
199
|
+
/** The commands the settings define, rebuilt when the setting changes. */
|
|
200
|
+
const configured = memo(() => [settings.get().commands], (items) => configuredCommands(items));
|
|
201
|
+
const executed = ctx.events.source();
|
|
202
|
+
const executionFailed = ctx.events.source();
|
|
203
|
+
/** Capabilities for the command's document: the one named, else the active one. */
|
|
204
|
+
const commandContext = (documentId, call = {}) => {
|
|
205
|
+
const target = documentId ?? ctx.get(DocumentsToken).getActiveId();
|
|
23
206
|
const get = (token) => target ? ctx.forDocument(token, target) : ctx.get(token);
|
|
24
207
|
return {
|
|
25
208
|
documentId: target,
|
|
26
|
-
|
|
209
|
+
...call.args === void 0 ? {} : { args: call.args },
|
|
210
|
+
...call.signal ? { signal: call.signal } : {},
|
|
27
211
|
get,
|
|
28
212
|
tryGet: (token) => {
|
|
29
213
|
try {
|
|
@@ -34,149 +218,296 @@ function createCommandsCapability(ctx, registry) {
|
|
|
34
218
|
}
|
|
35
219
|
};
|
|
36
220
|
};
|
|
37
|
-
/**
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (!
|
|
221
|
+
/** A family's command for one name, made once so it keeps its identity. */
|
|
222
|
+
const familyCommands = /* @__PURE__ */ new WeakMap();
|
|
223
|
+
const familyCommand = (family, name) => {
|
|
224
|
+
let byName = familyCommands.get(family);
|
|
225
|
+
if (!byName) {
|
|
226
|
+
byName = /* @__PURE__ */ new Map();
|
|
227
|
+
familyCommands.set(family, byName);
|
|
228
|
+
}
|
|
229
|
+
let entry = byName.get(name);
|
|
230
|
+
if (!entry) {
|
|
231
|
+
entry = registeredCommand({
|
|
232
|
+
...family.command(name),
|
|
233
|
+
id: `${family.prefix}${name}`
|
|
234
|
+
});
|
|
235
|
+
byName.set(name, entry);
|
|
236
|
+
}
|
|
237
|
+
return entry;
|
|
238
|
+
};
|
|
239
|
+
/** The command with this id: one added while the app runs, else the settings', else a family's. */
|
|
240
|
+
const entryOf = (id) => {
|
|
241
|
+
const { commands, families } = configured();
|
|
242
|
+
const entry = registered.get(id) ?? commands.get(id);
|
|
243
|
+
if (entry) return entry;
|
|
244
|
+
const family = families.find((candidate) => id.startsWith(candidate.prefix) && id.length > candidate.prefix.length);
|
|
245
|
+
return family ? familyCommand(family, id.slice(family.prefix.length)) : null;
|
|
246
|
+
};
|
|
247
|
+
/** Every command with its own definition (not a family's), each id once. */
|
|
248
|
+
const definedCommands = memo(() => [registeredVersion, configured()], (_version, { commands }) => {
|
|
249
|
+
const entries = new Map(commands);
|
|
250
|
+
for (const [id, entry] of registered) entries.set(id, entry);
|
|
251
|
+
return [...entries.values()];
|
|
252
|
+
});
|
|
253
|
+
const derive = (derivation, context, fallback) => {
|
|
254
|
+
if (!derivation) return fallback;
|
|
42
255
|
try {
|
|
43
|
-
return
|
|
256
|
+
return derivation(context);
|
|
44
257
|
} catch {
|
|
45
258
|
return fallback;
|
|
46
259
|
}
|
|
47
260
|
};
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const deriveAccent = (fn, c) => {
|
|
51
|
-
if (!fn) return void 0;
|
|
261
|
+
const deriveAccent = (derivation, context) => {
|
|
262
|
+
if (!derivation) return void 0;
|
|
52
263
|
try {
|
|
53
|
-
return
|
|
264
|
+
return derivation(context) ?? void 0;
|
|
54
265
|
} catch {
|
|
55
266
|
return;
|
|
56
267
|
}
|
|
57
268
|
};
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
269
|
+
const namesOf = (family, context) => {
|
|
270
|
+
try {
|
|
271
|
+
return family.names(context);
|
|
272
|
+
} catch {
|
|
273
|
+
return NO_NAMES;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
const { isTurnedOff, notAllowedKeyOf, refusalOf } = createAvailability(ctx, settings);
|
|
277
|
+
const derivationsOf = (entry, documentId) => {
|
|
278
|
+
const { definition } = entry;
|
|
279
|
+
const context = commandContext(documentId);
|
|
280
|
+
const label = labelOf(definition, context);
|
|
281
|
+
const turnedOff = isTurnedOff(definition);
|
|
282
|
+
const notAllowed = turnedOff ? null : notAllowedKeyOf(definition, context);
|
|
283
|
+
const shownWhenNotAllowed = settings.get().notAllowed === "disable";
|
|
284
|
+
const ruledOut = turnedOff || notAllowed !== null;
|
|
67
285
|
let active;
|
|
68
|
-
if (
|
|
286
|
+
if (definition.active) active = derive(definition.active, context, false);
|
|
69
287
|
else {
|
|
70
|
-
const shell =
|
|
71
|
-
const panel = panelTarget(
|
|
72
|
-
active = shell ?
|
|
288
|
+
const shell = context.tryGet(ShellToken);
|
|
289
|
+
const panel = panelTarget(definition);
|
|
290
|
+
active = shell ? definition.menu ? shell.isMenuOpen(definition.menu) : panel ? shell.isOpen(panel.id) : definition.modal ? shell.isOpen(definition.modal) : false : false;
|
|
73
291
|
}
|
|
74
|
-
|
|
75
|
-
|
|
292
|
+
const accent = deriveAccent(definition.iconAccent, context);
|
|
293
|
+
return [
|
|
294
|
+
entry,
|
|
76
295
|
label,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
menu: def.menu,
|
|
81
|
-
enabled: derive(def.enabled, c, true) && !categoryHidden,
|
|
296
|
+
accent?.primary,
|
|
297
|
+
accent?.secondary,
|
|
298
|
+
!ruledOut && derive(definition.enabled, context, true),
|
|
82
299
|
active,
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
300
|
+
!turnedOff && (notAllowed === null || shownWhenNotAllowed) && derive(definition.visible, context, true),
|
|
301
|
+
notAllowed !== null && shownWhenNotAllowed ? notAllowedMessage(notAllowed, label, context) : void 0
|
|
302
|
+
];
|
|
86
303
|
};
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
304
|
+
/** A resolved command, the same object until one of its derivations or its entry changes. */
|
|
305
|
+
const resolvedByKey = memoByKey((key) => {
|
|
306
|
+
const [id, documentId] = JSON.parse(key);
|
|
307
|
+
const entry = entryOf(id);
|
|
308
|
+
return entry ? derivationsOf(entry, documentId ?? void 0) : NOT_REGISTERED;
|
|
309
|
+
}, (_key, entry, label, accentPrimary, accentSecondary, enabled, active, visible, disabledReason) => entry && {
|
|
310
|
+
id: entry.definition.id,
|
|
311
|
+
label,
|
|
312
|
+
icon: entry.definition.icon,
|
|
313
|
+
iconAccent: accentOf(accentPrimary, accentSecondary),
|
|
314
|
+
shortcuts: entry.shortcuts,
|
|
315
|
+
menu: entry.definition.menu,
|
|
316
|
+
enabled,
|
|
317
|
+
active,
|
|
318
|
+
visible,
|
|
319
|
+
categories: entry.definition.categories ?? [],
|
|
320
|
+
...disabledReason === void 0 ? {} : { disabledReason }
|
|
321
|
+
});
|
|
322
|
+
const resolveCommand = (id, documentId) => entryOf(id) ? resolvedByKey(resolutionKey(id, documentId)) : null;
|
|
323
|
+
/**
|
|
324
|
+
* Every command's id for a document (`''` is the active one): the defined ones in the order
|
|
325
|
+
* they were added, then each family's names that no defined command took.
|
|
326
|
+
*/
|
|
327
|
+
const idsByDocument = memoByKey((documentKey) => {
|
|
328
|
+
const config = configured();
|
|
329
|
+
const context = commandContext(documentKey || void 0);
|
|
330
|
+
return [
|
|
331
|
+
definedCommands(),
|
|
332
|
+
config,
|
|
333
|
+
...config.families.map((family) => namesOf(family, context))
|
|
334
|
+
];
|
|
335
|
+
}, (_documentKey, ...inputs) => {
|
|
336
|
+
const [defined, { families }, ...names] = inputs;
|
|
337
|
+
const ids = defined.map((entry) => entry.definition.id);
|
|
338
|
+
const taken = new Set(ids);
|
|
339
|
+
families.forEach((family, index) => {
|
|
340
|
+
for (const name of names[index] ?? NO_NAMES) {
|
|
341
|
+
const id = `${family.prefix}${name}`;
|
|
342
|
+
if (taken.has(id)) continue;
|
|
343
|
+
taken.add(id);
|
|
344
|
+
ids.push(id);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
return ids;
|
|
348
|
+
});
|
|
349
|
+
const commandsByDocument = memoByKey((documentKey) => idsByDocument(documentKey).map((id) => resolveCommand(id, documentKey || void 0)), (_documentKey, ...resolved) => resolved.filter((command) => command !== null));
|
|
350
|
+
const listCommands = (documentId) => commandsByDocument(documentId ?? "");
|
|
351
|
+
const listShortcuts = memo(() => [definedCommands()], (entries) => entries.flatMap((entry) => entry.shortcuts.map((shortcut) => ({
|
|
352
|
+
commandId: entry.definition.id,
|
|
353
|
+
shortcut
|
|
354
|
+
}))));
|
|
355
|
+
/** What running a command does: its `run`, or opening what it opens. */
|
|
356
|
+
const runCommand = async (definition, context) => {
|
|
357
|
+
if (definition.run) {
|
|
358
|
+
await definition.run(context);
|
|
95
359
|
return;
|
|
96
360
|
}
|
|
97
|
-
const shell =
|
|
361
|
+
const shell = context.tryGet(ShellToken);
|
|
362
|
+
const panel = panelTarget(definition);
|
|
98
363
|
if (!shell) return;
|
|
99
|
-
|
|
100
|
-
if (
|
|
101
|
-
|
|
102
|
-
|
|
364
|
+
if (definition.menu) shell.toggleMenu(definition.menu);
|
|
365
|
+
else if (panel) shell.toggle(panel.id, {
|
|
366
|
+
exclusive: panel.exclusive,
|
|
367
|
+
focus: panel.focus
|
|
368
|
+
});
|
|
369
|
+
else if (definition.modal) shell.toggle(definition.modal, { exclusive: "modal" });
|
|
103
370
|
};
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
371
|
+
const execute = async (id, options = {}) => {
|
|
372
|
+
const { signal, documentId, args } = options;
|
|
373
|
+
if (signal?.aborted) throw new PluginError("operation-cancelled", "commands", "operation cancelled");
|
|
374
|
+
const entry = entryOf(id);
|
|
375
|
+
if (!entry) return {
|
|
376
|
+
status: "rejected",
|
|
377
|
+
reason: "not-found"
|
|
378
|
+
};
|
|
379
|
+
const resolved = resolveCommand(id, documentId);
|
|
380
|
+
if (!resolved?.visible) return {
|
|
381
|
+
status: "rejected",
|
|
382
|
+
reason: "hidden"
|
|
383
|
+
};
|
|
384
|
+
if (!resolved.enabled) return {
|
|
385
|
+
status: "rejected",
|
|
386
|
+
reason: "disabled"
|
|
387
|
+
};
|
|
388
|
+
const context = commandContext(documentId, {
|
|
389
|
+
args,
|
|
390
|
+
signal
|
|
391
|
+
});
|
|
392
|
+
try {
|
|
393
|
+
await ctx.cancellable(signal, runCommand(entry.definition, context));
|
|
394
|
+
} catch (error) {
|
|
395
|
+
if (signal?.aborted && isPluginError(error, "operation-cancelled")) throw error;
|
|
396
|
+
const failure = toPluginError("commands", error);
|
|
397
|
+
executionFailed.emit({
|
|
398
|
+
commandId: id,
|
|
399
|
+
documentId: context.documentId,
|
|
400
|
+
error: toPluginErrorInfo(failure)
|
|
401
|
+
});
|
|
402
|
+
throw failure;
|
|
403
|
+
}
|
|
404
|
+
executed.emit({
|
|
405
|
+
commandId: id,
|
|
406
|
+
documentId: context.documentId,
|
|
407
|
+
args
|
|
408
|
+
});
|
|
409
|
+
return { status: "executed" };
|
|
410
|
+
};
|
|
411
|
+
const registerOne = (definition, options) => {
|
|
412
|
+
if ((registered.has(definition.id) || configured().commands.has(definition.id)) && !options?.replace) throw new PluginError("conflict", "commands", `duplicate command '${definition.id}'`);
|
|
413
|
+
const entry = registeredCommand(definition);
|
|
414
|
+
registered.set(definition.id, entry);
|
|
415
|
+
registeredChanged();
|
|
416
|
+
return () => {
|
|
417
|
+
if (registered.get(definition.id) !== entry) return;
|
|
418
|
+
registered.delete(definition.id);
|
|
419
|
+
registeredChanged();
|
|
420
|
+
};
|
|
421
|
+
};
|
|
422
|
+
const turningOff = createTurningOff(settings, (id) => entryOf(id)?.definition ?? null, isTurnedOff);
|
|
423
|
+
return { api: {
|
|
424
|
+
...settings.api,
|
|
425
|
+
registerCommand: registerOne,
|
|
426
|
+
registerCommands: (definitions, options) => {
|
|
427
|
+
const removers = definitions.map((definition) => registerOne(definition, options));
|
|
428
|
+
return () => removers.forEach((remove) => remove());
|
|
119
429
|
},
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
430
|
+
hasCommand: (id) => entryOf(id) !== null,
|
|
431
|
+
getCommand: (id) => entryOf(id)?.definition ?? null,
|
|
432
|
+
listCommandIds: () => idsByDocument(""),
|
|
433
|
+
resolveCommand,
|
|
434
|
+
listCommands,
|
|
435
|
+
searchCommands: (query, documentId) => {
|
|
436
|
+
const needle = query.trim().toLowerCase();
|
|
437
|
+
return listCommands(documentId).filter((command) => command.visible && (needle === "" || command.label.toLowerCase().includes(needle) || command.id.includes(needle)));
|
|
123
438
|
},
|
|
439
|
+
listShortcuts,
|
|
124
440
|
execute,
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return
|
|
441
|
+
canExecute: (id, documentId) => {
|
|
442
|
+
const resolved = resolveCommand(id, documentId);
|
|
443
|
+
return !!resolved && resolved.enabled && resolved.visible;
|
|
128
444
|
},
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
445
|
+
getRefusal: (id, documentId) => {
|
|
446
|
+
const entry = entryOf(id);
|
|
447
|
+
return entry ? refusalOf(entry.definition, commandContext(documentId)) : null;
|
|
448
|
+
},
|
|
449
|
+
...turningOff,
|
|
450
|
+
onExecuted: executed.on,
|
|
451
|
+
onExecutionFailed: executionFailed.on,
|
|
452
|
+
matchStroke: (stroke, options) => {
|
|
453
|
+
let refused = null;
|
|
454
|
+
for (const entry of definedCommands()) {
|
|
455
|
+
if (options.inTextField && !entry.definition.shortcutInTextFields) continue;
|
|
456
|
+
if (!entry.parsed.some((shortcut) => matchShortcut(shortcut, stroke, options))) continue;
|
|
457
|
+
const context = commandContext();
|
|
458
|
+
const refusal = refusalOf(entry.definition, context);
|
|
459
|
+
if (!refusal) return {
|
|
460
|
+
commandId: entry.definition.id,
|
|
461
|
+
refusal: null
|
|
462
|
+
};
|
|
463
|
+
if (!derive(entry.definition.visible, context, true)) continue;
|
|
464
|
+
refused ?? (refused = {
|
|
465
|
+
commandId: entry.definition.id,
|
|
466
|
+
refusal
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
return refused;
|
|
470
|
+
},
|
|
471
|
+
getMenuTarget: (id) => {
|
|
472
|
+
const entry = entryOf(id);
|
|
473
|
+
return entry ? { menu: entry.definition.menu } : null;
|
|
474
|
+
}
|
|
475
|
+
} };
|
|
155
476
|
}
|
|
156
477
|
//#endregion
|
|
157
478
|
//#region src/commands.plugin.ts
|
|
158
479
|
/**
|
|
159
|
-
* The commands
|
|
160
|
-
*
|
|
161
|
-
* Definitions
|
|
162
|
-
* functions
|
|
480
|
+
* The commands: one set for the whole workspace, each resolved and run for a
|
|
481
|
+
* document (the one named, else the document in scope, else the active one).
|
|
482
|
+
* Definitions are settings and app content, never state, since they hold
|
|
483
|
+
* functions. `config` is the settings the app registers, over
|
|
484
|
+
* {@link COMMANDS_DEFAULTS}.
|
|
163
485
|
*/
|
|
164
|
-
const commandsPlugin = (config) => {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
486
|
+
const commandsPlugin = (config) => definePlugin({
|
|
487
|
+
id: "commands",
|
|
488
|
+
scope: "workspace",
|
|
489
|
+
token: CommandsToken$1,
|
|
490
|
+
optional: [I18nToken, ShellToken],
|
|
491
|
+
resolvesAnyCapability: true,
|
|
492
|
+
settings: {
|
|
493
|
+
defaults: COMMANDS_DEFAULTS,
|
|
494
|
+
registered: config
|
|
495
|
+
},
|
|
496
|
+
create: createCommandsController,
|
|
497
|
+
inScope: (commands, documentId) => ({
|
|
498
|
+
...commands,
|
|
499
|
+
resolveCommand: (id, target = documentId) => commands.resolveCommand(id, target),
|
|
500
|
+
listCommands: (target = documentId) => commands.listCommands(target),
|
|
501
|
+
searchCommands: (query, target = documentId) => commands.searchCommands(query, target),
|
|
502
|
+
execute: (id, options) => commands.execute(id, {
|
|
503
|
+
...options,
|
|
504
|
+
documentId: options?.documentId ?? documentId
|
|
505
|
+
}),
|
|
506
|
+
canExecute: (id, target = documentId) => commands.canExecute(id, target),
|
|
507
|
+
getRefusal: (id, target = documentId) => commands.getRefusal(id, target)
|
|
508
|
+
})
|
|
509
|
+
});
|
|
179
510
|
//#endregion
|
|
180
|
-
export { CommandsToken, commandsPlugin,
|
|
511
|
+
export { COMMANDS_DEFAULTS, CommandsToken, boundCommandOf, commandsPlugin, resolvedCommandsEqual, unknownCommandWarning, unregisteredCommand };
|
|
181
512
|
|
|
182
513
|
//# sourceMappingURL=index.js.map
|