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/dist/storage.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
- * שמירת המאקרו, ההקלטות והקטעים. ברירת המחדל: localStorage; הממשק ניתן
3
- * להחלפה כדי שמארח יוכל לשמור בקובץ (למשל ב-workspace של תוסף אוצריא).
2
+ * Persistence for macros, recordings and snippets. Default: localStorage;
3
+ * the interface is swappable so a host can persist to a file (e.g. a
4
+ * plugin workspace).
4
5
  */
5
6
  import type { RecordedMacro, SavedScript, Snippet } from './types.js';
6
7
  export interface PersistedMacroState {
@@ -10,15 +11,38 @@ export interface PersistedMacroState {
10
11
  snippets: Snippet[];
11
12
  }
12
13
  export interface MacroStorage {
13
- /** `null` כשאין מצב שמור או כשהשמור אינו קריא. */
14
+ /** `null` when there is no saved state or the saved state is unreadable. */
14
15
  load(): PersistedMacroState | null;
15
16
  save(state: PersistedMacroState): void;
16
17
  }
17
18
  export declare function emptyState(): PersistedMacroState;
18
- /** מפענחת מצב שמור. `null` על כל צורה לא צפויה — לא זורקת. */
19
+ /**
20
+ * Caps on imported data. Imports come from files users share with each
21
+ * other, so every field is validated and bounded — a malformed or oversized
22
+ * export must fail closed, not wedge the store or the UI.
23
+ */
24
+ export declare const IMPORT_LIMITS: {
25
+ /** Whole-file size, in UTF-16 code units of the JSON string. */
26
+ readonly maxJsonLength: 5000000;
27
+ /** Per list: scripts, recordings, snippets. */
28
+ readonly maxItems: 500;
29
+ readonly maxStepsPerRecording: 5000;
30
+ readonly maxNameLength: 200;
31
+ readonly maxShortcutLength: 60;
32
+ readonly maxTriggerLength: 60;
33
+ /** Snippet text and single recorded insert-text step. */
34
+ readonly maxTextLength: 100000;
35
+ readonly maxSourceLength: 200000;
36
+ };
37
+ /**
38
+ * Parses saved/imported state. `null` on any unexpected shape, oversized
39
+ * field or oversized file — never throws, never partially accepts: one
40
+ * invalid item rejects the whole document, so the caller can tell the user
41
+ * the file is bad instead of silently importing a subset.
42
+ */
19
43
  export declare function parsePersistedState(json: string): PersistedMacroState | null;
20
44
  export declare const DEFAULT_STORAGE_KEY = "superdoc-macros:v1";
21
- /** localStorage עם הגנות: גישה חסומה או מלאה אינה מפילה את הערכה. */
45
+ /** localStorage with guards: blocked or full storage must not take the toolkit down. */
22
46
  export declare function createLocalStorage(key?: string, storage?: Pick<Storage, 'getItem' | 'setItem'>): MacroStorage;
23
- /** אחסון בזיכרוןלבדיקות ולמצבים שבהם אין persistence. */
47
+ /** In-memory storagefor tests and for setups with no persistence. */
24
48
  export declare function createMemoryStorage(): MacroStorage;
package/dist/storage.js CHANGED
@@ -1,17 +1,105 @@
1
1
  export function emptyState() {
2
2
  return { version: 1, scripts: [], recordings: [], snippets: [] };
3
3
  }
4
+ /**
5
+ * Caps on imported data. Imports come from files users share with each
6
+ * other, so every field is validated and bounded — a malformed or oversized
7
+ * export must fail closed, not wedge the store or the UI.
8
+ */
9
+ export const IMPORT_LIMITS = {
10
+ /** Whole-file size, in UTF-16 code units of the JSON string. */
11
+ maxJsonLength: 5_000_000,
12
+ /** Per list: scripts, recordings, snippets. */
13
+ maxItems: 500,
14
+ maxStepsPerRecording: 5_000,
15
+ maxNameLength: 200,
16
+ maxShortcutLength: 60,
17
+ maxTriggerLength: 60,
18
+ /** Snippet text and single recorded insert-text step. */
19
+ maxTextLength: 100_000,
20
+ maxSourceLength: 200_000,
21
+ };
22
+ function boundedString(value, maxLength, allowEmpty = false) {
23
+ return typeof value === 'string' && value.length <= maxLength && (allowEmpty || value.length > 0);
24
+ }
25
+ function optionalBoundedString(value, maxLength) {
26
+ return value === undefined || boundedString(value, maxLength);
27
+ }
28
+ function isValidStep(value) {
29
+ if (typeof value !== 'object' || value === null)
30
+ return false;
31
+ const step = value;
32
+ switch (step.type) {
33
+ case 'command':
34
+ // The payload is opaque engine data; the id is what replay dispatches on.
35
+ return boundedString(step.id, IMPORT_LIMITS.maxNameLength);
36
+ case 'insert-text':
37
+ return boundedString(step.text, IMPORT_LIMITS.maxTextLength, true);
38
+ case 'insert-paragraph':
39
+ return true;
40
+ case 'delete-backward':
41
+ case 'delete-forward':
42
+ return typeof step.count === 'number' && Number.isInteger(step.count) && step.count > 0 && step.count <= IMPORT_LIMITS.maxTextLength;
43
+ default:
44
+ return false;
45
+ }
46
+ }
47
+ function isValidScript(value) {
48
+ if (typeof value !== 'object' || value === null)
49
+ return false;
50
+ const script = value;
51
+ return (boundedString(script.id, IMPORT_LIMITS.maxNameLength) &&
52
+ boundedString(script.name, IMPORT_LIMITS.maxNameLength) &&
53
+ boundedString(script.source, IMPORT_LIMITS.maxSourceLength, true) &&
54
+ optionalBoundedString(script.shortcut, IMPORT_LIMITS.maxShortcutLength));
55
+ }
56
+ function isValidRecording(value) {
57
+ if (typeof value !== 'object' || value === null)
58
+ return false;
59
+ const recording = value;
60
+ return (recording.version === 1 &&
61
+ boundedString(recording.id, IMPORT_LIMITS.maxNameLength) &&
62
+ boundedString(recording.name, IMPORT_LIMITS.maxNameLength) &&
63
+ optionalBoundedString(recording.createdAt, IMPORT_LIMITS.maxNameLength) &&
64
+ optionalBoundedString(recording.shortcut, IMPORT_LIMITS.maxShortcutLength) &&
65
+ Array.isArray(recording.steps) &&
66
+ recording.steps.length <= IMPORT_LIMITS.maxStepsPerRecording &&
67
+ recording.steps.every(isValidStep));
68
+ }
69
+ function isValidSnippet(value) {
70
+ if (typeof value !== 'object' || value === null)
71
+ return false;
72
+ const snippet = value;
73
+ return (boundedString(snippet.id, IMPORT_LIMITS.maxNameLength) &&
74
+ boundedString(snippet.name, IMPORT_LIMITS.maxNameLength) &&
75
+ boundedString(snippet.text, IMPORT_LIMITS.maxTextLength, true) &&
76
+ optionalBoundedString(snippet.trigger, IMPORT_LIMITS.maxTriggerLength) &&
77
+ optionalBoundedString(snippet.shortcut, IMPORT_LIMITS.maxShortcutLength));
78
+ }
4
79
  function isValidState(value) {
5
80
  if (typeof value !== 'object' || value === null)
6
81
  return false;
7
82
  const state = value;
8
83
  return (state.version === 1 &&
9
84
  Array.isArray(state.scripts) &&
85
+ state.scripts.length <= IMPORT_LIMITS.maxItems &&
86
+ state.scripts.every(isValidScript) &&
10
87
  Array.isArray(state.recordings) &&
11
- Array.isArray(state.snippets));
88
+ state.recordings.length <= IMPORT_LIMITS.maxItems &&
89
+ state.recordings.every(isValidRecording) &&
90
+ Array.isArray(state.snippets) &&
91
+ state.snippets.length <= IMPORT_LIMITS.maxItems &&
92
+ state.snippets.every(isValidSnippet));
12
93
  }
