@blankdotpage/cake 0.1.1 → 0.1.2

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
  });
@@ -2409,13 +2415,150 @@ function createRuntime(extensions) {
2409
2415
  const endInLine = lineIndex === endLoc.lineIndex ? endLoc.offsetInLine : line.cursorLength;
2410
2416
  const selectedRuns = sliceRuns(runs, startInLine, endInLine).selected;
2411
2417
  const content = runsToInlines(normalizeRuns(selectedRuns));
2412
- blocks.push({ type: "paragraph", content });
2418
+ const paragraph = { type: "paragraph", content };
2419
+ if (line.path.length > 1) {
2420
+ const wrapperPath = line.path.slice(0, -1);
2421
+ const wrapper = getBlockAtPath(state.doc.blocks, wrapperPath);
2422
+ if (wrapper && wrapper.type === "block-wrapper") {
2423
+ blocks.push({
2424
+ type: "block-wrapper",
2425
+ kind: wrapper.kind,
2426
+ data: wrapper.data,
2427
+ blocks: [paragraph]
2428
+ });
2429
+ continue;
2430
+ }
2431
+ }
2432
+ blocks.push(paragraph);
2413
2433
  }
2414
2434
  const sliceDoc = {
2415
2435
  blocks: blocks.length > 0 ? blocks : [{ type: "paragraph", content: [] }]
2416
2436
  };
2417
2437
  return serialize(normalize(sliceDoc)).source;
2418
2438
  }
2439
+ function escapeHtml(text) {
2440
+ return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2441
+ }
2442
+ function runsToHtml(runs) {
2443
+ var _a;
2444
+ let html = "";
2445
+ for (const run of runs) {
2446
+ let content = escapeHtml(run.text);
2447
+ const sortedMarks = [...run.marks].reverse();
2448
+ for (const mark of sortedMarks) {
2449
+ if (mark.kind === "bold") {
2450
+ content = `<strong>${content}</strong>`;
2451
+ } else if (mark.kind === "italic") {
2452
+ content = `<em>${content}</em>`;
2453
+ } else if (mark.kind === "strikethrough") {
2454
+ content = `<s>${content}</s>`;
2455
+ } else if (mark.kind === "link") {
2456
+ const url = ((_a = mark.data) == null ? void 0 : _a.url) ?? "";
2457
+ content = `<a href="${escapeHtml(url)}">${content}</a>`;
2458
+ }
2459
+ }
2460
+ html += content;
2461
+ }
2462
+ return html;
2463
+ }
2464
+ function serializeSelectionToHtml(state, selection) {
2465
+ const normalized = normalizeSelection$1(selection);
2466
+ const lines = flattenDocToLines(state.doc);
2467
+ const docCursorLength = cursorLengthForLines(lines);
2468
+ const cursorStart = Math.max(
2469
+ 0,
2470
+ Math.min(docCursorLength, Math.min(normalized.start, normalized.end))
2471
+ );
2472
+ const cursorEnd = Math.max(
2473
+ 0,
2474
+ Math.min(docCursorLength, Math.max(normalized.start, normalized.end))
2475
+ );
2476
+ if (cursorStart === cursorEnd) {
2477
+ return "";
2478
+ }
2479
+ const startLoc = resolveCursorToLine(lines, cursorStart);
2480
+ const endLoc = resolveCursorToLine(lines, cursorEnd);
2481
+ let html = "";
2482
+ let activeList = null;
2483
+ const closeList = () => {
2484
+ if (activeList) {
2485
+ html += `</${activeList.type}>`;
2486
+ activeList = null;
2487
+ }
2488
+ };
2489
+ const openList = (type, indent) => {
2490
+ if (activeList && activeList.type === type && activeList.indent === indent) {
2491
+ return;
2492
+ }
2493
+ closeList();
2494
+ html += `<${type}>`;
2495
+ activeList = { type, indent };
2496
+ };
2497
+ for (let lineIndex = startLoc.lineIndex; lineIndex <= endLoc.lineIndex; lineIndex += 1) {
2498
+ const line = lines[lineIndex];
2499
+ if (!line) {
2500
+ continue;
2501
+ }
2502
+ const block = getBlockAtPath(state.doc.blocks, line.path);
2503
+ if (!block || block.type !== "paragraph") {
2504
+ continue;
2505
+ }
2506
+ const runs = paragraphToRuns(block);
2507
+ const startInLine = lineIndex === startLoc.lineIndex ? startLoc.offsetInLine : 0;
2508
+ const endInLine = lineIndex === endLoc.lineIndex ? endLoc.offsetInLine : line.cursorLength;
2509
+ const selectedRuns = sliceRuns(runs, startInLine, endInLine).selected;
2510
+ let wrapperKind = null;
2511
+ let wrapperData;
2512
+ if (line.path.length > 1) {
2513
+ const wrapperPath = line.path.slice(0, -1);
2514
+ const wrapper = getBlockAtPath(state.doc.blocks, wrapperPath);
2515
+ if (wrapper && wrapper.type === "block-wrapper") {
2516
+ wrapperKind = wrapper.kind;
2517
+ wrapperData = wrapper.data;
2518
+ }
2519
+ }
2520
+ const plainText = runs.map((r) => r.text).join("");
2521
+ const listMatch = plainText.match(/^(\s*)([-*+]|\d+\.)( )(.*)$/);
2522
+ let lineHtml;
2523
+ if (listMatch && !wrapperKind) {
2524
+ const prefixLength = listMatch[1].length + listMatch[2].length + listMatch[3].length;
2525
+ const contentRuns = sliceRuns(runs, prefixLength, runs.reduce((sum, r) => sum + r.text.length, 0)).selected;
2526
+ lineHtml = runsToHtml(normalizeRuns(contentRuns));
2527
+ } else {
2528
+ lineHtml = runsToHtml(normalizeRuns(selectedRuns));
2529
+ }
2530
+ if (wrapperKind === "heading") {
2531
+ closeList();
2532
+ const level = Math.min(
2533
+ (wrapperData == null ? void 0 : wrapperData.level) ?? 1,
2534
+ 6
2535
+ );
2536
+ html += `<h${level} style="margin:0">${lineHtml}</h${level}>`;
2537
+ } else if (wrapperKind === "bullet-list") {
2538
+ openList("ul", 0);
2539
+ html += `<li>${lineHtml}</li>`;
2540
+ } else if (wrapperKind === "numbered-list") {
2541
+ openList("ol", 0);
2542
+ html += `<li>${lineHtml}</li>`;
2543
+ } else if (wrapperKind === "blockquote") {
2544
+ closeList();
2545
+ html += `<blockquote>${lineHtml}</blockquote>`;
2546
+ } else if (listMatch) {
2547
+ const isNumbered = /^\d+\.$/.test(listMatch[2]);
2548
+ const indent = Math.floor(listMatch[1].length / 2);
2549
+ openList(isNumbered ? "ol" : "ul", indent);
2550
+ html += `<li>${lineHtml}</li>`;
2551
+ } else {
2552
+ closeList();
2553
+ html += `<div>${lineHtml}</div>`;
2554
+ }
2555
+ }
2556
+ closeList();
2557
+ if (!html) {
2558
+ return "";
2559
+ }
2560
+ return `<div>${html}</div>`;
2561
+ }
2419
2562
  const runtime = {
2420
2563
  extensions,
2421
2564
  parse,
@@ -2423,6 +2566,7 @@ function createRuntime(extensions) {
2423
2566
  createState,
2424
2567
  updateSelection,
2425
2568
  serializeSelection,
2569
+ serializeSelectionToHtml,
2426
2570
  applyEdit
2427
2571
  };
2428
2572
  return runtime;
@@ -2577,7 +2721,7 @@ function normalizeNodes(result) {
2577
2721
  }
2578
2722
  return Array.isArray(result) ? result : [result];
2579
2723
  }
