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
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,113 @@ 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`) |
|
|
76
|
+
|
|
77
|
+
### Security
|
|
83
78
|
|
|
84
|
-
|
|
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 or cookies — plus a `default-src 'none'` CSP inside the iframe, so the script cannot fetch or open sockets to the public internet either. 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). When a run ends (result, error or timeout), its bridge is revoked: any late call is rejected and can no longer touch the document.
|
|
85
80
|
|
|
86
|
-
|
|
81
|
+
Honest limits: the browser offers no per-iframe memory cap, and a host call that already reached the engine cannot be aborted mid-flight (the engine exposes no cancellation) — what is guaranteed is that nothing new starts.
|
|
87
82
|
|
|
88
|
-
|
|
83
|
+
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
84
|
|
|
90
|
-
## 2.
|
|
85
|
+
## 2. Macro recorder
|
|
91
86
|
|
|
92
87
|
```ts
|
|
93
88
|
kit.startRecording();
|
|
94
|
-
//
|
|
95
|
-
const recording = kit.stopRecording('
|
|
89
|
+
// the user works normally: typing, formatting, lists...
|
|
90
|
+
const recording = kit.stopRecording('Standard intro', 'Ctrl+Alt+1');
|
|
96
91
|
|
|
97
|
-
//
|
|
92
|
+
// later, from anywhere in the document:
|
|
98
93
|
await kit.replayRecording(recording.id);
|
|
99
94
|
```
|
|
100
95
|
|
|
101
|
-
|
|
96
|
+
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
97
|
|
|
103
|
-
## 3.
|
|
98
|
+
## 3. Snippets and auto-text
|
|
104
99
|
|
|
105
100
|
```ts
|
|
106
|
-
kit.saveSnippet({ name: '
|
|
101
|
+
kit.saveSnippet({ name: 'BSD', text: 'בס"ד', trigger: 'בסד' });
|
|
107
102
|
kit.saveSnippet({
|
|
108
|
-
name: '
|
|
109
|
-
text: '
|
|
103
|
+
name: 'Signature',
|
|
104
|
+
text: 'Best regards, {{date}}',
|
|
110
105
|
shortcut: 'Ctrl+Alt+S',
|
|
111
106
|
});
|
|
112
|
-
kit.saveSnippet({ name: '
|
|
107
|
+
kit.saveSnippet({ name: 'Citation', text: '(see {{selection}})' });
|
|
113
108
|
|
|
114
|
-
kit.enableAutoText(); //
|
|
115
|
-
await kit.expandSnippet(id); //
|
|
109
|
+
kit.enableAutoText(); // from now on: typing the trigger + space expands it
|
|
110
|
+
await kit.expandSnippet(id); // or expand explicitly / via the shortcut
|
|
116
111
|
```
|
|
117
112
|
|
|
118
|
-
|
|
113
|
+
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.
|
|
119
114
|
|
|
120
|
-
##
|
|
115
|
+
## Localization
|
|
116
|
+
|
|
117
|
+
Runtime messages (failures shown to end users) default to English. A host with a localized UI swaps them once at startup:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import { setMacroMessages, HEBREW_MESSAGES } from 'superdoc-macros';
|
|
121
|
+
|
|
122
|
+
setMacroMessages(HEBREW_MESSAGES); // full Hebrew locale (included)
|
|
123
|
+
setMacroMessages({ scriptNotFound: '…' }); // or a partial override
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Persistence, import and export
|
|
121
127
|
|
|
122
128
|
```ts
|
|
123
129
|
import { createLocalStorage } from 'superdoc-macros';
|
|
124
130
|
|
|
125
131
|
const kit = new MacroKit({ host, storage: createLocalStorage('my-key') });
|
|
126
132
|
|
|
127
|
-
const json = kit.exportState(); //
|
|
133
|
+
const json = kit.exportState(); // backup / sharing
|
|
128
134
|
kit.importState(json, { merge: true });
|
|
129
135
|
```
|
|
130
136
|
|
|
131
|
-
`MacroStorage`
|
|
137
|
+
`MacroStorage` is a two-method interface — implement it to persist to a file (e.g. a plugin workspace).
|
|
138
|
+
|
|
139
|
+
Imports are strictly validated: every item and every recorded step is type-checked and size-bounded (see `IMPORT_LIMITS`), and one invalid item rejects the whole file — no partial imports.
|
|
140
|
+
|
|
141
|
+
## Shortcut safety
|
|
142
|
+
|
|
143
|
+
Saved bindings go through `kit.validateShortcut(shortcut, excludeId?)` — enforced on every save: a binding must parse, must carry a real modifier (Ctrl/Alt/Meta — a bare letter would fire on ordinary typing), must not collide with another saved item, and must not collide with shortcuts the host declared as reserved:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
const kit = new MacroKit({ host, reservedShortcuts: ['Ctrl+S', 'Ctrl+P', /* … the editor's registry … */] });
|
|
147
|
+
```
|
|
132
148
|
|
|
133
|
-
##
|
|
149
|
+
## Connecting a different host
|
|
134
150
|
|
|
135
|
-
|
|
151
|
+
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
152
|
|
|
137
|
-
##
|
|
153
|
+
## Known limitations
|
|
138
154
|
|
|
139
|
-
-
|
|
140
|
-
-
|
|
141
|
-
- `deleteBackward`
|
|
142
|
-
-
|
|
155
|
+
- No VBA execution. `.docm` files open normally but their macros are not run.
|
|
156
|
+
- The recorder does not capture caret movement or mouse selection (as in Word — replay acts from the current caret).
|
|
157
|
+
- `deleteBackward` and full-document text use the engine's internal view (ProseMirror) — available in the browser, not headless.
|
|
158
|
+
- The `eval` runner's time cap cannot stop an infinite synchronous loop (the iframe runner's can).
|
|
143
159
|
|
|
144
|
-
##
|
|
160
|
+
## Development
|
|
145
161
|
|
|
146
162
|
```bash
|
|
147
163
|
npm install
|
|
148
|
-
npm test # vitest
|
|
149
|
-
npm run build # tsc
|
|
164
|
+
npm test # vitest
|
|
165
|
+
npm run build # tsc → dist/
|
|
150
166
|
```
|
|
151
167
|
|
|
152
|
-
|
|
153
|
-
(.github/workflows/release.yml) מפרסם ל-npm ויוצר GitHub Release אוטומטית.
|
|
168
|
+
**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
169
|
|
|
155
|
-
##
|
|
170
|
+
## License
|
|
156
171
|
|
|
157
172
|
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,19 @@ 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;
|
|
105
|
+
/**
|
|
106
|
+
* Whether the adapter may fall back to the engine's internal ProseMirror
|
|
107
|
+
* view for the operations that have no public surface: backward/forward
|
|
108
|
+
* deletion, full-document text, and insertion when the Document API is
|
|
109
|
+
* missing. Default: true. Hosts that want to stay strictly on public
|
|
110
|
+
* surfaces set false — those operations then fail closed.
|
|
111
|
+
*/
|
|
112
|
+
viewFallback?: boolean;
|
|
124
113
|
}
|
|
125
114
|
export interface SuperdocMacroHost extends MacroHost {
|
|
126
|
-
/**
|
|
115
|
+
/** Removes the observation wrapper and the DOM listeners. Call before swapping documents. */
|
|
127
116
|
dispose(): void;
|
|
128
117
|
}
|
|
129
118
|
export declare function createSuperdocHost(options: SuperdocHostOptions): SuperdocMacroHost;
|