13
- /** מפענחת מצב שמור. `null` על כל צורה לא צפויה — לא זורקת. */
94
+ /**
95
+ * Parses saved/imported state. `null` on any unexpected shape, oversized
96
+ * field or oversized file — never throws, never partially accepts: one
97
+ * invalid item rejects the whole document, so the caller can tell the user
98
+ * the file is bad instead of silently importing a subset.
99
+ */
14
100
  export function parsePersistedState(json) {
101
+ if (json.length > IMPORT_LIMITS.maxJsonLength)
102
+ return null;
15
103
  try {
16
104
  const parsed = JSON.parse(json);
17
105
  return isValidState(parsed) ? parsed : null;
@@ -21,7 +109,7 @@ export function parsePersistedState(json) {
21
109
  }
22
110
  }
23
111
  export const DEFAULT_STORAGE_KEY = 'superdoc-macros:v1';
24
- /** localStorage עם הגנות: גישה חסומה או מלאה אינה מפילה את הערכה. */
112
+ /** localStorage with guards: blocked or full storage must not take the toolkit down. */
25
113
  export function createLocalStorage(key = DEFAULT_STORAGE_KEY, storage) {
26
114
  const backing = () => {
27
115
  if (storage)
@@ -48,12 +136,12 @@ export function createLocalStorage(key = DEFAULT_STORAGE_KEY, storage) {
48
136
  backing()?.setItem(key, JSON.stringify(state));
49
137
  }
50
138
  catch (error) {
51
- console.warn('[superdoc-macros] שמירת המאקרו נכשלה', error);
139
+ console.warn('[superdoc-macros] saving macros failed', error);
52
140
  }
53
141
  },
54
142
  };
55
143
  }
