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