@openvcs/sdk 0.2.11 → 0.2.13
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/index.d.ts +1 -0
- package/lib/runtime/index.js +3 -1
- package/lib/runtime/menu.d.ts +11 -11
- package/lib/runtime/menu.js +61 -97
- package/lib/runtime/modal.d.ts +74 -0
- package/lib/runtime/modal.js +95 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +1 -0
- package/lib/types/modal.d.ts +129 -0
- package/lib/types/modal.js +4 -0
- package/lib/types/plugin.d.ts +2 -0
- package/package.json +1 -1
- package/src/lib/runtime/index.ts +1 -0
- package/src/lib/runtime/menu.ts +65 -114
- package/src/lib/runtime/modal.ts +172 -0
- package/src/lib/types/index.ts +1 -0
- package/src/lib/types/modal.ts +152 -0
- package/src/lib/types/plugin.ts +2 -0
- package/test/modal.test.ts +24 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/** Describes horizontal alignment hints for modal content. */
|
|
2
|
+
export type ModalContentAlign = 'left' | 'centered' | 'right';
|
|
3
|
+
/** Describes visual emphasis for modal buttons. */
|
|
4
|
+
export type ModalButtonVariant = 'default' | 'primary' | 'danger';
|
|
5
|
+
/** Describes the text input kinds supported by plugin modals. */
|
|
6
|
+
export type ModalInputKind = 'text' | 'search' | 'password' | 'url' | 'number';
|
|
7
|
+
/** Describes one button option rendered inside a modal. */
|
|
8
|
+
export interface ModalButtonDefinition {
|
|
9
|
+
/** Stores the action id triggered when the button is pressed. */
|
|
10
|
+
id: string;
|
|
11
|
+
/** Stores the button label. */
|
|
12
|
+
content: string;
|
|
13
|
+
/** Stores the optional tooltip text. */
|
|
14
|
+
title?: string;
|
|
15
|
+
/** Stores the visual button variant. */
|
|
16
|
+
variant?: ModalButtonVariant;
|
|
17
|
+
/** Stores the button alignment hint. */
|
|
18
|
+
align?: ModalContentAlign;
|
|
19
|
+
/** Stores a static payload merged into the action payload. */
|
|
20
|
+
payload?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
/** Describes one text block rendered inside a modal. */
|
|
23
|
+
export interface ModalTextDefinition {
|
|
24
|
+
/** Always stores `text`. */
|
|
25
|
+
type: 'text';
|
|
26
|
+
/** Stores the block text. */
|
|
27
|
+
content: string;
|
|
28
|
+
/** Stores an optional tooltip. */
|
|
29
|
+
title?: string;
|
|
30
|
+
/** Stores the alignment hint. */
|
|
31
|
+
align?: ModalContentAlign;
|
|
32
|
+
}
|
|
33
|
+
/** Describes one separator rendered inside a modal. */
|
|
34
|
+
export interface ModalSeparatorDefinition {
|
|
35
|
+
/** Always stores `separator`. */
|
|
36
|
+
type: 'separator';
|
|
37
|
+
}
|
|
38
|
+
/** Describes one button rendered inside a modal body. */
|
|
39
|
+
export interface ModalButtonItemDefinition extends ModalButtonDefinition {
|
|
40
|
+
/** Always stores `button`. */
|
|
41
|
+
type: 'button';
|
|
42
|
+
}
|
|
43
|
+
/** Describes one text input rendered inside a modal. */
|
|
44
|
+
export interface ModalInputDefinition {
|
|
45
|
+
/** Always stores `input`. */
|
|
46
|
+
type: 'input';
|
|
47
|
+
/** Stores the input field id. */
|
|
48
|
+
id: string;
|
|
49
|
+
/** Stores the label text. */
|
|
50
|
+
label: string;
|
|
51
|
+
/** Stores the input kind. */
|
|
52
|
+
kind?: ModalInputKind;
|
|
53
|
+
/** Stores the default value. */
|
|
54
|
+
value?: string;
|
|
55
|
+
/** Stores the placeholder text. */
|
|
56
|
+
placeholder?: string;
|
|
57
|
+
/** Stores whether the field is required. */
|
|
58
|
+
required?: boolean;
|
|
59
|
+
/** Stores the alignment hint. */
|
|
60
|
+
align?: ModalContentAlign;
|
|
61
|
+
}
|
|
62
|
+
/** Describes one select option rendered inside a modal. */
|
|
63
|
+
export interface ModalSelectOptionDefinition {
|
|
64
|
+
/** Stores the option label. */
|
|
65
|
+
label: string;
|
|
66
|
+
/** Stores the option value. */
|
|
67
|
+
value: string;
|
|
68
|
+
/** Stores whether the option is selected by default. */
|
|
69
|
+
selected?: boolean;
|
|
70
|
+
}
|
|
71
|
+
/** Describes one select field rendered inside a modal. */
|
|
72
|
+
export interface ModalSelectDefinition {
|
|
73
|
+
/** Always stores `select`. */
|
|
74
|
+
type: 'select';
|
|
75
|
+
/** Stores the field id. */
|
|
76
|
+
id: string;
|
|
77
|
+
/** Stores the label text. */
|
|
78
|
+
label: string;
|
|
79
|
+
/** Stores the available options. */
|
|
80
|
+
options: ModalSelectOptionDefinition[];
|
|
81
|
+
/** Stores the default value. */
|
|
82
|
+
value?: string;
|
|
83
|
+
/** Stores the alignment hint. */
|
|
84
|
+
align?: ModalContentAlign;
|
|
85
|
+
}
|
|
86
|
+
/** Describes one row-level action rendered in a list item. */
|
|
87
|
+
export interface ModalListActionDefinition extends ModalButtonDefinition {
|
|
88
|
+
/** Always stores `button`. */
|
|
89
|
+
type: 'button';
|
|
90
|
+
}
|
|
91
|
+
/** Describes one list row rendered inside a modal list. */
|
|
92
|
+
export interface ModalListRowDefinition {
|
|
93
|
+
/** Stores the row id. */
|
|
94
|
+
id: string;
|
|
95
|
+
/** Stores the row title. */
|
|
96
|
+
title: string;
|
|
97
|
+
/** Stores a short status label. */
|
|
98
|
+
status?: string;
|
|
99
|
+
/** Stores a secondary metadata label. */
|
|
100
|
+
meta?: string;
|
|
101
|
+
/** Stores longer descriptive text. */
|
|
102
|
+
description?: string;
|
|
103
|
+
/** Stores the row actions. */
|
|
104
|
+
actions?: ModalListActionDefinition[];
|
|
105
|
+
}
|
|
106
|
+
/** Describes one list block rendered inside a modal. */
|
|
107
|
+
export interface ModalListDefinition {
|
|
108
|
+
/** Always stores `list`. */
|
|
109
|
+
type: 'list';
|
|
110
|
+
/** Stores the list id. */
|
|
111
|
+
id: string;
|
|
112
|
+
/** Stores the list label. */
|
|
113
|
+
label?: string;
|
|
114
|
+
/** Stores the empty-state text. */
|
|
115
|
+
emptyText?: string;
|
|
116
|
+
/** Stores the alignment hint. */
|
|
117
|
+
align?: ModalContentAlign;
|
|
118
|
+
/** Stores the list rows. */
|
|
119
|
+
items: ModalListRowDefinition[];
|
|
120
|
+
}
|
|
121
|
+
/** Describes one plugin modal content item. */
|
|
122
|
+
export type PluginModalContentItem = ModalTextDefinition | ModalSeparatorDefinition | ModalButtonItemDefinition | ModalInputDefinition | ModalSelectDefinition | ModalListDefinition;
|
|
123
|
+
/** Describes the structured payload used to render a plugin modal. */
|
|
124
|
+
export interface PluginModalDefinition {
|
|
125
|
+
/** Stores the modal title. */
|
|
126
|
+
title: string;
|
|
127
|
+
/** Stores the ordered list of modal content items. */
|
|
128
|
+
content: PluginModalContentItem[];
|
|
129
|
+
}
|
package/lib/types/plugin.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export type PluginSettingsValue = Record<string, unknown>;
|
|
|
23
23
|
export interface PluginHandleActionParams extends RequestParams {
|
|
24
24
|
/** Stores the action id selected by the user. */
|
|
25
25
|
action_id?: string;
|
|
26
|
+
/** Stores an optional payload supplied by the triggering UI. */
|
|
27
|
+
payload?: Record<string, unknown>;
|
|
26
28
|
}
|
|
27
29
|
/** Describes the params shape for plugin settings callbacks. */
|
|
28
30
|
export interface PluginSettingsValuesParams extends RequestParams {
|
package/package.json
CHANGED
package/src/lib/runtime/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ export { createDefaultPluginDelegates, createRuntimeDispatcher } from './dispatc
|
|
|
22
22
|
export { isPluginFailure, pluginError } from './errors';
|
|
23
23
|
export { createPluginRuntime } from './factory';
|
|
24
24
|
export { createHost } from './host';
|
|
25
|
+
export { ModalBuilder } from './modal';
|
|
25
26
|
export {
|
|
26
27
|
bootstrapPluginModule,
|
|
27
28
|
createRegisteredPluginRuntime,
|
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, params?.payload);
|
|
369
|
+
}
|
|
419
370
|
return null;
|
|
420
371
|
},
|
|
421
372
|
};
|