fix: preserve code block file reference locations
This commit is contained in:
@@ -1307,6 +1307,34 @@ const isLikelyFilePath = (value: string): boolean => {
|
|||||||
return isLikelyFilePathValue(parsed.path);
|
return isLikelyFilePathValue(parsed.path);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const findTextPosition = (textNodes: Text[], targetOffset: number): { node: Text; offset: number } | null => {
|
||||||
|
let currentOffset = 0;
|
||||||
|
|
||||||
|
for (const node of textNodes) {
|
||||||
|
const nextOffset = currentOffset + node.data.length;
|
||||||
|
if (targetOffset <= nextOffset) {
|
||||||
|
return { node, offset: Math.max(0, targetOffset - currentOffset) };
|
||||||
|
}
|
||||||
|
currentOffset = nextOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastNode = textNodes.at(-1);
|
||||||
|
return lastNode ? { node: lastNode, offset: lastNode.data.length } : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const unwrapBlockCodePathTokens = (container: HTMLElement): void => {
|
||||||
|
const tokenSpans = container.querySelectorAll<HTMLElement>(BLOCK_PATH_TOKEN_SELECTOR);
|
||||||
|
for (const span of Array.from(tokenSpans)) {
|
||||||
|
span.replaceWith(container.ownerDocument.createTextNode(span.textContent ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
const scannedBlocks = container.querySelectorAll<HTMLElement>(`code[${CODE_BLOCK_PATH_SCANNED_ATTR}]`);
|
||||||
|
for (const codeBlock of Array.from(scannedBlocks)) {
|
||||||
|
codeBlock.removeAttribute(CODE_BLOCK_PATH_SCANNED_ATTR);
|
||||||
|
codeBlock.normalize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const extractPathCandidateFromElement = (element: HTMLElement): string => {
|
const extractPathCandidateFromElement = (element: HTMLElement): string => {
|
||||||
if (element.tagName.toLowerCase() === 'a') {
|
if (element.tagName.toLowerCase() === 'a') {
|
||||||
const href = element.getAttribute('href')?.trim();
|
const href = element.getAttribute('href')?.trim();
|
||||||
@@ -1359,49 +1387,40 @@ const wrapBlockCodePathTokens = (container: HTMLElement): void => {
|
|||||||
currentNode = walker.nextNode();
|
currentNode = walker.nextNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const textNode of textNodes) {
|
const fullText = codeBlock.textContent ?? '';
|
||||||
// Skip nodes already inside one of our token spans.
|
if (!fullText.includes('.')) {
|
||||||
if (textNode.parentElement?.closest(BLOCK_PATH_TOKEN_SELECTOR)) {
|
codeBlock.setAttribute(CODE_BLOCK_PATH_SCANNED_ATTR, 'true');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
BLOCK_PATH_TOKEN_RE.lastIndex = 0;
|
||||||
|
const matches: Array<{ start: number; end: number; raw: string }> = [];
|
||||||
|
let match: RegExpExecArray | null = BLOCK_PATH_TOKEN_RE.exec(fullText);
|
||||||
|
while (match) {
|
||||||
|
const raw = match[0];
|
||||||
|
if (raw && isLikelyFilePath(raw)) {
|
||||||
|
matches.push({ start: match.index, end: match.index + raw.length, raw });
|
||||||
|
}
|
||||||
|
match = BLOCK_PATH_TOKEN_RE.exec(fullText);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const { start, end, raw } of matches.reverse()) {
|
||||||
|
const startPosition = findTextPosition(textNodes, start);
|
||||||
|
const endPosition = findTextPosition(textNodes, end);
|
||||||
|
if (!startPosition || !endPosition) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const text = textNode.data;
|
const range = doc.createRange();
|
||||||
if (!text || !text.includes('.')) {
|
range.setStart(startPosition.node, startPosition.offset);
|
||||||
continue;
|
range.setEnd(endPosition.node, endPosition.offset);
|
||||||
}
|
|
||||||
|
|
||||||
BLOCK_PATH_TOKEN_RE.lastIndex = 0;
|
const span = doc.createElement('span');
|
||||||
const matches: Array<{ start: number; end: number; raw: string }> = [];
|
span.setAttribute(BLOCK_PATH_TOKEN_ATTR, 'true');
|
||||||
let match: RegExpExecArray | null = BLOCK_PATH_TOKEN_RE.exec(text);
|
span.textContent = raw;
|
||||||
while (match) {
|
|
||||||
const raw = match[0];
|
|
||||||
if (raw && isLikelyFilePath(raw)) {
|
|
||||||
matches.push({ start: match.index, end: match.index + raw.length, raw });
|
|
||||||
}
|
|
||||||
match = BLOCK_PATH_TOKEN_RE.exec(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matches.length === 0) {
|
range.deleteContents();
|
||||||
continue;
|
range.insertNode(span);
|
||||||
}
|
|
||||||
|
|
||||||
const fragment = doc.createDocumentFragment();
|
|
||||||
let cursor = 0;
|
|
||||||
for (const { start, end, raw } of matches) {
|
|
||||||
if (start > cursor) {
|
|
||||||
fragment.appendChild(doc.createTextNode(text.slice(cursor, start)));
|
|
||||||
}
|
|
||||||
const span = doc.createElement('span');
|
|
||||||
span.setAttribute(BLOCK_PATH_TOKEN_ATTR, 'true');
|
|
||||||
span.textContent = raw;
|
|
||||||
fragment.appendChild(span);
|
|
||||||
cursor = end;
|
|
||||||
}
|
|
||||||
if (cursor < text.length) {
|
|
||||||
fragment.appendChild(doc.createTextNode(text.slice(cursor)));
|
|
||||||
}
|
|
||||||
|
|
||||||
textNode.parentNode?.replaceChild(fragment, textNode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
codeBlock.setAttribute(CODE_BLOCK_PATH_SCANNED_ATTR, 'true');
|
codeBlock.setAttribute(CODE_BLOCK_PATH_SCANNED_ATTR, 'true');
|
||||||
@@ -1520,6 +1539,7 @@ const useFileReferenceInteractions = ({
|
|||||||
for (const candidate of Array.from(annotated)) {
|
for (const candidate of Array.from(annotated)) {
|
||||||
clearFileLinkAttributes(candidate);
|
clearFileLinkAttributes(candidate);
|
||||||
}
|
}
|
||||||
|
unwrapBlockCodePathTokens(container);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
|
|||||||
Reference in New Issue
Block a user