@blankdotpage/cake 0.1.1 → 0.1.3

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/index.cjs CHANGED
@@ -1,44 +1,53 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const require$$0 = require("react");
4
- const reactDom = require("react-dom");
5
4
  const lucideReact = require("lucide-react");
6
5
  const TurndownService = require("turndown");
7
6
  const state = require("@codemirror/state");
8
- var jsxDevRuntime = { exports: {} };
9
- var reactJsxDevRuntime_production_min = {};
7
+ var jsxRuntime = { exports: {} };
8
+ var reactJsxRuntime_production_min = {};
10
9
  /**
11
10
  * @license React
12
- * react-jsx-dev-runtime.production.min.js
11
+ * react-jsx-runtime.production.min.js
13
12
  *
14
13
  * Copyright (c) Facebook, Inc. and its affiliates.
15
14
  *
16
15
  * This source code is licensed under the MIT license found in the
17
16
  * LICENSE file in the root directory of this source tree.
18
17
  */
19
- var hasRequiredReactJsxDevRuntime_production_min;
20
- function requireReactJsxDevRuntime_production_min() {
21
- if (hasRequiredReactJsxDevRuntime_production_min) return reactJsxDevRuntime_production_min;
22
- hasRequiredReactJsxDevRuntime_production_min = 1;
23
- var a = Symbol.for("react.fragment");
24
- reactJsxDevRuntime_production_min.Fragment = a;
25
- reactJsxDevRuntime_production_min.jsxDEV = void 0;
26
- return reactJsxDevRuntime_production_min;
27
- }
28
- var reactJsxDevRuntime_development = {};
18
+ var hasRequiredReactJsxRuntime_production_min;
19
+ function requireReactJsxRuntime_production_min() {
20
+ if (hasRequiredReactJsxRuntime_production_min) return reactJsxRuntime_production_min;
21
+ hasRequiredReactJsxRuntime_production_min = 1;
22
+ var f = require$$0, k = Symbol.for("react.element"), l = Symbol.for("react.fragment"), m = Object.prototype.hasOwnProperty, n = f.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner, p = { key: true, ref: true, __self: true, __source: true };
23
+ function q(c, a, g) {
24
+ var b, d = {}, e = null, h = null;
25
+ void 0 !== g && (e = "" + g);
26
+ void 0 !== a.key && (e = "" + a.key);
27
+ void 0 !== a.ref && (h = a.ref);
28
+ for (b in a) m.call(a, b) && !p.hasOwnProperty(b) && (d[b] = a[b]);
29
+ if (c && c.defaultProps) for (b in a = c.defaultProps, a) void 0 === d[b] && (d[b] = a[b]);
30
+ return { $$typeof: k, type: c, key: e, ref: h, props: d, _owner: n.current };
31
+ }
32
+ reactJsxRuntime_production_min.Fragment = l;
33
+ reactJsxRuntime_production_min.jsx = q;
34
+ reactJsxRuntime_production_min.jsxs = q;
35
+ return reactJsxRuntime_production_min;
36
+ }
37
+ var reactJsxRuntime_development = {};
29
38
  /**
30
39
  * @license React
31
- * react-jsx-dev-runtime.development.js
40
+ * react-jsx-runtime.development.js
32
41
  *
33
42
  * Copyright (c) Facebook, Inc. and its affiliates.
34
43
  *
35
44
  * This source code is licensed under the MIT license found in the
36
45
  * LICENSE file in the root directory of this source tree.
37
46
  */
