@replohq/sandbox-runtime 1.20.0 → 1.22.0
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/_vendor/schemas/comments.d.ts +906 -0
- package/_vendor/schemas/commentsOverlayRpc.d.ts +348 -0
- package/_vendor/schemas/commentsOverlayRpc.mjs +92 -0
- package/_vendor/schemas/generated/comments.d.ts +237 -0
- package/_vendor/schemas/generated/commentsOverlayRpc.d.ts +18 -0
- package/_vendor/schemas/generated/selectMode.d.ts +3 -1
- package/_vendor/schemas/selectMode.d.ts +113 -0
- package/_vendor/schemas/selectMode.mjs +1 -1
- package/_vendor/schemas/selectModeIframeRpc.d.ts +226 -0
- package/_vendor/schemas/selectModeIframeRpc.mjs +1 -1
- package/buildMetadata.js +3 -3
- package/chunk-CGRTGQO5.mjs +544 -0
- package/comments-overlay/CommentsOverlayMount.d.ts +1 -0
- package/comments-overlay/CommentsOverlayMount.js +44 -0
- package/comments-overlay/LICENSE-THIRD-PARTY.md +45 -0
- package/comments-overlay/bridge.d.ts +11 -0
- package/comments-overlay/bridge.js +136 -0
- package/comments-overlay/capture.d.ts +55 -0
- package/comments-overlay/capture.js +250 -0
- package/comments-overlay/originGate.d.ts +1 -0
- package/comments-overlay/originGate.js +11 -0
- package/comments-overlay/overlay.d.ts +52 -0
- package/comments-overlay/overlay.js +781 -0
- package/comments-overlay/resolve.d.ts +27 -0
- package/comments-overlay/resolve.js +201 -0
- package/comments-overlay/sourceLookup.d.ts +11 -0
- package/comments-overlay/sourceLookup.js +63 -0
- package/comments-overlay/textQuote.d.ts +17 -0
- package/comments-overlay/textQuote.js +87 -0
- package/comments-overlay/vendor/bippy.d.ts +53 -0
- package/comments-overlay/vendor/bippy.js +402 -0
- package/comments-overlay/vendor/elementAnchors.d.ts +9 -0
- package/comments-overlay/vendor/elementAnchors.js +144 -0
- package/comments-overlay/vendor/fiberElement.d.ts +21 -0
- package/comments-overlay/vendor/fiberElement.js +251 -0
- package/comments-overlay/vendor/htmlPreview.d.ts +13 -0
- package/comments-overlay/vendor/htmlPreview.js +179 -0
- package/comments-overlay/vendor/selector.d.ts +38 -0
- package/comments-overlay/vendor/selector.js +372 -0
- package/next/config.js +37 -1
- package/package.json +4 -4
- package/sandbox-runtime.js +2 -0
- package/select-mode/descriptor/elementDescriptor.d.ts +4 -3
- package/select-mode/descriptor/elementDescriptor.js +20 -30
- package/select-mode/iframeRpcChannel.d.ts +84 -0
- package/select-mode/symbolicate.js +25 -7
- package/chunk-QTGDSYSU.mjs +0 -249
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { CommentAnchorElement } from "../_vendor/schemas/generated/comments";
|
|
2
|
+
import type { ElementSource } from "./capture";
|
|
3
|
+
export type AnchorResolution = {
|
|
4
|
+
status: "attached" | "ambiguous" | "detached";
|
|
5
|
+
element?: Element;
|
|
6
|
+
range?: Range;
|
|
7
|
+
};
|
|
8
|
+
type ResolveAnchorOptions = {
|
|
9
|
+
/** The element this anchor resolved to last time; wins ties and survives DOM swaps. */
|
|
10
|
+
previous: Element | null;
|
|
11
|
+
/** Source lookup for candidates; null uses the live fiber source. */
|
|
12
|
+
getSource: ((element: Element) => ElementSource | null) | null;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Re-finds an element anchor (D-15). Every signal proposes candidates and
|
|
16
|
+
* adds its weight; `listKey` and `textPreview` only add to candidates another
|
|
17
|
+
* signal proposed. The best candidate at or above the threshold is
|
|
18
|
+
* `attached`, `ambiguous` when the runner-up is within the margin, and
|
|
19
|
+
* `detached` when nothing reaches the threshold.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveAnchor(anchor: CommentAnchorElement, root: ParentNode, options: ResolveAnchorOptions): AnchorResolution;
|
|
22
|
+
/**
|
|
23
|
+
* False when the resolved element has no box at this viewport (zero rect or
|
|
24
|
+
* `display: none`): its thread is listed but not pinned here.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isResolutionVisible(resolution: AnchorResolution): boolean;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getElementSource,
|
|
3
|
+
getTextPreview,
|
|
4
|
+
SHADOW_SELECTOR_DELIMITER
|
|
5
|
+
} from "./capture";
|
|
6
|
+
import { findQuoteRanges } from "./textQuote";
|
|
7
|
+
import { resolveLiveElement } from "./vendor/elementAnchors";
|
|
8
|
+
import {
|
|
9
|
+
findNearestListItemKey,
|
|
10
|
+
getElementFiber,
|
|
11
|
+
normalizeFilePath
|
|
12
|
+
} from "./vendor/fiberElement";
|
|
13
|
+
const ANCHOR_SIGNAL_WEIGHTS = {
|
|
14
|
+
semanticSelector: 3,
|
|
15
|
+
fileAndComponent: 3,
|
|
16
|
+
selector: 1.5,
|
|
17
|
+
nthOfTypePath: 1.5,
|
|
18
|
+
quote: 3,
|
|
19
|
+
lineWithinTolerance: 0.5,
|
|
20
|
+
listKey: 1,
|
|
21
|
+
textPreviewSimilarity: 1
|
|
22
|
+
};
|
|
23
|
+
const ANCHOR_ATTACH_THRESHOLD = 4;
|
|
24
|
+
const ANCHOR_AMBIGUITY_MARGIN = 0.5;
|
|
25
|
+
const ANCHOR_LINE_TOLERANCE = 5;
|
|
26
|
+
const TEXT_PREVIEW_SIMILARITY_THRESHOLD = 0.8;
|
|
27
|
+
const MAX_SOURCE_CANDIDATE_SCAN = 5e3;
|
|
28
|
+
function textSimilarity(first, second) {
|
|
29
|
+
if (first === second) {
|
|
30
|
+
return 1;
|
|
31
|
+
}
|
|
32
|
+
const longest = Math.max(first.length, second.length);
|
|
33
|
+
let previousRow = Array.from(
|
|
34
|
+
{ length: second.length + 1 },
|
|
35
|
+
(_, index) => index
|
|
36
|
+
);
|
|
37
|
+
for (let row = 1; row <= first.length; row++) {
|
|
38
|
+
const currentRow = [row];
|
|
39
|
+
for (let column = 1; column <= second.length; column++) {
|
|
40
|
+
const substitution = (previousRow[column - 1] ?? 0) + (first[row - 1] === second[column - 1] ? 0 : 1);
|
|
41
|
+
currentRow[column] = Math.min(
|
|
42
|
+
(previousRow[column] ?? 0) + 1,
|
|
43
|
+
(currentRow[column - 1] ?? 0) + 1,
|
|
44
|
+
substitution
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
previousRow = currentRow;
|
|
48
|
+
}
|
|
49
|
+
return 1 - (previousRow[second.length] ?? longest) / longest;
|
|
50
|
+
}
|
|
51
|
+
function querySegment(roots, selector) {
|
|
52
|
+
return roots.flatMap((root) => {
|
|
53
|
+
try {
|
|
54
|
+
return Array.from(root.querySelectorAll(selector));
|
|
55
|
+
} catch {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function queryAll(root, selector) {
|
|
61
|
+
if (!selector) {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
return Array.from(root.querySelectorAll(selector));
|
|
66
|
+
} catch {
|
|
67
|
+
if (!selector.includes(SHADOW_SELECTOR_DELIMITER)) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const [first = "", ...rest] = selector.split(SHADOW_SELECTOR_DELIMITER);
|
|
72
|
+
let matches = querySegment([root], first);
|
|
73
|
+
for (const segment of rest) {
|
|
74
|
+
const shadowRoots = matches.flatMap((host) => {
|
|
75
|
+
return host.shadowRoot ? [host.shadowRoot] : [];
|
|
76
|
+
});
|
|
77
|
+
matches = querySegment(shadowRoots, segment);
|
|
78
|
+
}
|
|
79
|
+
return matches;
|
|
80
|
+
}
|
|
81
|
+
function getSearchRoot(root) {
|
|
82
|
+
if (root instanceof Element) {
|
|
83
|
+
return root;
|
|
84
|
+
}
|
|
85
|
+
if ("body" in root && root.body instanceof Element) {
|
|
86
|
+
return root.body;
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
function getQuoteElement(range, tagName) {
|
|
91
|
+
const node = range.commonAncestorContainer;
|
|
92
|
+
const container = node instanceof Element ? node : node.parentElement;
|
|
93
|
+
for (let current = container; current; current = current.parentElement) {
|
|
94
|
+
if (current.tagName.toLowerCase() === tagName) {
|
|
95
|
+
return current;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return container;
|
|
99
|
+
}
|
|
100
|
+
function resolveAnchor(anchor, root, options) {
|
|
101
|
+
const getSource = options.getSource ?? getElementSource;
|
|
102
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
103
|
+
const add = (element2, weight, range2) => {
|
|
104
|
+
const candidate2 = candidates.get(element2) ?? { score: 0 };
|
|
105
|
+
candidate2.score += weight;
|
|
106
|
+
if (range2 && !candidate2.range) {
|
|
107
|
+
candidate2.range = range2;
|
|
108
|
+
}
|
|
109
|
+
candidates.set(element2, candidate2);
|
|
110
|
+
};
|
|
111
|
+
for (const element2 of queryAll(root, anchor.semanticSelector)) {
|
|
112
|
+
add(element2, ANCHOR_SIGNAL_WEIGHTS.semanticSelector, null);
|
|
113
|
+
}
|
|
114
|
+
for (const element2 of queryAll(root, anchor.selector)) {
|
|
115
|
+
add(element2, ANCHOR_SIGNAL_WEIGHTS.selector, null);
|
|
116
|
+
}
|
|
117
|
+
for (const element2 of queryAll(root, anchor.nthOfTypePath)) {
|
|
118
|
+
add(element2, ANCHOR_SIGNAL_WEIGHTS.nthOfTypePath, null);
|
|
119
|
+
}
|
|
120
|
+
const anchorSource = anchor.source;
|
|
121
|
+
if (anchorSource) {
|
|
122
|
+
const filePath = normalizeFilePath(anchorSource.filePath);
|
|
123
|
+
const componentName = anchorSource.componentChain[0];
|
|
124
|
+
const sameTag = queryAll(root, anchor.tagName).slice(
|
|
125
|
+
0,
|
|
126
|
+
MAX_SOURCE_CANDIDATE_SCAN
|
|
127
|
+
);
|
|
128
|
+
for (const element2 of sameTag) {
|
|
129
|
+
const source = getSource(element2);
|
|
130
|
+
if (!source || normalizeFilePath(source.filePath) !== filePath || source.componentChain[0] !== componentName) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const isLineClose = anchorSource.line !== void 0 && source.line !== void 0 && Math.abs(source.line - anchorSource.line) <= ANCHOR_LINE_TOLERANCE;
|
|
134
|
+
add(
|
|
135
|
+
element2,
|
|
136
|
+
ANCHOR_SIGNAL_WEIGHTS.fileAndComponent + (isLineClose ? ANCHOR_SIGNAL_WEIGHTS.lineWithinTolerance : 0),
|
|
137
|
+
null
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const searchRoot = getSearchRoot(root);
|
|
142
|
+
if (anchor.quote && searchRoot) {
|
|
143
|
+
const { withContext, exactOnly } = findQuoteRanges(
|
|
144
|
+
searchRoot,
|
|
145
|
+
anchor.quote
|
|
146
|
+
);
|
|
147
|
+
const uniqueExact = exactOnly.length === 1 ? exactOnly : [];
|
|
148
|
+
const quoteRanges = withContext.length > 0 ? withContext : uniqueExact;
|
|
149
|
+
for (const range2 of quoteRanges) {
|
|
150
|
+
const element2 = getQuoteElement(range2, anchor.tagName);
|
|
151
|
+
if (element2) {
|
|
152
|
+
add(element2, ANCHOR_SIGNAL_WEIGHTS.quote, range2);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
for (const [element2, candidate2] of candidates) {
|
|
157
|
+
if (anchor.listKey) {
|
|
158
|
+
const fiber = getElementFiber(element2);
|
|
159
|
+
if (fiber && findNearestListItemKey(fiber) === anchor.listKey) {
|
|
160
|
+
candidate2.score += ANCHOR_SIGNAL_WEIGHTS.listKey;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (textSimilarity(getTextPreview(element2), anchor.textPreview) >= TEXT_PREVIEW_SIMILARITY_THRESHOLD) {
|
|
164
|
+
candidate2.score += ANCHOR_SIGNAL_WEIGHTS.textPreviewSimilarity;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const previous = options.previous ? resolveLiveElement(options.previous) : null;
|
|
168
|
+
const ranked = Array.from(candidates).sort(
|
|
169
|
+
([elementA, candidateA], [elementB, candidateB]) => {
|
|
170
|
+
return candidateB.score - candidateA.score || Number(elementB === previous) - Number(elementA === previous);
|
|
171
|
+
}
|
|
172
|
+
);
|
|
173
|
+
const [best, runnerUp] = ranked;
|
|
174
|
+
if (!best || best[1].score < ANCHOR_ATTACH_THRESHOLD) {
|
|
175
|
+
return { status: "detached" };
|
|
176
|
+
}
|
|
177
|
+
const [element, candidate] = best;
|
|
178
|
+
const range = candidate.range ?? (anchor.quote ? findQuoteRanges(element, anchor.quote).exactOnly[0] : void 0);
|
|
179
|
+
const isAmbiguous = runnerUp !== void 0 && best[1].score - runnerUp[1].score <= ANCHOR_AMBIGUITY_MARGIN;
|
|
180
|
+
return {
|
|
181
|
+
status: isAmbiguous ? "ambiguous" : "attached",
|
|
182
|
+
element,
|
|
183
|
+
...range ? { range } : {}
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function isResolutionVisible(resolution) {
|
|
187
|
+
const element = resolution.element;
|
|
188
|
+
if (!element || resolution.status === "detached") {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
const view = element.ownerDocument.defaultView;
|
|
192
|
+
if (view?.getComputedStyle(element).display === "none") {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
const rect = element.getBoundingClientRect();
|
|
196
|
+
return rect.width > 0 || rect.height > 0;
|
|
197
|
+
}
|
|
198
|
+
export {
|
|
199
|
+
isResolutionVisible,
|
|
200
|
+
resolveAnchor
|
|
201
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ElementSource, SourceSymbolicator } from "./capture";
|
|
2
|
+
/**
|
|
3
|
+
* Resolver source lookup that answers in original files once symbolicated.
|
|
4
|
+
* Stored anchors carry original paths while live fibers point at compiled
|
|
5
|
+
* chunks, so unseen locations are symbolicated in batches in the background
|
|
6
|
+
* and `onUpdate` asks the caller to re-resolve with the new answers.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createSymbolicatedSourceLookup({ symbolicate, onUpdate, }: {
|
|
9
|
+
symbolicate: SourceSymbolicator;
|
|
10
|
+
onUpdate: () => void;
|
|
11
|
+
}): (element: Element) => ElementSource | null;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { getElementSource } from "./capture";
|
|
2
|
+
const MAX_LOCATIONS_PER_BATCH = 200;
|
|
3
|
+
function getLocationKey(location) {
|
|
4
|
+
return `${location.filePath}:${location.line ?? ""}:${location.col ?? ""}`;
|
|
5
|
+
}
|
|
6
|
+
function createSymbolicatedSourceLookup({
|
|
7
|
+
symbolicate,
|
|
8
|
+
onUpdate
|
|
9
|
+
}) {
|
|
10
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
11
|
+
const requested = /* @__PURE__ */ new Set();
|
|
12
|
+
let queue = [];
|
|
13
|
+
let isFlushScheduled = false;
|
|
14
|
+
const flush = async () => {
|
|
15
|
+
isFlushScheduled = false;
|
|
16
|
+
const batch = queue.slice(0, MAX_LOCATIONS_PER_BATCH);
|
|
17
|
+
queue = queue.slice(MAX_LOCATIONS_PER_BATCH);
|
|
18
|
+
if (queue.length > 0) {
|
|
19
|
+
scheduleFlush();
|
|
20
|
+
}
|
|
21
|
+
const results = await symbolicate(batch);
|
|
22
|
+
for (const [index, location] of batch.entries()) {
|
|
23
|
+
const result = results[index];
|
|
24
|
+
if (result) {
|
|
25
|
+
resolved.set(getLocationKey(location), result);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
onUpdate();
|
|
29
|
+
};
|
|
30
|
+
const scheduleFlush = () => {
|
|
31
|
+
if (isFlushScheduled) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
isFlushScheduled = true;
|
|
35
|
+
queueMicrotask(() => {
|
|
36
|
+
void flush();
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
return (element) => {
|
|
40
|
+
const source = getElementSource(element);
|
|
41
|
+
if (!source) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const key = getLocationKey(source);
|
|
45
|
+
const original = resolved.get(key);
|
|
46
|
+
if (original) {
|
|
47
|
+
return { ...original, componentChain: source.componentChain };
|
|
48
|
+
}
|
|
49
|
+
if (!requested.has(key)) {
|
|
50
|
+
requested.add(key);
|
|
51
|
+
queue.push({
|
|
52
|
+
filePath: source.filePath,
|
|
53
|
+
line: source.line,
|
|
54
|
+
col: source.col
|
|
55
|
+
});
|
|
56
|
+
scheduleFlush();
|
|
57
|
+
}
|
|
58
|
+
return source;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export {
|
|
62
|
+
createSymbolicatedSourceLookup
|
|
63
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CommentAnchorElement } from "../_vendor/schemas/generated/comments";
|
|
2
|
+
export type TextQuote = NonNullable<CommentAnchorElement["quote"]>;
|
|
3
|
+
export declare const OVERLAY_ROOT_ATTRIBUTE = "data-replo-comments-overlay";
|
|
4
|
+
/**
|
|
5
|
+
* Quote for a text selection: the exact text plus up to 80 characters on each
|
|
6
|
+
* side, taken within `container` so the context survives edits elsewhere.
|
|
7
|
+
*/
|
|
8
|
+
export declare function captureQuote(range: Range, container: Element): TextQuote | null;
|
|
9
|
+
/**
|
|
10
|
+
* Every occurrence of the quote under `root`: `withContext` also matches the
|
|
11
|
+
* prefix and suffix, `exactOnly` matches the quoted text alone (the context
|
|
12
|
+
* was edited). Text beyond 500k characters is not searched.
|
|
13
|
+
*/
|
|
14
|
+
export declare function findQuoteRanges(root: Element, quote: TextQuote): {
|
|
15
|
+
withContext: Range[];
|
|
16
|
+
exactOnly: Range[];
|
|
17
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const QUOTE_MAX_CHARS = 2e3;
|
|
2
|
+
const QUOTE_CONTEXT_CHARS = 80;
|
|
3
|
+
const TEXT_SEARCH_MAX_CHARS = 5e5;
|
|
4
|
+
const MAX_QUOTE_MATCHES = 20;
|
|
5
|
+
const OVERLAY_ROOT_ATTRIBUTE = "data-replo-comments-overlay";
|
|
6
|
+
function captureQuote(range, container) {
|
|
7
|
+
const exact = range.toString();
|
|
8
|
+
if (!exact.trim() || exact.length > QUOTE_MAX_CHARS) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const before = range.cloneRange();
|
|
12
|
+
before.selectNodeContents(container);
|
|
13
|
+
before.setEnd(range.startContainer, range.startOffset);
|
|
14
|
+
const after = range.cloneRange();
|
|
15
|
+
after.selectNodeContents(container);
|
|
16
|
+
after.setStart(range.endContainer, range.endOffset);
|
|
17
|
+
return {
|
|
18
|
+
exact,
|
|
19
|
+
prefix: before.toString().slice(-QUOTE_CONTEXT_CHARS),
|
|
20
|
+
suffix: after.toString().slice(0, QUOTE_CONTEXT_CHARS)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function indexText(root) {
|
|
24
|
+
const index = { text: "", nodes: [] };
|
|
25
|
+
const visit = (subtree) => {
|
|
26
|
+
const walker = root.ownerDocument.createTreeWalker(
|
|
27
|
+
subtree,
|
|
28
|
+
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT
|
|
29
|
+
);
|
|
30
|
+
for (let node = subtree; node; node = walker.nextNode()) {
|
|
31
|
+
if (node instanceof Element) {
|
|
32
|
+
if (node.shadowRoot && !visit(node.shadowRoot)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (!(node instanceof Text) || node.parentElement?.closest(`[${OVERLAY_ROOT_ATTRIBUTE}]`)) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
index.nodes.push({ node, start: index.text.length });
|
|
41
|
+
index.text += node.data;
|
|
42
|
+
if (index.text.length > TEXT_SEARCH_MAX_CHARS) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
};
|
|
48
|
+
return visit(root) ? index : null;
|
|
49
|
+
}
|
|
50
|
+
function toRange(index, start, end) {
|
|
51
|
+
const startEntry = index.nodes.findLast((entry) => entry.start <= start);
|
|
52
|
+
const endEntry = index.nodes.findLast((entry) => entry.start < end);
|
|
53
|
+
if (!startEntry || !endEntry || startEntry.node.getRootNode() !== endEntry.node.getRootNode()) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const range = startEntry.node.ownerDocument.createRange();
|
|
57
|
+
range.setStart(startEntry.node, start - startEntry.start);
|
|
58
|
+
range.setEnd(endEntry.node, end - endEntry.start);
|
|
59
|
+
return range;
|
|
60
|
+
}
|
|
61
|
+
function findQuoteRanges(root, quote) {
|
|
62
|
+
const result = {
|
|
63
|
+
withContext: [],
|
|
64
|
+
exactOnly: []
|
|
65
|
+
};
|
|
66
|
+
const index = quote.exact ? indexText(root) : null;
|
|
67
|
+
if (!index) {
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
for (let at = index.text.indexOf(quote.exact); at >= 0 && result.exactOnly.length < MAX_QUOTE_MATCHES; at = index.text.indexOf(quote.exact, at + 1)) {
|
|
71
|
+
const range = toRange(index, at, at + quote.exact.length);
|
|
72
|
+
if (!range) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
result.exactOnly.push(range);
|
|
76
|
+
const end = at + quote.exact.length;
|
|
77
|
+
if (index.text.slice(Math.max(0, at - quote.prefix.length), at) === quote.prefix && index.text.slice(end, end + quote.suffix.length) === quote.suffix) {
|
|
78
|
+
result.withContext.push(range);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
export {
|
|
84
|
+
OVERLAY_ROOT_ATTRIBUTE,
|
|
85
|
+
captureQuote,
|
|
86
|
+
findQuoteRanges
|
|
87
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2024-present Aiden Bai
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
* https://github.com/aidenybai/bippy
|
|
5
|
+
*/
|
|
6
|
+
export interface DebugSource {
|
|
7
|
+
fileName: string;
|
|
8
|
+
lineNumber?: number;
|
|
9
|
+
columnNumber?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface ServerComponentInfo {
|
|
12
|
+
name: string;
|
|
13
|
+
env?: string;
|
|
14
|
+
debugStack?: unknown;
|
|
15
|
+
owner?: Fiber | ServerComponentInfo | null;
|
|
16
|
+
}
|
|
17
|
+
export interface Fiber {
|
|
18
|
+
tag: number;
|
|
19
|
+
key: string | null;
|
|
20
|
+
type: unknown;
|
|
21
|
+
stateNode: unknown;
|
|
22
|
+
return: Fiber | null;
|
|
23
|
+
child: Fiber | null;
|
|
24
|
+
sibling: Fiber | null;
|
|
25
|
+
alternate: Fiber | null;
|
|
26
|
+
_debugSource?: DebugSource | null;
|
|
27
|
+
_debugStack?: unknown;
|
|
28
|
+
_debugOwner?: Fiber | ServerComponentInfo | null;
|
|
29
|
+
}
|
|
30
|
+
export interface StackFrame {
|
|
31
|
+
functionName?: string;
|
|
32
|
+
fileName?: string;
|
|
33
|
+
lineNumber?: number;
|
|
34
|
+
columnNumber?: number;
|
|
35
|
+
}
|
|
36
|
+
export declare function isFiber(value: unknown): value is Fiber;
|
|
37
|
+
export declare function isCompositeFiber(fiber: Fiber): boolean;
|
|
38
|
+
export declare function isHostFiber(fiber: Fiber): boolean;
|
|
39
|
+
export declare function getFiberFromHostInstance(element: Element): Fiber | null;
|
|
40
|
+
export declare function getDisplayName(type: unknown): string | null;
|
|
41
|
+
export declare function getLatestFiber(fiber: Fiber): Fiber;
|
|
42
|
+
export declare function parseStack(stackString: string): StackFrame[];
|
|
43
|
+
export declare function parseDebugStack(debugStack: unknown): {
|
|
44
|
+
frames: StackFrame[];
|
|
45
|
+
isTrusted: boolean;
|
|
46
|
+
};
|
|
47
|
+
export declare function normalizeFileName(fileName: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* Compiled (unsymbolicated) source of a fiber, in bippy's `getSource` order:
|
|
50
|
+
* `_debugSource` (React ≤18), the fiber's own `_debugStack` (React 19), an
|
|
51
|
+
* owned child's `_debugStack`, then the throwing dispatcher.
|
|
52
|
+
*/
|
|
53
|
+
export declare function getFiberSource(fiber: Fiber): DebugSource | null;
|