@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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mahdi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,16 @@
1
+ # @tarhnama/core
2
+
3
+ Framework-agnostic outline document model: flat blocks with outline levels, plus a derived tree index. No DOM, no editor dependency.
4
+
5
+ No DOM and no editor dependency, so it runs in Node — batch conversion, indexing, tests.
6
+
7
+ ```bash
8
+ npm install @tarhnama/core
9
+ ```
10
+
11
+ Part of **طرح‌نما**, a Persian-first outline editor. Everything is optional — features,
12
+ styling, and copy — so it can be embedded without bringing opinions you did not ask for.
13
+ See the [consumer guide](https://github.com/mahdi/tarhnama/blob/main/docs/CONSUMING.md) for the styling levels,
14
+ the feature switches and the i18n contract.
15
+
16
+ Licensed MIT.
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The colouring 1a draws in a code block — keywords in the accent, strings in green —
3
+ * as a pure function over text, so the editor's decorations and both readers colour the
4
+ * same characters.
5
+ *
6
+ * Deliberately small. It is not a parser and does not try to be: a full grammar per
7
+ * language is a dependency this package must not force on a host, and a host that wants
8
+ * one passes its own tokenizer with the same signature. What it does, it does exactly —
9
+ * whole words only, escapes honoured, comment markers inside strings left alone — because
10
+ * a highlighter that is right most of the time teaches the reader to distrust the colours.
11
+ *
12
+ * Numbers and identifiers are not tokens. The prototype colours neither consistently
13
+ * (`editor` is accent once in 1a and plain on the next line; `const` is plain in 2a-06),
14
+ * so there is no rule to measure — see docs/DESIGN-REQUESTS.md.
15
+ */
16
+ export type CodeTokenKind = 'keyword' | 'string' | 'comment';
17
+ export interface CodeToken {
18
+ /** Offset of the first character, in UTF-16 code units of the block's text. */
19
+ readonly from: number;
20
+ /** Offset after the last character. */
21
+ readonly to: number;
22
+ readonly kind: CodeTokenKind;
23
+ }
24
+ /** A host-supplied replacement for `tokenizeCode`, e.g. one backed by a real grammar. */
25
+ export type CodeTokenizer = (code: string, lang: string | undefined) => readonly CodeToken[];
26
+ export declare function tokenizeCode(code: string, lang: string | undefined): CodeToken[];
27
+ export interface CodeSegment {
28
+ readonly text: string;
29
+ /** Absent for the text between tokens. */
30
+ readonly kind?: CodeTokenKind;
31
+ }
32
+ /**
33
+ * The text cut into runs, each token its own. Every renderer goes through this, so the
34
+ * editor and both readers colour the same characters — including when a HOST tokenizer
35
+ * returns something malformed: a token that is empty, out of range or overlaps the one
36
+ * before is skipped, and the text itself always comes back whole.
37
+ */
38
+ export declare function codeSegments(code: string, tokens: readonly CodeToken[]): CodeSegment[];
39
+ //# sourceMappingURL=code-tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-tokens.d.ts","sourceRoot":"","sources":["../src/code-tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE7D,MAAM,WAAW,SAAS;IACxB,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;CAC9B;AAED,yFAAyF;AACzF,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,KAAK,SAAS,SAAS,EAAE,CAAC;AAiG7F,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,EAAE,CA4DhF;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,SAAS,EAAE,GAAG,WAAW,EAAE,CAWtF"}
@@ -0,0 +1,189 @@
1
+ /**
2
+ * The colouring 1a draws in a code block — keywords in the accent, strings in green —
3
+ * as a pure function over text, so the editor's decorations and both readers colour the
4
+ * same characters.
5
+ *
6
+ * Deliberately small. It is not a parser and does not try to be: a full grammar per
7
+ * language is a dependency this package must not force on a host, and a host that wants
8
+ * one passes its own tokenizer with the same signature. What it does, it does exactly —
9
+ * whole words only, escapes honoured, comment markers inside strings left alone — because
10
+ * a highlighter that is right most of the time teaches the reader to distrust the colours.
11
+ *
12
+ * Numbers and identifiers are not tokens. The prototype colours neither consistently
13
+ * (`editor` is accent once in 1a and plain on the next line; `const` is plain in 2a-06),
14
+ * so there is no rule to measure — see docs/DESIGN-REQUESTS.md.
15
+ */
16
+ const words = (list) => new Set(list.split(/\s+/).filter(Boolean));
17
+ const C_LIKE = {
18
+ lineComment: '//',
19
+ blockComment: true,
20
+ quotes: `'"\``,
21
+ rawQuotes: '',
22
+ multilineQuotes: '`',
23
+ tripleQuotes: false,
24
+ hashNeedsBoundary: false,
25
+ memberAccessIsNotKeyword: true,
26
+ };
27
+ /*
28
+ * Contextual words that are routinely identifiers (`type`, `from`, `of`, `as`, `get`, `set`)
29
+ * are left out: colouring `const type = …` as a keyword is wrong far more often than
30
+ * leaving `import x from` plain is.
31
+ */
32
+ const JS = {
33
+ ...C_LIKE,
34
+ keywords: words(`
35
+ abstract async await break case catch class const continue debugger declare default delete
36
+ do else enum export extends false finally for function if implements import in instanceof
37
+ interface keyof let namespace new null private protected public readonly return satisfies
38
+ static super switch this throw true try typeof undefined var void while with yield
39
+ `),
40
+ };
41
+ const JSON_GRAMMAR = {
42
+ keywords: words('true false null'),
43
+ lineComment: null,
44
+ blockComment: false,
45
+ quotes: '"',
46
+ rawQuotes: '',
47
+ multilineQuotes: '',
48
+ tripleQuotes: false,
49
+ hashNeedsBoundary: false,
50
+ memberAccessIsNotKeyword: false,
51
+ };
52
+ const PYTHON = {
53
+ keywords: words(`
54
+ False None True and as assert async await break class continue def del elif else except
55
+ finally for from global if import in is lambda nonlocal not or pass raise return try while
56
+ with yield
57
+ `),
58
+ lineComment: '#',
59
+ blockComment: false,
60
+ quotes: `'"`,
61
+ rawQuotes: '',
62
+ multilineQuotes: '',
63
+ tripleQuotes: true,
64
+ hashNeedsBoundary: false,
65
+ memberAccessIsNotKeyword: true,
66
+ };
67
+ const SHELL = {
68
+ keywords: words('if then else elif fi case esac for while until do done in function return exit local export select'),
69
+ lineComment: '#',
70
+ blockComment: false,
71
+ quotes: `'"`,
72
+ rawQuotes: "'",
73
+ multilineQuotes: `'"`,
74
+ tripleQuotes: false,
75
+ hashNeedsBoundary: true,
76
+ memberAccessIsNotKeyword: false,
77
+ };
78
+ const GRAMMARS = {
79
+ js: JS, jsx: JS, mjs: JS, cjs: JS, javascript: JS,
80
+ ts: JS, tsx: JS, mts: JS, cts: JS, typescript: JS,
81
+ json: JSON_GRAMMAR, jsonc: { ...JSON_GRAMMAR, lineComment: '//', blockComment: true },
82
+ py: PYTHON, python: PYTHON, python3: PYTHON,
83
+ sh: SHELL, bash: SHELL, zsh: SHELL, shell: SHELL, console: SHELL,
84
+ };
85
+ const WORD_START = /[\p{L}_$]/u;
86
+ const WORD_PART = /[\p{L}\p{N}_$]/u;
87
+ export function tokenizeCode(code, lang) {
88
+ const grammar = lang ? GRAMMARS[lang.trim().toLowerCase()] : undefined;
89
+ if (!grammar)
90
+ return [];
91
+ const tokens = [];
92
+ const length = code.length;
93
+ let i = 0;
94
+ while (i < length) {
95
+ const ch = code[i];
96
+ if (grammar.lineComment && code.startsWith(grammar.lineComment, i)) {
97
+ const atBoundary = i === 0 || /\s/.test(code[i - 1]);
98
+ if (!grammar.hashNeedsBoundary || atBoundary) {
99
+ const end = lineEnd(code, i);
100
+ tokens.push({ from: i, to: end, kind: 'comment' });
101
+ i = end;
102
+ continue;
103
+ }
104
+ }
105
+ if (grammar.blockComment && code.startsWith('/*', i)) {
106
+ const close = code.indexOf('*/', i + 2);
107
+ const end = close === -1 ? length : close + 2;
108
+ tokens.push({ from: i, to: end, kind: 'comment' });
109
+ i = end;
110
+ continue;
111
+ }
112
+ if (grammar.quotes.includes(ch)) {
113
+ const end = grammar.tripleQuotes && code.startsWith(ch.repeat(3), i)
114
+ ? tripleQuoteEnd(code, i, ch)
115
+ : quoteEnd(code, i, ch, grammar.multilineQuotes.includes(ch), !grammar.rawQuotes.includes(ch));
116
+ tokens.push({ from: i, to: end, kind: 'string' });
117
+ i = end;
118
+ continue;
119
+ }
120
+ if (WORD_START.test(ch)) {
121
+ let end = i + 1;
122
+ while (end < length && WORD_PART.test(code[end]))
123
+ end += 1;
124
+ const word = code.slice(i, end);
125
+ const member = grammar.memberAccessIsNotKeyword && i > 0 && code[i - 1] === '.';
126
+ if (!member && grammar.keywords.has(word))
127
+ tokens.push({ from: i, to: end, kind: 'keyword' });
128
+ i = end;
129
+ continue;
130
+ }
131
+ // A digit run is skipped whole, so `1e5` or `0x1f` never starts a word in its middle.
132
+ if (/\p{N}/u.test(ch)) {
133
+ let end = i + 1;
134
+ while (end < length && WORD_PART.test(code[end]))
135
+ end += 1;
136
+ i = end;
137
+ continue;
138
+ }
139
+ i += 1;
140
+ }
141
+ return tokens;
142
+ }
143
+ /**
144
+ * The text cut into runs, each token its own. Every renderer goes through this, so the
145
+ * editor and both readers colour the same characters — including when a HOST tokenizer
146
+ * returns something malformed: a token that is empty, out of range or overlaps the one
147
+ * before is skipped, and the text itself always comes back whole.
148
+ */
149
+ export function codeSegments(code, tokens) {
150
+ const segments = [];
151
+ let at = 0;
152
+ for (const token of tokens) {
153
+ if (token.from < at || token.to <= token.from || token.to > code.length)
154
+ continue;
155
+ if (token.from > at)
156
+ segments.push({ text: code.slice(at, token.from) });
157
+ segments.push({ text: code.slice(token.from, token.to), kind: token.kind });
158
+ at = token.to;
159
+ }
160
+ if (at < code.length)
161
+ segments.push({ text: code.slice(at) });
162
+ return segments;
163
+ }
164
+ function lineEnd(code, from) {
165
+ const newline = code.indexOf('\n', from);
166
+ return newline === -1 ? code.length : newline;
167
+ }
168
+ /** After the closing quote; an unterminated single-line quote stops at the end of its line. */
169
+ function quoteEnd(code, from, quote, multiline, escapes) {
170
+ let i = from + 1;
171
+ while (i < code.length) {
172
+ const ch = code[i];
173
+ if (escapes && ch === '\\') {
174
+ i += 2;
175
+ continue;
176
+ }
177
+ if (ch === quote)
178
+ return i + 1;
179
+ if (ch === '\n' && !multiline)
180
+ return i;
181
+ i += 1;
182
+ }
183
+ return code.length;
184
+ }
185
+ function tripleQuoteEnd(code, from, quote) {
186
+ const close = code.indexOf(quote.repeat(3), from + 3);
187
+ return close === -1 ? code.length : close + 3;
188
+ }
189
+ //# sourceMappingURL=code-tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-tokens.js","sourceRoot":"","sources":["../src/code-tokens.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ import type { OutlineFeatureName, OutlineFeatures } from './features.js';
2
+ /**
3
+ * What a right-click on the document offers.
4
+ *
5
+ * The spine's handle already has a node menu. The TEXT does not, so right-clicking a
6
+ * paragraph gets the browser's own menu — spell-check and "view source", nothing about the
7
+ * document you are writing.
8
+ *
9
+ * Computed as DATA, like the shortcut registry and for the same reasons: a host can render
10
+ * it in their own menu component, translate it through their own label pack, filter it, or
11
+ * ignore it entirely. Nothing here holds a closure or touches the DOM.
12
+ */
13
+ export type ContextCommand = 'cut' | 'copy' | 'paste' | 'undo' | 'redo' | 'indent' | 'outdent' | 'moveUp' | 'moveDown' | 'toggleCollapse' | 'isolate' | 'focus' | 'toggleLock';
14
+ /** What the host knows about the place that was right-clicked. */
15
+ export interface ContextTarget {
16
+ readonly blockId: string;
17
+ readonly level: number;
18
+ readonly hasSelection: boolean;
19
+ readonly locked: boolean;
20
+ readonly collapsed: boolean;
21
+ /** Whether this heading governs anything — a fold with nothing under it does nothing. */
22
+ readonly governsSubtree: boolean;
23
+ readonly canUndo: boolean;
24
+ readonly canRedo: boolean;
25
+ /**
26
+ * Whether a sibling exists to move past.
27
+ *
28
+ * Part of the target rather than derived here, for the same reason as the undo stack:
29
+ * this function is handed one block, and "is there a previous sibling" is a question only
30
+ * the whole document can answer. Getting it wrong is not cosmetic — "move down" was live
31
+ * on a heading with nowhere to go, and choosing it did nothing.
32
+ */
33
+ readonly canMoveUp: boolean;
34
+ readonly canMoveDown: boolean;
35
+ }
36
+ export interface ContextMenuItem {
37
+ readonly command: ContextCommand;
38
+ /** English; a host translates through their own label pack. */
39
+ readonly label: string;
40
+ readonly enabled: boolean;
41
+ /** A rule drawn before this item. Named here so a renderer does not invent grouping. */
42
+ readonly separator?: boolean;
43
+ readonly feature?: OutlineFeatureName;
44
+ }
45
+ /**
46
+ * Build the menu for one right-click.
47
+ *
48
+ * Two rules, and the difference between them is the whole design:
49
+ *
50
+ * **A command that cannot act right now is DIMMED.** The menu has the same shape every
51
+ * time it opens, so its geometry can be learned and the third item is always the third
52
+ * item. This is the rule the toolbars follow.
53
+ *
54
+ * **A command whose FEATURE is off is REMOVED.** Dimming says "not now", which is a
55
+ * promise. A feature the host switched off is never coming back, and leaving it there
56
+ * spends the reader's attention on it at every opening, forever.
57
+ */
58
+ export declare function contextMenuFor(target: ContextTarget, features: OutlineFeatures): readonly ContextMenuItem[];
59
+ //# sourceMappingURL=context-menu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-menu.d.ts","sourceRoot":"","sources":["../src/context-menu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEzE;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,cAAc,GACtB,KAAK,GACL,MAAM,GACN,OAAO,GACP,MAAM,GACN,MAAM,GACN,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,UAAU,GACV,gBAAgB,GAChB,SAAS,GACT,OAAO,GACP,YAAY,CAAC;AAEjB,kEAAkE;AAClE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,yFAAyF;IACzF,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,wFAAwF;IACxF,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,kBAAkB,CAAC;CACvC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,aAAa,EACrB,QAAQ,EAAE,eAAe,GACxB,SAAS,eAAe,EAAE,CA4D5B"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Build the menu for one right-click.
3
+ *
4
+ * Two rules, and the difference between them is the whole design:
5
+ *
6
+ * **A command that cannot act right now is DIMMED.** The menu has the same shape every
7
+ * time it opens, so its geometry can be learned and the third item is always the third
8
+ * item. This is the rule the toolbars follow.
9
+ *
10
+ * **A command whose FEATURE is off is REMOVED.** Dimming says "not now", which is a
11
+ * promise. A feature the host switched off is never coming back, and leaving it there
12
+ * spends the reader's attention on it at every opening, forever.
13
+ */
14
+ export function contextMenuFor(target, features) {
15
+ // A lock stops EDITING, never reading — so copy survives it and the mutating pair does not.
16
+ const editable = !target.locked;
17
+ const all = [
18
+ {
19
+ command: 'cut',
20
+ label: 'Cut',
21
+ enabled: target.hasSelection && editable,
22
+ feature: 'clipboard',
23
+ },
24
+ { command: 'copy', label: 'Copy', enabled: target.hasSelection, feature: 'clipboard' },
25
+ { command: 'paste', label: 'Paste', enabled: editable, feature: 'clipboard' },
26
+ {
27
+ command: 'undo',
28
+ label: 'Undo',
29
+ enabled: target.canUndo,
30
+ separator: true,
31
+ feature: 'history',
32
+ },
33
+ { command: 'redo', label: 'Redo', enabled: target.canRedo, feature: 'history' },
34
+ /*
35
+ * Structural commands carry no feature: indent and outdent ARE the outline, not a
36
+ * feature of it, and an editor without them is a different product rather than this
37
+ * one configured down.
38
+ */
39
+ { command: 'indent', label: 'Indent', enabled: editable && target.level > 0, separator: true },
40
+ { command: 'outdent', label: 'Outdent', enabled: editable && target.level > 1 },
41
+ { command: 'moveUp', label: 'Move up', enabled: editable && target.canMoveUp },
42
+ { command: 'moveDown', label: 'Move down', enabled: editable && target.canMoveDown },
43
+ {
44
+ command: 'toggleCollapse',
45
+ label: target.collapsed ? 'Expand' : 'Collapse',
46
+ // A heading that governs nothing has nothing to fold — the spine's eighth state,
47
+ // in menu form.
48
+ enabled: target.governsSubtree,
49
+ separator: true,
50
+ feature: 'collapse',
51
+ },
52
+ {
53
+ command: 'isolate',
54
+ label: 'Keep only this subtree open',
55
+ enabled: target.governsSubtree,
56
+ feature: 'collapse',
57
+ },
58
+ { command: 'focus', label: 'Focus this subtree', enabled: target.governsSubtree },
59
+ {
60
+ command: 'toggleLock',
61
+ label: target.locked ? 'Unlock' : 'Lock',
62
+ enabled: true,
63
+ separator: true,
64
+ feature: 'lock',
65
+ },
66
+ ];
67
+ return all.filter((item) => item.feature === undefined || features[item.feature]);
68
+ }
69
+ //# sourceMappingURL=context-menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-menu.js","sourceRoot":"","sources":["../src/context-menu.ts"],"names":[],"mappings":"AA8DA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,cAAc,CAC5B,MAAqB,EACrB,QAAyB;IAEzB,4FAA4F;IAC5F,MAAM,QAAQ,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC;IAEhC,MAAM,GAAG,GAAsB;QAC7B;YACE,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,KAAK;YACZ,OAAO,EAAE,MAAM,CAAC,YAAY,IAAI,QAAQ;YACxC,OAAO,EAAE,WAAW;SACrB;QACD,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE;QACtF,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE;QAE7E;YACE,OAAO,EAAE,MAAM;YACf,KAAK,EAAE,MAAM;YACb,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,SAAS,EAAE,IAAI;YACf,OAAO,EAAE,SAAS;SACnB;QACD,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE;QAE/E;;;;WAIG;QACH,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,MAAM,CAAC,KAAK,GAAG,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE;QAC9F,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,IAAI,MAAM,CAAC,KAAK,GAAG,CAAC,EAAE;QAC/E,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,IAAI,MAAM,CAAC,SAAS,EAAE;QAC9E,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,IAAI,MAAM,CAAC,WAAW,EAAE;QAEpF;YACE,OAAO,EAAE,gBAAgB;YACzB,KAAK,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU;YAC/C,iFAAiF;YACjF,gBAAgB;YAChB,OAAO,EAAE,MAAM,CAAC,cAAc;YAC9B,SAAS,EAAE,IAAI;YACf,OAAO,EAAE,UAAU;SACpB;QACD;YACE,OAAO,EAAE,SAAS;YAClB,KAAK,EAAE,6BAA6B;YACpC,OAAO,EAAE,MAAM,CAAC,cAAc;YAC9B,OAAO,EAAE,UAAU;SACpB;QACD,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,CAAC,cAAc,EAAE;QAEjF;YACE,OAAO,EAAE,YAAY;YACrB,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM;YACxC,OAAO,EAAE,IAAI;YACb,SAAS,EAAE,IAAI;YACf,OAAO,EAAE,MAAM;SAChB;KACF,CAAC;IAEF,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;AACpF,CAAC"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * What a host turns on.
3
+ *
4
+ * This editor is embedded in projects that want very different subsets of it: a note pane
5
+ * that needs no cross-document links, a review surface that needs no formatting, a viewer
6
+ * that needs no editing at all. Every one of those is a supported configuration rather
7
+ * than a fork.
8
+ *
9
+ * ── The rule that shapes this whole file ───────────────────────────────────────
10
+ *
11
+ * **Features gate BEHAVIOUR, never the schema.**
12
+ *
13
+ * It is tempting to make `links: false` remove the link mark from the document schema —
14
+ * it would be smaller, and it would tree-shake. It is also data loss: ProseMirror strips
15
+ * any mark its schema does not know, so a host with links disabled that opens a document
16
+ * containing links **destroys them on load**, silently, and writes the damaged version
17
+ * back on the next save. A document must round-trip through every configuration
18
+ * unchanged.
19
+ *
20
+ * So the schema is always complete, and a disabled feature removes its input rules, its
21
+ * keymap entries, its commands, its decorations and its UI. Bundle size is addressed the
22
+ * honest way instead — subpath exports, so a host that never imports the link picker
23
+ * never ships it.
24
+ */
25
+ /** Every switchable behaviour. Deliberately flat and small; this is a public contract. */
26
+ export interface OutlineFeatures {
27
+ /**
28
+ * `[[` autocomplete, anchor commands, link input rules, reference previews.
29
+ * Existing link marks still render and still round-trip when this is off.
30
+ */
31
+ readonly links: boolean;
32
+ /** Bold/italic/colour/highlight commands and the formatting keymap (⌘B and friends). */
33
+ readonly formatting: boolean;
34
+ /** Collapse state, the spine, the hidden-count decorations, ⌘. and the ladder. */
35
+ readonly collapse: boolean;
36
+ /** Read-only subtrees, the lock guard, and the padlock affordance. */
37
+ readonly lock: boolean;
38
+ /** Undo/redo. Turn OFF when adopting Yjs: two undo managers fight (see CLAUDE.md §4). */
39
+ readonly history: boolean;
40
+ /** Table editing commands. Existing tables still render either way. */
41
+ readonly tables: boolean;
42
+ /** Image insertion. Existing images still render either way. */
43
+ readonly images: boolean;
44
+ /**
45
+ * Outline-aware copy: the host's serializer decides the `text/plain` flavour.
46
+ * Off leaves ProseMirror's default, which loses every level.
47
+ */
48
+ readonly clipboard: boolean;
49
+ }
50
+ export type OutlineFeatureName = keyof OutlineFeatures;
51
+ /**
52
+ * The default: everything.
53
+ *
54
+ * A host that has not thought about features yet should get the whole editor, not an empty
55
+ * shell to debug. "Off unless named" reads as safer and is worse — the failure it produces
56
+ * is a missing feature with no error, which is the hardest kind to diagnose.
57
+ */
58
+ export declare const ALL_FEATURES: OutlineFeatures;
59
+ /** Nothing but text. Useful as a base to switch individual features back on. */
60
+ export declare const NO_FEATURES: OutlineFeatures;
61
+ /**
62
+ * Normalize whatever the host passed into a complete record.
63
+ *
64
+ * Three input shapes, because hosts reach for different ones and all three are reasonable:
65
+ *
66
+ * undefined → everything (the default)
67
+ * { links: false } → everything except links (a partial override)
68
+ * ['collapse', 'formatting'] → ONLY those two (an allowlist)
69
+ *
70
+ * The array form flips the default deliberately: writing a list reads as "these are the
71
+ * features I want", and having it mean "these, plus the six I did not mention" would be a
72
+ * trap. The object form keeps the opposite reading, which is why both exist.
73
+ *
74
+ * An unknown name in the array throws rather than being ignored — a typo'd feature that
75
+ * silently does nothing is the exact failure this API exists to prevent.
76
+ */
77
+ export declare function resolveFeatures(input?: Partial<OutlineFeatures> | readonly OutlineFeatureName[] | undefined): OutlineFeatures;
78
+ /**
79
+ * How the document is being used.
80
+ *
81
+ * A separate axis from features, not another flag among them: `read` is not "formatting
82
+ * off plus links off", it is a different contract — nothing may mutate the document, and
83
+ * the machinery that exists to mutate it does not need to be constructed at all.
84
+ */
85
+ export type OutlineMode = 'edit' | 'read';
86
+ /**
87
+ * The features a mode can actually support.
88
+ *
89
+ * Read mode keeps `collapse` and `links`, and that distinction is the point: **a lock stops
90
+ * editing, never reading.** A reader who cannot fold a section or follow a reference has
91
+ * been given a screenshot, not a document. Everything that mutates content is off, and no
92
+ * host flag can turn it back on — an editing command reachable in read mode is a bug, not
93
+ * a configuration.
94
+ */
95
+ export declare function featuresForMode(features: OutlineFeatures, mode: OutlineMode): OutlineFeatures;
96
+ /**
97
+ * A stable string for a resolved feature set.
98
+ *
99
+ * Exists because of how React memoization actually gets used. The natural way for a host to
100
+ * configure this editor is an inline literal:
101
+ *
102
+ * <OutlineEditor features={{ links: hasLinks }} />
103
+ *
104
+ * That object has a new identity on every render. Memoizing the extension list on it
105
+ * rebuilds the Tiptap editor on every render — the document is torn down and recreated
106
+ * between keystrokes, and the symptom is not an error but an editor that never settles:
107
+ * the spine's handles move continuously and a click can never land.
108
+ *
109
+ * An API that is only correct when the caller remembers to `useMemo` is a bad API, so the
110
+ * comparison is by CONTENT here rather than by reference. The key is ordered by
111
+ * `ALL_FEATURES` so two equal sets always produce the same string.
112
+ */
113
+ export declare function featureKey(features: OutlineFeatures): string;
114
+ //# sourceMappingURL=features.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features.d.ts","sourceRoot":"","sources":["../src/features.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,0FAA0F;AAC1F,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,wFAAwF;IACxF,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,yFAAyF;IACzF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,MAAM,kBAAkB,GAAG,MAAM,eAAe,CAAC;AAEvD;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,eAS1B,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,WAAW,EAAE,eASzB,CAAC;AAIF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAC7B,KAAK,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,SAAS,kBAAkB,EAAE,GAAG,SAAS,GAC3E,eAAe,CAkBjB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,CAAC;AAE1C;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,WAAW,GAAG,eAAe,CAU7F;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,CAE5D"}