@lofcz/embedpdf-plugin-commands 2.15.0 → 3.0.0-next.7
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/LICENSE +192 -21
- package/dist/index.cjs +196 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +150 -0
- package/dist/index.d.ts +150 -1
- package/dist/index.js +189 -379
- package/dist/index.js.map +1 -1
- package/package.json +21 -40
- package/dist/lib/actions.d.ts +0 -8
- package/dist/lib/commands-plugin.d.ts +0 -40
- package/dist/lib/index.d.ts +0 -8
- package/dist/lib/manifest.d.ts +0 -4
- package/dist/lib/reducer.d.ts +0 -5
- package/dist/lib/types.d.ts +0 -116
- package/dist/react/adapter.d.ts +0 -2
- package/dist/react/core.d.ts +0 -1
- package/dist/react/index.cjs +0 -2
- package/dist/react/index.cjs.map +0 -1
- package/dist/react/index.d.ts +0 -1
- package/dist/react/index.js +0 -90
- package/dist/react/index.js.map +0 -1
- package/dist/shared/components/index.d.ts +0 -1
- package/dist/shared/components/keyboard-shortcuts.d.ts +0 -6
- package/dist/shared/hooks/index.d.ts +0 -1
- package/dist/shared/hooks/use-commands.d.ts +0 -23
- package/dist/shared/index.d.ts +0 -4
- package/dist/shared/utils/index.d.ts +0 -1
- package/dist/shared/utils/keyboard-handler.d.ts +0 -10
- package/dist/shared-react/components/index.d.ts +0 -1
- package/dist/shared-react/components/keyboard-shortcuts.d.ts +0 -6
- package/dist/shared-react/hooks/index.d.ts +0 -1
- package/dist/shared-react/hooks/use-commands.d.ts +0 -23
- package/dist/shared-react/index.d.ts +0 -4
- package/dist/shared-react/utils/index.d.ts +0 -1
- package/dist/shared-react/utils/keyboard-handler.d.ts +0 -10
package/dist/index.js
CHANGED
|
@@ -1,381 +1,191 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
import { createCapabilityToken, definePlugin } from "@embedpdf/core";
|
|
2
|
+
import { I18nToken } from "@embedpdf/plugin-i18n";
|
|
3
|
+
import { ShellToken } from "@embedpdf/plugin-shell";
|
|
4
|
+
import { matchShortcut, parseShortcut } from "@embedpdf/core-ui";
|
|
5
|
+
//#region src/capability.ts
|
|
6
|
+
function registerCommand(registry, def) {
|
|
7
|
+
if (registry.has(def.id)) throw new Error(`[commands] duplicate command: ${def.id}`);
|
|
8
|
+
const shortcuts = def.shortcut === void 0 ? [] : [].concat(def.shortcut);
|
|
9
|
+
registry.set(def.id, {
|
|
10
|
+
def,
|
|
11
|
+
shortcuts,
|
|
12
|
+
parsed: shortcuts.map(parseShortcut)
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
const panelTarget = (def) => def.panel === void 0 ? null : typeof def.panel === "string" ? { id: def.panel } : def.panel;
|
|
16
|
+
function createCommandsCapability(ctx, registry) {
|
|
17
|
+
/** Bind capability resolution to the command's target document. The kernel
|
|
18
|
+
* resolves workspace tokens regardless of the document argument, so one
|
|
19
|
+
* code path serves both scopes. */
|
|
20
|
+
const commandCtx = (documentId) => {
|
|
21
|
+
const target = documentId ?? ctx.core().activeId;
|
|
22
|
+
const get = (token) => target ? ctx.forDocument(token, target) : ctx.get(token);
|
|
23
|
+
return {
|
|
24
|
+
documentId: target,
|
|
25
|
+
core: ctx.core,
|
|
26
|
+
get,
|
|
27
|
+
tryGet: (token) => {
|
|
28
|
+
try {
|
|
29
|
+
return get(token);
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
/** Derivations run against live state; a derivation that throws (e.g. it
|
|
37
|
+
* needs a document and none is open) falls back to the safe default —
|
|
38
|
+
* the button renders, disabled, exactly like v2's empty state. */
|
|
39
|
+
const derive = (fn, c, fallback) => {
|
|
40
|
+
if (!fn) return fallback;
|
|
41
|
+
try {
|
|
42
|
+
return fn(c);
|
|
43
|
+
} catch {
|
|
44
|
+
return fallback;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
/** Same guard as `derive`, for the icon-accent derivation: a throw (no
|
|
48
|
+
* document, provider missing) means "no accent" — the icon renders plain. */
|
|
49
|
+
const deriveAccent = (fn, c) => {
|
|
50
|
+
if (!fn) return void 0;
|
|
51
|
+
try {
|
|
52
|
+
return fn(c) ?? void 0;
|
|
53
|
+
} catch {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const resolve = (id, documentId) => {
|
|
58
|
+
const entry = registry.get(id);
|
|
59
|
+
if (!entry) return null;
|
|
60
|
+
const { def } = entry;
|
|
61
|
+
const c = commandCtx(documentId);
|
|
62
|
+
const disabled = ctx.getState().disabledCategories;
|
|
63
|
+
const categoryHidden = (def.categories ?? []).some((cat) => disabled.includes(cat));
|
|
64
|
+
const i18n = c.tryGet(I18nToken);
|
|
65
|
+
const label = i18n ? i18n.t(def.labelKey) : def.labelKey;
|
|
66
|
+
let active;
|
|
67
|
+
if (def.active) active = derive(def.active, c, false);
|
|
68
|
+
else {
|
|
69
|
+
const shell = c.tryGet(ShellToken);
|
|
70
|
+
const panel = panelTarget(def);
|
|
71
|
+
active = shell ? def.menu ? shell.isMenuOpen(def.menu) : panel ? shell.isOpen(panel.id) : def.modal ? shell.isOpen(def.modal) : false : false;
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
id: def.id,
|
|
75
|
+
label,
|
|
76
|
+
icon: def.icon,
|
|
77
|
+
iconAccent: deriveAccent(def.iconAccent, c),
|
|
78
|
+
shortcuts: entry.shortcuts,
|
|
79
|
+
menu: def.menu,
|
|
80
|
+
enabled: derive(def.enabled, c, true) && !categoryHidden,
|
|
81
|
+
active,
|
|
82
|
+
visible: derive(def.visible, c, true) && !categoryHidden,
|
|
83
|
+
categories: def.categories ?? []
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
const execute = (id, documentId) => {
|
|
87
|
+
const entry = registry.get(id);
|
|
88
|
+
if (!entry) return;
|
|
89
|
+
const resolved = resolve(id, documentId);
|
|
90
|
+
if (!resolved || !resolved.enabled || !resolved.visible) return;
|
|
91
|
+
const c = commandCtx(documentId);
|
|
92
|
+
if (entry.def.run) {
|
|
93
|
+
entry.def.run(c);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const shell = c.tryGet(ShellToken);
|
|
97
|
+
if (!shell) return;
|
|
98
|
+
const panel = panelTarget(entry.def);
|
|
99
|
+
if (entry.def.menu) shell.toggleMenu(entry.def.menu);
|
|
100
|
+
else if (panel) shell.toggle(panel.id, { exclusive: panel.exclusive });
|
|
101
|
+
else if (entry.def.modal) shell.toggle(entry.def.modal, { exclusive: "modal" });
|
|
102
|
+
};
|
|
103
|
+
return {
|
|
104
|
+
register: (def) => registerCommand(registry, def),
|
|
105
|
+
unregister: (id) => void registry.delete(id),
|
|
106
|
+
has: (id) => registry.has(id),
|
|
107
|
+
ids: () => [...registry.keys()],
|
|
108
|
+
resolve,
|
|
109
|
+
search: (query, documentId) => {
|
|
110
|
+
const q = query.trim().toLowerCase();
|
|
111
|
+
const hits = [];
|
|
112
|
+
for (const id of registry.keys()) {
|
|
113
|
+
const r = resolve(id, documentId);
|
|
114
|
+
if (!r || !r.visible) continue;
|
|
115
|
+
if (q === "" || r.label.toLowerCase().includes(q) || r.id.includes(q)) hits.push(r);
|
|
116
|
+
}
|
|
117
|
+
return hits;
|
|
118
|
+
},
|
|
119
|
+
menuTarget: (id) => {
|
|
120
|
+
const entry = registry.get(id);
|
|
121
|
+
return entry ? { menu: entry.def.menu } : null;
|
|
122
|
+
},
|
|
123
|
+
execute,
|
|
124
|
+
matchStroke: (stroke, opts) => {
|
|
125
|
+
for (const [id, entry] of registry) if (entry.parsed.some((p) => matchShortcut(p, stroke, opts))) return id;
|
|
126
|
+
return null;
|
|
127
|
+
},
|
|
128
|
+
disabledCategories: () => ctx.getState().disabledCategories,
|
|
129
|
+
isCategoryDisabled: (category) => ctx.getState().disabledCategories.includes(category),
|
|
130
|
+
disableCategory: (category) => ctx.dispatch({
|
|
131
|
+
type: "COMMANDS/DISABLE_CATEGORY",
|
|
132
|
+
category
|
|
133
|
+
}),
|
|
134
|
+
enableCategory: (category) => ctx.dispatch({
|
|
135
|
+
type: "COMMANDS/ENABLE_CATEGORY",
|
|
136
|
+
category
|
|
137
|
+
}),
|
|
138
|
+
setDisabledCategories: (categories) => ctx.dispatch({
|
|
139
|
+
type: "COMMANDS/SET_DISABLED_CATEGORIES",
|
|
140
|
+
categories
|
|
141
|
+
})
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
//#endregion
|
|
145
|
+
//#region src/reducer.ts
|
|
146
|
+
const initialCommandsState = { disabledCategories: [] };
|
|
147
|
+
function commandsReducer(state, action) {
|
|
148
|
+
switch (action.type) {
|
|
149
|
+
case "COMMANDS/DISABLE_CATEGORY": return state.disabledCategories.includes(action.category) ? state : { disabledCategories: [...state.disabledCategories, action.category] };
|
|
150
|
+
case "COMMANDS/ENABLE_CATEGORY": return state.disabledCategories.includes(action.category) ? { disabledCategories: state.disabledCategories.filter((c) => c !== action.category) } : state;
|
|
151
|
+
case "COMMANDS/SET_DISABLED_CATEGORIES": return { disabledCategories: [...action.categories] };
|
|
152
|
+
default: return state;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region src/types.ts
|
|
157
|
+
/** Value equality over resolved commands — `resolve()` mints a fresh object
|
|
158
|
+
* per read, so reactive bindings memo by value to re-render on real change. */
|
|
159
|
+
const resolvedCommandsEqual = (a, b) => {
|
|
160
|
+
if (a === b) return true;
|
|
161
|
+
if (!a || !b) return false;
|
|
162
|
+
return a.id === b.id && a.label === b.label && a.icon === b.icon && a.iconAccent?.primary === b.iconAccent?.primary && a.iconAccent?.secondary === b.iconAccent?.secondary && a.menu === b.menu && a.enabled === b.enabled && a.active === b.active && a.visible === b.visible && a.shortcuts.length === b.shortcuts.length && a.shortcuts.every((s, i) => s === b.shortcuts[i]);
|
|
13
163
|
};
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
this.registerCommand(command);
|
|
38
|
-
});
|
|
39
|
-
this.registry.getStore().subscribe((_action, newState) => {
|
|
40
|
-
this.onGlobalStoreChange(newState);
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
onDocumentClosed(documentId) {
|
|
44
|
-
this.previousStates.delete(documentId);
|
|
45
|
-
this.logger.debug(
|
|
46
|
-
"CommandsPlugin",
|
|
47
|
-
"DocumentClosed",
|
|
48
|
-
`Cleaned up command state cache for document: ${documentId}`
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
async initialize() {
|
|
52
|
-
this.logger.info("CommandsPlugin", "Initialize", "Commands plugin initialized");
|
|
53
|
-
}
|
|
54
|
-
async destroy() {
|
|
55
|
-
this.commandExecuted$.clear();
|
|
56
|
-
this.commandStateChanged$.clear();
|
|
57
|
-
this.shortcutExecuted$.clear();
|
|
58
|
-
this.categoryChanged$.clear();
|
|
59
|
-
this.commands.clear();
|
|
60
|
-
this.shortcutMap.clear();
|
|
61
|
-
this.previousStates.clear();
|
|
62
|
-
super.destroy();
|
|
63
|
-
}
|
|
64
|
-
// ─────────────────────────────────────────────────────────
|
|
65
|
-
// Category Management
|
|
66
|
-
// ─────────────────────────────────────────────────────────
|
|
67
|
-
disableCategoryImpl(category) {
|
|
68
|
-
const current = new Set(this.state.disabledCategories);
|
|
69
|
-
if (!current.has(category)) {
|
|
70
|
-
current.add(category);
|
|
71
|
-
this.dispatch(setDisabledCategories(Array.from(current)));
|
|
72
|
-
this.categoryChanged$.emit({ disabledCategories: Array.from(current) });
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
enableCategoryImpl(category) {
|
|
76
|
-
const current = new Set(this.state.disabledCategories);
|
|
77
|
-
if (current.has(category)) {
|
|
78
|
-
current.delete(category);
|
|
79
|
-
this.dispatch(setDisabledCategories(Array.from(current)));
|
|
80
|
-
this.categoryChanged$.emit({ disabledCategories: Array.from(current) });
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
toggleCategoryImpl(category) {
|
|
84
|
-
if (this.state.disabledCategories.includes(category)) {
|
|
85
|
-
this.enableCategoryImpl(category);
|
|
86
|
-
} else {
|
|
87
|
-
this.disableCategoryImpl(category);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
setDisabledCategoriesImpl(categories) {
|
|
91
|
-
this.dispatch(setDisabledCategories(categories));
|
|
92
|
-
this.categoryChanged$.emit({ disabledCategories: categories });
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Check if command has any disabled category
|
|
96
|
-
*/
|
|
97
|
-
isCommandCategoryDisabled(command) {
|
|
98
|
-
var _a;
|
|
99
|
-
if (!((_a = command.categories) == null ? void 0 : _a.length)) return false;
|
|
100
|
-
return command.categories.some((cat) => this.state.disabledCategories.includes(cat));
|
|
101
|
-
}
|
|
102
|
-
// ─────────────────────────────────────────────────────────
|
|
103
|
-
// Capability
|
|
104
|
-
// ─────────────────────────────────────────────────────────
|
|
105
|
-
buildCapability() {
|
|
106
|
-
return {
|
|
107
|
-
resolve: (commandId, documentId) => this.resolve(commandId, documentId),
|
|
108
|
-
execute: (commandId, documentId, source = "ui") => this.execute(commandId, documentId, source),
|
|
109
|
-
getAllCommands: (documentId) => this.getAllCommands(documentId),
|
|
110
|
-
getCommandsByCategory: (category, documentId) => this.getCommandsByCategory(category, documentId),
|
|
111
|
-
getCommandByShortcut: (shortcut) => this.getCommandByShortcut(shortcut),
|
|
112
|
-
getAllShortcuts: () => new Map(this.shortcutMap),
|
|
113
|
-
forDocument: (documentId) => this.createCommandScope(documentId),
|
|
114
|
-
registerCommand: (command) => this.registerCommand(command),
|
|
115
|
-
unregisterCommand: (commandId) => this.unregisterCommand(commandId),
|
|
116
|
-
// Category management
|
|
117
|
-
disableCategory: (category) => this.disableCategoryImpl(category),
|
|
118
|
-
enableCategory: (category) => this.enableCategoryImpl(category),
|
|
119
|
-
toggleCategory: (category) => this.toggleCategoryImpl(category),
|
|
120
|
-
setDisabledCategories: (categories) => this.setDisabledCategoriesImpl(categories),
|
|
121
|
-
getDisabledCategories: () => this.state.disabledCategories,
|
|
122
|
-
isCategoryDisabled: (category) => this.state.disabledCategories.includes(category),
|
|
123
|
-
// Events
|
|
124
|
-
onCommandExecuted: this.commandExecuted$.on,
|
|
125
|
-
onCommandStateChanged: this.commandStateChanged$.on,
|
|
126
|
-
onShortcutExecuted: this.shortcutExecuted$.on,
|
|
127
|
-
onCategoryChanged: this.categoryChanged$.on
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
// ─────────────────────────────────────────────────────────
|
|
131
|
-
// Document Scoping
|
|
132
|
-
// ─────────────────────────────────────────────────────────
|
|
133
|
-
createCommandScope(documentId) {
|
|
134
|
-
return {
|
|
135
|
-
resolve: (commandId) => this.resolve(commandId, documentId),
|
|
136
|
-
execute: (commandId, source = "ui") => this.execute(commandId, documentId, source),
|
|
137
|
-
getAllCommands: () => this.getAllCommands(documentId),
|
|
138
|
-
getCommandsByCategory: (category) => this.getCommandsByCategory(category, documentId),
|
|
139
|
-
onCommandStateChanged: (listener) => this.commandStateChanged$.on((event) => {
|
|
140
|
-
if (event.documentId === documentId) {
|
|
141
|
-
const { documentId: _, ...rest } = event;
|
|
142
|
-
listener(rest);
|
|
143
|
-
}
|
|
144
|
-
})
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
// ─────────────────────────────────────────────────────────
|
|
148
|
-
// Command Resolution
|
|
149
|
-
// ─────────────────────────────────────────────────────────
|
|
150
|
-
resolve(commandId, documentId) {
|
|
151
|
-
const resolvedDocId = documentId ?? this.getActiveDocumentId();
|
|
152
|
-
const command = this.commands.get(commandId);
|
|
153
|
-
if (!command) {
|
|
154
|
-
throw new Error(`Command not found: ${commandId}`);
|
|
155
|
-
}
|
|
156
|
-
const state = this.registry.getStore().getState();
|
|
157
|
-
const label = this.resolveLabel(command, state, resolvedDocId);
|
|
158
|
-
const shortcuts = command.shortcuts ? Array.isArray(command.shortcuts) ? command.shortcuts : [command.shortcuts] : void 0;
|
|
159
|
-
const explicitDisabled = this.resolveDynamic(command.disabled, state, resolvedDocId) ?? false;
|
|
160
|
-
const categoryDisabled = this.isCommandCategoryDisabled(command);
|
|
161
|
-
const isDisabled = explicitDisabled || categoryDisabled;
|
|
162
|
-
return {
|
|
163
|
-
id: command.id,
|
|
164
|
-
label,
|
|
165
|
-
icon: this.resolveDynamic(command.icon, state, resolvedDocId),
|
|
166
|
-
iconProps: this.resolveDynamic(command.iconProps, state, resolvedDocId),
|
|
167
|
-
active: this.resolveDynamic(command.active, state, resolvedDocId) ?? false,
|
|
168
|
-
disabled: isDisabled,
|
|
169
|
-
visible: this.resolveDynamic(command.visible, state, resolvedDocId) ?? true,
|
|
170
|
-
shortcuts,
|
|
171
|
-
shortcutLabel: command.shortcutLabel,
|
|
172
|
-
categories: command.categories,
|
|
173
|
-
description: command.description,
|
|
174
|
-
execute: () => command.action({
|
|
175
|
-
registry: this.registry,
|
|
176
|
-
state,
|
|
177
|
-
documentId: resolvedDocId,
|
|
178
|
-
logger: this.logger
|
|
179
|
-
})
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
resolveLabel(command, state, documentId) {
|
|
183
|
-
const labelKey = this.resolveDynamic(command.labelKey, state, documentId);
|
|
184
|
-
if (labelKey && this.i18n) {
|
|
185
|
-
const params = this.resolveDynamic(command.labelParams, state, documentId);
|
|
186
|
-
return this.i18n.t(labelKey, { params, documentId });
|
|
187
|
-
}
|
|
188
|
-
if (command.label) {
|
|
189
|
-
return command.label;
|
|
190
|
-
}
|
|
191
|
-
return command.id;
|
|
192
|
-
}
|
|
193
|
-
resolveDynamic(value, state, documentId) {
|
|
194
|
-
if (value === void 0) return void 0;
|
|
195
|
-
if (typeof value === "function") {
|
|
196
|
-
return value({
|
|
197
|
-
registry: this.registry,
|
|
198
|
-
state,
|
|
199
|
-
documentId,
|
|
200
|
-
logger: this.logger
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
return value;
|
|
204
|
-
}
|
|
205
|
-
// ─────────────────────────────────────────────────────────
|
|
206
|
-
// Command Execution
|
|
207
|
-
// ─────────────────────────────────────────────────────────
|
|
208
|
-
execute(commandId, documentId, source = "ui") {
|
|
209
|
-
const resolvedDocId = documentId ?? this.getActiveDocumentId();
|
|
210
|
-
const resolved = this.resolve(commandId, resolvedDocId);
|
|
211
|
-
if (resolved.disabled) {
|
|
212
|
-
this.logger.warn(
|
|
213
|
-
"CommandsPlugin",
|
|
214
|
-
"ExecutionBlocked",
|
|
215
|
-
`Command '${commandId}' is disabled for document '${resolvedDocId}'`
|
|
216
|
-
);
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
if (!resolved.visible) {
|
|
220
|
-
this.logger.warn(
|
|
221
|
-
"CommandsPlugin",
|
|
222
|
-
"ExecutionBlocked",
|
|
223
|
-
`Command '${commandId}' is not visible for document '${resolvedDocId}'`
|
|
224
|
-
);
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
resolved.execute();
|
|
228
|
-
this.commandExecuted$.emit({
|
|
229
|
-
commandId,
|
|
230
|
-
documentId: resolvedDocId,
|
|
231
|
-
source
|
|
232
|
-
});
|
|
233
|
-
this.logger.debug(
|
|
234
|
-
"CommandsPlugin",
|
|
235
|
-
"CommandExecuted",
|
|
236
|
-
`Command '${commandId}' executed for document '${resolvedDocId}' (source: ${source})`
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
// ─────────────────────────────────────────────────────────
|
|
240
|
-
// Command Registration
|
|
241
|
-
// ─────────────────────────────────────────────────────────
|
|
242
|
-
registerCommand(command) {
|
|
243
|
-
if (this.commands.has(command.id)) {
|
|
244
|
-
this.logger.warn(
|
|
245
|
-
"CommandsPlugin",
|
|
246
|
-
"CommandOverwrite",
|
|
247
|
-
`Command '${command.id}' already exists and will be overwritten`
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
this.commands.set(command.id, command);
|
|
251
|
-
if (command.shortcuts) {
|
|
252
|
-
const shortcuts = Array.isArray(command.shortcuts) ? command.shortcuts : [command.shortcuts];
|
|
253
|
-
shortcuts.forEach((shortcut) => {
|
|
254
|
-
const normalized = this.normalizeShortcut(shortcut);
|
|
255
|
-
this.shortcutMap.set(normalized, command.id);
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
this.logger.debug("CommandsPlugin", "CommandRegistered", `Command '${command.id}' registered`);
|
|
259
|
-
}
|
|
260
|
-
unregisterCommand(commandId) {
|
|
261
|
-
const command = this.commands.get(commandId);
|
|
262
|
-
if (!command) return;
|
|
263
|
-
if (command.shortcuts) {
|
|
264
|
-
const shortcuts = Array.isArray(command.shortcuts) ? command.shortcuts : [command.shortcuts];
|
|
265
|
-
shortcuts.forEach((shortcut) => {
|
|
266
|
-
const normalized = this.normalizeShortcut(shortcut);
|
|
267
|
-
this.shortcutMap.delete(normalized);
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
this.commands.delete(commandId);
|
|
271
|
-
this.logger.debug(
|
|
272
|
-
"CommandsPlugin",
|
|
273
|
-
"CommandUnregistered",
|
|
274
|
-
`Command '${commandId}' unregistered`
|
|
275
|
-
);
|
|
276
|
-
}
|
|
277
|
-
// ─────────────────────────────────────────────────────────
|
|
278
|
-
// Shortcuts
|
|
279
|
-
// ─────────────────────────────────────────────────────────
|
|
280
|
-
getCommandByShortcut(shortcut) {
|
|
281
|
-
const normalized = this.normalizeShortcut(shortcut);
|
|
282
|
-
const commandId = this.shortcutMap.get(normalized);
|
|
283
|
-
return commandId ? this.commands.get(commandId) ?? null : null;
|
|
284
|
-
}
|
|
285
|
-
normalizeShortcut(shortcut) {
|
|
286
|
-
return shortcut.toLowerCase().split("+").sort().join("+");
|
|
287
|
-
}
|
|
288
|
-
// ─────────────────────────────────────────────────────────
|
|
289
|
-
// Query Methods
|
|
290
|
-
// ─────────────────────────────────────────────────────────
|
|
291
|
-
getAllCommands(documentId) {
|
|
292
|
-
const resolvedDocId = documentId ?? this.getActiveDocumentId();
|
|
293
|
-
return Array.from(this.commands.keys()).map((id) => this.resolve(id, resolvedDocId));
|
|
294
|
-
}
|
|
295
|
-
getCommandsByCategory(category, documentId) {
|
|
296
|
-
const resolvedDocId = documentId ?? this.getActiveDocumentId();
|
|
297
|
-
return Array.from(this.commands.values()).filter((cmd) => {
|
|
298
|
-
var _a;
|
|
299
|
-
return (_a = cmd.categories) == null ? void 0 : _a.includes(category);
|
|
300
|
-
}).map((cmd) => this.resolve(cmd.id, resolvedDocId));
|
|
301
|
-
}
|
|
302
|
-
// ─────────────────────────────────────────────────────────
|
|
303
|
-
// State Change Detection
|
|
304
|
-
// ─────────────────────────────────────────────────────────
|
|
305
|
-
onGlobalStoreChange(newState) {
|
|
306
|
-
const documentIds = Object.keys(newState.core.documents);
|
|
307
|
-
documentIds.forEach((documentId) => {
|
|
308
|
-
this.detectCommandChanges(documentId, newState);
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
detectCommandChanges(documentId, newState) {
|
|
312
|
-
const coreDoc = newState.core.documents[documentId];
|
|
313
|
-
if (!coreDoc || coreDoc.status !== "loaded") return;
|
|
314
|
-
const previousCache = this.previousStates.get(documentId) ?? /* @__PURE__ */ new Map();
|
|
315
|
-
this.commands.forEach((command, commandId) => {
|
|
316
|
-
const newResolved = this.resolve(commandId, documentId);
|
|
317
|
-
const prevResolved = previousCache.get(commandId);
|
|
318
|
-
if (!prevResolved) {
|
|
319
|
-
previousCache.set(commandId, newResolved);
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
const changes = {};
|
|
323
|
-
if (prevResolved.active !== newResolved.active) {
|
|
324
|
-
changes.active = newResolved.active;
|
|
325
|
-
}
|
|
326
|
-
if (prevResolved.disabled !== newResolved.disabled) {
|
|
327
|
-
changes.disabled = newResolved.disabled;
|
|
328
|
-
}
|
|
329
|
-
if (prevResolved.visible !== newResolved.visible) {
|
|
330
|
-
changes.visible = newResolved.visible;
|
|
331
|
-
}
|
|
332
|
-
if (prevResolved.label !== newResolved.label) {
|
|
333
|
-
changes.label = newResolved.label;
|
|
334
|
-
}
|
|
335
|
-
if (prevResolved.icon !== newResolved.icon) {
|
|
336
|
-
changes.icon = newResolved.icon;
|
|
337
|
-
}
|
|
338
|
-
if (!arePropsEqual(prevResolved.iconProps, newResolved.iconProps)) {
|
|
339
|
-
changes.iconProps = newResolved.iconProps;
|
|
340
|
-
}
|
|
341
|
-
if (Object.keys(changes).length > 0) {
|
|
342
|
-
previousCache.set(commandId, newResolved);
|
|
343
|
-
this.commandStateChanged$.emit({
|
|
344
|
-
commandId,
|
|
345
|
-
documentId,
|
|
346
|
-
changes
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
this.previousStates.set(documentId, previousCache);
|
|
351
|
-
}
|
|
164
|
+
const CommandsToken = createCapabilityToken("commands");
|
|
165
|
+
//#endregion
|
|
166
|
+
//#region src/commands.plugin.ts
|
|
167
|
+
/**
|
|
168
|
+
* The commands plugin: workspace-scoped (one vocabulary for the whole
|
|
169
|
+
* workspace; resolution/execution bind to a target document per call).
|
|
170
|
+
* Definitions live in this closure — never in the store (they hold
|
|
171
|
+
* functions); the store slice holds only `disabledCategories`.
|
|
172
|
+
*/
|
|
173
|
+
const commandsPlugin = (config) => {
|
|
174
|
+
const registry = /* @__PURE__ */ new Map();
|
|
175
|
+
for (const def of config?.commands ?? []) registerCommand(registry, def);
|
|
176
|
+
return definePlugin({
|
|
177
|
+
id: "commands",
|
|
178
|
+
scope: "workspace",
|
|
179
|
+
token: CommandsToken,
|
|
180
|
+
initialState: {
|
|
181
|
+
...initialCommandsState,
|
|
182
|
+
disabledCategories: [...config?.disabledCategories ?? []]
|
|
183
|
+
},
|
|
184
|
+
reduce: commandsReducer,
|
|
185
|
+
capability: (ctx) => createCommandsCapability(ctx, registry)
|
|
186
|
+
});
|
|
352
187
|
};
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
};
|
|
358
|
-
const commandsReducer = (state = initialState, action) => {
|
|
359
|
-
switch (action.type) {
|
|
360
|
-
case SET_DISABLED_CATEGORIES:
|
|
361
|
-
return {
|
|
362
|
-
...state,
|
|
363
|
-
disabledCategories: action.payload
|
|
364
|
-
};
|
|
365
|
-
default:
|
|
366
|
-
return state;
|
|
367
|
-
}
|
|
368
|
-
};
|
|
369
|
-
const CommandsPluginPackage = {
|
|
370
|
-
manifest,
|
|
371
|
-
create: (registry, config) => new CommandsPlugin(COMMANDS_PLUGIN_ID, registry, config),
|
|
372
|
-
reducer: commandsReducer,
|
|
373
|
-
initialState
|
|
374
|
-
};
|
|
375
|
-
export {
|
|
376
|
-
COMMANDS_PLUGIN_ID,
|
|
377
|
-
CommandsPlugin,
|
|
378
|
-
CommandsPluginPackage,
|
|
379
|
-
manifest
|
|
380
|
-
};
|
|
381
|
-
//# sourceMappingURL=index.js.map
|
|
188
|
+
//#endregion
|
|
189
|
+
export { CommandsToken, commandsPlugin, commandsReducer, initialCommandsState, resolvedCommandsEqual };
|
|
190
|
+
|
|
191
|
+
//# sourceMappingURL=index.js.map
|