@owomark/core 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -10
- package/dist/.build-manifest.json +130 -0
- package/dist/browser.d.ts +66 -0
- package/dist/browser.js +396 -0
- package/dist/chunk-3KTK7CSS.js +82 -0
- package/dist/chunk-5JNL3LHV.js +215 -0
- package/dist/chunk-ASRCHEFF.js +0 -0
- package/dist/chunk-BKJCBEI7.js +397 -0
- package/dist/chunk-CJSBFWKS.js +549 -0
- package/dist/chunk-GA5EFGSZ.js +5820 -0
- package/dist/chunk-OOH46GIF.js +95 -0
- package/dist/chunk-ROJILHRQ.js +192 -0
- package/dist/chunk-WFPUIPWU.js +34 -0
- package/dist/chunk-WXVKSKP3.js +191 -0
- package/dist/chunk-YZYJIXGO.js +0 -0
- package/dist/editor-core-DbPhn6aI.d.ts +249 -0
- package/dist/index.d.ts +77 -86
- package/dist/index.js +161 -245
- package/dist/internal/dom-adapter.d.ts +37 -1
- package/dist/internal/dom-adapter.js +9 -2
- package/dist/public-zMo7BR9l.d.ts +469 -0
- package/dist/registry-C849sxCo.d.ts +74 -0
- package/dist/semantic/components/index.d.ts +9 -0
- package/dist/semantic/components/index.js +11 -0
- package/dist/semantic/editor/index.d.ts +9 -0
- package/dist/semantic/editor/index.js +13 -0
- package/dist/semantic/index.d.ts +7 -0
- package/dist/semantic/index.js +106 -0
- package/dist/semantic/runtime/index.d.ts +9 -0
- package/dist/semantic/runtime/index.js +13 -0
- package/dist/semantic/shared/index.d.ts +10 -0
- package/dist/semantic/shared/index.js +17 -0
- package/dist/semantic/syntax/index.d.ts +151 -0
- package/dist/semantic/syntax/index.js +63 -0
- package/dist/types-DMqYF6Zn.d.ts +83 -0
- package/package.json +29 -1
- package/dist/chunk-TRLKIMRD.js +0 -3227
- package/dist/dom-adapter-CTSJe5Uo.d.ts +0 -469
package/dist/chunk-TRLKIMRD.js
DELETED
|
@@ -1,3227 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
applyBlockquotePrefix,
|
|
3
|
-
getBlockquotePrefixAtPos,
|
|
4
|
-
normalizeMarkdownPaste
|
|
5
|
-
} from "./chunk-BGXCXQZP.js";
|
|
6
|
-
import {
|
|
7
|
-
deleteToLineEnd,
|
|
8
|
-
deleteToLineStart,
|
|
9
|
-
deleteWordBackward,
|
|
10
|
-
deleteWordForward
|
|
11
|
-
} from "./chunk-MPIWZLI3.js";
|
|
12
|
-
|
|
13
|
-
// src/parser/inline-tokens.ts
|
|
14
|
-
function overlaps(segs, start, end) {
|
|
15
|
-
return segs.some((s) => start < s.end && end > s.start);
|
|
16
|
-
}
|
|
17
|
-
function tok(type, text, start) {
|
|
18
|
-
return { type, text, start, end: start + text.length };
|
|
19
|
-
}
|
|
20
|
-
function isEscaped(raw, index) {
|
|
21
|
-
let backslashCount = 0;
|
|
22
|
-
for (let cursor = index - 1; cursor >= 0 && raw[cursor] === "\\"; cursor--) {
|
|
23
|
-
backslashCount += 1;
|
|
24
|
-
}
|
|
25
|
-
return backslashCount % 2 === 1;
|
|
26
|
-
}
|
|
27
|
-
function findInlineMathRanges(raw) {
|
|
28
|
-
const ranges = [];
|
|
29
|
-
for (let start = 0; start < raw.length; start++) {
|
|
30
|
-
if (raw[start] !== "$" || isEscaped(raw, start)) continue;
|
|
31
|
-
if (raw[start + 1] === "$") {
|
|
32
|
-
start += 1;
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
for (let end = start + 1; end < raw.length; end++) {
|
|
36
|
-
if (raw[end] !== "$" || isEscaped(raw, end)) continue;
|
|
37
|
-
if (raw[end - 1] === "$" || raw[end + 1] === "$") continue;
|
|
38
|
-
ranges.push({ start, end: end + 1 });
|
|
39
|
-
start = end;
|
|
40
|
-
break;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return ranges;
|
|
44
|
-
}
|
|
45
|
-
function tokenizeInline(raw, baseOffset = 0) {
|
|
46
|
-
if (!raw) return [{ type: "text", text: "", start: baseOffset, end: baseOffset }];
|
|
47
|
-
const segs = [];
|
|
48
|
-
for (const m of raw.matchAll(/`([^`]+)`/g)) {
|
|
49
|
-
const s = m.index;
|
|
50
|
-
segs.push({
|
|
51
|
-
start: s,
|
|
52
|
-
end: s + m[0].length,
|
|
53
|
-
tokens: [
|
|
54
|
-
tok("code-marker", "`", baseOffset + s),
|
|
55
|
-
tok("code", m[1], baseOffset + s + 1),
|
|
56
|
-
tok("code-marker", "`", baseOffset + s + m[0].length - 1)
|
|
57
|
-
]
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
for (const m of raw.matchAll(/!\[([^\]]*)\]\(([^)]*)\)/g)) {
|
|
61
|
-
const s = m.index;
|
|
62
|
-
if (!overlaps(segs, s, s + m[0].length)) {
|
|
63
|
-
const tokens = [];
|
|
64
|
-
let pos2 = s;
|
|
65
|
-
tokens.push(tok("image-marker", "![", baseOffset + pos2));
|
|
66
|
-
pos2 += 2;
|
|
67
|
-
tokens.push(tok("image-alt", m[1], baseOffset + pos2));
|
|
68
|
-
pos2 += m[1].length;
|
|
69
|
-
tokens.push(tok("link-bracket", "](", baseOffset + pos2));
|
|
70
|
-
pos2 += 2;
|
|
71
|
-
tokens.push(tok("image-url", m[2], baseOffset + pos2));
|
|
72
|
-
pos2 += m[2].length;
|
|
73
|
-
tokens.push(tok("link-bracket", ")", baseOffset + pos2));
|
|
74
|
-
segs.push({ start: s, end: s + m[0].length, tokens });
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
for (const m of raw.matchAll(/\[([^\]]*)\]\(([^)]*)\)/g)) {
|
|
78
|
-
const s = m.index;
|
|
79
|
-
if (!overlaps(segs, s, s + m[0].length)) {
|
|
80
|
-
const tokens = [];
|
|
81
|
-
let pos2 = s;
|
|
82
|
-
tokens.push(tok("link-bracket", "[", baseOffset + pos2));
|
|
83
|
-
pos2 += 1;
|
|
84
|
-
tokens.push(tok("link-text", m[1], baseOffset + pos2));
|
|
85
|
-
pos2 += m[1].length;
|
|
86
|
-
tokens.push(tok("link-bracket", "](", baseOffset + pos2));
|
|
87
|
-
pos2 += 2;
|
|
88
|
-
tokens.push(tok("link-url", m[2], baseOffset + pos2));
|
|
89
|
-
pos2 += m[2].length;
|
|
90
|
-
tokens.push(tok("link-bracket", ")", baseOffset + pos2));
|
|
91
|
-
segs.push({ start: s, end: s + m[0].length, tokens });
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
for (const m of raw.matchAll(/~~(.+?)~~/g)) {
|
|
95
|
-
const s = m.index;
|
|
96
|
-
if (!overlaps(segs, s, s + m[0].length)) {
|
|
97
|
-
segs.push({
|
|
98
|
-
start: s,
|
|
99
|
-
end: s + m[0].length,
|
|
100
|
-
tokens: [
|
|
101
|
-
tok("strikethrough-marker", "~~", baseOffset + s),
|
|
102
|
-
tok("strikethrough", m[1], baseOffset + s + 2),
|
|
103
|
-
tok("strikethrough-marker", "~~", baseOffset + s + m[0].length - 2)
|
|
104
|
-
]
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
for (const range of findInlineMathRanges(raw)) {
|
|
109
|
-
if (!overlaps(segs, range.start, range.end)) {
|
|
110
|
-
const innerStart = range.start + 1;
|
|
111
|
-
const innerEnd = range.end - 1;
|
|
112
|
-
segs.push({
|
|
113
|
-
start: range.start,
|
|
114
|
-
end: range.end,
|
|
115
|
-
tokens: [
|
|
116
|
-
tok("math-marker", "$", baseOffset + range.start),
|
|
117
|
-
tok("math-text", raw.slice(innerStart, innerEnd), baseOffset + innerStart),
|
|
118
|
-
tok("math-marker", "$", baseOffset + innerEnd)
|
|
119
|
-
]
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
for (const m of raw.matchAll(/\*\*(.+?)\*\*/g)) {
|
|
124
|
-
const s = m.index;
|
|
125
|
-
if (!overlaps(segs, s, s + m[0].length)) {
|
|
126
|
-
segs.push({
|
|
127
|
-
start: s,
|
|
128
|
-
end: s + m[0].length,
|
|
129
|
-
tokens: [
|
|
130
|
-
tok("strong-marker", "**", baseOffset + s),
|
|
131
|
-
tok("strong", m[1], baseOffset + s + 2),
|
|
132
|
-
tok("strong-marker", "**", baseOffset + s + m[0].length - 2)
|
|
133
|
-
]
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
for (const m of raw.matchAll(/(?<![a-zA-Z0-9])__(.+?)__(?![a-zA-Z0-9])/g)) {
|
|
138
|
-
const s = m.index;
|
|
139
|
-
if (!overlaps(segs, s, s + m[0].length)) {
|
|
140
|
-
segs.push({
|
|
141
|
-
start: s,
|
|
142
|
-
end: s + m[0].length,
|
|
143
|
-
tokens: [
|
|
144
|
-
tok("strong-marker", "__", baseOffset + s),
|
|
145
|
-
tok("strong", m[1], baseOffset + s + 2),
|
|
146
|
-
tok("strong-marker", "__", baseOffset + s + m[0].length - 2)
|
|
147
|
-
]
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
for (const m of raw.matchAll(/(?<!\*)\*([^*]+)\*(?!\*)/g)) {
|
|
152
|
-
const s = m.index;
|
|
153
|
-
if (!overlaps(segs, s, s + m[0].length)) {
|
|
154
|
-
segs.push({
|
|
155
|
-
start: s,
|
|
156
|
-
end: s + m[0].length,
|
|
157
|
-
tokens: [
|
|
158
|
-
tok("emphasis-marker", "*", baseOffset + s),
|
|
159
|
-
tok("emphasis", m[1], baseOffset + s + 1),
|
|
160
|
-
tok("emphasis-marker", "*", baseOffset + s + m[0].length - 1)
|
|
161
|
-
]
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
for (const m of raw.matchAll(/(?<![a-zA-Z0-9])_([^_]+)_(?![a-zA-Z0-9])/g)) {
|
|
166
|
-
const s = m.index;
|
|
167
|
-
if (!overlaps(segs, s, s + m[0].length)) {
|
|
168
|
-
segs.push({
|
|
169
|
-
start: s,
|
|
170
|
-
end: s + m[0].length,
|
|
171
|
-
tokens: [
|
|
172
|
-
tok("emphasis-marker", "_", baseOffset + s),
|
|
173
|
-
tok("emphasis", m[1], baseOffset + s + 1),
|
|
174
|
-
tok("emphasis-marker", "_", baseOffset + s + m[0].length - 1)
|
|
175
|
-
]
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
segs.sort((a, b) => a.start - b.start);
|
|
180
|
-
const result = [];
|
|
181
|
-
let pos = 0;
|
|
182
|
-
for (const seg of segs) {
|
|
183
|
-
if (seg.start > pos) {
|
|
184
|
-
result.push(tok("text", raw.slice(pos, seg.start), baseOffset + pos));
|
|
185
|
-
}
|
|
186
|
-
result.push(...seg.tokens);
|
|
187
|
-
pos = seg.end;
|
|
188
|
-
}
|
|
189
|
-
if (pos < raw.length) {
|
|
190
|
-
result.push(tok("text", raw.slice(pos), baseOffset + pos));
|
|
191
|
-
}
|
|
192
|
-
return result;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// src/parser/blocks.ts
|
|
196
|
-
var MATH_FENCE_RE = /^\$\$\s*$/;
|
|
197
|
-
var SINGLE_LINE_MATH_BLOCK_RE = /^ {0,3}\$\$(?!\s*$)([^\n]*?)(?<!\\)\$\$\s*$/;
|
|
198
|
-
function tokenizeBlock(raw, blockType, baseOffset = 0) {
|
|
199
|
-
if (blockType === "code-fence") {
|
|
200
|
-
return tokenizeCodeFence(raw, baseOffset);
|
|
201
|
-
}
|
|
202
|
-
if (blockType === "directive-container") {
|
|
203
|
-
return tokenizeMultiLineBlock(raw, baseOffset, (line, i, isFirst, isLast) => {
|
|
204
|
-
if (isFirst || isLast && /^:::\s*$/.test(line)) {
|
|
205
|
-
return [{ type: "fence-marker", text: line, start: -1, end: -1 }];
|
|
206
|
-
}
|
|
207
|
-
return tokenizeInline(line, -1);
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
if (blockType === "math-block") {
|
|
211
|
-
const singleLineMatch = raw.match(SINGLE_LINE_MATH_BLOCK_RE);
|
|
212
|
-
if (singleLineMatch) {
|
|
213
|
-
const leadingWhitespace = raw.match(/^ {0,3}/)?.[0] ?? "";
|
|
214
|
-
const trimmed = raw.slice(leadingWhitespace.length).replace(/\s+$/, "");
|
|
215
|
-
const inner = trimmed.slice(2, -2);
|
|
216
|
-
const start = baseOffset + leadingWhitespace.length;
|
|
217
|
-
return [
|
|
218
|
-
{ type: "fence-marker", text: "$$", start, end: start + 2 },
|
|
219
|
-
{ type: "math-text", text: inner, start: start + 2, end: start + 2 + inner.length },
|
|
220
|
-
{ type: "fence-marker", text: "$$", start: start + 2 + inner.length, end: start + 4 + inner.length }
|
|
221
|
-
];
|
|
222
|
-
}
|
|
223
|
-
return tokenizeMultiLineBlock(raw, baseOffset, (line, _i, isFirst, isLast) => {
|
|
224
|
-
if ((isFirst || isLast) && MATH_FENCE_RE.test(line)) {
|
|
225
|
-
return [{ type: "fence-marker", text: line, start: -1, end: -1 }];
|
|
226
|
-
}
|
|
227
|
-
return [{ type: "math-text", text: line, start: -1, end: -1 }];
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
if (blockType === "table") {
|
|
231
|
-
return tokenizeMultiLineBlock(raw, baseOffset, (line) => {
|
|
232
|
-
return tokenizeInline(line, -1);
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
if (blockType === "thematic-break") {
|
|
236
|
-
return [{ type: "hr", text: raw, start: baseOffset, end: baseOffset + raw.length }];
|
|
237
|
-
}
|
|
238
|
-
const headingMatch = raw.match(/^(#{1,6}\s)(.*)/);
|
|
239
|
-
if (headingMatch && blockType === "heading") {
|
|
240
|
-
return [
|
|
241
|
-
{ type: "heading-marker", text: headingMatch[1], start: baseOffset, end: baseOffset + headingMatch[1].length },
|
|
242
|
-
...tokenizeInline(headingMatch[2], baseOffset + headingMatch[1].length)
|
|
243
|
-
];
|
|
244
|
-
}
|
|
245
|
-
const bqMatch = raw.match(/^((?:>\s?)+)(.*)/);
|
|
246
|
-
if (bqMatch && blockType === "blockquote") {
|
|
247
|
-
return [
|
|
248
|
-
{ type: "blockquote-marker", text: bqMatch[1], start: baseOffset, end: baseOffset + bqMatch[1].length },
|
|
249
|
-
...tokenizeInline(bqMatch[2], baseOffset + bqMatch[1].length)
|
|
250
|
-
];
|
|
251
|
-
}
|
|
252
|
-
const listMatch = raw.match(/^(\s*(?:[-*+]|\d+\.)\s)(.*)/);
|
|
253
|
-
if (listMatch && (blockType === "unordered-list" || blockType === "ordered-list")) {
|
|
254
|
-
return [
|
|
255
|
-
{ type: "list-marker", text: listMatch[1], start: baseOffset, end: baseOffset + listMatch[1].length },
|
|
256
|
-
...tokenizeInline(listMatch[2], baseOffset + listMatch[1].length)
|
|
257
|
-
];
|
|
258
|
-
}
|
|
259
|
-
return tokenizeInline(raw, baseOffset);
|
|
260
|
-
}
|
|
261
|
-
function tokenizeMultiLineBlock(raw, baseOffset, classifyLine) {
|
|
262
|
-
const lines = raw.split("\n");
|
|
263
|
-
const tokens = [];
|
|
264
|
-
let offset = baseOffset;
|
|
265
|
-
for (let i = 0; i < lines.length; i++) {
|
|
266
|
-
const line = lines[i];
|
|
267
|
-
if (i > 0) {
|
|
268
|
-
tokens.push({ type: "text", text: "\n", start: offset, end: offset + 1 });
|
|
269
|
-
offset += 1;
|
|
270
|
-
}
|
|
271
|
-
const lineTokens = classifyLine(line, i, i === 0, i === lines.length - 1);
|
|
272
|
-
let pos = offset;
|
|
273
|
-
for (const t of lineTokens) {
|
|
274
|
-
if (t.start === -1) {
|
|
275
|
-
t.start = pos;
|
|
276
|
-
t.end = pos + t.text.length;
|
|
277
|
-
}
|
|
278
|
-
pos = t.end;
|
|
279
|
-
}
|
|
280
|
-
tokens.push(...lineTokens);
|
|
281
|
-
offset += line.length;
|
|
282
|
-
}
|
|
283
|
-
return tokens;
|
|
284
|
-
}
|
|
285
|
-
function tokenizeCodeFence(raw, baseOffset) {
|
|
286
|
-
const lines = raw.split("\n");
|
|
287
|
-
const tokens = [];
|
|
288
|
-
let offset = baseOffset;
|
|
289
|
-
for (let i = 0; i < lines.length; i++) {
|
|
290
|
-
const line = lines[i];
|
|
291
|
-
if (i > 0) {
|
|
292
|
-
tokens.push({ type: "text", text: "\n", start: offset, end: offset + 1 });
|
|
293
|
-
offset += 1;
|
|
294
|
-
}
|
|
295
|
-
if (i === 0) {
|
|
296
|
-
const fenceMatch = line.match(/^( {0,3}[`~]{3,})(.*)/);
|
|
297
|
-
if (fenceMatch) {
|
|
298
|
-
tokens.push({ type: "fence-marker", text: fenceMatch[1], start: offset, end: offset + fenceMatch[1].length });
|
|
299
|
-
if (fenceMatch[2]) {
|
|
300
|
-
tokens.push({ type: "fence-lang", text: fenceMatch[2], start: offset + fenceMatch[1].length, end: offset + line.length });
|
|
301
|
-
}
|
|
302
|
-
} else {
|
|
303
|
-
tokens.push({ type: "fence-marker", text: line, start: offset, end: offset + line.length });
|
|
304
|
-
}
|
|
305
|
-
} else if (i === lines.length - 1 && /^ {0,3}[`~]{3,}\s*$/.test(line)) {
|
|
306
|
-
tokens.push({ type: "fence-marker", text: line, start: offset, end: offset + line.length });
|
|
307
|
-
} else {
|
|
308
|
-
tokens.push({ type: "code-block-text", text: line, start: offset, end: offset + line.length });
|
|
309
|
-
}
|
|
310
|
-
offset += line.length;
|
|
311
|
-
}
|
|
312
|
-
return tokens;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// src/model/document.ts
|
|
316
|
-
function createBlockIdGenerator() {
|
|
317
|
-
let nextId = 1;
|
|
318
|
-
return () => "b" + nextId++;
|
|
319
|
-
}
|
|
320
|
-
var defaultNextId = 1;
|
|
321
|
-
function defaultGenBlockId() {
|
|
322
|
-
return "b" + defaultNextId++;
|
|
323
|
-
}
|
|
324
|
-
function resetBlockIdCounter() {
|
|
325
|
-
defaultNextId = 1;
|
|
326
|
-
}
|
|
327
|
-
function tokensToDecorators(tokens, raw, baseOffset) {
|
|
328
|
-
const decorators = [];
|
|
329
|
-
let pos = baseOffset;
|
|
330
|
-
for (const token of tokens) {
|
|
331
|
-
if (token.start > pos) {
|
|
332
|
-
const gapText = raw.slice(pos - baseOffset, token.start - baseOffset);
|
|
333
|
-
if (gapText) {
|
|
334
|
-
decorators.push({ type: "text", leaves: [{ text: gapText }] });
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
decorators.push({
|
|
338
|
-
type: token.type,
|
|
339
|
-
leaves: [{ text: token.text }]
|
|
340
|
-
});
|
|
341
|
-
pos = token.end;
|
|
342
|
-
}
|
|
343
|
-
if (pos - baseOffset < raw.length) {
|
|
344
|
-
const trailing = raw.slice(pos - baseOffset);
|
|
345
|
-
if (trailing) {
|
|
346
|
-
decorators.push({ type: "text", leaves: [{ text: trailing }] });
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
return decorators;
|
|
350
|
-
}
|
|
351
|
-
var HEADING_RE = /^(#{1,6})\s/;
|
|
352
|
-
var UL_RE = /^(\s*)([-*+])\s/;
|
|
353
|
-
var OL_RE = /^(\s*)(\d+)\.\s/;
|
|
354
|
-
var BQ_RE = /^(\s*>)/;
|
|
355
|
-
var FENCE_RE = /^( {0,3})([`~]{3,})(.*)/;
|
|
356
|
-
var HR_RE = /^(\*{3,}|-{3,}|_{3,})\s*$/;
|
|
357
|
-
var DIRECTIVE_OPEN_RE = /^:::(\w+)/;
|
|
358
|
-
var DIRECTIVE_CLOSE_RE = /^:::\s*$/;
|
|
359
|
-
var TABLE_SEPARATOR_RE = /^\|[\s:]*-{3,}[\s:]*(\|[\s:]*-{3,}[\s:]*)*\|?\s*$/;
|
|
360
|
-
var TABLE_ROW_RE = /^\|/;
|
|
361
|
-
function parseMarkdownToDocument(markdown, genId) {
|
|
362
|
-
const genBlockId = genId ?? defaultGenBlockId;
|
|
363
|
-
const lines = markdown.split("\n");
|
|
364
|
-
const blocks = [];
|
|
365
|
-
let i = 0;
|
|
366
|
-
let offset = 0;
|
|
367
|
-
while (i < lines.length) {
|
|
368
|
-
const line = lines[i];
|
|
369
|
-
const fenceMatch = line.match(FENCE_RE);
|
|
370
|
-
if (fenceMatch) {
|
|
371
|
-
const marker = fenceMatch[2];
|
|
372
|
-
const closeRe = new RegExp(`^ {0,3}${marker[0]}{${marker.length},}\\s*$`);
|
|
373
|
-
let closeIndex = -1;
|
|
374
|
-
for (let j = i + 1; j < lines.length; j++) {
|
|
375
|
-
if (closeRe.test(lines[j])) {
|
|
376
|
-
closeIndex = j;
|
|
377
|
-
break;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
if (closeIndex !== -1) {
|
|
381
|
-
const lang = fenceMatch[3].trim().split(/\s+/)[0] || "";
|
|
382
|
-
const fenceLines = lines.slice(i, closeIndex + 1);
|
|
383
|
-
const raw = fenceLines.join("\n");
|
|
384
|
-
const tokens2 = tokenizeBlock(raw, "code-fence", offset);
|
|
385
|
-
blocks.push({
|
|
386
|
-
type: "code-fence",
|
|
387
|
-
raw,
|
|
388
|
-
language: lang,
|
|
389
|
-
id: genBlockId(),
|
|
390
|
-
depth: 0,
|
|
391
|
-
decorators: tokensToDecorators(tokens2, raw, offset)
|
|
392
|
-
});
|
|
393
|
-
offset += raw.length + 1;
|
|
394
|
-
i = closeIndex + 1;
|
|
395
|
-
continue;
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
const directiveMatch = line.match(DIRECTIVE_OPEN_RE);
|
|
399
|
-
if (directiveMatch) {
|
|
400
|
-
const directiveName = directiveMatch[1];
|
|
401
|
-
let closeIndex = -1;
|
|
402
|
-
for (let j = i + 1; j < lines.length; j++) {
|
|
403
|
-
if (DIRECTIVE_CLOSE_RE.test(lines[j])) {
|
|
404
|
-
closeIndex = j;
|
|
405
|
-
break;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
if (closeIndex !== -1) {
|
|
409
|
-
const containerLines = lines.slice(i, closeIndex + 1);
|
|
410
|
-
const raw = containerLines.join("\n");
|
|
411
|
-
const tokens2 = tokenizeBlock(raw, "directive-container", offset);
|
|
412
|
-
blocks.push({
|
|
413
|
-
type: "directive-container",
|
|
414
|
-
raw,
|
|
415
|
-
directiveName,
|
|
416
|
-
id: genBlockId(),
|
|
417
|
-
depth: 0,
|
|
418
|
-
decorators: tokensToDecorators(tokens2, raw, offset)
|
|
419
|
-
});
|
|
420
|
-
offset += raw.length + 1;
|
|
421
|
-
i = closeIndex + 1;
|
|
422
|
-
continue;
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
if (SINGLE_LINE_MATH_BLOCK_RE.test(line)) {
|
|
426
|
-
const tokens2 = tokenizeBlock(line, "math-block", offset);
|
|
427
|
-
blocks.push({
|
|
428
|
-
type: "math-block",
|
|
429
|
-
raw: line,
|
|
430
|
-
id: genBlockId(),
|
|
431
|
-
depth: 0,
|
|
432
|
-
decorators: tokensToDecorators(tokens2, line, offset)
|
|
433
|
-
});
|
|
434
|
-
offset += line.length + 1;
|
|
435
|
-
i++;
|
|
436
|
-
continue;
|
|
437
|
-
}
|
|
438
|
-
if (MATH_FENCE_RE.test(line)) {
|
|
439
|
-
let closeIndex = -1;
|
|
440
|
-
for (let j = i + 1; j < lines.length; j++) {
|
|
441
|
-
if (MATH_FENCE_RE.test(lines[j])) {
|
|
442
|
-
closeIndex = j;
|
|
443
|
-
break;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
if (closeIndex !== -1) {
|
|
447
|
-
const mathLines = lines.slice(i, closeIndex + 1);
|
|
448
|
-
const raw = mathLines.join("\n");
|
|
449
|
-
const tokens2 = tokenizeBlock(raw, "math-block", offset);
|
|
450
|
-
blocks.push({
|
|
451
|
-
type: "math-block",
|
|
452
|
-
raw,
|
|
453
|
-
id: genBlockId(),
|
|
454
|
-
depth: 0,
|
|
455
|
-
decorators: tokensToDecorators(tokens2, raw, offset)
|
|
456
|
-
});
|
|
457
|
-
offset += raw.length + 1;
|
|
458
|
-
i = closeIndex + 1;
|
|
459
|
-
continue;
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
if (TABLE_ROW_RE.test(line) && i + 1 < lines.length && TABLE_SEPARATOR_RE.test(lines[i + 1])) {
|
|
463
|
-
let endIndex = i + 1;
|
|
464
|
-
for (let j = i + 2; j < lines.length; j++) {
|
|
465
|
-
if (TABLE_ROW_RE.test(lines[j])) {
|
|
466
|
-
endIndex = j;
|
|
467
|
-
} else {
|
|
468
|
-
break;
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
const tableLines = lines.slice(i, endIndex + 1);
|
|
472
|
-
const raw = tableLines.join("\n");
|
|
473
|
-
const tokens2 = tokenizeBlock(raw, "table", offset);
|
|
474
|
-
blocks.push({
|
|
475
|
-
type: "table",
|
|
476
|
-
raw,
|
|
477
|
-
id: genBlockId(),
|
|
478
|
-
depth: 0,
|
|
479
|
-
decorators: tokensToDecorators(tokens2, raw, offset)
|
|
480
|
-
});
|
|
481
|
-
offset += raw.length + 1;
|
|
482
|
-
i = endIndex + 1;
|
|
483
|
-
continue;
|
|
484
|
-
}
|
|
485
|
-
if (HR_RE.test(line.trim())) {
|
|
486
|
-
const tokens2 = tokenizeBlock(line, "thematic-break", offset);
|
|
487
|
-
blocks.push({
|
|
488
|
-
type: "thematic-break",
|
|
489
|
-
raw: line,
|
|
490
|
-
id: genBlockId(),
|
|
491
|
-
depth: 0,
|
|
492
|
-
decorators: tokensToDecorators(tokens2, line, offset)
|
|
493
|
-
});
|
|
494
|
-
offset += line.length + 1;
|
|
495
|
-
i++;
|
|
496
|
-
continue;
|
|
497
|
-
}
|
|
498
|
-
const headingMatch = line.match(HEADING_RE);
|
|
499
|
-
if (headingMatch) {
|
|
500
|
-
const tokens2 = tokenizeBlock(line, "heading", offset);
|
|
501
|
-
blocks.push({
|
|
502
|
-
type: "heading",
|
|
503
|
-
raw: line,
|
|
504
|
-
headingLevel: headingMatch[1].length,
|
|
505
|
-
id: genBlockId(),
|
|
506
|
-
depth: 0,
|
|
507
|
-
decorators: tokensToDecorators(tokens2, line, offset)
|
|
508
|
-
});
|
|
509
|
-
offset += line.length + 1;
|
|
510
|
-
i++;
|
|
511
|
-
continue;
|
|
512
|
-
}
|
|
513
|
-
if (BQ_RE.test(line)) {
|
|
514
|
-
const bqMatch = line.match(/^((?:>\s?)+)/);
|
|
515
|
-
const bqDepth = bqMatch ? (bqMatch[1].match(/>/g) ?? []).length : 0;
|
|
516
|
-
const tokens2 = tokenizeBlock(line, "blockquote", offset);
|
|
517
|
-
blocks.push({
|
|
518
|
-
type: "blockquote",
|
|
519
|
-
raw: line,
|
|
520
|
-
id: genBlockId(),
|
|
521
|
-
depth: bqDepth,
|
|
522
|
-
decorators: tokensToDecorators(tokens2, line, offset)
|
|
523
|
-
});
|
|
524
|
-
offset += line.length + 1;
|
|
525
|
-
i++;
|
|
526
|
-
continue;
|
|
527
|
-
}
|
|
528
|
-
const ulMatch = line.match(UL_RE);
|
|
529
|
-
if (ulMatch) {
|
|
530
|
-
const indent = ulMatch[1].length;
|
|
531
|
-
const tokens2 = tokenizeBlock(line, "unordered-list", offset);
|
|
532
|
-
blocks.push({
|
|
533
|
-
type: "unordered-list",
|
|
534
|
-
raw: line,
|
|
535
|
-
id: genBlockId(),
|
|
536
|
-
depth: Math.floor(indent / 2),
|
|
537
|
-
decorators: tokensToDecorators(tokens2, line, offset)
|
|
538
|
-
});
|
|
539
|
-
offset += line.length + 1;
|
|
540
|
-
i++;
|
|
541
|
-
continue;
|
|
542
|
-
}
|
|
543
|
-
const olMatch = line.match(OL_RE);
|
|
544
|
-
if (olMatch) {
|
|
545
|
-
const indent = olMatch[1].length;
|
|
546
|
-
const tokens2 = tokenizeBlock(line, "ordered-list", offset);
|
|
547
|
-
blocks.push({
|
|
548
|
-
type: "ordered-list",
|
|
549
|
-
raw: line,
|
|
550
|
-
id: genBlockId(),
|
|
551
|
-
depth: Math.floor(indent / 2),
|
|
552
|
-
decorators: tokensToDecorators(tokens2, line, offset)
|
|
553
|
-
});
|
|
554
|
-
offset += line.length + 1;
|
|
555
|
-
i++;
|
|
556
|
-
continue;
|
|
557
|
-
}
|
|
558
|
-
const tokens = tokenizeBlock(line, "paragraph", offset);
|
|
559
|
-
blocks.push({
|
|
560
|
-
type: "paragraph",
|
|
561
|
-
raw: line,
|
|
562
|
-
id: genBlockId(),
|
|
563
|
-
depth: 0,
|
|
564
|
-
decorators: tokensToDecorators(tokens, line, offset)
|
|
565
|
-
});
|
|
566
|
-
offset += line.length + 1;
|
|
567
|
-
i++;
|
|
568
|
-
}
|
|
569
|
-
return { blocks };
|
|
570
|
-
}
|
|
571
|
-
function blocksMatch(a, b) {
|
|
572
|
-
return a.raw === b.raw && a.type === b.type;
|
|
573
|
-
}
|
|
574
|
-
function reconcileBlocks(oldBlocks, newBlocks) {
|
|
575
|
-
const oldLen = oldBlocks.length;
|
|
576
|
-
const newLen = newBlocks.length;
|
|
577
|
-
let head = 0;
|
|
578
|
-
while (head < oldLen && head < newLen && blocksMatch(oldBlocks[head], newBlocks[head])) {
|
|
579
|
-
head++;
|
|
580
|
-
}
|
|
581
|
-
let tail = 0;
|
|
582
|
-
while (tail < oldLen - head && tail < newLen - head && blocksMatch(oldBlocks[oldLen - 1 - tail], newBlocks[newLen - 1 - tail])) {
|
|
583
|
-
tail++;
|
|
584
|
-
}
|
|
585
|
-
const result = new Array(newLen);
|
|
586
|
-
for (let i = 0; i < head; i++) {
|
|
587
|
-
result[i] = oldBlocks[i];
|
|
588
|
-
}
|
|
589
|
-
for (let i = head; i < newLen - tail; i++) {
|
|
590
|
-
result[i] = newBlocks[i];
|
|
591
|
-
}
|
|
592
|
-
for (let j = 0; j < tail; j++) {
|
|
593
|
-
result[newLen - tail + j] = oldBlocks[oldLen - tail + j];
|
|
594
|
-
}
|
|
595
|
-
return result;
|
|
596
|
-
}
|
|
597
|
-
function serializeDocument(doc) {
|
|
598
|
-
return doc.blocks.map((b) => b.raw).join("\n");
|
|
599
|
-
}
|
|
600
|
-
var blockIdIndexCache = /* @__PURE__ */ new WeakMap();
|
|
601
|
-
function getBlockIdIndex(blocks) {
|
|
602
|
-
let index = blockIdIndexCache.get(blocks);
|
|
603
|
-
if (index) return index;
|
|
604
|
-
index = /* @__PURE__ */ new Map();
|
|
605
|
-
for (let i = 0; i < blocks.length; i++) {
|
|
606
|
-
index.set(blocks[i].id, i);
|
|
607
|
-
}
|
|
608
|
-
blockIdIndexCache.set(blocks, index);
|
|
609
|
-
return index;
|
|
610
|
-
}
|
|
611
|
-
function getBlockById(doc, blockId) {
|
|
612
|
-
const idx = getBlockIdIndex(doc.blocks).get(blockId);
|
|
613
|
-
return idx !== void 0 ? doc.blocks[idx] : void 0;
|
|
614
|
-
}
|
|
615
|
-
function getBlockIndexById(doc, blockId) {
|
|
616
|
-
return getBlockIdIndex(doc.blocks).get(blockId) ?? -1;
|
|
617
|
-
}
|
|
618
|
-
var prefixSumCache = /* @__PURE__ */ new WeakMap();
|
|
619
|
-
function getPrefixSums(blocks) {
|
|
620
|
-
let sums = prefixSumCache.get(blocks);
|
|
621
|
-
if (sums) return sums;
|
|
622
|
-
sums = new Array(blocks.length);
|
|
623
|
-
let pos = 0;
|
|
624
|
-
for (let i = 0; i < blocks.length; i++) {
|
|
625
|
-
sums[i] = pos;
|
|
626
|
-
pos += blocks[i].raw.length + 1;
|
|
627
|
-
}
|
|
628
|
-
prefixSumCache.set(blocks, sums);
|
|
629
|
-
return sums;
|
|
630
|
-
}
|
|
631
|
-
function getBlockAtOffset(doc, offset) {
|
|
632
|
-
const { blocks } = doc;
|
|
633
|
-
if (blocks.length === 0) return { blockIndex: 0, offsetInBlock: 0 };
|
|
634
|
-
const sums = getPrefixSums(blocks);
|
|
635
|
-
let lo = 0;
|
|
636
|
-
let hi = blocks.length - 1;
|
|
637
|
-
while (lo < hi) {
|
|
638
|
-
const mid = lo + hi + 1 >>> 1;
|
|
639
|
-
if (sums[mid] <= offset) {
|
|
640
|
-
lo = mid;
|
|
641
|
-
} else {
|
|
642
|
-
hi = mid - 1;
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
return { blockIndex: lo, offsetInBlock: offset - sums[lo] };
|
|
646
|
-
}
|
|
647
|
-
function getBlockStartOffset(doc, blockIndex) {
|
|
648
|
-
const { blocks } = doc;
|
|
649
|
-
if (blocks.length === 0 || blockIndex <= 0) return 0;
|
|
650
|
-
const sums = getPrefixSums(blocks);
|
|
651
|
-
const clamped = Math.min(blockIndex, blocks.length - 1);
|
|
652
|
-
return sums[clamped];
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
// src/model/virtual-selection.ts
|
|
656
|
-
function linearToVirtualPosition(doc, linearOffset) {
|
|
657
|
-
const { blocks } = doc;
|
|
658
|
-
if (blocks.length === 0) return { blockId: "", offset: 0 };
|
|
659
|
-
const { blockIndex, offsetInBlock } = getBlockAtOffset(doc, linearOffset);
|
|
660
|
-
const block = blocks[blockIndex];
|
|
661
|
-
return {
|
|
662
|
-
blockId: block.id,
|
|
663
|
-
offset: Math.max(0, Math.min(offsetInBlock, block.raw.length))
|
|
664
|
-
};
|
|
665
|
-
}
|
|
666
|
-
function linearToVirtual(doc, selection) {
|
|
667
|
-
return {
|
|
668
|
-
anchor: linearToVirtualPosition(doc, selection.anchor),
|
|
669
|
-
focus: linearToVirtualPosition(doc, selection.focus)
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
function virtualPositionToLinear(doc, vpos) {
|
|
673
|
-
const idx = getBlockIndexById(doc, vpos.blockId);
|
|
674
|
-
if (idx === -1) {
|
|
675
|
-
return clampToLastBlock(doc, vpos.offset);
|
|
676
|
-
}
|
|
677
|
-
const block = doc.blocks[idx];
|
|
678
|
-
const start = getBlockStartOffset(doc, idx);
|
|
679
|
-
return start + Math.max(0, Math.min(vpos.offset, block.raw.length));
|
|
680
|
-
}
|
|
681
|
-
function virtualToLinear(doc, vsel) {
|
|
682
|
-
return {
|
|
683
|
-
anchor: virtualPositionToLinear(doc, vsel.anchor),
|
|
684
|
-
focus: virtualPositionToLinear(doc, vsel.focus)
|
|
685
|
-
};
|
|
686
|
-
}
|
|
687
|
-
function clampToLastBlock(doc, offset) {
|
|
688
|
-
if (doc.blocks.length === 0) return 0;
|
|
689
|
-
const lastIdx = doc.blocks.length - 1;
|
|
690
|
-
const lastBlock = doc.blocks[lastIdx];
|
|
691
|
-
const start = getBlockStartOffset(doc, lastIdx);
|
|
692
|
-
return start + Math.min(offset, lastBlock.raw.length);
|
|
693
|
-
}
|
|
694
|
-
function virtualPositionsEqual(a, b) {
|
|
695
|
-
return a.blockId === b.blockId && a.offset === b.offset;
|
|
696
|
-
}
|
|
697
|
-
function isVirtualSelectionCollapsed(vsel) {
|
|
698
|
-
return virtualPositionsEqual(vsel.anchor, vsel.focus);
|
|
699
|
-
}
|
|
700
|
-
function getBlockIndexForPosition(doc, vpos) {
|
|
701
|
-
return getBlockIndexById(doc, vpos.blockId);
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
// src/patterns/side-annotation.ts
|
|
705
|
-
var SIDE_ANNOTATION_TAIL_RE = /\(>(?:[}{:\]\[|\-!?~]|->|=>|~>|}\->|}\=>|}\!|}\?|\]->|\]=>|\|->|\|=>)[^\n)]+\)\s*$/;
|
|
706
|
-
var SIDE_CONTINUATION_TAIL_RE = /\(>\+\)\s*$/;
|
|
707
|
-
|
|
708
|
-
// src/commands/enter.ts
|
|
709
|
-
var NOT_HANDLED = {
|
|
710
|
-
handled: false,
|
|
711
|
-
value: "",
|
|
712
|
-
selectionStart: 0,
|
|
713
|
-
selectionEnd: 0
|
|
714
|
-
};
|
|
715
|
-
function getLineAt(text, pos) {
|
|
716
|
-
let lineStart = pos;
|
|
717
|
-
while (lineStart > 0 && text[lineStart - 1] !== "\n") lineStart--;
|
|
718
|
-
let lineEnd = pos;
|
|
719
|
-
while (lineEnd < text.length && text[lineEnd] !== "\n") lineEnd++;
|
|
720
|
-
return { line: text.slice(lineStart, lineEnd), lineStart, lineEnd };
|
|
721
|
-
}
|
|
722
|
-
function escapeRegExp(text) {
|
|
723
|
-
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
724
|
-
}
|
|
725
|
-
function parseStructuralTokens(line) {
|
|
726
|
-
let index = 0;
|
|
727
|
-
let listSeen = false;
|
|
728
|
-
const tokens = [];
|
|
729
|
-
while (index < line.length) {
|
|
730
|
-
const remainder = line.slice(index);
|
|
731
|
-
const tokenMatch = remainder.match(/^(?:\s*> ?|\s*(?:[-*+]|\d+\.)\s+)/);
|
|
732
|
-
if (!tokenMatch) break;
|
|
733
|
-
const raw = tokenMatch[0];
|
|
734
|
-
const trimmed = raw.trimStart();
|
|
735
|
-
if (trimmed.startsWith(">")) {
|
|
736
|
-
tokens.push({ kind: "blockquote", raw });
|
|
737
|
-
index += raw.length;
|
|
738
|
-
continue;
|
|
739
|
-
}
|
|
740
|
-
if (listSeen) break;
|
|
741
|
-
const unordered = trimmed.match(/^([-*+])\s+$/);
|
|
742
|
-
if (unordered) {
|
|
743
|
-
tokens.push({ kind: "unordered-list", raw, marker: unordered[1] });
|
|
744
|
-
listSeen = true;
|
|
745
|
-
index += raw.length;
|
|
746
|
-
continue;
|
|
747
|
-
}
|
|
748
|
-
const ordered = trimmed.match(/^(\d+)\.\s+$/);
|
|
749
|
-
if (ordered) {
|
|
750
|
-
tokens.push({ kind: "ordered-list", raw, number: parseInt(ordered[1], 10) });
|
|
751
|
-
listSeen = true;
|
|
752
|
-
index += raw.length;
|
|
753
|
-
continue;
|
|
754
|
-
}
|
|
755
|
-
break;
|
|
756
|
-
}
|
|
757
|
-
return {
|
|
758
|
-
prefix: line.slice(0, index),
|
|
759
|
-
rest: line.slice(index),
|
|
760
|
-
tokens
|
|
761
|
-
};
|
|
762
|
-
}
|
|
763
|
-
function getContinuationPrefix(tokens) {
|
|
764
|
-
return tokens.map((token) => {
|
|
765
|
-
if (token.kind === "ordered-list") {
|
|
766
|
-
return token.raw.replace(/\d+\.(\s+)$/, `${token.number + 1}.$1`);
|
|
767
|
-
}
|
|
768
|
-
return token.raw;
|
|
769
|
-
}).join("");
|
|
770
|
-
}
|
|
771
|
-
function matchFenceLine(line) {
|
|
772
|
-
const { prefix, rest } = parseStructuralTokens(line);
|
|
773
|
-
const match = rest.match(/^([`~]{3,})([^\n]*)$/);
|
|
774
|
-
if (!match) return null;
|
|
775
|
-
return { prefix, marker: match[1] };
|
|
776
|
-
}
|
|
777
|
-
function isMatchingFenceClose(line, prefix, marker) {
|
|
778
|
-
const { prefix: linePrefix, rest } = parseStructuralTokens(line);
|
|
779
|
-
if (linePrefix !== prefix) return false;
|
|
780
|
-
const closeRe = new RegExp(`^${escapeRegExp(marker[0])}{${marker.length},}\\s*$`);
|
|
781
|
-
return closeRe.test(rest);
|
|
782
|
-
}
|
|
783
|
-
function findFenceContext(text, pos) {
|
|
784
|
-
const lines = text.slice(0, pos).split("\n");
|
|
785
|
-
let context = null;
|
|
786
|
-
for (const line of lines) {
|
|
787
|
-
const fence = matchFenceLine(line);
|
|
788
|
-
if (!context) {
|
|
789
|
-
if (fence) context = fence;
|
|
790
|
-
continue;
|
|
791
|
-
}
|
|
792
|
-
if (isMatchingFenceClose(line, context.prefix, context.marker)) {
|
|
793
|
-
context = null;
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
return context;
|
|
797
|
-
}
|
|
798
|
-
function hasSideAnnotationTail(content) {
|
|
799
|
-
return SIDE_ANNOTATION_TAIL_RE.test(content) || SIDE_CONTINUATION_TAIL_RE.test(content);
|
|
800
|
-
}
|
|
801
|
-
function getContinuationContentSplit(content, cursorInContent) {
|
|
802
|
-
const markerMatch = content.match(SIDE_CONTINUATION_TAIL_RE);
|
|
803
|
-
if (!markerMatch || markerMatch.index == null) return null;
|
|
804
|
-
if (cursorInContent < 0 || cursorInContent >= markerMatch.index) return null;
|
|
805
|
-
const afterCursor = content.slice(cursorInContent);
|
|
806
|
-
const beforeMarkerAfterCursor = afterCursor.slice(0, markerMatch.index - cursorInContent);
|
|
807
|
-
if (!beforeMarkerAfterCursor.trim()) return null;
|
|
808
|
-
return {
|
|
809
|
-
beforeCursor: content.slice(0, cursorInContent),
|
|
810
|
-
afterCursor
|
|
811
|
-
};
|
|
812
|
-
}
|
|
813
|
-
function handleSideAnnotationContinuation(text, line, lineStart, lineEnd, selectionEnd, structural) {
|
|
814
|
-
const content = structural.rest;
|
|
815
|
-
if (!hasSideAnnotationTail(content)) return NOT_HANDLED;
|
|
816
|
-
const cursorInContent = selectionEnd - lineStart - structural.prefix.length;
|
|
817
|
-
const splitContent = getContinuationContentSplit(content, cursorInContent);
|
|
818
|
-
if (splitContent) {
|
|
819
|
-
const newText2 = text.slice(0, lineStart) + structural.prefix + splitContent.beforeCursor + `
|
|
820
|
-
${structural.prefix}` + splitContent.afterCursor + text.slice(lineEnd);
|
|
821
|
-
const newCursor2 = lineStart + structural.prefix.length + splitContent.beforeCursor.length + 1 + structural.prefix.length;
|
|
822
|
-
return { handled: true, value: newText2, selectionStart: newCursor2, selectionEnd: newCursor2 };
|
|
823
|
-
}
|
|
824
|
-
const contentBeforeMarker = content.replace(SIDE_CONTINUATION_TAIL_RE, "").trim();
|
|
825
|
-
if (SIDE_CONTINUATION_TAIL_RE.test(content) && !contentBeforeMarker) {
|
|
826
|
-
const newText2 = text.slice(0, lineStart) + structural.prefix + text.slice(lineEnd);
|
|
827
|
-
const newCursor2 = lineStart + structural.prefix.length;
|
|
828
|
-
return { handled: true, value: newText2, selectionStart: newCursor2, selectionEnd: newCursor2 };
|
|
829
|
-
}
|
|
830
|
-
const suffix = " (>+)";
|
|
831
|
-
const prefix = structural.prefix;
|
|
832
|
-
const insert = `
|
|
833
|
-
${prefix}${suffix}`;
|
|
834
|
-
const insertAt = SIDE_CONTINUATION_TAIL_RE.test(text.slice(selectionEnd)) ? lineEnd : selectionEnd;
|
|
835
|
-
let newText = text.slice(0, insertAt) + insert + text.slice(insertAt);
|
|
836
|
-
const hasTerminalBlankLine = text.endsWith("\n");
|
|
837
|
-
const hasFollowingSideContinuation = hasSideContinuationBelow(text, lineEnd);
|
|
838
|
-
if (hasTerminalBlankLine && hasFollowingSideContinuation && newText.endsWith("\n")) {
|
|
839
|
-
newText = newText.slice(0, -1);
|
|
840
|
-
}
|
|
841
|
-
const newCursor = insertAt + 1 + prefix.length;
|
|
842
|
-
return { handled: true, value: newText, selectionStart: newCursor, selectionEnd: newCursor };
|
|
843
|
-
}
|
|
844
|
-
function hasSideContinuationBelow(text, lineEnd) {
|
|
845
|
-
if (lineEnd >= text.length || text[lineEnd] !== "\n") return false;
|
|
846
|
-
const nextLineStart = lineEnd + 1;
|
|
847
|
-
if (nextLineStart >= text.length) return false;
|
|
848
|
-
let nextLineEnd = nextLineStart;
|
|
849
|
-
while (nextLineEnd < text.length && text[nextLineEnd] !== "\n") nextLineEnd++;
|
|
850
|
-
const nextLine = text.slice(nextLineStart, nextLineEnd);
|
|
851
|
-
const nextStructural = parseStructuralTokens(nextLine);
|
|
852
|
-
return hasSideAnnotationTail(nextStructural.rest);
|
|
853
|
-
}
|
|
854
|
-
function handleMarkdownEnter(params) {
|
|
855
|
-
const { text, selectionStart, selectionEnd } = params;
|
|
856
|
-
const { line, lineStart, lineEnd } = getLineAt(text, selectionStart);
|
|
857
|
-
const cursorInLine = selectionStart - lineStart;
|
|
858
|
-
const structural = parseStructuralTokens(line);
|
|
859
|
-
const listToken = structural.tokens.find((t) => t.kind === "unordered-list" || t.kind === "ordered-list");
|
|
860
|
-
const hasOnlyBlockquotes = structural.tokens.length > 0 && structural.tokens.every((t) => t.kind === "blockquote");
|
|
861
|
-
const fenceContextBeforeLine = findFenceContext(text, lineStart);
|
|
862
|
-
const textAfterCursor = line.slice(cursorInLine).trim();
|
|
863
|
-
const afterCursorIsSideMarkerOnly = params.enableSideAnnotation !== false && SIDE_CONTINUATION_TAIL_RE.test(textAfterCursor);
|
|
864
|
-
const sideContinuationSplitCandidate = params.enableSideAnnotation !== false && getContinuationContentSplit(structural.rest, cursorInLine - structural.prefix.length) !== null;
|
|
865
|
-
if (afterCursorIsSideMarkerOnly) {
|
|
866
|
-
const content = structural.rest;
|
|
867
|
-
const contentBeforeMarker = content.replace(SIDE_CONTINUATION_TAIL_RE, "").trim();
|
|
868
|
-
if (!contentBeforeMarker) {
|
|
869
|
-
const newText = text.slice(0, lineStart) + structural.prefix + text.slice(lineEnd);
|
|
870
|
-
const newCursor = lineStart + structural.prefix.length;
|
|
871
|
-
return { handled: true, value: newText, selectionStart: newCursor, selectionEnd: newCursor };
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
if (textAfterCursor && !hasOnlyBlockquotes && !afterCursorIsSideMarkerOnly && !sideContinuationSplitCandidate) {
|
|
875
|
-
return NOT_HANDLED;
|
|
876
|
-
}
|
|
877
|
-
const fenceMatch = matchFenceLine(line);
|
|
878
|
-
if (fenceMatch && !(fenceContextBeforeLine && isMatchingFenceClose(line, fenceContextBeforeLine.prefix, fenceContextBeforeLine.marker))) {
|
|
879
|
-
const prefix = fenceMatch.prefix;
|
|
880
|
-
const marker = fenceMatch.marker;
|
|
881
|
-
const textAfter = text.slice(lineEnd);
|
|
882
|
-
const closingRe = new RegExp(`^[\\s\\S]*?\\n${escapeRegExp(prefix)}${escapeRegExp(marker[0])}{${marker.length},}\\s*$`, "m");
|
|
883
|
-
if (!closingRe.test(textAfter)) {
|
|
884
|
-
const insert = `
|
|
885
|
-
${prefix}
|
|
886
|
-
${prefix}${marker}`;
|
|
887
|
-
const newText = text.slice(0, selectionEnd) + insert + text.slice(selectionEnd);
|
|
888
|
-
const newCursor = selectionEnd + 1 + prefix.length;
|
|
889
|
-
return { handled: true, value: newText, selectionStart: newCursor, selectionEnd: newCursor };
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
const fenceContext = findFenceContext(text, selectionStart);
|
|
893
|
-
if (fenceContext) {
|
|
894
|
-
const innerIndent = structural.rest.match(/^(\s+)/)?.[1] ?? "";
|
|
895
|
-
if (!fenceContext.prefix && !innerIndent) return NOT_HANDLED;
|
|
896
|
-
const insert = `
|
|
897
|
-
${fenceContext.prefix}${innerIndent}`;
|
|
898
|
-
const newText = text.slice(0, selectionEnd) + insert + text.slice(selectionEnd);
|
|
899
|
-
const newCursor = selectionEnd + insert.length;
|
|
900
|
-
return { handled: true, value: newText, selectionStart: newCursor, selectionEnd: newCursor };
|
|
901
|
-
}
|
|
902
|
-
if (listToken) {
|
|
903
|
-
const contentAfterPrefix = structural.rest.trim();
|
|
904
|
-
if (!contentAfterPrefix) {
|
|
905
|
-
if (structural.tokens.length === 1) {
|
|
906
|
-
const newText2 = text.slice(0, lineStart) + "\n" + text.slice(lineEnd);
|
|
907
|
-
const newCursor2 = lineStart + 1;
|
|
908
|
-
return { handled: true, value: newText2, selectionStart: newCursor2, selectionEnd: newCursor2 };
|
|
909
|
-
}
|
|
910
|
-
const outerTokens = structural.tokens.filter((t) => t !== listToken);
|
|
911
|
-
const outerPrefix = outerTokens.map((t) => t.raw).join("");
|
|
912
|
-
const newText = text.slice(0, lineStart) + outerPrefix + text.slice(lineEnd);
|
|
913
|
-
const newCursor = lineStart + outerPrefix.length;
|
|
914
|
-
return { handled: true, value: newText, selectionStart: newCursor, selectionEnd: newCursor };
|
|
915
|
-
}
|
|
916
|
-
if (contentAfterPrefix) {
|
|
917
|
-
const prefix = getContinuationPrefix(structural.tokens);
|
|
918
|
-
const insert = `
|
|
919
|
-
${prefix}`;
|
|
920
|
-
const newText = text.slice(0, selectionEnd) + insert + text.slice(selectionEnd);
|
|
921
|
-
const newCursor = selectionEnd + insert.length;
|
|
922
|
-
return { handled: true, value: newText, selectionStart: newCursor, selectionEnd: newCursor };
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
if (params.enableSideAnnotation !== false) {
|
|
926
|
-
const sideResult = handleSideAnnotationContinuation(text, line, lineStart, lineEnd, selectionEnd, structural);
|
|
927
|
-
if (sideResult.handled) return sideResult;
|
|
928
|
-
}
|
|
929
|
-
if (hasOnlyBlockquotes) {
|
|
930
|
-
const contentAfterPrefix = structural.rest.trim();
|
|
931
|
-
if (!contentAfterPrefix) {
|
|
932
|
-
const newText2 = text.slice(0, lineStart) + "\n" + text.slice(lineEnd);
|
|
933
|
-
const newCursor2 = lineStart + 1;
|
|
934
|
-
return { handled: true, value: newText2, selectionStart: newCursor2, selectionEnd: newCursor2 };
|
|
935
|
-
}
|
|
936
|
-
const prefix = /\s$/.test(structural.prefix) ? structural.prefix : `${structural.prefix} `;
|
|
937
|
-
const before = text.slice(0, selectionEnd);
|
|
938
|
-
const after = text.slice(selectionEnd);
|
|
939
|
-
const newText = before + `
|
|
940
|
-
${prefix}` + after;
|
|
941
|
-
const newCursor = selectionEnd + 1 + prefix.length;
|
|
942
|
-
return { handled: true, value: newText, selectionStart: newCursor, selectionEnd: newCursor };
|
|
943
|
-
}
|
|
944
|
-
return NOT_HANDLED;
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
// src/commands/indent.ts
|
|
948
|
-
var FOUR_SPACE_LANGUAGES = /* @__PURE__ */ new Set(["py", "python", "rs", "rust"]);
|
|
949
|
-
var TWO_SPACE_LANGUAGES = /* @__PURE__ */ new Set([
|
|
950
|
-
"bash",
|
|
951
|
-
"c",
|
|
952
|
-
"c++",
|
|
953
|
-
"cpp",
|
|
954
|
-
"css",
|
|
955
|
-
"html",
|
|
956
|
-
"javascript",
|
|
957
|
-
"js",
|
|
958
|
-
"json",
|
|
959
|
-
"jsx",
|
|
960
|
-
"shell",
|
|
961
|
-
"sh",
|
|
962
|
-
"ts",
|
|
963
|
-
"tsx",
|
|
964
|
-
"typescript"
|
|
965
|
-
]);
|
|
966
|
-
function getLineStarts(text) {
|
|
967
|
-
const starts = [0];
|
|
968
|
-
for (let i = 0; i < text.length; i++) {
|
|
969
|
-
if (text[i] === "\n") starts.push(i + 1);
|
|
970
|
-
}
|
|
971
|
-
return starts;
|
|
972
|
-
}
|
|
973
|
-
function findLineIndex(lineStarts, position) {
|
|
974
|
-
let low = 0;
|
|
975
|
-
let high = lineStarts.length - 1;
|
|
976
|
-
while (low <= high) {
|
|
977
|
-
const mid = Math.floor((low + high) / 2);
|
|
978
|
-
const lineStart = lineStarts[mid];
|
|
979
|
-
const nextLineStart = mid + 1 < lineStarts.length ? lineStarts[mid + 1] : Number.MAX_SAFE_INTEGER;
|
|
980
|
-
if (position < lineStart) {
|
|
981
|
-
high = mid - 1;
|
|
982
|
-
continue;
|
|
983
|
-
}
|
|
984
|
-
if (position >= nextLineStart) {
|
|
985
|
-
low = mid + 1;
|
|
986
|
-
continue;
|
|
987
|
-
}
|
|
988
|
-
return mid;
|
|
989
|
-
}
|
|
990
|
-
return lineStarts.length - 1;
|
|
991
|
-
}
|
|
992
|
-
function getLineRange(text, lineStarts, lineIndex) {
|
|
993
|
-
const start = lineStarts[lineIndex];
|
|
994
|
-
const end = lineIndex + 1 < lineStarts.length ? lineStarts[lineIndex + 1] : text.length;
|
|
995
|
-
return { start, end };
|
|
996
|
-
}
|
|
997
|
-
function parseFence(line) {
|
|
998
|
-
const match = line.match(/^\s*([`~]{3,})([^\n]*)$/);
|
|
999
|
-
if (!match) return null;
|
|
1000
|
-
return { marker: match[1], info: match[2].trim() };
|
|
1001
|
-
}
|
|
1002
|
-
function isClosingFence(line, marker) {
|
|
1003
|
-
const pattern = new RegExp(`^\\s*${marker[0]}{${marker.length},}\\s*$`);
|
|
1004
|
-
return pattern.test(line);
|
|
1005
|
-
}
|
|
1006
|
-
function normalizeFenceLanguage(info) {
|
|
1007
|
-
if (!info) return "";
|
|
1008
|
-
return info.split(/\s+/)[0]?.toLowerCase() || "";
|
|
1009
|
-
}
|
|
1010
|
-
function resolveFenceContext(text, selectionStart) {
|
|
1011
|
-
const lineStarts = getLineStarts(text);
|
|
1012
|
-
const currentLineIndex = findLineIndex(lineStarts, selectionStart);
|
|
1013
|
-
let activeFence = null;
|
|
1014
|
-
for (let lineIndex = 0; lineIndex < lineStarts.length; lineIndex++) {
|
|
1015
|
-
const { start, end } = getLineRange(text, lineStarts, lineIndex);
|
|
1016
|
-
const line = text.slice(start, end).replace(/\n$/, "");
|
|
1017
|
-
if (!activeFence) {
|
|
1018
|
-
const fence = parseFence(line);
|
|
1019
|
-
if (fence) activeFence = { ...fence, lineIndex };
|
|
1020
|
-
continue;
|
|
1021
|
-
}
|
|
1022
|
-
if (isClosingFence(line, activeFence.marker)) {
|
|
1023
|
-
if (currentLineIndex > activeFence.lineIndex && currentLineIndex < lineIndex) {
|
|
1024
|
-
return {
|
|
1025
|
-
fence: {
|
|
1026
|
-
language: normalizeFenceLanguage(activeFence.info),
|
|
1027
|
-
startLineIndex: activeFence.lineIndex + 1,
|
|
1028
|
-
endLineIndex: lineIndex - 1
|
|
1029
|
-
},
|
|
1030
|
-
lineStarts
|
|
1031
|
-
};
|
|
1032
|
-
}
|
|
1033
|
-
activeFence = null;
|
|
1034
|
-
continue;
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
if (activeFence && currentLineIndex > activeFence.lineIndex) {
|
|
1038
|
-
return {
|
|
1039
|
-
fence: {
|
|
1040
|
-
language: normalizeFenceLanguage(activeFence.info),
|
|
1041
|
-
startLineIndex: activeFence.lineIndex + 1,
|
|
1042
|
-
endLineIndex: lineStarts.length - 1
|
|
1043
|
-
},
|
|
1044
|
-
lineStarts
|
|
1045
|
-
};
|
|
1046
|
-
}
|
|
1047
|
-
return { fence: null, lineStarts };
|
|
1048
|
-
}
|
|
1049
|
-
function resolveIndentSize(language, mode) {
|
|
1050
|
-
if (mode === "2") return 2;
|
|
1051
|
-
if (mode === "4") return 4;
|
|
1052
|
-
const normalized = language.toLowerCase();
|
|
1053
|
-
if (FOUR_SPACE_LANGUAGES.has(normalized)) return 4;
|
|
1054
|
-
if (TWO_SPACE_LANGUAGES.has(normalized)) return 2;
|
|
1055
|
-
return 2;
|
|
1056
|
-
}
|
|
1057
|
-
function getSelectionLineIndexes(text, lineStarts, selectionStart, selectionEnd) {
|
|
1058
|
-
const selectionEndsAtLineStart = selectionEnd > selectionStart && lineStarts.includes(selectionEnd);
|
|
1059
|
-
const startLineIndex = findLineIndex(lineStarts, selectionStart);
|
|
1060
|
-
const effectiveEnd = selectionEndsAtLineStart ? selectionEnd - 1 : selectionEnd > selectionStart && text[selectionEnd - 1] === "\n" ? selectionEnd - 1 : selectionEnd;
|
|
1061
|
-
const endLineIndex = findLineIndex(lineStarts, Math.max(selectionStart, effectiveEnd));
|
|
1062
|
-
return { startLineIndex, endLineIndex };
|
|
1063
|
-
}
|
|
1064
|
-
function applyMarkdownIndent(params) {
|
|
1065
|
-
const { text, selectionStart, selectionEnd, shiftKey, mode } = params;
|
|
1066
|
-
const { fence, lineStarts } = resolveFenceContext(text, selectionStart);
|
|
1067
|
-
if (!fence) {
|
|
1068
|
-
return { handled: false, value: text, selectionStart, selectionEnd, indentSize: 0, language: "" };
|
|
1069
|
-
}
|
|
1070
|
-
const indentSize = resolveIndentSize(fence.language, mode);
|
|
1071
|
-
const indentUnit = " ".repeat(indentSize);
|
|
1072
|
-
if (selectionStart === selectionEnd) {
|
|
1073
|
-
const currentLineIndex = findLineIndex(lineStarts, selectionStart);
|
|
1074
|
-
const { start, end } = getLineRange(text, lineStarts, currentLineIndex);
|
|
1075
|
-
const line = text.slice(start, end);
|
|
1076
|
-
if (shiftKey) {
|
|
1077
|
-
const leadingSpaces = line.match(/^ */)?.[0].length || 0;
|
|
1078
|
-
const removable = Math.min(indentSize, leadingSpaces);
|
|
1079
|
-
if (removable === 0) {
|
|
1080
|
-
return { handled: true, value: text, selectionStart, selectionEnd, indentSize, language: fence.language };
|
|
1081
|
-
}
|
|
1082
|
-
const nextValue3 = `${text.slice(0, start)}${line.slice(removable)}${text.slice(end)}`;
|
|
1083
|
-
const cursorShift = Math.min(removable, Math.max(0, selectionStart - start));
|
|
1084
|
-
const nextSelection2 = selectionStart - cursorShift;
|
|
1085
|
-
return { handled: true, value: nextValue3, selectionStart: nextSelection2, selectionEnd: nextSelection2, indentSize, language: fence.language };
|
|
1086
|
-
}
|
|
1087
|
-
const nextValue2 = `${text.slice(0, selectionStart)}${indentUnit}${text.slice(selectionEnd)}`;
|
|
1088
|
-
const nextSelection = selectionStart + indentUnit.length;
|
|
1089
|
-
return { handled: true, value: nextValue2, selectionStart: nextSelection, selectionEnd: nextSelection, indentSize, language: fence.language };
|
|
1090
|
-
}
|
|
1091
|
-
const { startLineIndex, endLineIndex } = getSelectionLineIndexes(text, lineStarts, selectionStart, selectionEnd);
|
|
1092
|
-
const boundedStartLineIndex = Math.max(startLineIndex, fence.startLineIndex);
|
|
1093
|
-
const boundedEndLineIndex = Math.min(endLineIndex, fence.endLineIndex);
|
|
1094
|
-
if (boundedStartLineIndex > boundedEndLineIndex) {
|
|
1095
|
-
return { handled: false, value: text, selectionStart, selectionEnd, indentSize, language: fence.language };
|
|
1096
|
-
}
|
|
1097
|
-
const segmentStart = lineStarts[boundedStartLineIndex];
|
|
1098
|
-
const segmentEnd = boundedEndLineIndex + 1 < lineStarts.length ? lineStarts[boundedEndLineIndex + 1] : text.length;
|
|
1099
|
-
const segmentText = text.slice(segmentStart, segmentEnd);
|
|
1100
|
-
const hasTrailingNewline = segmentText.endsWith("\n");
|
|
1101
|
-
const lines = (hasTrailingNewline ? segmentText.slice(0, -1) : segmentText).split("\n");
|
|
1102
|
-
let totalInserted = 0;
|
|
1103
|
-
let totalRemoved = 0;
|
|
1104
|
-
const nextLines = lines.map((line) => {
|
|
1105
|
-
if (shiftKey) {
|
|
1106
|
-
const leadingSpaces = line.match(/^ */)?.[0].length || 0;
|
|
1107
|
-
const removable = Math.min(indentSize, leadingSpaces);
|
|
1108
|
-
totalRemoved += removable;
|
|
1109
|
-
return line.slice(removable);
|
|
1110
|
-
}
|
|
1111
|
-
totalInserted += indentUnit.length;
|
|
1112
|
-
return `${indentUnit}${line}`;
|
|
1113
|
-
});
|
|
1114
|
-
const nextSegment = `${nextLines.join("\n")}${hasTrailingNewline ? "\n" : ""}`;
|
|
1115
|
-
const nextValue = `${text.slice(0, segmentStart)}${nextSegment}${text.slice(segmentEnd)}`;
|
|
1116
|
-
const firstLineAdjustment = shiftKey ? Math.min(indentSize, text.slice(segmentStart, getLineRange(text, lineStarts, boundedStartLineIndex).end).match(/^ */)?.[0].length || 0) : indentUnit.length;
|
|
1117
|
-
return {
|
|
1118
|
-
handled: true,
|
|
1119
|
-
value: nextValue,
|
|
1120
|
-
selectionStart: selectionStart + (shiftKey ? -Math.min(firstLineAdjustment, Math.max(0, selectionStart - segmentStart)) : indentUnit.length),
|
|
1121
|
-
selectionEnd: selectionEnd + (shiftKey ? -totalRemoved : totalInserted),
|
|
1122
|
-
indentSize,
|
|
1123
|
-
language: fence.language
|
|
1124
|
-
};
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
// src/commands/char-input.ts
|
|
1128
|
-
var PAIRS = {
|
|
1129
|
-
"(": { open: "(", close: ")" },
|
|
1130
|
-
"[": { open: "[", close: "]" },
|
|
1131
|
-
"{": { open: "{", close: "}" },
|
|
1132
|
-
'"': { open: '"', close: '"' },
|
|
1133
|
-
"'": { open: "'", close: "'" },
|
|
1134
|
-
"`": { open: "`", close: "`" }
|
|
1135
|
-
};
|
|
1136
|
-
var CLOSING_CHARS = /* @__PURE__ */ new Set([")", "]", "}", '"', "'", "`"]);
|
|
1137
|
-
var MARKDOWN_PAIRS = {
|
|
1138
|
-
"*": { open: "*", close: "*" },
|
|
1139
|
-
"$": { open: "$", close: "$" }
|
|
1140
|
-
};
|
|
1141
|
-
function handleCharInput(params) {
|
|
1142
|
-
const { key, text, selectionStart, selectionEnd, isComposing } = params;
|
|
1143
|
-
if (isComposing) return NOT_HANDLED;
|
|
1144
|
-
if (key.length !== 1) return NOT_HANDLED;
|
|
1145
|
-
const hasSelection = selectionStart !== selectionEnd;
|
|
1146
|
-
const charAfter = selectionEnd < text.length ? text[selectionEnd] : "";
|
|
1147
|
-
if (!hasSelection && CLOSING_CHARS.has(key) && charAfter === key) {
|
|
1148
|
-
return {
|
|
1149
|
-
handled: true,
|
|
1150
|
-
value: text,
|
|
1151
|
-
selectionStart: selectionStart + 1,
|
|
1152
|
-
selectionEnd: selectionEnd + 1
|
|
1153
|
-
};
|
|
1154
|
-
}
|
|
1155
|
-
const pair = PAIRS[key];
|
|
1156
|
-
if (pair) {
|
|
1157
|
-
if (hasSelection) {
|
|
1158
|
-
const selected = text.slice(selectionStart, selectionEnd);
|
|
1159
|
-
const newText2 = text.slice(0, selectionStart) + pair.open + selected + pair.close + text.slice(selectionEnd);
|
|
1160
|
-
return { handled: true, value: newText2, selectionStart: selectionStart + 1, selectionEnd: selectionEnd + 1 };
|
|
1161
|
-
}
|
|
1162
|
-
if (key === '"' || key === "'" || key === "`") {
|
|
1163
|
-
const charBefore = selectionStart > 0 ? text[selectionStart - 1] : "";
|
|
1164
|
-
if (/\w/.test(charBefore)) return NOT_HANDLED;
|
|
1165
|
-
if (key === "`" && charBefore === "`") return NOT_HANDLED;
|
|
1166
|
-
}
|
|
1167
|
-
const newText = text.slice(0, selectionStart) + pair.open + pair.close + text.slice(selectionEnd);
|
|
1168
|
-
const newCursor = selectionStart + 1;
|
|
1169
|
-
return { handled: true, value: newText, selectionStart: newCursor, selectionEnd: newCursor };
|
|
1170
|
-
}
|
|
1171
|
-
if (key === "*" && !hasSelection) {
|
|
1172
|
-
let lineStart = selectionStart;
|
|
1173
|
-
while (lineStart > 0 && text[lineStart - 1] !== "\n") lineStart--;
|
|
1174
|
-
const textBeforeCursor = text.slice(lineStart, selectionStart);
|
|
1175
|
-
if (/^\s*$/.test(textBeforeCursor)) return NOT_HANDLED;
|
|
1176
|
-
}
|
|
1177
|
-
const mdPair = MARKDOWN_PAIRS[key];
|
|
1178
|
-
if (mdPair) {
|
|
1179
|
-
if (hasSelection) {
|
|
1180
|
-
const selected = text.slice(selectionStart, selectionEnd);
|
|
1181
|
-
const newText2 = text.slice(0, selectionStart) + mdPair.open + selected + mdPair.close + text.slice(selectionEnd);
|
|
1182
|
-
return { handled: true, value: newText2, selectionStart: selectionStart + 1, selectionEnd: selectionEnd + 1 };
|
|
1183
|
-
}
|
|
1184
|
-
if (charAfter === key) {
|
|
1185
|
-
return { handled: true, value: text, selectionStart: selectionStart + 1, selectionEnd: selectionEnd + 1 };
|
|
1186
|
-
}
|
|
1187
|
-
if (key === "*" && selectionStart > 0 && text[selectionStart - 1] === "*") {
|
|
1188
|
-
return NOT_HANDLED;
|
|
1189
|
-
}
|
|
1190
|
-
if (key === "$" && selectionStart > 0 && text[selectionStart - 1] === "$") {
|
|
1191
|
-
return NOT_HANDLED;
|
|
1192
|
-
}
|
|
1193
|
-
const newText = text.slice(0, selectionStart) + mdPair.open + mdPair.close + text.slice(selectionEnd);
|
|
1194
|
-
const newCursor = selectionStart + 1;
|
|
1195
|
-
return { handled: true, value: newText, selectionStart: newCursor, selectionEnd: newCursor };
|
|
1196
|
-
}
|
|
1197
|
-
return NOT_HANDLED;
|
|
1198
|
-
}
|
|
1199
|
-
var ALL_PAIRS = [...Object.values(PAIRS), ...Object.values(MARKDOWN_PAIRS)];
|
|
1200
|
-
function findMatchedPair(text, pos) {
|
|
1201
|
-
if (pos <= 0 || pos >= text.length) return null;
|
|
1202
|
-
const charBefore = text[pos - 1];
|
|
1203
|
-
const charAfter = text[pos];
|
|
1204
|
-
return ALL_PAIRS.find((p) => p.open === charBefore && p.close === charAfter) ?? null;
|
|
1205
|
-
}
|
|
1206
|
-
function deletePair(text, pos) {
|
|
1207
|
-
const newText = text.slice(0, pos - 1) + text.slice(pos + 1);
|
|
1208
|
-
const newCursor = pos - 1;
|
|
1209
|
-
return { handled: true, value: newText, selectionStart: newCursor, selectionEnd: newCursor };
|
|
1210
|
-
}
|
|
1211
|
-
function handleSmartDelete(params) {
|
|
1212
|
-
const { text, selectionStart, selectionEnd } = params;
|
|
1213
|
-
if (selectionStart !== selectionEnd || selectionStart >= text.length) return NOT_HANDLED;
|
|
1214
|
-
if (findMatchedPair(text, selectionStart)) return deletePair(text, selectionStart);
|
|
1215
|
-
return NOT_HANDLED;
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
// src/commands/backspace.ts
|
|
1219
|
-
var PAIRS2 = {
|
|
1220
|
-
"(": { open: "(", close: ")" },
|
|
1221
|
-
"[": { open: "[", close: "]" },
|
|
1222
|
-
"{": { open: "{", close: "}" },
|
|
1223
|
-
'"': { open: '"', close: '"' },
|
|
1224
|
-
"'": { open: "'", close: "'" },
|
|
1225
|
-
"`": { open: "`", close: "`" },
|
|
1226
|
-
"*": { open: "*", close: "*" },
|
|
1227
|
-
"$": { open: "$", close: "$" }
|
|
1228
|
-
};
|
|
1229
|
-
var ALL_PAIRS2 = Object.values(PAIRS2);
|
|
1230
|
-
var SIDE_CONTINUATION_ONLY_RE = /^(?<prefix>(?:\s*> ?|\s*(?:[-*+]|\d+\.)\s+)*)\s*\(>\+\)\s*$/;
|
|
1231
|
-
function findMatchedPair2(text, pos) {
|
|
1232
|
-
if (pos <= 0 || pos >= text.length) return null;
|
|
1233
|
-
const charBefore = text[pos - 1];
|
|
1234
|
-
const charAfter = text[pos];
|
|
1235
|
-
return ALL_PAIRS2.find((p) => p.open === charBefore && p.close === charAfter) ?? null;
|
|
1236
|
-
}
|
|
1237
|
-
function deletePair2(text, pos) {
|
|
1238
|
-
const newText = text.slice(0, pos - 1) + text.slice(pos + 1);
|
|
1239
|
-
const newCursor = pos - 1;
|
|
1240
|
-
return { handled: true, value: newText, selectionStart: newCursor, selectionEnd: newCursor };
|
|
1241
|
-
}
|
|
1242
|
-
function getLineAt2(text, pos) {
|
|
1243
|
-
let lineStart = pos;
|
|
1244
|
-
while (lineStart > 0 && text[lineStart - 1] !== "\n") lineStart--;
|
|
1245
|
-
let lineEnd = pos;
|
|
1246
|
-
while (lineEnd < text.length && text[lineEnd] !== "\n") lineEnd++;
|
|
1247
|
-
return { line: text.slice(lineStart, lineEnd), lineStart, lineEnd };
|
|
1248
|
-
}
|
|
1249
|
-
function getEmptySideContinuationBackspaceResult(text, pos) {
|
|
1250
|
-
const { line, lineStart, lineEnd } = getLineAt2(text, pos);
|
|
1251
|
-
const match = SIDE_CONTINUATION_ONLY_RE.exec(line);
|
|
1252
|
-
if (!match) return NOT_HANDLED;
|
|
1253
|
-
const prefix = match.groups?.prefix ?? "";
|
|
1254
|
-
const expectedCursor = lineStart + prefix.length;
|
|
1255
|
-
if (pos !== expectedCursor) return NOT_HANDLED;
|
|
1256
|
-
const prevLineCursor = Math.max(0, lineStart - 1);
|
|
1257
|
-
if (lineEnd < text.length && text[lineEnd] === "\n") {
|
|
1258
|
-
const newText2 = text.slice(0, lineStart) + text.slice(lineEnd + 1);
|
|
1259
|
-
return {
|
|
1260
|
-
handled: true,
|
|
1261
|
-
value: newText2,
|
|
1262
|
-
selectionStart: prevLineCursor,
|
|
1263
|
-
selectionEnd: prevLineCursor
|
|
1264
|
-
};
|
|
1265
|
-
}
|
|
1266
|
-
const removalStart = lineStart > 0 && text[lineStart - 1] === "\n" ? lineStart - 1 : lineStart;
|
|
1267
|
-
const newText = text.slice(0, removalStart) + text.slice(lineEnd);
|
|
1268
|
-
return {
|
|
1269
|
-
handled: true,
|
|
1270
|
-
value: newText,
|
|
1271
|
-
selectionStart: prevLineCursor,
|
|
1272
|
-
selectionEnd: prevLineCursor
|
|
1273
|
-
};
|
|
1274
|
-
}
|
|
1275
|
-
function handleSmartBackspace(params) {
|
|
1276
|
-
const { text, selectionStart, selectionEnd } = params;
|
|
1277
|
-
if (selectionStart !== selectionEnd || selectionStart === 0) return NOT_HANDLED;
|
|
1278
|
-
const sideContinuationResult = getEmptySideContinuationBackspaceResult(text, selectionStart);
|
|
1279
|
-
if (sideContinuationResult.handled) return sideContinuationResult;
|
|
1280
|
-
if (findMatchedPair2(text, selectionStart)) return deletePair2(text, selectionStart);
|
|
1281
|
-
return NOT_HANDLED;
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
// src/commands/inline-format.ts
|
|
1285
|
-
function toggleBold(params) {
|
|
1286
|
-
return toggleWrap(params, "**");
|
|
1287
|
-
}
|
|
1288
|
-
function toggleItalic(params) {
|
|
1289
|
-
return toggleWrap(params, "*");
|
|
1290
|
-
}
|
|
1291
|
-
function insertLink(params) {
|
|
1292
|
-
const { text, selectionStart, selectionEnd, url } = params;
|
|
1293
|
-
const hasSelection = selectionStart !== selectionEnd;
|
|
1294
|
-
const selected = text.slice(selectionStart, selectionEnd);
|
|
1295
|
-
const linkUrl = url || "url";
|
|
1296
|
-
if (hasSelection) {
|
|
1297
|
-
const insert2 = `[${selected}](${linkUrl})`;
|
|
1298
|
-
const newText2 = text.slice(0, selectionStart) + insert2 + text.slice(selectionEnd);
|
|
1299
|
-
const urlStart = selectionStart + 1 + selected.length + 2;
|
|
1300
|
-
const urlEnd = urlStart + linkUrl.length;
|
|
1301
|
-
return { handled: true, value: newText2, selectionStart: urlStart, selectionEnd: urlEnd };
|
|
1302
|
-
}
|
|
1303
|
-
const insert = `[link text](${linkUrl})`;
|
|
1304
|
-
const newText = text.slice(0, selectionStart) + insert + text.slice(selectionEnd);
|
|
1305
|
-
const textStart = selectionStart + 1;
|
|
1306
|
-
const textEnd = textStart + 9;
|
|
1307
|
-
return { handled: true, value: newText, selectionStart: textStart, selectionEnd: textEnd };
|
|
1308
|
-
}
|
|
1309
|
-
function insertCodeFence(params) {
|
|
1310
|
-
const { text, selectionStart, selectionEnd, language } = params;
|
|
1311
|
-
const lang = language || "";
|
|
1312
|
-
const insert = `\`\`\`${lang}
|
|
1313
|
-
|
|
1314
|
-
\`\`\``;
|
|
1315
|
-
const newText = text.slice(0, selectionStart) + insert + text.slice(selectionEnd);
|
|
1316
|
-
const newCursor = selectionStart + 3 + lang.length + 1;
|
|
1317
|
-
return { handled: true, value: newText, selectionStart: newCursor, selectionEnd: newCursor };
|
|
1318
|
-
}
|
|
1319
|
-
function insertMathBlock(params) {
|
|
1320
|
-
const { text, selectionStart, selectionEnd } = params;
|
|
1321
|
-
const insert = "$$\n\n$$";
|
|
1322
|
-
const newText = text.slice(0, selectionStart) + insert + text.slice(selectionEnd);
|
|
1323
|
-
const newCursor = selectionStart + insert.indexOf("\n") + 1;
|
|
1324
|
-
return { handled: true, value: newText, selectionStart: newCursor, selectionEnd: newCursor };
|
|
1325
|
-
}
|
|
1326
|
-
function insertTable(params) {
|
|
1327
|
-
const { text, selectionStart, selectionEnd } = params;
|
|
1328
|
-
const insert = "| Column 1 | Column 2 | Column 3 |\n| --- | --- | --- |\n| | | |";
|
|
1329
|
-
const newText = text.slice(0, selectionStart) + insert + text.slice(selectionEnd);
|
|
1330
|
-
const newCursor = selectionStart + "| Column 1 | Column 2 | Column 3 |\n| --- | --- | --- |\n| ".length;
|
|
1331
|
-
return { handled: true, value: newText, selectionStart: newCursor, selectionEnd: newCursor };
|
|
1332
|
-
}
|
|
1333
|
-
function insertImage(params) {
|
|
1334
|
-
const { text, selectionStart, selectionEnd } = params;
|
|
1335
|
-
const insert = "";
|
|
1336
|
-
const newText = text.slice(0, selectionStart) + insert + text.slice(selectionEnd);
|
|
1337
|
-
const altStart = selectionStart + 2;
|
|
1338
|
-
const altEnd = altStart + 8;
|
|
1339
|
-
return { handled: true, value: newText, selectionStart: altStart, selectionEnd: altEnd };
|
|
1340
|
-
}
|
|
1341
|
-
function insertSideAnnotation(params) {
|
|
1342
|
-
const { text, selectionStart, selectionEnd, type } = params;
|
|
1343
|
-
const typeSymbol = type || "}";
|
|
1344
|
-
const hasSelection = selectionStart !== selectionEnd;
|
|
1345
|
-
if (!hasSelection) {
|
|
1346
|
-
const insert = ` (>${typeSymbol} \u6807\u6CE8)`;
|
|
1347
|
-
const newText2 = text.slice(0, selectionStart) + insert + text.slice(selectionEnd);
|
|
1348
|
-
const annoStart2 = selectionStart + 4 + typeSymbol.length;
|
|
1349
|
-
const annoEnd2 = annoStart2 + 2;
|
|
1350
|
-
return { handled: true, value: newText2, selectionStart: annoStart2, selectionEnd: annoEnd2 };
|
|
1351
|
-
}
|
|
1352
|
-
const selected = text.slice(selectionStart, selectionEnd);
|
|
1353
|
-
const lines = selected.split("\n");
|
|
1354
|
-
const nonEmptyLines = lines.filter((l) => l.trim());
|
|
1355
|
-
if (nonEmptyLines.length <= 3) {
|
|
1356
|
-
const result = [];
|
|
1357
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1358
|
-
const line = lines[i];
|
|
1359
|
-
if (!line.trim()) {
|
|
1360
|
-
result.push(line);
|
|
1361
|
-
continue;
|
|
1362
|
-
}
|
|
1363
|
-
if (i === 0 || result.every((r) => !r.trim())) {
|
|
1364
|
-
result.push(`${line} (>${typeSymbol} \u6807\u6CE8)`);
|
|
1365
|
-
} else {
|
|
1366
|
-
result.push(`${line} (>+)`);
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
const newSelected = result.join("\n");
|
|
1370
|
-
const newText2 = text.slice(0, selectionStart) + newSelected + text.slice(selectionEnd);
|
|
1371
|
-
const annoOffset2 = newSelected.indexOf("\u6807\u6CE8");
|
|
1372
|
-
const annoStart2 = selectionStart + annoOffset2;
|
|
1373
|
-
const annoEnd2 = annoStart2 + 2;
|
|
1374
|
-
return { handled: true, value: newText2, selectionStart: annoStart2, selectionEnd: annoEnd2 };
|
|
1375
|
-
}
|
|
1376
|
-
const indent = "";
|
|
1377
|
-
const container = `:::side
|
|
1378
|
-
(>${typeSymbol} \u6807\u6CE8)
|
|
1379
|
-
|
|
1380
|
-
${selected}
|
|
1381
|
-
:::`;
|
|
1382
|
-
const newText = text.slice(0, selectionStart) + container + text.slice(selectionEnd);
|
|
1383
|
-
const annoOffset = container.indexOf("\u6807\u6CE8");
|
|
1384
|
-
const annoStart = selectionStart + annoOffset;
|
|
1385
|
-
const annoEnd = annoStart + 2;
|
|
1386
|
-
return { handled: true, value: newText, selectionStart: annoStart, selectionEnd: annoEnd };
|
|
1387
|
-
}
|
|
1388
|
-
function toggleWrap(params, wrapper) {
|
|
1389
|
-
const { text, selectionStart, selectionEnd } = params;
|
|
1390
|
-
const wLen = wrapper.length;
|
|
1391
|
-
const hasSelection = selectionStart !== selectionEnd;
|
|
1392
|
-
if (hasSelection) {
|
|
1393
|
-
const selected = text.slice(selectionStart, selectionEnd);
|
|
1394
|
-
const before = text.slice(Math.max(0, selectionStart - wLen), selectionStart);
|
|
1395
|
-
const after = text.slice(selectionEnd, selectionEnd + wLen);
|
|
1396
|
-
if (before === wrapper && after === wrapper) {
|
|
1397
|
-
const newText3 = text.slice(0, selectionStart - wLen) + selected + text.slice(selectionEnd + wLen);
|
|
1398
|
-
return {
|
|
1399
|
-
handled: true,
|
|
1400
|
-
value: newText3,
|
|
1401
|
-
selectionStart: selectionStart - wLen,
|
|
1402
|
-
selectionEnd: selectionEnd - wLen
|
|
1403
|
-
};
|
|
1404
|
-
}
|
|
1405
|
-
const newText2 = text.slice(0, selectionStart) + wrapper + selected + wrapper + text.slice(selectionEnd);
|
|
1406
|
-
return {
|
|
1407
|
-
handled: true,
|
|
1408
|
-
value: newText2,
|
|
1409
|
-
selectionStart: selectionStart + wLen,
|
|
1410
|
-
selectionEnd: selectionEnd + wLen
|
|
1411
|
-
};
|
|
1412
|
-
}
|
|
1413
|
-
const insert = wrapper + wrapper;
|
|
1414
|
-
const newText = text.slice(0, selectionStart) + insert + text.slice(selectionEnd);
|
|
1415
|
-
const newCursor = selectionStart + wLen;
|
|
1416
|
-
return { handled: true, value: newText, selectionStart: newCursor, selectionEnd: newCursor };
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
// src/commands/slash/registry.ts
|
|
1420
|
-
function createCommandRegistry() {
|
|
1421
|
-
const commands = /* @__PURE__ */ new Map();
|
|
1422
|
-
function register(def) {
|
|
1423
|
-
if (commands.has(def.id)) {
|
|
1424
|
-
console.warn(`[owomark] CommandRegistry: overriding existing command "${def.id}"`);
|
|
1425
|
-
}
|
|
1426
|
-
commands.set(def.id, {
|
|
1427
|
-
def,
|
|
1428
|
-
lowerId: def.id.toLowerCase(),
|
|
1429
|
-
lowerLabel: def.label.toLowerCase(),
|
|
1430
|
-
lowerKeywords: def.keywords?.map((k) => k.toLowerCase()) ?? []
|
|
1431
|
-
});
|
|
1432
|
-
return () => commands.delete(def.id);
|
|
1433
|
-
}
|
|
1434
|
-
function unregister(id) {
|
|
1435
|
-
commands.delete(id);
|
|
1436
|
-
}
|
|
1437
|
-
function get(id) {
|
|
1438
|
-
return commands.get(id)?.def;
|
|
1439
|
-
}
|
|
1440
|
-
function getAll() {
|
|
1441
|
-
return Array.from(commands.values()).map((c) => c.def);
|
|
1442
|
-
}
|
|
1443
|
-
const DEFAULT_PRIORITY = 100;
|
|
1444
|
-
function search(query) {
|
|
1445
|
-
const lowerQuery = query.toLowerCase().trim();
|
|
1446
|
-
const results = [];
|
|
1447
|
-
for (const { def, lowerId, lowerLabel, lowerKeywords } of commands.values()) {
|
|
1448
|
-
if (def.slashMenu === false) continue;
|
|
1449
|
-
if (lowerQuery === "") {
|
|
1450
|
-
results.push(def);
|
|
1451
|
-
continue;
|
|
1452
|
-
}
|
|
1453
|
-
const matched = lowerId.includes(lowerQuery) || lowerLabel.includes(lowerQuery) || lowerKeywords.some((k) => k.includes(lowerQuery));
|
|
1454
|
-
if (matched) {
|
|
1455
|
-
results.push(def);
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
results.sort((a, b) => {
|
|
1459
|
-
const catA = a.category ?? "";
|
|
1460
|
-
const catB = b.category ?? "";
|
|
1461
|
-
if (catA !== catB) return catA.localeCompare(catB);
|
|
1462
|
-
return (a.priority ?? DEFAULT_PRIORITY) - (b.priority ?? DEFAULT_PRIORITY);
|
|
1463
|
-
});
|
|
1464
|
-
return results;
|
|
1465
|
-
}
|
|
1466
|
-
return { register, unregister, get, getAll, search };
|
|
1467
|
-
}
|
|
1468
|
-
|
|
1469
|
-
// src/core/block-context.ts
|
|
1470
|
-
var BLOCK_TO_CONTEXT = {
|
|
1471
|
-
"heading": "heading",
|
|
1472
|
-
"paragraph": "paragraph",
|
|
1473
|
-
"unordered-list": "unordered-list",
|
|
1474
|
-
"ordered-list": "ordered-list",
|
|
1475
|
-
"blockquote": "blockquote",
|
|
1476
|
-
"code-fence": "code-block",
|
|
1477
|
-
"thematic-break": "thematic-break",
|
|
1478
|
-
"directive-container": "directive-container",
|
|
1479
|
-
"math-block": "math-block",
|
|
1480
|
-
"table": "table"
|
|
1481
|
-
};
|
|
1482
|
-
function resolveBlockContextType(block) {
|
|
1483
|
-
if (block.type === "paragraph" && !block.raw.trim()) return "empty";
|
|
1484
|
-
return BLOCK_TO_CONTEXT[block.type];
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
// src/core/core-events.ts
|
|
1488
|
-
function createCoreEventHub() {
|
|
1489
|
-
const changeListeners = /* @__PURE__ */ new Set();
|
|
1490
|
-
const selectionListeners = /* @__PURE__ */ new Set();
|
|
1491
|
-
const compositionListeners = /* @__PURE__ */ new Set();
|
|
1492
|
-
const stateListeners = /* @__PURE__ */ new Set();
|
|
1493
|
-
const slashStateListeners = /* @__PURE__ */ new Set();
|
|
1494
|
-
return {
|
|
1495
|
-
onChange(callback) {
|
|
1496
|
-
changeListeners.add(callback);
|
|
1497
|
-
return () => changeListeners.delete(callback);
|
|
1498
|
-
},
|
|
1499
|
-
onSelectionChange(callback) {
|
|
1500
|
-
selectionListeners.add(callback);
|
|
1501
|
-
return () => selectionListeners.delete(callback);
|
|
1502
|
-
},
|
|
1503
|
-
onCompositionStateChange(callback) {
|
|
1504
|
-
compositionListeners.add(callback);
|
|
1505
|
-
return () => compositionListeners.delete(callback);
|
|
1506
|
-
},
|
|
1507
|
-
onStateChange(callback) {
|
|
1508
|
-
stateListeners.add(callback);
|
|
1509
|
-
return () => stateListeners.delete(callback);
|
|
1510
|
-
},
|
|
1511
|
-
onSlashStateChange(callback) {
|
|
1512
|
-
slashStateListeners.add(callback);
|
|
1513
|
-
return () => slashStateListeners.delete(callback);
|
|
1514
|
-
},
|
|
1515
|
-
emitChange(markdown) {
|
|
1516
|
-
for (const listener of changeListeners) listener(markdown);
|
|
1517
|
-
},
|
|
1518
|
-
emitSelectionChange(selection) {
|
|
1519
|
-
for (const listener of selectionListeners) listener(selection);
|
|
1520
|
-
},
|
|
1521
|
-
emitCompositionChange(active) {
|
|
1522
|
-
for (const listener of compositionListeners) listener(active);
|
|
1523
|
-
},
|
|
1524
|
-
emitStateChange(snapshot) {
|
|
1525
|
-
for (const listener of stateListeners) listener(snapshot);
|
|
1526
|
-
},
|
|
1527
|
-
emitSlashStateChange(state) {
|
|
1528
|
-
for (const listener of slashStateListeners) listener(state);
|
|
1529
|
-
}
|
|
1530
|
-
};
|
|
1531
|
-
}
|
|
1532
|
-
|
|
1533
|
-
// src/core/core-selection.ts
|
|
1534
|
-
function toVirtualSelectionOrNull(doc, selection) {
|
|
1535
|
-
if (!selection) return null;
|
|
1536
|
-
return linearToVirtual(doc, selection);
|
|
1537
|
-
}
|
|
1538
|
-
function getLinearSelectionOrFallback(doc, selection) {
|
|
1539
|
-
if (!selection) return { anchor: 0, focus: 0 };
|
|
1540
|
-
return virtualToLinear(doc, selection);
|
|
1541
|
-
}
|
|
1542
|
-
|
|
1543
|
-
// src/commands/slash/slash-manager.ts
|
|
1544
|
-
var INACTIVE_STATE = {
|
|
1545
|
-
active: false,
|
|
1546
|
-
triggerOffset: 0,
|
|
1547
|
-
query: "",
|
|
1548
|
-
selectedIndex: 0,
|
|
1549
|
-
filteredCommands: [],
|
|
1550
|
-
pending: false
|
|
1551
|
-
};
|
|
1552
|
-
function createSlashManager(registry, deps) {
|
|
1553
|
-
let state = { ...INACTIVE_STATE };
|
|
1554
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
1555
|
-
function emit() {
|
|
1556
|
-
const snapshot = { ...state };
|
|
1557
|
-
for (const listener of listeners) listener(snapshot);
|
|
1558
|
-
}
|
|
1559
|
-
function setState(patch) {
|
|
1560
|
-
state = { ...state, ...patch };
|
|
1561
|
-
emit();
|
|
1562
|
-
}
|
|
1563
|
-
function reset() {
|
|
1564
|
-
state = { ...INACTIVE_STATE };
|
|
1565
|
-
emit();
|
|
1566
|
-
}
|
|
1567
|
-
function mapSelectionThroughReplacement(selection, startOffset, endOffset, replacementLength) {
|
|
1568
|
-
const delta = replacementLength - (endOffset - startOffset);
|
|
1569
|
-
function mapOffset(offset) {
|
|
1570
|
-
if (offset <= startOffset) return offset;
|
|
1571
|
-
if (offset >= endOffset) return offset + delta;
|
|
1572
|
-
return startOffset + replacementLength;
|
|
1573
|
-
}
|
|
1574
|
-
return {
|
|
1575
|
-
anchor: mapOffset(selection.anchor),
|
|
1576
|
-
focus: mapOffset(selection.focus)
|
|
1577
|
-
};
|
|
1578
|
-
}
|
|
1579
|
-
function replaceBlockMarkdownById(blockId, markdown) {
|
|
1580
|
-
const currentDoc = deps.getDocument();
|
|
1581
|
-
const blockIndex = getBlockIndexById(currentDoc, blockId);
|
|
1582
|
-
if (blockIndex === void 0) return false;
|
|
1583
|
-
const block = currentDoc.blocks[blockIndex];
|
|
1584
|
-
if (!block) return false;
|
|
1585
|
-
const currentMarkdown = deps.getMarkdown();
|
|
1586
|
-
const blockStart = getBlockStartOffset(currentDoc, blockIndex);
|
|
1587
|
-
const blockEnd = blockStart + block.raw.length;
|
|
1588
|
-
let removeStart = blockStart;
|
|
1589
|
-
let removeEnd = blockEnd;
|
|
1590
|
-
let replacement = markdown ?? "";
|
|
1591
|
-
if (markdown === null) {
|
|
1592
|
-
const hasPrev = blockIndex > 0;
|
|
1593
|
-
const hasNext = blockIndex < currentDoc.blocks.length - 1;
|
|
1594
|
-
if (hasNext) {
|
|
1595
|
-
removeEnd += 1;
|
|
1596
|
-
} else if (hasPrev) {
|
|
1597
|
-
removeStart = Math.max(0, removeStart - 1);
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
const nextMarkdown = currentMarkdown.slice(0, removeStart) + replacement + currentMarkdown.slice(removeEnd);
|
|
1601
|
-
const nextSelection = mapSelectionThroughReplacement(
|
|
1602
|
-
deps.getLinearSelection(),
|
|
1603
|
-
removeStart,
|
|
1604
|
-
removeEnd,
|
|
1605
|
-
replacement.length
|
|
1606
|
-
);
|
|
1607
|
-
deps.applyUpdate(nextMarkdown, nextSelection);
|
|
1608
|
-
return true;
|
|
1609
|
-
}
|
|
1610
|
-
function buildCommandContext(triggerInfo) {
|
|
1611
|
-
const markdown = deps.getMarkdown();
|
|
1612
|
-
const sel = deps.getLinearSelection();
|
|
1613
|
-
const doc = deps.getDocument();
|
|
1614
|
-
const pos = Math.min(sel.anchor, sel.focus);
|
|
1615
|
-
const { blockIndex } = getBlockAtOffset(doc, pos);
|
|
1616
|
-
const block = doc.blocks[blockIndex];
|
|
1617
|
-
let latestPlaceholderId = null;
|
|
1618
|
-
const ctx = {
|
|
1619
|
-
text: markdown,
|
|
1620
|
-
selectionStart: pos,
|
|
1621
|
-
selectionEnd: Math.max(sel.anchor, sel.focus),
|
|
1622
|
-
applyUpdate: (newText, newSel) => {
|
|
1623
|
-
deps.applyUpdate(newText, { anchor: newSel.anchor, focus: newSel.focus });
|
|
1624
|
-
},
|
|
1625
|
-
document: doc,
|
|
1626
|
-
blockType: block?.type ?? "paragraph",
|
|
1627
|
-
blockDepth: block?.depth ?? 0,
|
|
1628
|
-
triggerInfo,
|
|
1629
|
-
enableSideAnnotation: deps.featureFlags.enableSideAnnotation,
|
|
1630
|
-
enableMath: deps.featureFlags.enableMath,
|
|
1631
|
-
turnIntoBlock(prefix) {
|
|
1632
|
-
const md = deps.getMarkdown();
|
|
1633
|
-
const d = deps.getDocument();
|
|
1634
|
-
const curSel = deps.getLinearSelection();
|
|
1635
|
-
const p = Math.min(curSel.anchor, curSel.focus);
|
|
1636
|
-
const { blockIndex: bi } = getBlockAtOffset(d, p);
|
|
1637
|
-
const blk = d.blocks[bi];
|
|
1638
|
-
if (!blk) return;
|
|
1639
|
-
const blockStart = getBlockStartOffset(d, bi);
|
|
1640
|
-
const oldPrefix = getContentStartOffset(blk.raw, blk.type);
|
|
1641
|
-
const content = blk.raw.slice(oldPrefix);
|
|
1642
|
-
const newRaw = prefix + content;
|
|
1643
|
-
const newText = md.slice(0, blockStart) + newRaw + md.slice(blockStart + blk.raw.length);
|
|
1644
|
-
const newCursor = blockStart + prefix.length + content.length;
|
|
1645
|
-
deps.applyUpdate(newText, { anchor: newCursor, focus: newCursor });
|
|
1646
|
-
},
|
|
1647
|
-
replaceTriggerText(text) {
|
|
1648
|
-
if (!triggerInfo) return;
|
|
1649
|
-
const md = deps.getMarkdown();
|
|
1650
|
-
const before = md.slice(0, triggerInfo.startOffset);
|
|
1651
|
-
const after = md.slice(triggerInfo.endOffset);
|
|
1652
|
-
const newText = before + text + after;
|
|
1653
|
-
const newCursor = triggerInfo.startOffset + text.length;
|
|
1654
|
-
deps.applyUpdate(newText, { anchor: newCursor, focus: newCursor });
|
|
1655
|
-
},
|
|
1656
|
-
insertBlockBelow(blockMarkdown) {
|
|
1657
|
-
const md = deps.getMarkdown();
|
|
1658
|
-
const d = deps.getDocument();
|
|
1659
|
-
const curSel = deps.getLinearSelection();
|
|
1660
|
-
const p = Math.min(curSel.anchor, curSel.focus);
|
|
1661
|
-
const { blockIndex: bi } = getBlockAtOffset(d, p);
|
|
1662
|
-
const blockEnd = getBlockStartOffset(d, bi) + (d.blocks[bi]?.raw.length ?? 0);
|
|
1663
|
-
const newText = md.slice(0, blockEnd) + "\n" + blockMarkdown + md.slice(blockEnd);
|
|
1664
|
-
const newCursor = blockEnd + 1 + blockMarkdown.length;
|
|
1665
|
-
deps.applyUpdate(newText, { anchor: newCursor, focus: newCursor });
|
|
1666
|
-
},
|
|
1667
|
-
insertPlaceholderBlock(blockMarkdown) {
|
|
1668
|
-
const d = deps.getDocument();
|
|
1669
|
-
const curSel = deps.getLinearSelection();
|
|
1670
|
-
const p = Math.min(curSel.anchor, curSel.focus);
|
|
1671
|
-
const { blockIndex: bi } = getBlockAtOffset(d, p);
|
|
1672
|
-
const blk = d.blocks[bi];
|
|
1673
|
-
if (!blk) return null;
|
|
1674
|
-
const blockStart = getBlockStartOffset(d, bi);
|
|
1675
|
-
const md = deps.getMarkdown();
|
|
1676
|
-
const newText = md.slice(0, blockStart) + blockMarkdown + md.slice(blockStart + blk.raw.length);
|
|
1677
|
-
const newCursor = blockStart + blockMarkdown.length;
|
|
1678
|
-
deps.applyUpdate(newText, { anchor: newCursor, focus: newCursor });
|
|
1679
|
-
const updatedDoc = deps.getDocument();
|
|
1680
|
-
const updatedPos = Math.max(0, Math.min(newCursor, deps.getMarkdown().length));
|
|
1681
|
-
const { blockIndex: updatedIndex } = getBlockAtOffset(updatedDoc, updatedPos);
|
|
1682
|
-
latestPlaceholderId = updatedDoc.blocks[updatedIndex]?.id ?? null;
|
|
1683
|
-
return latestPlaceholderId;
|
|
1684
|
-
},
|
|
1685
|
-
replaceBlockById(blockId, blockMarkdown) {
|
|
1686
|
-
return replaceBlockMarkdownById(blockId, blockMarkdown);
|
|
1687
|
-
},
|
|
1688
|
-
removeBlockById(blockId) {
|
|
1689
|
-
return replaceBlockMarkdownById(blockId, null);
|
|
1690
|
-
}
|
|
1691
|
-
};
|
|
1692
|
-
return {
|
|
1693
|
-
ctx,
|
|
1694
|
-
getLatestPlaceholderId: () => latestPlaceholderId
|
|
1695
|
-
};
|
|
1696
|
-
}
|
|
1697
|
-
function getExecutableSlashCommands(query, triggerInfo) {
|
|
1698
|
-
const candidates = registry.search(query);
|
|
1699
|
-
if (candidates.length === 0) return candidates;
|
|
1700
|
-
const { ctx } = buildCommandContext(triggerInfo);
|
|
1701
|
-
return candidates.filter((cmd) => cmd.canExecute ? cmd.canExecute(ctx) : true);
|
|
1702
|
-
}
|
|
1703
|
-
function runCommand(cmd, ctx, payload, onError) {
|
|
1704
|
-
try {
|
|
1705
|
-
const result = cmd.execute(ctx, payload);
|
|
1706
|
-
if (result instanceof Promise) {
|
|
1707
|
-
result.catch((err) => {
|
|
1708
|
-
onError?.();
|
|
1709
|
-
console.error(`[owomark] ${ctx.triggerInfo ? "Slash command" : "Command"} "${cmd.id}" failed:`, err);
|
|
1710
|
-
});
|
|
1711
|
-
}
|
|
1712
|
-
} catch (err) {
|
|
1713
|
-
onError?.();
|
|
1714
|
-
console.error(`[owomark] ${ctx.triggerInfo ? "Slash command" : "Command"} "${cmd.id}" threw:`, err);
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
function isValidTriggerPosition(pos, markdown, doc) {
|
|
1718
|
-
if (doc.blocks.length === 0 && pos === 0) return true;
|
|
1719
|
-
const { blockIndex } = getBlockAtOffset(doc, pos);
|
|
1720
|
-
const block = doc.blocks[blockIndex];
|
|
1721
|
-
if (!block) return false;
|
|
1722
|
-
if (block.type === "code-fence") return false;
|
|
1723
|
-
const blockStart = getBlockStartOffset(doc, blockIndex);
|
|
1724
|
-
const offsetInBlock = pos - blockStart;
|
|
1725
|
-
const raw = block.raw;
|
|
1726
|
-
const contentStart = getContentStartOffset(raw, block.type);
|
|
1727
|
-
if (offsetInBlock === contentStart) {
|
|
1728
|
-
return true;
|
|
1729
|
-
}
|
|
1730
|
-
if (offsetInBlock > 0) {
|
|
1731
|
-
const prevChar = raw[offsetInBlock - 1];
|
|
1732
|
-
if (prevChar === " " || prevChar === " ") {
|
|
1733
|
-
return true;
|
|
1734
|
-
}
|
|
1735
|
-
}
|
|
1736
|
-
return false;
|
|
1737
|
-
}
|
|
1738
|
-
function getContentStartOffset(raw, blockType) {
|
|
1739
|
-
if (blockType === "heading") {
|
|
1740
|
-
const m = raw.match(/^#{1,6}\s/);
|
|
1741
|
-
return m ? m[0].length : 0;
|
|
1742
|
-
}
|
|
1743
|
-
if (blockType === "blockquote") {
|
|
1744
|
-
const m = raw.match(/^(?:>\s?)+/);
|
|
1745
|
-
return m ? m[0].length : 0;
|
|
1746
|
-
}
|
|
1747
|
-
if (blockType === "unordered-list" || blockType === "ordered-list") {
|
|
1748
|
-
const m = raw.match(/^\s*(?:[-*+]|\d+\.)\s/);
|
|
1749
|
-
return m ? m[0].length : 0;
|
|
1750
|
-
}
|
|
1751
|
-
return 0;
|
|
1752
|
-
}
|
|
1753
|
-
function tryActivate(pos, markdown, doc) {
|
|
1754
|
-
if (deps.isComposing()) return false;
|
|
1755
|
-
if (state.active) return false;
|
|
1756
|
-
if (!isValidTriggerPosition(pos, markdown, doc)) return false;
|
|
1757
|
-
const available = getExecutableSlashCommands("", {
|
|
1758
|
-
startOffset: pos,
|
|
1759
|
-
endOffset: pos + 1,
|
|
1760
|
-
query: ""
|
|
1761
|
-
});
|
|
1762
|
-
if (available.length === 0) return false;
|
|
1763
|
-
setState({
|
|
1764
|
-
active: true,
|
|
1765
|
-
triggerOffset: pos,
|
|
1766
|
-
query: "",
|
|
1767
|
-
selectedIndex: 0,
|
|
1768
|
-
filteredCommands: available,
|
|
1769
|
-
pending: false
|
|
1770
|
-
});
|
|
1771
|
-
return true;
|
|
1772
|
-
}
|
|
1773
|
-
function updateQuery(cursorOffset, markdown) {
|
|
1774
|
-
if (!state.active) return;
|
|
1775
|
-
const queryStart = state.triggerOffset + 1;
|
|
1776
|
-
if (cursorOffset < queryStart) {
|
|
1777
|
-
reset();
|
|
1778
|
-
return;
|
|
1779
|
-
}
|
|
1780
|
-
const query = markdown.slice(queryStart, cursorOffset);
|
|
1781
|
-
if (query.includes(" ")) {
|
|
1782
|
-
reset();
|
|
1783
|
-
return;
|
|
1784
|
-
}
|
|
1785
|
-
const filtered = getExecutableSlashCommands(query, {
|
|
1786
|
-
startOffset: state.triggerOffset,
|
|
1787
|
-
endOffset: cursorOffset,
|
|
1788
|
-
query
|
|
1789
|
-
});
|
|
1790
|
-
if (filtered.length === 0) {
|
|
1791
|
-
reset();
|
|
1792
|
-
return;
|
|
1793
|
-
}
|
|
1794
|
-
setState({
|
|
1795
|
-
query,
|
|
1796
|
-
filteredCommands: filtered,
|
|
1797
|
-
selectedIndex: 0
|
|
1798
|
-
});
|
|
1799
|
-
}
|
|
1800
|
-
function handleKeyDown(key, shiftKey, ctrlKey, metaKey, altKey) {
|
|
1801
|
-
if (!state.active) return false;
|
|
1802
|
-
if (key === "Shift" || key === "Control" || key === "Alt" || key === "Meta") {
|
|
1803
|
-
return false;
|
|
1804
|
-
}
|
|
1805
|
-
if (key === "Escape") {
|
|
1806
|
-
reset();
|
|
1807
|
-
return true;
|
|
1808
|
-
}
|
|
1809
|
-
if (key === "ArrowUp") {
|
|
1810
|
-
const newIndex = state.selectedIndex <= 0 ? state.filteredCommands.length - 1 : state.selectedIndex - 1;
|
|
1811
|
-
setState({ selectedIndex: newIndex });
|
|
1812
|
-
return true;
|
|
1813
|
-
}
|
|
1814
|
-
if (key === "ArrowDown") {
|
|
1815
|
-
const newIndex = state.selectedIndex >= state.filteredCommands.length - 1 ? 0 : state.selectedIndex + 1;
|
|
1816
|
-
setState({ selectedIndex: newIndex });
|
|
1817
|
-
return true;
|
|
1818
|
-
}
|
|
1819
|
-
if (key === "Enter") {
|
|
1820
|
-
executeByIndex(state.selectedIndex);
|
|
1821
|
-
return true;
|
|
1822
|
-
}
|
|
1823
|
-
return false;
|
|
1824
|
-
}
|
|
1825
|
-
function handleBackspace(cursorOffset, markdown) {
|
|
1826
|
-
if (!state.active) return false;
|
|
1827
|
-
if (cursorOffset <= state.triggerOffset + 1) {
|
|
1828
|
-
reset();
|
|
1829
|
-
return false;
|
|
1830
|
-
}
|
|
1831
|
-
return false;
|
|
1832
|
-
}
|
|
1833
|
-
function executeByIndex(index) {
|
|
1834
|
-
if (!state.active) return;
|
|
1835
|
-
const cmd = state.filteredCommands[index];
|
|
1836
|
-
if (!cmd) return;
|
|
1837
|
-
const originalTrigger = {
|
|
1838
|
-
startOffset: state.triggerOffset,
|
|
1839
|
-
endOffset: state.triggerOffset + 1 + state.query.length,
|
|
1840
|
-
query: state.query
|
|
1841
|
-
};
|
|
1842
|
-
reset();
|
|
1843
|
-
deps.pushHistory();
|
|
1844
|
-
const markdown = deps.getMarkdown();
|
|
1845
|
-
const before = markdown.slice(0, originalTrigger.startOffset);
|
|
1846
|
-
const after = markdown.slice(originalTrigger.endOffset);
|
|
1847
|
-
const cleanedText = before + after;
|
|
1848
|
-
const cursorAt = originalTrigger.startOffset;
|
|
1849
|
-
deps.applyUpdate(cleanedText, { anchor: cursorAt, focus: cursorAt });
|
|
1850
|
-
const triggerInfo = {
|
|
1851
|
-
startOffset: cursorAt,
|
|
1852
|
-
endOffset: cursorAt,
|
|
1853
|
-
query: originalTrigger.query
|
|
1854
|
-
};
|
|
1855
|
-
const builtCtx = buildCommandContext(triggerInfo);
|
|
1856
|
-
const ctx = builtCtx.ctx;
|
|
1857
|
-
if (cmd.canExecute && !cmd.canExecute(ctx)) {
|
|
1858
|
-
deps.applyUpdate(markdown, { anchor: originalTrigger.endOffset, focus: originalTrigger.endOffset });
|
|
1859
|
-
return;
|
|
1860
|
-
}
|
|
1861
|
-
try {
|
|
1862
|
-
const result = cmd.execute(ctx);
|
|
1863
|
-
if (result instanceof Promise) {
|
|
1864
|
-
setState({
|
|
1865
|
-
active: false,
|
|
1866
|
-
pending: true,
|
|
1867
|
-
triggerOffset: cursorAt,
|
|
1868
|
-
query: originalTrigger.query,
|
|
1869
|
-
selectedIndex: 0,
|
|
1870
|
-
filteredCommands: [cmd]
|
|
1871
|
-
});
|
|
1872
|
-
result.then(() => {
|
|
1873
|
-
reset();
|
|
1874
|
-
}).catch((err) => {
|
|
1875
|
-
const placeholderId = builtCtx.getLatestPlaceholderId();
|
|
1876
|
-
if (placeholderId) {
|
|
1877
|
-
if (!replaceBlockMarkdownById(placeholderId, `/${originalTrigger.query}`)) {
|
|
1878
|
-
deps.applyUpdate(markdown, { anchor: originalTrigger.endOffset, focus: originalTrigger.endOffset });
|
|
1879
|
-
}
|
|
1880
|
-
} else {
|
|
1881
|
-
deps.applyUpdate(markdown, { anchor: originalTrigger.endOffset, focus: originalTrigger.endOffset });
|
|
1882
|
-
}
|
|
1883
|
-
reset();
|
|
1884
|
-
console.error(`[owomark] Slash command "${cmd.id}" failed:`, err);
|
|
1885
|
-
});
|
|
1886
|
-
return;
|
|
1887
|
-
}
|
|
1888
|
-
reset();
|
|
1889
|
-
} catch (err) {
|
|
1890
|
-
deps.applyUpdate(markdown, { anchor: originalTrigger.endOffset, focus: originalTrigger.endOffset });
|
|
1891
|
-
reset();
|
|
1892
|
-
console.error(`[owomark] Slash command "${cmd.id}" threw:`, err);
|
|
1893
|
-
}
|
|
1894
|
-
}
|
|
1895
|
-
function executeById(id, payload) {
|
|
1896
|
-
const cmd = registry.get(id);
|
|
1897
|
-
if (!cmd) return;
|
|
1898
|
-
const { ctx } = buildCommandContext(null);
|
|
1899
|
-
if (cmd.canExecute && !cmd.canExecute(ctx)) {
|
|
1900
|
-
return;
|
|
1901
|
-
}
|
|
1902
|
-
runCommand(cmd, ctx, payload);
|
|
1903
|
-
}
|
|
1904
|
-
function dismiss() {
|
|
1905
|
-
if (state.active) reset();
|
|
1906
|
-
}
|
|
1907
|
-
return {
|
|
1908
|
-
registry,
|
|
1909
|
-
getSlashState: () => ({ ...state, filteredCommands: [...state.filteredCommands] }),
|
|
1910
|
-
tryActivate,
|
|
1911
|
-
updateQuery,
|
|
1912
|
-
handleKeyDown,
|
|
1913
|
-
handleBackspace,
|
|
1914
|
-
executeByIndex,
|
|
1915
|
-
executeById,
|
|
1916
|
-
dismiss,
|
|
1917
|
-
onSlashStateChange(listener) {
|
|
1918
|
-
listeners.add(listener);
|
|
1919
|
-
return () => listeners.delete(listener);
|
|
1920
|
-
}
|
|
1921
|
-
};
|
|
1922
|
-
}
|
|
1923
|
-
|
|
1924
|
-
// src/commands/slash/builtin-commands.ts
|
|
1925
|
-
function makeFormatCommand(id, label, shortcut, executeFn) {
|
|
1926
|
-
return {
|
|
1927
|
-
id,
|
|
1928
|
-
label,
|
|
1929
|
-
category: "format",
|
|
1930
|
-
slashMenu: false,
|
|
1931
|
-
shortcut,
|
|
1932
|
-
execute(ctx) {
|
|
1933
|
-
const result = executeFn({
|
|
1934
|
-
text: ctx.text,
|
|
1935
|
-
selectionStart: ctx.selectionStart,
|
|
1936
|
-
selectionEnd: ctx.selectionEnd
|
|
1937
|
-
});
|
|
1938
|
-
if (result.handled) {
|
|
1939
|
-
ctx.applyUpdate(result.value, { anchor: result.selectionStart, focus: result.selectionEnd });
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1942
|
-
};
|
|
1943
|
-
}
|
|
1944
|
-
var bold = makeFormatCommand("bold", "Bold", "Ctrl+B", toggleBold);
|
|
1945
|
-
var italic = makeFormatCommand("italic", "Italic", "Ctrl+I", toggleItalic);
|
|
1946
|
-
var link = {
|
|
1947
|
-
id: "link",
|
|
1948
|
-
label: "Link",
|
|
1949
|
-
category: "insert",
|
|
1950
|
-
slashMenu: true,
|
|
1951
|
-
priority: 50,
|
|
1952
|
-
shortcut: "Ctrl+K",
|
|
1953
|
-
execute(ctx, payload) {
|
|
1954
|
-
const url = typeof payload === "string" ? payload : void 0;
|
|
1955
|
-
const result = insertLink({
|
|
1956
|
-
text: ctx.text,
|
|
1957
|
-
selectionStart: ctx.selectionStart,
|
|
1958
|
-
selectionEnd: ctx.selectionEnd,
|
|
1959
|
-
url
|
|
1960
|
-
});
|
|
1961
|
-
if (result.handled) {
|
|
1962
|
-
ctx.applyUpdate(result.value, { anchor: result.selectionStart, focus: result.selectionEnd });
|
|
1963
|
-
}
|
|
1964
|
-
}
|
|
1965
|
-
};
|
|
1966
|
-
var codeFence = {
|
|
1967
|
-
id: "code-fence",
|
|
1968
|
-
label: "Code Block",
|
|
1969
|
-
category: "insert",
|
|
1970
|
-
slashMenu: true,
|
|
1971
|
-
priority: 60,
|
|
1972
|
-
keywords: ["code", "fence", "block"],
|
|
1973
|
-
execute(ctx, payload) {
|
|
1974
|
-
const lang = typeof payload === "string" ? payload : void 0;
|
|
1975
|
-
const result = insertCodeFence({
|
|
1976
|
-
text: ctx.text,
|
|
1977
|
-
selectionStart: ctx.selectionStart,
|
|
1978
|
-
selectionEnd: ctx.selectionEnd,
|
|
1979
|
-
language: lang
|
|
1980
|
-
});
|
|
1981
|
-
if (result.handled) {
|
|
1982
|
-
ctx.applyUpdate(result.value, { anchor: result.selectionStart, focus: result.selectionEnd });
|
|
1983
|
-
}
|
|
1984
|
-
}
|
|
1985
|
-
};
|
|
1986
|
-
var mathBlock = {
|
|
1987
|
-
id: "math",
|
|
1988
|
-
label: "Math Block",
|
|
1989
|
-
category: "insert",
|
|
1990
|
-
slashMenu: true,
|
|
1991
|
-
priority: 65,
|
|
1992
|
-
keywords: ["math", "equation", "formula", "latex", "katex"],
|
|
1993
|
-
canExecute(ctx) {
|
|
1994
|
-
return ctx.enableMath;
|
|
1995
|
-
},
|
|
1996
|
-
execute(ctx) {
|
|
1997
|
-
const result = insertMathBlock({
|
|
1998
|
-
text: ctx.text,
|
|
1999
|
-
selectionStart: ctx.selectionStart,
|
|
2000
|
-
selectionEnd: ctx.selectionEnd
|
|
2001
|
-
});
|
|
2002
|
-
if (result.handled) {
|
|
2003
|
-
ctx.applyUpdate(result.value, { anchor: result.selectionStart, focus: result.selectionEnd });
|
|
2004
|
-
}
|
|
2005
|
-
}
|
|
2006
|
-
};
|
|
2007
|
-
var sideAnnotation = {
|
|
2008
|
-
id: "side-annotation",
|
|
2009
|
-
label: "Side Annotation",
|
|
2010
|
-
category: "insert",
|
|
2011
|
-
slashMenu: true,
|
|
2012
|
-
priority: 70,
|
|
2013
|
-
shortcut: "Ctrl+Shift+]",
|
|
2014
|
-
keywords: ["side", "annotation", "brace", "margin", "note"],
|
|
2015
|
-
canExecute(ctx) {
|
|
2016
|
-
return ctx.enableSideAnnotation;
|
|
2017
|
-
},
|
|
2018
|
-
execute(ctx, payload) {
|
|
2019
|
-
const type = typeof payload === "string" ? payload : void 0;
|
|
2020
|
-
const result = insertSideAnnotation({
|
|
2021
|
-
text: ctx.text,
|
|
2022
|
-
selectionStart: ctx.selectionStart,
|
|
2023
|
-
selectionEnd: ctx.selectionEnd,
|
|
2024
|
-
type
|
|
2025
|
-
});
|
|
2026
|
-
if (result.handled) {
|
|
2027
|
-
ctx.applyUpdate(result.value, { anchor: result.selectionStart, focus: result.selectionEnd });
|
|
2028
|
-
}
|
|
2029
|
-
}
|
|
2030
|
-
};
|
|
2031
|
-
var heading1 = {
|
|
2032
|
-
id: "heading-1",
|
|
2033
|
-
label: "Heading 1",
|
|
2034
|
-
category: "basic",
|
|
2035
|
-
slashMenu: true,
|
|
2036
|
-
priority: 10,
|
|
2037
|
-
keywords: ["h1", "title"],
|
|
2038
|
-
execute(ctx) {
|
|
2039
|
-
ctx.turnIntoBlock("# ");
|
|
2040
|
-
}
|
|
2041
|
-
};
|
|
2042
|
-
var heading2 = {
|
|
2043
|
-
id: "heading-2",
|
|
2044
|
-
label: "Heading 2",
|
|
2045
|
-
category: "basic",
|
|
2046
|
-
slashMenu: true,
|
|
2047
|
-
priority: 11,
|
|
2048
|
-
keywords: ["h2"],
|
|
2049
|
-
execute(ctx) {
|
|
2050
|
-
ctx.turnIntoBlock("## ");
|
|
2051
|
-
}
|
|
2052
|
-
};
|
|
2053
|
-
var heading3 = {
|
|
2054
|
-
id: "heading-3",
|
|
2055
|
-
label: "Heading 3",
|
|
2056
|
-
category: "basic",
|
|
2057
|
-
slashMenu: true,
|
|
2058
|
-
priority: 12,
|
|
2059
|
-
keywords: ["h3"],
|
|
2060
|
-
execute(ctx) {
|
|
2061
|
-
ctx.turnIntoBlock("### ");
|
|
2062
|
-
}
|
|
2063
|
-
};
|
|
2064
|
-
var blockquote = {
|
|
2065
|
-
id: "blockquote",
|
|
2066
|
-
label: "Blockquote",
|
|
2067
|
-
category: "basic",
|
|
2068
|
-
slashMenu: true,
|
|
2069
|
-
priority: 20,
|
|
2070
|
-
keywords: ["quote"],
|
|
2071
|
-
execute(ctx) {
|
|
2072
|
-
ctx.turnIntoBlock("> ");
|
|
2073
|
-
}
|
|
2074
|
-
};
|
|
2075
|
-
var unorderedList = {
|
|
2076
|
-
id: "unordered-list",
|
|
2077
|
-
label: "Bullet List",
|
|
2078
|
-
category: "basic",
|
|
2079
|
-
slashMenu: true,
|
|
2080
|
-
priority: 30,
|
|
2081
|
-
keywords: ["bullet", "list", "ul"],
|
|
2082
|
-
execute(ctx) {
|
|
2083
|
-
ctx.turnIntoBlock("- ");
|
|
2084
|
-
}
|
|
2085
|
-
};
|
|
2086
|
-
var orderedList = {
|
|
2087
|
-
id: "ordered-list",
|
|
2088
|
-
label: "Numbered List",
|
|
2089
|
-
category: "basic",
|
|
2090
|
-
slashMenu: true,
|
|
2091
|
-
priority: 31,
|
|
2092
|
-
keywords: ["number", "list", "ol"],
|
|
2093
|
-
execute(ctx) {
|
|
2094
|
-
ctx.turnIntoBlock("1. ");
|
|
2095
|
-
}
|
|
2096
|
-
};
|
|
2097
|
-
var thematicBreak = {
|
|
2098
|
-
id: "thematic-break",
|
|
2099
|
-
label: "Divider",
|
|
2100
|
-
category: "basic",
|
|
2101
|
-
slashMenu: true,
|
|
2102
|
-
priority: 40,
|
|
2103
|
-
keywords: ["divider", "hr", "line", "separator"],
|
|
2104
|
-
execute(ctx) {
|
|
2105
|
-
ctx.insertBlockBelow("---");
|
|
2106
|
-
}
|
|
2107
|
-
};
|
|
2108
|
-
var table = {
|
|
2109
|
-
id: "insert-table",
|
|
2110
|
-
label: "Table",
|
|
2111
|
-
category: "insert",
|
|
2112
|
-
slashMenu: true,
|
|
2113
|
-
priority: 75,
|
|
2114
|
-
keywords: ["table", "grid"],
|
|
2115
|
-
execute(ctx) {
|
|
2116
|
-
const result = insertTable({
|
|
2117
|
-
text: ctx.text,
|
|
2118
|
-
selectionStart: ctx.selectionStart,
|
|
2119
|
-
selectionEnd: ctx.selectionEnd
|
|
2120
|
-
});
|
|
2121
|
-
if (result.handled) {
|
|
2122
|
-
ctx.applyUpdate(result.value, { anchor: result.selectionStart, focus: result.selectionEnd });
|
|
2123
|
-
}
|
|
2124
|
-
}
|
|
2125
|
-
};
|
|
2126
|
-
var image = {
|
|
2127
|
-
id: "insert-image",
|
|
2128
|
-
label: "Image",
|
|
2129
|
-
category: "insert",
|
|
2130
|
-
slashMenu: true,
|
|
2131
|
-
priority: 80,
|
|
2132
|
-
keywords: ["image", "picture", "photo", "img"],
|
|
2133
|
-
execute(ctx) {
|
|
2134
|
-
const result = insertImage({
|
|
2135
|
-
text: ctx.text,
|
|
2136
|
-
selectionStart: ctx.selectionStart,
|
|
2137
|
-
selectionEnd: ctx.selectionEnd
|
|
2138
|
-
});
|
|
2139
|
-
if (result.handled) {
|
|
2140
|
-
ctx.applyUpdate(result.value, { anchor: result.selectionStart, focus: result.selectionEnd });
|
|
2141
|
-
}
|
|
2142
|
-
}
|
|
2143
|
-
};
|
|
2144
|
-
var builtinCommands = [
|
|
2145
|
-
bold,
|
|
2146
|
-
italic,
|
|
2147
|
-
link,
|
|
2148
|
-
codeFence,
|
|
2149
|
-
mathBlock,
|
|
2150
|
-
sideAnnotation,
|
|
2151
|
-
heading1,
|
|
2152
|
-
heading2,
|
|
2153
|
-
heading3,
|
|
2154
|
-
blockquote,
|
|
2155
|
-
unorderedList,
|
|
2156
|
-
orderedList,
|
|
2157
|
-
thematicBreak,
|
|
2158
|
-
table,
|
|
2159
|
-
image
|
|
2160
|
-
];
|
|
2161
|
-
|
|
2162
|
-
// src/core/editor-core.ts
|
|
2163
|
-
var HISTORY_LIMIT = 100;
|
|
2164
|
-
var HISTORY_SNAPSHOT_DEBOUNCE_MS = 300;
|
|
2165
|
-
var TAB_INDENT_SPACES = 2;
|
|
2166
|
-
var TAB_INDENT = " ".repeat(TAB_INDENT_SPACES);
|
|
2167
|
-
function createOwoMarkCore(options) {
|
|
2168
|
-
const genBlockId = createBlockIdGenerator();
|
|
2169
|
-
const events = createCoreEventHub();
|
|
2170
|
-
let doc = { blocks: [] };
|
|
2171
|
-
let composition = { active: false, range: null };
|
|
2172
|
-
let lastSelection = null;
|
|
2173
|
-
let indentMode = options?.indentMode ?? "auto";
|
|
2174
|
-
const featureFlags = {
|
|
2175
|
-
enableSideAnnotation: options?.enableSideAnnotation !== false,
|
|
2176
|
-
enableMath: options?.enableMath !== false
|
|
2177
|
-
};
|
|
2178
|
-
const undoStack = [];
|
|
2179
|
-
const redoStack = [];
|
|
2180
|
-
let lastHistoryMarkdown = "";
|
|
2181
|
-
let documentChangeCallback = null;
|
|
2182
|
-
const blockContextListeners = /* @__PURE__ */ new Set();
|
|
2183
|
-
let lastBlockContext = null;
|
|
2184
|
-
let historyTimer = null;
|
|
2185
|
-
const commandRegistry = createCommandRegistry();
|
|
2186
|
-
let slashMgr = null;
|
|
2187
|
-
function getMarkdown() {
|
|
2188
|
-
return serializeDocument(doc);
|
|
2189
|
-
}
|
|
2190
|
-
function getLinearSelection() {
|
|
2191
|
-
return getLinearSelectionOrFallback(doc, lastSelection);
|
|
2192
|
-
}
|
|
2193
|
-
function buildSnapshot() {
|
|
2194
|
-
return {
|
|
2195
|
-
markdown: getMarkdown(),
|
|
2196
|
-
document: doc,
|
|
2197
|
-
selection: {
|
|
2198
|
-
linear: lastSelection ? virtualToLinear(doc, lastSelection) : null,
|
|
2199
|
-
virtual: lastSelection
|
|
2200
|
-
},
|
|
2201
|
-
composition
|
|
2202
|
-
};
|
|
2203
|
-
}
|
|
2204
|
-
function computeBlockContext() {
|
|
2205
|
-
const sel = getLinearSelection();
|
|
2206
|
-
const pos = Math.min(sel.anchor, sel.focus);
|
|
2207
|
-
const markdown = getMarkdown();
|
|
2208
|
-
if (doc.blocks.length === 0 || doc.blocks.length === 1 && doc.blocks[0].raw === "") {
|
|
2209
|
-
return {
|
|
2210
|
-
type: "empty",
|
|
2211
|
-
depth: 0,
|
|
2212
|
-
blockIndex: 0,
|
|
2213
|
-
blockId: doc.blocks[0]?.id ?? ""
|
|
2214
|
-
};
|
|
2215
|
-
}
|
|
2216
|
-
const { blockIndex } = getBlockAtOffset(doc, Math.min(pos, Math.max(0, markdown.length - 1)));
|
|
2217
|
-
const block = doc.blocks[blockIndex];
|
|
2218
|
-
if (!block) {
|
|
2219
|
-
return { type: "empty", depth: 0, blockIndex: 0, blockId: "" };
|
|
2220
|
-
}
|
|
2221
|
-
const contextType = resolveBlockContextType(block);
|
|
2222
|
-
const result = {
|
|
2223
|
-
type: contextType,
|
|
2224
|
-
depth: block.depth,
|
|
2225
|
-
blockIndex,
|
|
2226
|
-
blockId: block.id
|
|
2227
|
-
};
|
|
2228
|
-
if (block.type === "heading") {
|
|
2229
|
-
result.headingLevel = block.headingLevel;
|
|
2230
|
-
}
|
|
2231
|
-
if (block.type === "code-fence") {
|
|
2232
|
-
result.language = block.language;
|
|
2233
|
-
}
|
|
2234
|
-
return result;
|
|
2235
|
-
}
|
|
2236
|
-
function emitBlockContextChange() {
|
|
2237
|
-
const ctx = computeBlockContext();
|
|
2238
|
-
if (lastBlockContext && lastBlockContext.type === ctx.type && lastBlockContext.blockIndex === ctx.blockIndex && lastBlockContext.blockId === ctx.blockId && lastBlockContext.depth === ctx.depth) {
|
|
2239
|
-
return;
|
|
2240
|
-
}
|
|
2241
|
-
lastBlockContext = ctx;
|
|
2242
|
-
for (const listener of blockContextListeners) listener(ctx);
|
|
2243
|
-
}
|
|
2244
|
-
function resetExternalDocumentState(markdown) {
|
|
2245
|
-
undoStack.length = 0;
|
|
2246
|
-
redoStack.length = 0;
|
|
2247
|
-
if (historyTimer) {
|
|
2248
|
-
clearTimeout(historyTimer);
|
|
2249
|
-
historyTimer = null;
|
|
2250
|
-
}
|
|
2251
|
-
composition = { active: false, range: null };
|
|
2252
|
-
lastSelection = null;
|
|
2253
|
-
lastHistoryMarkdown = markdown;
|
|
2254
|
-
}
|
|
2255
|
-
function syncEmptyAttrOnDoc() {
|
|
2256
|
-
return doc.blocks.length === 0 || doc.blocks.length === 1 && doc.blocks[0].raw === "";
|
|
2257
|
-
}
|
|
2258
|
-
function setMarkdown(markdown) {
|
|
2259
|
-
doc = parseMarkdownToDocument(markdown, genBlockId);
|
|
2260
|
-
resetExternalDocumentState(markdown);
|
|
2261
|
-
const endOffset = Math.max(0, markdown.length);
|
|
2262
|
-
const endLinear = { anchor: endOffset, focus: endOffset };
|
|
2263
|
-
lastSelection = linearToVirtual(doc, endLinear);
|
|
2264
|
-
documentChangeCallback?.(doc, lastSelection);
|
|
2265
|
-
lastHistoryMarkdown = markdown;
|
|
2266
|
-
events.emitChange(markdown);
|
|
2267
|
-
events.emitStateChange(buildSnapshot());
|
|
2268
|
-
}
|
|
2269
|
-
function pushHistory() {
|
|
2270
|
-
const md = getMarkdown();
|
|
2271
|
-
if (md === lastHistoryMarkdown) return;
|
|
2272
|
-
undoStack.push({ markdown: lastHistoryMarkdown, selection: getLinearSelection() });
|
|
2273
|
-
if (undoStack.length > HISTORY_LIMIT) undoStack.shift();
|
|
2274
|
-
redoStack.length = 0;
|
|
2275
|
-
lastHistoryMarkdown = md;
|
|
2276
|
-
}
|
|
2277
|
-
function scheduleHistorySnapshot() {
|
|
2278
|
-
if (historyTimer) clearTimeout(historyTimer);
|
|
2279
|
-
historyTimer = setTimeout(pushHistory, HISTORY_SNAPSHOT_DEBOUNCE_MS);
|
|
2280
|
-
}
|
|
2281
|
-
function applyUpdate(newMarkdown, newSelection) {
|
|
2282
|
-
const oldDoc = doc;
|
|
2283
|
-
const newDoc = parseMarkdownToDocument(newMarkdown, genBlockId);
|
|
2284
|
-
doc = { blocks: reconcileBlocks(oldDoc.blocks, newDoc.blocks) };
|
|
2285
|
-
const vsel = linearToVirtual(doc, newSelection);
|
|
2286
|
-
lastSelection = vsel;
|
|
2287
|
-
documentChangeCallback?.(doc, vsel);
|
|
2288
|
-
scheduleHistorySnapshot();
|
|
2289
|
-
events.emitChange(newMarkdown);
|
|
2290
|
-
events.emitStateChange(buildSnapshot());
|
|
2291
|
-
emitBlockContextChange();
|
|
2292
|
-
}
|
|
2293
|
-
slashMgr = createSlashManager(commandRegistry, {
|
|
2294
|
-
getMarkdown,
|
|
2295
|
-
getDocument: () => doc,
|
|
2296
|
-
getLinearSelection,
|
|
2297
|
-
isComposing: () => composition.active,
|
|
2298
|
-
applyUpdate: (newText, sel) => applyUpdate(newText, sel),
|
|
2299
|
-
pushHistory,
|
|
2300
|
-
featureFlags
|
|
2301
|
-
});
|
|
2302
|
-
slashMgr.onSlashStateChange((state) => {
|
|
2303
|
-
events.emitSlashStateChange(state);
|
|
2304
|
-
});
|
|
2305
|
-
for (const cmd of builtinCommands) {
|
|
2306
|
-
commandRegistry.register(cmd);
|
|
2307
|
-
}
|
|
2308
|
-
function replaceMarkdown(markdown, selection) {
|
|
2309
|
-
pushHistory();
|
|
2310
|
-
applyUpdate(markdown, selection);
|
|
2311
|
-
}
|
|
2312
|
-
function performUndo() {
|
|
2313
|
-
pushHistory();
|
|
2314
|
-
const entry = undoStack.pop();
|
|
2315
|
-
if (!entry) return;
|
|
2316
|
-
redoStack.push({ markdown: getMarkdown(), selection: getLinearSelection() });
|
|
2317
|
-
doc = parseMarkdownToDocument(entry.markdown, genBlockId);
|
|
2318
|
-
lastHistoryMarkdown = entry.markdown;
|
|
2319
|
-
const vsel = linearToVirtual(doc, entry.selection);
|
|
2320
|
-
lastSelection = vsel;
|
|
2321
|
-
documentChangeCallback?.(doc, vsel);
|
|
2322
|
-
events.emitChange(entry.markdown);
|
|
2323
|
-
events.emitStateChange(buildSnapshot());
|
|
2324
|
-
}
|
|
2325
|
-
function performRedo() {
|
|
2326
|
-
const entry = redoStack.pop();
|
|
2327
|
-
if (!entry) return;
|
|
2328
|
-
undoStack.push({ markdown: getMarkdown(), selection: getLinearSelection() });
|
|
2329
|
-
doc = parseMarkdownToDocument(entry.markdown, genBlockId);
|
|
2330
|
-
lastHistoryMarkdown = entry.markdown;
|
|
2331
|
-
const vsel = linearToVirtual(doc, entry.selection);
|
|
2332
|
-
lastSelection = vsel;
|
|
2333
|
-
documentChangeCallback?.(doc, vsel);
|
|
2334
|
-
events.emitChange(entry.markdown);
|
|
2335
|
-
events.emitStateChange(buildSnapshot());
|
|
2336
|
-
}
|
|
2337
|
-
function dispatch(command, payload) {
|
|
2338
|
-
if (command === "undo") {
|
|
2339
|
-
performUndo();
|
|
2340
|
-
return;
|
|
2341
|
-
}
|
|
2342
|
-
if (command === "redo") {
|
|
2343
|
-
performRedo();
|
|
2344
|
-
return;
|
|
2345
|
-
}
|
|
2346
|
-
const idMap = {
|
|
2347
|
-
toggleBold: "bold",
|
|
2348
|
-
toggleItalic: "italic",
|
|
2349
|
-
insertLink: "link",
|
|
2350
|
-
insertCodeFence: "code-fence",
|
|
2351
|
-
insertMath: "math",
|
|
2352
|
-
insertSideAnnotation: "side-annotation"
|
|
2353
|
-
};
|
|
2354
|
-
const id = idMap[command] ?? command;
|
|
2355
|
-
slashMgr.executeById(id, payload);
|
|
2356
|
-
}
|
|
2357
|
-
function applyBeforeInput(intent, selection) {
|
|
2358
|
-
if (composition.active) {
|
|
2359
|
-
return { action: "allow-native", snapshot: buildSnapshot() };
|
|
2360
|
-
}
|
|
2361
|
-
const sel = selection ?? getLinearSelection();
|
|
2362
|
-
const markdown = getMarkdown();
|
|
2363
|
-
const pos = Math.min(sel.anchor, sel.focus);
|
|
2364
|
-
const end = Math.max(sel.anchor, sel.focus);
|
|
2365
|
-
const { inputType, data } = intent;
|
|
2366
|
-
if (inputType === "insertParagraph") {
|
|
2367
|
-
let text = markdown;
|
|
2368
|
-
let cursor = pos;
|
|
2369
|
-
if (pos !== end) {
|
|
2370
|
-
text = text.slice(0, pos) + text.slice(end);
|
|
2371
|
-
}
|
|
2372
|
-
const result = handleMarkdownEnter({ text, selectionStart: cursor, selectionEnd: cursor, enableSideAnnotation: featureFlags.enableSideAnnotation });
|
|
2373
|
-
if (result.handled) {
|
|
2374
|
-
applyUpdate(result.value, { anchor: result.selectionStart, focus: result.selectionEnd });
|
|
2375
|
-
} else {
|
|
2376
|
-
const newText = text.slice(0, cursor) + "\n" + text.slice(cursor);
|
|
2377
|
-
const newCursor = cursor + 1;
|
|
2378
|
-
applyUpdate(newText, { anchor: newCursor, focus: newCursor });
|
|
2379
|
-
}
|
|
2380
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2381
|
-
}
|
|
2382
|
-
if (inputType === "insertLineBreak") {
|
|
2383
|
-
const newText = markdown.slice(0, pos) + "\n" + markdown.slice(end);
|
|
2384
|
-
const newCursor = pos + 1;
|
|
2385
|
-
applyUpdate(newText, { anchor: newCursor, focus: newCursor });
|
|
2386
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2387
|
-
}
|
|
2388
|
-
if (inputType === "deleteContentBackward") {
|
|
2389
|
-
if (slashMgr.getSlashState().active) {
|
|
2390
|
-
slashMgr.handleBackspace(pos, markdown);
|
|
2391
|
-
if (slashMgr.getSlashState().active) {
|
|
2392
|
-
if (pos !== end) {
|
|
2393
|
-
const newText = markdown.slice(0, pos) + markdown.slice(end);
|
|
2394
|
-
applyUpdate(newText, { anchor: pos, focus: pos });
|
|
2395
|
-
slashMgr.updateQuery(pos, newText);
|
|
2396
|
-
} else if (pos > 0) {
|
|
2397
|
-
const newText = markdown.slice(0, pos - 1) + markdown.slice(pos);
|
|
2398
|
-
applyUpdate(newText, { anchor: pos - 1, focus: pos - 1 });
|
|
2399
|
-
slashMgr.updateQuery(pos - 1, newText);
|
|
2400
|
-
}
|
|
2401
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2402
|
-
}
|
|
2403
|
-
}
|
|
2404
|
-
if (pos !== end) {
|
|
2405
|
-
const newText = markdown.slice(0, pos) + markdown.slice(end);
|
|
2406
|
-
applyUpdate(newText, { anchor: pos, focus: pos });
|
|
2407
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2408
|
-
}
|
|
2409
|
-
const result = handleSmartBackspace({ text: markdown, selectionStart: pos, selectionEnd: pos });
|
|
2410
|
-
if (result.handled) {
|
|
2411
|
-
applyUpdate(result.value, { anchor: result.selectionStart, focus: result.selectionEnd });
|
|
2412
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2413
|
-
}
|
|
2414
|
-
if (pos > 0) {
|
|
2415
|
-
const newText = markdown.slice(0, pos - 1) + markdown.slice(pos);
|
|
2416
|
-
applyUpdate(newText, { anchor: pos - 1, focus: pos - 1 });
|
|
2417
|
-
}
|
|
2418
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2419
|
-
}
|
|
2420
|
-
if (inputType === "deleteContentForward") {
|
|
2421
|
-
if (pos !== end) {
|
|
2422
|
-
const newText = markdown.slice(0, pos) + markdown.slice(end);
|
|
2423
|
-
applyUpdate(newText, { anchor: pos, focus: pos });
|
|
2424
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2425
|
-
}
|
|
2426
|
-
const delResult = handleSmartDelete({ text: markdown, selectionStart: pos, selectionEnd: pos });
|
|
2427
|
-
if (delResult.handled) {
|
|
2428
|
-
applyUpdate(delResult.value, { anchor: delResult.selectionStart, focus: delResult.selectionEnd });
|
|
2429
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2430
|
-
}
|
|
2431
|
-
if (pos < markdown.length) {
|
|
2432
|
-
const newText = markdown.slice(0, pos) + markdown.slice(pos + 1);
|
|
2433
|
-
applyUpdate(newText, { anchor: pos, focus: pos });
|
|
2434
|
-
}
|
|
2435
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2436
|
-
}
|
|
2437
|
-
if (inputType === "deleteWordBackward") {
|
|
2438
|
-
if (pos !== end) {
|
|
2439
|
-
const newText = markdown.slice(0, pos) + markdown.slice(end);
|
|
2440
|
-
applyUpdate(newText, { anchor: pos, focus: pos });
|
|
2441
|
-
} else {
|
|
2442
|
-
const { newText, newPos } = deleteWordBackward(markdown, pos);
|
|
2443
|
-
applyUpdate(newText, { anchor: newPos, focus: newPos });
|
|
2444
|
-
}
|
|
2445
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2446
|
-
}
|
|
2447
|
-
if (inputType === "deleteWordForward") {
|
|
2448
|
-
if (pos !== end) {
|
|
2449
|
-
const newText = markdown.slice(0, pos) + markdown.slice(end);
|
|
2450
|
-
applyUpdate(newText, { anchor: pos, focus: pos });
|
|
2451
|
-
} else {
|
|
2452
|
-
const { newText, newPos } = deleteWordForward(markdown, pos);
|
|
2453
|
-
applyUpdate(newText, { anchor: newPos, focus: newPos });
|
|
2454
|
-
}
|
|
2455
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2456
|
-
}
|
|
2457
|
-
if (inputType === "deleteSoftLineBackward" || inputType === "deleteHardLineBackward") {
|
|
2458
|
-
if (pos !== end) {
|
|
2459
|
-
const newText = markdown.slice(0, pos) + markdown.slice(end);
|
|
2460
|
-
applyUpdate(newText, { anchor: pos, focus: pos });
|
|
2461
|
-
} else {
|
|
2462
|
-
const { newText, newPos } = deleteToLineStart(markdown, pos);
|
|
2463
|
-
applyUpdate(newText, { anchor: newPos, focus: newPos });
|
|
2464
|
-
}
|
|
2465
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2466
|
-
}
|
|
2467
|
-
if (inputType === "deleteSoftLineForward" || inputType === "deleteHardLineForward") {
|
|
2468
|
-
if (pos !== end) {
|
|
2469
|
-
const newText = markdown.slice(0, pos) + markdown.slice(end);
|
|
2470
|
-
applyUpdate(newText, { anchor: pos, focus: pos });
|
|
2471
|
-
} else {
|
|
2472
|
-
const { newText, newPos } = deleteToLineEnd(markdown, pos);
|
|
2473
|
-
applyUpdate(newText, { anchor: newPos, focus: newPos });
|
|
2474
|
-
}
|
|
2475
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2476
|
-
}
|
|
2477
|
-
if (inputType === "insertText" && data) {
|
|
2478
|
-
if (data.length === 1) {
|
|
2479
|
-
const result = handleCharInput({
|
|
2480
|
-
key: data,
|
|
2481
|
-
text: markdown,
|
|
2482
|
-
selectionStart: pos,
|
|
2483
|
-
selectionEnd: end,
|
|
2484
|
-
isComposing: false
|
|
2485
|
-
});
|
|
2486
|
-
if (result.handled) {
|
|
2487
|
-
applyUpdate(result.value, { anchor: result.selectionStart, focus: result.selectionEnd });
|
|
2488
|
-
if (data === "/") {
|
|
2489
|
-
slashMgr.tryActivate(pos, result.value, doc);
|
|
2490
|
-
} else if (slashMgr.getSlashState().active) {
|
|
2491
|
-
slashMgr.updateQuery(result.selectionStart, result.value);
|
|
2492
|
-
}
|
|
2493
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2494
|
-
}
|
|
2495
|
-
}
|
|
2496
|
-
if (data === "/" && slashMgr.tryActivate(pos, markdown, doc)) {
|
|
2497
|
-
const newText2 = markdown.slice(0, pos) + "/" + markdown.slice(end);
|
|
2498
|
-
const newCursor2 = pos + 1;
|
|
2499
|
-
applyUpdate(newText2, { anchor: newCursor2, focus: newCursor2 });
|
|
2500
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2501
|
-
}
|
|
2502
|
-
if (slashMgr.getSlashState().active) {
|
|
2503
|
-
const newText2 = markdown.slice(0, pos) + data + markdown.slice(end);
|
|
2504
|
-
const newCursor2 = pos + data.length;
|
|
2505
|
-
applyUpdate(newText2, { anchor: newCursor2, focus: newCursor2 });
|
|
2506
|
-
slashMgr.updateQuery(newCursor2, newText2);
|
|
2507
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2508
|
-
}
|
|
2509
|
-
const newText = markdown.slice(0, pos) + data + markdown.slice(end);
|
|
2510
|
-
const newCursor = pos + data.length;
|
|
2511
|
-
applyUpdate(newText, { anchor: newCursor, focus: newCursor });
|
|
2512
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2513
|
-
}
|
|
2514
|
-
if (inputType === "insertFromPaste") {
|
|
2515
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2516
|
-
}
|
|
2517
|
-
if (inputType === "historyUndo") {
|
|
2518
|
-
performUndo();
|
|
2519
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2520
|
-
}
|
|
2521
|
-
if (inputType === "historyRedo") {
|
|
2522
|
-
performRedo();
|
|
2523
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2524
|
-
}
|
|
2525
|
-
if (inputType === "formatBold") {
|
|
2526
|
-
dispatch("toggleBold");
|
|
2527
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2528
|
-
}
|
|
2529
|
-
if (inputType === "formatItalic") {
|
|
2530
|
-
dispatch("toggleItalic");
|
|
2531
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2532
|
-
}
|
|
2533
|
-
return { action: "allow-native", snapshot: buildSnapshot() };
|
|
2534
|
-
}
|
|
2535
|
-
function applyKeyDown(intent, selection) {
|
|
2536
|
-
if (composition.active) {
|
|
2537
|
-
return { action: "allow-native", snapshot: buildSnapshot() };
|
|
2538
|
-
}
|
|
2539
|
-
if (slashMgr.getSlashState().active) {
|
|
2540
|
-
const consumed = slashMgr.handleKeyDown(
|
|
2541
|
-
intent.key,
|
|
2542
|
-
intent.shiftKey,
|
|
2543
|
-
intent.ctrlKey,
|
|
2544
|
-
intent.metaKey,
|
|
2545
|
-
intent.altKey
|
|
2546
|
-
);
|
|
2547
|
-
if (consumed) {
|
|
2548
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2549
|
-
}
|
|
2550
|
-
}
|
|
2551
|
-
const sel = selection ?? getLinearSelection();
|
|
2552
|
-
const markdown = getMarkdown();
|
|
2553
|
-
const pos = Math.min(sel.anchor, sel.focus);
|
|
2554
|
-
const end = Math.max(sel.anchor, sel.focus);
|
|
2555
|
-
const { key, ctrlKey, metaKey, altKey, shiftKey } = intent;
|
|
2556
|
-
if (key === "Tab") {
|
|
2557
|
-
const result = applyMarkdownIndent({
|
|
2558
|
-
text: markdown,
|
|
2559
|
-
selectionStart: pos,
|
|
2560
|
-
selectionEnd: end,
|
|
2561
|
-
shiftKey,
|
|
2562
|
-
mode: indentMode
|
|
2563
|
-
});
|
|
2564
|
-
if (result.handled) {
|
|
2565
|
-
applyUpdate(result.value, { anchor: result.selectionStart, focus: result.selectionEnd });
|
|
2566
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2567
|
-
}
|
|
2568
|
-
if (shiftKey) {
|
|
2569
|
-
const lineStart = markdown.lastIndexOf("\n", pos - 1) + 1;
|
|
2570
|
-
const textFromLineStart = markdown.slice(lineStart);
|
|
2571
|
-
const leadingSpaces = textFromLineStart.match(/^ */)?.[0].length ?? 0;
|
|
2572
|
-
const removable = Math.min(TAB_INDENT_SPACES, leadingSpaces);
|
|
2573
|
-
if (removable > 0) {
|
|
2574
|
-
const newText = markdown.slice(0, lineStart) + textFromLineStart.slice(removable);
|
|
2575
|
-
const cursorShift = Math.min(removable, pos - lineStart);
|
|
2576
|
-
const newPos = pos - cursorShift;
|
|
2577
|
-
applyUpdate(newText, { anchor: newPos, focus: newPos });
|
|
2578
|
-
}
|
|
2579
|
-
} else {
|
|
2580
|
-
const newText = markdown.slice(0, pos) + TAB_INDENT + markdown.slice(end);
|
|
2581
|
-
const newPos = pos + TAB_INDENT.length;
|
|
2582
|
-
applyUpdate(newText, { anchor: newPos, focus: newPos });
|
|
2583
|
-
}
|
|
2584
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2585
|
-
}
|
|
2586
|
-
if ((ctrlKey || metaKey) && key === "b") {
|
|
2587
|
-
const result = toggleBold({ text: markdown, selectionStart: pos, selectionEnd: end });
|
|
2588
|
-
if (result.handled) {
|
|
2589
|
-
applyUpdate(result.value, { anchor: result.selectionStart, focus: result.selectionEnd });
|
|
2590
|
-
}
|
|
2591
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2592
|
-
}
|
|
2593
|
-
if ((ctrlKey || metaKey) && key === "i") {
|
|
2594
|
-
const result = toggleItalic({ text: markdown, selectionStart: pos, selectionEnd: end });
|
|
2595
|
-
if (result.handled) {
|
|
2596
|
-
applyUpdate(result.value, { anchor: result.selectionStart, focus: result.selectionEnd });
|
|
2597
|
-
}
|
|
2598
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2599
|
-
}
|
|
2600
|
-
if ((ctrlKey || metaKey) && key === "k") {
|
|
2601
|
-
const result = insertLink({ text: markdown, selectionStart: pos, selectionEnd: end });
|
|
2602
|
-
if (result.handled) {
|
|
2603
|
-
applyUpdate(result.value, { anchor: result.selectionStart, focus: result.selectionEnd });
|
|
2604
|
-
}
|
|
2605
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2606
|
-
}
|
|
2607
|
-
if ((ctrlKey || metaKey) && key === "z" && !shiftKey) {
|
|
2608
|
-
performUndo();
|
|
2609
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2610
|
-
}
|
|
2611
|
-
if ((ctrlKey || metaKey) && (key === "z" && shiftKey || key === "y")) {
|
|
2612
|
-
performRedo();
|
|
2613
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2614
|
-
}
|
|
2615
|
-
if (key.length === 1 && !ctrlKey && !metaKey && !altKey) {
|
|
2616
|
-
const result = handleCharInput({
|
|
2617
|
-
key,
|
|
2618
|
-
text: markdown,
|
|
2619
|
-
selectionStart: pos,
|
|
2620
|
-
selectionEnd: end,
|
|
2621
|
-
isComposing: false
|
|
2622
|
-
});
|
|
2623
|
-
if (result.handled) {
|
|
2624
|
-
applyUpdate(result.value, { anchor: result.selectionStart, focus: result.selectionEnd });
|
|
2625
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2626
|
-
}
|
|
2627
|
-
}
|
|
2628
|
-
return { action: "allow-native", snapshot: buildSnapshot() };
|
|
2629
|
-
}
|
|
2630
|
-
function applyPasteIntent(intent, selection) {
|
|
2631
|
-
const cleaned = normalizeMarkdownPaste(intent.text);
|
|
2632
|
-
const markdown = getMarkdown();
|
|
2633
|
-
const sel = selection ?? getLinearSelection();
|
|
2634
|
-
const pos = Math.min(sel.anchor, sel.focus);
|
|
2635
|
-
const end = Math.max(sel.anchor, sel.focus);
|
|
2636
|
-
const blockquotePrefix = getBlockquotePrefixAtPos(markdown, pos);
|
|
2637
|
-
const toInsert = applyBlockquotePrefix(cleaned, blockquotePrefix);
|
|
2638
|
-
const newText = markdown.slice(0, pos) + toInsert + markdown.slice(end);
|
|
2639
|
-
const newCursor = pos + toInsert.length;
|
|
2640
|
-
pushHistory();
|
|
2641
|
-
applyUpdate(newText, { anchor: newCursor, focus: newCursor });
|
|
2642
|
-
return { action: "handled", snapshot: buildSnapshot() };
|
|
2643
|
-
}
|
|
2644
|
-
function handleCompositionStart(selection) {
|
|
2645
|
-
const vsel = selection ? toVirtualSelectionOrNull(doc, selection) : lastSelection;
|
|
2646
|
-
pushHistory();
|
|
2647
|
-
composition = { active: true, range: vsel };
|
|
2648
|
-
events.emitCompositionChange(true);
|
|
2649
|
-
}
|
|
2650
|
-
function handleCompositionUpdate() {
|
|
2651
|
-
composition = { ...composition, active: true };
|
|
2652
|
-
}
|
|
2653
|
-
function handleCompositionEnd(_selection, _fallbackText) {
|
|
2654
|
-
composition = { active: false, range: null };
|
|
2655
|
-
events.emitCompositionChange(false);
|
|
2656
|
-
return { action: "composition-sync", snapshot: buildSnapshot() };
|
|
2657
|
-
}
|
|
2658
|
-
function syncText(text, selection) {
|
|
2659
|
-
const oldMarkdown = getMarkdown();
|
|
2660
|
-
if (text === oldMarkdown) return;
|
|
2661
|
-
const oldDoc = doc;
|
|
2662
|
-
const newDoc = parseMarkdownToDocument(text, genBlockId);
|
|
2663
|
-
doc = { blocks: reconcileBlocks(oldDoc.blocks, newDoc.blocks) };
|
|
2664
|
-
const linearSel = selection ?? getLinearSelection();
|
|
2665
|
-
const vsel = linearToVirtual(doc, linearSel);
|
|
2666
|
-
lastSelection = vsel;
|
|
2667
|
-
documentChangeCallback?.(doc, vsel);
|
|
2668
|
-
scheduleHistorySnapshot();
|
|
2669
|
-
events.emitChange(text);
|
|
2670
|
-
events.emitStateChange(buildSnapshot());
|
|
2671
|
-
}
|
|
2672
|
-
function updateSelection(selection) {
|
|
2673
|
-
lastSelection = linearToVirtual(doc, selection);
|
|
2674
|
-
events.emitSelectionChange(selection);
|
|
2675
|
-
emitBlockContextChange();
|
|
2676
|
-
}
|
|
2677
|
-
if (options?.initialMarkdown) {
|
|
2678
|
-
doc = parseMarkdownToDocument(options.initialMarkdown, genBlockId);
|
|
2679
|
-
const endOffset = Math.max(0, options.initialMarkdown.length);
|
|
2680
|
-
const endLinear = { anchor: endOffset, focus: endOffset };
|
|
2681
|
-
lastSelection = linearToVirtual(doc, endLinear);
|
|
2682
|
-
lastHistoryMarkdown = options.initialMarkdown;
|
|
2683
|
-
}
|
|
2684
|
-
return {
|
|
2685
|
-
getSnapshot: buildSnapshot,
|
|
2686
|
-
getMarkdown,
|
|
2687
|
-
getSelection: () => lastSelection ? virtualToLinear(doc, lastSelection) : null,
|
|
2688
|
-
getDocument: () => doc,
|
|
2689
|
-
getVirtualSelection: () => lastSelection,
|
|
2690
|
-
getComposition: () => composition,
|
|
2691
|
-
setMarkdown,
|
|
2692
|
-
replaceMarkdown,
|
|
2693
|
-
dispatch,
|
|
2694
|
-
setIndentMode(mode) {
|
|
2695
|
-
indentMode = mode;
|
|
2696
|
-
},
|
|
2697
|
-
setEnableSideAnnotation(enabled) {
|
|
2698
|
-
featureFlags.enableSideAnnotation = enabled;
|
|
2699
|
-
},
|
|
2700
|
-
setEnableMath(enabled) {
|
|
2701
|
-
featureFlags.enableMath = enabled;
|
|
2702
|
-
if (slashMgr.getSlashState().active) {
|
|
2703
|
-
const selection = getLinearSelection();
|
|
2704
|
-
slashMgr.updateQuery(Math.max(selection.anchor, selection.focus), getMarkdown());
|
|
2705
|
-
}
|
|
2706
|
-
},
|
|
2707
|
-
syncText,
|
|
2708
|
-
applyBeforeInput,
|
|
2709
|
-
applyKeyDown,
|
|
2710
|
-
applyPaste: applyPasteIntent,
|
|
2711
|
-
handleCompositionStart,
|
|
2712
|
-
handleCompositionUpdate,
|
|
2713
|
-
handleCompositionEnd,
|
|
2714
|
-
updateSelection,
|
|
2715
|
-
// Block context
|
|
2716
|
-
getBlockContext: computeBlockContext,
|
|
2717
|
-
insertBlock(type, options2) {
|
|
2718
|
-
const commandMap = {
|
|
2719
|
-
"code-fence": "code-fence",
|
|
2720
|
-
"blockquote": "blockquote",
|
|
2721
|
-
"math": "math",
|
|
2722
|
-
"table": "insert-table",
|
|
2723
|
-
"image": "insert-image",
|
|
2724
|
-
"thematic-break": "thematic-break"
|
|
2725
|
-
};
|
|
2726
|
-
dispatch(commandMap[type], options2?.language);
|
|
2727
|
-
},
|
|
2728
|
-
// Slash command system
|
|
2729
|
-
getCommandRegistry: () => commandRegistry,
|
|
2730
|
-
getSlashState: () => slashMgr.getSlashState(),
|
|
2731
|
-
registerCommand: (def) => commandRegistry.register(def),
|
|
2732
|
-
executeSlashCommand: (index) => slashMgr.executeByIndex(index),
|
|
2733
|
-
dismissSlashMenu: () => slashMgr.dismiss(),
|
|
2734
|
-
onChange: events.onChange,
|
|
2735
|
-
onSelectionChange: events.onSelectionChange,
|
|
2736
|
-
onCompositionStateChange: events.onCompositionStateChange,
|
|
2737
|
-
onStateChange: events.onStateChange,
|
|
2738
|
-
onSlashStateChange: (callback) => slashMgr.onSlashStateChange(callback),
|
|
2739
|
-
onBlockContextChange(callback) {
|
|
2740
|
-
blockContextListeners.add(callback);
|
|
2741
|
-
return () => blockContextListeners.delete(callback);
|
|
2742
|
-
},
|
|
2743
|
-
onDocumentChange(callback) {
|
|
2744
|
-
documentChangeCallback = callback;
|
|
2745
|
-
return () => {
|
|
2746
|
-
if (documentChangeCallback === callback) {
|
|
2747
|
-
documentChangeCallback = null;
|
|
2748
|
-
}
|
|
2749
|
-
};
|
|
2750
|
-
},
|
|
2751
|
-
destroy() {
|
|
2752
|
-
if (historyTimer) {
|
|
2753
|
-
clearTimeout(historyTimer);
|
|
2754
|
-
historyTimer = null;
|
|
2755
|
-
}
|
|
2756
|
-
}
|
|
2757
|
-
};
|
|
2758
|
-
}
|
|
2759
|
-
|
|
2760
|
-
// src/model/selection.ts
|
|
2761
|
-
function domRangeToOffset(root, range) {
|
|
2762
|
-
const anchor = nodeOffsetToLinear(root, range.startContainer, range.startOffset);
|
|
2763
|
-
const focus = nodeOffsetToLinear(root, range.endContainer, range.endOffset);
|
|
2764
|
-
if (anchor === null || focus === null) return null;
|
|
2765
|
-
return { anchor, focus };
|
|
2766
|
-
}
|
|
2767
|
-
function offsetToDomRange(root, selection) {
|
|
2768
|
-
const doc = root.ownerDocument;
|
|
2769
|
-
const range = doc.createRange();
|
|
2770
|
-
const textLen = getModelTextLength(root);
|
|
2771
|
-
const clampedAnchor = Math.max(0, Math.min(selection.anchor, textLen));
|
|
2772
|
-
const clampedFocus = Math.max(0, Math.min(selection.focus, textLen));
|
|
2773
|
-
const start = linearToNodeOffset(root, Math.min(clampedAnchor, clampedFocus));
|
|
2774
|
-
const end = linearToNodeOffset(root, Math.max(clampedAnchor, clampedFocus));
|
|
2775
|
-
if (!start || !end) return null;
|
|
2776
|
-
range.setStart(start.node, start.offset);
|
|
2777
|
-
range.setEnd(end.node, end.offset);
|
|
2778
|
-
return range;
|
|
2779
|
-
}
|
|
2780
|
-
function restoreSelection(root, selection) {
|
|
2781
|
-
const textLen = getModelTextLength(root);
|
|
2782
|
-
const clamped = {
|
|
2783
|
-
anchor: Math.max(0, Math.min(selection.anchor, textLen)),
|
|
2784
|
-
focus: Math.max(0, Math.min(selection.focus, textLen))
|
|
2785
|
-
};
|
|
2786
|
-
const range = offsetToDomRange(root, clamped);
|
|
2787
|
-
if (!range) return;
|
|
2788
|
-
const sel = root.ownerDocument.getSelection();
|
|
2789
|
-
if (!sel) return;
|
|
2790
|
-
sel.removeAllRanges();
|
|
2791
|
-
if (clamped.anchor <= clamped.focus) {
|
|
2792
|
-
sel.addRange(range);
|
|
2793
|
-
} else {
|
|
2794
|
-
sel.addRange(range);
|
|
2795
|
-
sel.collapseToEnd();
|
|
2796
|
-
const focusPos = linearToNodeOffset(root, clamped.focus);
|
|
2797
|
-
if (focusPos) {
|
|
2798
|
-
sel.extend(focusPos.node, focusPos.offset);
|
|
2799
|
-
}
|
|
2800
|
-
}
|
|
2801
|
-
}
|
|
2802
|
-
function readSelection(root) {
|
|
2803
|
-
const sel = root.ownerDocument.getSelection();
|
|
2804
|
-
if (!sel || sel.rangeCount === 0) return null;
|
|
2805
|
-
if (!root.contains(sel.anchorNode)) return null;
|
|
2806
|
-
const anchor = nodeOffsetToLinear(root, sel.anchorNode, sel.anchorOffset);
|
|
2807
|
-
const focus = nodeOffsetToLinear(root, sel.focusNode, sel.focusOffset);
|
|
2808
|
-
if (anchor === null || focus === null) return null;
|
|
2809
|
-
return { anchor, focus };
|
|
2810
|
-
}
|
|
2811
|
-
var blockCacheMap = /* @__PURE__ */ new WeakMap();
|
|
2812
|
-
function getBlocks(root) {
|
|
2813
|
-
let cached = blockCacheMap.get(root);
|
|
2814
|
-
if (cached) return cached;
|
|
2815
|
-
cached = Array.from(root.querySelectorAll("[data-owo-block]"));
|
|
2816
|
-
blockCacheMap.set(root, cached);
|
|
2817
|
-
return cached;
|
|
2818
|
-
}
|
|
2819
|
-
function invalidateBlockCache(root) {
|
|
2820
|
-
blockCacheMap.delete(root);
|
|
2821
|
-
}
|
|
2822
|
-
function getOwnerBlock(root, node) {
|
|
2823
|
-
if (node === root) return null;
|
|
2824
|
-
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
2825
|
-
const el = node;
|
|
2826
|
-
if (el.hasAttribute("data-owo-block")) return el;
|
|
2827
|
-
return el.closest?.("[data-owo-block]");
|
|
2828
|
-
}
|
|
2829
|
-
return node.parentElement?.closest("[data-owo-block]") ?? null;
|
|
2830
|
-
}
|
|
2831
|
-
function getModelTextLength(root) {
|
|
2832
|
-
const blocks = getBlocks(root);
|
|
2833
|
-
if (blocks.length === 0) return 0;
|
|
2834
|
-
let len = 0;
|
|
2835
|
-
for (let i = 0; i < blocks.length; i++) {
|
|
2836
|
-
if (i > 0) len += 1;
|
|
2837
|
-
len += blocks[i].textContent?.length ?? 0;
|
|
2838
|
-
}
|
|
2839
|
-
return len;
|
|
2840
|
-
}
|
|
2841
|
-
function nodeOffsetToLinear(root, node, offset) {
|
|
2842
|
-
if (!root.contains(node)) return null;
|
|
2843
|
-
if (node === root) {
|
|
2844
|
-
const targetChild2 = root.childNodes[offset];
|
|
2845
|
-
if (targetChild2) {
|
|
2846
|
-
return countTextBefore(root, targetChild2);
|
|
2847
|
-
}
|
|
2848
|
-
return getModelTextLength(root);
|
|
2849
|
-
}
|
|
2850
|
-
if (node.nodeType === Node.TEXT_NODE) {
|
|
2851
|
-
return countTextBefore(root, node) + offset;
|
|
2852
|
-
}
|
|
2853
|
-
const el = node;
|
|
2854
|
-
const targetChild = el.childNodes[offset];
|
|
2855
|
-
if (targetChild) {
|
|
2856
|
-
return countTextBefore(root, targetChild);
|
|
2857
|
-
}
|
|
2858
|
-
const block = getOwnerBlock(root, node);
|
|
2859
|
-
if (block) {
|
|
2860
|
-
return countBlockStartOffset(root, block) + (node === block ? block.textContent?.length ?? 0 : countTextWithinBlockBefore(block, node) + (node.textContent?.length ?? 0));
|
|
2861
|
-
}
|
|
2862
|
-
return getModelTextLength(root);
|
|
2863
|
-
}
|
|
2864
|
-
function countTextBefore(root, target) {
|
|
2865
|
-
const blocks = getBlocks(root);
|
|
2866
|
-
const targetBlock = getOwnerBlock(root, target);
|
|
2867
|
-
let count = 0;
|
|
2868
|
-
for (let i = 0; i < blocks.length; i++) {
|
|
2869
|
-
const block = blocks[i];
|
|
2870
|
-
if (i > 0) count += 1;
|
|
2871
|
-
if (block === targetBlock) {
|
|
2872
|
-
count += countTextWithinBlockBefore(block, target);
|
|
2873
|
-
return count;
|
|
2874
|
-
}
|
|
2875
|
-
const pos = block.compareDocumentPosition(target);
|
|
2876
|
-
if ((pos & Node.DOCUMENT_POSITION_FOLLOWING) !== 0) {
|
|
2877
|
-
count += block.textContent?.length ?? 0;
|
|
2878
|
-
} else {
|
|
2879
|
-
return count;
|
|
2880
|
-
}
|
|
2881
|
-
}
|
|
2882
|
-
return count;
|
|
2883
|
-
}
|
|
2884
|
-
function countBlockStartOffset(root, targetBlock) {
|
|
2885
|
-
const blocks = getBlocks(root);
|
|
2886
|
-
let offset = 0;
|
|
2887
|
-
for (const block of blocks) {
|
|
2888
|
-
if (block === targetBlock) return offset;
|
|
2889
|
-
offset += (block.textContent?.length ?? 0) + 1;
|
|
2890
|
-
}
|
|
2891
|
-
return offset;
|
|
2892
|
-
}
|
|
2893
|
-
function countTextWithinBlockBefore(block, target) {
|
|
2894
|
-
const walker = block.ownerDocument.createTreeWalker(block, NodeFilter.SHOW_TEXT);
|
|
2895
|
-
let count = 0;
|
|
2896
|
-
while (walker.nextNode()) {
|
|
2897
|
-
const textNode = walker.currentNode;
|
|
2898
|
-
if (textNode === target) break;
|
|
2899
|
-
const pos = textNode.compareDocumentPosition(target);
|
|
2900
|
-
const isBeforeTarget = (pos & Node.DOCUMENT_POSITION_FOLLOWING) !== 0;
|
|
2901
|
-
const isInsideTarget = (pos & Node.DOCUMENT_POSITION_CONTAINED_BY) !== 0;
|
|
2902
|
-
if (isBeforeTarget && !isInsideTarget) {
|
|
2903
|
-
count += textNode.textContent?.length ?? 0;
|
|
2904
|
-
} else {
|
|
2905
|
-
break;
|
|
2906
|
-
}
|
|
2907
|
-
}
|
|
2908
|
-
return count;
|
|
2909
|
-
}
|
|
2910
|
-
function linearToNodeOffset(root, offset) {
|
|
2911
|
-
const blocks = getBlocks(root);
|
|
2912
|
-
if (blocks.length === 0) {
|
|
2913
|
-
return { node: root, offset: 0 };
|
|
2914
|
-
}
|
|
2915
|
-
let pos = 0;
|
|
2916
|
-
for (let i = 0; i < blocks.length; i++) {
|
|
2917
|
-
const block = blocks[i];
|
|
2918
|
-
if (i > 0) {
|
|
2919
|
-
if (pos === offset) {
|
|
2920
|
-
return firstPositionInBlock(block);
|
|
2921
|
-
}
|
|
2922
|
-
pos += 1;
|
|
2923
|
-
}
|
|
2924
|
-
const blockTextLen = block.textContent?.length ?? 0;
|
|
2925
|
-
if (pos + blockTextLen >= offset) {
|
|
2926
|
-
const inBlockOffset = offset - pos;
|
|
2927
|
-
if (inBlockOffset === 0) {
|
|
2928
|
-
return firstPositionInBlock(block);
|
|
2929
|
-
}
|
|
2930
|
-
const walker = block.ownerDocument.createTreeWalker(block, NodeFilter.SHOW_TEXT);
|
|
2931
|
-
let blockPos = 0;
|
|
2932
|
-
while (walker.nextNode()) {
|
|
2933
|
-
const textNode = walker.currentNode;
|
|
2934
|
-
const len = textNode.textContent?.length ?? 0;
|
|
2935
|
-
if (blockPos + len >= inBlockOffset) {
|
|
2936
|
-
return { node: textNode, offset: inBlockOffset - blockPos };
|
|
2937
|
-
}
|
|
2938
|
-
blockPos += len;
|
|
2939
|
-
}
|
|
2940
|
-
return lastPositionInBlock(block);
|
|
2941
|
-
}
|
|
2942
|
-
pos += blockTextLen;
|
|
2943
|
-
}
|
|
2944
|
-
return lastPositionInBlock(blocks[blocks.length - 1]);
|
|
2945
|
-
}
|
|
2946
|
-
function firstPositionInBlock(block) {
|
|
2947
|
-
const walker = block.ownerDocument.createTreeWalker(block, NodeFilter.SHOW_TEXT);
|
|
2948
|
-
if (walker.nextNode()) {
|
|
2949
|
-
return { node: walker.currentNode, offset: 0 };
|
|
2950
|
-
}
|
|
2951
|
-
return { node: block, offset: 0 };
|
|
2952
|
-
}
|
|
2953
|
-
function lastPositionInBlock(block) {
|
|
2954
|
-
const walker = block.ownerDocument.createTreeWalker(block, NodeFilter.SHOW_TEXT);
|
|
2955
|
-
let lastText = null;
|
|
2956
|
-
while (walker.nextNode()) lastText = walker.currentNode;
|
|
2957
|
-
if (lastText) {
|
|
2958
|
-
return { node: lastText, offset: lastText.textContent?.length ?? 0 };
|
|
2959
|
-
}
|
|
2960
|
-
return { node: block, offset: block.childNodes.length };
|
|
2961
|
-
}
|
|
2962
|
-
|
|
2963
|
-
// src/model/dirty-range.ts
|
|
2964
|
-
function expandDirtyRange(current, blockIndex, totalBlocks) {
|
|
2965
|
-
if (totalBlocks <= 0) return current;
|
|
2966
|
-
const clamped = Math.max(0, Math.min(blockIndex, totalBlocks - 1));
|
|
2967
|
-
if (!current) {
|
|
2968
|
-
return { startBlock: clamped, endBlock: clamped };
|
|
2969
|
-
}
|
|
2970
|
-
return {
|
|
2971
|
-
startBlock: Math.min(current.startBlock, clamped),
|
|
2972
|
-
endBlock: Math.max(current.endBlock, clamped)
|
|
2973
|
-
};
|
|
2974
|
-
}
|
|
2975
|
-
function expandWithContext(range, totalBlocks, context = 1) {
|
|
2976
|
-
if (totalBlocks <= 0) return null;
|
|
2977
|
-
return {
|
|
2978
|
-
startBlock: Math.max(0, range.startBlock - context),
|
|
2979
|
-
endBlock: Math.min(totalBlocks - 1, range.endBlock + context)
|
|
2980
|
-
};
|
|
2981
|
-
}
|
|
2982
|
-
|
|
2983
|
-
// src/render/blockquote-bars.ts
|
|
2984
|
-
var BQ_BAR_WIDTH = 3;
|
|
2985
|
-
var BQ_GAP = 12;
|
|
2986
|
-
var BQ_STEP = BQ_BAR_WIDTH + BQ_GAP;
|
|
2987
|
-
function buildBlockquoteBarsBoxShadow(depth) {
|
|
2988
|
-
if (depth <= 1) return null;
|
|
2989
|
-
const shadows = [];
|
|
2990
|
-
for (let i = depth - 1; i >= 1; i--) {
|
|
2991
|
-
const barEnd = i * BQ_STEP;
|
|
2992
|
-
shadows.push(`inset ${barEnd}px 0 0 0 var(--owo-editor-bg, #ffffff)`);
|
|
2993
|
-
shadows.push(`inset ${barEnd + BQ_BAR_WIDTH}px 0 0 0 var(--owo-editor-border-strong, #cbd5e1)`);
|
|
2994
|
-
}
|
|
2995
|
-
return shadows.join(", ");
|
|
2996
|
-
}
|
|
2997
|
-
|
|
2998
|
-
// src/render/block-render.ts
|
|
2999
|
-
function applyBlockquoteBarsStyle(el, depth) {
|
|
3000
|
-
const shadow = buildBlockquoteBarsBoxShadow(depth);
|
|
3001
|
-
if (!shadow) return;
|
|
3002
|
-
el.style.paddingLeft = `${depth * BQ_STEP}px`;
|
|
3003
|
-
el.style.boxShadow = shadow;
|
|
3004
|
-
}
|
|
3005
|
-
var TOKEN_TO_CLASS = {
|
|
3006
|
-
"text": "",
|
|
3007
|
-
"strong-marker": "owo-syntax-marker",
|
|
3008
|
-
"strong": "owo-syntax-strong",
|
|
3009
|
-
"emphasis-marker": "owo-syntax-marker",
|
|
3010
|
-
"emphasis": "owo-syntax-emphasis",
|
|
3011
|
-
"code-marker": "owo-syntax-marker",
|
|
3012
|
-
"code": "owo-syntax-inline-code",
|
|
3013
|
-
"link-bracket": "owo-syntax-marker",
|
|
3014
|
-
"link-text": "owo-syntax-link-text",
|
|
3015
|
-
"link-url": "owo-syntax-link-url",
|
|
3016
|
-
"image-marker": "owo-syntax-marker",
|
|
3017
|
-
"image-alt": "owo-syntax-link-text",
|
|
3018
|
-
"image-url": "owo-syntax-link-url",
|
|
3019
|
-
"heading-marker": "owo-syntax-heading-marker",
|
|
3020
|
-
"list-marker": "owo-syntax-list-marker",
|
|
3021
|
-
"blockquote-marker": "owo-syntax-blockquote-marker",
|
|
3022
|
-
"fence-marker": "owo-syntax-fence",
|
|
3023
|
-
"fence-lang": "owo-syntax-fence-lang",
|
|
3024
|
-
"code-block-text": "owo-syntax-code-block",
|
|
3025
|
-
"strikethrough-marker": "owo-syntax-marker",
|
|
3026
|
-
"strikethrough": "owo-syntax-strikethrough",
|
|
3027
|
-
"math-marker": "owo-syntax-marker",
|
|
3028
|
-
"math-text": "owo-syntax-math",
|
|
3029
|
-
"hr": "owo-syntax-hr",
|
|
3030
|
-
"html": "owo-syntax-html"
|
|
3031
|
-
};
|
|
3032
|
-
var BLOCK_TYPE_TO_CLASS = {
|
|
3033
|
-
"heading": "owo-block-heading",
|
|
3034
|
-
"paragraph": "owo-block-paragraph",
|
|
3035
|
-
"unordered-list": "owo-block-list",
|
|
3036
|
-
"ordered-list": "owo-block-list",
|
|
3037
|
-
"blockquote": "owo-block-blockquote",
|
|
3038
|
-
"code-fence": "owo-block-code-fence",
|
|
3039
|
-
"directive-container": "owo-block-directive-container",
|
|
3040
|
-
"thematic-break": "owo-block-hr",
|
|
3041
|
-
"math-block": "owo-block-math",
|
|
3042
|
-
"table": "owo-block-table"
|
|
3043
|
-
};
|
|
3044
|
-
function createBlockElement(doc, tokens, blockIndex, blockType, headingLevel, depth) {
|
|
3045
|
-
const div = doc.createElement("div");
|
|
3046
|
-
div.setAttribute("data-owo-block", String(blockIndex));
|
|
3047
|
-
div.className = BLOCK_TYPE_TO_CLASS[blockType] || "owo-block-paragraph";
|
|
3048
|
-
if (blockType === "heading" && headingLevel) {
|
|
3049
|
-
div.setAttribute("data-owo-heading", String(headingLevel));
|
|
3050
|
-
}
|
|
3051
|
-
if (blockType === "blockquote" && depth && depth > 0) {
|
|
3052
|
-
div.setAttribute("data-owo-depth", String(depth));
|
|
3053
|
-
applyBlockquoteBarsStyle(div, depth);
|
|
3054
|
-
}
|
|
3055
|
-
renderTokensInto(doc, div, tokens);
|
|
3056
|
-
return div;
|
|
3057
|
-
}
|
|
3058
|
-
function renderTokensInto(doc, container, tokens) {
|
|
3059
|
-
for (const token of tokens) {
|
|
3060
|
-
const cls = TOKEN_TO_CLASS[token.type];
|
|
3061
|
-
const parts = token.text.split("\n");
|
|
3062
|
-
for (let i = 0; i < parts.length; i++) {
|
|
3063
|
-
if (i > 0) {
|
|
3064
|
-
container.appendChild(doc.createTextNode("\n"));
|
|
3065
|
-
}
|
|
3066
|
-
const part = parts[i];
|
|
3067
|
-
if (!part && i < parts.length - 1) continue;
|
|
3068
|
-
const leaf = doc.createElement("span");
|
|
3069
|
-
leaf.setAttribute("data-owo-leaf", "true");
|
|
3070
|
-
leaf.textContent = part;
|
|
3071
|
-
if (cls) {
|
|
3072
|
-
const decorator = doc.createElement("span");
|
|
3073
|
-
decorator.className = cls;
|
|
3074
|
-
decorator.setAttribute("data-owo-token", token.type);
|
|
3075
|
-
decorator.appendChild(leaf);
|
|
3076
|
-
container.appendChild(decorator);
|
|
3077
|
-
} else {
|
|
3078
|
-
if (part) {
|
|
3079
|
-
container.appendChild(leaf);
|
|
3080
|
-
}
|
|
3081
|
-
}
|
|
3082
|
-
}
|
|
3083
|
-
}
|
|
3084
|
-
if (container.childNodes.length === 0) {
|
|
3085
|
-
container.appendChild(doc.createElement("br"));
|
|
3086
|
-
}
|
|
3087
|
-
}
|
|
3088
|
-
function updateBlockElement(doc, existing, tokens, blockType, headingLevel, depth) {
|
|
3089
|
-
const newClass = BLOCK_TYPE_TO_CLASS[blockType] || "owo-block-paragraph";
|
|
3090
|
-
if (existing.className !== newClass) {
|
|
3091
|
-
existing.className = newClass;
|
|
3092
|
-
}
|
|
3093
|
-
if (blockType === "heading" && headingLevel) {
|
|
3094
|
-
existing.setAttribute("data-owo-heading", String(headingLevel));
|
|
3095
|
-
} else {
|
|
3096
|
-
existing.removeAttribute("data-owo-heading");
|
|
3097
|
-
}
|
|
3098
|
-
if (blockType === "blockquote" && depth && depth > 0) {
|
|
3099
|
-
existing.setAttribute("data-owo-depth", String(depth));
|
|
3100
|
-
applyBlockquoteBarsStyle(existing, depth);
|
|
3101
|
-
} else {
|
|
3102
|
-
existing.removeAttribute("data-owo-depth");
|
|
3103
|
-
existing.style.paddingLeft = "";
|
|
3104
|
-
existing.style.boxShadow = "";
|
|
3105
|
-
}
|
|
3106
|
-
existing.textContent = "";
|
|
3107
|
-
renderTokensInto(doc, existing, tokens);
|
|
3108
|
-
return true;
|
|
3109
|
-
}
|
|
3110
|
-
|
|
3111
|
-
// src/render/dom-patch.ts
|
|
3112
|
-
function fullRender(root, doc) {
|
|
3113
|
-
const ownerDoc = root.ownerDocument;
|
|
3114
|
-
root.textContent = "";
|
|
3115
|
-
let offset = 0;
|
|
3116
|
-
for (let i = 0; i < doc.blocks.length; i++) {
|
|
3117
|
-
const block = doc.blocks[i];
|
|
3118
|
-
const tokens = tokenizeBlock(block.raw, block.type, offset);
|
|
3119
|
-
const el = createBlockElement(ownerDoc, tokens, i, block.type, block.type === "heading" ? block.headingLevel : void 0, block.depth);
|
|
3120
|
-
root.appendChild(el);
|
|
3121
|
-
offset += block.raw.length + 1;
|
|
3122
|
-
}
|
|
3123
|
-
invalidateBlockCache(root);
|
|
3124
|
-
}
|
|
3125
|
-
function patchDirtyBlocks(root, doc, dirty) {
|
|
3126
|
-
const ownerDoc = root.ownerDocument;
|
|
3127
|
-
const blockElements = getBlockElements(root);
|
|
3128
|
-
if (blockElements.length !== doc.blocks.length) {
|
|
3129
|
-
fullRender(root, doc);
|
|
3130
|
-
return;
|
|
3131
|
-
}
|
|
3132
|
-
for (let i = dirty.startBlock; i <= dirty.endBlock && i < doc.blocks.length; i++) {
|
|
3133
|
-
const block = doc.blocks[i];
|
|
3134
|
-
const offset = getBlockStartOffset(doc, i);
|
|
3135
|
-
const tokens = tokenizeBlock(block.raw, block.type, offset);
|
|
3136
|
-
const existing = blockElements[i];
|
|
3137
|
-
if (existing) {
|
|
3138
|
-
updateBlockElement(ownerDoc, existing, tokens, block.type, block.type === "heading" ? block.headingLevel : void 0, block.depth);
|
|
3139
|
-
}
|
|
3140
|
-
}
|
|
3141
|
-
}
|
|
3142
|
-
function detectAndRenderDirty(root, oldDoc, newDoc, fullFallback = false) {
|
|
3143
|
-
const oldBlockCount = oldDoc.blocks.length;
|
|
3144
|
-
const newBlockCount = newDoc.blocks.length;
|
|
3145
|
-
if (oldBlockCount !== newBlockCount) {
|
|
3146
|
-
fullRender(root, newDoc);
|
|
3147
|
-
return;
|
|
3148
|
-
}
|
|
3149
|
-
let dirty = null;
|
|
3150
|
-
for (let i = 0; i < newBlockCount; i++) {
|
|
3151
|
-
if (newDoc.blocks[i].raw !== oldDoc.blocks[i].raw || newDoc.blocks[i].type !== oldDoc.blocks[i].type) {
|
|
3152
|
-
dirty = expandDirtyRange(dirty, i, newBlockCount);
|
|
3153
|
-
}
|
|
3154
|
-
}
|
|
3155
|
-
if (dirty) {
|
|
3156
|
-
const expanded = expandWithContext(dirty, newBlockCount);
|
|
3157
|
-
if (expanded) patchDirtyBlocks(root, newDoc, expanded);
|
|
3158
|
-
} else if (fullFallback) {
|
|
3159
|
-
fullRender(root, newDoc);
|
|
3160
|
-
}
|
|
3161
|
-
}
|
|
3162
|
-
function getBlockElements(root) {
|
|
3163
|
-
const elements = [];
|
|
3164
|
-
for (const child of root.children) {
|
|
3165
|
-
if (child instanceof HTMLElement && child.hasAttribute("data-owo-block")) {
|
|
3166
|
-
elements.push(child);
|
|
3167
|
-
}
|
|
3168
|
-
}
|
|
3169
|
-
return elements;
|
|
3170
|
-
}
|
|
3171
|
-
|
|
3172
|
-
export {
|
|
3173
|
-
tokenizeInline,
|
|
3174
|
-
tokenizeBlock,
|
|
3175
|
-
createBlockIdGenerator,
|
|
3176
|
-
resetBlockIdCounter,
|
|
3177
|
-
parseMarkdownToDocument,
|
|
3178
|
-
reconcileBlocks,
|
|
3179
|
-
serializeDocument,
|
|
3180
|
-
getBlockById,
|
|
3181
|
-
getBlockIndexById,
|
|
3182
|
-
getBlockAtOffset,
|
|
3183
|
-
getBlockStartOffset,
|
|
3184
|
-
linearToVirtualPosition,
|
|
3185
|
-
linearToVirtual,
|
|
3186
|
-
virtualPositionToLinear,
|
|
3187
|
-
virtualToLinear,
|
|
3188
|
-
virtualPositionsEqual,
|
|
3189
|
-
isVirtualSelectionCollapsed,
|
|
3190
|
-
getBlockIndexForPosition,
|
|
3191
|
-
SIDE_ANNOTATION_TAIL_RE,
|
|
3192
|
-
SIDE_CONTINUATION_TAIL_RE,
|
|
3193
|
-
NOT_HANDLED,
|
|
3194
|
-
handleMarkdownEnter,
|
|
3195
|
-
resolveIndentSize,
|
|
3196
|
-
applyMarkdownIndent,
|
|
3197
|
-
handleCharInput,
|
|
3198
|
-
handleSmartDelete,
|
|
3199
|
-
handleSmartBackspace,
|
|
3200
|
-
toggleBold,
|
|
3201
|
-
toggleItalic,
|
|
3202
|
-
insertLink,
|
|
3203
|
-
insertCodeFence,
|
|
3204
|
-
insertMathBlock,
|
|
3205
|
-
insertTable,
|
|
3206
|
-
insertImage,
|
|
3207
|
-
insertSideAnnotation,
|
|
3208
|
-
createCommandRegistry,
|
|
3209
|
-
resolveBlockContextType,
|
|
3210
|
-
createOwoMarkCore,
|
|
3211
|
-
domRangeToOffset,
|
|
3212
|
-
offsetToDomRange,
|
|
3213
|
-
restoreSelection,
|
|
3214
|
-
readSelection,
|
|
3215
|
-
invalidateBlockCache,
|
|
3216
|
-
expandDirtyRange,
|
|
3217
|
-
expandWithContext,
|
|
3218
|
-
BQ_STEP,
|
|
3219
|
-
buildBlockquoteBarsBoxShadow,
|
|
3220
|
-
TOKEN_TO_CLASS,
|
|
3221
|
-
BLOCK_TYPE_TO_CLASS,
|
|
3222
|
-
createBlockElement,
|
|
3223
|
-
updateBlockElement,
|
|
3224
|
-
fullRender,
|
|
3225
|
-
patchDirtyBlocks,
|
|
3226
|
-
detectAndRenderDirty
|
|
3227
|
-
};
|