@openvcs/sdk 0.2.11 → 0.2.12
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/lib/runtime/menu.d.ts +11 -11
- package/lib/runtime/menu.js +61 -97
- package/package.json +1 -1
- package/src/lib/runtime/menu.ts +65 -114
package/lib/runtime/menu.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ type MenubarMenuOptions = {
|
|
|
5
5
|
before?: string;
|
|
6
6
|
after?: string;
|
|
7
7
|
};
|
|
8
|
-
/** Runs
|
|
8
|
+
/** Runs a registered action handler by id. */
|
|
9
9
|
export declare function runRegisteredAction(actionId: string, ...args: unknown[]): Promise<boolean>;
|
|
10
10
|
export interface MenuHandle {
|
|
11
11
|
id: string;
|
|
@@ -17,26 +17,26 @@ export interface MenuHandle {
|
|
|
17
17
|
}
|
|
18
18
|
/** Returns a menu by id, or null when it does not exist. */
|
|
19
19
|
export declare function getMenu(menuId: string): MenuHandle | null;
|
|
20
|
-
/** Returns a menu by id, creating it
|
|
20
|
+
/** Returns a menu by id, creating it if needed. */
|
|
21
21
|
export declare function getOrCreateMenu(menuId: string, label: string): MenuHandle | null;
|
|
22
|
-
/** Creates a menu at a specific
|
|
22
|
+
/** Creates a menu at a specific position. */
|
|
23
23
|
export declare function createMenu(menuId: string, label: string, options?: MenubarMenuOptions): MenuHandle | null;
|
|
24
|
-
/** Adds one
|
|
24
|
+
/** Adds one item to a menu. */
|
|
25
25
|
export declare function addMenuItem(menuId: string, item: MenubarItem): void;
|
|
26
|
-
/** Adds one separator
|
|
26
|
+
/** Adds one separator to a menu. */
|
|
27
27
|
export declare function addMenuSeparator(menuId: string, beforeAction?: string): void;
|
|
28
|
-
/** Removes one menu
|
|
28
|
+
/** Removes one menu from the registry. */
|
|
29
29
|
export declare function removeMenu(menuId: string): void;
|
|
30
|
-
/** Hides one menu from the
|
|
30
|
+
/** Hides one menu from the registry. */
|
|
31
31
|
export declare function hideMenu(menuId: string): void;
|
|
32
|
-
/** Shows one
|
|
32
|
+
/** Shows one menu from the registry. */
|
|
33
33
|
export declare function showMenu(menuId: string): void;
|
|
34
34
|
/** Registers an action handler by id. */
|
|
35
35
|
export declare function registerAction(id: string, handler: (...args: unknown[]) => unknown): void;
|
|
36
|
-
/** Invokes a host command when
|
|
36
|
+
/** Invokes a host command when a host helper is available. */
|
|
37
37
|
export declare function invoke<T = unknown>(cmd: string, args?: unknown): Promise<T>;
|
|
38
|
-
/** Emits a
|
|
38
|
+
/** Emits a notification when the host helper is available. */
|
|
39
39
|
export declare function notify(msg: string): void;
|
|
40
|
-
/** Builds
|
|
40
|
+
/** Builds SDK delegates from the local menu/action registries. */
|
|
41
41
|
export declare function createMenuPluginDelegates(): PluginDelegates<PluginRuntimeContext>;
|
|
42
42
|
export {};
|
package/lib/runtime/menu.js
CHANGED
|
@@ -18,31 +18,35 @@ exports.createMenuPluginDelegates = createMenuPluginDelegates;
|
|
|
18
18
|
const menus = new Map();
|
|
19
19
|
const menuOrder = [];
|
|
20
20
|
const actionHandlers = new Map();
|
|
21
|
-
let
|
|
22
|
-
/** Returns the OpenVCS
|
|
21
|
+
let syntheticId = 0;
|
|
22
|
+
/** Returns the host-side OpenVCS helper, when the environment provides one. */
|
|
23
23
|
function getOpenVCS() {
|
|
24
24
|
return globalThis.OpenVCS;
|
|
25
25
|
}
|
|
26
|
-
/** Normalizes menu
|
|
26
|
+
/** Normalizes a menu id for stable map lookup. */
|
|
27
27
|
function normalizeMenuId(menuId) {
|
|
28
28
|
return String(menuId || '').trim();
|
|
29
29
|
}
|
|
30
|
-
/** Allocates a stable synthetic id for
|
|
30
|
+
/** Allocates a stable synthetic id for generated menu entries. */
|
|
31
31
|
function allocateSyntheticId(prefix) {
|
|
32
|
-
|
|
33
|
-
return `${prefix}-${
|
|
32
|
+
syntheticId += 1;
|
|
33
|
+
return `${prefix}-${syntheticId}`;
|
|
34
34
|
}
|
|
35
|
-
/** Returns
|
|
35
|
+
/** Returns the stored menu state for one id, if present. */
|
|
36
36
|
function getStoredMenu(menuId) {
|
|
37
37
|
return menus.get(normalizeMenuId(menuId)) || null;
|
|
38
38
|
}
|
|
39
|
-
/**
|
|
39
|
+
/** Removes one menu id from the ordering list. */
|
|
40
|
+
function removeMenuId(menuId) {
|
|
41
|
+
const id = normalizeMenuId(menuId);
|
|
42
|
+
const index = menuOrder.indexOf(id);
|
|
43
|
+
if (index >= 0)
|
|
44
|
+
menuOrder.splice(index, 1);
|
|
45
|
+
}
|
|
46
|
+
/** Inserts one menu id into the ordering list. */
|
|
40
47
|
function placeMenuId(menuId, options) {
|
|
41
48
|
const id = normalizeMenuId(menuId);
|
|
42
|
-
|
|
43
|
-
if (existingIndex >= 0) {
|
|
44
|
-
menuOrder.splice(existingIndex, 1);
|
|
45
|
-
}
|
|
49
|
+
removeMenuId(id);
|
|
46
50
|
const beforeId = normalizeMenuId(options?.before || '');
|
|
47
51
|
const afterId = normalizeMenuId(options?.after || '');
|
|
48
52
|
if (afterId) {
|
|
@@ -61,7 +65,7 @@ function placeMenuId(menuId, options) {
|
|
|
61
65
|
}
|
|
62
66
|
menuOrder.push(id);
|
|
63
67
|
}
|
|
64
|
-
/** Ensures a menu record exists for
|
|
68
|
+
/** Ensures a menu record exists for one id. */
|
|
65
69
|
function ensureStoredMenu(menuId, label, options) {
|
|
66
70
|
const id = normalizeMenuId(menuId);
|
|
67
71
|
const safeLabel = String(label || '').trim() || id;
|
|
@@ -69,46 +73,47 @@ function ensureStoredMenu(menuId, label, options) {
|
|
|
69
73
|
if (!menu) {
|
|
70
74
|
menu = { id, label: safeLabel, items: [] };
|
|
71
75
|
menus.set(id, menu);
|
|
72
|
-
placeMenuId(id, options);
|
|
73
|
-
return menu;
|
|
74
76
|
}
|
|
75
|
-
|
|
77
|
+
else {
|
|
78
|
+
menu.label = safeLabel;
|
|
79
|
+
}
|
|
76
80
|
placeMenuId(id, options);
|
|
77
81
|
return menu;
|
|
78
82
|
}
|
|
79
|
-
/** Finds
|
|
80
|
-
function
|
|
83
|
+
/** Finds a stored item by action id. */
|
|
84
|
+
function findStoredItem(menu, actionId) {
|
|
81
85
|
const id = normalizeMenuId(actionId);
|
|
82
86
|
return menu.items.find((item) => item.action === id) || null;
|
|
83
87
|
}
|
|
84
|
-
/** Inserts a menu item
|
|
88
|
+
/** Inserts a menu item at the requested position. */
|
|
85
89
|
function insertMenuItem(menu, item, before, after) {
|
|
86
90
|
const beforeId = normalizeMenuId(before || '');
|
|
87
91
|
const afterId = normalizeMenuId(after || '');
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
if (
|
|
91
|
-
menu.items.splice(
|
|
92
|
-
}
|
|
93
|
-
menu.items.splice(index, 0, item);
|
|
92
|
+
const removeExisting = () => {
|
|
93
|
+
const index = menu.items.findIndex((entry) => entry.id === item.id || entry.action === item.action);
|
|
94
|
+
if (index >= 0)
|
|
95
|
+
menu.items.splice(index, 1);
|
|
94
96
|
};
|
|
95
97
|
if (beforeId) {
|
|
96
98
|
const beforeIndex = menu.items.findIndex((entry) => entry.action === beforeId);
|
|
97
99
|
if (beforeIndex >= 0) {
|
|
98
|
-
|
|
100
|
+
removeExisting();
|
|
101
|
+
menu.items.splice(beforeIndex, 0, item);
|
|
99
102
|
return;
|
|
100
103
|
}
|
|
101
104
|
}
|
|
102
105
|
if (afterId) {
|
|
103
106
|
const afterIndex = menu.items.findIndex((entry) => entry.action === afterId);
|
|
104
107
|
if (afterIndex >= 0) {
|
|
105
|
-
|
|
108
|
+
removeExisting();
|
|
109
|
+
menu.items.splice(afterIndex + 1, 0, item);
|
|
106
110
|
return;
|
|
107
111
|
}
|
|
108
112
|
}
|
|
109
|
-
|
|
113
|
+
removeExisting();
|
|
114
|
+
menu.items.push(item);
|
|
110
115
|
}
|
|
111
|
-
/** Converts one stored
|
|
116
|
+
/** Converts one stored item into a serializable menu payload element. */
|
|
112
117
|
function serializeMenuItem(item) {
|
|
113
118
|
if (item.hidden)
|
|
114
119
|
return null;
|
|
@@ -132,7 +137,7 @@ function serializeMenuItem(item) {
|
|
|
132
137
|
label: item.label,
|
|
133
138
|
};
|
|
134
139
|
}
|
|
135
|
-
/** Serializes the
|
|
140
|
+
/** Serializes the local registry into plugin menu payloads. */
|
|
136
141
|
function serializeMenus() {
|
|
137
142
|
return menuOrder
|
|
138
143
|
.map((menuId, index) => {
|
|
@@ -150,7 +155,7 @@ function serializeMenus() {
|
|
|
150
155
|
})
|
|
151
156
|
.filter((menu) => Boolean(menu));
|
|
152
157
|
}
|
|
153
|
-
/** Runs
|
|
158
|
+
/** Runs a registered action handler by id. */
|
|
154
159
|
async function runRegisteredAction(actionId, ...args) {
|
|
155
160
|
const id = String(actionId || '').trim();
|
|
156
161
|
if (!id)
|
|
@@ -161,45 +166,33 @@ async function runRegisteredAction(actionId, ...args) {
|
|
|
161
166
|
await handler(...args);
|
|
162
167
|
return true;
|
|
163
168
|
}
|
|
164
|
-
/** Creates a stable
|
|
169
|
+
/** Creates a stable handle for one stored menu. */
|
|
165
170
|
function createMenuHandle(menuId) {
|
|
166
171
|
const id = normalizeMenuId(menuId);
|
|
167
172
|
return {
|
|
168
173
|
id,
|
|
169
174
|
addItem(item) {
|
|
170
|
-
const menu = getStoredMenu(this.id) || ensureStoredMenu(this.id, this.id);
|
|
171
175
|
const label = String(item?.label || '').trim();
|
|
172
176
|
const action = String(item?.action || '').trim();
|
|
173
177
|
if (!label || !action)
|
|
174
178
|
return;
|
|
175
|
-
const
|
|
179
|
+
const menu = getStoredMenu(this.id) || ensureStoredMenu(this.id, this.id);
|
|
180
|
+
insertMenuItem(menu, {
|
|
176
181
|
kind: 'button',
|
|
177
182
|
id: action,
|
|
178
183
|
label,
|
|
179
184
|
title: item.title,
|
|
180
185
|
action,
|
|
181
|
-
};
|
|
182
|
-
insertMenuItem(menu, entry, item.before, item.after);
|
|
183
|
-
const openvcs = getOpenVCS();
|
|
184
|
-
if (!openvcs?.menus)
|
|
185
|
-
return;
|
|
186
|
-
openvcs.menus.getOrCreate(menu.id, menu.label);
|
|
187
|
-
openvcs.menus.addMenuItem(menu.id, item);
|
|
186
|
+
}, item.before, item.after);
|
|
188
187
|
},
|
|
189
188
|
addSeparator(beforeAction) {
|
|
190
189
|
const menu = getStoredMenu(this.id) || ensureStoredMenu(this.id, this.id);
|
|
191
|
-
|
|
190
|
+
insertMenuItem(menu, {
|
|
192
191
|
kind: 'separator',
|
|
193
192
|
id: allocateSyntheticId(`${menu.id}-separator`),
|
|
194
193
|
label: 'Separator',
|
|
195
194
|
content: '—',
|
|
196
|
-
};
|
|
197
|
-
insertMenuItem(menu, entry, beforeAction);
|
|
198
|
-
const openvcs = getOpenVCS();
|
|
199
|
-
if (!openvcs?.menus)
|
|
200
|
-
return;
|
|
201
|
-
openvcs.menus.getOrCreate(menu.id, menu.label);
|
|
202
|
-
openvcs.menus.addMenuSeparator(menu.id, beforeAction);
|
|
195
|
+
}, beforeAction);
|
|
203
196
|
},
|
|
204
197
|
removeItem(actionId) {
|
|
205
198
|
const menu = getStoredMenu(this.id);
|
|
@@ -207,37 +200,22 @@ function createMenuHandle(menuId) {
|
|
|
207
200
|
return;
|
|
208
201
|
const idToRemove = normalizeMenuId(actionId);
|
|
209
202
|
menu.items = menu.items.filter((item) => item.action !== idToRemove);
|
|
210
|
-
const openvcs = getOpenVCS();
|
|
211
|
-
if (openvcs?.menus) {
|
|
212
|
-
const handle = openvcs.menus.get(this.id);
|
|
213
|
-
handle?.removeItem(idToRemove);
|
|
214
|
-
}
|
|
215
203
|
},
|
|
216
204
|
hideItem(actionId) {
|
|
217
205
|
const menu = getStoredMenu(this.id);
|
|
218
206
|
if (!menu)
|
|
219
207
|
return;
|
|
220
|
-
const item =
|
|
208
|
+
const item = findStoredItem(menu, actionId);
|
|
221
209
|
if (item)
|
|
222
210
|
item.hidden = true;
|
|
223
|
-
const openvcs = getOpenVCS();
|
|
224
|
-
if (!openvcs?.menus)
|
|
225
|
-
return;
|
|
226
|
-
const handle = openvcs.menus.get(this.id);
|
|
227
|
-
handle?.hideItem(actionId);
|
|
228
211
|
},
|
|
229
212
|
showItem(actionId) {
|
|
230
213
|
const menu = getStoredMenu(this.id);
|
|
231
214
|
if (!menu)
|
|
232
215
|
return;
|
|
233
|
-
const item =
|
|
216
|
+
const item = findStoredItem(menu, actionId);
|
|
234
217
|
if (item)
|
|
235
218
|
item.hidden = false;
|
|
236
|
-
const openvcs = getOpenVCS();
|
|
237
|
-
if (!openvcs?.menus)
|
|
238
|
-
return;
|
|
239
|
-
const handle = openvcs.menus.get(this.id);
|
|
240
|
-
handle?.showItem(actionId);
|
|
241
219
|
},
|
|
242
220
|
};
|
|
243
221
|
}
|
|
@@ -248,53 +226,41 @@ function getMenu(menuId) {
|
|
|
248
226
|
return null;
|
|
249
227
|
return createMenuHandle(stored.id);
|
|
250
228
|
}
|
|
251
|
-
/** Returns a menu by id, creating it
|
|
229
|
+
/** Returns a menu by id, creating it if needed. */
|
|
252
230
|
function getOrCreateMenu(menuId, label) {
|
|
253
231
|
const stored = ensureStoredMenu(menuId, label);
|
|
254
232
|
return createMenuHandle(stored.id);
|
|
255
233
|
}
|
|
256
|
-
/** Creates a menu at a specific
|
|
234
|
+
/** Creates a menu at a specific position. */
|
|
257
235
|
function createMenu(menuId, label, options) {
|
|
258
236
|
const stored = ensureStoredMenu(menuId, label, options);
|
|
259
237
|
return createMenuHandle(stored.id);
|
|
260
238
|
}
|
|
261
|
-
/** Adds one
|
|
239
|
+
/** Adds one item to a menu. */
|
|
262
240
|
function addMenuItem(menuId, item) {
|
|
263
|
-
|
|
264
|
-
const handle = createMenuHandle(menu.id);
|
|
265
|
-
handle.addItem(item);
|
|
241
|
+
createMenuHandle(menuId).addItem(item);
|
|
266
242
|
}
|
|
267
|
-
/** Adds one separator
|
|
243
|
+
/** Adds one separator to a menu. */
|
|
268
244
|
function addMenuSeparator(menuId, beforeAction) {
|
|
269
|
-
|
|
270
|
-
const handle = createMenuHandle(menu.id);
|
|
271
|
-
handle.addSeparator(beforeAction);
|
|
245
|
+
createMenuHandle(menuId).addSeparator(beforeAction);
|
|
272
246
|
}
|
|
273
|
-
/** Removes one menu
|
|
247
|
+
/** Removes one menu from the registry. */
|
|
274
248
|
function removeMenu(menuId) {
|
|
275
249
|
const id = normalizeMenuId(menuId);
|
|
276
250
|
menus.delete(id);
|
|
277
|
-
|
|
278
|
-
if (index >= 0)
|
|
279
|
-
menuOrder.splice(index, 1);
|
|
280
|
-
const openvcs = getOpenVCS();
|
|
281
|
-
openvcs?.menus?.remove(id);
|
|
251
|
+
removeMenuId(id);
|
|
282
252
|
}
|
|
283
|
-
/** Hides one menu from the
|
|
253
|
+
/** Hides one menu from the registry. */
|
|
284
254
|
function hideMenu(menuId) {
|
|
285
255
|
const menu = getStoredMenu(menuId);
|
|
286
256
|
if (menu)
|
|
287
257
|
menu.hidden = true;
|
|
288
|
-
const openvcs = getOpenVCS();
|
|
289
|
-
openvcs?.menus?.hide(normalizeMenuId(menuId));
|
|
290
258
|
}
|
|
291
|
-
/** Shows one
|
|
259
|
+
/** Shows one menu from the registry. */
|
|
292
260
|
function showMenu(menuId) {
|
|
293
261
|
const menu = getStoredMenu(menuId);
|
|
294
262
|
if (menu)
|
|
295
263
|
menu.hidden = false;
|
|
296
|
-
const openvcs = getOpenVCS();
|
|
297
|
-
openvcs?.menus?.show(normalizeMenuId(menuId));
|
|
298
264
|
}
|
|
299
265
|
/** Registers an action handler by id. */
|
|
300
266
|
function registerAction(id, handler) {
|
|
@@ -302,23 +268,21 @@ function registerAction(id, handler) {
|
|
|
302
268
|
if (!key)
|
|
303
269
|
return;
|
|
304
270
|
actionHandlers.set(key, handler);
|
|
305
|
-
const openvcs = getOpenVCS();
|
|
306
|
-
openvcs?.registerAction(key, handler);
|
|
307
271
|
}
|
|
308
|
-
/** Invokes a host command when
|
|
272
|
+
/** Invokes a host command when a host helper is available. */
|
|
309
273
|
function invoke(cmd, args) {
|
|
310
274
|
const openvcs = getOpenVCS();
|
|
311
275
|
if (!openvcs) {
|
|
312
|
-
return Promise.reject(new Error('OpenVCS not available'));
|
|
276
|
+
return Promise.reject(new Error('OpenVCS host is not available in this runtime'));
|
|
313
277
|
}
|
|
314
278
|
return openvcs.invoke(cmd, args);
|
|
315
279
|
}
|
|
316
|
-
/** Emits a
|
|
280
|
+
/** Emits a notification when the host helper is available. */
|
|
317
281
|
function notify(msg) {
|
|
318
282
|
const openvcs = getOpenVCS();
|
|
319
283
|
openvcs?.notify(msg);
|
|
320
284
|
}
|
|
321
|
-
/** Builds
|
|
285
|
+
/** Builds SDK delegates from the local menu/action registries. */
|
|
322
286
|
function createMenuPluginDelegates() {
|
|
323
287
|
return {
|
|
324
288
|
async 'plugin.get_menus'() {
|
|
@@ -326,9 +290,9 @@ function createMenuPluginDelegates() {
|
|
|
326
290
|
},
|
|
327
291
|
async 'plugin.handle_action'(params) {
|
|
328
292
|
const actionId = String(params?.action_id || '').trim();
|
|
329
|
-
if (
|
|
330
|
-
|
|
331
|
-
|
|
293
|
+
if (actionId) {
|
|
294
|
+
await runRegisteredAction(actionId);
|
|
295
|
+
}
|
|
332
296
|
return null;
|
|
333
297
|
},
|
|
334
298
|
};
|
package/package.json
CHANGED
package/src/lib/runtime/menu.ts
CHANGED
|
@@ -11,22 +11,10 @@ import type {
|
|
|
11
11
|
import type { PluginRuntimeContext } from './contracts.js';
|
|
12
12
|
|
|
13
13
|
type MenubarMenuOptions = { before?: string; after?: string };
|
|
14
|
-
|
|
15
14
|
type MenuEntryKind = 'button' | 'text' | 'separator';
|
|
16
15
|
|
|
17
16
|
type OpenVCSGlobal = typeof globalThis & {
|
|
18
17
|
OpenVCS?: {
|
|
19
|
-
menus?: {
|
|
20
|
-
get(menuId: string): unknown;
|
|
21
|
-
getOrCreate(menuId: string, label: string): unknown;
|
|
22
|
-
create(menuId: string, label: string, options?: MenubarMenuOptions): unknown;
|
|
23
|
-
addMenuItem(menuId: string, item: MenubarItem): void;
|
|
24
|
-
addMenuSeparator(menuId: string, beforeAction?: string): void;
|
|
25
|
-
remove(menuId: string): void;
|
|
26
|
-
hide(menuId: string): void;
|
|
27
|
-
show(menuId: string): void;
|
|
28
|
-
};
|
|
29
|
-
registerAction(id: string, handler: (...args: unknown[]) => unknown): void;
|
|
30
18
|
invoke<T = unknown>(cmd: string, args?: unknown): Promise<T>;
|
|
31
19
|
notify(msg: string): void;
|
|
32
20
|
};
|
|
@@ -66,36 +54,40 @@ interface SerializedMenuDefinition {
|
|
|
66
54
|
const menus = new Map<string, StoredMenuState>();
|
|
67
55
|
const menuOrder: string[] = [];
|
|
68
56
|
const actionHandlers = new Map<string, (...args: unknown[]) => unknown>();
|
|
69
|
-
let
|
|
57
|
+
let syntheticId = 0;
|
|
70
58
|
|
|
71
|
-
/** Returns the OpenVCS
|
|
59
|
+
/** Returns the host-side OpenVCS helper, when the environment provides one. */
|
|
72
60
|
function getOpenVCS() {
|
|
73
61
|
return (globalThis as OpenVCSGlobal).OpenVCS;
|
|
74
62
|
}
|
|
75
63
|
|
|
76
|
-
/** Normalizes menu
|
|
64
|
+
/** Normalizes a menu id for stable map lookup. */
|
|
77
65
|
function normalizeMenuId(menuId: string): string {
|
|
78
66
|
return String(menuId || '').trim();
|
|
79
67
|
}
|
|
80
68
|
|
|
81
|
-
/** Allocates a stable synthetic id for
|
|
69
|
+
/** Allocates a stable synthetic id for generated menu entries. */
|
|
82
70
|
function allocateSyntheticId(prefix: string): string {
|
|
83
|
-
|
|
84
|
-
return `${prefix}-${
|
|
71
|
+
syntheticId += 1;
|
|
72
|
+
return `${prefix}-${syntheticId}`;
|
|
85
73
|
}
|
|
86
74
|
|
|
87
|
-
/** Returns
|
|
75
|
+
/** Returns the stored menu state for one id, if present. */
|
|
88
76
|
function getStoredMenu(menuId: string): StoredMenuState | null {
|
|
89
77
|
return menus.get(normalizeMenuId(menuId)) || null;
|
|
90
78
|
}
|
|
91
79
|
|
|
92
|
-
/**
|
|
80
|
+
/** Removes one menu id from the ordering list. */
|
|
81
|
+
function removeMenuId(menuId: string): void {
|
|
82
|
+
const id = normalizeMenuId(menuId);
|
|
83
|
+
const index = menuOrder.indexOf(id);
|
|
84
|
+
if (index >= 0) menuOrder.splice(index, 1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Inserts one menu id into the ordering list. */
|
|
93
88
|
function placeMenuId(menuId: string, options?: MenubarMenuOptions): void {
|
|
94
89
|
const id = normalizeMenuId(menuId);
|
|
95
|
-
|
|
96
|
-
if (existingIndex >= 0) {
|
|
97
|
-
menuOrder.splice(existingIndex, 1);
|
|
98
|
-
}
|
|
90
|
+
removeMenuId(id);
|
|
99
91
|
|
|
100
92
|
const beforeId = normalizeMenuId(options?.before || '');
|
|
101
93
|
const afterId = normalizeMenuId(options?.after || '');
|
|
@@ -119,8 +111,12 @@ function placeMenuId(menuId: string, options?: MenubarMenuOptions): void {
|
|
|
119
111
|
menuOrder.push(id);
|
|
120
112
|
}
|
|
121
113
|
|
|
122
|
-
/** Ensures a menu record exists for
|
|
123
|
-
function ensureStoredMenu(
|
|
114
|
+
/** Ensures a menu record exists for one id. */
|
|
115
|
+
function ensureStoredMenu(
|
|
116
|
+
menuId: string,
|
|
117
|
+
label: string,
|
|
118
|
+
options?: MenubarMenuOptions,
|
|
119
|
+
): StoredMenuState {
|
|
124
120
|
const id = normalizeMenuId(menuId);
|
|
125
121
|
const safeLabel = String(label || '').trim() || id;
|
|
126
122
|
let menu = menus.get(id);
|
|
@@ -128,38 +124,35 @@ function ensureStoredMenu(menuId: string, label: string, options?: MenubarMenuOp
|
|
|
128
124
|
if (!menu) {
|
|
129
125
|
menu = { id, label: safeLabel, items: [] };
|
|
130
126
|
menus.set(id, menu);
|
|
131
|
-
|
|
132
|
-
|
|
127
|
+
} else {
|
|
128
|
+
menu.label = safeLabel;
|
|
133
129
|
}
|
|
134
130
|
|
|
135
|
-
menu.label = safeLabel;
|
|
136
131
|
placeMenuId(id, options);
|
|
137
132
|
return menu;
|
|
138
133
|
}
|
|
139
134
|
|
|
140
|
-
/** Finds
|
|
141
|
-
function
|
|
135
|
+
/** Finds a stored item by action id. */
|
|
136
|
+
function findStoredItem(menu: StoredMenuState, actionId: string): StoredMenuItem | null {
|
|
142
137
|
const id = normalizeMenuId(actionId);
|
|
143
138
|
return menu.items.find((item) => item.action === id) || null;
|
|
144
139
|
}
|
|
145
140
|
|
|
146
|
-
/** Inserts a menu item
|
|
141
|
+
/** Inserts a menu item at the requested position. */
|
|
147
142
|
function insertMenuItem(menu: StoredMenuState, item: StoredMenuItem, before?: string, after?: string): void {
|
|
148
143
|
const beforeId = normalizeMenuId(before || '');
|
|
149
144
|
const afterId = normalizeMenuId(after || '');
|
|
150
145
|
|
|
151
|
-
const
|
|
152
|
-
const
|
|
153
|
-
if (
|
|
154
|
-
menu.items.splice(existingIndex, 1);
|
|
155
|
-
}
|
|
156
|
-
menu.items.splice(index, 0, item);
|
|
146
|
+
const removeExisting = () => {
|
|
147
|
+
const index = menu.items.findIndex((entry) => entry.id === item.id || entry.action === item.action);
|
|
148
|
+
if (index >= 0) menu.items.splice(index, 1);
|
|
157
149
|
};
|
|
158
150
|
|
|
159
151
|
if (beforeId) {
|
|
160
152
|
const beforeIndex = menu.items.findIndex((entry) => entry.action === beforeId);
|
|
161
153
|
if (beforeIndex >= 0) {
|
|
162
|
-
|
|
154
|
+
removeExisting();
|
|
155
|
+
menu.items.splice(beforeIndex, 0, item);
|
|
163
156
|
return;
|
|
164
157
|
}
|
|
165
158
|
}
|
|
@@ -167,15 +160,17 @@ function insertMenuItem(menu: StoredMenuState, item: StoredMenuItem, before?: st
|
|
|
167
160
|
if (afterId) {
|
|
168
161
|
const afterIndex = menu.items.findIndex((entry) => entry.action === afterId);
|
|
169
162
|
if (afterIndex >= 0) {
|
|
170
|
-
|
|
163
|
+
removeExisting();
|
|
164
|
+
menu.items.splice(afterIndex + 1, 0, item);
|
|
171
165
|
return;
|
|
172
166
|
}
|
|
173
167
|
}
|
|
174
168
|
|
|
175
|
-
|
|
169
|
+
removeExisting();
|
|
170
|
+
menu.items.push(item);
|
|
176
171
|
}
|
|
177
172
|
|
|
178
|
-
/** Converts one stored
|
|
173
|
+
/** Converts one stored item into a serializable menu payload element. */
|
|
179
174
|
function serializeMenuItem(item: StoredMenuItem): SerializedMenuItem | null {
|
|
180
175
|
if (item.hidden) return null;
|
|
181
176
|
|
|
@@ -202,7 +197,7 @@ function serializeMenuItem(item: StoredMenuItem): SerializedMenuItem | null {
|
|
|
202
197
|
};
|
|
203
198
|
}
|
|
204
199
|
|
|
205
|
-
/** Serializes the
|
|
200
|
+
/** Serializes the local registry into plugin menu payloads. */
|
|
206
201
|
function serializeMenus(): SerializedMenuDefinition[] {
|
|
207
202
|
return menuOrder
|
|
208
203
|
.map((menuId, index) => {
|
|
@@ -214,13 +209,13 @@ function serializeMenus(): SerializedMenuDefinition[] {
|
|
|
214
209
|
order: index + 1,
|
|
215
210
|
elements: menu.items
|
|
216
211
|
.map((item) => serializeMenuItem(item))
|
|
217
|
-
|
|
212
|
+
.filter((item): item is SerializedMenuItem => Boolean(item)),
|
|
218
213
|
};
|
|
219
214
|
})
|
|
220
215
|
.filter((menu): menu is SerializedMenuDefinition => Boolean(menu));
|
|
221
216
|
}
|
|
222
217
|
|
|
223
|
-
/** Runs
|
|
218
|
+
/** Runs a registered action handler by id. */
|
|
224
219
|
export async function runRegisteredAction(actionId: string, ...args: unknown[]): Promise<boolean> {
|
|
225
220
|
const id = String(actionId || '').trim();
|
|
226
221
|
if (!id) return false;
|
|
@@ -241,80 +236,52 @@ export interface MenuHandle {
|
|
|
241
236
|
showItem(actionId: string): void;
|
|
242
237
|
}
|
|
243
238
|
|
|
244
|
-
/** Creates a stable
|
|
239
|
+
/** Creates a stable handle for one stored menu. */
|
|
245
240
|
function createMenuHandle(menuId: string): MenuHandle {
|
|
246
241
|
const id = normalizeMenuId(menuId);
|
|
247
242
|
|
|
248
243
|
return {
|
|
249
244
|
id,
|
|
250
245
|
addItem(item: MenubarItem) {
|
|
251
|
-
const menu = getStoredMenu(this.id) || ensureStoredMenu(this.id, this.id);
|
|
252
246
|
const label = String(item?.label || '').trim();
|
|
253
247
|
const action = String(item?.action || '').trim();
|
|
254
248
|
if (!label || !action) return;
|
|
255
249
|
|
|
256
|
-
const
|
|
250
|
+
const menu = getStoredMenu(this.id) || ensureStoredMenu(this.id, this.id);
|
|
251
|
+
insertMenuItem(menu, {
|
|
257
252
|
kind: 'button',
|
|
258
253
|
id: action,
|
|
259
254
|
label,
|
|
260
255
|
title: item.title,
|
|
261
256
|
action,
|
|
262
|
-
};
|
|
263
|
-
insertMenuItem(menu, entry, item.before, item.after);
|
|
264
|
-
|
|
265
|
-
const openvcs = getOpenVCS();
|
|
266
|
-
if (!openvcs?.menus) return;
|
|
267
|
-
openvcs.menus.getOrCreate(menu.id, menu.label);
|
|
268
|
-
openvcs.menus.addMenuItem(menu.id, item);
|
|
257
|
+
}, item.before, item.after);
|
|
269
258
|
},
|
|
270
259
|
addSeparator(beforeAction?: string) {
|
|
271
260
|
const menu = getStoredMenu(this.id) || ensureStoredMenu(this.id, this.id);
|
|
272
|
-
|
|
261
|
+
insertMenuItem(menu, {
|
|
273
262
|
kind: 'separator',
|
|
274
263
|
id: allocateSyntheticId(`${menu.id}-separator`),
|
|
275
264
|
label: 'Separator',
|
|
276
265
|
content: '—',
|
|
277
|
-
};
|
|
278
|
-
insertMenuItem(menu, entry, beforeAction);
|
|
279
|
-
|
|
280
|
-
const openvcs = getOpenVCS();
|
|
281
|
-
if (!openvcs?.menus) return;
|
|
282
|
-
openvcs.menus.getOrCreate(menu.id, menu.label);
|
|
283
|
-
openvcs.menus.addMenuSeparator(menu.id, beforeAction);
|
|
266
|
+
}, beforeAction);
|
|
284
267
|
},
|
|
285
268
|
removeItem(actionId: string) {
|
|
286
269
|
const menu = getStoredMenu(this.id);
|
|
287
270
|
if (!menu) return;
|
|
288
271
|
const idToRemove = normalizeMenuId(actionId);
|
|
289
272
|
menu.items = menu.items.filter((item) => item.action !== idToRemove);
|
|
290
|
-
|
|
291
|
-
const openvcs = getOpenVCS();
|
|
292
|
-
if (openvcs?.menus) {
|
|
293
|
-
const handle = openvcs.menus.get(this.id) as MenuHandle | null;
|
|
294
|
-
handle?.removeItem(idToRemove);
|
|
295
|
-
}
|
|
296
273
|
},
|
|
297
274
|
hideItem(actionId: string) {
|
|
298
275
|
const menu = getStoredMenu(this.id);
|
|
299
276
|
if (!menu) return;
|
|
300
|
-
const item =
|
|
277
|
+
const item = findStoredItem(menu, actionId);
|
|
301
278
|
if (item) item.hidden = true;
|
|
302
|
-
|
|
303
|
-
const openvcs = getOpenVCS();
|
|
304
|
-
if (!openvcs?.menus) return;
|
|
305
|
-
const handle = openvcs.menus.get(this.id) as MenuHandle | null;
|
|
306
|
-
handle?.hideItem(actionId);
|
|
307
279
|
},
|
|
308
280
|
showItem(actionId: string) {
|
|
309
281
|
const menu = getStoredMenu(this.id);
|
|
310
282
|
if (!menu) return;
|
|
311
|
-
const item =
|
|
283
|
+
const item = findStoredItem(menu, actionId);
|
|
312
284
|
if (item) item.hidden = false;
|
|
313
|
-
|
|
314
|
-
const openvcs = getOpenVCS();
|
|
315
|
-
if (!openvcs?.menus) return;
|
|
316
|
-
const handle = openvcs.menus.get(this.id) as MenuHandle | null;
|
|
317
|
-
handle?.showItem(actionId);
|
|
318
285
|
},
|
|
319
286
|
};
|
|
320
287
|
}
|
|
@@ -326,59 +293,45 @@ export function getMenu(menuId: string): MenuHandle | null {
|
|
|
326
293
|
return createMenuHandle(stored.id);
|
|
327
294
|
}
|
|
328
295
|
|
|
329
|
-
/** Returns a menu by id, creating it
|
|
296
|
+
/** Returns a menu by id, creating it if needed. */
|
|
330
297
|
export function getOrCreateMenu(menuId: string, label: string): MenuHandle | null {
|
|
331
298
|
const stored = ensureStoredMenu(menuId, label);
|
|
332
299
|
return createMenuHandle(stored.id);
|
|
333
300
|
}
|
|
334
301
|
|
|
335
|
-
/** Creates a menu at a specific
|
|
302
|
+
/** Creates a menu at a specific position. */
|
|
336
303
|
export function createMenu(menuId: string, label: string, options?: MenubarMenuOptions): MenuHandle | null {
|
|
337
304
|
const stored = ensureStoredMenu(menuId, label, options);
|
|
338
305
|
return createMenuHandle(stored.id);
|
|
339
306
|
}
|
|
340
307
|
|
|
341
|
-
/** Adds one
|
|
308
|
+
/** Adds one item to a menu. */
|
|
342
309
|
export function addMenuItem(menuId: string, item: MenubarItem): void {
|
|
343
|
-
|
|
344
|
-
const handle = createMenuHandle(menu.id);
|
|
345
|
-
handle.addItem(item);
|
|
310
|
+
createMenuHandle(menuId).addItem(item);
|
|
346
311
|
}
|
|
347
312
|
|
|
348
|
-
/** Adds one separator
|
|
313
|
+
/** Adds one separator to a menu. */
|
|
349
314
|
export function addMenuSeparator(menuId: string, beforeAction?: string): void {
|
|
350
|
-
|
|
351
|
-
const handle = createMenuHandle(menu.id);
|
|
352
|
-
handle.addSeparator(beforeAction);
|
|
315
|
+
createMenuHandle(menuId).addSeparator(beforeAction);
|
|
353
316
|
}
|
|
354
317
|
|
|
355
|
-
/** Removes one menu
|
|
318
|
+
/** Removes one menu from the registry. */
|
|
356
319
|
export function removeMenu(menuId: string): void {
|
|
357
320
|
const id = normalizeMenuId(menuId);
|
|
358
321
|
menus.delete(id);
|
|
359
|
-
|
|
360
|
-
if (index >= 0) menuOrder.splice(index, 1);
|
|
361
|
-
|
|
362
|
-
const openvcs = getOpenVCS();
|
|
363
|
-
openvcs?.menus?.remove(id);
|
|
322
|
+
removeMenuId(id);
|
|
364
323
|
}
|
|
365
324
|
|
|
366
|
-
/** Hides one menu from the
|
|
325
|
+
/** Hides one menu from the registry. */
|
|
367
326
|
export function hideMenu(menuId: string): void {
|
|
368
327
|
const menu = getStoredMenu(menuId);
|
|
369
328
|
if (menu) menu.hidden = true;
|
|
370
|
-
|
|
371
|
-
const openvcs = getOpenVCS();
|
|
372
|
-
openvcs?.menus?.hide(normalizeMenuId(menuId));
|
|
373
329
|
}
|
|
374
330
|
|
|
375
|
-
/** Shows one
|
|
331
|
+
/** Shows one menu from the registry. */
|
|
376
332
|
export function showMenu(menuId: string): void {
|
|
377
333
|
const menu = getStoredMenu(menuId);
|
|
378
334
|
if (menu) menu.hidden = false;
|
|
379
|
-
|
|
380
|
-
const openvcs = getOpenVCS();
|
|
381
|
-
openvcs?.menus?.show(normalizeMenuId(menuId));
|
|
382
335
|
}
|
|
383
336
|
|
|
384
337
|
/** Registers an action handler by id. */
|
|
@@ -386,27 +339,24 @@ export function registerAction(id: string, handler: (...args: unknown[]) => unkn
|
|
|
386
339
|
const key = String(id || '').trim();
|
|
387
340
|
if (!key) return;
|
|
388
341
|
actionHandlers.set(key, handler);
|
|
389
|
-
|
|
390
|
-
const openvcs = getOpenVCS();
|
|
391
|
-
openvcs?.registerAction(key, handler);
|
|
392
342
|
}
|
|
393
343
|
|
|
394
|
-
/** Invokes a host command when
|
|
344
|
+
/** Invokes a host command when a host helper is available. */
|
|
395
345
|
export function invoke<T = unknown>(cmd: string, args?: unknown): Promise<T> {
|
|
396
346
|
const openvcs = getOpenVCS();
|
|
397
347
|
if (!openvcs) {
|
|
398
|
-
return Promise.reject(new Error('OpenVCS not available'));
|
|
348
|
+
return Promise.reject(new Error('OpenVCS host is not available in this runtime'));
|
|
399
349
|
}
|
|
400
350
|
return openvcs.invoke(cmd, args);
|
|
401
351
|
}
|
|
402
352
|
|
|
403
|
-
/** Emits a
|
|
353
|
+
/** Emits a notification when the host helper is available. */
|
|
404
354
|
export function notify(msg: string): void {
|
|
405
355
|
const openvcs = getOpenVCS();
|
|
406
356
|
openvcs?.notify(msg);
|
|
407
357
|
}
|
|
408
358
|
|
|
409
|
-
/** Builds
|
|
359
|
+
/** Builds SDK delegates from the local menu/action registries. */
|
|
410
360
|
export function createMenuPluginDelegates(): PluginDelegates<PluginRuntimeContext> {
|
|
411
361
|
return {
|
|
412
362
|
async 'plugin.get_menus'(): Promise<PluginMenuDefinition[]> {
|
|
@@ -414,8 +364,9 @@ export function createMenuPluginDelegates(): PluginDelegates<PluginRuntimeContex
|
|
|
414
364
|
},
|
|
415
365
|
async 'plugin.handle_action'(params: PluginHandleActionParams): Promise<null> {
|
|
416
366
|
const actionId = String(params?.action_id || '').trim();
|
|
417
|
-
if (
|
|
418
|
-
|
|
367
|
+
if (actionId) {
|
|
368
|
+
await runRegisteredAction(actionId);
|
|
369
|
+
}
|
|
419
370
|
return null;
|
|
420
371
|
},
|
|
421
372
|
};
|