38
- var hasRequiredReactJsxDevRuntime_development;
39
- function requireReactJsxDevRuntime_development() {
40
- if (hasRequiredReactJsxDevRuntime_development) return reactJsxDevRuntime_development;
41
- hasRequiredReactJsxDevRuntime_development = 1;
47
+ var hasRequiredReactJsxRuntime_development;
48
+ function requireReactJsxRuntime_development() {
49
+ if (hasRequiredReactJsxRuntime_development) return reactJsxRuntime_development;
50
+ hasRequiredReactJsxRuntime_development = 1;
42
51
  if (process.env.NODE_ENV !== "production") {
43
52
  (function() {
44
53
  var React = require$$0;
@@ -525,10 +534,6 @@ function requireReactJsxDevRuntime_development() {
525
534
  };
526
535
  var specialPropKeyWarningShown;
527
536
  var specialPropRefWarningShown;
528
- var didWarnAboutStringRefs;
529
- {
530
- didWarnAboutStringRefs = {};
531
- }
532
537
  function hasValidRef(config) {
533
538
  {
534
539
  if (hasOwnProperty.call(config, "ref")) {
@@ -553,13 +558,7 @@ function requireReactJsxDevRuntime_development() {
553
558
  }
554
559
  function warnIfStringRefCannotBeAutoConverted(config, self) {
555
560
  {
556
- if (typeof config.ref === "string" && ReactCurrentOwner.current && self && ReactCurrentOwner.current.stateNode !== self) {
557
- var componentName = getComponentNameFromType(ReactCurrentOwner.current.type);
558
- if (!didWarnAboutStringRefs[componentName]) {
559
- error('Component "%s" contains the string ref "%s". Support for string refs will be removed in a future major release. This case cannot be automatically converted to an arrow function. We ask you to manually fix this case by using useRef() or createRef() instead. Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref', getComponentNameFromType(ReactCurrentOwner.current.type), config.ref);
560
- didWarnAboutStringRefs[componentName] = true;
561
- }
562
- }
561
+ if (typeof config.ref === "string" && ReactCurrentOwner.current && self) ;
563
562
  }
564
563
  }
565
564
  function defineKeyPropWarningGetter(props, displayName) {
@@ -713,11 +712,6 @@ function requireReactJsxDevRuntime_development() {
713
712
  }
714
713
  function getSourceInfoErrorAddendum(source) {
715
714
  {
716
- if (source !== void 0) {
717
- var fileName = source.fileName.replace(/^.*[\\\/]/, "");
718
- var lineNumber = source.lineNumber;
719
- return "\n\nCheck your code at " + fileName + ":" + lineNumber + ".";
720
- }
721
715
  return "";
722
716
  }
723
717
  }
@@ -843,7 +837,7 @@ function requireReactJsxDevRuntime_development() {
843
837
  if (type === void 0 || typeof type === "object" && type !== null && Object.keys(type).length === 0) {
844
838
  info += " You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.";
845
839
  }
846
- var sourceInfo = getSourceInfoErrorAddendum(source);
840
+ var sourceInfo = getSourceInfoErrorAddendum();
847
841
  if (sourceInfo) {
848
842
  info += sourceInfo;
849
843
  } else {
@@ -907,19 +901,31 @@ function requireReactJsxDevRuntime_development() {
907
901
  return element;
908
902
  }
909
903
  }
910
- var jsxDEV$1 = jsxWithValidation;
911
- reactJsxDevRuntime_development.Fragment = REACT_FRAGMENT_TYPE;
912
- reactJsxDevRuntime_development.jsxDEV = jsxDEV$1;
904
+ function jsxWithValidationStatic(type, props, key) {
905
+ {
906
+ return jsxWithValidation(type, props, key, true);
907
+ }
908
+ }
909
+ function jsxWithValidationDynamic(type, props, key) {
910
+ {
911
+ return jsxWithValidation(type, props, key, false);
912
+ }
913
+ }
914
+ var jsx = jsxWithValidationDynamic;
915
+ var jsxs = jsxWithValidationStatic;
916
+ reactJsxRuntime_development.Fragment = REACT_FRAGMENT_TYPE;
917
+ reactJsxRuntime_development.jsx = jsx;
918
+ reactJsxRuntime_development.jsxs = jsxs;
913
919
  })();
914
920
  }
915
- return reactJsxDevRuntime_development;
921
+ return reactJsxRuntime_development;
916
922
  }
917
923
  if (process.env.NODE_ENV === "production") {
918
- jsxDevRuntime.exports = requireReactJsxDevRuntime_production_min();
924
+ jsxRuntime.exports = requireReactJsxRuntime_production_min();
919
925
  } else {
920
- jsxDevRuntime.exports = requireReactJsxDevRuntime_development();
926
+ jsxRuntime.exports = requireReactJsxRuntime_development();
921
927
  }
922
- var jsxDevRuntimeExports = jsxDevRuntime.exports;
928
+ var jsxRuntimeExports = jsxRuntime.exports;
923
929
  const graphemeSegmenter = new Intl.Segmenter(void 0, {
924
930
  granularity: "grapheme"
925
931
  });
@@ -993,15 +999,36 @@ class CursorSourceBuilder {
993
999
  if (!text) {
994
1000
  return;
995
1001
  }
996
- for (const segment of graphemeSegments(text)) {
997
- this.sourceParts.push(segment.segment);
998
- this.sourceLengthValue += segment.segment.length;
999
- const sourceLength = this.sourceLengthValue;
1000
- this.cursorLength += 1;
1001
- this.boundaries.push({
1002
- sourceBackward: sourceLength,
1003
- sourceForward: sourceLength
1004
- });
1002
+ let isAllAscii = true;
1003
+ for (let i = 0; i < text.length; i++) {
1004
+ if (text.charCodeAt(i) >= 128) {
1005
+ isAllAscii = false;
1006
+ break;
1007
+ }
1008
+ }
1009
+ if (isAllAscii) {
1010
+ for (let i = 0; i < text.length; i++) {
1011
+ const char = text[i];
1012
+ this.sourceParts.push(char);
1013
+ this.sourceLengthValue += 1;
1014
+ const sourceLength = this.sourceLengthValue;
1015
+ this.cursorLength += 1;
1016
+ this.boundaries.push({
1017
+ sourceBackward: sourceLength,
1018
+ sourceForward: sourceLength
1019
+ });
1020
+ }
1021
+ } else {
1022
+ for (const segment of graphemeSegments(text)) {
1023
+ this.sourceParts.push(segment.segment);
1024
+ this.sourceLengthValue += segment.segment.length;
1025
+ const sourceLength = this.sourceLengthValue;
1026
+ this.cursorLength += 1;
1027
+ this.boundaries.push({
1028
+ sourceBackward: sourceLength,
1029
+ sourceForward: sourceLength
1030
+ });
1031
+ }
1005
1032
  }
1006
1033
  }
1007
1034
  appendCursorAtom(sourceText, cursorUnits = 1) {
@@ -1051,6 +1078,15 @@ class CursorSourceBuilder {
1051
1078
  };
1052
1079
  }
1053
1080
  }
1081
+ function isStructuralEdit(command) {
1082
+ return command.type === "insert" || command.type === "delete-backward" || command.type === "delete-forward" || command.type === "insert-line-break" || command.type === "exit-block-wrapper";
1083
+ }
1084
+ function isApplyEditCommand(command) {
1085
+ return command.type === "insert" || command.type === "insert-line-break" || command.type === "delete-backward" || command.type === "delete-forward";
1086
+ }
1087
+ function defineExtension(extension) {
1088
+ return extension;
1089
+ }
1054
1090
  const defaultSelection$1 = { start: 0, end: 0, affinity: "forward" };
1055
1091
  function createRuntime(extensions) {
1056
1092
  const toggleMarkerToKind = /* @__PURE__ */ new Map();
@@ -1292,7 +1328,7 @@ function createRuntime(extensions) {
1292
1328
  }
1293
1329
  }
1294
1330
  const selection = normalizeSelection$1(state2.selection);
1295
- if (command.type === "insert" || command.type === "delete-backward" || command.type === "delete-forward" || command.type === "insert-line-break" || command.type === "exit-block-wrapper") {
1331
+ if (isStructuralEdit(command)) {
1296
1332
  const structural = applyStructuralEdit(command, state2.doc, selection);
1297
1333
  if (!structural) {
1298
1334
  if (command.type === "delete-backward" || command.type === "delete-forward") {
@@ -1334,6 +1370,39 @@ function createRuntime(extensions) {
1334
1370
  }
1335
1371
  };
1336
1372
  }
1373
+ if (command.type === "insert" || command.type === "insert-line-break") {
1374
+ const cursorLength = state2.map.cursorLength;
1375
+ const cursorStart = Math.max(
1376
+ 0,
1377
+ Math.min(cursorLength, Math.min(selection.start, selection.end))
1378
+ );
1379
+ const cursorEnd = Math.max(
1380
+ 0,
1381
+ Math.min(cursorLength, Math.max(selection.start, selection.end))
1382
+ );
1383
+ const range = { start: cursorStart, end: cursorEnd };
1384
+ const fullDocReplace = range.start === 0 && range.end === cursorLength;
1385
+ const from = fullDocReplace ? 0 : state2.map.cursorToSource(range.start, "backward");
1386
+ const to = fullDocReplace ? state2.source.length : state2.map.cursorToSource(range.end, "forward");
1387
+ const fromClamped = Math.max(0, Math.min(from, state2.source.length));
1388
+ const toClamped = Math.max(
1389
+ fromClamped,
1390
+ Math.min(to, state2.source.length)
1391
+ );
1392
+ const insertText = command.type === "insert" ? command.text : "\n";
1393
+ const nextSource = state2.source.slice(0, fromClamped) + insertText + state2.source.slice(toClamped);
1394
+ const next2 = createState(nextSource);
1395
+ const caretSource2 = fromClamped + insertText.length;
1396
+ const caretCursor2 = next2.map.sourceToCursor(caretSource2, "forward");
1397
+ return {
1398
+ ...next2,
1399
+ selection: {
1400
+ start: caretCursor2.cursorOffset,
1401
+ end: caretCursor2.cursorOffset,
1402
+ affinity: caretCursor2.affinity
1403
+ }
1404
+ };
1405
+ }
1337
1406
  return state2;
1338
1407
  }
1339
1408
  const interim = createStateFromDoc(structural.doc);
@@ -2411,13 +2480,150 @@ function createRuntime(extensions) {
2411
2480
  const endInLine = lineIndex === endLoc.lineIndex ? endLoc.offsetInLine : line.cursorLength;
2412
2481
  const selectedRuns = sliceRuns(runs, startInLine, endInLine).selected;
2413
2482
  const content = runsToInlines(normalizeRuns(selectedRuns));
2414
- blocks.push({ type: "paragraph", content });
2483
+ const paragraph = { type: "paragraph", content };
2484
+ if (line.path.length > 1) {
2485
+ const wrapperPath = line.path.slice(0, -1);
2486
+ const wrapper = getBlockAtPath(state2.doc.blocks, wrapperPath);
2487
+ if (wrapper && wrapper.type === "block-wrapper") {
2488
+ blocks.push({
2489
+ type: "block-wrapper",
2490
+ kind: wrapper.kind,
2491
+ data: wrapper.data,
2492
+ blocks: [paragraph]
2493
+ });
2494
+ continue;
2495
+ }
2496
+ }
2497
+ blocks.push(paragraph);
2415
2498
  }
2416
2499
  const sliceDoc = {
2417
2500
  blocks: blocks.length > 0 ? blocks : [{ type: "paragraph", content: [] }]
2418
2501
  };
2419
2502
  return serialize(normalize(sliceDoc)).source;
2420
2503
  }
2504
+ function escapeHtml(text) {
2505
+ return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2506
+ }
2507
+ function runsToHtml(runs) {
2508
+ var _a;
2509
+ let html = "";
2510
+ for (const run of runs) {
2511
+ let content = escapeHtml(run.text);
2512
+ const sortedMarks = [...run.marks].reverse();
2513
+ for (const mark of sortedMarks) {
2514
+ if (mark.kind === "bold") {
2515
+ content = `<strong>${content}</strong>`;
2516
+ } else if (mark.kind === "italic") {
2517
+ content = `<em>${content}</em>`;
2518
+ } else if (mark.kind === "strikethrough") {
2519
+ content = `<s>${content}</s>`;
2520
+ } else if (mark.kind === "link") {
2521
+ const url = ((_a = mark.data) == null ? void 0 : _a.url) ?? "";
2522
+ content = `<a href="${escapeHtml(url)}">${content}</a>`;
2523
+ }
2524
+ }
2525
+ html += content;
2526
+ }
2527
+ return html;
2528
+ }
2529
+ function serializeSelectionToHtml(state2, selection) {
2530
+ const normalized = normalizeSelection$1(selection);
2531
+ const lines = flattenDocToLines(state2.doc);
2532
+ const docCursorLength = cursorLengthForLines(lines);
2533
+ const cursorStart = Math.max(
2534
+ 0,
2535
+ Math.min(docCursorLength, Math.min(normalized.start, normalized.end))
2536
+ );
2537
+ const cursorEnd = Math.max(
2538
+ 0,
2539
+ Math.min(docCursorLength, Math.max(normalized.start, normalized.end))
2540
+ );
2541
+ if (cursorStart === cursorEnd) {
2542
+ return "";
2543
+ }
2544
+ const startLoc = resolveCursorToLine(lines, cursorStart);
2545
+ const endLoc = resolveCursorToLine(lines, cursorEnd);
2546
+ let html = "";
2547
+ let activeList = null;
2548
+ const closeList = () => {
2549
+ if (activeList) {
2550
+ html += `</${activeList.type}>`;
2551
+ activeList = null;
2552
+ }
2553
+ };
2554
+ const openList = (type, indent) => {
2555
+ if (activeList && activeList.type === type && activeList.indent === indent) {
2556
+ return;
2557
+ }
2558
+ closeList();
2559
+ html += `<${type}>`;
2560
+ activeList = { type, indent };
2561
+ };
2562
+ for (let lineIndex = startLoc.lineIndex; lineIndex <= endLoc.lineIndex; lineIndex += 1) {
2563
+ const line = lines[lineIndex];
2564
+ if (!line) {
2565
+ continue;
2566
+ }
2567
+ const block = getBlockAtPath(state2.doc.blocks, line.path);
2568
+ if (!block || block.type !== "paragraph") {
2569
+ continue;
2570
+ }
2571
+ const runs = paragraphToRuns(block);
2572
+ const startInLine = lineIndex === startLoc.lineIndex ? startLoc.offsetInLine : 0;
2573
+ const endInLine = lineIndex === endLoc.lineIndex ? endLoc.offsetInLine : line.cursorLength;
2574
+ const selectedRuns = sliceRuns(runs, startInLine, endInLine).selected;
2575
+ let wrapperKind = null;
2576
+ let wrapperData;
2577
+ if (line.path.length > 1) {
2578
+ const wrapperPath = line.path.slice(0, -1);
2579
+ const wrapper = getBlockAtPath(state2.doc.blocks, wrapperPath);
2580
+ if (wrapper && wrapper.type === "block-wrapper") {
2581
+ wrapperKind = wrapper.kind;
2582
+ wrapperData = wrapper.data;
2583
+ }
2584
+ }
2585
+ const plainText = runs.map((r) => r.text).join("");
2586
+ const listMatch = plainText.match(/^(\s*)([-*+]|\d+\.)( )(.*)$/);
2587
+ let lineHtml;
2588
+ if (listMatch && !wrapperKind) {
2589
+ const prefixLength = listMatch[1].length + listMatch[2].length + listMatch[3].length;
2590
+ const contentRuns = sliceRuns(runs, prefixLength, runs.reduce((sum, r) => sum + r.text.length, 0)).selected;
2591
+ lineHtml = runsToHtml(normalizeRuns(contentRuns));
2592
+ } else {
2593
+ lineHtml = runsToHtml(normalizeRuns(selectedRuns));
2594
+ }
2595
+ if (wrapperKind === "heading") {
2596
+ closeList();
2597
+ const level = Math.min(
2598
+ (wrapperData == null ? void 0 : wrapperData.level) ?? 1,
2599
+ 6
2600
+ );
2601
+ html += `<h${level} style="margin:0">${lineHtml}</h${level}>`;
2602
+ } else if (wrapperKind === "bullet-list") {
2603
+ openList("ul", 0);
2604
+ html += `<li>${lineHtml}</li>`;
2605
+ } else if (wrapperKind === "numbered-list") {
2606
+ openList("ol", 0);
2607
+ html += `<li>${lineHtml}</li>`;
2608
+ } else if (wrapperKind === "blockquote") {
2609
+ closeList();
2610
+ html += `<blockquote>${lineHtml}</blockquote>`;
2611
+ } else if (listMatch) {
2612
+ const isNumbered = /^\d+\.$/.test(listMatch[2]);
2613
+ const indent = Math.floor(listMatch[1].length / 2);
2614
+ openList(isNumbered ? "ol" : "ul", indent);
2615
+ html += `<li>${lineHtml}</li>`;
2616
+ } else {
2617
+ closeList();
2618
+ html += `<div>${lineHtml}</div>`;
2619
+ }
2620
+ }
2621
+ closeList();
2622
+ if (!html) {
2623
+ return "";
2624
+ }
2625
+ return `<div>${html}</div>`;
2626
+ }
2421
2627
  const runtime = {
2422
2628
  extensions,
2423
2629
  parse,
@@ -2425,6 +2631,7 @@ function createRuntime(extensions) {
2425
2631
  createState,
2426
2632
  updateSelection,
2427
2633
  serializeSelection,
2634
+ serializeSelectionToHtml,
2428
2635
  applyEdit
2429
2636
  };
2430
2637
  return runtime;
@@ -2438,7 +2645,20 @@ function parseLiteralBlock(source, start, context) {
2438
2645
  return { block: { type: "paragraph", content }, nextPos: end };
2439
2646
  }
2440
2647
  function parseLiteralInline(source, start, end) {
2441
- const segment = graphemeSegments(source.slice(start, end))[0];
2648
+ const code = source.charCodeAt(start);
2649
+ if (code < 128) {
2650
+ const text2 = source[start] ?? "";
2651
+ return { inline: { type: "text", text: text2 }, nextPos: start + 1 };
2652
+ }
2653
+ if (code >= 55296 && code <= 56319) {
2654
+ const lowCode = source.charCodeAt(start + 1);
2655
+ if (lowCode >= 56320 && lowCode <= 57343) {
2656
+ const segment2 = graphemeSegments(source.slice(start, Math.min(start + 10, end)))[0];
2657
+ const text2 = segment2 ? segment2.segment : source.slice(start, start + 2);
2658
+ return { inline: { type: "text", text: text2 }, nextPos: start + text2.length };
2659
+ }
2660
+ }
2661
+ const segment = graphemeSegments(source.slice(start, Math.min(start + 10, end)))[0];
2442
2662
  const text = segment ? segment.segment : source[start] ?? "";
2443
2663
  return { inline: { type: "text", text }, nextPos: start + text.length };
2444
2664
  }
@@ -2481,29 +2701,66 @@ function normalizeSelection$1(selection) {
2481
2701
  affinity: isRange ? "backward" : selection.affinity
2482
2702
  };
2483
2703
  }
2704
+ const graphemeCache = /* @__PURE__ */ new WeakMap();
2484
2705
  function createTextRun(node, cursorStart) {
2485
- const segments = graphemeSegments(node.data);
2706
+ const data = node.data;
2707
+ const cached = graphemeCache.get(node);
2708
+ if (cached && cached.data === data) {
2709
+ const segmentCount = Math.max(0, cached.boundaryOffsets.length - 1);
2710
+ return {
2711
+ node,
2712
+ cursorStart,
2713
+ cursorEnd: cursorStart + segmentCount,
2714
+ boundaryOffsets: cached.boundaryOffsets
2715
+ };
2716
+ }
2717
+ const segments = graphemeSegments(data);
2486
2718
  const boundaryOffsets = [0];
2487
2719
  for (const segment of segments) {
2488
2720
  boundaryOffsets.push(segment.index + segment.segment.length);
2489
2721
  }
2490
2722
  const cursorEnd = cursorStart + segments.length;
2723
+ graphemeCache.set(node, { data, boundaryOffsets });
2491
2724
  return { node, cursorStart, cursorEnd, boundaryOffsets };
2492
2725
  }
2493
2726
  function boundaryIndexForOffset(boundaryOffsets, offset) {
2494
2727
  if (offset <= 0) {
2495
2728
  return 0;
2496
2729
  }
2497
- for (let i = 1; i < boundaryOffsets.length; i += 1) {
2498
- const boundary = boundaryOffsets[i];
2499
- if (offset < boundary) {
2500
- return i - 1;
2730
+ const lastIndex = boundaryOffsets.length - 1;
2731
+ if (lastIndex <= 0) {
2732
+ return 0;
2733
+ }
2734
+ const lastBoundary = boundaryOffsets[lastIndex];
2735
+ if (offset >= lastBoundary) {
2736
+ return lastIndex;
2737
+ }
2738
+ let low = 1;
2739
+ let high = lastIndex;
2740
+ while (low < high) {
2741
+ const mid = low + high >>> 1;
2742
+ const boundary2 = boundaryOffsets[mid];
2743
+ if (boundary2 < offset) {
2744
+ low = mid + 1;
2745
+ } else {
2746
+ high = mid;
2501
2747
  }
2502
- if (offset === boundary) {
2503
- return i;
2748
+ }
2749
+ const boundary = boundaryOffsets[low];
2750
+ return boundary === offset ? low : low - 1;
2751
+ }
2752
+ function firstRunStartingAfterCursor(runs, cursorOffset) {
2753
+ let low = 0;
2754
+ let high = runs.length;
2755
+ while (low < high) {
2756
+ const mid = low + high >>> 1;
2757
+ if (runs[mid].cursorStart <= cursorOffset) {
2758
+ low = mid + 1;
2759
+ } else {
2760
+ high = mid;
2504
2761
  }
2505
2762
  }
2506
- return boundaryOffsets.length - 1;
2763
+ return low;
2507
2764
  }
2508
2765
  function createDomMap(runs) {
2509
2766
  const runForNode = /* @__PURE__ */ new Map();
@@ -2518,33 +2775,34 @@ function createDomMap(runs) {
2518
2775
  if (runs.length === 0) {
2519
2776
  return null;
2520
2777
  }
2521
- for (let i = 0; i < runs.length; i += 1) {
2522
- const run = runs[i];
2523
- const previous = i > 0 ? runs[i - 1] : null;
2524
- const next = i + 1 < runs.length ? runs[i + 1] : null;
2525
- if (cursorOffset < run.cursorStart) {
2526
- if (!previous) {
2527
- return { node: run.node, offset: run.boundaryOffsets[0] };
2528
- }
2529
- const previousOffset = previous.boundaryOffsets[previous.boundaryOffsets.length - 1];
2530
- return affinity === "backward" ? { node: previous.node, offset: previousOffset } : { node: run.node, offset: run.boundaryOffsets[0] };
2531
- }
2532
- if (cursorOffset === run.cursorStart && previous && affinity === "backward") {
2533
- return {
2534
- node: previous.node,
2535
- offset: previous.boundaryOffsets[previous.boundaryOffsets.length - 1]
2536
- };
2537
- }
2538
- if (cursorOffset <= run.cursorEnd) {
2539
- if (cursorOffset === run.cursorEnd && affinity === "forward" && next && next.cursorStart === cursorOffset) {
2540
- return { node: next.node, offset: next.boundaryOffsets[0] };
2541
- }
2542
- const index = Math.max(0, cursorOffset - run.cursorStart);
2543
- const boundedIndex = Math.min(index, run.boundaryOffsets.length - 1);
2544
- return { node: run.node, offset: run.boundaryOffsets[boundedIndex] };
2778
+ const nextIndex = firstRunStartingAfterCursor(runs, cursorOffset);
2779
+ const runIndex = nextIndex - 1;
2780
+ if (runIndex < 0) {
2781
+ const first = runs[0];
2782
+ return { node: first.node, offset: first.boundaryOffsets[0] };
2783
+ }
2784
+ const run = runs[runIndex];
2785
+ const previous = runIndex > 0 ? runs[runIndex - 1] : null;
2786
+ const next = nextIndex < runs.length ? runs[nextIndex] : null;
2787
+ if (cursorOffset === run.cursorStart && previous && affinity === "backward") {
2788
+ return {
2789
+ node: previous.node,
2790
+ offset: previous.boundaryOffsets[previous.boundaryOffsets.length - 1]
2791
+ };
2792
+ }
2793
+ if (cursorOffset <= run.cursorEnd) {
2794
+ if (cursorOffset === run.cursorEnd && affinity === "forward" && next && next.cursorStart === cursorOffset) {
2795
+ return { node: next.node, offset: next.boundaryOffsets[0] };
2545
2796
  }
2797
+ const index = Math.max(0, cursorOffset - run.cursorStart);
2798
+ const boundedIndex = Math.min(index, run.boundaryOffsets.length - 1);
2799
+ return { node: run.node, offset: run.boundaryOffsets[boundedIndex] };
2546
2800
  }
2547
- const last = runs[runs.length - 1];
2801
+ if (next) {
2802
+ const runEndOffset = run.boundaryOffsets[run.boundaryOffsets.length - 1];
2803
+ return affinity === "backward" ? { node: run.node, offset: runEndOffset } : { node: next.node, offset: next.boundaryOffsets[0] };
2804
+ }
2805
+ const last = run;
2548
2806
  return {
2549
2807
  node: last.node,
2550
2808
  offset: last.boundaryOffsets[last.boundaryOffsets.length - 1]
@@ -2579,7 +2837,7 @@ function normalizeNodes(result) {
2579
2837
  }
2580
2838
  return Array.isArray(result) ? result : [result];
2581
2839
  }
2582
- function renderDocContent(doc, extensions, _root) {
2840
+ function renderDocContent(doc, extensions, root) {
2583
2841
  const runs = [];
2584
2842
  let cursorOffset = 0;
2585
2843
  let lineIndex = 0;
@@ -2599,7 +2857,60 @@ function renderDocContent(doc, extensions, _root) {
2599
2857
  lineIndex += 1;
2600
2858
  }
2601
2859
  };
2602
- function renderInline(inline) {
2860
+ function getBlockKey(block) {
2861
+ if (block.type === "paragraph") {
2862
+ return "paragraph";
2863
+ }
2864
+ if (block.type === "block-wrapper") {
2865
+ return `block-wrapper:${block.kind}`;
2866
+ }
2867
+ if (block.type === "block-atom") {
2868
+ return `block-atom:${block.kind}`;
2869
+ }
2870
+ return "unknown";
2871
+ }
2872
+ function getElementKey(element) {
2873
+ if (element.hasAttribute("data-block")) {
2874
+ const blockType = element.getAttribute("data-block") ?? "unknown";
2875
+ const lineKind = element instanceof HTMLElement ? element.dataset.lineKind : null;
2876
+ if (lineKind && lineKind !== blockType) {
2877
+ return lineKind;
2878
+ }
2879
+ return blockType;
2880
+ }
2881
+ if (element.hasAttribute("data-block-wrapper")) {
2882
+ return `block-wrapper:${element.getAttribute("data-block-wrapper")}`;
2883
+ }
2884
+ if (element.hasAttribute("data-block-atom")) {
2885
+ return `block-atom:${element.getAttribute("data-block-atom")}`;
2886
+ }
2887
+ return "unknown";
2888
+ }
2889
+ function getInlineKey(inline) {
2890
+ if (inline.type === "text") {
2891
+ return "text";
2892
+ }
2893
+ if (inline.type === "inline-wrapper") {
2894
+ return `inline-wrapper:${inline.kind}`;
2895
+ }
2896
+ if (inline.type === "inline-atom") {
2897
+ return `inline-atom:${inline.kind}`;
2898
+ }
2899
+ return "unknown";
2900
+ }
2901
+ function getInlineElementKey(element) {
2902
+ if (element.classList.contains("cake-text")) {
2903
+ return "text";
2904
+ }
2905
+ if (element.hasAttribute("data-inline")) {
2906
+ return `inline-wrapper:${element.getAttribute("data-inline")}`;
2907
+ }
2908
+ if (element.hasAttribute("data-inline-atom")) {
2909
+ return `inline-atom:${element.getAttribute("data-inline-atom")}`;
2910
+ }
2911
+ return "unknown";
2912
+ }
2913
+ function reconcileInline(inline, existing) {
2603
2914
  for (const extension of extensions) {
2604
2915
  const render = extension.renderInline;
2605
2916
  if (!render) {
@@ -2611,6 +2922,17 @@ function renderDocContent(doc, extensions, _root) {
2611
2922
  }
2612
2923
  }
2613
2924
  if (inline.type === "text") {
2925
+ const canReuse = existing && existing instanceof HTMLSpanElement && getInlineElementKey(existing) === "text";
2926
+ if (canReuse) {
2927
+ const textNode = existing.firstChild;
2928
+ if (textNode instanceof Text) {
2929
+ if (textNode.textContent !== inline.text) {
2930
+ textNode.textContent = inline.text;
2931
+ }
2932
+ createTextRun$1(textNode);
2933
+ return [existing];
2934
+ }
2935
+ }
2614
2936
  const element = document.createElement("span");
2615
2937
  element.className = "cake-text";
2616
2938
  const node = document.createTextNode(inline.text);
@@ -2619,16 +2941,29 @@ function renderDocContent(doc, extensions, _root) {
2619
2941
  return [element];
2620
2942
  }
2621
2943
  if (inline.type === "inline-wrapper") {
2944
+ const canReuse = existing && existing instanceof HTMLSpanElement && getInlineElementKey(existing) === getInlineKey(inline);
2945
+ if (canReuse) {
2946
+ reconcileInlineChildren(existing, inline.children);
2947
+ return [existing];
2948
+ }
2622
2949
  const element = document.createElement("span");
2623
2950
  element.setAttribute("data-inline", inline.kind);
2624
2951
  for (const child of inline.children) {
2625
- for (const node of renderInline(child)) {
2952
+ for (const node of reconcileInline(child, null)) {
2626
2953
  element.append(node);
2627
2954
  }
2628
2955
  }
2629
2956
  return [element];
2630
2957
  }
2631
2958
  if (inline.type === "inline-atom") {
2959
+ const canReuse = existing && existing instanceof HTMLSpanElement && getInlineElementKey(existing) === getInlineKey(inline);
2960
+ if (canReuse) {
2961
+ const textNode = existing.firstChild;
2962
+ if (textNode instanceof Text) {
2963
+ createTextRun$1(textNode);
2964
+ return [existing];
2965
+ }
2966
+ }
2632
2967
  const element = document.createElement("span");
2633
2968
  element.setAttribute("data-inline-atom", inline.kind);
2634
2969
  const node = document.createTextNode(" ");
@@ -2638,7 +2973,25 @@ function renderDocContent(doc, extensions, _root) {
2638
2973
  }
2639
2974
  return [];
2640
2975
  }
2641
- function renderBlock(block) {
2976
+ function reconcileInlineChildren(parent, inlines) {
2977
+ const mergedInlines = mergeInlineForRender(inlines);
2978
+ const existingChildren2 = Array.from(parent.children);
2979
+ const newChildren = [];
2980
+ mergedInlines.forEach((inline, i) => {
2981
+ const existingChild = existingChildren2[i] ?? null;
2982
+ const canReuse = existingChild && getInlineElementKey(existingChild) === getInlineKey(inline);
2983
+ const nodes = reconcileInline(inline, canReuse ? existingChild : null);
2984
+ newChildren.push(...nodes);
2985
+ });
2986
+ if (newChildren.length === existingChildren2.length && newChildren.every((node, i) => node === existingChildren2[i])) {
2987
+ return;
2988
+ }
2989
+ parent.replaceChildren(...newChildren);
2990
+ }
2991
+ function renderInline(inline) {
2992
+ return reconcileInline(inline, null);
2993
+ }
2994
+ function reconcileBlock(block, existing) {
2642
2995
  for (const extension of extensions) {
2643
2996
  const render = extension.renderBlock;
2644
2997
  if (!render) {
@@ -2650,12 +3003,35 @@ function renderDocContent(doc, extensions, _root) {
2650
3003
  }
2651
3004
  }
2652
3005
  if (block.type === "paragraph") {
3006
+ const canReuse = existing && existing instanceof HTMLDivElement && getElementKey(existing) === "paragraph";
3007
+ const currentLineIndex = context.getLineIndex();
3008
+ context.incrementLineIndex();
3009
+ if (canReuse) {
3010
+ existing.setAttribute("data-line-index", String(currentLineIndex));
3011
+ if (block.content.length === 0) {
3012
+ const firstChild = existing.firstChild;
3013
+ if (firstChild instanceof Text && existing.querySelector("br")) {
3014
+ if (firstChild.textContent !== "") {
3015
+ firstChild.textContent = "";
3016
+ }
3017
+ createTextRun$1(firstChild);
3018
+ return [existing];
3019
+ }
3020
+ existing.replaceChildren();
3021
+ const textNode = document.createTextNode("");
3022
+ createTextRun$1(textNode);
3023
+ existing.append(textNode);
3024
+ existing.append(document.createElement("br"));
3025
+ return [existing];
3026
+ }
3027
+ reconcileInlineChildren(existing, block.content);
3028
+ return [existing];
3029
+ }
2653
3030
  const element = document.createElement("div");
2654
3031
  element.setAttribute("data-block", "paragraph");
2655
- element.setAttribute("data-line-index", String(context.getLineIndex()));
3032
+ element.setAttribute("data-line-index", String(currentLineIndex));
2656
3033
  element.classList.add("cake-line");
2657
3034
  element.dataset.lineKind = "paragraph";
2658
- context.incrementLineIndex();
2659
3035
  if (block.content.length === 0) {
2660
3036
  const textNode = document.createTextNode("");
2661
3037
  createTextRun$1(textNode);
@@ -2664,7 +3040,7 @@ function renderDocContent(doc, extensions, _root) {
2664
3040
  } else {
2665
3041
  const mergedContent = mergeInlineForRender(block.content);
2666
3042
  for (const inline of mergedContent) {
2667
- for (const node of renderInline(inline)) {
3043
+ for (const node of reconcileInline(inline, null)) {
2668
3044
  element.append(node);
2669
3045
  }
2670
3046
  }
@@ -2672,6 +3048,11 @@ function renderDocContent(doc, extensions, _root) {
2672
3048
  return [element];
2673
3049
  }
2674
3050
  if (block.type === "block-wrapper") {
3051
+ const canReuse = existing && existing instanceof HTMLDivElement && getElementKey(existing) === getBlockKey(block);
3052
+ if (canReuse) {
3053
+ reconcileBlockChildren(existing, block.blocks);
3054
+ return [existing];
3055
+ }
2675
3056
  const element = document.createElement("div");
2676
3057
  element.setAttribute("data-block-wrapper", block.kind);
2677
3058
  for (const node of renderBlocks(block.blocks)) {
@@ -2680,15 +3061,41 @@ function renderDocContent(doc, extensions, _root) {
2680
3061
  return [element];
2681
3062
  }
2682
3063
  if (block.type === "block-atom") {
3064
+ const canReuse = existing && existing instanceof HTMLDivElement && getElementKey(existing) === getBlockKey(block);
3065
+ const currentLineIndex = context.getLineIndex();
3066
+ context.incrementLineIndex();
3067
+ if (canReuse) {
3068
+ existing.setAttribute("data-line-index", String(currentLineIndex));
3069
+ return [existing];
3070
+ }
2683
3071
  const element = document.createElement("div");
2684
3072
  element.setAttribute("data-block-atom", block.kind);
2685
- element.setAttribute("data-line-index", String(context.getLineIndex()));
3073
+ element.setAttribute("data-line-index", String(currentLineIndex));
2686
3074
  element.classList.add("cake-line");
2687
- context.incrementLineIndex();
2688
3075
  return [element];
2689
3076
  }
2690
3077
  return [];
2691
3078
  }
3079
+ function reconcileBlockChildren(parent, blocks) {
3080
+ const existingChildren2 = Array.from(parent.children);
3081
+ const newChildren = [];
3082
+ blocks.forEach((block, index) => {
3083
+ const existingChild = existingChildren2[index] ?? null;
3084
+ const canReuse = existingChild && getElementKey(existingChild) === getBlockKey(block);
3085
+ const nodes = reconcileBlock(block, canReuse ? existingChild : null);
3086
+ newChildren.push(...nodes);
3087
+ if (index < blocks.length - 1) {
3088
+ cursorOffset += 1;
3089
+ }
3090
+ });
3091
+ if (newChildren.length === existingChildren2.length && newChildren.every((node, i) => node === existingChildren2[i])) {
3092
+ return;
3093
+ }
3094
+ parent.replaceChildren(...newChildren);
3095
+ }
3096
+ function renderBlock(block) {
3097
+ return reconcileBlock(block, null);
3098
+ }
2692
3099
  function renderBlocks(blocks) {
2693
3100
  const nodes = [];
2694
3101
  blocks.forEach((block, index) => {
@@ -2699,10 +3106,17 @@ function renderDocContent(doc, extensions, _root) {
2699
3106
  });
2700
3107
  return nodes;
2701
3108
  }
3109
+ const existingChildren = root ? Array.from(root.children) : [];
2702
3110
  const contentNodes = [];
2703
- for (const node of renderBlocks(doc.blocks)) {
2704
- contentNodes.push(node);
2705
- }
3111
+ doc.blocks.forEach((block, index) => {
3112
+ const existingChild = existingChildren[index] ?? null;
3113
+ const canReuse = existingChild && getElementKey(existingChild) === getBlockKey(block);
3114
+ const nodes = reconcileBlock(block, canReuse ? existingChild : null);
3115
+ contentNodes.push(...nodes);
3116
+ if (index < doc.blocks.length - 1) {
3117
+ cursorOffset += 1;
3118
+ }
3119
+ });
2706
3120
  return { content: contentNodes, map: createDomMap(runs) };
2707
3121
  }
2708
3122
  function mergeInlineForRender(inlines) {
@@ -2776,7 +3190,9 @@ function applyDomSelection(selection, map) {
2776
3190
  if (!anchorPoint || !focusPoint) {
2777
3191
  return;
2778
3192
  }
2779
- domSelection.removeAllRanges();
3193
+ if (domSelection.rangeCount > 0 && domSelection.anchorNode === anchorPoint.node && domSelection.anchorOffset === anchorPoint.offset && domSelection.focusNode === focusPoint.node && domSelection.focusOffset === focusPoint.offset) {
3194
+ return;
3195
+ }
2780
3196
  if (isCollapsed) {
2781
3197
  domSelection.collapse(anchorPoint.node, anchorPoint.offset);
2782
3198
  return;
@@ -2796,6 +3212,7 @@ function applyDomSelection(selection, map) {
2796
3212
  domSelection.extend(focusPoint.node, focusPoint.offset);
2797
3213
  return;
2798
3214
  }
3215
+ domSelection.removeAllRanges();
2799
3216
  const range = document.createRange();
2800
3217
  const rangeStart = isForward ? anchorPoint : focusPoint;
2801
3218
  const rangeEnd = isForward ? focusPoint : anchorPoint;
@@ -2939,7 +3356,7 @@ function findTextNodeAtOrBefore$1(nodes, start) {
2939
3356
  return null;
2940
3357
  }
2941
3358
  const BOLD_KIND$1 = "bold";
2942
- const boldExtension = {
3359
+ const boldExtension = defineExtension({
2943
3360
  name: "bold",
2944
3361
  toggleInline: { kind: BOLD_KIND$1, markers: ["**"] },
2945
3362
  keybindings: [
@@ -3022,7 +3439,7 @@ const boldExtension = {
3022
3439
  }
3023
3440
  return element;
3024
3441
  }
3025
- };
3442
+ });
3026
3443
  function countSingleAsterisks(source, start, end) {
3027
3444
  let count = 0;
3028
3445
  for (let i = start; i < end; i += 1) {
@@ -3041,7 +3458,7 @@ function countSingleAsterisks(source, start, end) {
3041
3458
  const BOLD_KIND = "bold";
3042
3459
  const ITALIC_KIND$1 = "italic";
3043
3460
  const MARKERS = ["***", "___"];
3044
- const combinedEmphasisExtension = {
3461
+ const combinedEmphasisExtension = defineExtension({
3045
3462
  name: "combined-emphasis",
3046
3463
  parseInline(source, start, end, context) {
3047
3464
  const marker = MARKERS.find((m) => source.slice(start, start + 3) === m);
@@ -3075,7 +3492,7 @@ const combinedEmphasisExtension = {
3075
3492
  nextPos: close + 3
3076
3493
  };
3077
3494
  }
3078
- };
3495
+ });
3079
3496
  function ensureHttpsProtocol(url) {
3080
3497
  const trimmedUrl = url.trim();
3081
3498
  if (!trimmedUrl) {
@@ -3123,7 +3540,7 @@ function getPopoverPosition(params) {
3123
3540
  };
3124
3541
  }
3125
3542
  function CakeLinkPopover(params) {
3126
- const { container, contentRoot, toOverlayRect } = params;
3543
+ const { container, contentRoot, toOverlayRect, getSelection, executeCommand } = params;
3127
3544
  const anchorRef = require$$0.useRef(null);
3128
3545
  const popoverRef = require$$0.useRef(null);
3129
3546
  const inputRef = require$$0.useRef(null);
@@ -3224,13 +3641,13 @@ function CakeLinkPopover(params) {
3224
3641
  if (state2.status !== "open") {
3225
3642
  return;
3226
3643
  }
3227
- container.addEventListener("scroll", reposition, { passive: true });
3644
+ container.addEventListener("scroll", close, { passive: true });
3228
3645
  window.addEventListener("resize", reposition);
3229
3646
  return () => {
3230
- container.removeEventListener("scroll", reposition);
3647
+ container.removeEventListener("scroll", close);
3231
3648
  window.removeEventListener("resize", reposition);
3232
3649
  };
3233
- }, [container, reposition, state2.status]);
3650
+ }, [close, container, reposition, state2.status]);
3234
3651
  const handleMouseDown = require$$0.useCallback(
3235
3652
  (event) => {
3236
3653
  event.stopPropagation();
@@ -3287,6 +3704,12 @@ function CakeLinkPopover(params) {
3287
3704
  window.open(displayUrl, "_blank", "noopener,noreferrer");
3288
3705
  };
3289
3706
  const handleUnlink = () => {
3707
+ const selection = getSelection();
3708
+ if (!selection) {
3709
+ close();
3710
+ return;
3711
+ }
3712
+ executeCommand({ type: "unlink", start: selection.start, end: selection.end });
3290
3713
  close();
3291
3714
  };
3292
3715
  const handleInputKeyDown = (event) => {
@@ -3299,7 +3722,7 @@ function CakeLinkPopover(params) {
3299
3722
  handleCancel();
3300
3723
  }
3301
3724
  };
3302
- return /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(
3725
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
3303
3726
  "div",
3304
3727
  {
3305
3728
  className: "cake-link-popover",
@@ -3312,7 +3735,7 @@ function CakeLinkPopover(params) {
3312
3735
  },
3313
3736
  onMouseDown: handleMouseDown,
3314
3737
  onClick: (event) => event.stopPropagation(),
3315
- children: state2.isEditing ? /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(
3738
+ children: state2.isEditing ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
3316
3739
  "form",
3317
3740
  {
3318
3741
  className: "cake-link-editor",
@@ -3321,7 +3744,7 @@ function CakeLinkPopover(params) {
3321
3744
  handleSave();
3322
3745
  },
3323
3746
  children: [
3324
- /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(
3747
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3325
3748
  "input",
3326
3749
  {
3327
3750
  className: "cake-link-input",
@@ -3338,56 +3761,24 @@ function CakeLinkPopover(params) {
3338
3761
  },
3339
3762
  onKeyDown: handleInputKeyDown,
3340
3763
  placeholder: "https://"
3341
- },
3342
- void 0,
3343
- false,
3344
- {
3345
- fileName: "/Users/moboudra/dev/blankpage/cake/src/cake/extensions/link/link-popover.tsx",
3346
- lineNumber: 281,
3347
- columnNumber: 11
3348
- },
3349
- this
3764
+ }
3350
3765
  ),
3351
- /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV("button", { type: "submit", className: "cake-link-save", children: "Save" }, void 0, false, {
3352
- fileName: "/Users/moboudra/dev/blankpage/cake/src/cake/extensions/link/link-popover.tsx",
3353
- lineNumber: 297,
3354
- columnNumber: 11
3355
- }, this),
3356
- /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(
3766
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "submit", className: "cake-link-save", children: "Save" }),
3767
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3357
3768
  "button",
3358
3769
  {
3359
3770
  type: "button",
3360
3771
  className: "cake-link-cancel",
3361
3772
  onClick: handleCancel,
3362
3773
  children: "Cancel"
3363
- },
3364
- void 0,
3365
- false,
3366
- {
3367
- fileName: "/Users/moboudra/dev/blankpage/cake/src/cake/extensions/link/link-popover.tsx",
3368
- lineNumber: 300,
3369
- columnNumber: 11
3370
- },
3371
- this
3774
+ }
3372
3775
  )
3373
3776
  ]
3374
- },
3375
- void 0,
3376
- true,
3377
- {
3378
- fileName: "/Users/moboudra/dev/blankpage/cake/src/cake/extensions/link/link-popover.tsx",
3379
- lineNumber: 274,
3380
- columnNumber: 9
3381
- },
3382
- this
3383
- ) : /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(jsxDevRuntimeExports.Fragment, { children: [
3384
- /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV("div", { className: "cake-link-url", title: displayUrl, children: displayUrl }, void 0, false, {
3385
- fileName: "/Users/moboudra/dev/blankpage/cake/src/cake/extensions/link/link-popover.tsx",
3386
- lineNumber: 310,
3387
- columnNumber: 11
3388
- }, this),
3389
- /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV("div", { className: "cake-link-actions", children: [
3390
- /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(
3777
+ }
3778
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3779
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "cake-link-url", title: displayUrl, children: displayUrl }),
3780
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "cake-link-actions", children: [
3781
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3391
3782
  "button",
3392
3783
  {
3393
3784
  type: "button",
@@ -3395,22 +3786,10 @@ function CakeLinkPopover(params) {
3395
3786
  onClick: handleEdit,
3396
3787
  title: "Edit link",
3397
3788
  "aria-label": "Edit link",
3398
- children: /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(lucideReact.Pencil, { className: "cake-link-icon" }, void 0, false, {
3399
- fileName: "/Users/moboudra/dev/blankpage/cake/src/cake/extensions/link/link-popover.tsx",
3400
- lineNumber: 321,
3401
- columnNumber: 15
3402
- }, this)
3403
- },
3404
- void 0,
3405
- false,
3406
- {
3407
- fileName: "/Users/moboudra/dev/blankpage/cake/src/cake/extensions/link/link-popover.tsx",
3408
- lineNumber: 314,
3409
- columnNumber: 13
3410
- },
3411
- this
3789
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(lucideReact.Pencil, { className: "cake-link-icon" })
3790
+ }
3412
3791
  ),
3413
- /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(
3792
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3414
3793
  "button",
3415
3794
  {
3416
3795
  type: "button",
@@ -3418,22 +3797,10 @@ function CakeLinkPopover(params) {
3418
3797
  onClick: handleOpen,
3419
3798
  title: "Open link",
3420
3799
  "aria-label": "Open link",
3421
- children: /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(lucideReact.ExternalLink, { className: "cake-link-icon" }, void 0, false, {
3422
- fileName: "/Users/moboudra/dev/blankpage/cake/src/cake/extensions/link/link-popover.tsx",
3423
- lineNumber: 330,
3424
- columnNumber: 15
3425
- }, this)
3426
- },
3427
- void 0,
3428
- false,
3429
- {
3430
- fileName: "/Users/moboudra/dev/blankpage/cake/src/cake/extensions/link/link-popover.tsx",
3431
- lineNumber: 323,
3432
- columnNumber: 13
3433
- },
3434
- this
3800
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(lucideReact.ExternalLink, { className: "cake-link-icon" })
3801
+ }
3435
3802
  ),
3436
- /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(
3803
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3437
3804
  "button",
3438
3805
  {
3439
3806
  type: "button",
@@ -3441,40 +3808,12 @@ function CakeLinkPopover(params) {
3441
3808
  onClick: handleUnlink,
3442
3809
  title: "Remove link",
3443
3810
  "aria-label": "Remove link",
3444
- children: /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(lucideReact.Unlink, { className: "cake-link-icon" }, void 0, false, {
3445
- fileName: "/Users/moboudra/dev/blankpage/cake/src/cake/extensions/link/link-popover.tsx",
3446
- lineNumber: 339,
3447
- columnNumber: 15
3448
- }, this)
3449
- },
3450
- void 0,
3451
- false,
3452
- {
3453
- fileName: "/Users/moboudra/dev/blankpage/cake/src/cake/extensions/link/link-popover.tsx",
3454
- lineNumber: 332,
3455
- columnNumber: 13
3456
- },
3457
- this
3811
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(lucideReact.Unlink, { className: "cake-link-icon" })
3812
+ }
3458
3813
  )
3459
- ] }, void 0, true, {
3460
- fileName: "/Users/moboudra/dev/blankpage/cake/src/cake/extensions/link/link-popover.tsx",
3461
- lineNumber: 313,
3462
- columnNumber: 11
3463
- }, this)
3464
- ] }, void 0, true, {
3465
- fileName: "/Users/moboudra/dev/blankpage/cake/src/cake/extensions/link/link-popover.tsx",
3466
- lineNumber: 309,
3467
- columnNumber: 9
3468
- }, this)
3469
- },
3470
- void 0,
3471
- false,
3472
- {
3473
- fileName: "/Users/moboudra/dev/blankpage/cake/src/cake/extensions/link/link-popover.tsx",
3474
- lineNumber: 261,
3475
- columnNumber: 5
3476
- },
3477
- this
3814
+ ] })
3815
+ ] })
3816
+ }
3478
3817
  );
3479
3818
  }
3480
3819
  function buildLayoutModel(lines, measurer) {
@@ -3674,7 +4013,7 @@ function cursorOffsetToVisibleOffset(lines, cursorOffset) {
3674
4013
  return codeUnitIndex + (lastLine.cursorToCodeUnit[lastOffset] ?? lastLine.text.length);
3675
4014
  }
3676
4015
  const LINK_KIND = "link";
3677
- const linkExtension = {
4016
+ const linkExtension = defineExtension({
3678
4017
  name: "link",
3679
4018
  inlineWrapperAffinity: [{ kind: LINK_KIND, inclusive: false }],
3680
4019
  keybindings: [
@@ -3702,6 +4041,39 @@ const linkExtension = {
3702
4041
  }
3703
4042
  ],
3704
4043
  onEdit(command, state2) {
4044
+ if (command.type === "unlink") {
4045
+ const cursorPos = command.start;
4046
+ const sourcePos = state2.map.cursorToSource(cursorPos, "forward");
4047
+ const source = state2.source;
4048
+ let linkStart = sourcePos;
4049
+ while (linkStart > 0 && source[linkStart] !== "[") {
4050
+ linkStart--;
4051
+ }
4052
+ if (source[linkStart] !== "[") {
4053
+ return null;
4054
+ }
4055
+ const labelClose = source.indexOf("](", linkStart + 1);
4056
+ if (labelClose === -1) {
4057
+ return null;
4058
+ }
4059
+ const urlClose = source.indexOf(")", labelClose + 2);
4060
+ if (urlClose === -1) {
4061
+ return null;
4062
+ }
4063
+ const label2 = source.slice(linkStart + 1, labelClose);
4064
+ const nextSource2 = source.slice(0, linkStart) + label2 + source.slice(urlClose + 1);
4065
+ const newState = state2.runtime.createState(nextSource2);
4066
+ const labelEndSource = linkStart + label2.length;
4067
+ const newCursor = newState.map.sourceToCursor(labelEndSource, "forward");
4068
+ return {
4069
+ source: nextSource2,
4070
+ selection: {
4071
+ start: newCursor.cursorOffset,
4072
+ end: newCursor.cursorOffset,
4073
+ affinity: "forward"
4074
+ }
4075
+ };
4076
+ }
3705
4077
  if (command.type !== "wrap-link") {
3706
4078
  return null;
3707
4079
  }
@@ -3823,26 +4195,20 @@ const linkExtension = {
3823
4195
  if (!context.contentRoot || !context.toOverlayRect) {
3824
4196
  return null;
3825
4197
  }
3826
- return /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(
4198
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
3827
4199
  CakeLinkPopover,
3828
4200
  {
3829
4201
  container: context.container,
3830
4202
  contentRoot: context.contentRoot,
3831
- toOverlayRect: context.toOverlayRect
3832
- },
3833
- void 0,
3834
- false,
3835
- {
3836
- fileName: "/Users/moboudra/dev/blankpage/cake/src/cake/extensions/link/link.tsx",
3837
- lineNumber: 179,
3838
- columnNumber: 7
3839
- },
3840
- this
4203
+ toOverlayRect: context.toOverlayRect,
4204
+ getSelection: context.getSelection,
4205
+ executeCommand: context.executeCommand
4206
+ }
3841
4207
  );
3842
4208
  }
3843
- };
4209
+ });
3844
4210
  const PIPE_LINK_KIND = "pipe-link";
3845
- const pipeLinkExtension = {
4211
+ const pipeLinkExtension = defineExtension({
3846
4212
  name: "pipe-link",
3847
4213
  parseInline(source, start, end) {
3848
4214
  if (source[start] !== "|") {
@@ -3913,10 +4279,10 @@ const pipeLinkExtension = {
3913
4279
  }
3914
4280
  return element;
3915
4281
  }
3916
- };
4282
+ });
3917
4283
  const BLOCKQUOTE_KIND = "blockquote";
3918
4284
  const PREFIX = "> ";
3919
- const blockquoteExtension = {
4285
+ const blockquoteExtension = defineExtension({
3920
4286
  name: "blockquote",
3921
4287
  parseBlock(source, start, context) {
3922
4288
  if (source.slice(start, start + PREFIX.length) !== PREFIX) {
@@ -3985,9 +4351,9 @@ const blockquoteExtension = {
3985
4351
  }
3986
4352
  return element;
3987
4353
  }
3988
- };
4354
+ });
3989
4355
  const ITALIC_KIND = "italic";
3990
- const italicExtension = {
4356
+ const italicExtension = defineExtension({
3991
4357
  name: "italic",
3992
4358
  toggleInline: { kind: ITALIC_KIND, markers: ["*", "_"] },
3993
4359
  keybindings: [
@@ -4057,7 +4423,7 @@ const italicExtension = {
4057
4423
  }
4058
4424
  return element;
4059
4425
  }
4060
- };
4426
+ });
4061
4427
  const HEADING_KIND = "heading";
4062
4428
  const HEADING_PATTERN = /^(#{1,3}) /;
4063
4429
  function findLineStartInSource(source, sourceOffset) {
@@ -4158,7 +4524,7 @@ function shouldExitHeadingOnLineBreak(state2) {
4158
4524
  const contentStart = lineStart + marker.length;
4159
4525
  return sourcePos >= contentStart && sourcePos <= lineEnd;
4160
4526
  }
4161
- const headingExtension = {
4527
+ const headingExtension = defineExtension({
4162
4528
  name: "heading",
4163
4529
  onEdit(command, state2) {
4164
4530
  if (command.type === "delete-backward") {
@@ -4299,7 +4665,7 @@ const headingExtension = {
4299
4665
  }
4300
4666
  return lineElement;
4301
4667
  }
4302
- };
4668
+ });
4303
4669
  const IMAGE_KIND = "image";
4304
4670
  const IMAGE_PATTERN = /^!\[([^\]]*)\]\(([^)]*)\)$/;
4305
4671
  const UPLOADING_PATTERN = /^!\[uploading:([^\]]+)\]\(\)$/;
@@ -4317,7 +4683,7 @@ function isImageData(data) {
4317
4683
  }
4318
4684
  return false;
4319
4685
  }
4320
- const imageExtension = {
4686
+ const imageExtension = defineExtension({
4321
4687
  name: "image",
4322
4688
  parseBlock(source, start) {
4323
4689
  let lineEnd = source.indexOf("\n", start);
@@ -4412,7 +4778,7 @@ const imageExtension = {
4412
4778
  }
4413
4779
  return element;
4414
4780
  }
4415
- };
4781
+ });
4416
4782
  const LIST_LINE_REGEX$2 = /^(\s*)([-*+]|\d+\.)( )(.*)$/;
4417
4783
  const INDENT_SIZE = 2;
4418
4784
  function parseMarkerType(marker) {
@@ -5231,7 +5597,7 @@ function getParagraphText(block) {
5231
5597
  }
5232
5598
  return text;
5233
5599
  }
5234
- const listExtension = {
5600
+ const listExtension = defineExtension({
5235
5601
  name: "list",
5236
5602
  keybindings: [
5237
5603
  {
@@ -5324,7 +5690,7 @@ const listExtension = {
5324
5690
  }
5325
5691
  return element;
5326
5692
  }
5327
- };
5693
+ });
5328
5694
  const THUMB_MIN_HEIGHT = 30;
5329
5695
  const SCROLL_HIDE_DELAY = 500;
5330
5696
  const TRACK_PADDING = 8;
@@ -5337,6 +5703,7 @@ function ScrollbarOverlay({ container }) {
5337
5703
  const [isDragging, setIsDragging] = require$$0.useState(false);
5338
5704
  const [isHovered, setIsHovered] = require$$0.useState(false);
5339
5705
  const [isScrolling, setIsScrolling] = require$$0.useState(false);
5706
+ const [isDarkMode, setIsDarkMode] = require$$0.useState(false);
5340
5707
  const dragStartRef = require$$0.useRef(
5341
5708
  null
5342
5709
  );
@@ -5351,6 +5718,24 @@ function ScrollbarOverlay({ container }) {
5351
5718
  );
5352
5719
  const maxScrollTop = scrollHeight - clientHeight;
5353
5720
  const thumbTop = maxScrollTop > 0 ? TRACK_PADDING + scrollTop / maxScrollTop * (trackHeight - thumbHeight) : TRACK_PADDING;
5721
+ require$$0.useEffect(() => {
5722
+ function checkDarkMode() {
5723
+ const html = document.documentElement;
5724
+ setIsDarkMode(html.classList.contains("dark"));
5725
+ }
5726
+ checkDarkMode();
5727
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
5728
+ mediaQuery.addEventListener("change", checkDarkMode);
5729
+ const observer = new MutationObserver(checkDarkMode);
5730
+ observer.observe(document.documentElement, {
5731
+ attributes: true,
5732
+ attributeFilter: ["class"]
5733
+ });
5734
+ return () => {
5735
+ mediaQuery.removeEventListener("change", checkDarkMode);
5736
+ observer.disconnect();
5737
+ };
5738
+ }, []);
5354
5739
  require$$0.useEffect(() => {
5355
5740
  function update() {
5356
5741
  setState({
@@ -5488,6 +5873,13 @@ function ScrollbarOverlay({ container }) {
5488
5873
  return null;
5489
5874
  }
5490
5875
  const isVisible = isDragging || isHovered || isScrolling;
5876
+ const wrapperStyle = {
5877
+ position: "absolute",
5878
+ inset: 0,
5879
+ pointerEvents: "none",
5880
+ overflow: "hidden",
5881
+ zIndex: 50
5882
+ };
5491
5883
  const trackStyle = {
5492
5884
  position: "absolute",
5493
5885
  top: 0,
@@ -5497,6 +5889,12 @@ function ScrollbarOverlay({ container }) {
5497
5889
  height: clientHeight,
5498
5890
  pointerEvents: "auto"
5499
5891
  };
5892
+ const getThumbColor = () => {
5893
+ if (isDarkMode) {
5894
+ return isDragging ? "rgba(255, 255, 255, 0.5)" : "rgba(255, 255, 255, 0.3)";
5895
+ }
5896
+ return isDragging ? "rgba(0, 0, 0, 0.5)" : "rgba(0, 0, 0, 0.3)";
5897
+ };
5500
5898
  const thumbStyle = {
5501
5899
  position: "absolute",
5502
5900
  right: "2px",
@@ -5504,58 +5902,39 @@ function ScrollbarOverlay({ container }) {
5504
5902
  borderRadius: "9999px",
5505
5903
  height: thumbHeight,
5506
5904
  top: thumbTop,
5507
- opacity: isVisible ? 1 : 0
5905
+ opacity: isVisible ? 1 : 0,
5906
+ backgroundColor: getThumbColor(),
5907
+ transition: "opacity 150ms",
5908
+ cursor: "pointer"
5508
5909
  };
5509
- return /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(
5910
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: wrapperStyle, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
5510
5911
  "div",
5511
5912
  {
5512
5913
  "data-testid": "custom-scrollbar",
5513
5914
  "aria-hidden": "true",
5514
- className: "pointer-events-auto absolute top-0 right-0 bottom-0 z-50 w-3",
5515
5915
  style: trackStyle,
5516
5916
  onClick: handleTrackClick,
5517
5917
  onMouseEnter: () => setIsHovered(true),
5518
5918
  onMouseLeave: () => setIsHovered(false),
5519
- children: /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(
5919
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
5520
5920
  "div",
5521
5921
  {
5522
5922
  "data-testid": "scrollbar-thumb",
5523
- className: `absolute right-0.5 w-1.5 cursor-pointer rounded-full transition-opacity duration-150 ${isDragging ? "bg-black/50 dark:bg-white/50" : "bg-black/30 dark:bg-white/30"}`,
5524
5923
  style: thumbStyle,
5525
5924
  onMouseDown: handleThumbMouseDown
5526
- },
5527
- void 0,
5528
- false,
5529
- {
5530
- fileName: "/Users/moboudra/dev/blankpage/cake/src/cake/extensions/scrollbar/index.tsx",
5531
- lineNumber: 230,
5532
- columnNumber: 7
5533
- },
5534
- this
5925
+ }
5535
5926
  )
5536
- },
5537
- void 0,
5538
- false,
5539
- {
5540
- fileName: "/Users/moboudra/dev/blankpage/cake/src/cake/extensions/scrollbar/index.tsx",
5541
- lineNumber: 221,
5542
- columnNumber: 5
5543
- },
5544
- this
5545
- );
5927
+ }
5928
+ ) });
5546
5929
  }
5547
- const scrollbarExtension = {
5930
+ const scrollbarExtension = defineExtension({
5548
5931
  name: "scrollbar",
5549
5932
  renderOverlay(context) {
5550
- return /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(ScrollbarOverlay, { container: context.container }, void 0, false, {
5551
- fileName: "/Users/moboudra/dev/blankpage/cake/src/cake/extensions/scrollbar/index.tsx",
5552
- lineNumber: 247,
5553
- columnNumber: 12
5554
- }, this);
5933
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollbarOverlay, { container: context.container });
5555
5934
  }
5556
- };
5935
+ });
5557
5936
  const STRIKE_KIND = "strikethrough";
5558
- const strikethroughExtension = {
5937
+ const strikethroughExtension = defineExtension({
5559
5938
  name: "strikethrough",
5560
5939
  toggleInline: { kind: STRIKE_KIND, markers: ["~~"] },
5561
5940
  keybindings: [
@@ -5628,8 +6007,8 @@ const strikethroughExtension = {
5628
6007
  }
5629
6008
  return element;
5630
6009
  }
5631
- };
5632
- const bundledExtensions = [
6010
+ });
6011
+ const bundledExtensionsWithoutImage = [
5633
6012
  blockquoteExtension,
5634
6013
  headingExtension,
5635
6014
  listExtension,
@@ -5638,7 +6017,10 @@ const bundledExtensions = [
5638
6017
  italicExtension,
5639
6018
  strikethroughExtension,
5640
6019
  pipeLinkExtension,
5641
- linkExtension,
6020
+ linkExtension
6021
+ ];
6022
+ const bundledExtensions = [
6023
+ ...bundledExtensionsWithoutImage,
5642
6024
  imageExtension
5643
6025
  ];
5644
6026
  function toLayoutRect(params) {
@@ -5698,6 +6080,46 @@ function cursorOffsetToCodeUnit(cursorToCodeUnit, offset) {
5698
6080
  const clamped = Math.max(0, Math.min(offset, cursorToCodeUnit.length - 1));
5699
6081
  return cursorToCodeUnit[clamped] ?? 0;
5700
6082
  }
6083
+ function createDomPositionResolver(lineElement) {
6084
+ const textNodes = [];
6085
+ const cumulativeEnds = [];
6086
+ const walker = createTextWalker(lineElement);
6087
+ let current = walker.nextNode();
6088
+ let total = 0;
6089
+ while (current) {
6090
+ if (current instanceof Text) {
6091
+ const length = current.data.length;
6092
+ textNodes.push(current);
6093
+ total += length;
6094
+ cumulativeEnds.push(total);
6095
+ }
6096
+ current = walker.nextNode();
6097
+ }
6098
+ if (textNodes.length === 0) {
6099
+ return () => {
6100
+ if (!lineElement.textContent) {
6101
+ return { node: lineElement, offset: 0 };
6102
+ }
6103
+ return { node: lineElement, offset: lineElement.childNodes.length };
6104
+ };
6105
+ }
6106
+ return (offsetInLine) => {
6107
+ const clamped = Math.max(0, Math.min(offsetInLine, total));
6108
+ let low = 0;
6109
+ let high = cumulativeEnds.length - 1;
6110
+ while (low < high) {
6111
+ const mid = low + high >>> 1;
6112
+ if ((cumulativeEnds[mid] ?? 0) < clamped) {
6113
+ low = mid + 1;
6114
+ } else {
6115
+ high = mid;
6116
+ }
6117
+ }
6118
+ const node = textNodes[low] ?? lineElement;
6119
+ const prevEnd = low > 0 ? cumulativeEnds[low - 1] ?? 0 : 0;
6120
+ return { node, offset: clamped - prevEnd };
6121
+ };
6122
+ }
5701
6123
  function measureCharacterRect(params) {
5702
6124
  if (params.lineLength <= 0) {
5703
6125
  return null;
@@ -5710,101 +6132,155 @@ function measureCharacterRect(params) {
5710
6132
  params.cursorToCodeUnit,
5711
6133
  Math.min(params.offset + 1, params.lineLength)
5712
6134
  );
5713
- const startPosition = resolveDomPosition(params.lineElement, startCodeUnit);
5714
- const endPosition = resolveDomPosition(params.lineElement, endCodeUnit);
5715
- const range = document.createRange();
5716
- range.setStart(startPosition.node, startPosition.offset);
5717
- range.setEnd(endPosition.node, endPosition.offset);
5718
- const rects = range.getClientRects();
6135
+ const startPosition = params.resolveDomPosition(startCodeUnit);
6136
+ const endPosition = params.resolveDomPosition(endCodeUnit);
6137
+ params.range.setStart(startPosition.node, startPosition.offset);
6138
+ params.range.setEnd(endPosition.node, endPosition.offset);
6139
+ const rects = params.range.getClientRects();
5719
6140
  if (rects.length > 0) {
5720
6141
  return rects[0] ?? null;
5721
6142
  }
5722
- const rect = range.getBoundingClientRect();
6143
+ const rect = params.range.getBoundingClientRect();
5723
6144
  if (rect.width === 0 && rect.height === 0) {
5724
6145
  return null;
5725
6146
  }
5726
6147
  return rect;
5727
6148
  }
5728
6149
  function measureLineRows(params) {
6150
+ var _a, _b;
6151
+ const fallbackLineBox = toLayoutRect({
6152
+ rect: params.lineRect,
6153
+ containerRect: params.containerRect,
6154
+ scroll: params.scroll
6155
+ });
5729
6156
  if (params.lineLength === 0) {
5730
- const lineBox = toLayoutRect({
5731
- rect: params.lineRect,
5732
- containerRect: params.containerRect,
5733
- scroll: params.scroll
5734
- });
5735
6157
  return [
5736
6158
  {
5737
6159
  startOffset: 0,
5738
6160
  endOffset: 0,
5739
- rect: { ...lineBox, width: 0 }
6161
+ rect: { ...fallbackLineBox, width: 0 }
5740
6162
  }
5741
6163
  ];
5742
6164
  }
5743
- const fullLineRange = document.createRange();
5744
- const fullLineStart = resolveDomPosition(params.lineElement, 0);
5745
- const fullLineEnd = resolveDomPosition(
5746
- params.lineElement,
5747
- params.codeUnitLength
5748
- );
5749
- fullLineRange.setStart(fullLineStart.node, fullLineStart.offset);
5750
- fullLineRange.setEnd(fullLineEnd.node, fullLineEnd.offset);
5751
- const fullLineRects = groupDomRectsByRow(
5752
- Array.from(fullLineRange.getClientRects())
5753
- );
5754
- const fallbackLineBox = toLayoutRect({
5755
- rect: params.lineRect,
5756
- containerRect: params.containerRect,
5757
- scroll: params.scroll
5758
- });
5759
- const charRect = measureCharacterRect({
5760
- lineElement: params.lineElement,
5761
- offset: 0,
5762
- lineLength: params.lineLength,
5763
- cursorToCodeUnit: params.cursorToCodeUnit
5764
- });
5765
- const charWidth = (charRect == null ? void 0 : charRect.width) ?? 0;
5766
- if (fullLineRects.length === 0 || charWidth <= 0) {
5767
- const rows2 = [
6165
+ const WRAP_THRESHOLD_PX = 3;
6166
+ const resolvePosition = createDomPositionResolver(params.lineElement);
6167
+ const scratchRange = document.createRange();
6168
+ const topCache = /* @__PURE__ */ new Map();
6169
+ const fullLineStart = resolvePosition(0);
6170
+ const fullLineEnd = resolvePosition(params.codeUnitLength);
6171
+ scratchRange.setStart(fullLineStart.node, fullLineStart.offset);
6172
+ scratchRange.setEnd(fullLineEnd.node, fullLineEnd.offset);
6173
+ const fullLineRects = groupDomRectsByRow(Array.from(scratchRange.getClientRects()));
6174
+ if (fullLineRects.length === 0) {
6175
+ return [
5768
6176
  {
5769
6177
  startOffset: 0,
5770
6178
  endOffset: params.lineLength,
5771
6179
  rect: fallbackLineBox
5772
6180
  }
5773
6181
  ];
5774
- return rows2.length === 1 ? [
6182
+ }
6183
+ function offsetToTop(offset) {
6184
+ if (topCache.has(offset)) {
6185
+ return topCache.get(offset) ?? null;
6186
+ }
6187
+ const rect = measureCharacterRect({
6188
+ lineElement: params.lineElement,
6189
+ offset,
6190
+ lineLength: params.lineLength,
6191
+ cursorToCodeUnit: params.cursorToCodeUnit,
6192
+ resolveDomPosition: resolvePosition,
6193
+ range: scratchRange
6194
+ });
6195
+ const top = rect ? rect.top : null;
6196
+ topCache.set(offset, top);
6197
+ return top;
6198
+ }
6199
+ function findFirstMeasurableOffset(from) {
6200
+ for (let offset = Math.max(0, from); offset < params.lineLength; offset++) {
6201
+ if (offsetToTop(offset) !== null) {
6202
+ return offset;
6203
+ }
6204
+ }
6205
+ return null;
6206
+ }
6207
+ function findNextRowStartOffset(fromExclusive, rowTop) {
6208
+ const lastIndex = params.lineLength - 1;
6209
+ if (fromExclusive > lastIndex) {
6210
+ return null;
6211
+ }
6212
+ const isNewRowAt = (offset) => {
6213
+ const top = offsetToTop(offset);
6214
+ return top !== null && Math.abs(top - rowTop) > WRAP_THRESHOLD_PX;
6215
+ };
6216
+ let step = 1;
6217
+ let lastSame = fromExclusive - 1;
6218
+ let probe = fromExclusive;
6219
+ while (probe <= lastIndex) {
6220
+ if (isNewRowAt(probe)) {
6221
+ break;
6222
+ }
6223
+ lastSame = probe;
6224
+ probe += step;
6225
+ step *= 2;
6226
+ }
6227
+ if (probe > lastIndex) {
6228
+ probe = lastIndex;
6229
+ if (!isNewRowAt(probe)) {
6230
+ return null;
6231
+ }
6232
+ }
6233
+ let low = Math.max(fromExclusive, lastSame + 1);
6234
+ let high = probe;
6235
+ while (low < high) {
6236
+ const mid = low + high >>> 1;
6237
+ if (isNewRowAt(mid)) {
6238
+ high = mid;
6239
+ } else {
6240
+ low = mid + 1;
6241
+ }
6242
+ }
6243
+ return low < params.lineLength ? low : null;
6244
+ }
6245
+ const rows = [];
6246
+ const firstMeasurable = findFirstMeasurableOffset(0);
6247
+ if (firstMeasurable === null) {
6248
+ return [
5775
6249
  {
5776
- ...rows2[0],
5777
- rect: {
5778
- ...rows2[0].rect,
5779
- top: fallbackLineBox.top,
5780
- height: fallbackLineBox.height
5781
- }
6250
+ startOffset: 0,
6251
+ endOffset: params.lineLength,
6252
+ rect: fallbackLineBox
5782
6253
  }
5783
- ] : rows2;
6254
+ ];
5784
6255
  }
5785
- let startOffset = 0;
5786
- const rows = fullLineRects.map((rect, index) => {
5787
- const remaining = params.lineLength - startOffset;
5788
- const isLast = index === fullLineRects.length - 1;
5789
- const estimatedLength = Math.max(1, Math.round(rect.width / charWidth));
5790
- const rowLength = isLast ? remaining : Math.min(remaining, estimatedLength);
5791
- const row = {
5792
- startOffset,
5793
- endOffset: startOffset + rowLength,
6256
+ let currentRowStart = 0;
6257
+ let currentRowTop = ((_a = fullLineRects[0]) == null ? void 0 : _a.top) ?? params.lineRect.top;
6258
+ let searchFrom = firstMeasurable + 1;
6259
+ let rowIndex = 0;
6260
+ while (currentRowStart < params.lineLength) {
6261
+ const nextRowStart = findNextRowStartOffset(searchFrom, currentRowTop);
6262
+ const currentRowEnd = nextRowStart ?? params.lineLength;
6263
+ const domRect = fullLineRects[rowIndex] ?? params.lineRect;
6264
+ rows.push({
6265
+ startOffset: currentRowStart,
6266
+ endOffset: currentRowEnd,
5794
6267
  rect: toLayoutRect({
5795
- rect,
6268
+ rect: domRect,
5796
6269
  containerRect: params.containerRect,
5797
6270
  scroll: params.scroll
5798
6271
  })
5799
- };
5800
- startOffset += rowLength;
5801
- return row;
5802
- });
5803
- if (rows.length > 0) {
5804
- rows[rows.length - 1] = {
5805
- ...rows[rows.length - 1],
5806
- endOffset: params.lineLength
5807
- };
6272
+ });
6273
+ if (nextRowStart === null) {
6274
+ break;
6275
+ }
6276
+ currentRowStart = nextRowStart;
6277
+ rowIndex += 1;
6278
+ const nextMeasurable = findFirstMeasurableOffset(currentRowStart);
6279
+ if (nextMeasurable === null) {
6280
+ break;
6281
+ }
6282
+ currentRowTop = ((_b = fullLineRects[rowIndex]) == null ? void 0 : _b.top) ?? currentRowTop;
6283
+ searchFrom = nextMeasurable + 1;
5808
6284
  }
5809
6285
  if (rows.length === 1) {
5810
6286
  rows[0] = {
@@ -5876,6 +6352,44 @@ function measureLayoutModelFromDom(params) {
5876
6352
  }
5877
6353
  return buildLayoutModel(params.lines, measurer);
5878
6354
  }
6355
+ function measureLayoutModelRangeFromDom(params) {
6356
+ const measurer = createDomLayoutMeasurer({
6357
+ root: params.root,
6358
+ container: params.container,
6359
+ lines: params.lines
6360
+ });
6361
+ if (!measurer) {
6362
+ return null;
6363
+ }
6364
+ const clampedStart = Math.max(0, Math.min(params.startLineIndex, params.lines.length - 1));
6365
+ const clampedEnd = Math.max(clampedStart, Math.min(params.endLineIndex, params.lines.length - 1));
6366
+ const lineOffsets = getLineOffsets(params.lines);
6367
+ let lineStartOffset = lineOffsets[clampedStart] ?? 0;
6368
+ const layouts = [];
6369
+ for (let lineIndex = clampedStart; lineIndex <= clampedEnd; lineIndex += 1) {
6370
+ const lineInfo = params.lines[lineIndex];
6371
+ if (!lineInfo) {
6372
+ continue;
6373
+ }
6374
+ const measurement = measurer.measureLine({
6375
+ lineIndex: lineInfo.lineIndex,
6376
+ lineText: lineInfo.text,
6377
+ lineLength: lineInfo.cursorLength,
6378
+ lineHasNewline: lineInfo.hasNewline,
6379
+ top: 0
6380
+ });
6381
+ layouts.push({
6382
+ lineIndex: lineInfo.lineIndex,
6383
+ lineStartOffset,
6384
+ lineLength: lineInfo.cursorLength,
6385
+ lineHasNewline: lineInfo.hasNewline,
6386
+ lineBox: measurement.lineBox,
6387
+ rows: measurement.rows
6388
+ });
6389
+ lineStartOffset += lineInfo.cursorLength + (lineInfo.hasNewline ? 1 : 0);
6390
+ }
6391
+ return { container: measurer.container, lines: layouts };
6392
+ }
5879
6393
  function getLineElement(root, lineIndex) {
5880
6394
  return root.querySelector(`[data-line-index="${lineIndex}"]`);
5881
6395
  }
@@ -6069,7 +6583,7 @@ function computeSelectionRects(layout, selection, measurer) {
6069
6583
  return rects;
6070
6584
  }
6071
6585
  function computeCaretRect(caret) {
6072
- const height = caret.lineRect.height;
6586
+ const height = caret.fontSize * 1.2;
6073
6587
  const contentHeight = caret.lineRect.height > 0 ? Math.max(
6074
6588
  0,
6075
6589
  caret.lineRect.height - caret.padding.top - caret.padding.bottom
@@ -6091,7 +6605,17 @@ function getSelectionGeometry(params) {
6091
6605
  end: selection.start,
6092
6606
  affinity: selection.affinity
6093
6607
  };
6094
- const layout = shouldMeasureLayout(normalized) ? measureLayoutModelFromDom({ lines: docLines, root, container }) : null;
6608
+ const layout = shouldMeasureLayout(normalized) ? (() => {
6609
+ const startLine2 = resolveOffsetToLine(docLines, normalized.start);
6610
+ const endLine = resolveOffsetToLine(docLines, normalized.end);
6611
+ return measureLayoutModelRangeFromDom({
6612
+ lines: docLines,
6613
+ root,
6614
+ container,
6615
+ startLineIndex: startLine2.lineIndex,
6616
+ endLineIndex: endLine.lineIndex
6617
+ });
6618
+ })() : null;
6095
6619
  const containerRect = container.getBoundingClientRect();
6096
6620
  const scroll = { top: container.scrollTop, left: container.scrollLeft };
6097
6621
  const startLine = resolveOffsetToLine(docLines, normalized.start);
@@ -6123,6 +6647,7 @@ function getSelectionGeometry(params) {
6123
6647
  scroll
6124
6648
  }),
6125
6649
  lineLength: lineInfo.cursorLength,
6650
+ fontSize: getComputedFontSize(lineElement),
6126
6651
  padding: getComputedVerticalPadding(lineElement)
6127
6652
  };
6128
6653
  const caretRect = computeCaretRect(caretMeasurement);
@@ -6166,6 +6691,7 @@ function getSelectionGeometry(params) {
6166
6691
  scroll
6167
6692
  }),
6168
6693
  lineLength: lineInfo.cursorLength,
6694
+ fontSize: getComputedFontSize(focusLineElement),
6169
6695
  padding: getComputedVerticalPadding(focusLineElement)
6170
6696
  };
6171
6697
  focusRect = computeCaretRect(caretMeasurement);
@@ -6292,6 +6818,11 @@ function getComputedVerticalPadding(lineElement) {
6292
6818
  bottom: Number.isFinite(bottom) ? bottom : 0
6293
6819
  };
6294
6820
  }
6821
+ function getComputedFontSize(lineElement) {
6822
+ const fontSize = window.getComputedStyle(lineElement).fontSize;
6823
+ const parsed = Number.parseFloat(fontSize);
6824
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 16;
6825
+ }
6295
6826
  function rectRight(rect) {
6296
6827
  return rect.left + rect.width;
6297
6828
  }
@@ -6689,6 +7220,36 @@ function nextWordBreak(text, offset) {
6689
7220
  }
6690
7221
  return text.length;
6691
7222
  }
7223
+ const MAX_HTML_INPUT_LENGTH = 5e5;
7224
+ const MAX_MARKDOWN_OUTPUT_LENGTH = 1e5;
7225
+ function isHTMLElement(node) {
7226
+ return node !== null && node.nodeType === Node.ELEMENT_NODE;
7227
+ }
7228
+ function isElement(node) {
7229
+ return node !== null && node.nodeType === Node.ELEMENT_NODE;
7230
+ }
7231
+ const CLEANUP_PATTERNS = {
7232
+ unescapeHeaders: /^\\(#{1,6})\s+/gm,
7233
+ unescapeBlockquote: /^\\>/gm,
7234
+ unescapeMarkdown: /\\([*_`~[\]])/g,
7235
+ unescapeListBullets: /^(\s*)\\([-*+])(\s+)/gm,
7236
+ unescapeListNumbers: /^(\s*)(\d+)\\\.(\s+)/gm,
7237
+ normalizeBullets: /^(\s*)[-*+](\s{2,})/gm,
7238
+ normalizeNumbers: /^[\s]*\d+\.[\s]+/gm,
7239
+ normalizeHeaders: /^(#{1,6})[\s]{2,}/gm,
7240
+ removeTrailingSpaces: /[ \t]+$/gm,
7241
+ headersInBlockquotes: /^>\s*(#{1,6}\s+.*)/gm,
7242
+ excessiveNewlines: /\n{3,}/g,
7243
+ interlacedTableGaps: /(\|[^\n]*\|)\s*\n\s*\n+\s*(\|[^\n]*\|)/g,
7244
+ complexTableGaps: /(\|[^\n]*\|)(\s*\n){2,}(\|[^\n]*\|)/g
7245
+ };
7246
+ const HTML_PREPROCESSING_PATTERNS = {
7247
+ removeStyleAndDataAttrs: /\s(?:style|data-[^=]*|id)="[^"]*"/gi,
7248
+ removeNonCodeClasses: /\sclass="(?![^"]*(?:language-|hljs))[^"]*"/gi,
7249
+ removeEmptyElements: /<(\w+)[^>]*>\s*<\/\1>/gi,
7250
+ normalizeSpaces: /[ \t]{2,}/g,
7251
+ reduceBlankLines: /\n\s*\n/g
7252
+ };
6692
7253
  const turndownService = new TurndownService({
6693
7254
  headingStyle: "atx",
6694
7255
  bulletListMarker: "-",
@@ -6706,6 +7267,13 @@ turndownService.addRule("strikethrough", {
6706
7267
  turndownService.addRule("codeBlock", {
6707
7268
  filter: "pre",
6708
7269
  replacement: (content, node) => {
7270
+ if (!isHTMLElement(node)) {
7271
+ return `
7272
+ \`\`\`
7273
+ ${content}
7274
+ \`\`\`
7275
+ `;
7276
+ }
6709
7277
  const codeElement = node.querySelector("code");
6710
7278
  if (codeElement) {
6711
7279
  const className = codeElement.className || "";
@@ -6724,12 +7292,308 @@ ${content}
6724
7292
  `;
6725
7293
  }
6726
7294
  });
6727
- function htmlToMarkdownForPaste(html) {
6728
- const trimmed = html.trim();
6729
- if (!trimmed) {
7295
+ turndownService.addRule("tableRow", {
7296
+ filter: "tr",
7297
+ replacement: (_content, node) => {
7298
+ var _a;
7299
+ if (!isHTMLElement(node)) {
7300
+ return "";
7301
+ }
7302
+ const isHeaderRow = ((_a = node.parentNode) == null ? void 0 : _a.nodeName) === "THEAD";
7303
+ const cells = Array.from(node.querySelectorAll("td, th"));
7304
+ const cellContents = cells.map((cell) => {
7305
+ const text = cell.textContent || "";
7306
+ return text.replace(/\|/g, "\\|").trim();
7307
+ });
7308
+ let result = "| " + cellContents.join(" | ") + " |\n";
7309
+ if (isHeaderRow) {
7310
+ const separators = cells.map((cell) => {
7311
+ const align = cell.getAttribute("align");
7312
+ if (align === "center") {
7313
+ return ":-------------:";
7314
+ }
7315
+ if (align === "right") {
7316
+ return "-------------:";
7317
+ }
7318
+ return "-------------";
7319
+ });
7320
+ result += "| " + separators.join(" | ") + " |\n";
7321
+ }
7322
+ return result;
7323
+ }
7324
+ });
7325
+ turndownService.addRule("taskList", {
7326
+ filter: (node) => {
7327
+ if (!isHTMLElement(node)) {
7328
+ return false;
7329
+ }
7330
+ if (node.nodeName !== "LI") {
7331
+ return false;
7332
+ }
7333
+ if (node.querySelector('input[type="checkbox"]') !== null) {
7334
+ return true;
7335
+ }
7336
+ const textContent = node.textContent || "";
7337
+ const hasCheckboxSymbols = /^[\s]*[☐☑✓✗[\]]/m.test(textContent) || /^[\s]*\[[ x]\]/m.test(textContent);
7338
+ const hasCheckboxClass = Boolean(
7339
+ node.className && (node.className.includes("task") || node.className.includes("checkbox") || node.className.includes("todo"))
7340
+ );
7341
+ return hasCheckboxSymbols || hasCheckboxClass;
7342
+ },
7343
+ replacement: (content, node) => {
7344
+ if (!isHTMLElement(node)) {
7345
+ return content;
7346
+ }
7347
+ const checkbox = node.querySelector('input[type="checkbox"]');
7348
+ const isCheckbox = checkbox instanceof HTMLInputElement && checkbox.type === "checkbox";
7349
+ let isChecked = false;
7350
+ if (isCheckbox) {
7351
+ isChecked = checkbox.checked;
7352
+ } else {
7353
+ const textContent2 = node.textContent || "";
7354
+ isChecked = /^[\s]*[☑✓✗]/.test(textContent2) || /^[\s]*\[x\]/i.test(textContent2);
7355
+ }
7356
+ const prefix = isChecked ? "- [x] " : "- [ ] ";
7357
+ let textContent = content;
7358
+ textContent = textContent.replace(/^\s*\[[ x]\]\s*/gi, "");
7359
+ textContent = textContent.replace(/^\s*[☐☑✓✗]\s*/g, "");
7360
+ textContent = textContent.replace(/^\s*\[[x ]\]\s*/gi, "");
7361
+ textContent = textContent.replace(/^\s*\\?\[[ x]\\?\]\s*/gi, "");
7362
+ textContent = textContent.replace(/\\?\[\\?\s*\\?\]\\?\s*/g, "");
7363
+ return prefix + textContent.trim() + "\n";
7364
+ }
7365
+ });
7366
+ turndownService.addRule("list", {
7367
+ filter: ["ul", "ol"],
7368
+ replacement: (content, node) => {
7369
+ const parent = node.parentNode;
7370
+ const isNested = isElement(parent) && parent.tagName === "LI";
7371
+ if (isNested) {
7372
+ return "\n" + content;
7373
+ }
7374
+ return "\n" + content + "\n";
7375
+ }
7376
+ });
7377
+ turndownService.addRule("blockquote", {
7378
+ filter: "blockquote",
7379
+ replacement: (content) => {
7380
+ const lines = content.trim().split("\n");
7381
+ const processedLines = lines.map((line) => {
7382
+ const trimmed = line.trim();
7383
+ if (!trimmed) {
7384
+ return ">";
7385
+ }
7386
+ const existingQuotes = trimmed.match(/^(>\s*)+/);
7387
+ if (existingQuotes) {
7388
+ return existingQuotes[0] + " " + trimmed;
7389
+ }
7390
+ return "> " + trimmed;
7391
+ });
7392
+ return "\n" + processedLines.join("\n") + "\n";
7393
+ }
7394
+ });
7395
+ turndownService.addRule("horizontalRule", {
7396
+ filter: "hr",
7397
+ replacement: () => "\n---\n"
7398
+ });
7399
+ turndownService.addRule("inlineCode", {
7400
+ filter: (node) => {
7401
+ const parent = node.parentNode;
7402
+ return node.nodeName === "CODE" && !(isElement(parent) && parent.tagName === "PRE");
7403
+ },
7404
+ replacement: (content) => {
7405
+ const backtickCount = Math.max(
7406
+ 1,
7407
+ (content.match(/`+/g) || []).reduce(
7408
+ (max, match) => Math.max(max, match.length),
7409
+ 0
7410
+ ) + 1
7411
+ );
7412
+ const delimiter = "`".repeat(backtickCount);
7413
+ return delimiter + content + delimiter;
7414
+ }
7415
+ });
7416
+ turndownService.addRule("image", {
7417
+ filter: "img",
7418
+ replacement: (_content, node) => {
7419
+ if (!isHTMLElement(node)) {
7420
+ return "";
7421
+ }
7422
+ const src = node.getAttribute("src") || "";
7423
+ const alt = node.getAttribute("alt") || "";
7424
+ const title = node.getAttribute("title");
7425
+ if (title) {
7426
+ return `![${alt}](${src} "${title}")`;
7427
+ }
7428
+ return `![${alt}](${src})`;
7429
+ }
7430
+ });
7431
+ turndownService.addRule("highlight", {
7432
+ filter: (node) => {
7433
+ var _a, _b;
7434
+ if (!isHTMLElement(node)) {
7435
+ return false;
7436
+ }
7437
+ return node.nodeName === "MARK" || node.nodeName === "SPAN" && (((_a = node.style) == null ? void 0 : _a.backgroundColor) === "yellow" || ((_b = node.className) == null ? void 0 : _b.includes("highlight")));
7438
+ },
7439
+ replacement: (content) => `==${content}==`
7440
+ });
7441
+ turndownService.addRule("headerWithId", {
7442
+ filter: ["h1", "h2", "h3", "h4", "h5", "h6"],
7443
+ replacement: (content, node) => {
7444
+ if (!isHTMLElement(node)) {
7445
+ return content;
7446
+ }
7447
+ const rawLevel = parseInt(node.nodeName.charAt(1));
7448
+ const level = Math.min(rawLevel, 3);
7449
+ const hashes = "#".repeat(level);
7450
+ const id = node.getAttribute("id");
7451
+ if (id) {
7452
+ return `
7453
+ ${hashes} ${content} {#${id}}
7454
+ `;
7455
+ }
7456
+ return `
7457
+ ${hashes} ${content}
7458
+ `;
7459
+ }
7460
+ });
7461
+ function processTextNodes(element) {
7462
+ const spans = Array.from(element.querySelectorAll("span"));
7463
+ spans.forEach((span) => {
7464
+ var _a;
7465
+ const textContent = span.textContent || "";
7466
+ const textNode = document.createTextNode(textContent);
7467
+ (_a = span.parentNode) == null ? void 0 : _a.replaceChild(textNode, span);
7468
+ });
7469
+ }
7470
+ function detectSourceApp(html) {
7471
+ const detectionPatterns = {
7472
+ notion: ["notion-", "notranslate"],
7473
+ github: ["github.com", "js-file-line-container"],
7474
+ slack: ["slack-", "c-message"],
7475
+ "google-docs": ["docs.google.com", "kix-"]
7476
+ };
7477
+ for (const [app, patterns] of Object.entries(detectionPatterns)) {
7478
+ if (patterns.some((pattern) => html.includes(pattern))) {
7479
+ return app;
7480
+ }
7481
+ }
7482
+ return "unknown";
7483
+ }
7484
+ function preprocessForApp(html, app) {
7485
+ switch (app) {
7486
+ case "notion":
7487
+ return html.replace(/<div[^>]*class="[^"]*notion-[^"]*"[^>]*>/gi, "<div>").replace(/<span[^>]*class="[^"]*notion-[^"]*"[^>]*>/gi, "<span>").replace(/<details[^>]*>/gi, "<div>").replace(/<\/details>/gi, "</div>").replace(/<summary[^>]*>/gi, "<strong>").replace(/<\/summary>/gi, "</strong>");
7488
+ case "github":
7489
+ return html.replace(/<td[^>]*class="[^"]*blob-num[^"]*"[^>]*>.*?<\/td>/gi, "").replace(/<span[^>]*class="[^"]*pl-[^"]*"[^>]*>/gi, "<span>").replace(/<span[^>]*class="[^"]*highlight[^"]*"[^>]*>/gi, "<span>");
7490
+ case "slack":
7491
+ return html.replace(/<span[^>]*class="[^"]*c-member[^"]*"[^>]*>/gi, "<span>").replace(/<span[^>]*data-stringify-type="mention"[^>]*>/gi, "<span>").replace(
7492
+ /<span[^>]*class="[^"]*c-emoji[^"]*"[^>]*>([^<]*)<\/span>/gi,
7493
+ "$1"
7494
+ );
7495
+ case "google-docs":
7496
+ return html.replace(
7497
+ /<span[^>]*style="[^"]*font-weight:[^;"]*bold[^"]*"[^>]*>/gi,
7498
+ "<strong>"
7499
+ ).replace(
7500
+ /<span[^>]*style="[^"]*font-style:[^;"]*italic[^"]*"[^>]*>/gi,
7501
+ "<em>"
7502
+ ).replace(/<\/span>/gi, "").replace(/<p[^>]*style="[^"]*"[^>]*>/gi, "<p>");
7503
+ default:
7504
+ return html;
7505
+ }
7506
+ }
7507
+ function cleanupMarkdown(markdown) {
7508
+ return markdown.replace(CLEANUP_PATTERNS.unescapeHeaders, "$1 ").replace(CLEANUP_PATTERNS.unescapeBlockquote, ">").replace(CLEANUP_PATTERNS.unescapeMarkdown, "$1").replace(CLEANUP_PATTERNS.unescapeListBullets, "$1$2$3").replace(CLEANUP_PATTERNS.unescapeListNumbers, "$1$2.$3").replace(CLEANUP_PATTERNS.normalizeBullets, "$1- ").replace(CLEANUP_PATTERNS.normalizeNumbers, (match) => {
7509
+ var _a;
7510
+ const num = ((_a = match.match(/\d+/)) == null ? void 0 : _a[0]) || "1";
7511
+ return `${num}. `;
7512
+ }).replace(CLEANUP_PATTERNS.normalizeHeaders, "$1 ").replace(CLEANUP_PATTERNS.removeTrailingSpaces, "").replace(CLEANUP_PATTERNS.headersInBlockquotes, "\n$1").replace(CLEANUP_PATTERNS.excessiveNewlines, "\n\n").replace(CLEANUP_PATTERNS.interlacedTableGaps, "$1\n$2").replace(CLEANUP_PATTERNS.complexTableGaps, "$1\n$3").replace(/\*\*\[([^\]]+?)\]\(([^)]+?)\)\*\*/g, "[**$1**]($2)").replace(/__\[([^\]]+?)\]\(([^)]+?)\)__/g, "[**$1**]($2)").replace(/\*\[([^\]]+?)\]\(([^)]+?)\)\*/g, "[*$1*]($2)").replace(/_\[([^\]]+?)\]\(([^)]+?)\)_/g, "[*$1*]($2)").trim();
7513
+ }
7514
+ function preprocessHtml(html) {
7515
+ const sourceApp = detectSourceApp(html);
7516
+ let processedHtml = preprocessForApp(html, sourceApp);
7517
+ const parser = new DOMParser();
7518
+ const doc = parser.parseFromString(processedHtml, "text/html");
7519
+ if (doc.body) {
7520
+ processTextNodes(doc.body);
7521
+ processedHtml = doc.body.innerHTML;
7522
+ }
7523
+ return processedHtml.replace(HTML_PREPROCESSING_PATTERNS.removeStyleAndDataAttrs, "").replace(HTML_PREPROCESSING_PATTERNS.removeNonCodeClasses, "").replace(HTML_PREPROCESSING_PATTERNS.removeEmptyElements, "").replace(HTML_PREPROCESSING_PATTERNS.normalizeSpaces, " ").replace(HTML_PREPROCESSING_PATTERNS.reduceBlankLines, "\n\n").trim();
7524
+ }
7525
+ function sanitizeContent(content) {
7526
+ return content.replace(/<script[^>]*>.*?<\/script>/gi, "").replace(
7527
+ /<(?:iframe|object|embed)[^>]*>.*?<\/(?:iframe|object|embed)>/gi,
7528
+ ""
7529
+ );
7530
+ }
7531
+ function limitContentLength(content, maxLength = MAX_MARKDOWN_OUTPUT_LENGTH) {
7532
+ return content.substring(0, maxLength);
7533
+ }
7534
+ function sanitizeMarkdown(markdown) {
7535
+ return limitContentLength(sanitizeContent(markdown));
7536
+ }
7537
+ function shouldProcessPaste(htmlContent) {
7538
+ if (htmlContent.length > MAX_HTML_INPUT_LENGTH) {
7539
+ console.warn("HTML content too large for paste processing");
7540
+ return false;
7541
+ }
7542
+ const hasFormatting = /<(?:strong|b|em|i|u|s|del|strike|code|pre|h[1-6]|blockquote|ul|ol|li|table|tr|td|th|a|img|mark|span|div)[\s>]/i.test(
7543
+ htmlContent
7544
+ );
7545
+ if (!hasFormatting) {
7546
+ return false;
7547
+ }
7548
+ if (/<img\s/i.test(htmlContent)) {
7549
+ return true;
7550
+ }
7551
+ const strippedContent = htmlContent.replace(/<[^>]*>/g, "").trim();
7552
+ if (!strippedContent || strippedContent.length < 3) {
7553
+ return false;
7554
+ }
7555
+ return true;
7556
+ }
7557
+ function normalizeListPrefixes(content) {
7558
+ const lines = content.split("\n");
7559
+ let currentListType = null;
7560
+ let currentNumber = 1;
7561
+ return lines.map((line) => {
7562
+ const match = line.match(/^(\s*)([-*+]|\d+\.)( +)(.*)$/);
7563
+ if (match) {
7564
+ const [, indent, marker, , listContent] = match;
7565
+ if (currentListType === null) {
7566
+ currentListType = /\d+\./.test(marker) ? "numbered" : "bullet";
7567
+ }
7568
+ const newMarker = currentListType === "bullet" ? "-" : `${currentNumber}.`;
7569
+ if (currentListType === "numbered") {
7570
+ currentNumber += 1;
7571
+ }
7572
+ return `${indent}${newMarker} ${listContent}`;
7573
+ }
7574
+ return line;
7575
+ }).join("\n");
7576
+ }
7577
+ function convertHtmlToMarkdown(html) {
7578
+ try {
7579
+ const processedHtml = preprocessHtml(html);
7580
+ const markdown = turndownService.turndown(processedHtml);
7581
+ return cleanupMarkdown(markdown);
7582
+ } catch (error) {
7583
+ console.error("Error converting HTML to markdown:", error);
6730
7584
  return "";
6731
7585
  }
6732
- return turndownService.turndown(trimmed);
7586
+ }
7587
+ function htmlToMarkdownForPaste(htmlContent) {
7588
+ if (!shouldProcessPaste(htmlContent)) {
7589
+ return null;
7590
+ }
7591
+ const markdown = convertHtmlToMarkdown(htmlContent);
7592
+ if (!markdown.trim()) {
7593
+ return null;
7594
+ }
7595
+ const normalizedMarkdown = normalizeListPrefixes(markdown);
7596
+ return sanitizeMarkdown(normalizedMarkdown).replace(/\r\n?/g, "\n");
6733
7597
  }
6734
7598
  const defaultSelection = { start: 0, end: 0, affinity: "forward" };
6735
7599
  const COMPOSITION_COMMIT_CLEAR_DELAY_MS = 50;
@@ -6759,10 +7623,13 @@ class CakeEngine {
6759
7623
  this.caretBlinkTimeoutId = null;
6760
7624
  this.overlayUpdateId = null;
6761
7625
  this.scrollCaretIntoViewId = null;
7626
+ this.selectionRectElements = [];
7627
+ this.lastSelectionRects = null;
6762
7628
  this.extensionsRoot = null;
6763
7629
  this.placeholderRoot = null;
6764
7630
  this.lastFocusRect = null;
6765
7631
  this.verticalNavGoalX = null;
7632
+ this.lastRenderPerf = null;
6766
7633
  this.history = {
6767
7634
  undoStack: [],
6768
7635
  redoStack: [],
@@ -6816,6 +7683,9 @@ class CakeEngine {
6816
7683
  set state(value) {
6817
7684
  this._state = value;
6818
7685
  }
7686
+ getLastRenderPerf() {
7687
+ return this.lastRenderPerf;
7688
+ }
6819
7689
  isEventTargetInContentRoot(target) {
6820
7690
  if (target === this.container) {
6821
7691
  return true;
@@ -6852,6 +7722,9 @@ class CakeEngine {
6852
7722
  getSelection() {
6853
7723
  return this.state.selection;
6854
7724
  }
7725
+ getCursorLength() {
7726
+ return this.state.map.cursorLength;
7727
+ }
6855
7728
  getFocusRect() {
6856
7729
  return this.lastFocusRect;
6857
7730
  }
@@ -6906,12 +7779,18 @@ class CakeEngine {
6906
7779
  if (!this.isComposing) {
6907
7780
  this.applySelection(this.state.selection);
6908
7781
  }
7782
+ this.scheduleScrollCaretIntoView();
6909
7783
  }
6910
7784
  setValue({ value, selection }) {
6911
- const nextSelection = selection ?? this.state.selection;
6912
- if (value === this.state.source && selection === void 0) {
7785
+ const valueChanged = value !== this.state.source;
7786
+ if (!valueChanged && selection === void 0) {
7787
+ return;
7788
+ }
7789
+ if (!valueChanged && selection !== void 0) {
7790
+ this.setSelection(selection);
6913
7791
  return;
6914
7792
  }
7793
+ const nextSelection = selection ?? this.state.selection;
6915
7794
  this.state = this.runtime.createState(value, nextSelection);
6916
7795
  this.render();
6917
7796
  }
@@ -6976,7 +7855,7 @@ class CakeEngine {
6976
7855
  }
6977
7856
  executeCommand(command) {
6978
7857
  var _a;
6979
- const shouldOpenLinkPopover = command.type === "wrap-link" && command.openPopover;
7858
+ const shouldOpenLinkPopover = "openPopover" in command && command.openPopover === true;
6980
7859
  const nextState = this.runtime.applyEdit(command, this.state);
6981
7860
  if (nextState === this.state) {
6982
7861
  return false;
@@ -7151,6 +8030,14 @@ class CakeEngine {
7151
8030
  this.patchedCaretRangeFromPoint = null;
7152
8031
  }
7153
8032
  render() {
8033
+ const perfEnabled = this.container.dataset.cakePerf === "1";
8034
+ let perfStart = 0;
8035
+ let renderStart = 0;
8036
+ let renderAndMapMs = 0;
8037
+ let applySelectionMs = 0;
8038
+ if (perfEnabled) {
8039
+ perfStart = performance.now();
8040
+ }
7154
8041
  if (!this.contentRoot) {
7155
8042
  const containerPosition = window.getComputedStyle(
7156
8043
  this.container
@@ -7166,16 +8053,40 @@ class CakeEngine {
7166
8053
  this.container.replaceChildren(this.contentRoot, overlay, extensionsRoot);
7167
8054
  this.attachDragListeners();
7168
8055
  }
8056
+ if (perfEnabled) {
8057
+ renderStart = performance.now();
8058
+ }
7169
8059
  const { content, map } = renderDocContent(
7170
8060
  this.state.doc,
7171
8061
  this.extensions,
7172
8062
  this.contentRoot
7173
8063
  );
7174
- this.contentRoot.replaceChildren(...content);
8064
+ const existingChildren = Array.from(this.contentRoot.childNodes);
8065
+ const needsUpdate = content.length !== existingChildren.length || content.some((node, i) => node !== existingChildren[i]);
8066
+ if (needsUpdate) {
8067
+ this.contentRoot.replaceChildren(...content);
8068
+ }
7175
8069
  this.domMap = map;
7176
- this.updateExtensionsOverlayPosition();
7177
- if (!this.isComposing) {
8070
+ if (perfEnabled) {
8071
+ renderAndMapMs = performance.now() - renderStart;
8072
+ }
8073
+ if (!this.isComposing && this.hasFocus()) {
8074
+ const selectionStart = perfEnabled ? performance.now() : 0;
7178
8075
  this.applySelection(this.state.selection);
8076
+ if (perfEnabled) {
8077
+ applySelectionMs = performance.now() - selectionStart;
8078
+ }
8079
+ }
8080
+ if (perfEnabled) {
8081
+ const totalMs = performance.now() - perfStart;
8082
+ this.lastRenderPerf = {
8083
+ totalMs,
8084
+ renderAndMapMs,
8085
+ applySelectionMs,
8086
+ didUpdateDom: needsUpdate,
8087
+ blockCount: this.state.doc.blocks.length,
8088
+ runCount: map.runs.length
8089
+ };
7179
8090
  }
7180
8091
  this.updatePlaceholder();
7181
8092
  this.scheduleOverlayUpdate();
@@ -7425,22 +8336,31 @@ class CakeEngine {
7425
8336
  this.suppressSelectionChange = false;
7426
8337
  return;
7427
8338
  }
7428
- const hit2 = this.pendingClickHit ?? this.hitTestFromClientPoint(event.clientX, event.clientY);
7429
- this.pendingClickHit = null;
7430
- if (hit2) {
8339
+ const pendingHit = this.pendingClickHit;
8340
+ const selection = this.state.selection;
8341
+ if (pendingHit && selection.start !== selection.end) {
7431
8342
  const newSelection = {
7432
- start: hit2.cursorOffset,
7433
- end: hit2.cursorOffset,
7434
- affinity: hit2.affinity
8343
+ start: pendingHit.cursorOffset,
8344
+ end: pendingHit.cursorOffset,
8345
+ affinity: pendingHit.affinity
7435
8346
  };
8347
+ this.pendingClickHit = null;
8348
+ this.suppressSelectionChange = true;
7436
8349
  this.state = this.runtime.updateSelection(this.state, newSelection, {
7437
8350
  kind: "dom"
7438
8351
  });
7439
8352
  this.applySelection(this.state.selection);
7440
8353
  (_b = this.onSelectionChange) == null ? void 0 : _b.call(this, this.state.selection);
7441
8354
  this.scheduleOverlayUpdate();
8355
+ setTimeout(() => {
8356
+ this.suppressSelectionChange = false;
8357
+ }, 0);
8358
+ return;
7442
8359
  }
7443
- this.suppressSelectionChange = false;
8360
+ this.pendingClickHit = null;
8361
+ setTimeout(() => {
8362
+ this.suppressSelectionChange = false;
8363
+ }, 0);
7444
8364
  return;
7445
8365
  }
7446
8366
  this.pendingClickHit = null;
@@ -7767,6 +8687,13 @@ class CakeEngine {
7767
8687
  }
7768
8688
  event.preventDefault();
7769
8689
  clipboardData.setData("text/plain", text);
8690
+ const html = this.runtime.serializeSelectionToHtml(
8691
+ this.state,
8692
+ this.state.selection
8693
+ );
8694
+ if (html) {
8695
+ clipboardData.setData("text/html", html);
8696
+ }
7770
8697
  }
7771
8698
  handleCut(event) {
7772
8699
  if (this.readOnly) {
@@ -7811,7 +8738,7 @@ class CakeEngine {
7811
8738
  if (!command) {
7812
8739
  continue;
7813
8740
  }
7814
- if (command.type === "insert" || command.type === "insert-line-break" || command.type === "delete-backward" || command.type === "delete-forward") {
8741
+ if (isApplyEditCommand(command)) {
7815
8742
  this.applyEdit(command);
7816
8743
  } else {
7817
8744
  this.executeCommand(command);
@@ -8331,6 +9258,39 @@ class CakeEngine {
8331
9258
  return { start: target, end: target, affinity: direction };
8332
9259
  }
8333
9260
  const currentPos = selection.start;
9261
+ const currentAffinity = selection.affinity ?? "forward";
9262
+ const measurement = this.getLayoutForNavigation();
9263
+ if (measurement) {
9264
+ const { lines, layout } = measurement;
9265
+ const { rowStart, rowEnd } = getVisualRowBoundaries({
9266
+ lines,
9267
+ layout,
9268
+ offset: currentPos,
9269
+ affinity: currentAffinity
9270
+ });
9271
+ if (direction === "backward" && currentPos === rowStart && currentAffinity === "forward" && currentPos > 0) {
9272
+ const prevBoundaries = getVisualRowBoundaries({
9273
+ lines,
9274
+ layout,
9275
+ offset: currentPos,
9276
+ affinity: "backward"
9277
+ });
9278
+ if (prevBoundaries.rowEnd !== rowEnd || prevBoundaries.rowStart !== rowStart) {
9279
+ return { start: currentPos, end: currentPos, affinity: "backward" };
9280
+ }
9281
+ }
9282
+ if (direction === "forward" && currentPos === rowEnd && currentAffinity === "backward" && currentPos < this.state.map.cursorLength) {
9283
+ const nextBoundaries = getVisualRowBoundaries({
9284
+ lines,
9285
+ layout,
9286
+ offset: currentPos,
9287
+ affinity: "forward"
9288
+ });
9289
+ if (nextBoundaries.rowEnd !== rowEnd || nextBoundaries.rowStart !== rowStart) {
9290
+ return { start: currentPos, end: currentPos, affinity: "forward" };
9291
+ }
9292
+ }
9293
+ }
8334
9294
  const nextPos = this.moveOffsetByChar(currentPos, direction);
8335
9295
  if (nextPos === null) {
8336
9296
  return null;
@@ -9020,6 +9980,7 @@ class CakeEngine {
9020
9980
  }
9021
9981
  this.overlayUpdateId = window.requestAnimationFrame(() => {
9022
9982
  this.overlayUpdateId = null;
9983
+ this.updateExtensionsOverlayPosition();
9023
9984
  this.updateSelectionOverlay();
9024
9985
  });
9025
9986
  }
@@ -9049,12 +10010,34 @@ class CakeEngine {
9049
10010
  overlay.style.zIndex = "2";
9050
10011
  const caret = document.createElement("div");
9051
10012
  caret.className = "cake-caret";
10013
+ caret.style.position = "absolute";
9052
10014
  caret.style.display = "none";
9053
10015
  overlay.append(caret);
9054
10016
  this.overlayRoot = overlay;
9055
10017
  this.caretElement = caret;
10018
+ this.selectionRectElements = [];
10019
+ this.lastSelectionRects = null;
9056
10020
  return overlay;
9057
10021
  }
10022
+ selectionRectsEqual(prev, next) {
10023
+ if (!prev) {
10024
+ return false;
10025
+ }
10026
+ if (prev.length !== next.length) {
10027
+ return false;
10028
+ }
10029
+ for (let index = 0; index < prev.length; index += 1) {
10030
+ const a = prev[index];
10031
+ const b = next[index];
10032
+ if (!a || !b) {
10033
+ return false;
10034
+ }
10035
+ if (a.top !== b.top || a.left !== b.left || a.width !== b.width || a.height !== b.height) {
10036
+ return false;
10037
+ }
10038
+ }
10039
+ return true;
10040
+ }
9058
10041
  ensureExtensionsRoot() {
9059
10042
  if (this.extensionsRoot) {
9060
10043
  return this.extensionsRoot;
@@ -9076,8 +10059,8 @@ class CakeEngine {
9076
10059
  if (!this.extensionsRoot) {
9077
10060
  return;
9078
10061
  }
9079
- const scrollTop = this.container.scrollTop;
9080
- const scrollLeft = this.container.scrollLeft;
10062
+ const scrollTop = Math.max(0, this.container.scrollTop);
10063
+ const scrollLeft = Math.max(0, this.container.scrollLeft);
9081
10064
  if (scrollTop === 0 && scrollLeft === 0) {
9082
10065
  this.extensionsRoot.style.transform = "";
9083
10066
  return;
@@ -9115,21 +10098,34 @@ class CakeEngine {
9115
10098
  if (!this.overlayRoot || !this.caretElement) {
9116
10099
  return;
9117
10100
  }
9118
- const existing = Array.from(
9119
- this.overlayRoot.querySelectorAll(".cake-selection-rect")
9120
- );
9121
- existing.forEach((node) => node.remove());
9122
- const fragment = document.createDocumentFragment();
9123
- rects.forEach((rect) => {
9124
- const element = document.createElement("div");
9125
- element.className = "cake-selection-rect";
10101
+ if (this.selectionRectsEqual(this.lastSelectionRects, rects)) {
10102
+ return;
10103
+ }
10104
+ this.lastSelectionRects = rects;
10105
+ while (this.selectionRectElements.length > rects.length) {
10106
+ const element = this.selectionRectElements.pop();
10107
+ element == null ? void 0 : element.remove();
10108
+ }
10109
+ if (this.selectionRectElements.length < rects.length) {
10110
+ const fragment = document.createDocumentFragment();
10111
+ while (this.selectionRectElements.length < rects.length) {
10112
+ const element = document.createElement("div");
10113
+ element.className = "cake-selection-rect";
10114
+ fragment.append(element);
10115
+ this.selectionRectElements.push(element);
10116
+ }
10117
+ this.overlayRoot.insertBefore(fragment, this.caretElement);
10118
+ }
10119
+ rects.forEach((rect, index) => {
10120
+ const element = this.selectionRectElements[index];
10121
+ if (!element) {
10122
+ return;
10123
+ }
9126
10124
  element.style.top = `${rect.top}px`;
9127
10125
  element.style.left = `${rect.left}px`;
9128
10126
  element.style.width = `${rect.width}px`;
9129
10127
  element.style.height = `${rect.height}px`;
9130
- fragment.append(element);
9131
10128
  });
9132
- this.overlayRoot.insertBefore(fragment, this.caretElement);
9133
10129
  }
9134
10130
  updateCaret(position) {
9135
10131
  if (!this.caretElement) {
@@ -9296,7 +10292,7 @@ class CakeEngine {
9296
10292
  return { cursorOffset: cursor.cursorOffset, affinity: "backward" };
9297
10293
  }
9298
10294
  handlePointerDown(event) {
9299
- var _a;
10295
+ var _a, _b, _c;
9300
10296
  if (!this.isEventTargetInContentRoot(event.target)) {
9301
10297
  return;
9302
10298
  }
@@ -9361,13 +10357,51 @@ class CakeEngine {
9361
10357
  }
9362
10358
  }
9363
10359
  }
9364
- if (selection.start === selection.end && !event.shiftKey) {
9365
- this.suppressSelectionChange = true;
10360
+ if (!event.shiftKey) {
9366
10361
  const hit2 = this.hitTestFromClientPoint(event.clientX, event.clientY);
9367
- if (hit2) {
10362
+ if (!hit2) {
10363
+ return;
10364
+ }
10365
+ if (selection.start !== selection.end) {
10366
+ const selStart2 = Math.min(selection.start, selection.end);
10367
+ const selEnd2 = Math.max(selection.start, selection.end);
10368
+ const clickedInsideSelection2 = hit2.cursorOffset >= selStart2 && hit2.cursorOffset <= selEnd2;
10369
+ if (clickedInsideSelection2) {
10370
+ this.suppressSelectionChange = false;
10371
+ this.blockTrustedTextDrag = true;
10372
+ this.pendingClickHit = hit2;
10373
+ } else {
10374
+ this.suppressSelectionChange = true;
10375
+ this.pendingClickHit = hit2;
10376
+ const newSelection = {
10377
+ start: hit2.cursorOffset,
10378
+ end: hit2.cursorOffset,
10379
+ affinity: hit2.affinity
10380
+ };
10381
+ this.state = this.runtime.updateSelection(this.state, newSelection, {
10382
+ kind: "dom"
10383
+ });
10384
+ this.applySelection(this.state.selection);
10385
+ (_b = this.onSelectionChange) == null ? void 0 : _b.call(this, this.state.selection);
10386
+ this.scheduleOverlayUpdate();
10387
+ return;
10388
+ }
10389
+ } else {
10390
+ this.suppressSelectionChange = true;
9368
10391
  this.pendingClickHit = hit2;
10392
+ const newSelection = {
10393
+ start: hit2.cursorOffset,
10394
+ end: hit2.cursorOffset,
10395
+ affinity: hit2.affinity
10396
+ };
10397
+ this.state = this.runtime.updateSelection(this.state, newSelection, {
10398
+ kind: "dom"
10399
+ });
10400
+ this.applySelection(this.state.selection);
10401
+ (_c = this.onSelectionChange) == null ? void 0 : _c.call(this, this.state.selection);
10402
+ this.scheduleOverlayUpdate();
10403
+ return;
9369
10404
  }
9370
- return;
9371
10405
  }
9372
10406
  this.suppressSelectionChange = false;
9373
10407
  const selStart = Math.min(selection.start, selection.end);
@@ -9479,10 +10513,6 @@ class CakeEngine {
9479
10513
  }
9480
10514
  handlePointerUp(event) {
9481
10515
  this.blockTrustedTextDrag = false;
9482
- if (this.pendingClickHit) {
9483
- this.pendingClickHit = null;
9484
- this.suppressSelectionChange = false;
9485
- }
9486
10516
  if (this.selectionDragState) {
9487
10517
  if (event.pointerId !== this.selectionDragState.pointerId) {
9488
10518
  return;
@@ -9982,7 +11012,7 @@ function findOffsetInTextNode(textNode, clientX, clientY) {
9982
11012
  let closestDistance = Infinity;
9983
11013
  let closestYDistance = Infinity;
9984
11014
  let closestCaretX = 0;
9985
- let closestRowTop = 0;
11015
+ let selectedViaRightEdge = false;
9986
11016
  const rowInfo = /* @__PURE__ */ new Map();
9987
11017
  const range = document.createRange();
9988
11018
  let lastCharRect = null;
@@ -10051,15 +11081,28 @@ function findOffsetInTextNode(textNode, clientX, clientY) {
10051
11081
  closestDistance = xDistance;
10052
11082
  closestOffset = i;
10053
11083
  closestCaretX = caretX;
10054
- closestRowTop = bestRect.top;
11084
+ bestRect.top;
10055
11085
  closestYDistance = yDistance;
11086
+ selectedViaRightEdge = false;
11087
+ }
11088
+ if (i < text.length) {
11089
+ const rightEdgeX = bestRect.right;
11090
+ const rightEdgeDistance = Math.abs(clientX - rightEdgeX);
11091
+ if (rightEdgeDistance < closestDistance) {
11092
+ closestDistance = rightEdgeDistance;
11093
+ closestOffset = i + 1;
11094
+ closestCaretX = rightEdgeX;
11095
+ bestRect.top;
11096
+ closestYDistance = yDistance;
11097
+ selectedViaRightEdge = true;
11098
+ }
10056
11099
  }
10057
11100
  } else if (yDistance < closestYDistance || yDistance === closestYDistance && xDistance < closestDistance) {
10058
11101
  closestYDistance = yDistance;
10059
11102
  closestDistance = xDistance;
10060
11103
  closestOffset = i;
10061
11104
  closestCaretX = caretX;
10062
- closestRowTop = bestRect.top;
11105
+ bestRect.top;
10063
11106
  }
10064
11107
  }
10065
11108
  let closestRow = null;
@@ -10084,11 +11127,11 @@ function findOffsetInTextNode(textNode, clientX, clientY) {
10084
11127
  if (clientX < closestRow.left) {
10085
11128
  closestOffset = closestRow.startOffset;
10086
11129
  closestCaretX = closestRow.left;
10087
- closestRowTop = closestRow.top;
11130
+ closestRow.top;
10088
11131
  } else if (clientX > closestRow.right) {
10089
- closestOffset = closestRow.endOffset;
11132
+ closestOffset = Math.min(closestRow.endOffset + 1, text.length);
10090
11133
  closestCaretX = closestRow.right;
10091
- closestRowTop = closestRow.top;
11134
+ closestRow.top;
10092
11135
  } else if (clientY < closestRow.top || clientY > closestRow.bottom) {
10093
11136
  let bestXDistance = Infinity;
10094
11137
  const range2 = document.createRange();
@@ -10108,12 +11151,12 @@ function findOffsetInTextNode(textNode, clientX, clientY) {
10108
11151
  bestXDistance = xDist;
10109
11152
  closestOffset = i;
10110
11153
  closestCaretX = rect.left;
10111
- closestRowTop = closestRow.top;
11154
+ closestRow.top;
10112
11155
  }
10113
11156
  }
10114
11157
  }
10115
11158
  }
10116
- const pastRowEnd = closestRow !== null && clientX > closestRow.right && Math.abs(clientY - closestRowTop) < 1;
11159
+ const pastRowEnd = selectedViaRightEdge || closestRow !== null && clientX > closestRow.right;
10117
11160
  if (Math.abs(clientX - closestCaretX) <= 2 && closestOffset < text.length && text[closestOffset] === "\n") {
10118
11161
  closestOffset = Math.max(0, closestOffset - 1);
10119
11162
  }
@@ -10566,8 +11609,9 @@ const CakeEditor = require$$0.forwardRef(
10566
11609
  const lastEmittedSelectionRef = require$$0.useRef(null);
10567
11610
  const [overlayRoot, setOverlayRoot] = require$$0.useState(null);
10568
11611
  const [contentRoot, setContentRoot] = require$$0.useState(null);
11612
+ const baseExtensions = props.disableImageExtension ? bundledExtensionsWithoutImage : bundledExtensions;
10569
11613
  const allExtensionsRef = require$$0.useRef([
10570
- ...bundledExtensions,
11614
+ ...baseExtensions,
10571
11615
  ...props.extensions ?? []
10572
11616
  ]);
10573
11617
  require$$0.useEffect(() => {
@@ -10725,6 +11769,10 @@ const CakeEditor = require$$0.forwardRef(
10725
11769
  }
10726
11770
  return { start: selection.start, end: selection.end };
10727
11771
  },
11772
+ getCursorLength: () => {
11773
+ var _a;
11774
+ return ((_a = engineRef.current) == null ? void 0 : _a.getCursorLength()) ?? 0;
11775
+ },
10728
11776
  insertText: (text) => {
10729
11777
  var _a;
10730
11778
  (_a = engineRef.current) == null ? void 0 : _a.insertText(text);
@@ -10740,10 +11788,10 @@ const CakeEditor = require$$0.forwardRef(
10740
11788
  containerStyle.position = "relative";
10741
11789
  }
10742
11790
  const containerClassName = props.className ? `cake ${props.className}` : "cake";
10743
- const overlayContext = overlayRoot && containerRef.current && contentRoot ? {
11791
+ const overlayContext = containerRef.current && contentRoot ? {
10744
11792
  container: containerRef.current,
10745
11793
  contentRoot,
10746
- overlayRoot,
11794
+ overlayRoot: overlayRoot ?? void 0,
10747
11795
  toOverlayRect: (rect) => {
10748
11796
  var _a;
10749
11797
  const containerRect = (_a = containerRef.current) == null ? void 0 : _a.getBoundingClientRect();
@@ -10778,10 +11826,17 @@ const CakeEditor = require$$0.forwardRef(
10778
11826
  }
10779
11827
  const focus = selection.start === selection.end ? selection.start : Math.max(selection.start, selection.end);
10780
11828
  return { start: focus, end: focus };
11829
+ },
11830
+ executeCommand: (command) => {
11831
+ var _a;
11832
+ return ((_a = engineRef.current) == null ? void 0 : _a.executeCommand(command)) ?? false;
10781
11833
  }
10782
11834
  } : null;
10783
- return /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(jsxDevRuntimeExports.Fragment, { children: [
10784
- /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(
11835
+ const hasOverlayExtensions = allExtensionsRef.current.some(
11836
+ (ext) => ext.renderOverlay
11837
+ );
11838
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { position: "relative", height: "100%" }, children: [
11839
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
10785
11840
  "div",
10786
11841
  {
10787
11842
  ref: containerRef,
@@ -10792,35 +11847,12 @@ const CakeEditor = require$$0.forwardRef(
10792
11847
  var _a;
10793
11848
  (_a = props.onBlur) == null ? void 0 : _a.call(props, event.nativeEvent);
10794
11849
  }
10795
- },
10796
- void 0,
10797
- false,
10798
- {
10799
- fileName: "/Users/moboudra/dev/blankpage/cake/src/cake/react/CakeEditor.tsx",
10800
- lineNumber: 351,
10801
- columnNumber: 9
10802
- },
10803
- this
11850
+ }
10804
11851
  ),
10805
- overlayRoot && overlayContext ? reactDom.createPortal(
10806
- /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(jsxDevRuntimeExports.Fragment, { children: allExtensionsRef.current.map(
10807
- (extension) => extension.renderOverlay ? /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(require$$0.Fragment, { children: extension.renderOverlay(overlayContext) }, extension.name, false, {
10808
- fileName: "/Users/moboudra/dev/blankpage/cake/src/cake/react/CakeEditor.tsx",
10809
- lineNumber: 365,
10810
- columnNumber: 21
10811
- }, this) : null
10812
- ) }, void 0, false, {
10813
- fileName: "/Users/moboudra/dev/blankpage/cake/src/cake/react/CakeEditor.tsx",
10814
- lineNumber: 362,
10815
- columnNumber: 15
10816
- }, this),
10817
- overlayRoot
11852
+ overlayContext && hasOverlayExtensions ? allExtensionsRef.current.map(
11853
+ (extension) => extension.renderOverlay ? /* @__PURE__ */ jsxRuntimeExports.jsx(require$$0.Fragment, { children: extension.renderOverlay(overlayContext) }, extension.name) : null
10818
11854
  ) : null
10819
- ] }, void 0, true, {
10820
- fileName: "/Users/moboudra/dev/blankpage/cake/src/cake/react/CakeEditor.tsx",
10821
- lineNumber: 350,
10822
- columnNumber: 7
10823
- }, this);
11855
+ ] });
10824
11856
  }
10825
11857
  );
10826
11858
  CakeEditor.displayName = "CakeEditor";
@@ -10829,6 +11861,7 @@ exports.CakeEngine = CakeEngine;
10829
11861
  exports.blockquoteExtension = blockquoteExtension;
10830
11862
  exports.boldExtension = boldExtension;
10831
11863
  exports.bundledExtensions = bundledExtensions;
11864
+ exports.bundledExtensionsWithoutImage = bundledExtensionsWithoutImage;
10832
11865
  exports.combinedEmphasisExtension = combinedEmphasisExtension;
10833
11866
  exports.headingExtension = headingExtension;
10834
11867
  exports.imageExtension = imageExtension;