@tarhnama/core 0.1.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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +16 -0
  3. package/dist/code-tokens.d.ts +39 -0
  4. package/dist/code-tokens.d.ts.map +1 -0
  5. package/dist/code-tokens.js +189 -0
  6. package/dist/code-tokens.js.map +1 -0
  7. package/dist/context-menu.d.ts +59 -0
  8. package/dist/context-menu.d.ts.map +1 -0
  9. package/dist/context-menu.js +69 -0
  10. package/dist/context-menu.js.map +1 -0
  11. package/dist/features.d.ts +114 -0
  12. package/dist/features.d.ts.map +1 -0
  13. package/dist/features.js +128 -0
  14. package/dist/features.js.map +1 -0
  15. package/dist/index.d.ts +18 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +12 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/nested.d.ts +66 -0
  20. package/dist/nested.d.ts.map +1 -0
  21. package/dist/nested.js +194 -0
  22. package/dist/nested.js.map +1 -0
  23. package/dist/ops.d.ts +120 -0
  24. package/dist/ops.d.ts.map +1 -0
  25. package/dist/ops.js +333 -0
  26. package/dist/ops.js.map +1 -0
  27. package/dist/shortcuts.d.ts +90 -0
  28. package/dist/shortcuts.d.ts.map +1 -0
  29. package/dist/shortcuts.js +179 -0
  30. package/dist/shortcuts.js.map +1 -0
  31. package/dist/spine.d.ts +92 -0
  32. package/dist/spine.d.ts.map +1 -0
  33. package/dist/spine.js +213 -0
  34. package/dist/spine.js.map +1 -0
  35. package/dist/styles.d.ts +63 -0
  36. package/dist/styles.d.ts.map +1 -0
  37. package/dist/styles.js +169 -0
  38. package/dist/styles.js.map +1 -0
  39. package/dist/tree.d.ts +70 -0
  40. package/dist/tree.d.ts.map +1 -0
  41. package/dist/tree.js +190 -0
  42. package/dist/tree.js.map +1 -0
  43. package/dist/types.d.ts +239 -0
  44. package/dist/types.d.ts.map +1 -0
  45. package/dist/types.js +47 -0
  46. package/dist/types.js.map +1 -0
  47. package/package.json +52 -0
  48. package/src/code-tokens.ts +236 -0
  49. package/src/context-menu.ts +139 -0
  50. package/src/features.ts +173 -0
  51. package/src/index.ts +58 -0
  52. package/src/nested.ts +271 -0
  53. package/src/ops.ts +341 -0
  54. package/src/shortcuts.ts +280 -0
  55. package/src/spine.ts +214 -0
  56. package/src/styles.ts +201 -0
  57. package/src/tree.ts +189 -0
  58. package/src/types.ts +275 -0
