@ai-matrx/rich-editor 0.0.0 → 0.1.1
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/CHANGELOG.md +19 -0
- package/LICENSE +1 -1
- package/README.md +13 -3
- package/dist/core/block-insert.d.ts +27 -0
- package/dist/core/block-insert.js +24 -0
- package/dist/core/caret-context.d.ts +24 -0
- package/dist/core/caret-context.js +73 -0
- package/dist/core/clipboard-text.d.ts +5 -0
- package/dist/core/clipboard-text.js +47 -0
- package/dist/core/commands.d.ts +65 -0
- package/dist/core/commands.js +299 -0
- package/dist/core/extensions.d.ts +29 -0
- package/dist/core/extensions.js +326 -0
- package/dist/core/find-replace.d.ts +34 -0
- package/dist/core/find-replace.js +67 -0
- package/dist/core/history-approval.d.ts +11 -0
- package/dist/core/history-approval.js +43 -0
- package/dist/core/host-value.d.ts +29 -0
- package/dist/core/host-value.js +10 -0
- package/dist/core/html-to-markdown.d.ts +3 -0
- package/dist/core/html-to-markdown.js +12 -0
- package/dist/core/markdown-format.d.ts +18 -0
- package/dist/core/markdown-format.js +354 -0
- package/dist/core/markdown-parse.d.ts +35 -0
- package/dist/core/markdown-parse.js +533 -0
- package/dist/core/markdown-serialize.d.ts +27 -0
- package/dist/core/markdown-serialize.js +394 -0
- package/dist/core/outline.d.ts +17 -0
- package/dist/core/outline.js +41 -0
- package/dist/core/paste-html.d.ts +10 -0
- package/dist/core/paste-html.js +198 -0
- package/dist/core/paste-markdown.d.ts +18 -0
- package/dist/core/paste-markdown.js +70 -0
- package/dist/core/placeholders.d.ts +22 -0
- package/dist/core/placeholders.js +60 -0
- package/dist/core/save-plan.d.ts +47 -0
- package/dist/core/save-plan.js +287 -0
- package/dist/core/shortcuts.d.ts +13 -0
- package/dist/core/shortcuts.js +50 -0
- package/dist/core/source-format.d.ts +28 -0
- package/dist/core/source-format.js +104 -0
- package/dist/core/text-metrics.d.ts +11 -0
- package/dist/core/text-metrics.js +35 -0
- package/dist/core/variable-name.d.ts +13 -0
- package/dist/core/variable-name.js +13 -0
- package/dist/core/variables.d.ts +37 -0
- package/dist/core/variables.js +77 -0
- package/dist/core/visual-document.d.ts +52 -0
- package/dist/core/visual-document.js +142 -0
- package/dist/core/visual-find.d.ts +27 -0
- package/dist/core/visual-find.js +93 -0
- package/dist/format/format-target.d.ts +39 -0
- package/dist/format/format-target.js +79 -0
- package/dist/in-place/caret-handoff.d.ts +3 -0
- package/dist/in-place/caret-handoff.js +14 -0
- package/dist/in-place/in-place-session.d.ts +24 -0
- package/dist/in-place/in-place-session.js +32 -0
- package/package.json +95 -5
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
const FORMAT_COMMAND_IDS = [
|
|
2
|
+
"bold",
|
|
3
|
+
"italic",
|
|
4
|
+
"strike",
|
|
5
|
+
"code",
|
|
6
|
+
"link",
|
|
7
|
+
"heading1",
|
|
8
|
+
"heading2",
|
|
9
|
+
"heading3",
|
|
10
|
+
"bulletList",
|
|
11
|
+
"orderedList",
|
|
12
|
+
"taskList",
|
|
13
|
+
"quote",
|
|
14
|
+
"codeBlock"
|
|
15
|
+
];
|
|
16
|
+
function isFormatCommandId(id) {
|
|
17
|
+
return FORMAT_COMMAND_IDS.includes(id);
|
|
18
|
+
}
|
|
19
|
+
const INLINE = {
|
|
20
|
+
bold: { marker: "**", chars: ["*", "_"], runs: [2, 3], size: 2 },
|
|
21
|
+
italic: { marker: "*", chars: ["*", "_"], runs: [1, 3], size: 1 },
|
|
22
|
+
strike: { marker: "~~", chars: ["~"], runs: [2], size: 2 },
|
|
23
|
+
code: { marker: "`", chars: ["`"], runs: [1], size: 1 }
|
|
24
|
+
};
|
|
25
|
+
const LINE_PREFIX = /^( {0,3})(#{1,6}[ \t]+|[-*+][ \t]+\[[ xX]\][ \t]+|[-*+][ \t]+|\d{1,9}[.)][ \t]+|>[ \t]?)?/;
|
|
26
|
+
function linesTouched(text, from, to) {
|
|
27
|
+
const start = text.lastIndexOf("\n", from - 1) + 1;
|
|
28
|
+
const effectiveTo = to > from && text[to - 1] === "\n" ? to - 1 : to;
|
|
29
|
+
const endBreak = text.indexOf("\n", effectiveTo);
|
|
30
|
+
const end = endBreak === -1 ? text.length : endBreak;
|
|
31
|
+
const out = [];
|
|
32
|
+
let offset = start;
|
|
33
|
+
for (const line of text.slice(start, end).split("\n")) {
|
|
34
|
+
out.push({ start: offset, end: offset + line.length, text: line });
|
|
35
|
+
offset += line.length + 1;
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
function runLength(text, pos, dir, ch) {
|
|
40
|
+
let n = 0;
|
|
41
|
+
let i = dir === -1 ? pos - 1 : pos;
|
|
42
|
+
while (i >= 0 && i < text.length && text[i] === ch) {
|
|
43
|
+
n += 1;
|
|
44
|
+
i += dir;
|
|
45
|
+
}
|
|
46
|
+
return n;
|
|
47
|
+
}
|
|
48
|
+
const WORD = /[\p{L}\p{N}_'-]/u;
|
|
49
|
+
function wordAt(text, pos) {
|
|
50
|
+
let from = pos;
|
|
51
|
+
let to = pos;
|
|
52
|
+
while (from > 0 && WORD.test(text[from - 1] ?? "")) from -= 1;
|
|
53
|
+
while (to < text.length && WORD.test(text[to] ?? "")) to += 1;
|
|
54
|
+
return to > from ? { from, to } : null;
|
|
55
|
+
}
|
|
56
|
+
function inlineSegments(text, from, to) {
|
|
57
|
+
const out = [];
|
|
58
|
+
for (const line of linesTouched(text, from, to)) {
|
|
59
|
+
const prefix = LINE_PREFIX.exec(line.text)?.[0].length ?? 0;
|
|
60
|
+
let s = Math.max(from, line.start + (from <= line.start + prefix ? prefix : 0));
|
|
61
|
+
let e = Math.min(to, line.end);
|
|
62
|
+
while (s < e && /\s/.test(text[s] ?? "")) s += 1;
|
|
63
|
+
while (e > s && /\s/.test(text[e - 1] ?? "")) e -= 1;
|
|
64
|
+
if (e > s) out.push({ from: s, to: e });
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
function wrapOf(text, seg, spec) {
|
|
69
|
+
for (const ch of spec.chars) {
|
|
70
|
+
const lead = runLength(text, seg.from, 1, ch);
|
|
71
|
+
const trail = runLength(text, seg.to, -1, ch);
|
|
72
|
+
const innerFrom = seg.from + lead;
|
|
73
|
+
const innerTo = seg.to - trail;
|
|
74
|
+
if (innerTo <= innerFrom) continue;
|
|
75
|
+
const before = runLength(text, seg.from, -1, ch) + lead;
|
|
76
|
+
const after = trail + runLength(text, seg.to, 1, ch);
|
|
77
|
+
if (spec.runs.includes(before) && spec.runs.includes(after)) return { innerFrom, innerTo };
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
function mapPos(pos, changes, assoc) {
|
|
82
|
+
let delta = 0;
|
|
83
|
+
for (const c of changes) {
|
|
84
|
+
if (c.to < pos || c.to === pos && (c.from < pos || assoc === 1)) {
|
|
85
|
+
if (c.from >= pos && assoc === -1) continue;
|
|
86
|
+
delta += c.insert.length - (c.to - c.from);
|
|
87
|
+
} else if (c.from < pos && pos < c.to) {
|
|
88
|
+
return c.from + delta + (assoc === 1 ? c.insert.length : 0);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return pos + delta;
|
|
92
|
+
}
|
|
93
|
+
function sorted(changes) {
|
|
94
|
+
return [...changes].sort((a, b) => a.from - b.from || a.to - b.to);
|
|
95
|
+
}
|
|
96
|
+
function inlineCommand(text, from, to, spec) {
|
|
97
|
+
if (from === to) {
|
|
98
|
+
const word = wordAt(text, from);
|
|
99
|
+
if (!word) {
|
|
100
|
+
const insert = `${spec.marker}${spec.marker}`;
|
|
101
|
+
const caret = from + spec.marker.length;
|
|
102
|
+
return { changes: [{ from, to, insert }], anchor: caret, head: caret };
|
|
103
|
+
}
|
|
104
|
+
const result = inlineCommand(text, word.from, word.to, spec);
|
|
105
|
+
const shifted = mapPos(from, sorted(result.changes), 1);
|
|
106
|
+
return { ...result, anchor: shifted, head: shifted };
|
|
107
|
+
}
|
|
108
|
+
const segments = inlineSegments(text, from, to);
|
|
109
|
+
if (segments.length === 0) return { changes: [], anchor: from, head: to };
|
|
110
|
+
const wraps = segments.map((seg) => wrapOf(text, seg, spec));
|
|
111
|
+
const allOn = wraps.every((w) => w !== null);
|
|
112
|
+
const changes = [];
|
|
113
|
+
segments.forEach((seg, i) => {
|
|
114
|
+
const w = wraps[i];
|
|
115
|
+
if (allOn && w) {
|
|
116
|
+
changes.push({ from: w.innerFrom - spec.size, to: w.innerFrom, insert: "" });
|
|
117
|
+
changes.push({ from: w.innerTo, to: w.innerTo + spec.size, insert: "" });
|
|
118
|
+
} else if (!w) {
|
|
119
|
+
changes.push({ from: seg.from, to: seg.from, insert: spec.marker });
|
|
120
|
+
changes.push({ from: seg.to, to: seg.to, insert: spec.marker });
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
const ordered = sorted(changes);
|
|
124
|
+
const startAt = allOn ? wraps[0].innerFrom : segments[0].from;
|
|
125
|
+
const endAt = allOn ? wraps[wraps.length - 1].innerTo : segments[segments.length - 1].to;
|
|
126
|
+
const anchor = mapPos(startAt, ordered, 1);
|
|
127
|
+
const head = mapPos(endAt, ordered, -1);
|
|
128
|
+
return { changes: ordered, anchor, head: Math.max(anchor, head) };
|
|
129
|
+
}
|
|
130
|
+
const WHOLE_LINK = /^\[([^\]\n]*)\]\(([^)\n]*)\)$/;
|
|
131
|
+
const URLISH = /^(https?:\/\/|mailto:|www\.)\S+$/i;
|
|
132
|
+
function linkCommand(text, from, to) {
|
|
133
|
+
const selected = text.slice(from, to);
|
|
134
|
+
const whole = WHOLE_LINK.exec(selected);
|
|
135
|
+
if (whole) {
|
|
136
|
+
const label2 = whole[1] ?? "";
|
|
137
|
+
return { changes: [{ from, to, insert: label2 }], anchor: from, head: from + label2.length };
|
|
138
|
+
}
|
|
139
|
+
if (text[from - 1] === "[" && text.slice(to, to + 2) === "](") {
|
|
140
|
+
const close = text.indexOf(")", to + 2);
|
|
141
|
+
const nl = text.indexOf("\n", to);
|
|
142
|
+
if (close !== -1 && (nl === -1 || close < nl)) {
|
|
143
|
+
return {
|
|
144
|
+
changes: [
|
|
145
|
+
{ from: from - 1, to: from, insert: "" },
|
|
146
|
+
{ from: to, to: close + 1, insert: "" }
|
|
147
|
+
],
|
|
148
|
+
anchor: from - 1,
|
|
149
|
+
head: to - 1
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (URLISH.test(selected)) {
|
|
154
|
+
const insert2 = `[](${selected})`;
|
|
155
|
+
return { changes: [{ from, to, insert: insert2 }], anchor: from + 1, head: from + 1 };
|
|
156
|
+
}
|
|
157
|
+
const label = selected || "link";
|
|
158
|
+
const insert = `[${label}]()`;
|
|
159
|
+
const urlStart = from + label.length + 3;
|
|
160
|
+
return { changes: [{ from, to, insert }], anchor: urlStart, head: urlStart };
|
|
161
|
+
}
|
|
162
|
+
const QUOTE_PREFIX = /^ {0,3}(?:>[ \t]?)+/;
|
|
163
|
+
function quoteLength(line) {
|
|
164
|
+
return QUOTE_PREFIX.exec(line)?.[0].length ?? 0;
|
|
165
|
+
}
|
|
166
|
+
function lineKindOf(line) {
|
|
167
|
+
const m = LINE_PREFIX.exec(line.slice(quoteLength(line)));
|
|
168
|
+
const p = m?.[2];
|
|
169
|
+
if (!p) return null;
|
|
170
|
+
if (p.startsWith("#")) return { kind: "heading", level: p.trim().length };
|
|
171
|
+
if (/^[-*+][ \t]+\[[ xX]\]/.test(p)) return { kind: "task" };
|
|
172
|
+
if (/^[-*+]/.test(p)) return { kind: "bullet" };
|
|
173
|
+
if (/^\d/.test(p)) return { kind: "ordered" };
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
function sameKind(a, b) {
|
|
177
|
+
if (!a || a.kind !== b.kind) return false;
|
|
178
|
+
return a.kind !== "heading" || b.kind === "heading" && a.level === b.level;
|
|
179
|
+
}
|
|
180
|
+
function lineCommand(text, from, to, want, prefixOf) {
|
|
181
|
+
const lines = linesTouched(text, from, to);
|
|
182
|
+
const content = lines.filter((l) => l.text.trim() !== "");
|
|
183
|
+
const targets = content.length ? content : lines.slice(0, 1);
|
|
184
|
+
const allOn = content.length > 0 && content.every((l) => sameKind(lineKindOf(l.text), want));
|
|
185
|
+
const changes = [];
|
|
186
|
+
let n = 1;
|
|
187
|
+
for (const line of targets) {
|
|
188
|
+
const base = quoteLength(line.text);
|
|
189
|
+
const m = LINE_PREFIX.exec(line.text.slice(base));
|
|
190
|
+
const existing = m?.[0] ?? "";
|
|
191
|
+
const indent = m?.[1] ?? "";
|
|
192
|
+
const next = allOn ? indent : `${indent}${prefixOf(n++)}`;
|
|
193
|
+
if (existing !== next) changes.push({ from: line.start + base, to: line.start + base + existing.length, insert: next });
|
|
194
|
+
}
|
|
195
|
+
const ordered = sorted(changes);
|
|
196
|
+
const first = lines[0];
|
|
197
|
+
const last = lines[lines.length - 1];
|
|
198
|
+
const anchor = mapPos(Math.max(from, first.start), ordered, 1);
|
|
199
|
+
const head = from === to ? anchor : mapPos(Math.min(to, last.end), ordered, 1);
|
|
200
|
+
return { changes: ordered, anchor: Math.min(anchor, head), head: Math.max(anchor, head) };
|
|
201
|
+
}
|
|
202
|
+
function quoteCommand(text, from, to) {
|
|
203
|
+
const lines = linesTouched(text, from, to);
|
|
204
|
+
const content = lines.filter((l) => l.text.trim() !== "");
|
|
205
|
+
const allOn = content.length > 0 && content.every((l) => /^ {0,3}>/.test(l.text));
|
|
206
|
+
const changes = [];
|
|
207
|
+
for (const line of lines) {
|
|
208
|
+
if (allOn) {
|
|
209
|
+
const m = /^( {0,3})>[ \t]?/.exec(line.text);
|
|
210
|
+
if (m) changes.push({ from: line.start + m[1].length, to: line.start + m[0].length, insert: "" });
|
|
211
|
+
} else if (!/^ {0,3}>/.test(line.text)) {
|
|
212
|
+
changes.push({ from: line.start, to: line.start, insert: line.text.trim() === "" ? ">" : "> " });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const ordered = sorted(changes);
|
|
216
|
+
const anchor = mapPos(from, ordered, 1);
|
|
217
|
+
const head = from === to ? anchor : mapPos(to, ordered, 1);
|
|
218
|
+
return { changes: ordered, anchor, head };
|
|
219
|
+
}
|
|
220
|
+
const FENCE = /^ {0,3}(`{3,}|~{3,})/;
|
|
221
|
+
function codeBlockCommand(text, from, to) {
|
|
222
|
+
const lines = linesTouched(text, from, to);
|
|
223
|
+
const first = lines[0];
|
|
224
|
+
const last = lines[lines.length - 1];
|
|
225
|
+
if (lines.length >= 2 && FENCE.test(first.text) && FENCE.test(last.text)) {
|
|
226
|
+
const changes2 = [
|
|
227
|
+
{ from: first.start, to: Math.min(first.end + 1, text.length), insert: "" },
|
|
228
|
+
{ from: last.start - 1, to: last.end, insert: "" }
|
|
229
|
+
];
|
|
230
|
+
const ordered2 = sorted(changes2);
|
|
231
|
+
return { changes: ordered2, anchor: first.start, head: mapPos(last.start - 1, ordered2, -1) };
|
|
232
|
+
}
|
|
233
|
+
const prevEnd = first.start - 1;
|
|
234
|
+
const prevStart = prevEnd > 0 ? text.lastIndexOf("\n", prevEnd - 1) + 1 : 0;
|
|
235
|
+
const prevLine = prevEnd >= 0 ? text.slice(prevStart, prevEnd) : "";
|
|
236
|
+
const nextStart = last.end + 1;
|
|
237
|
+
const nextBreak = text.indexOf("\n", nextStart);
|
|
238
|
+
const nextLine = nextStart <= text.length ? text.slice(nextStart, nextBreak === -1 ? text.length : nextBreak) : "";
|
|
239
|
+
if (prevEnd >= 0 && FENCE.test(prevLine) && nextStart <= text.length && FENCE.test(nextLine) && nextLine.trim().replace(/[`~]/g, "") === "") {
|
|
240
|
+
const nextEnd = nextBreak === -1 ? text.length : nextBreak;
|
|
241
|
+
const changes2 = [
|
|
242
|
+
{ from: prevStart, to: first.start, insert: "" },
|
|
243
|
+
{ from: last.end, to: nextEnd, insert: "" }
|
|
244
|
+
];
|
|
245
|
+
const ordered2 = sorted(changes2);
|
|
246
|
+
return { changes: ordered2, anchor: mapPos(from, ordered2, 1), head: mapPos(to, ordered2, -1) };
|
|
247
|
+
}
|
|
248
|
+
const changes = [
|
|
249
|
+
{ from: first.start, to: first.start, insert: "```\n" },
|
|
250
|
+
{ from: last.end, to: last.end, insert: "\n```" }
|
|
251
|
+
];
|
|
252
|
+
const ordered = sorted(changes);
|
|
253
|
+
const anchor = mapPos(from, ordered, 1);
|
|
254
|
+
return { changes: ordered, anchor, head: from === to ? anchor : mapPos(to, ordered, -1) };
|
|
255
|
+
}
|
|
256
|
+
function formatMarkdown(text, from, to, command) {
|
|
257
|
+
const a = Math.max(0, Math.min(from, to, text.length));
|
|
258
|
+
const b = Math.min(text.length, Math.max(from, to));
|
|
259
|
+
switch (command) {
|
|
260
|
+
case "bold":
|
|
261
|
+
case "italic":
|
|
262
|
+
case "strike":
|
|
263
|
+
case "code":
|
|
264
|
+
return inlineCommand(text, a, b, INLINE[command]);
|
|
265
|
+
case "link":
|
|
266
|
+
return linkCommand(text, a, b);
|
|
267
|
+
case "heading1":
|
|
268
|
+
case "heading2":
|
|
269
|
+
case "heading3": {
|
|
270
|
+
const level = Number(command.slice(-1));
|
|
271
|
+
return lineCommand(text, a, b, { kind: "heading", level }, () => `${"#".repeat(level)} `);
|
|
272
|
+
}
|
|
273
|
+
case "bulletList":
|
|
274
|
+
return lineCommand(text, a, b, { kind: "bullet" }, () => "- ");
|
|
275
|
+
case "orderedList":
|
|
276
|
+
return lineCommand(text, a, b, { kind: "ordered" }, (n) => `${n}. `);
|
|
277
|
+
case "taskList":
|
|
278
|
+
return lineCommand(text, a, b, { kind: "task" }, () => "- [ ] ");
|
|
279
|
+
case "quote":
|
|
280
|
+
return quoteCommand(text, a, b);
|
|
281
|
+
case "codeBlock":
|
|
282
|
+
return codeBlockCommand(text, a, b);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function isFormatActive(text, from, to, command) {
|
|
286
|
+
const a = Math.max(0, Math.min(from, to, text.length));
|
|
287
|
+
const b = Math.min(text.length, Math.max(from, to));
|
|
288
|
+
switch (command) {
|
|
289
|
+
case "bold":
|
|
290
|
+
case "italic":
|
|
291
|
+
case "strike":
|
|
292
|
+
case "code": {
|
|
293
|
+
const range = a === b ? wordAt(text, a) : { from: a, to: b };
|
|
294
|
+
if (!range) return false;
|
|
295
|
+
const segs = inlineSegments(text, range.from, range.to);
|
|
296
|
+
return segs.length > 0 && segs.every((s) => wrapOf(text, s, INLINE[command]) !== null);
|
|
297
|
+
}
|
|
298
|
+
case "link":
|
|
299
|
+
return WHOLE_LINK.test(text.slice(a, b)) || text[a - 1] === "[" && text.slice(b, b + 2) === "](";
|
|
300
|
+
case "quote": {
|
|
301
|
+
const content = linesTouched(text, a, b).filter((l) => l.text.trim() !== "");
|
|
302
|
+
return content.length > 0 && content.every((l) => /^ {0,3}>/.test(l.text));
|
|
303
|
+
}
|
|
304
|
+
case "codeBlock":
|
|
305
|
+
return false;
|
|
306
|
+
default: {
|
|
307
|
+
const want = command === "bulletList" ? { kind: "bullet" } : command === "orderedList" ? { kind: "ordered" } : command === "taskList" ? { kind: "task" } : { kind: "heading", level: Number(command.slice(-1)) };
|
|
308
|
+
const content = linesTouched(text, a, b).filter((l) => l.text.trim() !== "");
|
|
309
|
+
return content.length > 0 && content.every((l) => sameKind(lineKindOf(l.text), want));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
const FORMAT_COMMAND_KEYS = {
|
|
314
|
+
bold: ["Mod-b"],
|
|
315
|
+
italic: ["Mod-i"],
|
|
316
|
+
strike: ["Mod-Shift-x", "Mod-Shift-s"],
|
|
317
|
+
code: ["Mod-e"],
|
|
318
|
+
link: ["Mod-k"],
|
|
319
|
+
heading1: ["Mod-Alt-1"],
|
|
320
|
+
heading2: ["Mod-Alt-2"],
|
|
321
|
+
heading3: ["Mod-Alt-3"],
|
|
322
|
+
orderedList: ["Mod-Shift-7"],
|
|
323
|
+
bulletList: ["Mod-Shift-8"],
|
|
324
|
+
taskList: ["Mod-Shift-9"],
|
|
325
|
+
quote: ["Mod-Shift-."],
|
|
326
|
+
codeBlock: ["Mod-Alt-c"]
|
|
327
|
+
};
|
|
328
|
+
function keyNameOf(event, apple) {
|
|
329
|
+
const mod = apple ? event.metaKey || event.ctrlKey : event.ctrlKey;
|
|
330
|
+
if (!mod) return null;
|
|
331
|
+
let base = null;
|
|
332
|
+
if (/^Digit\d$/.test(event.code)) base = event.code.slice(5);
|
|
333
|
+
else if (/^Key[A-Z]$/.test(event.code)) base = event.code.slice(3).toLowerCase();
|
|
334
|
+
else if (event.code === "Period") base = ".";
|
|
335
|
+
else if (event.key.length === 1) base = event.key.toLowerCase();
|
|
336
|
+
if (!base) return null;
|
|
337
|
+
return ["Mod", event.altKey ? "Alt" : null, event.shiftKey ? "Shift" : null, base].filter(Boolean).join("-");
|
|
338
|
+
}
|
|
339
|
+
function formatCommandForKey(keyName) {
|
|
340
|
+
if (!keyName) return null;
|
|
341
|
+
for (const id of FORMAT_COMMAND_IDS) {
|
|
342
|
+
if (FORMAT_COMMAND_KEYS[id].includes(keyName)) return id;
|
|
343
|
+
}
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
export {
|
|
347
|
+
FORMAT_COMMAND_IDS,
|
|
348
|
+
FORMAT_COMMAND_KEYS,
|
|
349
|
+
formatCommandForKey,
|
|
350
|
+
formatMarkdown,
|
|
351
|
+
isFormatActive,
|
|
352
|
+
isFormatCommandId,
|
|
353
|
+
keyNameOf
|
|
354
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { JSONContent } from "@tiptap/core";
|
|
2
|
+
import type { Schema } from "@tiptap/pm/model";
|
|
3
|
+
import type { LinkDefinition, SourceBlock } from "@ai-matrx/content-ir/source";
|
|
4
|
+
import { type Adjacency } from "./markdown-serialize.js";
|
|
5
|
+
export interface ParsedChild {
|
|
6
|
+
json: JSONContent;
|
|
7
|
+
/** The source id of the child (its `mdId`). */
|
|
8
|
+
id: string;
|
|
9
|
+
/** The child's exact stored bytes (no trailing line breaks). */
|
|
10
|
+
raw: string;
|
|
11
|
+
/** The line breaks that followed it inside the block. */
|
|
12
|
+
trail: string;
|
|
13
|
+
/** Why the child is held as source, or null when it is rich text. */
|
|
14
|
+
lockedReason: string | null;
|
|
15
|
+
}
|
|
16
|
+
export interface ParsedProse {
|
|
17
|
+
children: ParsedChild[];
|
|
18
|
+
/** Set when the whole block is held as source. */
|
|
19
|
+
lockedReason: string | null;
|
|
20
|
+
}
|
|
21
|
+
/** Reason string that marks a held-as-source page-break directive line. */
|
|
22
|
+
export declare const PAGE_BREAK_REASON = "page break";
|
|
23
|
+
/**
|
|
24
|
+
* Parse one prose block. Every top-level construct is either rich text that
|
|
25
|
+
* serializes back to its exact bytes, or a `sourceLocked` atom holding them.
|
|
26
|
+
*/
|
|
27
|
+
export declare function parseProseBlock(block: SourceBlock, schema: Schema, adjacency: Map<string, Adjacency>, nextId: () => string,
|
|
28
|
+
/**
|
|
29
|
+
* The WHOLE document's link reference definitions (THE link-reference rule,
|
|
30
|
+
* content-ir `collectLinkDefinitions`): each segment is lexed on its own, so
|
|
31
|
+
* without them `[Apple Support][1]` in a cell could not see `[1]: …` below
|
|
32
|
+
* the table (verify-RC-B4 round 9, R9-4). The bytes still round-trip — the
|
|
33
|
+
* link mark keeps its `[1]` tail.
|
|
34
|
+
*/
|
|
35
|
+
linkDefinitions?: ReadonlyMap<string, LinkDefinition>): ParsedProse;
|