superdoc-macros 0.2.0 → 0.3.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 +69 -66
- package/dist/host/superdoc-host.d.ts +2 -21
- package/dist/host/superdoc-host.js +56 -28
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/manager.d.ts +24 -21
- package/dist/manager.js +36 -28
- package/dist/messages.d.ts +50 -0
- package/dist/messages.js +78 -0
- package/dist/recorder/recorder.d.ts +7 -19
- package/dist/recorder/recorder.js +21 -6
- package/dist/scripting/eval-runner.d.ts +1 -12
- package/dist/scripting/eval-runner.js +20 -6
- package/dist/scripting/iframe-runner.d.ts +5 -4
- package/dist/scripting/iframe-runner.js +27 -8
- 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 +8 -6
- package/dist/shortcuts.js +5 -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 +7 -6
- package/dist/storage.js +4 -4
- package/dist/types.d.ts +33 -32
- package/dist/types.js +6 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,61 +1,54 @@
|
|
|
1
1
|
# superdoc-macros
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A macro toolkit for **SuperDoc v2**-based editors. Originally built for [otzaria-word-editor](https://github.com/Y-PLONI/otzaria-word-editor), but fully generic: it has no dependency on that project — nor on the superdoc package itself (the engine is consumed structurally, through its public surfaces), and the core works against any editor that implements a small `MacroHost` interface.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Three capabilities, in the spirit of Word macros:
|
|
6
6
|
|
|
7
|
-
|
|
|
7
|
+
| Capability | What it gives you |
|
|
8
8
|
| --- | --- |
|
|
9
|
-
|
|
|
10
|
-
|
|
|
11
|
-
|
|
|
9
|
+
| **Scripted macros** | User-written JavaScript macros that run in a real sandbox (an isolated iframe) against a small, safe document API |
|
|
10
|
+
| **Macro recorder** | "Record → work normally → stop → replay" — records commands and typing, like Word's recorder |
|
|
11
|
+
| **Snippets (AutoText)** | Templates with variables (`{{date}}`, `{{selection}}`…), keyboard shortcuts, and auto-expansion while typing (type a trigger word + space) |
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Plus: persistence (localStorage or custom storage), JSON import/export, keyboard shortcut binding, and localizable runtime messages (English by default, Hebrew locale included).
|
|
14
14
|
|
|
15
|
-
>
|
|
15
|
+
> **A note on VBA:** the toolkit does not execute VBA macros from `.docm` files — there is no VBA engine in the browser. It provides a parallel, JavaScript-based macro system suited to a browser-hosted editor.
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## Installation
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
npm install superdoc-macros
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
npm install github:palmoni5/superdoc-macros
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## התחלה מהירה (עם SuperDoc)
|
|
23
|
+
## Quick start (with SuperDoc)
|
|
30
24
|
|
|
31
25
|
```ts
|
|
32
26
|
import { MacroKit, createSuperdocHost } from 'superdoc-macros';
|
|
33
27
|
|
|
34
|
-
// superdoc —
|
|
28
|
+
// superdoc — a ready SuperDoc instance (after onReady); container — the element the document renders in.
|
|
35
29
|
const host = createSuperdocHost({ superdoc, container });
|
|
36
30
|
const kit = new MacroKit({ host });
|
|
37
31
|
|
|
38
|
-
//
|
|
32
|
+
// Keyboard shortcuts for everything saved, and auto-text:
|
|
39
33
|
const unbindKeys = kit.attachShortcuts(container);
|
|
40
34
|
const disableAutoText = kit.enableAutoText();
|
|
41
35
|
|
|
42
|
-
//
|
|
36
|
+
// On document swap / teardown:
|
|
43
37
|
unbindKeys();
|
|
44
38
|
disableAutoText();
|
|
45
39
|
host.dispose();
|
|
46
40
|
```
|
|
47
41
|
|
|
48
|
-
## 1.
|
|
42
|
+
## 1. Scripted macros
|
|
49
43
|
|
|
50
|
-
|
|
44
|
+
A script receives an `api` object; every method is async:
|
|
51
45
|
|
|
52
46
|
```ts
|
|
53
47
|
kit.saveScript({
|
|
54
|
-
name: '
|
|
48
|
+
name: 'Heading helper',
|
|
55
49
|
shortcut: 'Ctrl+Alt+D',
|
|
56
50
|
source: `
|
|
57
51
|
await api.bold();
|
|
58
|
-
await api.insertText('בעניין ');
|
|
59
52
|
const selected = await api.getSelectionText();
|
|
60
53
|
if (selected) await api.insertText(selected);
|
|
61
54
|
await api.bold();
|
|
@@ -67,91 +60,101 @@ const result = await kit.runScript(kit.listScripts()[0].id);
|
|
|
67
60
|
if (!result.ok) console.warn(result.message);
|
|
68
61
|
```
|
|
69
62
|
|
|
70
|
-
###
|
|
63
|
+
### The script API
|
|
71
64
|
|
|
72
|
-
|
|
|
65
|
+
| Method | Description |
|
|
73
66
|
| --- | --- |
|
|
74
|
-
| `api.command(id, payload?)` |
|
|
75
|
-
| `api.hasCommand(id)` / `api.commandIds()` |
|
|
76
|
-
| `api.insertText(text)` / `api.insertParagraph()` |
|
|
77
|
-
| `api.deleteBackward(count?)` |
|
|
78
|
-
| `api.getSelection()` / `api.getSelectionText()` |
|
|
79
|
-
| `api.getDocumentText()` |
|
|
80
|
-
| `api.replaceAll(find, replace)` |
|
|
81
|
-
| `api.bold()` / `italic()` / `underline()` / `bulletList()` / `directionRtl()` … |
|
|
82
|
-
| `api.log(...)` |
|
|
67
|
+
| `api.command(id, payload?)` | Any command from the SuperDoc catalog (`'text-align'`, `'font-size'`…). Returns `{ok}`, never throws |
|
|
68
|
+
| `api.hasCommand(id)` / `api.commandIds()` | Capability discovery |
|
|
69
|
+
| `api.insertText(text)` / `api.insertParagraph()` | Insert at the caret |
|
|
70
|
+
| `api.deleteBackward(count?)` | Delete backwards |
|
|
71
|
+
| `api.getSelection()` / `api.getSelectionText()` | The current selection |
|
|
72
|
+
| `api.getDocumentText()` | The full document text |
|
|
73
|
+
| `api.replaceAll(find, replace)` | Replace everywhere; returns the count |
|
|
74
|
+
| `api.bold()` / `italic()` / `underline()` / `bulletList()` / `directionRtl()` … | Sugar for common commands — these throw on failure, so the script stops |
|
|
75
|
+
| `api.log(...)` | Run log (delivered to the kit's `onLog`) |
|
|
83
76
|
|
|
84
|
-
###
|
|
77
|
+
### Security
|
|
85
78
|
|
|
86
|
-
|
|
79
|
+
The default is a **real sandbox**: scripts run in an iframe with `sandbox="allow-scripts"` only — an opaque origin, no access to the application's DOM, localStorage, cookies, or credentialed network. The script's only way to touch the document is the API above, with a time cap (default 30 s — enforced even against infinite loops, by removing the iframe) and a call cap (10,000).
|
|
87
80
|
|
|
88
|
-
|
|
81
|
+
If you must waive isolation (e.g. a CSP that blocks `srcdoc`), switch to the direct runner: `new MacroKit({ host, runner: 'eval' })` — see the warnings in the code.
|
|
89
82
|
|
|
90
|
-
## 2.
|
|
83
|
+
## 2. Macro recorder
|
|
91
84
|
|
|
92
85
|
```ts
|
|
93
86
|
kit.startRecording();
|
|
94
|
-
//
|
|
95
|
-
const recording = kit.stopRecording('
|
|
87
|
+
// the user works normally: typing, formatting, lists...
|
|
88
|
+
const recording = kit.stopRecording('Standard intro', 'Ctrl+Alt+1');
|
|
96
89
|
|
|
97
|
-
//
|
|
90
|
+
// later, from anywhere in the document:
|
|
98
91
|
await kit.replayRecording(recording.id);
|
|
99
92
|
```
|
|
100
93
|
|
|
101
|
-
|
|
94
|
+
The recorder captures **commands and typing**, not caret positions — exactly like Word's recorder: replay applies wherever the caret stands. Consecutive keystrokes coalesce into one step, `undo`/`redo` are not recorded (configurable via `RecorderOptions`), and recordings persist as clean JSON that can be exported and shared. `updateRecording({id, name?, shortcut?})` renames a recording or edits its shortcut.
|
|
102
95
|
|
|
103
|
-
## 3.
|
|
96
|
+
## 3. Snippets and auto-text
|
|
104
97
|
|
|
105
98
|
```ts
|
|
106
|
-
kit.saveSnippet({ name: '
|
|
99
|
+
kit.saveSnippet({ name: 'BSD', text: 'בס"ד', trigger: 'בסד' });
|
|
107
100
|
kit.saveSnippet({
|
|
108
|
-
name: '
|
|
109
|
-
text: '
|
|
101
|
+
name: 'Signature',
|
|
102
|
+
text: 'Best regards, {{date}}',
|
|
110
103
|
shortcut: 'Ctrl+Alt+S',
|
|
111
104
|
});
|
|
112
|
-
kit.saveSnippet({ name: '
|
|
105
|
+
kit.saveSnippet({ name: 'Citation', text: '(see {{selection}})' });
|
|
113
106
|
|
|
114
|
-
kit.enableAutoText(); //
|
|
115
|
-
await kit.expandSnippet(id); //
|
|
107
|
+
kit.enableAutoText(); // from now on: typing the trigger + space expands it
|
|
108
|
+
await kit.expandSnippet(id); // or expand explicitly / via the shortcut
|
|
116
109
|
```
|
|
117
110
|
|
|
118
|
-
|
|
111
|
+
Built-in variables: `{{date}}`, `{{time}}`, `{{datetime}}` (formatted with the browser locale, or an explicit `locale` option), `{{selection}}`. Any other name resolves from the `variables` passed to `expandSnippet`; a variable with no value stays visible in the text.
|
|
112
|
+
|
|
113
|
+
## Localization
|
|
114
|
+
|
|
115
|
+
Runtime messages (failures shown to end users) default to English. A host with a localized UI swaps them once at startup:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { setMacroMessages, HEBREW_MESSAGES } from 'superdoc-macros';
|
|
119
|
+
|
|
120
|
+
setMacroMessages(HEBREW_MESSAGES); // full Hebrew locale (included)
|
|
121
|
+
setMacroMessages({ scriptNotFound: '…' }); // or a partial override
|
|
122
|
+
```
|
|
119
123
|
|
|
120
|
-
##
|
|
124
|
+
## Persistence, import and export
|
|
121
125
|
|
|
122
126
|
```ts
|
|
123
127
|
import { createLocalStorage } from 'superdoc-macros';
|
|
124
128
|
|
|
125
129
|
const kit = new MacroKit({ host, storage: createLocalStorage('my-key') });
|
|
126
130
|
|
|
127
|
-
const json = kit.exportState(); //
|
|
131
|
+
const json = kit.exportState(); // backup / sharing
|
|
128
132
|
kit.importState(json, { merge: true });
|
|
129
133
|
```
|
|
130
134
|
|
|
131
|
-
`MacroStorage`
|
|
135
|
+
`MacroStorage` is a two-method interface — implement it to persist to a file (e.g. a plugin workspace).
|
|
132
136
|
|
|
133
|
-
##
|
|
137
|
+
## Connecting a different host
|
|
134
138
|
|
|
135
|
-
|
|
139
|
+
The whole toolkit works against a single `MacroHost` interface (commands, text insertion, selection, replace, typing events). `createSuperdocHost` is the implementation for SuperDoc v2 in `ui: false` mode; another editor plugs in with its own implementation — see `src/types.ts` and the double in `tests/fake-host.ts`.
|
|
136
140
|
|
|
137
|
-
##
|
|
141
|
+
## Known limitations
|
|
138
142
|
|
|
139
|
-
-
|
|
140
|
-
-
|
|
141
|
-
- `deleteBackward`
|
|
142
|
-
-
|
|
143
|
+
- No VBA execution. `.docm` files open normally but their macros are not run.
|
|
144
|
+
- The recorder does not capture caret movement or mouse selection (as in Word — replay acts from the current caret).
|
|
145
|
+
- `deleteBackward` and full-document text use the engine's internal view (ProseMirror) — available in the browser, not headless.
|
|
146
|
+
- The `eval` runner's time cap cannot stop an infinite synchronous loop (the iframe runner's can).
|
|
143
147
|
|
|
144
|
-
##
|
|
148
|
+
## Development
|
|
145
149
|
|
|
146
150
|
```bash
|
|
147
151
|
npm install
|
|
148
|
-
npm test # vitest
|
|
149
|
-
npm run build # tsc
|
|
152
|
+
npm test # vitest
|
|
153
|
+
npm run build # tsc → dist/
|
|
150
154
|
```
|
|
151
155
|
|
|
152
|
-
|
|
153
|
-
(.github/workflows/release.yml) מפרסם ל-npm ויוצר GitHub Release אוטומטית.
|
|
156
|
+
**Releasing:** bump `version` in package.json and push to main — the workflow (.github/workflows/release.yml) publishes to npm and creates a GitHub Release automatically.
|
|
154
157
|
|
|
155
|
-
##
|
|
158
|
+
## License
|
|
156
159
|
|
|
157
160
|
MIT
|
|
@@ -1,22 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* מימוש `MacroHost` מעל SuperDoc v2 במצב מנוע-בלבד (`ui: false`) — הקונפיגורציה
|
|
3
|
-
* של otzaria-word-editor.
|
|
4
|
-
*
|
|
5
|
-
* המשטחים שבשימוש, לפי סדר עדיפות:
|
|
6
|
-
* 1. `superdoc.ui.commands` — קטלוג הפקודות של ה-controller (הרצה + תצפית).
|
|
7
|
-
* 2. `superdoc.activeEditor.doc` — ה-Document API הציבורי (בחירה, הכנסה, בלוקים).
|
|
8
|
-
* 3. `superdoc.ui.search` — חיפוש/החלפה.
|
|
9
|
-
* 4. `superdoc.activeEditor.view` — מופע ProseMirror הפנימי, **רק** לפערים
|
|
10
|
-
* שאין להם משטח ציבורי: מחיקה לאחור וטקסט מלא של המסמך. קיים בדפדפן
|
|
11
|
-
* ו-null ב-headless.
|
|
12
|
-
*
|
|
13
|
-
* הטיפוסים כאן מבניים (structural) ואינם מייבאים מ-superdoc: הערכה לא תלויה
|
|
14
|
-
* בחבילה, וגרסת מנוע שמשנה שדה תיכשל סגור (הפונקציה תחזיר כשל) ולא תקרוס.
|
|
15
|
-
*
|
|
16
|
-
* תצפית הפקודות למקליט נעשית בעטיפת `executeAsync` על אובייקט ה-commands.
|
|
17
|
-
* זה מכסה כל מסלול שקורא לו — כולל ה-CommandAdapter של otzaria — בלי לשנות
|
|
18
|
-
* את הקוד הקורא. `dispose()` מחזיר את המתודה המקורית.
|
|
19
|
-
*/
|
|
20
1
|
import type { MacroHost } from '../types.js';
|
|
21
2
|
interface CommandStateLike {
|
|
22
3
|
reason?: string;
|
|
@@ -119,11 +100,11 @@ export interface SuperdocLike {
|
|
|
119
100
|
}
|
|
120
101
|
export interface SuperdocHostOptions {
|
|
121
102
|
superdoc: SuperdocLike;
|
|
122
|
-
/**
|
|
103
|
+
/** The element the document renders in — typing events are captured on it. */
|
|
123
104
|
container?: HTMLElement | null;
|
|
124
105
|
}
|
|
125
106
|
export interface SuperdocMacroHost extends MacroHost {
|
|
126
|
-
/**
|
|
107
|
+
/** Removes the observation wrapper and the DOM listeners. Call before swapping documents. */
|
|
127
108
|
dispose(): void;
|
|
128
109
|
}
|
|
129
110
|
export declare function createSuperdocHost(options: SuperdocHostOptions): SuperdocMacroHost;
|
|
@@ -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,17 +40,17 @@ 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
|
+
// Read at call time, never cached: activeEditor is replaced on every document open.
|
|
23
47
|
const commands = () => superdoc.ui?.commands ?? null;
|
|
24
48
|
const doc = () => superdoc.activeEditor?.doc ?? null;
|
|
25
49
|
const view = () => superdoc.activeEditor?.view ?? null;
|
|
26
50
|
const search = () => superdoc.ui?.search ?? null;
|
|
27
51
|
const commandListeners = new Set();
|
|
28
52
|
const inputListeners = new Set();
|
|
29
|
-
/*
|
|
53
|
+
/* Command observation: wrap executeAsync, once, restored on dispose. */
|
|
30
54
|
const wrapped = commands();
|
|
31
55
|
const originalExecuteAsync = wrapped?.executeAsync;
|
|
32
56
|
if (wrapped && originalExecuteAsync) {
|
|
@@ -36,13 +60,13 @@ export function createSuperdocHost(options) {
|
|
|
36
60
|
listener(id, payload);
|
|
37
61
|
}
|
|
38
62
|
catch (error) {
|
|
39
|
-
console.warn('[superdoc-macros]
|
|
63
|
+
console.warn('[superdoc-macros] command listener threw', error);
|
|
40
64
|
}
|
|
41
65
|
}
|
|
42
66
|
return originalExecuteAsync.call(wrapped, id, payload);
|
|
43
67
|
};
|
|
44
68
|
}
|
|
45
|
-
/*
|
|
69
|
+
/* Typing: beforeinput on the container, capture phase. */
|
|
46
70
|
const onBeforeInput = (event) => {
|
|
47
71
|
const input = event;
|
|
48
72
|
let mapped = null;
|
|
@@ -72,7 +96,7 @@ export function createSuperdocHost(options) {
|
|
|
72
96
|
listener(mapped);
|
|
73
97
|
}
|
|
74
98
|
catch (error) {
|
|
75
|
-
console.warn('[superdoc-macros]
|
|
99
|
+
console.warn('[superdoc-macros] input listener threw', error);
|
|
76
100
|
}
|
|
77
101
|
}
|
|
78
102
|
};
|
|
@@ -119,23 +143,23 @@ export function createSuperdocHost(options) {
|
|
|
119
143
|
async execute(id, payload) {
|
|
120
144
|
const bus = commands();
|
|
121
145
|
if (!bus)
|
|
122
|
-
return
|
|
146
|
+
return notReady();
|
|
123
147
|
if (!bus.has(id))
|
|
124
|
-
return failed(
|
|
148
|
+
return failed(macroMessages().unknownCommand(id), 'unknown-command');
|
|
125
149
|
let result;
|
|
126
150
|
try {
|
|
127
151
|
result = await bus.executeAsync(id, payload);
|
|
128
152
|
}
|
|
129
153
|
catch (error) {
|
|
130
|
-
return failed(error instanceof Error ? error.message :
|
|
154
|
+
return failed(error instanceof Error ? error.message : macroMessages().actionFailed, 'threw');
|
|
131
155
|
}
|
|
132
|
-
// false =
|
|
156
|
+
// false = the controller did not route the command; the command state explains why.
|
|
133
157
|
if (result === false) {
|
|
134
158
|
const reason = bus.get(id).getState().reason;
|
|
135
|
-
return failed(reason ?
|
|
159
|
+
return failed(reason ? `${macroMessages().actionFailed} (${reason})` : macroMessages().actionFailed, reason);
|
|
136
160
|
}
|
|
137
161
|
if (typeof result === 'object' && result !== null) {
|
|
138
|
-
return receiptOutcome(result,
|
|
162
|
+
return receiptOutcome(result, macroMessages().commandFailed(id));
|
|
139
163
|
}
|
|
140
164
|
return { ok: true };
|
|
141
165
|
},
|
|
@@ -143,7 +167,7 @@ export function createSuperdocHost(options) {
|
|
|
143
167
|
async insertText(text) {
|
|
144
168
|
const insert = doc()?.insert;
|
|
145
169
|
if (typeof insert === 'function') {
|
|
146
|
-
//
|
|
170
|
+
// Without a target the insertion falls to the end of the document — so the target comes from the live selection.
|
|
147
171
|
const snapshot = await readSelection(false);
|
|
148
172
|
try {
|
|
149
173
|
const receipt = await insert({
|
|
@@ -151,13 +175,13 @@ export function createSuperdocHost(options) {
|
|
|
151
175
|
type: 'text',
|
|
152
176
|
...(snapshot.selectionTarget ? { target: snapshot.selectionTarget } : {}),
|
|
153
177
|
});
|
|
154
|
-
return receiptOutcome(receipt,
|
|
178
|
+
return receiptOutcome(receipt, macroMessages().insertTextFailed);
|
|
155
179
|
}
|
|
156
180
|
catch (error) {
|
|
157
|
-
return failed(error instanceof Error ? error.message :
|
|
181
|
+
return failed(error instanceof Error ? error.message : macroMessages().insertTextFailed, 'threw');
|
|
158
182
|
}
|
|
159
183
|
}
|
|
160
|
-
//
|
|
184
|
+
// Fallback: direct ProseMirror, when the Document API is unavailable.
|
|
161
185
|
const pm = view();
|
|
162
186
|
if (pm) {
|
|
163
187
|
try {
|
|
@@ -168,16 +192,16 @@ export function createSuperdocHost(options) {
|
|
|
168
192
|
return { ok: true };
|
|
169
193
|
}
|
|
170
194
|
catch (error) {
|
|
171
|
-
return failed(error instanceof Error ? error.message :
|
|
195
|
+
return failed(error instanceof Error ? error.message : macroMessages().insertTextFailed, 'threw');
|
|
172
196
|
}
|
|
173
197
|
}
|
|
174
|
-
return
|
|
198
|
+
return notReady();
|
|
175
199
|
},
|
|
176
200
|
async deleteBackward(count) {
|
|
177
|
-
//
|
|
201
|
+
// No public deletion surface — this is the main use of the ProseMirror escape hatch.
|
|
178
202
|
const pm = view();
|
|
179
203
|
if (!pm)
|
|
180
|
-
return failed(
|
|
204
|
+
return failed(macroMessages().deletionUnavailable, 'view-unavailable');
|
|
181
205
|
try {
|
|
182
206
|
const { from } = pm.state.selection;
|
|
183
207
|
const start = Math.max(0, from - Math.max(0, Math.trunc(count)));
|
|
@@ -189,7 +213,7 @@ export function createSuperdocHost(options) {
|
|
|
189
213
|
return { ok: true };
|
|
190
214
|
}
|
|
191
215
|
catch (error) {
|
|
192
|
-
return failed(error instanceof Error ? error.message :
|
|
216
|
+
return failed(error instanceof Error ? error.message : macroMessages().deleteFailed, 'threw');
|
|
193
217
|
}
|
|
194
218
|
},
|
|
195
219
|
getSelection(options) {
|
|
@@ -198,24 +222,28 @@ export function createSuperdocHost(options) {
|
|
|
198
222
|
async replaceAll(query, replacement) {
|
|
199
223
|
const handle = search();
|
|
200
224
|
if (!handle)
|
|
201
|
-
return { ok: false, replaced: 0, message:
|
|
225
|
+
return { ok: false, replaced: 0, message: macroMessages().searchUnavailable };
|
|
202
226
|
try {
|
|
203
227
|
handle.open?.();
|
|
204
228
|
const slice = handle.search(query);
|
|
205
229
|
if (slice?.available === false) {
|
|
206
|
-
return { ok: false, replaced: 0, message:
|
|
230
|
+
return { ok: false, replaced: 0, message: macroMessages().searchUnavailableInDocument };
|
|
207
231
|
}
|
|
208
232
|
const total = typeof slice?.total === 'number' ? slice.total : 0;
|
|
209
233
|
if (total === 0)
|
|
210
234
|
return { ok: true, replaced: 0 };
|
|
211
235
|
const result = await handle.replaceAll(replacement);
|
|
212
236
|
if (result && result.ok === false) {
|
|
213
|
-
return {
|
|
237
|
+
return {
|
|
238
|
+
ok: false,
|
|
239
|
+
replaced: 0,
|
|
240
|
+
message: `${macroMessages().replaceFailed}${result.reason ? ` (${result.reason})` : ''}`,
|
|
241
|
+
};
|
|
214
242
|
}
|
|
215
243
|
return { ok: true, replaced: total };
|
|
216
244
|
}
|
|
217
245
|
catch (error) {
|
|
218
|
-
return { ok: false, replaced: 0, message: error instanceof Error ? error.message :
|
|
246
|
+
return { ok: false, replaced: 0, message: error instanceof Error ? error.message : macroMessages().replaceFailed };
|
|
219
247
|
}
|
|
220
248
|
finally {
|
|
221
249
|
try {
|
|
@@ -223,7 +251,7 @@ export function createSuperdocHost(options) {
|
|
|
223
251
|
handle.close?.();
|
|
224
252
|
}
|
|
225
253
|
catch {
|
|
226
|
-
/*
|
|
254
|
+
/* cleanup only */
|
|
227
255
|
}
|
|
228
256
|
}
|
|
229
257
|
},
|
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
2
|
export { MacroKit, type MacroKitOptions } 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';
|
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';
|
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,18 +17,19 @@ 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'];
|
|
33
34
|
}
|
|
34
35
|
export declare class MacroKit {
|
|
@@ -51,17 +52,17 @@ export declare class MacroKit {
|
|
|
51
52
|
}): SavedScript;
|
|
52
53
|
removeScript(id: string): void;
|
|
53
54
|
runScript(id: string): Promise<MacroRunResult>;
|
|
54
|
-
/**
|
|
55
|
+
/** Runs an unsaved script — e.g. from the macro editor before saving. */
|
|
55
56
|
runSource(source: string): Promise<MacroRunResult>;
|
|
56
57
|
get isRecording(): boolean;
|
|
57
58
|
get recordedStepCount(): number;
|
|
58
59
|
startRecording(): void;
|
|
59
|
-
/**
|
|
60
|
+
/** Stops and saves. `null` when no step was recorded — there is nothing to save. */
|
|
60
61
|
stopRecording(name: string, shortcut?: string): RecordedMacro | null;
|
|
61
62
|
cancelRecording(): void;
|
|
62
63
|
listRecordings(): readonly RecordedMacro[];
|
|
63
64
|
removeRecording(id: string): void;
|
|
64
|
-
/**
|
|
65
|
+
/** Renames a recording or edits its shortcut. `null` when the recording was not found. */
|
|
65
66
|
updateRecording(input: {
|
|
66
67
|
id: string;
|
|
67
68
|
name?: string;
|
|
@@ -81,20 +82,22 @@ export declare class MacroKit {
|
|
|
81
82
|
ok: boolean;
|
|
82
83
|
message?: string;
|
|
83
84
|
}>;
|
|
84
|
-
/**
|
|
85
|
+
/** Enables auto-text (trigger + space). Returns a disable function. */
|
|
85
86
|
enableAutoText(): () => void;
|
|
86
87
|
disableAutoText(): void;
|
|
87
88
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
89
|
+
* Binds the shortcuts of everything saved (scripts, recordings, snippets)
|
|
90
|
+
* to a target — usually the editor container or `window`. The list is
|
|
91
|
+
* live: a new save is picked up without rebinding. Returns a dispose
|
|
92
|
+
* function.
|
|
91
93
|
*/
|
|
92
94
|
attachShortcuts(target: ShortcutTarget): () => void;
|
|
93
95
|
private currentBindings;
|
|
94
96
|
exportState(): string;
|
|
95
97
|
/**
|
|
96
|
-
*
|
|
97
|
-
* `id`
|
|
98
|
+
* Imports JSON produced by `exportState`. With `merge: true` an imported
|
|
99
|
+
* item with an existing `id` replaces it; without merge the whole state is
|
|
100
|
+
* replaced.
|
|
98
101
|
*/
|
|
99
102
|
importState(json: string, options?: {
|
|
100
103
|
merge?: boolean;
|