2580
- function renderDocContent(doc, extensions, _root) {
2724
+ function renderDocContent(doc, extensions, root) {
2581
2725
  const runs = [];
2582
2726
  let cursorOffset = 0;
2583
2727
  let lineIndex = 0;
@@ -2597,7 +2741,60 @@ function renderDocContent(doc, extensions, _root) {
2597
2741
  lineIndex += 1;
2598
2742
  }
2599
2743
  };
2600
- function renderInline(inline) {
2744
+ function getBlockKey(block) {
2745
+ if (block.type === "paragraph") {
2746
+ return "paragraph";
2747
+ }
2748
+ if (block.type === "block-wrapper") {
2749
+ return `block-wrapper:${block.kind}`;
2750
+ }
2751
+ if (block.type === "block-atom") {
2752
+ return `block-atom:${block.kind}`;
2753
+ }
2754
+ return "unknown";
2755
+ }
2756
+ function getElementKey(element) {
2757
+ if (element.hasAttribute("data-block")) {
2758
+ const blockType = element.getAttribute("data-block") ?? "unknown";
2759
+ const lineKind = element instanceof HTMLElement ? element.dataset.lineKind : null;
2760
+ if (lineKind && lineKind !== blockType) {
2761
+ return lineKind;
2762
+ }
2763
+ return blockType;
2764
+ }
2765
+ if (element.hasAttribute("data-block-wrapper")) {
2766
+ return `block-wrapper:${element.getAttribute("data-block-wrapper")}`;
2767
+ }
2768
+ if (element.hasAttribute("data-block-atom")) {
2769
+ return `block-atom:${element.getAttribute("data-block-atom")}`;
2770
+ }
2771
+ return "unknown";
2772
+ }
2773
+ function getInlineKey(inline) {
2774
+ if (inline.type === "text") {
2775
+ return "text";
2776
+ }
2777
+ if (inline.type === "inline-wrapper") {
2778
+ return `inline-wrapper:${inline.kind}`;
2779
+ }
2780
+ if (inline.type === "inline-atom") {
2781
+ return `inline-atom:${inline.kind}`;
2782
+ }
2783
+ return "unknown";
2784
+ }
2785
+ function getInlineElementKey(element) {
2786
+ if (element.classList.contains("cake-text")) {
2787
+ return "text";
2788
+ }
2789
+ if (element.hasAttribute("data-inline")) {
2790
+ return `inline-wrapper:${element.getAttribute("data-inline")}`;
2791
+ }
2792
+ if (element.hasAttribute("data-inline-atom")) {
2793
+ return `inline-atom:${element.getAttribute("data-inline-atom")}`;
2794
+ }
2795
+ return "unknown";
2796
+ }
2797
+ function reconcileInline(inline, existing) {
2601
2798
  for (const extension of extensions) {
2602
2799
  const render = extension.renderInline;
2603
2800
  if (!render) {
@@ -2609,6 +2806,17 @@ function renderDocContent(doc, extensions, _root) {
2609
2806
  }
2610
2807
  }
2611
2808
  if (inline.type === "text") {
2809
+ const canReuse = existing && existing instanceof HTMLSpanElement && getInlineElementKey(existing) === "text";
2810
+ if (canReuse) {
2811
+ const textNode = existing.firstChild;
2812
+ if (textNode instanceof Text) {
2813
+ if (textNode.textContent !== inline.text) {
2814
+ textNode.textContent = inline.text;
2815
+ }
2816
+ createTextRun$1(textNode);
2817
+ return [existing];
2818
+ }
2819
+ }
2612
2820
  const element = document.createElement("span");
2613
2821
  element.className = "cake-text";
2614
2822
  const node = document.createTextNode(inline.text);
@@ -2617,16 +2825,29 @@ function renderDocContent(doc, extensions, _root) {
2617
2825
  return [element];
2618
2826
  }
2619
2827
  if (inline.type === "inline-wrapper") {
2828
+ const canReuse = existing && existing instanceof HTMLSpanElement && getInlineElementKey(existing) === getInlineKey(inline);
2829
+ if (canReuse) {
2830
+ reconcileInlineChildren(existing, inline.children);
2831
+ return [existing];
2832
+ }
2620
2833
  const element = document.createElement("span");
2621
2834
  element.setAttribute("data-inline", inline.kind);
2622
2835
  for (const child of inline.children) {
2623
- for (const node of renderInline(child)) {
2836
+ for (const node of reconcileInline(child, null)) {
2624
2837
  element.append(node);
2625
2838
  }
2626
2839
  }
2627
2840
  return [element];
2628
2841
  }
2629
2842
  if (inline.type === "inline-atom") {
2843
+ const canReuse = existing && existing instanceof HTMLSpanElement && getInlineElementKey(existing) === getInlineKey(inline);
2844
+ if (canReuse) {
2845
+ const textNode = existing.firstChild;
2846
+ if (textNode instanceof Text) {
2847
+ createTextRun$1(textNode);
2848
+ return [existing];
2849
+ }
2850
+ }
2630
2851
  const element = document.createElement("span");
2631
2852
  element.setAttribute("data-inline-atom", inline.kind);
2632
2853
  const node = document.createTextNode(" ");
@@ -2636,7 +2857,25 @@ function renderDocContent(doc, extensions, _root) {
2636
2857
  }
2637
2858
  return [];
2638
2859
  }
2639
- function renderBlock(block) {
2860
+ function reconcileInlineChildren(parent, inlines) {
2861
+ const mergedInlines = mergeInlineForRender(inlines);
2862
+ const existingChildren2 = Array.from(parent.children);
2863
+ const newChildren = [];
2864
+ mergedInlines.forEach((inline, i) => {
2865
+ const existingChild = existingChildren2[i] ?? null;
2866
+ const canReuse = existingChild && getInlineElementKey(existingChild) === getInlineKey(inline);
2867
+ const nodes = reconcileInline(inline, canReuse ? existingChild : null);
2868
+ newChildren.push(...nodes);
2869
+ });
2870
+ if (newChildren.length === existingChildren2.length && newChildren.every((node, i) => node === existingChildren2[i])) {
2871
+ return;
2872
+ }
2873
+ parent.replaceChildren(...newChildren);
2874
+ }
2875
+ function renderInline(inline) {
2876
+ return reconcileInline(inline, null);
2877
+ }
2878
+ function reconcileBlock(block, existing) {
2640
2879
  for (const extension of extensions) {
2641
2880
  const render = extension.renderBlock;
2642
2881
  if (!render) {
@@ -2648,12 +2887,35 @@ function renderDocContent(doc, extensions, _root) {
2648
2887
  }
2649
2888
  }
2650
2889
  if (block.type === "paragraph") {
2890
+ const canReuse = existing && existing instanceof HTMLDivElement && getElementKey(existing) === "paragraph";
2891
+ const currentLineIndex = context.getLineIndex();
2892
+ context.incrementLineIndex();
2893
+ if (canReuse) {
2894
+ existing.setAttribute("data-line-index", String(currentLineIndex));
2895
+ if (block.content.length === 0) {
2896
+ const firstChild = existing.firstChild;
2897
+ if (firstChild instanceof Text && existing.querySelector("br")) {
2898
+ if (firstChild.textContent !== "") {
2899
+ firstChild.textContent = "";
2900
+ }
2901
+ createTextRun$1(firstChild);
2902
+ return [existing];
2903
+ }
2904
+ existing.replaceChildren();
2905
+ const textNode = document.createTextNode("");
2906
+ createTextRun$1(textNode);
2907
+ existing.append(textNode);
2908
+ existing.append(document.createElement("br"));
2909
+ return [existing];
2910
+ }
2911
+ reconcileInlineChildren(existing, block.content);
2912
+ return [existing];
2913
+ }
2651
2914
  const element = document.createElement("div");
2652
2915
  element.setAttribute("data-block", "paragraph");
2653
- element.setAttribute("data-line-index", String(context.getLineIndex()));
2916
+ element.setAttribute("data-line-index", String(currentLineIndex));
2654
2917
  element.classList.add("cake-line");
2655
2918
  element.dataset.lineKind = "paragraph";
2656
- context.incrementLineIndex();
2657
2919
  if (block.content.length === 0) {
2658
2920
  const textNode = document.createTextNode("");
2659
2921
  createTextRun$1(textNode);
@@ -2662,7 +2924,7 @@ function renderDocContent(doc, extensions, _root) {
2662
2924
  } else {
2663
2925
  const mergedContent = mergeInlineForRender(block.content);
2664
2926
  for (const inline of mergedContent) {
2665
- for (const node of renderInline(inline)) {
2927
+ for (const node of reconcileInline(inline, null)) {
2666
2928
  element.append(node);
2667
2929
  }
2668
2930
  }
@@ -2670,6 +2932,11 @@ function renderDocContent(doc, extensions, _root) {
2670
2932
  return [element];
2671
2933
  }
2672
2934
  if (block.type === "block-wrapper") {
2935
+ const canReuse = existing && existing instanceof HTMLDivElement && getElementKey(existing) === getBlockKey(block);
2936
+ if (canReuse) {
2937
+ reconcileBlockChildren(existing, block.blocks);
2938
+ return [existing];
2939
+ }
2673
2940
  const element = document.createElement("div");
2674
2941
  element.setAttribute("data-block-wrapper", block.kind);
2675
2942
  for (const node of renderBlocks(block.blocks)) {
@@ -2678,15 +2945,41 @@ function renderDocContent(doc, extensions, _root) {
2678
2945
  return [element];
2679
2946
  }
2680
2947
  if (block.type === "block-atom") {
2948
+ const canReuse = existing && existing instanceof HTMLDivElement && getElementKey(existing) === getBlockKey(block);
2949
+ const currentLineIndex = context.getLineIndex();
2950
+ context.incrementLineIndex();
2951
+ if (canReuse) {
2952
+ existing.setAttribute("data-line-index", String(currentLineIndex));
2953
+ return [existing];
2954
+ }
2681
2955
  const element = document.createElement("div");
2682
2956
  element.setAttribute("data-block-atom", block.kind);
2683
- element.setAttribute("data-line-index", String(context.getLineIndex()));
2957
+ element.setAttribute("data-line-index", String(currentLineIndex));
2684
2958
  element.classList.add("cake-line");
2685
- context.incrementLineIndex();
2686
2959
  return [element];
2687
2960
  }
2688
2961
  return [];
2689
2962
  }
2963
+ function reconcileBlockChildren(parent, blocks) {
2964
+ const existingChildren2 = Array.from(parent.children);
2965
+ const newChildren = [];
2966
+ blocks.forEach((block, index) => {
2967
+ const existingChild = existingChildren2[index] ?? null;
2968
+ const canReuse = existingChild && getElementKey(existingChild) === getBlockKey(block);
2969
+ const nodes = reconcileBlock(block, canReuse ? existingChild : null);
2970
+ newChildren.push(...nodes);
2971
+ if (index < blocks.length - 1) {
2972
+ cursorOffset += 1;
2973
+ }
2974
+ });
2975
+ if (newChildren.length === existingChildren2.length && newChildren.every((node, i) => node === existingChildren2[i])) {
2976
+ return;
2977
+ }
2978
+ parent.replaceChildren(...newChildren);
2979
+ }
2980
+ function renderBlock(block) {
2981
+ return reconcileBlock(block, null);
2982
+ }
2690
2983
  function renderBlocks(blocks) {
2691
2984
  const nodes = [];
2692
2985
  blocks.forEach((block, index) => {
@@ -2697,10 +2990,17 @@ function renderDocContent(doc, extensions, _root) {
2697
2990
  });
2698
2991
  return nodes;
2699
2992
  }
2993
+ const existingChildren = root ? Array.from(root.children) : [];
2700
2994
  const contentNodes = [];
2701
- for (const node of renderBlocks(doc.blocks)) {
2702
- contentNodes.push(node);
2703
- }
2995
+ doc.blocks.forEach((block, index) => {
2996
+ const existingChild = existingChildren[index] ?? null;
2997
+ const canReuse = existingChild && getElementKey(existingChild) === getBlockKey(block);
2998
+ const nodes = reconcileBlock(block, canReuse ? existingChild : null);
2999
+ contentNodes.push(...nodes);
3000
+ if (index < doc.blocks.length - 1) {
3001
+ cursorOffset += 1;
3002
+ }
3003
+ });
2704
3004
  return { content: contentNodes, map: createDomMap(runs) };
2705
3005
  }
2706
3006
  function mergeInlineForRender(inlines) {
@@ -3222,13 +3522,13 @@ function CakeLinkPopover(params) {
3222
3522
  if (state.status !== "open") {
3223
3523
  return;
3224
3524
  }
3225
- container.addEventListener("scroll", reposition, { passive: true });
3525
+ container.addEventListener("scroll", close, { passive: true });
3226
3526
  window.addEventListener("resize", reposition);
3227
3527
  return () => {
3228
- container.removeEventListener("scroll", reposition);
3528
+ container.removeEventListener("scroll", close);
3229
3529
  window.removeEventListener("resize", reposition);
3230
3530
  };
3231
- }, [container, reposition, state.status]);
3531
+ }, [close, container, reposition, state.status]);
3232
3532
  const handleMouseDown = useCallback(
3233
3533
  (event) => {
3234
3534
  event.stopPropagation();
@@ -3297,7 +3597,7 @@ function CakeLinkPopover(params) {
3297
3597
  handleCancel();
3298
3598
  }
3299
3599
  };
3300
- return /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(
3600
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
3301
3601
  "div",
3302
3602
  {
3303
3603
  className: "cake-link-popover",
@@ -3310,7 +3610,7 @@ function CakeLinkPopover(params) {
3310
3610
  },
3311
3611
  onMouseDown: handleMouseDown,
3312
3612
  onClick: (event) => event.stopPropagation(),
3313
- children: state.isEditing ? /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(
3613
+ children: state.isEditing ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
3314
3614
  "form",
3315
3615
  {
3316
3616
  className: "cake-link-editor",
@@ -3319,7 +3619,7 @@ function CakeLinkPopover(params) {
3319
3619
  handleSave();
3320
3620
  },
3321
3621
  children: [
3322
- /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(
3622
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3323
3623
  "input",
3324
3624
  {
3325
3625
  className: "cake-link-input",
@@ -3336,56 +3636,24 @@ function CakeLinkPopover(params) {
3336
3636
  },
3337
3637
  onKeyDown: handleInputKeyDown,
3338
3638
  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
3639
+ }
3348
3640
  ),
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(
3641
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "submit", className: "cake-link-save", children: "Save" }),
3642
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3355
3643
  "button",
3356
3644
  {
3357
3645
  type: "button",
3358
3646
  className: "cake-link-cancel",
3359
3647
  onClick: handleCancel,
3360
3648
  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
3649
+ }
3370
3650
  )
3371
3651
  ]
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(
3652
+ }
3653
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3654
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "cake-link-url", title: displayUrl, children: displayUrl }),
3655
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "cake-link-actions", children: [
3656
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3389
3657
  "button",
3390
3658
  {
3391
3659
  type: "button",
@@ -3393,22 +3661,10 @@ function CakeLinkPopover(params) {
3393
3661
  onClick: handleEdit,
3394
3662
  title: "Edit link",
3395
3663
  "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
3664
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(Pencil, { className: "cake-link-icon" })
3665
+ }
3410
3666
  ),
3411
- /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(
3667
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3412
3668
  "button",
3413
3669
  {
3414
3670
  type: "button",
@@ -3416,22 +3672,10 @@ function CakeLinkPopover(params) {
3416
3672
  onClick: handleOpen,
3417
3673
  title: "Open link",
3418
3674
  "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
3675
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "cake-link-icon" })
3676
+ }
3433
3677
  ),
3434
- /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(
3678
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3435
3679
  "button",
3436
3680
  {
3437
3681
  type: "button",
@@ -3439,40 +3683,12 @@ function CakeLinkPopover(params) {
3439
3683
  onClick: handleUnlink,
3440
3684
  title: "Remove link",
3441
3685
  "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
3686
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(Unlink, { className: "cake-link-icon" })
3687
+ }
3456
3688
  )
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
3689
+ ] })
3690
+ ] })
3691
+ }
3476
3692
  );
3477
3693
  }
3478
3694
  function buildLayoutModel(lines, measurer) {
@@ -3821,21 +4037,13 @@ const linkExtension = {
3821
4037
  if (!context.contentRoot || !context.toOverlayRect) {
3822
4038
  return null;
3823
4039
  }
3824
- return /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(
4040
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
3825
4041
  CakeLinkPopover,
3826
4042
  {
3827
4043
  container: context.container,
3828
4044
  contentRoot: context.contentRoot,
3829
4045
  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
4046
+ }
3839
4047
  );
3840
4048
  }
3841
4049
  };
@@ -5335,6 +5543,7 @@ function ScrollbarOverlay({ container }) {
5335
5543
  const [isDragging, setIsDragging] = useState(false);
5336
5544
  const [isHovered, setIsHovered] = useState(false);
5337
5545
  const [isScrolling, setIsScrolling] = useState(false);
5546
+ const [isDarkMode, setIsDarkMode] = useState(false);
5338
5547
  const dragStartRef = useRef(
5339
5548
  null
5340
5549
  );
@@ -5349,6 +5558,24 @@ function ScrollbarOverlay({ container }) {
5349
5558
  );
5350
5559
  const maxScrollTop = scrollHeight - clientHeight;
5351
5560
  const thumbTop = maxScrollTop > 0 ? TRACK_PADDING + scrollTop / maxScrollTop * (trackHeight - thumbHeight) : TRACK_PADDING;
5561
+ useEffect(() => {
5562
+ function checkDarkMode() {
5563
+ const html = document.documentElement;
5564
+ setIsDarkMode(html.classList.contains("dark"));
5565
+ }
5566
+ checkDarkMode();
5567
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
5568
+ mediaQuery.addEventListener("change", checkDarkMode);
5569
+ const observer = new MutationObserver(checkDarkMode);
5570
+ observer.observe(document.documentElement, {
5571
+ attributes: true,
5572
+ attributeFilter: ["class"]
5573
+ });
5574
+ return () => {
5575
+ mediaQuery.removeEventListener("change", checkDarkMode);
5576
+ observer.disconnect();
5577
+ };
5578
+ }, []);
5352
5579
  useEffect(() => {
5353
5580
  function update() {
5354
5581
  setState({
@@ -5486,6 +5713,13 @@ function ScrollbarOverlay({ container }) {
5486
5713
  return null;
5487
5714
  }
5488
5715
  const isVisible = isDragging || isHovered || isScrolling;
5716
+ const wrapperStyle = {
5717
+ position: "absolute",
5718
+ inset: 0,
5719
+ pointerEvents: "none",
5720
+ overflow: "hidden",
5721
+ zIndex: 50
5722
+ };
5489
5723
  const trackStyle = {
5490
5724
  position: "absolute",
5491
5725
  top: 0,
@@ -5495,6 +5729,12 @@ function ScrollbarOverlay({ container }) {
5495
5729
  height: clientHeight,
5496
5730
  pointerEvents: "auto"
5497
5731
  };
5732
+ const getThumbColor = () => {
5733
+ if (isDarkMode) {
5734
+ return isDragging ? "rgba(255, 255, 255, 0.5)" : "rgba(255, 255, 255, 0.3)";
5735
+ }
5736
+ return isDragging ? "rgba(0, 0, 0, 0.5)" : "rgba(0, 0, 0, 0.3)";
5737
+ };
5498
5738
  const thumbStyle = {
5499
5739
  position: "absolute",
5500
5740
  right: "2px",
@@ -5502,54 +5742,35 @@ function ScrollbarOverlay({ container }) {
5502
5742
  borderRadius: "9999px",
5503
5743
  height: thumbHeight,
5504
5744
  top: thumbTop,
5505
- opacity: isVisible ? 1 : 0
5745
+ opacity: isVisible ? 1 : 0,
5746
+ backgroundColor: getThumbColor(),
5747
+ transition: "opacity 150ms",
5748
+ cursor: "pointer"
5506
5749
  };
5507
- return /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(
5750
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: wrapperStyle, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
5508
5751
  "div",
5509
5752
  {
5510
5753
  "data-testid": "custom-scrollbar",
5511
5754
  "aria-hidden": "true",
5512
- className: "pointer-events-auto absolute top-0 right-0 bottom-0 z-50 w-3",
5513
5755
  style: trackStyle,
5514
5756
  onClick: handleTrackClick,
5515
5757
  onMouseEnter: () => setIsHovered(true),
5516
5758
  onMouseLeave: () => setIsHovered(false),
5517
- children: /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(
5759
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
5518
5760
  "div",
5519
5761
  {
5520
5762
  "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
5763
  style: thumbStyle,
5523
5764
  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
5765
+ }
5533
5766
  )
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
- );
5767
+ }
5768
+ ) });
5544
5769
  }
5545
5770
  const scrollbarExtension = {
5546
5771
  name: "scrollbar",
5547
5772
  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);
5773
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollbarOverlay, { container: context.container });
5553
5774
  }
5554
5775
  };
5555
5776
  const STRIKE_KIND = "strikethrough";
@@ -6067,7 +6288,7 @@ function computeSelectionRects(layout, selection, measurer) {
6067
6288
  return rects;
6068
6289
  }
6069
6290
  function computeCaretRect(caret) {
6070
- const height = caret.lineRect.height;
6291
+ const height = caret.fontSize * 1.2;
6071
6292
  const contentHeight = caret.lineRect.height > 0 ? Math.max(
6072
6293
  0,
6073
6294
  caret.lineRect.height - caret.padding.top - caret.padding.bottom
@@ -6121,6 +6342,7 @@ function getSelectionGeometry(params) {
6121
6342
  scroll
6122
6343
  }),
6123
6344
  lineLength: lineInfo.cursorLength,
6345
+ fontSize: getComputedFontSize(lineElement),
6124
6346
  padding: getComputedVerticalPadding(lineElement)
6125
6347
  };
6126
6348
  const caretRect = computeCaretRect(caretMeasurement);
@@ -6164,6 +6386,7 @@ function getSelectionGeometry(params) {
6164
6386
  scroll
6165
6387
  }),
6166
6388
  lineLength: lineInfo.cursorLength,
6389
+ fontSize: getComputedFontSize(focusLineElement),
6167
6390
  padding: getComputedVerticalPadding(focusLineElement)
6168
6391
  };
6169
6392
  focusRect = computeCaretRect(caretMeasurement);
@@ -6290,6 +6513,11 @@ function getComputedVerticalPadding(lineElement) {
6290
6513
  bottom: Number.isFinite(bottom) ? bottom : 0
6291
6514
  };
6292
6515
  }
6516
+ function getComputedFontSize(lineElement) {
6517
+ const fontSize = window.getComputedStyle(lineElement).fontSize;
6518
+ const parsed = Number.parseFloat(fontSize);
6519
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 16;
6520
+ }
6293
6521
  function rectRight(rect) {
6294
6522
  return rect.left + rect.width;
6295
6523
  }
@@ -6687,6 +6915,36 @@ function nextWordBreak(text, offset) {
6687
6915
  }
6688
6916
  return text.length;
6689
6917
  }
6918
+ const MAX_HTML_INPUT_LENGTH = 5e5;
6919
+ const MAX_MARKDOWN_OUTPUT_LENGTH = 1e5;
6920
+ function isHTMLElement(node) {
6921
+ return node !== null && node.nodeType === Node.ELEMENT_NODE;
6922
+ }
6923
+ function isElement(node) {
6924
+ return node !== null && node.nodeType === Node.ELEMENT_NODE;
6925
+ }
6926
+ const CLEANUP_PATTERNS = {
6927
+ unescapeHeaders: /^\\(#{1,6})\s+/gm,
6928
+ unescapeBlockquote: /^\\>/gm,
6929
+ unescapeMarkdown: /\\([*_`~[\]])/g,
6930
+ unescapeListBullets: /^(\s*)\\([-*+])(\s+)/gm,
6931
+ unescapeListNumbers: /^(\s*)(\d+)\\\.(\s+)/gm,
6932
+ normalizeBullets: /^(\s*)[-*+](\s{2,})/gm,
6933
+ normalizeNumbers: /^[\s]*\d+\.[\s]+/gm,
6934
+ normalizeHeaders: /^(#{1,6})[\s]{2,}/gm,
6935
+ removeTrailingSpaces: /[ \t]+$/gm,
6936
+ headersInBlockquotes: /^>\s*(#{1,6}\s+.*)/gm,
6937
+ excessiveNewlines: /\n{3,}/g,
6938
+ interlacedTableGaps: /(\|[^\n]*\|)\s*\n\s*\n+\s*(\|[^\n]*\|)/g,
6939
+ complexTableGaps: /(\|[^\n]*\|)(\s*\n){2,}(\|[^\n]*\|)/g
6940
+ };
6941
+ const HTML_PREPROCESSING_PATTERNS = {
6942
+ removeStyleAndDataAttrs: /\s(?:style|data-[^=]*|id)="[^"]*"/gi,
6943
+ removeNonCodeClasses: /\sclass="(?![^"]*(?:language-|hljs))[^"]*"/gi,
6944
+ removeEmptyElements: /<(\w+)[^>]*>\s*<\/\1>/gi,
6945
+ normalizeSpaces: /[ \t]{2,}/g,
6946
+ reduceBlankLines: /\n\s*\n/g
6947
+ };
6690
6948
  const turndownService = new TurndownService({
6691
6949
  headingStyle: "atx",
6692
6950
  bulletListMarker: "-",
@@ -6704,6 +6962,13 @@ turndownService.addRule("strikethrough", {
6704
6962
  turndownService.addRule("codeBlock", {
6705
6963
  filter: "pre",
6706
6964
  replacement: (content, node) => {
6965
+ if (!isHTMLElement(node)) {
6966
+ return `
6967
+ \`\`\`
6968
+ ${content}
6969
+ \`\`\`
6970
+ `;
6971
+ }
6707
6972
  const codeElement = node.querySelector("code");
6708
6973
  if (codeElement) {
6709
6974
  const className = codeElement.className || "";
@@ -6722,12 +6987,308 @@ ${content}
6722
6987
  `;
6723
6988
  }
6724
6989
  });
6725
- function htmlToMarkdownForPaste(html) {
6726
- const trimmed = html.trim();
6727
- if (!trimmed) {
6990
+ turndownService.addRule("tableRow", {
6991
+ filter: "tr",
6992
+ replacement: (_content, node) => {
6993
+ var _a;
6994
+ if (!isHTMLElement(node)) {
6995
+ return "";
6996
+ }
6997
+ const isHeaderRow = ((_a = node.parentNode) == null ? void 0 : _a.nodeName) === "THEAD";
6998
+ const cells = Array.from(node.querySelectorAll("td, th"));
6999
+ const cellContents = cells.map((cell) => {
7000
+ const text = cell.textContent || "";
7001
+ return text.replace(/\|/g, "\\|").trim();
7002
+ });
7003
+ let result = "| " + cellContents.join(" | ") + " |\n";
7004
+ if (isHeaderRow) {
7005
+ const separators = cells.map((cell) => {
7006
+ const align = cell.getAttribute("align");
7007
+ if (align === "center") {
7008
+ return ":-------------:";
7009
+ }
7010
+ if (align === "right") {
7011
+ return "-------------:";
7012
+ }
7013
+ return "-------------";
7014
+ });
7015
+ result += "| " + separators.join(" | ") + " |\n";
7016
+ }
7017
+ return result;
7018
+ }
7019
+ });
7020
+ turndownService.addRule("taskList", {
7021
+ filter: (node) => {
7022
+ if (!isHTMLElement(node)) {
7023
+ return false;
7024
+ }
7025
+ if (node.nodeName !== "LI") {
7026
+ return false;
7027
+ }
7028
+ if (node.querySelector('input[type="checkbox"]') !== null) {
7029
+ return true;
7030
+ }
7031
+ const textContent = node.textContent || "";
7032
+ const hasCheckboxSymbols = /^[\s]*[☐☑✓✗[\]]/m.test(textContent) || /^[\s]*\[[ x]\]/m.test(textContent);
7033
+ const hasCheckboxClass = Boolean(
7034
+ node.className && (node.className.includes("task") || node.className.includes("checkbox") || node.className.includes("todo"))
7035
+ );
7036
+ return hasCheckboxSymbols || hasCheckboxClass;
7037
+ },
7038
+ replacement: (content, node) => {
7039
+ if (!isHTMLElement(node)) {
7040
+ return content;
7041
+ }
7042
+ const checkbox = node.querySelector('input[type="checkbox"]');
7043
+ const isCheckbox = checkbox instanceof HTMLInputElement && checkbox.type === "checkbox";
7044
+ let isChecked = false;
7045
+ if (isCheckbox) {
7046
+ isChecked = checkbox.checked;
7047
+ } else {
7048
+ const textContent2 = node.textContent || "";
7049
+ isChecked = /^[\s]*[☑✓✗]/.test(textContent2) || /^[\s]*\[x\]/i.test(textContent2);
7050
+ }
7051
+ const prefix = isChecked ? "- [x] " : "- [ ] ";
7052
+ let textContent = content;
7053
+ textContent = textContent.replace(/^\s*\[[ x]\]\s*/gi, "");
7054
+ textContent = textContent.replace(/^\s*[☐☑✓✗]\s*/g, "");
7055
+ textContent = textContent.replace(/^\s*\[[x ]\]\s*/gi, "");
7056
+ textContent = textContent.replace(/^\s*\\?\[[ x]\\?\]\s*/gi, "");
7057
+ textContent = textContent.replace(/\\?\[\\?\s*\\?\]\\?\s*/g, "");
7058
+ return prefix + textContent.trim() + "\n";
7059
+ }
7060
+ });
7061
+ turndownService.addRule("list", {
7062
+ filter: ["ul", "ol"],
7063
+ replacement: (content, node) => {
7064
+ const parent = node.parentNode;
7065
+ const isNested = isElement(parent) && parent.tagName === "LI";
7066
+ if (isNested) {
7067
+ return "\n" + content;
7068
+ }
7069
+ return "\n" + content + "\n";
7070
+ }
7071
+ });
7072
+ turndownService.addRule("blockquote", {
7073
+ filter: "blockquote",
7074
+ replacement: (content) => {
7075
+ const lines = content.trim().split("\n");
7076
+ const processedLines = lines.map((line) => {
7077
+ const trimmed = line.trim();
7078
+ if (!trimmed) {
7079
+ return ">";
7080
+ }
7081
+ const existingQuotes = trimmed.match(/^(>\s*)+/);
7082
+ if (existingQuotes) {
7083
+ return existingQuotes[0] + " " + trimmed;
7084
+ }
7085
+ return "> " + trimmed;
7086
+ });
7087
+ return "\n" + processedLines.join("\n") + "\n";
7088
+ }
7089
+ });
7090
+ turndownService.addRule("horizontalRule", {
7091
+ filter: "hr",
7092
+ replacement: () => "\n---\n"
7093
+ });
7094
+ turndownService.addRule("inlineCode", {
7095
+ filter: (node) => {
7096
+ const parent = node.parentNode;
7097
+ return node.nodeName === "CODE" && !(isElement(parent) && parent.tagName === "PRE");
7098
+ },
7099
+ replacement: (content) => {
7100
+ const backtickCount = Math.max(
7101
+ 1,
7102
+ (content.match(/`+/g) || []).reduce(
7103
+ (max, match) => Math.max(max, match.length),
7104
+ 0
7105
+ ) + 1
7106
+ );
7107
+ const delimiter = "`".repeat(backtickCount);
7108
+ return delimiter + content + delimiter;
7109
+ }
7110
+ });
7111
+ turndownService.addRule("image", {
7112
+ filter: "img",
7113
+ replacement: (_content, node) => {
7114
+ if (!isHTMLElement(node)) {
7115
+ return "";
7116
+ }
7117
+ const src = node.getAttribute("src") || "";
7118
+ const alt = node.getAttribute("alt") || "";
7119
+ const title = node.getAttribute("title");
7120
+ if (title) {
7121
+ return `![${alt}](${src} "${title}")`;
7122
+ }
7123
+ return `![${alt}](${src})`;
7124
+ }
7125
+ });
7126
+ turndownService.addRule("highlight", {
7127
+ filter: (node) => {
7128
+ var _a, _b;
7129
+ if (!isHTMLElement(node)) {
7130
+ return false;
7131
+ }
7132
+ 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")));
7133
+ },
7134
+ replacement: (content) => `==${content}==`
7135
+ });
7136
+ turndownService.addRule("headerWithId", {
7137
+ filter: ["h1", "h2", "h3", "h4", "h5", "h6"],
7138
+ replacement: (content, node) => {
7139
+ if (!isHTMLElement(node)) {
7140
+ return content;
7141
+ }
7142
+ const rawLevel = parseInt(node.nodeName.charAt(1));
7143
+ const level = Math.min(rawLevel, 3);
7144
+ const hashes = "#".repeat(level);
7145
+ const id = node.getAttribute("id");
7146
+ if (id) {
7147
+ return `
7148
+ ${hashes} ${content} {#${id}}
7149
+ `;
7150
+ }
7151
+ return `
7152
+ ${hashes} ${content}
7153
+ `;
7154
+ }
7155
+ });
7156
+ function processTextNodes(element) {
7157
+ const spans = Array.from(element.querySelectorAll("span"));
7158
+ spans.forEach((span) => {
7159
+ var _a;
7160
+ const textContent = span.textContent || "";
7161
+ const textNode = document.createTextNode(textContent);
7162
+ (_a = span.parentNode) == null ? void 0 : _a.replaceChild(textNode, span);
7163
+ });
7164
+ }
7165
+ function detectSourceApp(html) {
7166
+ const detectionPatterns = {
7167
+ notion: ["notion-", "notranslate"],
7168
+ github: ["github.com", "js-file-line-container"],
7169
+ slack: ["slack-", "c-message"],
7170
+ "google-docs": ["docs.google.com", "kix-"]
7171
+ };
7172
+ for (const [app, patterns] of Object.entries(detectionPatterns)) {
7173
+ if (patterns.some((pattern) => html.includes(pattern))) {
7174
+ return app;
7175
+ }
7176
+ }
7177
+ return "unknown";
7178
+ }
7179
+ function preprocessForApp(html, app) {
7180
+ switch (app) {
7181
+ case "notion":
7182
+ 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>");
7183
+ case "github":
7184
+ return html.replace(/<td[^>]*class="[^"]*blob-num[^"]*"[^>]*>.*?<\/td>/gi, "").replace(/<span[^>]*class="[^"]*pl-[^"]*"[^>]*>/gi, "<span>").replace(/<span[^>]*class="[^"]*highlight[^"]*"[^>]*>/gi, "<span>");
7185
+ case "slack":
7186
+ return html.replace(/<span[^>]*class="[^"]*c-member[^"]*"[^>]*>/gi, "<span>").replace(/<span[^>]*data-stringify-type="mention"[^>]*>/gi, "<span>").replace(
7187
+ /<span[^>]*class="[^"]*c-emoji[^"]*"[^>]*>([^<]*)<\/span>/gi,
7188
+ "$1"
7189
+ );
7190
+ case "google-docs":
7191
+ return html.replace(
7192
+ /<span[^>]*style="[^"]*font-weight:[^;"]*bold[^"]*"[^>]*>/gi,
7193
+ "<strong>"
7194
+ ).replace(
7195
+ /<span[^>]*style="[^"]*font-style:[^;"]*italic[^"]*"[^>]*>/gi,
7196
+ "<em>"
7197
+ ).replace(/<\/span>/gi, "").replace(/<p[^>]*style="[^"]*"[^>]*>/gi, "<p>");
7198
+ default:
7199
+ return html;
7200
+ }
7201
+ }
7202
+ function cleanupMarkdown(markdown) {
7203
+ 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) => {
7204
+ var _a;
7205
+ const num = ((_a = match.match(/\d+/)) == null ? void 0 : _a[0]) || "1";
7206
+ return `${num}. `;
7207
+ }).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();
7208
+ }
7209
+ function preprocessHtml(html) {
7210
+ const sourceApp = detectSourceApp(html);
7211
+ let processedHtml = preprocessForApp(html, sourceApp);
7212
+ const parser = new DOMParser();
7213
+ const doc = parser.parseFromString(processedHtml, "text/html");
7214
+ if (doc.body) {
7215
+ processTextNodes(doc.body);
7216
+ processedHtml = doc.body.innerHTML;
7217
+ }
7218
+ 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();
7219
+ }
7220
+ function sanitizeContent(content) {
7221
+ return content.replace(/<script[^>]*>.*?<\/script>/gi, "").replace(
7222
+ /<(?:iframe|object|embed)[^>]*>.*?<\/(?:iframe|object|embed)>/gi,
7223
+ ""
7224
+ );
7225
+ }
7226
+ function limitContentLength(content, maxLength = MAX_MARKDOWN_OUTPUT_LENGTH) {
7227
+ return content.substring(0, maxLength);
7228
+ }
7229
+ function sanitizeMarkdown(markdown) {
7230
+ return limitContentLength(sanitizeContent(markdown));
7231
+ }
7232
+ function shouldProcessPaste(htmlContent) {
7233
+ if (htmlContent.length > MAX_HTML_INPUT_LENGTH) {
7234
+ console.warn("HTML content too large for paste processing");
7235
+ return false;
7236
+ }
7237
+ 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(
7238
+ htmlContent
7239
+ );
7240
+ if (!hasFormatting) {
7241
+ return false;
7242
+ }
7243
+ if (/<img\s/i.test(htmlContent)) {
7244
+ return true;
7245
+ }
7246
+ const strippedContent = htmlContent.replace(/<[^>]*>/g, "").trim();
7247
+ if (!strippedContent || strippedContent.length < 3) {
7248
+ return false;
7249
+ }
7250
+ return true;
7251
+ }
7252
+ function normalizeListPrefixes(content) {
7253
+ const lines = content.split("\n");
7254
+ let currentListType = null;
7255
+ let currentNumber = 1;
7256
+ return lines.map((line) => {
7257
+ const match = line.match(/^(\s*)([-*+]|\d+\.)( +)(.*)$/);
7258
+ if (match) {
7259
+ const [, indent, marker, , listContent] = match;
7260
+ if (currentListType === null) {
7261
+ currentListType = /\d+\./.test(marker) ? "numbered" : "bullet";
7262
+ }
7263
+ const newMarker = currentListType === "bullet" ? "-" : `${currentNumber}.`;
7264
+ if (currentListType === "numbered") {
7265
+ currentNumber += 1;
7266
+ }
7267
+ return `${indent}${newMarker} ${listContent}`;
7268
+ }
7269
+ return line;
7270
+ }).join("\n");
7271
+ }
7272
+ function convertHtmlToMarkdown(html) {
7273
+ try {
7274
+ const processedHtml = preprocessHtml(html);
7275
+ const markdown = turndownService.turndown(processedHtml);
7276
+ return cleanupMarkdown(markdown);
7277
+ } catch (error) {
7278
+ console.error("Error converting HTML to markdown:", error);
6728
7279
  return "";
6729
7280
  }
6730
- return turndownService.turndown(trimmed);
7281
+ }
7282
+ function htmlToMarkdownForPaste(htmlContent) {
7283
+ if (!shouldProcessPaste(htmlContent)) {
7284
+ return null;
7285
+ }
7286
+ const markdown = convertHtmlToMarkdown(htmlContent);
7287
+ if (!markdown.trim()) {
7288
+ return null;
7289
+ }
7290
+ const normalizedMarkdown = normalizeListPrefixes(markdown);
7291
+ return sanitizeMarkdown(normalizedMarkdown).replace(/\r\n?/g, "\n");
6731
7292
  }