56
- /** אחסון בזיכרוןלבדיקות ולמצבים שבהם אין persistence. */
144
+ /** In-memory storagefor tests and for setups with no persistence. */
57
145
  export function createMemoryStorage() {
58
146
  let saved = null;
59
147
  return {
package/dist/types.d.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  /**
2
- * החוזים המשותפים של הערכה.
2
+ * Shared contracts.
3
3
  *
4
- * `MacroHost` הוא נקודת החיבור היחידה לעורך: כל שלוש היכולות (סקריפטים,
5
- * מקליט, קטעי טקסט) עובדות מולו ולא מול SuperDoc ישירות. כך אפשר לבדוק את
6
- * הערכה עם כפיל בזיכרון, וכך מארח אחר (גרסת מנוע אחרת, עורך אחר) מתחבר
7
- * במימוש אחד של הממשק הזה.
4
+ * `MacroHost` is the single connection point to the editor: all three
5
+ * capabilities (scripts, recorder, snippets) work against it rather than
6
+ * against SuperDoc directly. That is what makes the toolkit testable with an
7
+ * in-memory double, and what lets another host (a different engine version,
8
+ * a different editor) plug in with one implementation of this interface.
8
9
  */
9
- /** תוצאת פעולה. אותה צורה כמו `CommandOutcome` של otzaria-word-editor. */
10
+ /** Result of an operation. Same shape as otzaria-word-editor's `CommandOutcome`. */
10
11
  export type MacroOutcome = {
11
12
  ok: true;
12
13
  } | {
@@ -14,20 +15,20 @@ export type MacroOutcome = {
14
15
  message: string;
15
16
  reason?: string;
16
17
  };
17
- /** תצלום הבחירה במסמך ברגע הקריאה. */
18
+ /** Snapshot of the document selection at the moment of the call. */
18
19
  export interface SelectionSnapshot {
19
- /** הטקסט המסומן. `''` כשאין בחירה או כשלא התבקש. */
20
+ /** The selected text. `''` when there is no selection or it was not requested. */
20
21
  text: string;
21
- /** האם יש טווח מסומן ולא רק סמן. */
22
+ /** Whether a range is selected, as opposed to a caret only. */
22
23
  hasRange: boolean;
23
- /** מזהה הפסקה שהבחירה מתחילה בה, או `null`. */
24
+ /** Id of the block the selection starts in, or `null`. */
24
25
  blockId: string | null;
25
- /** היעד שפעולות כתיבה (`insert`) צורכות. אטוםנמסר חזרה למנוע כמו שהוא. */
26
+ /** The target that write operations (`insert`) consume. Opaquehanded back to the engine as-is. */
26
27
  selectionTarget: unknown | null;
27
- /** האם הבחירה ריקה (סמן בלבד). */
28
+ /** Whether the selection is empty (caret only). */
28
29
  empty: boolean;
29
30
  }
30
- /** אירוע הקלדה שהמארח מדווח למקליט ולהשלמה האוטומטית. */
31
+ /** A typing event the host reports to the recorder and to auto-text. */
31
32
  export type TextInputEvent = {
32
33
  kind: 'insert-text';
33
34
  text: string;
@@ -39,40 +40,42 @@ export type TextInputEvent = {
39
40
  kind: 'delete-forward';
40
41
  };
41
42
  /**
42
- * מה שהערכה צריכה מהעורך. מימוש ל-SuperDoc v2 נמצא ב-`createSuperdocHost`;
43
- * לבדיקות יש כפיל בזיכרון.
43
+ * What the toolkit needs from the editor. The SuperDoc v2 implementation is
44
+ * `createSuperdocHost`; tests use an in-memory double.
44
45
  */
45
46
  export interface MacroHost {
46
47
  commands: {
47
- /** האם המנוע מכיר את הפקודה. */
48
+ /** Whether the engine recognizes the command. */
48
49
  has(id: string): boolean;
49
- /** מריצה פקודה מהקטלוג של המנוע ומחזירה תוצאה מנורמלת. */
50
+ /** Runs a command from the engine's catalog and returns a normalized outcome. */
50
51
  execute(id: string, payload?: unknown): Promise<MacroOutcome>;
51
- /** מזהי הפקודות המוכרות, אם המארח יודע למנות אותם. */
52
+ /** The known command ids, when the host can enumerate them. */
52
53
  ids(): readonly string[];
53
54
  };
54
- /** מכניסה טקסט במיקום הסמן (או בסוף המסמך כשאין סמן). */
55
+ /** Inserts text at the caret (or at the end of the document when there is no caret). */
55
56
  insertText(text: string): Promise<MacroOutcome>;
56
- /** מוחקת תווים לאחור מהסמן. */
57
+ /** Deletes characters backwards from the caret. */
57
58
  deleteBackward(count: number): Promise<MacroOutcome>;
58
- /** תצלום הבחירה הנוכחית. לעולם לא זורקת. */
59
+ /** Deletes characters forwards from the caret. */
60
+ deleteForward(count: number): Promise<MacroOutcome>;
61
+ /** Snapshot of the current selection. Never throws. */
59
62
  getSelection(options?: {
60
63
  includeText?: boolean;
61
64
  }): Promise<SelectionSnapshot>;
62
- /** מחליפה את כל המופעים של `query` ב-`replacement`. מחזירה כמה הוחלפו. */
65
+ /** Replaces every occurrence of `query` with `replacement`. Returns how many were replaced. */
63
66
  replaceAll(query: string, replacement: string): Promise<{
64
67
  ok: boolean;
65
68
  replaced: number;
66
69
  message?: string;
67
70
  }>;
68
- /** הטקסט המלא של גוף המסמך. `''` כשאינו זמין. */
71
+ /** The full text of the document body. `''` when unavailable. */
69
72
  getDocumentText(): Promise<string>;
70
- /** מאזינה לכל פקודה שהמנוע מריץ (מכל מקור). מחזירה פונקציית ביטול. */
73
+ /** Observes every command the engine runs (from any source). Returns a dispose function. */
71
74
  onCommand(listener: (id: string, payload: unknown) => void): () => void;
72
- /** מאזינה להקלדה במסמך. מחזירה פונקציית ביטול. */
75
+ /** Observes typing in the document. Returns a dispose function. */
73
76
  onTextInput(listener: (event: TextInputEvent) => void): () => void;
74
77
  }
75
- /** צעד אחד במאקרו מוקלט. JSON-serializable במלואו. */
78
+ /** One step of a recorded macro. Fully JSON-serializable. */
76
79
  export type MacroStep = {
77
80
  type: 'command';
78
81
  id: string;
@@ -89,7 +92,7 @@ export type MacroStep = {
89
92
  type: 'delete-forward';
90
93
  count: number;
91
94
  };
92
- /** מאקרו מוקלט, כפי שהוא נשמר ומיובא/מיוצא. */
95
+ /** A recorded macro, as persisted and imported/exported. */
93
96
  export interface RecordedMacro {
94
97
  version: 1;
95
98
  id: string;
@@ -99,21 +102,21 @@ export interface RecordedMacro {
99
102
  shortcut?: string;
100
103
  steps: MacroStep[];
101
104
  }
102
- /** מאקרו כתובסקריפט JavaScript שרץ מול ה-API של הערכה. */
105
+ /** A written macro a JavaScript script that runs against the toolkit's API. */
103
106
  export interface SavedScript {
104
107
  id: string;
105
108
  name: string;
106
109
  source: string;
107
110
  shortcut?: string;
108
111
  }
109
- /** קטע טקסט (Snippet / AutoText). */
112
+ /** A text snippet (AutoText building block). */
110
113
  export interface Snippet {
111
114
  id: string;
112
115
  name: string;
113
- /** תוכן הקטע. תומך במשתני `{{...}}` — ראו `renderSnippet`. */
116
+ /** Snippet content. Supports `{{...}}` variables see `renderSnippet`. */
114
117
  text: string;
115
- /** מילת הפעלה להשלמה אוטומטית: הקלדת המילה ואחריה רווח מחליפה אותה בתוכן. */
118
+ /** Auto-text trigger word: typing the word followed by a space replaces it with the content. */
116
119
  trigger?: string;
117
- /** קיצור מקלדת, למשל `Ctrl+Alt+1`. */
120
+ /** Keyboard shortcut, e.g. `Ctrl+Alt+1`. */
118
121
  shortcut?: string;
119
122
  }
package/dist/types.js CHANGED
@@ -1,9 +1,10 @@
1
1
  /**
2
- * החוזים המשותפים של הערכה.
2
+ * Shared contracts.
3
3
  *
4
- * `MacroHost` הוא נקודת החיבור היחידה לעורך: כל שלוש היכולות (סקריפטים,
5
- * מקליט, קטעי טקסט) עובדות מולו ולא מול SuperDoc ישירות. כך אפשר לבדוק את
6
- * הערכה עם כפיל בזיכרון, וכך מארח אחר (גרסת מנוע אחרת, עורך אחר) מתחבר
7
- * במימוש אחד של הממשק הזה.
4
+ * `MacroHost` is the single connection point to the editor: all three
5
+ * capabilities (scripts, recorder, snippets) work against it rather than
6
+ * against SuperDoc directly. That is what makes the toolkit testable with an
7
+ * in-memory double, and what lets another host (a different engine version,
8
+ * a different editor) plug in with one implementation of this interface.
8
9
  */
9
10
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superdoc-macros",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Macro toolkit for SuperDoc-based editors: sandboxed scripted macros, a Word-style macro recorder, and snippets with auto-text expansion.",
5
5
  "license": "MIT",
6
6
  "type": "module",