superdoc-macros 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -66
- package/dist/host/superdoc-host.d.ts +10 -21
- package/dist/host/superdoc-host.js +168 -51
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -2
- package/dist/manager.d.ts +49 -21
- package/dist/manager.js +102 -29
- package/dist/messages.d.ts +55 -0
- package/dist/messages.js +88 -0
- package/dist/recorder/recorder.d.ts +17 -15
- package/dist/recorder/recorder.js +5 -6
- package/dist/scripting/eval-runner.d.ts +14 -12
- package/dist/scripting/eval-runner.js +51 -8
- package/dist/scripting/iframe-runner.d.ts +5 -4
- package/dist/scripting/iframe-runner.js +42 -12
- package/dist/scripting/macro-api.d.ts +12 -23
- package/dist/scripting/macro-api.js +24 -9
- package/dist/scripting/runner.d.ts +3 -3
- package/dist/shortcuts.d.ts +20 -6
- package/dist/shortcuts.js +24 -4
- package/dist/snippets/autotext.d.ts +16 -13
- package/dist/snippets/autotext.js +8 -6
- package/dist/snippets/snippets.d.ts +14 -10
- package/dist/snippets/snippets.js +8 -6
- package/dist/storage.d.ts +30 -6
- package/dist/storage.js +93 -5
- package/dist/types.d.ts +35 -32
- package/dist/types.js +6 -5
- package/package.json +1 -1
|
@@ -1,5 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The `MacroHost` implementation on top of SuperDoc v2 in engine-only mode
|
|
3
|
+
* (`ui: false`) — the configuration otzaria-word-editor runs.
|
|
4
|
+
*
|
|
5
|
+
* The surfaces used, in order of preference:
|
|
6
|
+
* 1. `superdoc.ui.commands` — the controller's command catalog (execution + observation).
|
|
7
|
+
* 2. `superdoc.activeEditor.doc` — the public Document API (selection, insertion, blocks).
|
|
8
|
+
* 3. `superdoc.ui.search` — find/replace.
|
|
9
|
+
* 4. `superdoc.activeEditor.view` — the internal ProseMirror instance,
|
|
10
|
+
* **only** for gaps that have no public surface: backward deletion and
|
|
11
|
+
* the document's full text. Present in the browser, null headless.
|
|
12
|
+
*
|
|
13
|
+
* The types here are structural and do not import from superdoc: the toolkit
|
|
14
|
+
* does not depend on the package, and an engine version that changes a field
|
|
15
|
+
* fails closed (the function returns a failure) rather than crashing.
|
|
16
|
+
*
|
|
17
|
+
* Command observation for the recorder wraps `executeAsync` on the commands
|
|
18
|
+
* object. That covers every path that calls it — including otzaria's
|
|
19
|
+
* CommandAdapter — without changing the calling code. `dispose()` restores
|
|
20
|
+
* the original method.
|
|
21
|
+
*/
|
|
22
|
+
import { macroMessages } from '../messages.js';
|
|
23
|
+
/* ---------- Helpers ---------- */
|
|
24
|
+
function notReady() {
|
|
25
|
+
return { ok: false, message: macroMessages().noDocument, reason: 'not-ready' };
|
|
26
|
+
}
|
|
3
27
|
function failed(message, reason) {
|
|
4
28
|
return { ok: false, message, reason };
|
|
5
29
|
}
|
|
@@ -16,43 +40,104 @@ function receiptOutcome(receipt, failedAction) {
|
|
|
16
40
|
function emptySelection() {
|
|
17
41
|
return { text: '', hasRange: false, blockId: null, selectionTarget: null, empty: true };
|
|
18
42
|
}
|
|
19
|
-
/* ----------
|
|
43
|
+
/* ---------- The implementation ---------- */
|
|
20
44
|
export function createSuperdocHost(options) {
|
|
21
45
|
const { superdoc, container } = options;
|
|
22
|
-
|
|
46
|
+
const viewFallback = options.viewFallback ?? true;
|
|
47
|
+
// Read at call time, never cached: activeEditor is replaced on every document open.
|
|
23
48
|
const commands = () => superdoc.ui?.commands ?? null;
|
|
24
49
|
const doc = () => superdoc.activeEditor?.doc ?? null;
|
|
25
|
-
const view = () => superdoc.activeEditor?.view ?? null;
|
|
26
50
|
const search = () => superdoc.ui?.search ?? null;
|
|
51
|
+
/**
|
|
52
|
+
* The single gate to the internal ProseMirror view. Every use of a
|
|
53
|
+
* non-public surface goes through here, so disabling the fallback (or a
|
|
54
|
+
* future engine that hides the view) degrades to closed failures in one
|
|
55
|
+
* place.
|
|
56
|
+
*/
|
|
57
|
+
const view = () => viewFallback ? (superdoc.activeEditor?.view ?? null) : null;
|
|
27
58
|
const commandListeners = new Set();
|
|
28
59
|
const inputListeners = new Set();
|
|
29
|
-
|
|
60
|
+
const emitInput = (mapped) => {
|
|
61
|
+
for (const listener of inputListeners) {
|
|
62
|
+
try {
|
|
63
|
+
listener(mapped);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
console.warn('[superdoc-macros] input listener threw', error);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
/* Command observation: wrap executeAsync, once, restored on dispose.
|
|
71
|
+
Listeners are notified only after the engine reports success — a command
|
|
72
|
+
that was refused (not routed, or a failed receipt) must not enter a
|
|
73
|
+
recording, or replay would re-fail it or, worse, apply it in a context
|
|
74
|
+
where it now succeeds unintended. */
|
|
30
75
|
const wrapped = commands();
|
|
31
76
|
const originalExecuteAsync = wrapped?.executeAsync;
|
|
32
77
|
if (wrapped && originalExecuteAsync) {
|
|
33
78
|
wrapped.executeAsync = function (id, payload) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
79
|
+
const result = originalExecuteAsync.call(wrapped, id, payload);
|
|
80
|
+
void Promise.resolve(result)
|
|
81
|
+
.then((value) => {
|
|
82
|
+
if (value === false)
|
|
83
|
+
return;
|
|
84
|
+
if (typeof value === 'object' && value !== null && value.success === false)
|
|
85
|
+
return;
|
|
86
|
+
for (const listener of commandListeners) {
|
|
87
|
+
try {
|
|
88
|
+
listener(id, payload);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
console.warn('[superdoc-macros] command listener threw', error);
|
|
92
|
+
}
|
|
37
93
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
return originalExecuteAsync.call(wrapped, id, payload);
|
|
94
|
+
})
|
|
95
|
+
.catch(() => undefined); // a thrown command is a failure — nothing to record.
|
|
96
|
+
return result;
|
|
43
97
|
};
|
|
44
98
|
}
|
|
45
|
-
/*
|
|
99
|
+
/* Typing: beforeinput on the container, capture phase.
|
|
100
|
+
|
|
101
|
+
IME composition is folded to a single event: while composing, the
|
|
102
|
+
engine fires insertCompositionText repeatedly with the growing
|
|
103
|
+
candidate text, and mapping each one would record the word once per
|
|
104
|
+
keystroke. The events are suppressed during composition and the final
|
|
105
|
+
text is emitted once, from compositionend. */
|
|
106
|
+
let composing = false;
|
|
107
|
+
const onCompositionStart = () => {
|
|
108
|
+
composing = true;
|
|
109
|
+
};
|
|
110
|
+
const onCompositionEnd = (event) => {
|
|
111
|
+
composing = false;
|
|
112
|
+
const data = event.data;
|
|
113
|
+
if (typeof data === 'string' && data.length > 0)
|
|
114
|
+
emitInput({ kind: 'insert-text', text: data });
|
|
115
|
+
};
|
|
46
116
|
const onBeforeInput = (event) => {
|
|
47
117
|
const input = event;
|
|
48
118
|
let mapped = null;
|
|
49
119
|
switch (input.inputType) {
|
|
50
120
|
case 'insertText':
|
|
51
121
|
case 'insertCompositionText':
|
|
122
|
+
if (composing)
|
|
123
|
+
break; // the final text arrives from compositionend.
|
|
52
124
|
if (typeof input.data === 'string' && input.data.length > 0) {
|
|
53
125
|
mapped = { kind: 'insert-text', text: input.data };
|
|
54
126
|
}
|
|
55
127
|
break;
|
|
128
|
+
// Paste, drop and spellcheck replacement carry their text either in
|
|
129
|
+
// `data` or in a dataTransfer — without this branch a recording
|
|
130
|
+
// silently missed everything the user pasted.
|
|
131
|
+
case 'insertFromPaste':
|
|
132
|
+
case 'insertFromDrop':
|
|
133
|
+
case 'insertReplacementText': {
|
|
134
|
+
const text = typeof input.data === 'string' && input.data.length > 0
|
|
135
|
+
? input.data
|
|
136
|
+
: input.dataTransfer?.getData('text/plain') ?? '';
|
|
137
|
+
if (text.length > 0)
|
|
138
|
+
mapped = { kind: 'insert-text', text };
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
56
141
|
case 'insertParagraph':
|
|
57
142
|
mapped = { kind: 'insert-paragraph' };
|
|
58
143
|
break;
|
|
@@ -65,29 +150,32 @@ export function createSuperdocHost(options) {
|
|
|
65
150
|
default:
|
|
66
151
|
break;
|
|
67
152
|
}
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
for (const listener of inputListeners) {
|
|
71
|
-
try {
|
|
72
|
-
listener(mapped);
|
|
73
|
-
}
|
|
74
|
-
catch (error) {
|
|
75
|
-
console.warn('[superdoc-macros] מאזין הקלדה זרק', error);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
153
|
+
if (mapped)
|
|
154
|
+
emitInput(mapped);
|
|
78
155
|
};
|
|
79
156
|
container?.addEventListener('beforeinput', onBeforeInput, true);
|
|
80
|
-
|
|
157
|
+
container?.addEventListener('compositionstart', onCompositionStart, true);
|
|
158
|
+
container?.addEventListener('compositionend', onCompositionEnd, true);
|
|
159
|
+
/**
|
|
160
|
+
* `failed: true` means the engine call itself threw — as opposed to a
|
|
161
|
+
* clean "no selection" answer. Callers that write relative to the caret
|
|
162
|
+
* must fail closed on it: falling back to "no target" would send the text
|
|
163
|
+
* to the end of the document, far from where the user is looking.
|
|
164
|
+
*/
|
|
165
|
+
async function readSelectionDetailed(includeText) {
|
|
81
166
|
const current = doc()?.selection?.current;
|
|
82
167
|
if (typeof current !== 'function')
|
|
83
|
-
return emptySelection();
|
|
168
|
+
return { snapshot: emptySelection(), failed: false };
|
|
84
169
|
let info;
|
|
85
170
|
try {
|
|
86
171
|
info = await current(includeText ? { includeText: true } : undefined);
|
|
87
172
|
}
|
|
88
173
|
catch {
|
|
89
|
-
return emptySelection();
|
|
174
|
+
return { snapshot: emptySelection(), failed: true };
|
|
90
175
|
}
|
|
176
|
+
return { snapshot: parseSelectionInfo(info), failed: false };
|
|
177
|
+
}
|
|
178
|
+
function parseSelectionInfo(info) {
|
|
91
179
|
if (!info || typeof info !== 'object')
|
|
92
180
|
return emptySelection();
|
|
93
181
|
const segments = Array.isArray(info.target?.segments) ? info.target.segments : [];
|
|
@@ -119,23 +207,23 @@ export function createSuperdocHost(options) {
|
|
|
119
207
|
async execute(id, payload) {
|
|
120
208
|
const bus = commands();
|
|
121
209
|
if (!bus)
|
|
122
|
-
return
|
|
210
|
+
return notReady();
|
|
123
211
|
if (!bus.has(id))
|
|
124
|
-
return failed(
|
|
212
|
+
return failed(macroMessages().unknownCommand(id), 'unknown-command');
|
|
125
213
|
let result;
|
|
126
214
|
try {
|
|
127
215
|
result = await bus.executeAsync(id, payload);
|
|
128
216
|
}
|
|
129
217
|
catch (error) {
|
|
130
|
-
return failed(error instanceof Error ? error.message :
|
|
218
|
+
return failed(error instanceof Error ? error.message : macroMessages().actionFailed, 'threw');
|
|
131
219
|
}
|
|
132
|
-
// false =
|
|
220
|
+
// false = the controller did not route the command; the command state explains why.
|
|
133
221
|
if (result === false) {
|
|
134
222
|
const reason = bus.get(id).getState().reason;
|
|
135
|
-
return failed(reason ?
|
|
223
|
+
return failed(reason ? `${macroMessages().actionFailed} (${reason})` : macroMessages().actionFailed, reason);
|
|
136
224
|
}
|
|
137
225
|
if (typeof result === 'object' && result !== null) {
|
|
138
|
-
return receiptOutcome(result,
|
|
226
|
+
return receiptOutcome(result, macroMessages().commandFailed(id));
|
|
139
227
|
}
|
|
140
228
|
return { ok: true };
|
|
141
229
|
},
|
|
@@ -143,21 +231,25 @@ export function createSuperdocHost(options) {
|
|
|
143
231
|
async insertText(text) {
|
|
144
232
|
const insert = doc()?.insert;
|
|
145
233
|
if (typeof insert === 'function') {
|
|
146
|
-
//
|
|
147
|
-
const snapshot = await
|
|
234
|
+
// Without a target the insertion falls to the end of the document — so the target comes from the live selection.
|
|
235
|
+
const { snapshot, failed: selectionFailed } = await readSelectionDetailed(false);
|
|
236
|
+
// A failed read is not "no selection": inserting without a target
|
|
237
|
+
// would land the text at the end of the document. Fail closed.
|
|
238
|
+
if (selectionFailed)
|
|
239
|
+
return failed(macroMessages().selectionUnavailable, 'selection-read-failed');
|
|
148
240
|
try {
|
|
149
241
|
const receipt = await insert({
|
|
150
242
|
value: text,
|
|
151
243
|
type: 'text',
|
|
152
244
|
...(snapshot.selectionTarget ? { target: snapshot.selectionTarget } : {}),
|
|
153
245
|
});
|
|
154
|
-
return receiptOutcome(receipt,
|
|
246
|
+
return receiptOutcome(receipt, macroMessages().insertTextFailed);
|
|
155
247
|
}
|
|
156
248
|
catch (error) {
|
|
157
|
-
return failed(error instanceof Error ? error.message :
|
|
249
|
+
return failed(error instanceof Error ? error.message : macroMessages().insertTextFailed, 'threw');
|
|
158
250
|
}
|
|
159
251
|
}
|
|
160
|
-
//
|
|
252
|
+
// Fallback: direct ProseMirror, when the Document API is unavailable.
|
|
161
253
|
const pm = view();
|
|
162
254
|
if (pm) {
|
|
163
255
|
try {
|
|
@@ -168,16 +260,16 @@ export function createSuperdocHost(options) {
|
|
|
168
260
|
return { ok: true };
|
|
169
261
|
}
|
|
170
262
|
catch (error) {
|
|
171
|
-
return failed(error instanceof Error ? error.message :
|
|
263
|
+
return failed(error instanceof Error ? error.message : macroMessages().insertTextFailed, 'threw');
|
|
172
264
|
}
|
|
173
265
|
}
|
|
174
|
-
return
|
|
266
|
+
return notReady();
|
|
175
267
|
},
|
|
176
268
|
async deleteBackward(count) {
|
|
177
|
-
//
|
|
269
|
+
// No public deletion surface — this is the main use of the ProseMirror escape hatch.
|
|
178
270
|
const pm = view();
|
|
179
271
|
if (!pm)
|
|
180
|
-
return failed(
|
|
272
|
+
return failed(macroMessages().deletionUnavailable, 'view-unavailable');
|
|
181
273
|
try {
|
|
182
274
|
const { from } = pm.state.selection;
|
|
183
275
|
const start = Math.max(0, from - Math.max(0, Math.trunc(count)));
|
|
@@ -189,33 +281,56 @@ export function createSuperdocHost(options) {
|
|
|
189
281
|
return { ok: true };
|
|
190
282
|
}
|
|
191
283
|
catch (error) {
|
|
192
|
-
return failed(error instanceof Error ? error.message :
|
|
284
|
+
return failed(error instanceof Error ? error.message : macroMessages().deleteFailed, 'threw');
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
async deleteForward(count) {
|
|
288
|
+
const pm = view();
|
|
289
|
+
if (!pm)
|
|
290
|
+
return failed(macroMessages().deletionUnavailable, 'view-unavailable');
|
|
291
|
+
try {
|
|
292
|
+
const { from } = pm.state.selection;
|
|
293
|
+
const size = pm.state.doc.content.size;
|
|
294
|
+
const end = Math.min(size, from + Math.max(0, Math.trunc(count)));
|
|
295
|
+
if (end === from)
|
|
296
|
+
return { ok: true };
|
|
297
|
+
const tr = pm.state.tr;
|
|
298
|
+
tr.delete(from, end);
|
|
299
|
+
pm.dispatch(tr);
|
|
300
|
+
return { ok: true };
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
return failed(error instanceof Error ? error.message : macroMessages().deleteFailed, 'threw');
|
|
193
304
|
}
|
|
194
305
|
},
|
|
195
|
-
getSelection(options) {
|
|
196
|
-
return
|
|
306
|
+
async getSelection(options) {
|
|
307
|
+
return (await readSelectionDetailed(options?.includeText ?? false)).snapshot;
|
|
197
308
|
},
|
|
198
309
|
async replaceAll(query, replacement) {
|
|
199
310
|
const handle = search();
|
|
200
311
|
if (!handle)
|
|
201
|
-
return { ok: false, replaced: 0, message:
|
|
312
|
+
return { ok: false, replaced: 0, message: macroMessages().searchUnavailable };
|
|
202
313
|
try {
|
|
203
314
|
handle.open?.();
|
|
204
315
|
const slice = handle.search(query);
|
|
205
316
|
if (slice?.available === false) {
|
|
206
|
-
return { ok: false, replaced: 0, message:
|
|
317
|
+
return { ok: false, replaced: 0, message: macroMessages().searchUnavailableInDocument };
|
|
207
318
|
}
|
|
208
319
|
const total = typeof slice?.total === 'number' ? slice.total : 0;
|
|
209
320
|
if (total === 0)
|
|
210
321
|
return { ok: true, replaced: 0 };
|
|
211
322
|
const result = await handle.replaceAll(replacement);
|
|
212
323
|
if (result && result.ok === false) {
|
|
213
|
-
return {
|
|
324
|
+
return {
|
|
325
|
+
ok: false,
|
|
326
|
+
replaced: 0,
|
|
327
|
+
message: `${macroMessages().replaceFailed}${result.reason ? ` (${result.reason})` : ''}`,
|
|
328
|
+
};
|
|
214
329
|
}
|
|
215
330
|
return { ok: true, replaced: total };
|
|
216
331
|
}
|
|
217
332
|
catch (error) {
|
|
218
|
-
return { ok: false, replaced: 0, message: error instanceof Error ? error.message :
|
|
333
|
+
return { ok: false, replaced: 0, message: error instanceof Error ? error.message : macroMessages().replaceFailed };
|
|
219
334
|
}
|
|
220
335
|
finally {
|
|
221
336
|
try {
|
|
@@ -223,7 +338,7 @@ export function createSuperdocHost(options) {
|
|
|
223
338
|
handle.close?.();
|
|
224
339
|
}
|
|
225
340
|
catch {
|
|
226
|
-
/*
|
|
341
|
+
/* cleanup only */
|
|
227
342
|
}
|
|
228
343
|
}
|
|
229
344
|
},
|
|
@@ -250,6 +365,8 @@ export function createSuperdocHost(options) {
|
|
|
250
365
|
if (wrapped && originalExecuteAsync)
|
|
251
366
|
wrapped.executeAsync = originalExecuteAsync;
|
|
252
367
|
container?.removeEventListener('beforeinput', onBeforeInput, true);
|
|
368
|
+
container?.removeEventListener('compositionstart', onCompositionStart, true);
|
|
369
|
+
container?.removeEventListener('compositionend', onCompositionEnd, true);
|
|
253
370
|
commandListeners.clear();
|
|
254
371
|
inputListeners.clear();
|
|
255
372
|
},
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type { MacroHost, MacroOutcome, MacroStep, RecordedMacro, SavedScript, SelectionSnapshot, Snippet, TextInputEvent, } from './types.js';
|
|
2
|
-
export { MacroKit, type MacroKitOptions } from './manager.js';
|
|
2
|
+
export { MacroKit, type MacroKitOptions, type ShortcutValidation } from './manager.js';
|
|
3
|
+
export { ENGLISH_MESSAGES, HEBREW_MESSAGES, setMacroMessages, type MacroMessages, } from './messages.js';
|
|
3
4
|
export { createSuperdocHost, type SuperdocHostOptions, type SuperdocLike, type SuperdocMacroHost } from './host/superdoc-host.js';
|
|
4
5
|
export { createMacroApi, MacroError, type MacroApi, type MacroBridge, type ScriptSelection } from './scripting/macro-api.js';
|
|
5
6
|
export { createEvalRunner } from './scripting/eval-runner.js';
|
|
@@ -8,5 +9,5 @@ export type { MacroRunner, MacroRunOptions, MacroRunResult } from './scripting/r
|
|
|
8
9
|
export { MacroRecorder, replayMacro, type RecorderOptions, type ReplayOptions, type ReplayResult } from './recorder/recorder.js';
|
|
9
10
|
export { renderSnippet, expandSnippet, usesSelection, type ExpandOptions, type RenderContext } from './snippets/snippets.js';
|
|
10
11
|
export { AutoText, type AutoTextOptions } from './snippets/autotext.js';
|
|
11
|
-
export { parseShortcut, eventMatches, bindShortcuts, type ParsedShortcut, type ShortcutBinding, type ShortcutTarget } from './shortcuts.js';
|
|
12
|
-
export { createLocalStorage, createMemoryStorage, emptyState, parsePersistedState, DEFAULT_STORAGE_KEY, type MacroStorage, type PersistedMacroState, } from './storage.js';
|
|
12
|
+
export { parseShortcut, eventMatches, bindShortcuts, shortcutSignatures, hasBindingModifier, type ParsedShortcut, type ShortcutBinding, type ShortcutTarget, } from './shortcuts.js';
|
|
13
|
+
export { createLocalStorage, createMemoryStorage, emptyState, parsePersistedState, DEFAULT_STORAGE_KEY, IMPORT_LIMITS, type MacroStorage, type PersistedMacroState, } from './storage.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { MacroKit } from './manager.js';
|
|
2
|
+
export { ENGLISH_MESSAGES, HEBREW_MESSAGES, setMacroMessages, } from './messages.js';
|
|
2
3
|
export { createSuperdocHost } from './host/superdoc-host.js';
|
|
3
4
|
export { createMacroApi, MacroError } from './scripting/macro-api.js';
|
|
4
5
|
export { createEvalRunner } from './scripting/eval-runner.js';
|
|
@@ -6,5 +7,5 @@ export { createIframeRunner, SANDBOX_BOOTSTRAP, isProtocolMessage } from './scri
|
|
|
6
7
|
export { MacroRecorder, replayMacro } from './recorder/recorder.js';
|
|
7
8
|
export { renderSnippet, expandSnippet, usesSelection } from './snippets/snippets.js';
|
|
8
9
|
export { AutoText } from './snippets/autotext.js';
|
|
9
|
-
export { parseShortcut, eventMatches, bindShortcuts } from './shortcuts.js';
|
|
10
|
-
export { createLocalStorage, createMemoryStorage, emptyState, parsePersistedState, DEFAULT_STORAGE_KEY, } from './storage.js';
|
|
10
|
+
export { parseShortcut, eventMatches, bindShortcuts, shortcutSignatures, hasBindingModifier, } from './shortcuts.js';
|
|
11
|
+
export { createLocalStorage, createMemoryStorage, emptyState, parsePersistedState, DEFAULT_STORAGE_KEY, IMPORT_LIMITS, } from './storage.js';
|
package/dist/manager.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `MacroKit` —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* `MacroKit` — the facade a host installs once to get all three capabilities
|
|
3
|
+
* wired together: scripts (sandboxed), the recorder, and snippets with
|
|
4
|
+
* auto-text — plus persistence, import/export and keyboard shortcuts.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* One safety rule is enforced here: no running while recording, and no two
|
|
7
|
+
* runs at once. A replay or script running during a recording would be
|
|
8
|
+
* recorded itself and duplicate itself on the next replay.
|
|
9
9
|
*/
|
|
10
10
|
import { type MacroApiOptions } from './scripting/macro-api.js';
|
|
11
11
|
import type { MacroRunner, MacroRunOptions, MacroRunResult } from './scripting/runner.js';
|
|
@@ -17,20 +17,34 @@ import { type MacroStorage } from './storage.js';
|
|
|
17
17
|
import type { MacroHost, RecordedMacro, SavedScript, Snippet } from './types.js';
|
|
18
18
|
export interface MacroKitOptions {
|
|
19
19
|
host: MacroHost;
|
|
20
|
-
/**
|
|
20
|
+
/** Default: localStorage. */
|
|
21
21
|
storage?: MacroStorage;
|
|
22
22
|
/**
|
|
23
|
-
* `'iframe'` (
|
|
24
|
-
*
|
|
23
|
+
* `'iframe'` (the default) runs scripts in a sandbox; `'eval'` runs them
|
|
24
|
+
* directly — see the warning in eval-runner. A custom runner can also be
|
|
25
|
+
* passed.
|
|
25
26
|
*/
|
|
26
27
|
runner?: MacroRunner | 'iframe' | 'eval';
|
|
27
|
-
/**
|
|
28
|
+
/** Run options for scripts (time, call cap). */
|
|
28
29
|
runOptions?: MacroRunOptions;
|
|
29
|
-
/**
|
|
30
|
+
/** Auto-text options. */
|
|
30
31
|
autoText?: Omit<AutoTextOptions, 'onExpand' | 'onError'> & AutoTextOptions;
|
|
31
|
-
/**
|
|
32
|
+
/** Run log for `api.log`. */
|
|
32
33
|
onLog?: MacroApiOptions['onLog'];
|
|
34
|
+
/**
|
|
35
|
+
* Shortcuts the host already owns (e.g. its ribbon registry). A saved
|
|
36
|
+
* binding that collides with any of these is rejected — otherwise the
|
|
37
|
+
* macro binding, attached in the capture phase, would silently shadow an
|
|
38
|
+
* editor shortcut. Unparseable entries are ignored.
|
|
39
|
+
*/
|
|
40
|
+
reservedShortcuts?: readonly string[];
|
|
33
41
|
}
|
|
42
|
+
export type ShortcutValidation = {
|
|
43
|
+
ok: true;
|
|
44
|
+
} | {
|
|
45
|
+
ok: false;
|
|
46
|
+
message: string;
|
|
47
|
+
};
|
|
34
48
|
export declare class MacroKit {
|
|
35
49
|
private readonly host;
|
|
36
50
|
private readonly storage;
|
|
@@ -40,8 +54,20 @@ export declare class MacroKit {
|
|
|
40
54
|
private state;
|
|
41
55
|
private readonly recorder;
|
|
42
56
|
private readonly autoText;
|
|
57
|
+
private readonly reservedSignatures;
|
|
43
58
|
private running;
|
|
44
59
|
constructor(options: MacroKitOptions);
|
|
60
|
+
/**
|
|
61
|
+
* Whether a shortcut is acceptable for a saved binding: parseable, carries
|
|
62
|
+
* a real modifier, not reserved by the host, and not already used by
|
|
63
|
+
* another saved item (`excludeId` skips the item being edited). Empty or
|
|
64
|
+
* undefined means "no shortcut" and is fine. The save paths enforce this;
|
|
65
|
+
* UIs call it directly to show the message before saving.
|
|
66
|
+
*/
|
|
67
|
+
validateShortcut(shortcut: string | undefined, excludeId?: string): ShortcutValidation;
|
|
68
|
+
private findShortcutOwner;
|
|
69
|
+
/** Throws a MacroError when the shortcut is unacceptable. The save paths call this. */
|
|
70
|
+
private requireValidShortcut;
|
|
45
71
|
listScripts(): readonly SavedScript[];
|
|
46
72
|
saveScript(input: {
|
|
47
73
|
id?: string;
|
|
@@ -51,17 +77,17 @@ export declare class MacroKit {
|
|
|
51
77
|
}): SavedScript;
|
|
52
78
|
removeScript(id: string): void;
|
|
53
79
|
runScript(id: string): Promise<MacroRunResult>;
|
|
54
|
-
/**
|
|
80
|
+
/** Runs an unsaved script — e.g. from the macro editor before saving. */
|
|
55
81
|
runSource(source: string): Promise<MacroRunResult>;
|
|
56
82
|
get isRecording(): boolean;
|
|
57
83
|
get recordedStepCount(): number;
|
|
58
84
|
startRecording(): void;
|
|
59
|
-
/**
|
|
85
|
+
/** Stops and saves. `null` when no step was recorded — there is nothing to save. */
|
|
60
86
|
stopRecording(name: string, shortcut?: string): RecordedMacro | null;
|
|
61
87
|
cancelRecording(): void;
|
|
62
88
|
listRecordings(): readonly RecordedMacro[];
|
|
63
89
|
removeRecording(id: string): void;
|
|
64
|
-
/**
|
|
90
|
+
/** Renames a recording or edits its shortcut. `null` when the recording was not found. */
|
|
65
91
|
updateRecording(input: {
|
|
66
92
|
id: string;
|
|
67
93
|
name?: string;
|
|
@@ -81,20 +107,22 @@ export declare class MacroKit {
|
|
|
81
107
|
ok: boolean;
|
|
82
108
|
message?: string;
|
|
83
109
|
}>;
|
|
84
|
-
/**
|
|
110
|
+
/** Enables auto-text (trigger + space). Returns a disable function. */
|
|
85
111
|
enableAutoText(): () => void;
|
|
86
112
|
disableAutoText(): void;
|
|
87
113
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
114
|
+
* Binds the shortcuts of everything saved (scripts, recordings, snippets)
|
|
115
|
+
* to a target — usually the editor container or `window`. The list is
|
|
116
|
+
* live: a new save is picked up without rebinding. Returns a dispose
|
|
117
|
+
* function.
|
|
91
118
|
*/
|
|
92
119
|
attachShortcuts(target: ShortcutTarget): () => void;
|
|
93
120
|
private currentBindings;
|
|
94
121
|
exportState(): string;
|
|
95
122
|
/**
|
|
96
|
-
*
|
|
97
|
-
* `id`
|
|
123
|
+
* Imports JSON produced by `exportState`. With `merge: true` an imported
|
|
124
|
+
* item with an existing `id` replaces it; without merge the whole state is
|
|
125
|
+
* replaced.
|
|
98
126
|
*/
|
|
99
127
|
importState(json: string, options?: {
|
|
100
128
|
merge?: boolean;
|