6732
7293
  const defaultSelection = { start: 0, end: 0, affinity: "forward" };
6733
7294
  const COMPOSITION_COMMIT_CLEAR_DELAY_MS = 50;
@@ -6906,10 +7467,15 @@ class CakeEngine {
6906
7467
  }
6907
7468
  }
6908
7469
  setValue({ value, selection }) {
6909
- const nextSelection = selection ?? this.state.selection;
6910
- if (value === this.state.source && selection === void 0) {
7470
+ const valueChanged = value !== this.state.source;
7471
+ if (!valueChanged && selection === void 0) {
6911
7472
  return;
6912
7473
  }
7474
+ if (!valueChanged && selection !== void 0) {
7475
+ this.setSelection(selection);
7476
+ return;
7477
+ }
7478
+ const nextSelection = selection ?? this.state.selection;
6913
7479
  this.state = this.runtime.createState(value, nextSelection);
6914
7480
  this.render();
6915
7481
  }
@@ -7169,7 +7735,11 @@ class CakeEngine {
7169
7735
  this.extensions,
7170
7736
  this.contentRoot
7171
7737
  );
7172
- this.contentRoot.replaceChildren(...content);
7738
+ const existingChildren = Array.from(this.contentRoot.childNodes);
7739
+ const needsUpdate = content.length !== existingChildren.length || content.some((node, i) => node !== existingChildren[i]);
7740
+ if (needsUpdate) {
7741
+ this.contentRoot.replaceChildren(...content);
7742
+ }
7173
7743
  this.domMap = map;
7174
7744
  this.updateExtensionsOverlayPosition();
7175
7745
  if (!this.isComposing) {
@@ -7765,6 +8335,13 @@ class CakeEngine {
7765
8335
  }
7766
8336
  event.preventDefault();
7767
8337
  clipboardData.setData("text/plain", text);
8338
+ const html = this.runtime.serializeSelectionToHtml(
8339
+ this.state,
8340
+ this.state.selection
8341
+ );
8342
+ if (html) {
8343
+ clipboardData.setData("text/html", html);
8344
+ }
7768
8345
  }
7769
8346
  handleCut(event) {
7770
8347
  if (this.readOnly) {
@@ -9074,8 +9651,8 @@ class CakeEngine {
9074
9651
  if (!this.extensionsRoot) {
9075
9652
  return;
9076
9653
  }
9077
- const scrollTop = this.container.scrollTop;
9078
- const scrollLeft = this.container.scrollLeft;
9654
+ const scrollTop = Math.max(0, this.container.scrollTop);
9655
+ const scrollLeft = Math.max(0, this.container.scrollLeft);
9079
9656
  if (scrollTop === 0 && scrollLeft === 0) {
9080
9657
  this.extensionsRoot.style.transform = "";
9081
9658
  return;
@@ -10738,10 +11315,10 @@ const CakeEditor = forwardRef(
10738
11315
  containerStyle.position = "relative";
10739
11316
  }
10740
11317
  const containerClassName = props.className ? `cake ${props.className}` : "cake";
10741
- const overlayContext = overlayRoot && containerRef.current && contentRoot ? {
11318
+ const overlayContext = containerRef.current && contentRoot ? {
10742
11319
  container: containerRef.current,
10743
11320
  contentRoot,
10744
- overlayRoot,
11321
+ overlayRoot: overlayRoot ?? void 0,
10745
11322
  toOverlayRect: (rect) => {
10746
11323
  var _a;
10747
11324
  const containerRect = (_a = containerRef.current) == null ? void 0 : _a.getBoundingClientRect();
@@ -10778,8 +11355,11 @@ const CakeEditor = forwardRef(
10778
11355
  return { start: focus, end: focus };
10779
11356
  }
10780
11357
  } : null;
10781
- return /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(jsxDevRuntimeExports.Fragment, { children: [
10782
- /* @__PURE__ */ jsxDevRuntimeExports.jsxDEV(
11358
+ const hasOverlayExtensions = allExtensionsRef.current.some(
11359
+ (ext) => ext.renderOverlay
11360
+ );
11361
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { position: "relative", height: "100%" }, children: [
11362
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
10783
11363
  "div",
10784
11364
  {
10785
11365
  ref: containerRef,
@@ -10790,35 +11370,12 @@ const CakeEditor = forwardRef(
10790
11370
  var _a;
10791
11371
  (_a = props.onBlur) == null ? void 0 : _a.call(props, event.nativeEvent);
10792
11372
  }
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
11373
+ }
10802
11374
  ),
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
11375
+ overlayContext && hasOverlayExtensions ? allExtensionsRef.current.map(
11376
+ (extension) => extension.renderOverlay ? /* @__PURE__ */ jsxRuntimeExports.jsx(Fragment, { children: extension.renderOverlay(overlayContext) }, extension.name) : null
10816
11377
  ) : 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);
11378
+ ] });
10822
11379
  }
10823
11380
  );
10824
11381
  CakeEditor.displayName = "CakeEditor";