@blankdotpage/cake 0.1.8 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -1
- package/dist/cake/clipboard.d.ts +1 -0
- package/dist/cake/clipboard.d.ts.map +1 -0
- package/dist/cake/clipboard.js +391 -0
- package/dist/cake/core/mapping/cursor-source-map.d.ts +1 -0
- package/dist/cake/core/mapping/cursor-source-map.d.ts.map +1 -0
- package/dist/cake/core/mapping/cursor-source-map.js +146 -0
- package/dist/cake/core/runtime.d.ts +1 -0
- package/dist/cake/core/runtime.d.ts.map +1 -0
- package/dist/cake/core/runtime.js +1758 -0
- package/dist/cake/core/types.d.ts +1 -0
- package/dist/cake/core/types.d.ts.map +1 -0
- package/dist/cake/core/types.js +1 -0
- package/dist/cake/dom/dom-map.d.ts +1 -0
- package/dist/cake/dom/dom-map.d.ts.map +1 -0
- package/dist/cake/dom/dom-map.js +151 -0
- package/dist/cake/dom/dom-selection.d.ts +1 -0
- package/dist/cake/dom/dom-selection.d.ts.map +1 -0
- package/dist/cake/dom/dom-selection.js +216 -0
- package/dist/cake/dom/render.d.ts +1 -0
- package/dist/cake/dom/render.d.ts.map +1 -0
- package/dist/cake/dom/render.js +470 -0
- package/dist/cake/dom/types.d.ts +1 -0
- package/dist/cake/dom/types.d.ts.map +1 -0
- package/dist/cake/dom/types.js +1 -0
- package/dist/cake/editor/cake-editor.d.ts +230 -0
- package/dist/cake/editor/cake-editor.d.ts.map +1 -0
- package/dist/cake/editor/cake-editor.js +3589 -0
- package/dist/cake/editor/selection/selection-geometry-dom.d.ts +24 -0
- package/dist/cake/editor/selection/selection-geometry-dom.d.ts.map +1 -0
- package/dist/cake/editor/selection/selection-geometry-dom.js +302 -0
- package/dist/cake/editor/selection/selection-geometry.d.ts +22 -0
- package/dist/cake/editor/selection/selection-geometry.d.ts.map +1 -0
- package/dist/cake/editor/selection/selection-geometry.js +158 -0
- package/dist/cake/editor/selection/selection-layout-dom.d.ts +50 -0
- package/dist/cake/editor/selection/selection-layout-dom.d.ts.map +1 -0
- package/dist/cake/editor/selection/selection-layout-dom.js +781 -0
- package/dist/cake/editor/selection/selection-layout.d.ts +55 -0
- package/dist/cake/editor/selection/selection-layout.d.ts.map +1 -0
- package/dist/cake/editor/selection/selection-layout.js +128 -0
- package/dist/cake/editor/selection/selection-navigation.d.ts +22 -0
- package/dist/cake/editor/selection/selection-navigation.d.ts.map +1 -0
- package/dist/cake/editor/selection/selection-navigation.js +229 -0
- package/dist/cake/editor/selection/visible-text.d.ts +5 -0
- package/dist/cake/editor/selection/visible-text.d.ts.map +1 -0
- package/dist/cake/editor/selection/visible-text.js +66 -0
- package/dist/cake/engine/cake-engine.d.ts +1 -0
- package/dist/cake/engine/cake-engine.d.ts.map +1 -0
- package/dist/cake/engine/cake-engine.js +3589 -0
- package/dist/cake/engine/selection/selection-geometry-dom.d.ts +1 -0
- package/dist/cake/engine/selection/selection-geometry-dom.d.ts.map +1 -0
- package/dist/cake/engine/selection/selection-geometry-dom.js +302 -0
- package/dist/cake/engine/selection/selection-geometry.d.ts +1 -0
- package/dist/cake/engine/selection/selection-geometry.d.ts.map +1 -0
- package/dist/cake/engine/selection/selection-geometry.js +158 -0
- package/dist/cake/engine/selection/selection-layout-dom.d.ts +1 -0
- package/dist/cake/engine/selection/selection-layout-dom.d.ts.map +1 -0
- package/dist/cake/engine/selection/selection-layout-dom.js +781 -0
- package/dist/cake/engine/selection/selection-layout.d.ts +1 -0
- package/dist/cake/engine/selection/selection-layout.d.ts.map +1 -0
- package/dist/cake/engine/selection/selection-layout.js +128 -0
- package/dist/cake/engine/selection/selection-navigation.d.ts +1 -0
- package/dist/cake/engine/selection/selection-navigation.d.ts.map +1 -0
- package/dist/cake/engine/selection/selection-navigation.js +229 -0
- package/dist/cake/engine/selection/visible-text.d.ts +1 -0
- package/dist/cake/engine/selection/visible-text.d.ts.map +1 -0
- package/dist/cake/engine/selection/visible-text.js +66 -0
- package/dist/cake/extensions/blockquote/blockquote.d.ts +1 -0
- package/dist/cake/extensions/blockquote/blockquote.d.ts.map +1 -0
- package/dist/cake/extensions/blockquote/blockquote.js +177 -0
- package/dist/cake/extensions/blockquote/index.d.ts +2 -0
- package/dist/cake/extensions/blockquote/index.d.ts.map +1 -0
- package/dist/cake/extensions/blockquote/index.js +1 -0
- package/dist/cake/extensions/bold/bold.d.ts +1 -0
- package/dist/cake/extensions/bold/bold.d.ts.map +1 -0
- package/dist/cake/extensions/bold/bold.js +113 -0
- package/dist/cake/extensions/bold/index.d.ts +2 -0
- package/dist/cake/extensions/bold/index.d.ts.map +1 -0
- package/dist/cake/extensions/bold/index.js +1 -0
- package/dist/cake/extensions/bundles.d.ts +1 -0
- package/dist/cake/extensions/bundles.d.ts.map +1 -0
- package/dist/cake/extensions/bundles.js +12 -0
- package/dist/cake/extensions/combined-emphasis/combined-emphasis.d.ts +1 -0
- package/dist/cake/extensions/combined-emphasis/combined-emphasis.d.ts.map +1 -0
- package/dist/cake/extensions/combined-emphasis/combined-emphasis.js +42 -0
- package/dist/cake/extensions/combined-emphasis/index.d.ts +2 -0
- package/dist/cake/extensions/combined-emphasis/index.d.ts.map +1 -0
- package/dist/cake/extensions/combined-emphasis/index.js +1 -0
- package/dist/cake/extensions/heading/heading.d.ts +1 -0
- package/dist/cake/extensions/heading/heading.d.ts.map +1 -0
- package/dist/cake/extensions/heading/heading.js +337 -0
- package/dist/cake/extensions/heading/index.d.ts +2 -0
- package/dist/cake/extensions/heading/index.d.ts.map +1 -0
- package/dist/cake/extensions/heading/index.js +1 -0
- package/dist/cake/extensions/image/image.d.ts +1 -0
- package/dist/cake/extensions/image/image.d.ts.map +1 -0
- package/dist/cake/extensions/image/image.js +120 -0
- package/dist/cake/extensions/image/index.d.ts +2 -0
- package/dist/cake/extensions/image/index.d.ts.map +1 -0
- package/dist/cake/extensions/image/index.js +1 -0
- package/dist/cake/extensions/index.d.ts +3 -3
- package/dist/cake/extensions/index.d.ts.map +1 -0
- package/dist/cake/extensions/index.js +25 -0
- package/dist/cake/extensions/italic/index.d.ts +2 -0
- package/dist/cake/extensions/italic/index.d.ts.map +1 -0
- package/dist/cake/extensions/italic/index.js +1 -0
- package/dist/cake/extensions/italic/italic.d.ts +1 -0
- package/dist/cake/extensions/italic/italic.d.ts.map +1 -0
- package/dist/cake/extensions/italic/italic.js +91 -0
- package/dist/cake/extensions/link/index.d.ts +2 -0
- package/dist/cake/extensions/link/index.d.ts.map +1 -0
- package/dist/cake/extensions/link/index.js +1 -0
- package/dist/cake/extensions/link/link-popover.d.ts +1 -0
- package/dist/cake/extensions/link/link-popover.d.ts.map +1 -0
- package/dist/cake/extensions/link/link-popover.js +205 -0
- package/dist/cake/extensions/link/link.d.ts +1 -0
- package/dist/cake/extensions/link/link.d.ts.map +1 -0
- package/dist/cake/extensions/link/link.js +202 -0
- package/dist/cake/extensions/list/index.d.ts +2 -0
- package/dist/cake/extensions/list/index.d.ts.map +1 -0
- package/dist/cake/extensions/list/index.js +1 -0
- package/dist/cake/extensions/list/list-ast.d.ts +1 -0
- package/dist/cake/extensions/list/list-ast.d.ts.map +1 -0
- package/dist/cake/extensions/list/list-ast.js +248 -0
- package/dist/cake/extensions/list/list.d.ts +2 -1
- package/dist/cake/extensions/list/list.d.ts.map +1 -0
- package/dist/cake/extensions/list/list.js +859 -0
- package/dist/cake/extensions/scrollbar/index.d.ts +1 -0
- package/dist/cake/extensions/scrollbar/index.d.ts.map +1 -0
- package/dist/cake/extensions/scrollbar/index.js +216 -0
- package/dist/cake/extensions/strikethrough/index.d.ts +2 -0
- package/dist/cake/extensions/strikethrough/index.d.ts.map +1 -0
- package/dist/cake/extensions/strikethrough/index.js +1 -0
- package/dist/cake/extensions/strikethrough/strikethrough.d.ts +1 -0
- package/dist/cake/extensions/strikethrough/strikethrough.d.ts.map +1 -0
- package/dist/cake/extensions/strikethrough/strikethrough.js +84 -0
- package/dist/cake/extensions/types.d.ts +1 -0
- package/dist/cake/extensions/types.d.ts.map +1 -0
- package/dist/cake/extensions/types.js +1 -0
- package/dist/cake/index.d.ts +1 -2
- package/dist/cake/index.d.ts.map +1 -0
- package/dist/cake/index.js +1 -0
- package/dist/cake/react/CakeEditor.d.ts +2 -2
- package/dist/cake/react/CakeEditor.d.ts.map +1 -0
- package/dist/cake/react/CakeEditor.js +225 -0
- package/dist/cake/react/index.d.ts +58 -0
- package/dist/cake/react/index.d.ts.map +1 -0
- package/dist/cake/react/index.js +225 -0
- package/dist/cake/shared/platform.d.ts +1 -0
- package/dist/cake/shared/platform.d.ts.map +1 -0
- package/dist/cake/shared/platform.js +19 -0
- package/dist/cake/shared/segmenter.d.ts +1 -0
- package/dist/cake/shared/segmenter.d.ts.map +1 -0
- package/dist/cake/shared/segmenter.js +46 -0
- package/dist/cake/shared/url.d.ts +1 -0
- package/dist/cake/shared/url.d.ts.map +1 -0
- package/dist/cake/shared/url.js +37 -0
- package/dist/cake/shared/word-break.d.ts +1 -0
- package/dist/cake/shared/word-break.d.ts.map +1 -0
- package/dist/cake/shared/word-break.js +178 -0
- package/dist/cake/test/harness.d.ts +3 -2
- package/dist/cake/test/harness.d.ts.map +1 -0
- package/dist/cake/test/harness.js +504 -0
- package/dist/codemirror/markdown-commands.d.ts +1 -0
- package/dist/codemirror/markdown-commands.d.ts.map +1 -0
- package/dist/codemirror/markdown-commands.js +532 -0
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -11793
- package/package.json +12 -6
- package/dist/cake/extensions/pipe-link/pipe-link.d.ts +0 -1
- package/dist/index.cjs +0 -11793
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import { userEvent } from "vitest/browser";
|
|
2
|
+
import { createElement, Fragment } from "react";
|
|
3
|
+
import { createRoot } from "react-dom/client";
|
|
4
|
+
import { CakeEditor } from "../editor/cake-editor";
|
|
5
|
+
import { bundledExtensions } from "../extensions";
|
|
6
|
+
import { measureLayoutModelFromDom } from "../editor/selection/selection-layout-dom";
|
|
7
|
+
export function createTestHarness(valueOrOptions) {
|
|
8
|
+
const options = typeof valueOrOptions === "string"
|
|
9
|
+
? { value: valueOrOptions }
|
|
10
|
+
: valueOrOptions;
|
|
11
|
+
const container = document.createElement("div");
|
|
12
|
+
container.className = "cake";
|
|
13
|
+
container.style.width = "400px";
|
|
14
|
+
container.style.height = "200px";
|
|
15
|
+
container.style.position = "absolute";
|
|
16
|
+
container.style.top = "0";
|
|
17
|
+
container.style.left = "0";
|
|
18
|
+
document.body.appendChild(container);
|
|
19
|
+
let styleElement = null;
|
|
20
|
+
const caretStyles = `
|
|
21
|
+
.cake-caret {
|
|
22
|
+
width: 3px !important;
|
|
23
|
+
animation: none !important;
|
|
24
|
+
background-color: #000 !important;
|
|
25
|
+
display: block !important;
|
|
26
|
+
}
|
|
27
|
+
`;
|
|
28
|
+
styleElement = document.createElement("style");
|
|
29
|
+
styleElement.textContent = caretStyles + (options.css ?? "");
|
|
30
|
+
document.head.appendChild(styleElement);
|
|
31
|
+
const extensions = options.extensions ?? bundledExtensions;
|
|
32
|
+
const engine = new CakeEditor({
|
|
33
|
+
container,
|
|
34
|
+
value: options.value,
|
|
35
|
+
selection: { start: 0, end: 0, affinity: "forward" },
|
|
36
|
+
extensions,
|
|
37
|
+
});
|
|
38
|
+
let overlayRoot = null;
|
|
39
|
+
if (options.renderOverlays) {
|
|
40
|
+
const overlayContainer = engine.getOverlayRoot();
|
|
41
|
+
const contentRoot = engine.getContentRoot();
|
|
42
|
+
if (!overlayContainer || !contentRoot) {
|
|
43
|
+
throw new Error("Missing overlay root for extensions");
|
|
44
|
+
}
|
|
45
|
+
const overlayContext = {
|
|
46
|
+
container,
|
|
47
|
+
contentRoot,
|
|
48
|
+
overlayRoot: overlayContainer,
|
|
49
|
+
toOverlayRect: (rect) => {
|
|
50
|
+
const containerRect = container.getBoundingClientRect();
|
|
51
|
+
return {
|
|
52
|
+
top: rect.top - containerRect.top,
|
|
53
|
+
left: rect.left - containerRect.left,
|
|
54
|
+
width: rect.width,
|
|
55
|
+
height: rect.height,
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
insertText: (text) => {
|
|
59
|
+
engine.insertText(text);
|
|
60
|
+
},
|
|
61
|
+
replaceText: (oldText, newText) => {
|
|
62
|
+
engine.replaceText(oldText, newText);
|
|
63
|
+
},
|
|
64
|
+
getSelection: () => {
|
|
65
|
+
const selection = engine.getSelection();
|
|
66
|
+
if (!selection) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const focus = selection.start === selection.end
|
|
70
|
+
? selection.start
|
|
71
|
+
: Math.max(selection.start, selection.end);
|
|
72
|
+
return { start: focus, end: focus };
|
|
73
|
+
},
|
|
74
|
+
executeCommand: (command) => {
|
|
75
|
+
return engine.executeCommand(command);
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
const overlayElements = extensions.flatMap((extension) => {
|
|
79
|
+
if (!extension.renderOverlay) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
const rendered = extension.renderOverlay(overlayContext);
|
|
83
|
+
if (!rendered) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
return [createElement(Fragment, { key: extension.name }, rendered)];
|
|
87
|
+
});
|
|
88
|
+
overlayRoot = createRoot(overlayContainer);
|
|
89
|
+
overlayRoot.render(createElement(Fragment, null, ...overlayElements));
|
|
90
|
+
}
|
|
91
|
+
function getContentRoot() {
|
|
92
|
+
const root = container.querySelector(".cake-content");
|
|
93
|
+
if (!root || !(root instanceof HTMLElement)) {
|
|
94
|
+
throw new Error("Content root not found");
|
|
95
|
+
}
|
|
96
|
+
return root;
|
|
97
|
+
}
|
|
98
|
+
function getLine(index) {
|
|
99
|
+
const line = container.querySelector(`[data-line-index="${index}"]`);
|
|
100
|
+
if (!line || !(line instanceof HTMLElement)) {
|
|
101
|
+
throw new Error(`Line ${index} not found`);
|
|
102
|
+
}
|
|
103
|
+
return line;
|
|
104
|
+
}
|
|
105
|
+
function getLineCount() {
|
|
106
|
+
return container.querySelectorAll("[data-line-index]").length;
|
|
107
|
+
}
|
|
108
|
+
function getLineRect(index) {
|
|
109
|
+
return getLine(index).getBoundingClientRect();
|
|
110
|
+
}
|
|
111
|
+
function getTextNode(lineIndex = 0) {
|
|
112
|
+
const line = getLine(lineIndex);
|
|
113
|
+
const walker = document.createTreeWalker(line, NodeFilter.SHOW_TEXT);
|
|
114
|
+
const node = walker.nextNode();
|
|
115
|
+
if (!node || !(node instanceof Text)) {
|
|
116
|
+
throw new Error(`No text node in line ${lineIndex}`);
|
|
117
|
+
}
|
|
118
|
+
return node;
|
|
119
|
+
}
|
|
120
|
+
function getTextNodes(lineIndex = 0) {
|
|
121
|
+
const line = getLine(lineIndex);
|
|
122
|
+
const walker = document.createTreeWalker(line, NodeFilter.SHOW_TEXT);
|
|
123
|
+
const nodes = [];
|
|
124
|
+
let node = walker.nextNode();
|
|
125
|
+
while (node) {
|
|
126
|
+
if (node instanceof Text) {
|
|
127
|
+
nodes.push(node);
|
|
128
|
+
}
|
|
129
|
+
node = walker.nextNode();
|
|
130
|
+
}
|
|
131
|
+
return nodes;
|
|
132
|
+
}
|
|
133
|
+
function getCharRect(offset, lineIndex = 0) {
|
|
134
|
+
const textNodes = getTextNodes(lineIndex);
|
|
135
|
+
if (textNodes.length === 0) {
|
|
136
|
+
throw new Error(`No text nodes in line ${lineIndex}`);
|
|
137
|
+
}
|
|
138
|
+
let remaining = offset;
|
|
139
|
+
for (const textNode of textNodes) {
|
|
140
|
+
const len = textNode.data.length;
|
|
141
|
+
if (remaining < len) {
|
|
142
|
+
const range = document.createRange();
|
|
143
|
+
range.setStart(textNode, remaining);
|
|
144
|
+
range.setEnd(textNode, remaining + 1);
|
|
145
|
+
// Prefer `getClientRects()` because `getBoundingClientRect()` can span
|
|
146
|
+
// multiple rows at wrap boundaries. Some engines (notably WebKit) can
|
|
147
|
+
// include zero-width rect fragments; pick the largest rect.
|
|
148
|
+
const rects = Array.from(range.getClientRects());
|
|
149
|
+
if (rects.length > 0) {
|
|
150
|
+
let best = rects[0];
|
|
151
|
+
let bestArea = best.width * best.height;
|
|
152
|
+
for (const rect of rects) {
|
|
153
|
+
const area = rect.width * rect.height;
|
|
154
|
+
if (area > bestArea) {
|
|
155
|
+
best = rect;
|
|
156
|
+
bestArea = area;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (bestArea > 0) {
|
|
160
|
+
return best;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return range.getBoundingClientRect();
|
|
164
|
+
}
|
|
165
|
+
remaining -= len;
|
|
166
|
+
}
|
|
167
|
+
throw new Error(`Offset ${offset} out of bounds for line ${lineIndex} (len=${offset - remaining})`);
|
|
168
|
+
}
|
|
169
|
+
function dispatchSyntheticClickAt(clientX, clientY) {
|
|
170
|
+
// `userEvent.click(el, { position })` can quantize/round coordinates
|
|
171
|
+
// differently across engines (notably WebKit), which makes coordinate-based
|
|
172
|
+
// selection tests flaky. Dispatch events with explicit `clientX/Y` instead.
|
|
173
|
+
const target = document.elementFromPoint(clientX, clientY) ?? container;
|
|
174
|
+
// Ensure focus follows click semantics.
|
|
175
|
+
getContentRoot().focus();
|
|
176
|
+
const base = {
|
|
177
|
+
bubbles: true,
|
|
178
|
+
cancelable: true,
|
|
179
|
+
composed: true,
|
|
180
|
+
clientX,
|
|
181
|
+
clientY,
|
|
182
|
+
button: 0,
|
|
183
|
+
};
|
|
184
|
+
if (typeof PointerEvent !== "undefined") {
|
|
185
|
+
target.dispatchEvent(new PointerEvent("pointerdown", {
|
|
186
|
+
...base,
|
|
187
|
+
buttons: 1,
|
|
188
|
+
pointerId: 1,
|
|
189
|
+
pointerType: "mouse",
|
|
190
|
+
isPrimary: true,
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
target.dispatchEvent(new MouseEvent("mousedown", { ...base, buttons: 1 }));
|
|
194
|
+
if (typeof PointerEvent !== "undefined") {
|
|
195
|
+
target.dispatchEvent(new PointerEvent("pointerup", {
|
|
196
|
+
...base,
|
|
197
|
+
buttons: 0,
|
|
198
|
+
pointerId: 1,
|
|
199
|
+
pointerType: "mouse",
|
|
200
|
+
isPrimary: true,
|
|
201
|
+
}));
|
|
202
|
+
}
|
|
203
|
+
target.dispatchEvent(new MouseEvent("mouseup", { ...base, buttons: 0 }));
|
|
204
|
+
target.dispatchEvent(new MouseEvent("click", { ...base, detail: 1 }));
|
|
205
|
+
}
|
|
206
|
+
async function clickAtPosition(offset, side, lineIndex = 0) {
|
|
207
|
+
const charRect = getCharRect(offset, lineIndex);
|
|
208
|
+
let clickX;
|
|
209
|
+
if (side === "left") {
|
|
210
|
+
// Prefer clicking well inside the left half of the glyph so "left of"
|
|
211
|
+
// remains meaningful even for very narrow characters across engines.
|
|
212
|
+
clickX =
|
|
213
|
+
charRect.width >= 2
|
|
214
|
+
? charRect.left + charRect.width * 0.25
|
|
215
|
+
: charRect.left - 1;
|
|
216
|
+
}
|
|
217
|
+
else if (side === "right") {
|
|
218
|
+
// Prefer clicking well inside the right half of the glyph so "right of"
|
|
219
|
+
// is robust even when glyph widths differ slightly across engines/fonts.
|
|
220
|
+
//
|
|
221
|
+
// Some engines report very narrow (or even ~0 width) rects for certain
|
|
222
|
+
// characters (notably spaces). In that case, nudge past the right edge so
|
|
223
|
+
// we reliably land on the post-character boundary.
|
|
224
|
+
clickX =
|
|
225
|
+
charRect.width >= 2
|
|
226
|
+
? charRect.left + charRect.width * 0.75
|
|
227
|
+
: charRect.right + 1;
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
clickX = charRect.left + charRect.width / 2;
|
|
231
|
+
}
|
|
232
|
+
const clickY = charRect.top + charRect.height / 2;
|
|
233
|
+
dispatchSyntheticClickAt(clickX, clickY);
|
|
234
|
+
}
|
|
235
|
+
async function doubleClick(offset, lineIndex) {
|
|
236
|
+
const line = getLine(lineIndex);
|
|
237
|
+
const lineRect = line.getBoundingClientRect();
|
|
238
|
+
const charRect = getCharRect(offset, lineIndex);
|
|
239
|
+
const clickX = charRect.left + charRect.width / 2;
|
|
240
|
+
const clickY = charRect.top + charRect.height / 2;
|
|
241
|
+
await userEvent.dblClick(line, {
|
|
242
|
+
position: {
|
|
243
|
+
x: clickX - lineRect.left,
|
|
244
|
+
y: clickY - lineRect.top,
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
async function tripleClick(lineIndex) {
|
|
249
|
+
const line = getLine(lineIndex);
|
|
250
|
+
await userEvent.tripleClick(line);
|
|
251
|
+
}
|
|
252
|
+
async function clickAtCoords(clientX, clientY) {
|
|
253
|
+
dispatchSyntheticClickAt(clientX, clientY);
|
|
254
|
+
}
|
|
255
|
+
function getSelectionRects() {
|
|
256
|
+
const rects = container.querySelectorAll(".cake-selection-rect");
|
|
257
|
+
return Array.from(rects).map((rect) => {
|
|
258
|
+
const el = rect;
|
|
259
|
+
return {
|
|
260
|
+
top: parseFloat(el.style.top),
|
|
261
|
+
left: parseFloat(el.style.left),
|
|
262
|
+
width: parseFloat(el.style.width),
|
|
263
|
+
height: parseFloat(el.style.height),
|
|
264
|
+
};
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
function getCaretRect() {
|
|
268
|
+
const caret = container.querySelector(".cake-caret");
|
|
269
|
+
if (!caret) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
top: parseFloat(caret.style.top),
|
|
274
|
+
left: parseFloat(caret.style.left),
|
|
275
|
+
height: caret.offsetHeight,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function getVisualRows(lineIndex = 0) {
|
|
279
|
+
const lines = engine.getLines();
|
|
280
|
+
const root = engine.getContentRoot();
|
|
281
|
+
if (!root) {
|
|
282
|
+
return [];
|
|
283
|
+
}
|
|
284
|
+
const layout = measureLayoutModelFromDom({
|
|
285
|
+
lines,
|
|
286
|
+
root,
|
|
287
|
+
container,
|
|
288
|
+
});
|
|
289
|
+
if (!layout) {
|
|
290
|
+
return [];
|
|
291
|
+
}
|
|
292
|
+
const lineLayout = layout.lines[lineIndex];
|
|
293
|
+
if (!lineLayout) {
|
|
294
|
+
return [];
|
|
295
|
+
}
|
|
296
|
+
const containerRect = container.getBoundingClientRect();
|
|
297
|
+
return lineLayout.rows.map((row) => {
|
|
298
|
+
// Use actual character positions for left/right bounds
|
|
299
|
+
const startCharOffset = row.startOffset;
|
|
300
|
+
const endCharOffset = row.endOffset > row.startOffset ? row.endOffset - 1 : row.startOffset;
|
|
301
|
+
// Measure first character's left position
|
|
302
|
+
let left = row.rect.left + containerRect.left;
|
|
303
|
+
try {
|
|
304
|
+
const firstCharRect = getCharRect(lineLayout.lineStartOffset + startCharOffset, lineIndex);
|
|
305
|
+
left = firstCharRect.left;
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
// Fall back to layout model rect if char measurement fails
|
|
309
|
+
}
|
|
310
|
+
// Measure last character's right position
|
|
311
|
+
let right = row.rect.left + row.rect.width + containerRect.left;
|
|
312
|
+
try {
|
|
313
|
+
const lastCharRect = getCharRect(lineLayout.lineStartOffset + endCharOffset, lineIndex);
|
|
314
|
+
right = lastCharRect.right;
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
// Fall back to layout model rect if char measurement fails
|
|
318
|
+
}
|
|
319
|
+
// Note: Layout model endOffset is exclusive, but old harness used inclusive.
|
|
320
|
+
// Convert to inclusive by subtracting 1 (unless it's an empty row).
|
|
321
|
+
const inclusiveEndOffset = row.endOffset > row.startOffset
|
|
322
|
+
? row.endOffset - 1
|
|
323
|
+
: row.startOffset;
|
|
324
|
+
return {
|
|
325
|
+
startOffset: lineLayout.lineStartOffset + row.startOffset,
|
|
326
|
+
endOffset: lineLayout.lineStartOffset + inclusiveEndOffset,
|
|
327
|
+
top: row.rect.top + containerRect.top,
|
|
328
|
+
bottom: row.rect.top + row.rect.height + containerRect.top,
|
|
329
|
+
left,
|
|
330
|
+
right,
|
|
331
|
+
};
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
function assertCaretOnVisualRow(rowIndex, lineIndex = 0) {
|
|
335
|
+
const rows = getVisualRows(lineIndex);
|
|
336
|
+
if (rowIndex >= rows.length) {
|
|
337
|
+
throw new Error(`Row ${rowIndex} does not exist (only ${rows.length} rows)`);
|
|
338
|
+
}
|
|
339
|
+
const row = rows[rowIndex];
|
|
340
|
+
const caret = getCaretRect();
|
|
341
|
+
if (!caret) {
|
|
342
|
+
throw new Error("Caret not found");
|
|
343
|
+
}
|
|
344
|
+
if (caret.top < row.top - 2 || caret.top > row.bottom + 2) {
|
|
345
|
+
throw new Error(`Caret Y (${caret.top}) not on row ${rowIndex} (top: ${row.top}, bottom: ${row.bottom})`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function assertCaretAtEndOfVisualRow(rowIndex, lineIndex = 0) {
|
|
349
|
+
const rows = getVisualRows(lineIndex);
|
|
350
|
+
if (rowIndex >= rows.length) {
|
|
351
|
+
throw new Error(`Row ${rowIndex} does not exist (only ${rows.length} rows)`);
|
|
352
|
+
}
|
|
353
|
+
const row = rows[rowIndex];
|
|
354
|
+
const caret = getCaretRect();
|
|
355
|
+
if (!caret) {
|
|
356
|
+
throw new Error("Caret not found");
|
|
357
|
+
}
|
|
358
|
+
// Caret should be on this row
|
|
359
|
+
if (caret.top < row.top - 2 || caret.top > row.bottom + 2) {
|
|
360
|
+
throw new Error(`Caret Y (${caret.top}) not on row ${rowIndex} (top: ${row.top}, bottom: ${row.bottom})`);
|
|
361
|
+
}
|
|
362
|
+
// Caret X should be at the right edge of the row
|
|
363
|
+
if (caret.left < row.right - 5) {
|
|
364
|
+
throw new Error(`Caret X (${caret.left}) not at end of row ${rowIndex} (right: ${row.right})`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
function assertCaretAtStartOfVisualRow(rowIndex, lineIndex = 0) {
|
|
368
|
+
const rows = getVisualRows(lineIndex);
|
|
369
|
+
if (rowIndex >= rows.length) {
|
|
370
|
+
throw new Error(`Row ${rowIndex} does not exist (only ${rows.length} rows)`);
|
|
371
|
+
}
|
|
372
|
+
const row = rows[rowIndex];
|
|
373
|
+
const caret = getCaretRect();
|
|
374
|
+
if (!caret) {
|
|
375
|
+
throw new Error("Caret not found");
|
|
376
|
+
}
|
|
377
|
+
// Caret should be on this row
|
|
378
|
+
if (caret.top < row.top - 2 || caret.top > row.bottom + 2) {
|
|
379
|
+
throw new Error(`Caret Y (${caret.top}) not on row ${rowIndex} (top: ${row.top}, bottom: ${row.bottom})`);
|
|
380
|
+
}
|
|
381
|
+
// Caret X should be at the left edge of the row
|
|
382
|
+
if (caret.left > row.left + 5) {
|
|
383
|
+
throw new Error(`Caret X (${caret.left}) not at start of row ${rowIndex} (left: ${row.left})`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
async function typeText(text) {
|
|
387
|
+
const contentRoot = getContentRoot();
|
|
388
|
+
contentRoot.focus();
|
|
389
|
+
for (const char of text) {
|
|
390
|
+
const event = new InputEvent("beforeinput", {
|
|
391
|
+
bubbles: true,
|
|
392
|
+
cancelable: true,
|
|
393
|
+
inputType: "insertText",
|
|
394
|
+
data: char,
|
|
395
|
+
});
|
|
396
|
+
contentRoot.dispatchEvent(event);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
async function focus() {
|
|
400
|
+
const contentRoot = getContentRoot();
|
|
401
|
+
contentRoot.focus();
|
|
402
|
+
}
|
|
403
|
+
async function pressEnter() {
|
|
404
|
+
const contentRoot = getContentRoot();
|
|
405
|
+
contentRoot.focus();
|
|
406
|
+
const event = new InputEvent("beforeinput", {
|
|
407
|
+
bubbles: true,
|
|
408
|
+
cancelable: true,
|
|
409
|
+
inputType: "insertParagraph",
|
|
410
|
+
});
|
|
411
|
+
contentRoot.dispatchEvent(event);
|
|
412
|
+
}
|
|
413
|
+
async function pressBackspace() {
|
|
414
|
+
const contentRoot = getContentRoot();
|
|
415
|
+
contentRoot.focus();
|
|
416
|
+
const event = new InputEvent("beforeinput", {
|
|
417
|
+
bubbles: true,
|
|
418
|
+
cancelable: true,
|
|
419
|
+
inputType: "deleteContentBackward",
|
|
420
|
+
});
|
|
421
|
+
contentRoot.dispatchEvent(event);
|
|
422
|
+
}
|
|
423
|
+
async function pressTab() {
|
|
424
|
+
const contentRoot = getContentRoot();
|
|
425
|
+
contentRoot.focus();
|
|
426
|
+
const event = new KeyboardEvent("keydown", {
|
|
427
|
+
bubbles: true,
|
|
428
|
+
cancelable: true,
|
|
429
|
+
key: "Tab",
|
|
430
|
+
code: "Tab",
|
|
431
|
+
});
|
|
432
|
+
contentRoot.dispatchEvent(event);
|
|
433
|
+
}
|
|
434
|
+
async function pressShiftTab() {
|
|
435
|
+
const contentRoot = getContentRoot();
|
|
436
|
+
contentRoot.focus();
|
|
437
|
+
const event = new KeyboardEvent("keydown", {
|
|
438
|
+
bubbles: true,
|
|
439
|
+
cancelable: true,
|
|
440
|
+
key: "Tab",
|
|
441
|
+
code: "Tab",
|
|
442
|
+
shiftKey: true,
|
|
443
|
+
});
|
|
444
|
+
contentRoot.dispatchEvent(event);
|
|
445
|
+
}
|
|
446
|
+
async function pressKey(key, modifiers) {
|
|
447
|
+
const contentRoot = getContentRoot();
|
|
448
|
+
contentRoot.focus();
|
|
449
|
+
const event = new KeyboardEvent("keydown", {
|
|
450
|
+
bubbles: true,
|
|
451
|
+
cancelable: true,
|
|
452
|
+
key,
|
|
453
|
+
code: key.length === 1 ? `Key${key.toUpperCase()}` : key,
|
|
454
|
+
metaKey: modifiers?.meta ?? false,
|
|
455
|
+
ctrlKey: modifiers?.ctrl ?? false,
|
|
456
|
+
shiftKey: modifiers?.shift ?? false,
|
|
457
|
+
altKey: modifiers?.alt ?? false,
|
|
458
|
+
});
|
|
459
|
+
contentRoot.dispatchEvent(event);
|
|
460
|
+
}
|
|
461
|
+
function destroy() {
|
|
462
|
+
overlayRoot?.unmount();
|
|
463
|
+
engine.destroy();
|
|
464
|
+
container.remove();
|
|
465
|
+
if (styleElement) {
|
|
466
|
+
styleElement.remove();
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return {
|
|
470
|
+
container,
|
|
471
|
+
get contentRoot() {
|
|
472
|
+
return getContentRoot();
|
|
473
|
+
},
|
|
474
|
+
engine,
|
|
475
|
+
get selection() {
|
|
476
|
+
return engine.getSelection();
|
|
477
|
+
},
|
|
478
|
+
getLine,
|
|
479
|
+
getLineCount,
|
|
480
|
+
getLineRect,
|
|
481
|
+
getTextNode,
|
|
482
|
+
getCharRect,
|
|
483
|
+
getSelectionRects,
|
|
484
|
+
getCaretRect,
|
|
485
|
+
getVisualRows,
|
|
486
|
+
clickLeftOf: (offset, lineIndex) => clickAtPosition(offset, "left", lineIndex),
|
|
487
|
+
clickRightOf: (offset, lineIndex) => clickAtPosition(offset, "right", lineIndex),
|
|
488
|
+
clickAt: (offset, lineIndex) => clickAtPosition(offset, "center", lineIndex),
|
|
489
|
+
clickAtCoords,
|
|
490
|
+
doubleClick,
|
|
491
|
+
tripleClick,
|
|
492
|
+
typeText,
|
|
493
|
+
pressEnter,
|
|
494
|
+
pressBackspace,
|
|
495
|
+
pressTab,
|
|
496
|
+
pressShiftTab,
|
|
497
|
+
pressKey,
|
|
498
|
+
focus,
|
|
499
|
+
assertCaretOnVisualRow,
|
|
500
|
+
assertCaretAtEndOfVisualRow,
|
|
501
|
+
assertCaretAtStartOfVisualRow,
|
|
502
|
+
destroy,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown-commands.d.ts","sourceRoot":"","sources":["../../src/codemirror/markdown-commands.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAItD,eAAO,MAAM,UAAU,EAAE,YAuDxB,CAAC;AAGF,eAAO,MAAM,YAAY,EAAE,YAuD1B,CAAC;AAGF,eAAO,MAAM,eAAe,EAAE,YAwD7B,CAAC;AAGF,eAAO,MAAM,mBAAmB,EAAE,YAyDjC,CAAC;AAGF,eAAO,MAAM,UAAU,EAAE,YAsDxB,CAAC;AAGF,eAAO,MAAM,aAAa,EAAE,YAqD3B,CAAC;AAGF,eAAO,MAAM,gBAAgB,EAAE,YAqD9B,CAAC;AAGF,eAAO,MAAM,gBAAgB,EAAE,YAuD9B,CAAC;AAGF,eAAO,MAAM,WAAW,EAAE,YAqDzB,CAAC;AAMF,eAAO,MAAM,gBAAgB,EAAE,YAsE9B,CAAC;AAGF,eAAO,MAAM,kBAAkB,EAAE,YAoFhC,CAAC"}
|