@blankdotpage/cake 0.1.76 → 0.1.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cake/clipboard.d.ts.map +1 -1
- package/dist/cake/clipboard.js +29 -0
- package/dist/cake/core/runtime.d.ts +32 -1
- package/dist/cake/core/runtime.d.ts.map +1 -1
- package/dist/cake/core/runtime.js +121 -513
- package/dist/cake/editor/cake-editor.d.ts +6 -1
- package/dist/cake/editor/cake-editor.d.ts.map +1 -1
- package/dist/cake/editor/cake-editor.js +18 -3
- package/dist/cake/extensions/blockquote/blockquote.d.ts.map +1 -1
- package/dist/cake/extensions/blockquote/blockquote.js +9 -0
- package/dist/cake/extensions/bold/bold.d.ts.map +1 -1
- package/dist/cake/extensions/bold/bold.js +9 -14
- package/dist/cake/extensions/heading/heading.d.ts.map +1 -1
- package/dist/cake/extensions/heading/heading.js +11 -1
- package/dist/cake/extensions/italic/italic.d.ts +1 -7
- package/dist/cake/extensions/italic/italic.d.ts.map +1 -1
- package/dist/cake/extensions/italic/italic.js +27 -91
- package/dist/cake/extensions/link/link.d.ts.map +1 -1
- package/dist/cake/extensions/link/link.js +7 -0
- package/dist/cake/extensions/list/list.d.ts.map +1 -1
- package/dist/cake/extensions/list/list.js +52 -0
- package/dist/cake/extensions/strikethrough/strikethrough.d.ts.map +1 -1
- package/dist/cake/extensions/strikethrough/strikethrough.js +6 -0
- package/dist/cake/extensions/underline/underline.d.ts.map +1 -1
- package/dist/cake/extensions/underline/underline.js +6 -0
- package/dist/cake/react/index.d.ts +0 -1
- package/dist/cake/react/index.d.ts.map +1 -1
- package/dist/cake/react/index.js +1 -5
- package/package.json +1 -1
|
@@ -19,7 +19,6 @@ export function isApplyEditCommand(command) {
|
|
|
19
19
|
command.type === "delete-forward");
|
|
20
20
|
}
|
|
21
21
|
const defaultSelection = { start: 0, end: 0, affinity: "forward" };
|
|
22
|
-
const WORD_CHARACTER_PATTERN = /[\p{L}\p{N}_]/u;
|
|
23
22
|
function removeFromArray(arr, value) {
|
|
24
23
|
const index = arr.indexOf(value);
|
|
25
24
|
if (index === -1) {
|
|
@@ -40,6 +39,8 @@ export function createRuntimeForTests(extensions) {
|
|
|
40
39
|
const structuralReparsePolicies = [];
|
|
41
40
|
const domInlineRenderers = [];
|
|
42
41
|
const domBlockRenderers = [];
|
|
42
|
+
const inlineHtmlSerializers = [];
|
|
43
|
+
const serializeSelectionLineToHtmlFns = [];
|
|
43
44
|
const editor = {
|
|
44
45
|
registerInlineWrapperAffinity: (specs) => {
|
|
45
46
|
for (const spec of specs) {
|
|
@@ -125,6 +126,14 @@ export function createRuntimeForTests(extensions) {
|
|
|
125
126
|
domBlockRenderers.push(fn);
|
|
126
127
|
return () => removeFromArray(domBlockRenderers, fn);
|
|
127
128
|
},
|
|
129
|
+
registerInlineHtmlSerializer: (fn) => {
|
|
130
|
+
inlineHtmlSerializers.push(fn);
|
|
131
|
+
return () => removeFromArray(inlineHtmlSerializers, fn);
|
|
132
|
+
},
|
|
133
|
+
registerSerializeSelectionLineToHtml: (fn) => {
|
|
134
|
+
serializeSelectionLineToHtmlFns.push(fn);
|
|
135
|
+
return () => removeFromArray(serializeSelectionLineToHtmlFns, fn);
|
|
136
|
+
},
|
|
128
137
|
registerUI: () => {
|
|
129
138
|
return () => { };
|
|
130
139
|
},
|
|
@@ -145,11 +154,13 @@ export function createRuntimeForTests(extensions) {
|
|
|
145
154
|
structuralReparsePolicies,
|
|
146
155
|
domInlineRenderers,
|
|
147
156
|
domBlockRenderers,
|
|
157
|
+
inlineHtmlSerializers,
|
|
158
|
+
serializeSelectionLineToHtmlFns,
|
|
148
159
|
});
|
|
149
160
|
return runtime;
|
|
150
161
|
}
|
|
151
162
|
export function createRuntimeFromRegistry(registry) {
|
|
152
|
-
const { toggleMarkerToSpec, inclusiveAtEndByKind, parseBlockFns, parseInlineFns, serializeBlockFns, serializeInlineFns, normalizeBlockFns, normalizeInlineFns, onEditFns, structuralReparsePolicies, domInlineRenderers, domBlockRenderers, } = registry;
|
|
163
|
+
const { toggleMarkerToSpec, inclusiveAtEndByKind, parseBlockFns, parseInlineFns, serializeBlockFns, serializeInlineFns, normalizeBlockFns, normalizeInlineFns, onEditFns, structuralReparsePolicies, domInlineRenderers, domBlockRenderers, inlineHtmlSerializers, serializeSelectionLineToHtmlFns, } = registry;
|
|
153
164
|
const isInclusiveAtEnd = (kind) => inclusiveAtEndByKind.get(kind) ?? true;
|
|
154
165
|
const removedBlockSentinel = Symbol("removed-block");
|
|
155
166
|
const removedInlineSentinel = Symbol("removed-inline");
|
|
@@ -348,17 +359,13 @@ export function createRuntimeFromRegistry(registry) {
|
|
|
348
359
|
}
|
|
349
360
|
content.push(normalizedInline);
|
|
350
361
|
}
|
|
351
|
-
const mergedContent = mergeAdjacentInlines(content);
|
|
352
|
-
if (mergedContent !== content) {
|
|
353
|
-
changed = true;
|
|
354
|
-
}
|
|
355
362
|
if (!changed) {
|
|
356
363
|
normalizedBlockCache.set(block, next);
|
|
357
364
|
return next;
|
|
358
365
|
}
|
|
359
366
|
const normalized = {
|
|
360
367
|
...next,
|
|
361
|
-
content
|
|
368
|
+
content,
|
|
362
369
|
};
|
|
363
370
|
normalizedBlockCache.set(block, normalized);
|
|
364
371
|
return normalized;
|
|
@@ -414,52 +421,17 @@ export function createRuntimeFromRegistry(registry) {
|
|
|
414
421
|
}
|
|
415
422
|
let next = pre;
|
|
416
423
|
if (next.type === "inline-wrapper") {
|
|
417
|
-
const children = mergeAdjacentInlines(next.children
|
|
418
|
-
.map((child) => normalizeInline(child))
|
|
419
|
-
.filter((child) => child !== null));
|
|
420
424
|
next = {
|
|
421
425
|
...next,
|
|
422
|
-
children
|
|
426
|
+
children: next.children
|
|
427
|
+
.map((child) => normalizeInline(child))
|
|
428
|
+
.filter((child) => child !== null),
|
|
423
429
|
};
|
|
424
430
|
}
|
|
425
431
|
const normalized = applyInlineNormalizers(next);
|
|
426
432
|
normalizedInlineCache.set(inline, normalized ?? removedInlineSentinel);
|
|
427
433
|
return normalized;
|
|
428
434
|
}
|
|
429
|
-
function mergeAdjacentInlines(inlines) {
|
|
430
|
-
if (inlines.length < 2) {
|
|
431
|
-
return inlines;
|
|
432
|
-
}
|
|
433
|
-
const merged = [];
|
|
434
|
-
let changed = false;
|
|
435
|
-
for (const inline of inlines) {
|
|
436
|
-
const previous = merged[merged.length - 1];
|
|
437
|
-
if (previous?.type === "text" && inline.type === "text") {
|
|
438
|
-
merged[merged.length - 1] = {
|
|
439
|
-
...previous,
|
|
440
|
-
text: previous.text + inline.text,
|
|
441
|
-
};
|
|
442
|
-
changed = true;
|
|
443
|
-
continue;
|
|
444
|
-
}
|
|
445
|
-
if (previous?.type === "inline-wrapper" &&
|
|
446
|
-
inline.type === "inline-wrapper" &&
|
|
447
|
-
previous.kind === inline.kind &&
|
|
448
|
-
stableStringify(previous.data) === stableStringify(inline.data)) {
|
|
449
|
-
merged[merged.length - 1] = {
|
|
450
|
-
...previous,
|
|
451
|
-
children: mergeAdjacentInlines([
|
|
452
|
-
...previous.children,
|
|
453
|
-
...inline.children,
|
|
454
|
-
]),
|
|
455
|
-
};
|
|
456
|
-
changed = true;
|
|
457
|
-
continue;
|
|
458
|
-
}
|
|
459
|
-
merged.push(inline);
|
|
460
|
-
}
|
|
461
|
-
return changed ? merged : inlines;
|
|
462
|
-
}
|
|
463
435
|
function createTopLevelBlockSegment(block) {
|
|
464
436
|
const serialized = serializeBlock(block);
|
|
465
437
|
return {
|
|
@@ -611,15 +583,9 @@ export function createRuntimeFromRegistry(registry) {
|
|
|
611
583
|
? previousSegmented
|
|
612
584
|
: undefined;
|
|
613
585
|
const segmented = buildSegmentedDocState(normalized, reusablePrevious);
|
|
614
|
-
const cursorLength = segmented.map.cursorLength;
|
|
615
|
-
const clampedSelection = {
|
|
616
|
-
...selection,
|
|
617
|
-
start: Math.max(0, Math.min(cursorLength, selection.start)),
|
|
618
|
-
end: Math.max(0, Math.min(cursorLength, selection.end)),
|
|
619
|
-
};
|
|
620
586
|
return {
|
|
621
587
|
source: segmented.source,
|
|
622
|
-
selection
|
|
588
|
+
selection,
|
|
623
589
|
map: segmented.map,
|
|
624
590
|
doc: normalized,
|
|
625
591
|
runtime: runtime,
|
|
@@ -771,8 +737,7 @@ export function createRuntimeFromRegistry(registry) {
|
|
|
771
737
|
previousState: useIncrementalSegmentedDerivation ? state : undefined,
|
|
772
738
|
});
|
|
773
739
|
const interimAffinity = structural.nextAffinity ?? "forward";
|
|
774
|
-
const
|
|
775
|
-
const caretSource = interim.map.cursorToSource(interimCursor, interimAffinity);
|
|
740
|
+
const caretSource = interim.map.cursorToSource(structural.nextCursor, interimAffinity);
|
|
776
741
|
if (useIncrementalSegmentedDerivation) {
|
|
777
742
|
const caretCursor = interim.map.sourceToCursor(caretSource, interimAffinity);
|
|
778
743
|
return {
|
|
@@ -859,27 +824,6 @@ export function createRuntimeFromRegistry(registry) {
|
|
|
859
824
|
replaceText.length > 0 &&
|
|
860
825
|
range.start === range.end &&
|
|
861
826
|
textModel.getGraphemeAtCursor(range.start) === "\u200B";
|
|
862
|
-
if (command.type === "insert" && shouldReplacePlaceholder) {
|
|
863
|
-
const leadingWhitespace = replaceText.match(/^\s+/)?.[0] ?? "";
|
|
864
|
-
const around = marksAroundCursor(doc, range.start);
|
|
865
|
-
if (leadingWhitespace.length > 0) {
|
|
866
|
-
if (isMarksPrefix(around.left, around.right) &&
|
|
867
|
-
around.right.length > around.left.length) {
|
|
868
|
-
const whitespaceInsert = insertTextBeforePendingPlaceholderInDoc(doc, range.start, leadingWhitespace, around.left);
|
|
869
|
-
if (whitespaceInsert) {
|
|
870
|
-
const trailingText = replaceText.slice(leadingWhitespace.length);
|
|
871
|
-
if (trailingText.length === 0) {
|
|
872
|
-
return whitespaceInsert;
|
|
873
|
-
}
|
|
874
|
-
return applyStructuralEdit({ type: "insert", text: trailingText }, whitespaceInsert.doc, {
|
|
875
|
-
start: whitespaceInsert.nextCursor,
|
|
876
|
-
end: whitespaceInsert.nextCursor,
|
|
877
|
-
affinity: whitespaceInsert.nextAffinity,
|
|
878
|
-
});
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
827
|
const effectiveRange = shouldReplacePlaceholder
|
|
884
828
|
? { start: range.start, end: Math.min(docCursorLength, range.start + 1) }
|
|
885
829
|
: range;
|
|
@@ -1232,28 +1176,6 @@ export function createRuntimeFromRegistry(registry) {
|
|
|
1232
1176
|
}
|
|
1233
1177
|
return true;
|
|
1234
1178
|
}
|
|
1235
|
-
function removeMarkByKind(marks, kind) {
|
|
1236
|
-
let removed = false;
|
|
1237
|
-
return marks.filter((mark) => {
|
|
1238
|
-
if (!removed && mark.kind === kind) {
|
|
1239
|
-
removed = true;
|
|
1240
|
-
return false;
|
|
1241
|
-
}
|
|
1242
|
-
return true;
|
|
1243
|
-
});
|
|
1244
|
-
}
|
|
1245
|
-
function mergeMarksPreservingOrder(...groups) {
|
|
1246
|
-
const next = [];
|
|
1247
|
-
for (const group of groups) {
|
|
1248
|
-
for (const mark of group) {
|
|
1249
|
-
if (next.some((existing) => existing.key === mark.key)) {
|
|
1250
|
-
continue;
|
|
1251
|
-
}
|
|
1252
|
-
next.push(mark);
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
return next;
|
|
1256
|
-
}
|
|
1257
1179
|
function sliceRuns(runs, startCursor, endCursor) {
|
|
1258
1180
|
const [left, rest] = splitRunsAt(runs, startCursor);
|
|
1259
1181
|
const [selected, right] = splitRunsAt(rest, Math.max(0, endCursor - startCursor));
|
|
@@ -1362,237 +1284,6 @@ export function createRuntimeFromRegistry(registry) {
|
|
|
1362
1284
|
const right = marksAtGraphemeIndex(runs, loc.offsetInLine);
|
|
1363
1285
|
return { left: left ?? [], right: right ?? [] };
|
|
1364
1286
|
}
|
|
1365
|
-
function createPendingPlaceholderStateAtCursor(state, cursorOffset, marks) {
|
|
1366
|
-
const textModel = getEditorTextModelForDoc(state.doc);
|
|
1367
|
-
const lines = textModel.getStructuralLines();
|
|
1368
|
-
const loc = textModel.resolveOffsetToLine(cursorOffset);
|
|
1369
|
-
const line = lines[loc.lineIndex];
|
|
1370
|
-
if (!line) {
|
|
1371
|
-
return null;
|
|
1372
|
-
}
|
|
1373
|
-
const block = getBlockAtPath(state.doc.blocks, line.path);
|
|
1374
|
-
if (!block || block.type !== "paragraph") {
|
|
1375
|
-
return null;
|
|
1376
|
-
}
|
|
1377
|
-
const placeholder = "\u200B";
|
|
1378
|
-
const runs = paragraphToRuns(block);
|
|
1379
|
-
const { before, after } = sliceRuns(runs, loc.offsetInLine, loc.offsetInLine);
|
|
1380
|
-
const mergedRuns = normalizeRuns([
|
|
1381
|
-
...before,
|
|
1382
|
-
{ type: "text", text: placeholder, marks },
|
|
1383
|
-
...after,
|
|
1384
|
-
]);
|
|
1385
|
-
const nextBlock = {
|
|
1386
|
-
...block,
|
|
1387
|
-
content: runsToInlines(mergedRuns),
|
|
1388
|
-
};
|
|
1389
|
-
const parentPath = line.path.slice(0, -1);
|
|
1390
|
-
const indexInParent = line.path[line.path.length - 1] ?? 0;
|
|
1391
|
-
const nextDoc = {
|
|
1392
|
-
...state.doc,
|
|
1393
|
-
blocks: updateBlocksAtPath(state.doc.blocks, parentPath, (blocks) => blocks.map((child, index) => index === indexInParent ? nextBlock : child)),
|
|
1394
|
-
};
|
|
1395
|
-
const next = createStateFromDoc(nextDoc);
|
|
1396
|
-
const sourceHint = state.map.cursorToSource(cursorOffset, "backward");
|
|
1397
|
-
const searchStart = Math.max(0, sourceHint - 4);
|
|
1398
|
-
const placeholderStart = next.source.indexOf(placeholder, searchStart) ?? -1;
|
|
1399
|
-
const resolvedPlaceholderStart = placeholderStart !== -1 ? placeholderStart : next.source.indexOf(placeholder);
|
|
1400
|
-
if (resolvedPlaceholderStart === -1) {
|
|
1401
|
-
return null;
|
|
1402
|
-
}
|
|
1403
|
-
const startCursor = next.map.sourceToCursor(resolvedPlaceholderStart, "forward");
|
|
1404
|
-
return {
|
|
1405
|
-
...next,
|
|
1406
|
-
selection: {
|
|
1407
|
-
start: startCursor.cursorOffset,
|
|
1408
|
-
end: startCursor.cursorOffset,
|
|
1409
|
-
affinity: "forward",
|
|
1410
|
-
},
|
|
1411
|
-
};
|
|
1412
|
-
}
|
|
1413
|
-
function rewritePendingPlaceholderAtCursor(state, cursorOffset, marks) {
|
|
1414
|
-
const textModel = getEditorTextModelForDoc(state.doc);
|
|
1415
|
-
const lines = textModel.getStructuralLines();
|
|
1416
|
-
const loc = textModel.resolveOffsetToLine(cursorOffset);
|
|
1417
|
-
const line = lines[loc.lineIndex];
|
|
1418
|
-
if (!line) {
|
|
1419
|
-
return null;
|
|
1420
|
-
}
|
|
1421
|
-
const block = getBlockAtPath(state.doc.blocks, line.path);
|
|
1422
|
-
if (!block || block.type !== "paragraph") {
|
|
1423
|
-
return null;
|
|
1424
|
-
}
|
|
1425
|
-
const placeholder = "\u200B";
|
|
1426
|
-
const runs = paragraphToRuns(block);
|
|
1427
|
-
const { before, after } = sliceRuns(runs, loc.offsetInLine, loc.offsetInLine);
|
|
1428
|
-
const replacement = [];
|
|
1429
|
-
const firstAfter = after[0];
|
|
1430
|
-
if (firstAfter?.type === "text" && firstAfter.text.startsWith(placeholder)) {
|
|
1431
|
-
if (marks && marks.length > 0) {
|
|
1432
|
-
replacement.push({ type: "text", text: placeholder, marks });
|
|
1433
|
-
}
|
|
1434
|
-
if (firstAfter.text.length > placeholder.length) {
|
|
1435
|
-
replacement.push({
|
|
1436
|
-
...firstAfter,
|
|
1437
|
-
text: firstAfter.text.slice(placeholder.length),
|
|
1438
|
-
});
|
|
1439
|
-
}
|
|
1440
|
-
replacement.push(...after.slice(1));
|
|
1441
|
-
}
|
|
1442
|
-
else {
|
|
1443
|
-
const lastBefore = before[before.length - 1];
|
|
1444
|
-
if (lastBefore?.type !== "text" ||
|
|
1445
|
-
!lastBefore.text.endsWith(placeholder)) {
|
|
1446
|
-
return null;
|
|
1447
|
-
}
|
|
1448
|
-
const prefix = lastBefore.text.slice(0, -placeholder.length);
|
|
1449
|
-
if (prefix) {
|
|
1450
|
-
replacement.push({ ...lastBefore, text: prefix });
|
|
1451
|
-
}
|
|
1452
|
-
if (marks && marks.length > 0) {
|
|
1453
|
-
replacement.push({ type: "text", text: placeholder, marks });
|
|
1454
|
-
}
|
|
1455
|
-
replacement.push(...after);
|
|
1456
|
-
before.pop();
|
|
1457
|
-
}
|
|
1458
|
-
const mergedRuns = normalizeRuns([...before, ...replacement]);
|
|
1459
|
-
const nextBlock = {
|
|
1460
|
-
...block,
|
|
1461
|
-
content: runsToInlines(mergedRuns),
|
|
1462
|
-
};
|
|
1463
|
-
const parentPath = line.path.slice(0, -1);
|
|
1464
|
-
const indexInParent = line.path[line.path.length - 1] ?? 0;
|
|
1465
|
-
const nextDoc = {
|
|
1466
|
-
...state.doc,
|
|
1467
|
-
blocks: updateBlocksAtPath(state.doc.blocks, parentPath, (blocks) => blocks.map((child, index) => index === indexInParent ? nextBlock : child)),
|
|
1468
|
-
};
|
|
1469
|
-
const next = createStateFromDoc(nextDoc);
|
|
1470
|
-
return {
|
|
1471
|
-
...next,
|
|
1472
|
-
selection: {
|
|
1473
|
-
start: cursorOffset,
|
|
1474
|
-
end: cursorOffset,
|
|
1475
|
-
affinity: marks && marks.length > 0 ? "forward" : "backward",
|
|
1476
|
-
},
|
|
1477
|
-
};
|
|
1478
|
-
}
|
|
1479
|
-
function updatePendingPlaceholderMarksAtCursor(state, cursorOffset, marks) {
|
|
1480
|
-
return rewritePendingPlaceholderAtCursor(state, cursorOffset, marks);
|
|
1481
|
-
}
|
|
1482
|
-
function removePendingPlaceholderAtCursor(state, cursorOffset) {
|
|
1483
|
-
return rewritePendingPlaceholderAtCursor(state, cursorOffset, null);
|
|
1484
|
-
}
|
|
1485
|
-
function getPendingPlaceholderMarksAtCursor(state, cursorOffset) {
|
|
1486
|
-
const textModel = getEditorTextModelForDoc(state.doc);
|
|
1487
|
-
const lines = textModel.getStructuralLines();
|
|
1488
|
-
const loc = textModel.resolveOffsetToLine(cursorOffset);
|
|
1489
|
-
const line = lines[loc.lineIndex];
|
|
1490
|
-
if (!line) {
|
|
1491
|
-
return null;
|
|
1492
|
-
}
|
|
1493
|
-
const block = getBlockAtPath(state.doc.blocks, line.path);
|
|
1494
|
-
if (!block || block.type !== "paragraph") {
|
|
1495
|
-
return null;
|
|
1496
|
-
}
|
|
1497
|
-
const placeholder = "\u200B";
|
|
1498
|
-
const runs = paragraphToRuns(block);
|
|
1499
|
-
const { before, after } = sliceRuns(runs, loc.offsetInLine, loc.offsetInLine);
|
|
1500
|
-
const firstAfter = after[0];
|
|
1501
|
-
if (firstAfter?.type === "text" && firstAfter.text.startsWith(placeholder)) {
|
|
1502
|
-
return firstAfter.marks;
|
|
1503
|
-
}
|
|
1504
|
-
const lastBefore = before[before.length - 1];
|
|
1505
|
-
if (lastBefore?.type === "text" &&
|
|
1506
|
-
lastBefore.text.endsWith(placeholder)) {
|
|
1507
|
-
return lastBefore.marks;
|
|
1508
|
-
}
|
|
1509
|
-
return null;
|
|
1510
|
-
}
|
|
1511
|
-
function insertTextBeforePendingPlaceholderInDoc(doc, cursorOffset, text, marks) {
|
|
1512
|
-
const textModel = getEditorTextModelForDoc(doc);
|
|
1513
|
-
const lines = textModel.getStructuralLines();
|
|
1514
|
-
const loc = textModel.resolveOffsetToLine(cursorOffset);
|
|
1515
|
-
const line = lines[loc.lineIndex];
|
|
1516
|
-
if (!line) {
|
|
1517
|
-
return null;
|
|
1518
|
-
}
|
|
1519
|
-
const block = getBlockAtPath(doc.blocks, line.path);
|
|
1520
|
-
if (!block || block.type !== "paragraph") {
|
|
1521
|
-
return null;
|
|
1522
|
-
}
|
|
1523
|
-
const placeholder = "\u200B";
|
|
1524
|
-
const runs = paragraphToRuns(block);
|
|
1525
|
-
const { before, after } = sliceRuns(runs, loc.offsetInLine, loc.offsetInLine);
|
|
1526
|
-
const firstAfter = after[0];
|
|
1527
|
-
if (firstAfter?.type !== "text" ||
|
|
1528
|
-
!firstAfter.text.startsWith(placeholder)) {
|
|
1529
|
-
return null;
|
|
1530
|
-
}
|
|
1531
|
-
const mergedRuns = normalizeRuns([
|
|
1532
|
-
...before,
|
|
1533
|
-
...(text.length > 0 ? [{ type: "text", text, marks }] : []),
|
|
1534
|
-
firstAfter,
|
|
1535
|
-
...after.slice(1),
|
|
1536
|
-
]);
|
|
1537
|
-
const nextBlock = {
|
|
1538
|
-
...block,
|
|
1539
|
-
content: runsToInlines(mergedRuns),
|
|
1540
|
-
};
|
|
1541
|
-
const parentPath = line.path.slice(0, -1);
|
|
1542
|
-
const indexInParent = line.path[line.path.length - 1] ?? 0;
|
|
1543
|
-
const nextDoc = {
|
|
1544
|
-
...doc,
|
|
1545
|
-
blocks: updateBlocksAtPath(doc.blocks, parentPath, (blocks) => blocks.map((child, index) => index === indexInParent ? nextBlock : child)),
|
|
1546
|
-
};
|
|
1547
|
-
return {
|
|
1548
|
-
doc: nextDoc,
|
|
1549
|
-
nextCursor: cursorOffset + Array.from(graphemeSegments(text)).length,
|
|
1550
|
-
nextAffinity: "forward",
|
|
1551
|
-
};
|
|
1552
|
-
}
|
|
1553
|
-
function hasInlineMarkerBoundaryBefore(source, markerStart) {
|
|
1554
|
-
if (markerStart <= 0) {
|
|
1555
|
-
return true;
|
|
1556
|
-
}
|
|
1557
|
-
return !WORD_CHARACTER_PATTERN.test(source[markerStart - 1] ?? "");
|
|
1558
|
-
}
|
|
1559
|
-
function pickSafeCollapsedToggleMarkerSpec(params) {
|
|
1560
|
-
const { defaultSpec, source, insertAt, affinity } = params;
|
|
1561
|
-
const candidates = Array.from(toggleMarkerToSpec.values()).filter((spec, index, all) => spec.kind === defaultSpec.kind &&
|
|
1562
|
-
all.findIndex((candidate) => candidate.kind === spec.kind &&
|
|
1563
|
-
candidate.open === spec.open &&
|
|
1564
|
-
candidate.close === spec.close) === index);
|
|
1565
|
-
if (candidates.length <= 1) {
|
|
1566
|
-
return defaultSpec;
|
|
1567
|
-
}
|
|
1568
|
-
const previousChar = source[insertAt - 1] ?? "";
|
|
1569
|
-
const nextChar = source[insertAt] ?? "";
|
|
1570
|
-
let bestSpec = defaultSpec;
|
|
1571
|
-
let bestScore = Number.POSITIVE_INFINITY;
|
|
1572
|
-
for (const spec of candidates) {
|
|
1573
|
-
if (spec.open === "_" &&
|
|
1574
|
-
!hasInlineMarkerBoundaryBefore(source, insertAt)) {
|
|
1575
|
-
continue;
|
|
1576
|
-
}
|
|
1577
|
-
let score = 0;
|
|
1578
|
-
if (previousChar && spec.open[0] === previousChar) {
|
|
1579
|
-
score += affinity === "forward" ? 8 : 3;
|
|
1580
|
-
}
|
|
1581
|
-
if (nextChar &&
|
|
1582
|
-
spec.close[spec.close.length - 1] === nextChar) {
|
|
1583
|
-
score += affinity === "backward" ? 8 : 3;
|
|
1584
|
-
}
|
|
1585
|
-
if (spec.open === defaultSpec.open &&
|
|
1586
|
-
spec.close === defaultSpec.close) {
|
|
1587
|
-
score -= 0.5;
|
|
1588
|
-
}
|
|
1589
|
-
if (score < bestScore) {
|
|
1590
|
-
bestSpec = spec;
|
|
1591
|
-
bestScore = score;
|
|
1592
|
-
}
|
|
1593
|
-
}
|
|
1594
|
-
return bestSpec;
|
|
1595
|
-
}
|
|
1596
1287
|
function preferredAffinityAtGap(left, right, fallback) {
|
|
1597
1288
|
if (isMarksPrefix(left, right) && right.length > left.length) {
|
|
1598
1289
|
return "forward";
|
|
@@ -1845,49 +1536,51 @@ export function createRuntimeFromRegistry(registry) {
|
|
|
1845
1536
|
const openLen = openMarker.length;
|
|
1846
1537
|
const closeLen = closeMarker.length;
|
|
1847
1538
|
const placeholder = "\u200B";
|
|
1848
|
-
const markerMark = {
|
|
1849
|
-
kind: markerKind,
|
|
1850
|
-
data: undefined,
|
|
1851
|
-
key: markKey(markerKind, undefined),
|
|
1852
|
-
};
|
|
1853
1539
|
if (selection.start === selection.end) {
|
|
1854
1540
|
const caret = selection.start;
|
|
1855
|
-
const pendingPlaceholderMarks = getPendingPlaceholderMarksAtCursor(state, caret);
|
|
1856
|
-
if (pendingPlaceholderMarks) {
|
|
1857
|
-
const hasMarker = pendingPlaceholderMarks.some((mark) => mark.kind === markerKind);
|
|
1858
|
-
const around = marksAroundCursor(state.doc, caret);
|
|
1859
|
-
const nextMarks = hasMarker
|
|
1860
|
-
? removeMarkByKind(pendingPlaceholderMarks, markerKind)
|
|
1861
|
-
: mergeMarksPreservingOrder(around.left, pendingPlaceholderMarks, [markerMark]);
|
|
1862
|
-
const next = nextMarks.length > 0
|
|
1863
|
-
? updatePendingPlaceholderMarksAtCursor(state, caret, nextMarks)
|
|
1864
|
-
: removePendingPlaceholderAtCursor(state, caret);
|
|
1865
|
-
if (next) {
|
|
1866
|
-
return {
|
|
1867
|
-
...next,
|
|
1868
|
-
selection: {
|
|
1869
|
-
start: caret,
|
|
1870
|
-
end: caret,
|
|
1871
|
-
affinity: "forward",
|
|
1872
|
-
},
|
|
1873
|
-
};
|
|
1874
|
-
}
|
|
1875
|
-
}
|
|
1876
1541
|
// When the caret is at the end boundary of an inline wrapper, toggling the
|
|
1877
1542
|
// wrapper should "exit" it (so the next character types outside). This is
|
|
1878
1543
|
// best expressed in cursor space by flipping affinity to "forward" when we
|
|
1879
1544
|
// are leaving a wrapper of the requested kind.
|
|
1880
1545
|
const around = marksAroundCursor(state.doc, caret);
|
|
1881
1546
|
if (isMarksPrefix(around.right, around.left) &&
|
|
1882
|
-
around.left.length > around.right.length
|
|
1883
|
-
(selection.affinity ?? "forward") === "backward") {
|
|
1547
|
+
around.left.length > around.right.length) {
|
|
1884
1548
|
const exiting = around.left.slice(around.right.length);
|
|
1885
1549
|
if (exiting.some((mark) => mark.kind === markerKind)) {
|
|
1886
|
-
const
|
|
1887
|
-
if (
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1550
|
+
const toggledExitingIndex = exiting.findIndex((mark) => mark.kind === markerKind);
|
|
1551
|
+
if (exiting.length > 1 &&
|
|
1552
|
+
toggledExitingIndex !== -1 &&
|
|
1553
|
+
toggledExitingIndex < exiting.length - 1) {
|
|
1554
|
+
return {
|
|
1555
|
+
...state,
|
|
1556
|
+
selection: {
|
|
1557
|
+
start: caret,
|
|
1558
|
+
end: caret,
|
|
1559
|
+
affinity: "forward",
|
|
1560
|
+
},
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
if (exiting.length > 1) {
|
|
1564
|
+
const insertAtForward = map.cursorToSource(caret, "forward");
|
|
1565
|
+
const insertAtBackward = map.cursorToSource(caret, "backward");
|
|
1566
|
+
const between = source.slice(insertAtBackward, insertAtForward);
|
|
1567
|
+
const markerIndex = between.indexOf(closeMarker);
|
|
1568
|
+
if (markerIndex !== -1) {
|
|
1569
|
+
const insertAt = insertAtBackward + markerIndex + closeLen;
|
|
1570
|
+
const nextSource = source.slice(0, insertAt) +
|
|
1571
|
+
placeholder +
|
|
1572
|
+
source.slice(insertAt);
|
|
1573
|
+
const next = createState(nextSource);
|
|
1574
|
+
const placeholderStart = insertAt;
|
|
1575
|
+
const startCursor = next.map.sourceToCursor(placeholderStart, "forward");
|
|
1576
|
+
return {
|
|
1577
|
+
...next,
|
|
1578
|
+
selection: {
|
|
1579
|
+
start: startCursor.cursorOffset,
|
|
1580
|
+
end: startCursor.cursorOffset,
|
|
1581
|
+
affinity: "forward",
|
|
1582
|
+
},
|
|
1583
|
+
};
|
|
1891
1584
|
}
|
|
1892
1585
|
}
|
|
1893
1586
|
return {
|
|
@@ -1907,20 +1600,6 @@ export function createRuntimeFromRegistry(registry) {
|
|
|
1907
1600
|
around.right.length > around.left.length) {
|
|
1908
1601
|
const entering = around.right.slice(around.left.length);
|
|
1909
1602
|
if (entering.some((mark) => mark.kind === markerKind)) {
|
|
1910
|
-
const remainingMarks = removeMarkByKind(around.right, markerKind);
|
|
1911
|
-
const next = remainingMarks.length > 0
|
|
1912
|
-
? updatePendingPlaceholderMarksAtCursor(state, caret, remainingMarks)
|
|
1913
|
-
: removePendingPlaceholderAtCursor(state, caret);
|
|
1914
|
-
if (next) {
|
|
1915
|
-
return {
|
|
1916
|
-
...next,
|
|
1917
|
-
selection: {
|
|
1918
|
-
start: caret,
|
|
1919
|
-
end: caret,
|
|
1920
|
-
affinity: "backward",
|
|
1921
|
-
},
|
|
1922
|
-
};
|
|
1923
|
-
}
|
|
1924
1603
|
const insertAtBackward = map.cursorToSource(caret, "backward");
|
|
1925
1604
|
const insertAtForward = map.cursorToSource(caret, "forward");
|
|
1926
1605
|
const after = source.slice(insertAtBackward);
|
|
@@ -1979,18 +1658,6 @@ export function createRuntimeFromRegistry(registry) {
|
|
|
1979
1658
|
}
|
|
1980
1659
|
}
|
|
1981
1660
|
}
|
|
1982
|
-
if (isMarksPrefix(around.right, around.left) &&
|
|
1983
|
-
around.left.length > around.right.length &&
|
|
1984
|
-
(selection.affinity ?? "forward") === "backward" &&
|
|
1985
|
-
!around.left.some((mark) => mark.kind === markerKind)) {
|
|
1986
|
-
const next = createPendingPlaceholderStateAtCursor(state, caret, [
|
|
1987
|
-
...around.left,
|
|
1988
|
-
markerMark,
|
|
1989
|
-
]);
|
|
1990
|
-
if (next) {
|
|
1991
|
-
return next;
|
|
1992
|
-
}
|
|
1993
|
-
}
|
|
1994
1661
|
// Otherwise, insert an empty marker pair with a zero-width placeholder
|
|
1995
1662
|
// selected so the next typed character replaces it.
|
|
1996
1663
|
//
|
|
@@ -2012,63 +1679,34 @@ export function createRuntimeFromRegistry(registry) {
|
|
|
2012
1679
|
return null;
|
|
2013
1680
|
})();
|
|
2014
1681
|
// When at a boundary between cursor positions (insertAtBackward !== insertAtForward),
|
|
2015
|
-
//
|
|
2016
|
-
//
|
|
2017
|
-
//
|
|
2018
|
-
// Still guard against inserting a longer marker into a shorter boundary run,
|
|
2019
|
-
// which would create ambiguous source (e.g., *italic*****).
|
|
1682
|
+
// prefer insertAtBackward to keep new markers inside the current formatting context.
|
|
1683
|
+
// However, only do this if the new marker length is <= the boundary marker length,
|
|
1684
|
+
// otherwise we create ambiguous marker sequences (e.g., *italic***** doesn't parse).
|
|
2020
1685
|
const betweenLen = insertAtForward - insertAtBackward;
|
|
2021
|
-
const preferBackward = insertAtBackward !== insertAtForward &&
|
|
2022
|
-
(selection.affinity ?? "forward") === "backward" &&
|
|
2023
|
-
openLen <= betweenLen;
|
|
1686
|
+
const preferBackward = insertAtBackward !== insertAtForward && openLen <= betweenLen;
|
|
2024
1687
|
const insertAt = placeholderPos ?? (preferBackward ? insertAtBackward : insertAtForward);
|
|
2025
|
-
const insertMarkerSpec = placeholderPos === null
|
|
2026
|
-
? pickSafeCollapsedToggleMarkerSpec({
|
|
2027
|
-
defaultSpec: markerSpec,
|
|
2028
|
-
source,
|
|
2029
|
-
insertAt,
|
|
2030
|
-
affinity: selection.affinity ?? "forward",
|
|
2031
|
-
})
|
|
2032
|
-
: markerSpec;
|
|
2033
|
-
const insertOpenMarker = insertMarkerSpec.open;
|
|
2034
|
-
const insertCloseMarker = insertMarkerSpec.close;
|
|
2035
|
-
const insertOpenLen = insertOpenMarker.length;
|
|
2036
|
-
const baseMarks = (selection.affinity ?? "forward") === "backward"
|
|
2037
|
-
? around.left
|
|
2038
|
-
: around.right;
|
|
2039
|
-
const nextMarks = [
|
|
2040
|
-
...baseMarks.filter((mark) => mark.kind !== markerKind),
|
|
2041
|
-
markerMark,
|
|
2042
|
-
];
|
|
2043
|
-
if (placeholderPos !== null) {
|
|
2044
|
-
const next = updatePendingPlaceholderMarksAtCursor(state, caret, nextMarks);
|
|
2045
|
-
if (next) {
|
|
2046
|
-
return next;
|
|
2047
|
-
}
|
|
2048
|
-
}
|
|
2049
|
-
const docInserted = createPendingPlaceholderStateAtCursor(state, caret, nextMarks);
|
|
2050
|
-
if (docInserted) {
|
|
2051
|
-
return docInserted;
|
|
2052
|
-
}
|
|
2053
1688
|
const nextSource = placeholderPos !== null
|
|
2054
1689
|
? source.slice(0, insertAt) +
|
|
2055
|
-
|
|
1690
|
+
openMarker +
|
|
2056
1691
|
placeholder +
|
|
2057
|
-
|
|
1692
|
+
closeMarker +
|
|
2058
1693
|
source.slice(insertAt + placeholder.length)
|
|
2059
1694
|
: source.slice(0, insertAt) +
|
|
2060
|
-
|
|
1695
|
+
openMarker +
|
|
2061
1696
|
placeholder +
|
|
2062
|
-
|
|
1697
|
+
closeMarker +
|
|
2063
1698
|
source.slice(insertAt);
|
|
2064
1699
|
const next = createState(nextSource);
|
|
2065
|
-
const placeholderStart = insertAt +
|
|
1700
|
+
const placeholderStart = insertAt + openLen;
|
|
2066
1701
|
const startCursor = next.map.sourceToCursor(placeholderStart, "forward");
|
|
2067
|
-
return
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
1702
|
+
return {
|
|
1703
|
+
...next,
|
|
1704
|
+
selection: {
|
|
1705
|
+
start: startCursor.cursorOffset,
|
|
1706
|
+
end: startCursor.cursorOffset,
|
|
1707
|
+
affinity: "forward",
|
|
1708
|
+
},
|
|
1709
|
+
};
|
|
2072
1710
|
}
|
|
2073
1711
|
const cursorStart = Math.min(selection.start, selection.end);
|
|
2074
1712
|
const cursorEnd = Math.max(selection.start, selection.end);
|
|
@@ -2130,6 +1768,11 @@ export function createRuntimeFromRegistry(registry) {
|
|
|
2130
1768
|
const hasTargetMark = visibleRunsForDecision.some((run) => run.marks.some((mark) => mark.kind === markerKind));
|
|
2131
1769
|
const canUnwrap = hasTargetMark &&
|
|
2132
1770
|
visibleRunsForDecision.every((run) => run.marks.some((mark) => mark.kind === markerKind));
|
|
1771
|
+
const markerMark = {
|
|
1772
|
+
kind: markerKind,
|
|
1773
|
+
data: undefined,
|
|
1774
|
+
key: markKey(markerKind, undefined),
|
|
1775
|
+
};
|
|
2133
1776
|
const removeMark = (marks) => {
|
|
2134
1777
|
if (!marks.some((mark) => mark.kind === markerKind)) {
|
|
2135
1778
|
return marks;
|
|
@@ -2310,18 +1953,12 @@ export function createRuntimeFromRegistry(registry) {
|
|
|
2310
1953
|
// Apply marks in reverse order so outer marks wrap inner marks
|
|
2311
1954
|
const sortedMarks = [...run.marks].reverse();
|
|
2312
1955
|
for (const mark of sortedMarks) {
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
else if (mark.kind === "strikethrough") {
|
|
2320
|
-
content = `<s>${content}</s>`;
|
|
2321
|
-
}
|
|
2322
|
-
else if (mark.kind === "link") {
|
|
2323
|
-
const url = mark.data?.url ?? "";
|
|
2324
|
-
content = `<a href="${escapeHtml(url)}">${content}</a>`;
|
|
1956
|
+
for (const serializeMarkToHtml of inlineHtmlSerializers) {
|
|
1957
|
+
const next = serializeMarkToHtml({ kind: mark.kind, data: mark.data }, content, { escapeHtml });
|
|
1958
|
+
if (next !== null) {
|
|
1959
|
+
content = next;
|
|
1960
|
+
break;
|
|
1961
|
+
}
|
|
2325
1962
|
}
|
|
2326
1963
|
}
|
|
2327
1964
|
html += content;
|
|
@@ -2341,22 +1978,13 @@ export function createRuntimeFromRegistry(registry) {
|
|
|
2341
1978
|
const startLoc = textModel.resolveOffsetToLine(cursorStart);
|
|
2342
1979
|
const endLoc = textModel.resolveOffsetToLine(cursorEnd);
|
|
2343
1980
|
let html = "";
|
|
2344
|
-
let
|
|
2345
|
-
const
|
|
2346
|
-
if (
|
|
2347
|
-
html += `</${activeList.type}>`;
|
|
2348
|
-
activeList = null;
|
|
2349
|
-
}
|
|
2350
|
-
};
|
|
2351
|
-
const openList = (type, indent) => {
|
|
2352
|
-
if (activeList &&
|
|
2353
|
-
activeList.type === type &&
|
|
2354
|
-
activeList.indent === indent) {
|
|
1981
|
+
let activeGroup = null;
|
|
1982
|
+
const closeGroup = () => {
|
|
1983
|
+
if (!activeGroup) {
|
|
2355
1984
|
return;
|
|
2356
1985
|
}
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
activeList = { type, indent };
|
|
1986
|
+
html += activeGroup.close;
|
|
1987
|
+
activeGroup = null;
|
|
2360
1988
|
};
|
|
2361
1989
|
for (let lineIndex = startLoc.lineIndex; lineIndex <= endLoc.lineIndex; lineIndex += 1) {
|
|
2362
1990
|
const line = lines[lineIndex];
|
|
@@ -2373,66 +2001,46 @@ export function createRuntimeFromRegistry(registry) {
|
|
|
2373
2001
|
? endLoc.offsetInLine
|
|
2374
2002
|
: line.cursorLength;
|
|
2375
2003
|
const selectedRuns = sliceRuns(runs, startInLine, endInLine).selected;
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2004
|
+
const lineHtml = runsToHtml(normalizeRuns(selectedRuns));
|
|
2005
|
+
const lineText = runs
|
|
2006
|
+
.map((r) => (r.type === "text" ? r.text : " "))
|
|
2007
|
+
.join("");
|
|
2008
|
+
let wrapperBlock = null;
|
|
2379
2009
|
if (line.path.length > 1) {
|
|
2380
2010
|
const wrapperPath = line.path.slice(0, -1);
|
|
2381
2011
|
const wrapper = getBlockAtPath(state.doc.blocks, wrapperPath);
|
|
2382
2012
|
if (wrapper && wrapper.type === "block-wrapper") {
|
|
2383
|
-
|
|
2384
|
-
wrapperData = wrapper.data;
|
|
2013
|
+
wrapperBlock = wrapper;
|
|
2385
2014
|
}
|
|
2386
2015
|
}
|
|
2387
|
-
|
|
2388
|
-
const
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
else if (wrapperKind === "numbered-list") {
|
|
2416
|
-
openList("ol", 0);
|
|
2417
|
-
html += `<li>${lineHtml}</li>`;
|
|
2418
|
-
}
|
|
2419
|
-
else if (wrapperKind === "blockquote") {
|
|
2420
|
-
closeList();
|
|
2421
|
-
html += `<blockquote>${lineHtml}</blockquote>`;
|
|
2422
|
-
}
|
|
2423
|
-
else if (listMatch) {
|
|
2424
|
-
// Plain paragraph with list markers (cake v3 list model)
|
|
2425
|
-
const isNumbered = /^\d+\.$/.test(listMatch[2]);
|
|
2426
|
-
const indent = Math.floor(listMatch[1].length / 2);
|
|
2427
|
-
openList(isNumbered ? "ol" : "ul", indent);
|
|
2428
|
-
html += `<li>${lineHtml}</li>`;
|
|
2429
|
-
}
|
|
2430
|
-
else {
|
|
2431
|
-
closeList();
|
|
2432
|
-
html += `<div>${lineHtml}</div>`;
|
|
2433
|
-
}
|
|
2434
|
-
}
|
|
2435
|
-
closeList();
|
|
2016
|
+
let lineResult = null;
|
|
2017
|
+
for (const serializeLineToHtml of serializeSelectionLineToHtmlFns) {
|
|
2018
|
+
lineResult = serializeLineToHtml({
|
|
2019
|
+
state,
|
|
2020
|
+
line,
|
|
2021
|
+
block,
|
|
2022
|
+
wrapperBlock,
|
|
2023
|
+
lineText,
|
|
2024
|
+
startInLine,
|
|
2025
|
+
endInLine,
|
|
2026
|
+
lineCursorLength: line.cursorLength,
|
|
2027
|
+
selectedHtml: lineHtml,
|
|
2028
|
+
});
|
|
2029
|
+
if (lineResult) {
|
|
2030
|
+
break;
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
const group = lineResult?.group ?? null;
|
|
2034
|
+
if (!group || !activeGroup || activeGroup.key !== group.key) {
|
|
2035
|
+
closeGroup();
|
|
2036
|
+
if (group) {
|
|
2037
|
+
html += group.open;
|
|
2038
|
+
activeGroup = group;
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
html += lineResult?.html ?? `<div>${lineHtml}</div>`;
|
|
2042
|
+
}
|
|
2043
|
+
closeGroup();
|
|
2436
2044
|
if (!html) {
|
|
2437
2045
|
return "";
|
|
2438
2046
|
}
|