fix: preserve blank lines when copying HTTP packets

This commit is contained in:
ji ruo committed 2026-10-13 17:30:44 +08:00
1 parent bd0ba56c82
commit b9b137dc91
1 file changed
+40
+40
View File
@@ -111,6 +111,40 @@ function HighlightedBody({ body, format }: { body: string; format: BodyFormat })
return body;
}
// Empty grid rows have no text, so native selection copying drops their newlines.
// Map the selected DOM boundaries back to the raw packet instead.
function selectedPacketText(container: HTMLElement, value: string, lines: string[]): string | null {
const selection = window.getSelection();
if (!selection || selection.isCollapsed || selection.rangeCount !== 1) return null;
const range = selection.getRangeAt(0);
if (!container.contains(range.startContainer) || !container.contains(range.endContainer)) return null;
let start: number | null = null;
let end: number | null = null;
let offset = 0;
container.querySelectorAll("[data-line] code").forEach((code, index) => {
if (range.intersectsNode(code)) {
const selected = document.createRange();
selected.selectNodeContents(code);
if (range.compareBoundaryPoints(Range.START_TO_START, selected) > 0) {
selected.setStart(range.startContainer, range.startOffset);
}
if (range.compareBoundaryPoints(Range.END_TO_END, selected) < 0) {
selected.setEnd(range.endContainer, range.endOffset);
}
const prefix = document.createRange();
prefix.selectNodeContents(code);
prefix.setEnd(selected.startContainer, selected.startOffset);
start ??= offset + prefix.toString().length;
prefix.setEnd(selected.endContainer, selected.endOffset);
end = offset + prefix.toString().length;
}
const lineEnd = offset + lines[index].length;
offset = lineEnd + (value[lineEnd] === "\r" ? 2 : 1);
});
return start !== null && end !== null ? value.slice(start, end) : null;
}
export function HttpCodeBlock({ raw }: { raw: string }) {
const [wrapLines, setWrapLines] = React.useState(true);
const [copied, setCopied] = React.useState(false);
@@ -183,6 +217,12 @@ export function HttpCodeBlock({ raw }: { raw: string }) {
aria-readonly="true"
tabIndex={0}
className="max-h-[calc(100vh-15rem)] min-w-0 overflow-auto bg-background py-3 font-mono text-xs outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
onCopy={(event) => {
const text = selectedPacketText(event.currentTarget, value, lines);
if (text === null) return;
event.preventDefault();
event.clipboardData.setData("text/plain", text);
}}
onKeyDown={(event) => {
if (!(event.ctrlKey || event.metaKey) || event.key.toLowerCase() !== "a") return;
event.preventDefault();