package/dist/ops.js ADDED
@@ -0,0 +1,333 @@
1
+ import { BODY, MAX_LEVEL, } from './types.js';
2
+ import { ancestors, buildIndex, isLocked, prevHeading, subtreeRange } from './tree.js';
3
+ /**
4
+ * Structural operations. All are pure: they take a flat list and return a new one,
5
+ * sharing unchanged blocks by reference so downstream memoization works.
6
+ *
7
+ * The defining rule of this module: an operation on a heading applies to its whole
8
+ * SUBTREE. Indenting a heading and leaving its children behind — the bug in the
9
+ * earlier Tiptap scaffold — produces an orphaned document.
10
+ */
11
+ /**
12
+ * Can `i` be indented?
13
+ *
14
+ * The rule prevents skipped levels: a heading may go one deeper than the nearest
15
+ * preceding heading, never more. Without this you can indent from level 1 straight
16
+ * to level 3 and create a block with no representable parent.
17
+ *
18
+ * Body text has no level, so indenting it is meaningless — use `setLevel` to promote
19
+ * body text into a heading instead.
20
+ */
21
+ export function canIndent(blocks, i) {
22
+ const block = blocks[i];
23
+ if (!block || block.level === BODY)
24
+ return false;
25
+ // A locked block's position is fixed. Checked here rather than in `indent` so the UI
26
+ // can grey the control out instead of offering an action that silently does nothing.
27
+ if (isLocked(blocks, buildIndex(blocks), i))
28
+ return false;
29
+ const prev = prevHeading(blocks, i);
30
+ if (prev === -1)
31
+ return false; // first heading in the document: nothing to nest under
32
+ if (block.level > blocks[prev].level)
33
+ return false; // already a child of prev
34
+ // The deepest descendant must still fit within MAX_LEVEL after the shift.
35
+ const index = buildIndex(blocks);
36
+ const end = index.subtreeEnd[i];
37
+ for (let j = i; j < end; j++) {
38
+ const l = blocks[j].level;
39
+ if (l !== BODY && l + 1 > MAX_LEVEL)
40
+ return false;
41
+ }
42
+ return true;
43
+ }
44
+ export function indent(blocks, i) {
45
+ if (!canIndent(blocks, i))
46
+ return blocks;
47
+ return shiftSubtree(blocks, i, +1);
48
+ }
49
+ /** Level 1 cannot outdent — there is nothing above it. Body text has no level. */
50
+ export function canOutdent(blocks, i) {
51
+ const block = blocks[i];
52
+ if (!block || block.level < 2)
53
+ return false;
54
+ return !isLocked(blocks, buildIndex(blocks), i);
55
+ }
56
+ /**
57
+ * Outdent a heading and its subtree.
58
+ *
59
+ * Pleasant property of the flat model: the following siblings that were at the
60
+ * outdented block's old level automatically become its children, which is exactly
61
+ * what Workflowy and Word's outline view do — no extra code needed.
62
+ */
63
+ export function outdent(blocks, i) {
64
+ if (!canOutdent(blocks, i))
65
+ return blocks;
66
+ return shiftSubtree(blocks, i, -1);
67
+ }
68
+ function shiftSubtree(blocks, i, delta) {
69
+ const index = buildIndex(blocks);
70
+ const end = index.subtreeEnd[i];
71
+ const next = blocks.slice();
72
+ for (let j = i; j < end; j++) {
73
+ const b = blocks[j];
74
+ if (b.level === BODY)
75
+ continue; // body text has no level to shift
76
+ next[j] = { ...b, level: (b.level + delta) };
77
+ }
78
+ return next;
79
+ }
80
+ /** Set a block's level directly — used to promote body text to a heading and back. */
81
+ export function canSetLevel(blocks, i) {
82
+ return !!blocks[i] && !isLocked(blocks, buildIndex(blocks), i);
83
+ }
84
+ export function setLevel(blocks, i, level) {
85
+ if (!canSetLevel(blocks, i))
86
+ return blocks;
87
+ const block = blocks[i];
88
+ if (!block || block.level === level)
89
+ return blocks;
90
+ const next = blocks.slice();
91
+ next[i] = { ...block, level };
92
+ return next;
93
+ }
94
+ export function toggleCollapse(blocks, i) {
95
+ const block = blocks[i];
96
+ if (!block)
97
+ return blocks;
98
+ const next = blocks.slice();
99
+ next[i] = { ...block, collapsed: !block.collapsed };
100
+ return next;
101
+ }
102
+ /**
103
+ * Expand every collapsed ancestor of a block, so it can actually be reached.
104
+ *
105
+ * Needed wherever the app navigates to a block the user did not click: a search result, a
106
+ * cross-document link, a backlink. A collapsed subtree is ABSENT from the DOM, not hidden,
107
+ * so scrolling to it finds nothing and the jump silently does nothing at all — which reads
108
+ * as a broken feature rather than as "that match is folded away".
109
+ *
110
+ * Only ancestors are touched. The block's own collapse state is left alone: revealing a
111
+ * heading should not also unfold everything beneath it.
112
+ *
113
+ * Returns the SAME list when nothing was collapsed.
114
+ */
115
+ export function expandAncestors(blocks, blockId) {
116
+ const at = blocks.findIndex((block) => block.id === blockId);
117
+ if (at === -1)
118
+ return blocks;
119
+ const index = buildIndex(blocks);
120
+ const toOpen = new Set(ancestors(index, at).filter((i) => blocks[i]?.collapsed === true));
121
+ if (toOpen.size === 0)
122
+ return blocks;
123
+ return blocks.map((block, i) => (toOpen.has(i) ? { ...block, collapsed: false } : block));
124
+ }
125
+ export function setDirection(blocks, i, dir) {
126
+ const block = blocks[i];
127
+ if (!block || block.dir === dir)
128
+ return blocks;
129
+ const next = blocks.slice();
130
+ next[i] = { ...block, dir };
131
+ return next;
132
+ }
133
+ /**
134
+ * Move the subtree rooted at `from` so it begins at document position `to`.
135
+ *
136
+ * `to` is interpreted against the ORIGINAL list. Moving a subtree into itself is a
137
+ * no-op rather than an error, because drag-and-drop will attempt it constantly.
138
+ */
139
+ /**
140
+ * Can the subtree at `from` be moved to `to`?
141
+ *
142
+ * Both ends matter: a locked subtree cannot be dragged away, and nothing may be dropped
143
+ * INTO a locked one. Checking only the source is the easy mistake — it lets a reader
144
+ * append into a chapter they are not allowed to change.
145
+ */
146
+ export function canMoveSubtree(blocks, from, to) {
147
+ const index = buildIndex(blocks);
148
+ if (!blocks[from])
149
+ return false;
150
+ if (isLocked(blocks, index, from))
151
+ return false;
152
+ // A drop lands BETWEEN blocks, so the block before the insertion point is checked as
153
+ // well: landing just after a locked heading would place the block inside its subtree.
154
+ const before = to - 1;
155
+ if (before >= 0 && before < blocks.length && isLocked(blocks, index, before)) {
156
+ const [, end] = subtreeRange(index, before);
157
+ if (to < end)
158
+ return false;
159
+ }
160
+ if (to < blocks.length && isLocked(blocks, index, to))
161
+ return false;
162
+ return true;
163
+ }
164
+ export function moveSubtree(blocks, from, to) {
165
+ const index = buildIndex(blocks);
166
+ const end = index.subtreeEnd[from];
167
+ if (to >= from && to <= end)
168
+ return blocks; // dropping inside itself
169
+ if (!canMoveSubtree(blocks, from, to))
170
+ return blocks;
171
+ const slice = blocks.slice(from, end);
172
+ const rest = [...blocks.slice(0, from), ...blocks.slice(end)];
173
+ const insertAt = to > from ? to - (end - from) : to;
174
+ return [...rest.slice(0, insertAt), ...slice, ...rest.slice(insertAt)];
175
+ }
176
+ /**
177
+ * Repair skipped levels — the import path for real Word documents, which jump from
178
+ * H1 to H3 constantly.
179
+ *
180
+ * Each heading is clamped to at most one deeper than the preceding heading, and the
181
+ * first heading is clamped to 1. Body text is untouched.
182
+ */
183
+ export function normalizeLevels(blocks) {
184
+ let changed = false;
185
+ const next = blocks.slice();
186
+ let prevLevel = 0;
187
+ for (let i = 0; i < blocks.length; i++) {
188
+ const b = blocks[i];
189
+ if (b.level === BODY)
190
+ continue;
191
+ const max = (prevLevel + 1);
192
+ const level = b.level > max ? max : b.level;
193
+ if (level !== b.level) {
194
+ next[i] = { ...b, level };
195
+ changed = true;
196
+ }
197
+ prevLevel = level;
198
+ }
199
+ return changed ? next : blocks;
200
+ }
201
+ /** True when no heading is more than one level deeper than the one before it. */
202
+ export function isNormalized(blocks) {
203
+ let prevLevel = 0;
204
+ for (const b of blocks) {
205
+ if (b.level === BODY)
206
+ continue;
207
+ if (b.level > prevLevel + 1)
208
+ return false;
209
+ prevLevel = b.level;
210
+ }
211
+ return true;
212
+ }
213
+ /**
214
+ * Lock or unlock a block. Locking a heading makes its whole subtree read-only by
215
+ * derivation — only the heading itself carries the flag.
216
+ */
217
+ export function setLocked(blocks, i, locked) {
218
+ const block = blocks[i];
219
+ if (!block || (block.locked ?? false) === locked)
220
+ return blocks;
221
+ const out = [...blocks];
222
+ // Unlocking REMOVES the flag rather than setting it to `false`, so a document that
223
+ // never used locking serializes exactly as it did before the feature existed.
224
+ out[i] = locked ? { ...block, locked: true } : stripLocked(block);
225
+ return out;
226
+ }
227
+ function stripLocked(block) {
228
+ const { locked: _locked, ...rest } = block;
229
+ return rest;
230
+ }
231
+ /**
232
+ * The previous sibling of the block at `i`, or -1.
233
+ *
234
+ * A sibling is the nearest preceding block at the SAME level that shares a parent — which
235
+ * for this model means: walking backwards, the first block at that level, stopping if a
236
+ * shallower one appears first, because that is the parent and the run has ended.
237
+ */
238
+ function previousSibling(blocks, i) {
239
+ const level = blocks[i]?.level;
240
+ if (level === undefined)
241
+ return -1;
242
+ for (let j = i - 1; j >= 0; j--) {
243
+ const other = blocks[j]?.level;
244
+ if (other === undefined)
245
+ continue;
246
+ if (other === level)
247
+ return j;
248
+ // Body text (level 0) sits under headings and never terminates a heading's run, but a
249
+ // heading shallower than us IS our parent and there is no earlier sibling under it.
250
+ if (other !== BODY && other < level)
251
+ return -1;
252
+ if (level === BODY && other !== BODY)
253
+ return -1;
254
+ }
255
+ return -1;
256
+ }
257
+ /**
258
+ * Move a node before its previous sibling, subtree and all.
259
+ *
260
+ * "Up" is **a sibling**, not a row. A heading with forty blocks under it must jump the
261
+ * whole of its neighbour's subtree; swapping single rows would tear both apart, and the
262
+ * design's درخت menu calls this "move node" for that reason.
263
+ *
264
+ * A first sibling does not move. The tempting alternative — promote it above its parent —
265
+ * changes the node's LEVEL as a side effect of a move, and a command that quietly does two
266
+ * things is one users stop trusting. Indent and outdent are how a level changes.
267
+ */
268
+ export function moveBlockUp(blocks, blockId) {
269
+ const at = blocks.findIndex((block) => block.id === blockId);
270
+ if (at === -1)
271
+ return blocks;
272
+ const target = previousSibling(blocks, at);
273
+ if (target === -1)
274
+ return blocks;
275
+ return moveSubtree(blocks, at, target);
276
+ }
277
+ /**
278
+ * Move a node after its next sibling, subtree and all.
279
+ *
280
+ * The target is the END of the sibling's subtree, in ORIGINAL indices — `moveSubtree`
281
+ * interprets `to` against the list it was given and does the shift arithmetic itself.
282
+ * Pre-adjusting by the moved subtree's size (the obvious defensive move) lands the target
283
+ * back inside the node being moved, where `moveSubtree`'s "dropping inside itself" guard
284
+ * correctly refuses it and the command silently does nothing.
285
+ */
286
+ export function moveBlockDown(blocks, blockId) {
287
+ const at = blocks.findIndex((block) => block.id === blockId);
288
+ if (at === -1)
289
+ return blocks;
290
+ const index = buildIndex(blocks);
291
+ const [, end] = subtreeRange(index, at);
292
+ if (end >= blocks.length)
293
+ return blocks;
294
+ const level = blocks[at]?.level;
295
+ const nextLevel = blocks[end]?.level;
296
+ if (level === undefined || nextLevel === undefined)
297
+ return blocks;
298
+ // The block after our subtree is only a SIBLING if it is at our level; anything
299
+ // shallower is the end of our parent's run and there is nothing left to swap with.
300
+ if (nextLevel !== level)
301
+ return blocks;
302
+ const [, siblingEnd] = subtreeRange(index, end);
303
+ return moveSubtree(blocks, at, siblingEnd);
304
+ }
305
+ /**
306
+ * The level this heading skipped over, or `null`.
307
+ *
308
+ * `isNormalized` answers "does this document skip anywhere", which is enough to enable a
309
+ * repair command and useless for showing the reader WHERE. Frame 1a puts an amber
310
+ * «سطح ۳ جا افتاده» chip on the offending heading, so the question has to be asked of a
311
+ * single block.
312
+ *
313
+ * Returns the FIRST missing level, not the deepest. When 2 → 4 skips only 3 that is the
314
+ * same answer either way, but 1 → 4 skips both 2 and 3, and 2 is the one to add first —
315
+ * naming 3 would send someone to fix the wrong end of the gap.
316
+ *
317
+ * Body text is never an answer and never breaks the chain: level 0 is not a hierarchy
318
+ * level, it belongs to the nearest preceding heading, and a paragraph between two headings
319
+ * does not make the second one a skip.
320
+ */
321
+ export function skippedLevelAt(blocks, i) {
322
+ const block = blocks[i];
323
+ if (!block || block.level === BODY)
324
+ return null;
325
+ const prev = prevHeading(blocks, i);
326
+ // A document that opens below level 1 has no representable parent for its first heading.
327
+ const parentLevel = prev === -1 ? 0 : (blocks[prev]?.level ?? 0);
328
+ // Going back up, or one step down, is never a skip.
329
+ if (block.level <= parentLevel + 1)
330
+ return null;
331
+ return (parentLevel + 1);
332
+ }
333
+ //# sourceMappingURL=ops.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ops.js","sourceRoot":"","sources":["../src/ops.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ import { type OutlineFeatureName, type OutlineFeatures } from './features.js';
2
+ /**
3
+ * The keyboard shortcuts, as DATA.
4
+ *
5
+ * A hard-coded `addKeyboardShortcuts` in the editor extension has three problems, and each
6
+ * one lands on a different person:
7
+ *
8
+ * 1. A host cannot CHANGE it. Every embedder inherits `Tab` and `Mod-.` whether those
9
+ * collide with their own application or not — and `Tab` in particular is the web's
10
+ * focus key, so an editor that eats it is one a keyboard user cannot leave.
11
+ * 2. A host cannot READ it. Rendering a shortcuts sheet means retyping the list by hand,
12
+ * and the copy drifts from the bindings the moment either changes.
13
+ * 3. Nobody can turn it off, per binding or at all.
14
+ *
15
+ * So the table is plain serializable data — no closures, no editor reference — and the
16
+ * Tiptap extension is generated from it. A settings screen can render it, store it, diff
17
+ * it and hand back an override map.
18
+ */
19
+ /** The commands a shortcut can be bound to. Names, not functions — see the note above. */
20
+ export type ShortcutCommand = 'indent' | 'outdent' | 'setLevel1' | 'setLevel2' | 'setLevel3' | 'setLevel4' | 'setLevel5' | 'setLevel6' | 'setBodyText' | 'toggleCollapse' | 'collapseSiblings' | 'insertZwnj' | 'undo' | 'redo' | 'bold' | 'italic' | 'underline' | 'strike' | 'moveUp' | 'moveDown';
21
+ export interface ShortcutBinding {
22
+ readonly command: ShortcutCommand;
23
+ /** ProseMirror's spelling: `Mod-` is ⌘ on Apple and Ctrl elsewhere. */
24
+ readonly key: string;
25
+ /** For grouping in a help sheet. */
26
+ readonly group: 'structure' | 'collapse' | 'formatting' | 'editing' | 'persian';
27
+ /**
28
+ * The feature this binding belongs to, if any.
29
+ *
30
+ * Lets `features={{ collapse: false }}` drop `Mod-.` without the host having to know
31
+ * which key that was. A binding with no feature is structural and always survives —
32
+ * indent and outdent ARE the outline, not a feature of it.
33
+ */
34
+ readonly feature?: OutlineFeatureName;
35
+ /** Shown in a help sheet. English; a host translates via its own label pack. */
36
+ readonly description: string;
37
+ }
38
+ /**
39
+ * The defaults.
40
+ *
41
+ * `Mod-1…6` rather than 1…9: levels 7–9 exist in the model because Word documents contain
42
+ * them, but no keyboard has a comfortable ninth digit and the ladder covers the rest.
43
+ */
44
+ export declare const DEFAULT_SHORTCUTS: readonly ShortcutBinding[];
45
+ export interface ShortcutOptions {
46
+ /** `false` binds nothing at all. */
47
+ readonly enabled?: boolean;
48
+ /**
49
+ * Rebind (`'Mod-Shift-c'`) or unbind (`null`) by command name.
50
+ *
51
+ * By command rather than by key, because the host knows what they want to move, not
52
+ * what it is currently bound to — and the current binding is exactly the thing they
53
+ * would have to keep in sync by hand.
54
+ */
55
+ readonly overrides?: Readonly<Partial<Record<ShortcutCommand, string | null>>>;
56
+ /** Bindings whose feature is off are dropped. */
57
+ readonly features?: Partial<OutlineFeatures> | readonly OutlineFeatureName[];
58
+ }
59
+ /**
60
+ * Apply a host's overrides and feature set to the default table.
61
+ *
62
+ * Collisions THROW. Letting a rebinding silently win means the losing command stops
63
+ * working with nothing logged, discoverable only by someone trying it and wondering what
64
+ * broke — and by then the override is somewhere in the host's configuration, far from the
65
+ * symptom.
66
+ */
67
+ export declare function resolveShortcuts(options?: ShortcutOptions): readonly ShortcutBinding[];
68
+ export interface ShortcutGroup {
69
+ readonly group: ShortcutBinding['group'];
70
+ readonly bindings: readonly ShortcutBinding[];
71
+ }
72
+ /**
73
+ * Group the live bindings for display.
74
+ *
75
+ * The order is fixed rather than derived from the data: a sheet that reshuffles between
76
+ * openings cannot be learned, and learning it is the only reason it exists.
77
+ */
78
+ export declare function shortcutsFor(bindings: readonly ShortcutBinding[]): readonly ShortcutGroup[];
79
+ /** Which glyph set to render. Callers pass their own platform check. */
80
+ export type ShortcutPlatform = 'apple' | 'other';
81
+ /**
82
+ * A binding as a person reads it.
83
+ *
84
+ * `Mod` is ProseMirror's platform-agnostic spelling and must never reach a user: nobody
85
+ * has a Mod key. Apple users read glyphs with no separator, everyone else reads words
86
+ * joined by `+`, and getting that backwards is one of the small things that makes software
87
+ * feel foreign.
88
+ */
89
+ export declare function formatShortcut(key: string, platform: ShortcutPlatform): string;
90
+ //# sourceMappingURL=shortcuts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shortcuts.d.ts","sourceRoot":"","sources":["../src/shortcuts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAE/F;;;;;;;;;;;;;;;;GAgBG;AAEH,0FAA0F;AAC1F,MAAM,MAAM,eAAe,GACvB,QAAQ,GACR,SAAS,GACT,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,aAAa,GACb,gBAAgB,GAChB,kBAAkB,GAClB,YAAY,GACZ,MAAM,GACN,MAAM,GACN,MAAM,GACN,QAAQ,GACR,WAAW,GACX,QAAQ,GACR,QAAQ,GACR,UAAU,CAAC;AAEf,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,uEAAuE;IACvE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,oCAAoC;IACpC,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,UAAU,GAAG,YAAY,GAAG,SAAS,GAAG,SAAS,CAAC;IAChF;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,kBAAkB,CAAC;IACtC,gFAAgF;IAChF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAAS,eAAe,EA4EvD,CAAC;AAIF,MAAM,WAAW,eAAe;IAC9B,oCAAoC;IACpC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAC/E,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,SAAS,kBAAkB,EAAE,CAAC;CAC9E;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,eAAe,EAAE,CA6CtF;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;IACzC,QAAQ,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;CAC/C;AAWD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,GAAG,SAAS,aAAa,EAAE,CAK3F;AAED,wEAAwE;AACxE,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,OAAO,CAAC;AAgBjD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,MAAM,CAU9E"}
@@ -0,0 +1,179 @@
1
+ import { resolveFeatures } from './features.js';
2
+ /**
3
+ * The defaults.
4
+ *
5
+ * `Mod-1…6` rather than 1…9: levels 7–9 exist in the model because Word documents contain
6
+ * them, but no keyboard has a comfortable ninth digit and the ladder covers the rest.
7
+ */
8
+ export const DEFAULT_SHORTCUTS = [
9
+ { command: 'indent', key: 'Tab', group: 'structure', description: 'Indent block' },
10
+ { command: 'outdent', key: 'Shift-Tab', group: 'structure', description: 'Outdent block' },
11
+ { command: 'setLevel1', key: 'Mod-1', group: 'structure', description: 'Heading 1' },
12
+ { command: 'setLevel2', key: 'Mod-2', group: 'structure', description: 'Heading 2' },
13
+ { command: 'setLevel3', key: 'Mod-3', group: 'structure', description: 'Heading 3' },
14
+ { command: 'setLevel4', key: 'Mod-4', group: 'structure', description: 'Heading 4' },
15
+ { command: 'setLevel5', key: 'Mod-5', group: 'structure', description: 'Heading 5' },
16
+ { command: 'setLevel6', key: 'Mod-6', group: 'structure', description: 'Heading 6' },
17
+ { command: 'setBodyText', key: 'Mod-0', group: 'structure', description: 'Body text' },
18
+ { command: 'moveUp', key: 'Alt-ArrowUp', group: 'structure', description: 'Move block up' },
19
+ { command: 'moveDown', key: 'Alt-ArrowDown', group: 'structure', description: 'Move block down' },
20
+ {
21
+ command: 'toggleCollapse',
22
+ key: 'Mod-.',
23
+ group: 'collapse',
24
+ feature: 'collapse',
25
+ description: 'Collapse or expand this node',
26
+ },
27
+ {
28
+ command: 'collapseSiblings',
29
+ key: 'Mod-Shift-.',
30
+ group: 'collapse',
31
+ feature: 'collapse',
32
+ description: 'Collapse every sibling at this level',
33
+ },
34
+ {
35
+ command: 'bold',
36
+ key: 'Mod-b',
37
+ group: 'formatting',
38
+ feature: 'formatting',
39
+ description: 'Bold',
40
+ },
41
+ {
42
+ command: 'italic',
43
+ key: 'Mod-i',
44
+ group: 'formatting',
45
+ feature: 'formatting',
46
+ description: 'Italic',
47
+ },
48
+ {
49
+ command: 'underline',
50
+ key: 'Mod-u',
51
+ group: 'formatting',
52
+ feature: 'formatting',
53
+ description: 'Underline',
54
+ },
55
+ {
56
+ command: 'strike',
57
+ key: 'Mod-Shift-x',
58
+ group: 'formatting',
59
+ feature: 'formatting',
60
+ description: 'Strikethrough',
61
+ },
62
+ { command: 'undo', key: 'Mod-z', group: 'editing', feature: 'history', description: 'Undo' },
63
+ {
64
+ command: 'redo',
65
+ key: 'Mod-Shift-z',
66
+ group: 'editing',
67
+ feature: 'history',
68
+ description: 'Redo',
69
+ },
70
+ /**
71
+ * نیم‌فاصله. Not a convenience: this is how Persian compound words are written at all —
72
+ * می‌شود, نمی‌توان, دانش‌آموزان — and an editor without it cannot be used for real Persian.
73
+ */
74
+ {
75
+ command: 'insertZwnj',
76
+ key: 'Shift-Space',
77
+ group: 'persian',
78
+ description: 'Half-space (ZWNJ)',
79
+ },
80
+ ];
81
+ const COMMANDS = new Set(DEFAULT_SHORTCUTS.map((b) => b.command));
82
+ /**
83
+ * Apply a host's overrides and feature set to the default table.
84
+ *
85
+ * Collisions THROW. Letting a rebinding silently win means the losing command stops
86
+ * working with nothing logged, discoverable only by someone trying it and wondering what
87
+ * broke — and by then the override is somewhere in the host's configuration, far from the
88
+ * symptom.
89
+ */
90
+ export function resolveShortcuts(options) {
91
+ if (options?.enabled === false)
92
+ return [];
93
+ const features = resolveFeatures(options?.features);
94
+ const overrides = options?.overrides ?? {};
95
+ for (const name of Object.keys(overrides)) {
96
+ if (!COMMANDS.has(name)) {
97
+ throw new Error(`Unknown shortcut command ${JSON.stringify(name)}. Known: ${[...COMMANDS].join(', ')}.`);
98
+ }
99
+ }
100
+ const resolved = [];
101
+ for (const binding of DEFAULT_SHORTCUTS) {
102
+ if (binding.feature !== undefined && !features[binding.feature])
103
+ continue;
104
+ const override = Object.hasOwn(overrides, binding.command)
105
+ ? overrides[binding.command]
106
+ : undefined;
107
+ // An explicit null unbinds; `undefined` means the host said nothing about it.
108
+ if (override === null)
109
+ continue;
110
+ resolved.push(override === undefined ? binding : { ...binding, key: override });
111
+ }
112
+ /*
113
+ * Collisions are checked AFTER every override is applied, not while applying them, so
114
+ * that swapping two commands works in a single call. Checking as we go would reject
115
+ * `{ indent: null, toggleCollapse: 'Tab' }` depending on table order, which is a rule
116
+ * nobody could predict.
117
+ */
118
+ const seen = new Map();
119
+ for (const binding of resolved) {
120
+ const previous = seen.get(binding.key);
121
+ if (previous !== undefined) {
122
+ throw new Error(`Shortcut collision on ${binding.key}: ${previous} and ${binding.command}. ` +
123
+ `Unbind one with { ${previous}: null }.`);
124
+ }
125
+ seen.set(binding.key, binding.command);
126
+ }
127
+ return resolved;
128
+ }
129
+ /** Stable group order for a help sheet — structure first, because it is the product. */
130
+ const GROUP_ORDER = [
131
+ 'structure',
132
+ 'collapse',
133
+ 'formatting',
134
+ 'editing',
135
+ 'persian',
136
+ ];
137
+ /**
138
+ * Group the live bindings for display.
139
+ *
140
+ * The order is fixed rather than derived from the data: a sheet that reshuffles between
141
+ * openings cannot be learned, and learning it is the only reason it exists.
142
+ */
143
+ export function shortcutsFor(bindings) {
144
+ return GROUP_ORDER.map((group) => ({
145
+ group,
146
+ bindings: bindings.filter((binding) => binding.group === group),
147
+ })).filter((entry) => entry.bindings.length > 0);
148
+ }
149
+ const APPLE_GLYPHS = {
150
+ Mod: '⌘',
151
+ Shift: '⇧',
152
+ Alt: '⌥',
153
+ Ctrl: '⌃',
154
+ };
155
+ const ARROWS = {
156
+ ArrowUp: '↑',
157
+ ArrowDown: '↓',
158
+ ArrowLeft: '←',
159
+ ArrowRight: '→',
160
+ };
161
+ /**
162
+ * A binding as a person reads it.
163
+ *
164
+ * `Mod` is ProseMirror's platform-agnostic spelling and must never reach a user: nobody
165
+ * has a Mod key. Apple users read glyphs with no separator, everyone else reads words
166
+ * joined by `+`, and getting that backwards is one of the small things that makes software
167
+ * feel foreign.
168
+ */
169
+ export function formatShortcut(key, platform) {
170
+ const parts = key.split('-');
171
+ const last = parts.pop() ?? '';
172
+ // A single letter is shown capitalised — `Mod-b` is written ⌘B on every key cap.
173
+ const final = ARROWS[last] ?? (last.length === 1 ? last.toUpperCase() : last);
174
+ if (platform === 'apple') {
175
+ return parts.map((part) => APPLE_GLYPHS[part] ?? part).join('') + final;
176
+ }
177
+ return [...parts.map((part) => (part === 'Mod' ? 'Ctrl' : part)), final].join('+');
178
+ }
179
+ //# sourceMappingURL=shortcuts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shortcuts.js","sourceRoot":"","sources":["../src/shortcuts.ts"],"names":[],"mappings":"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"}