Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions templates/repo/diff/blob_excerpt.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</td>
{{else}}
{{$inlineDiff := $.section.GetComputedInlineDiffFor $line ctx.Locale}}
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"><span rel="{{if $line.LeftIdx}}diff-{{$.FileNameHash}}L{{$line.LeftIdx}}{{end}}"></span></td>
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"><span rel="{{if $line.LeftIdx}}diff-{{$.FileNameHash}}L{{$line.LeftIdx}}{{end}}"{{if $line.LeftIdx}} id="diff-{{$.FileNameHash}}L{{$line.LeftIdx}}"{{end}}></span></td>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why duplicate rel= and id=?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not have a clear defination like diff-{hash}-{L|R}xxx?

<td class="lines-escape lines-escape-old">{{if and $line.LeftIdx $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}</td>
<td class="lines-type-marker lines-type-marker-old">{{if $line.LeftIdx}}<span class="tw-font-mono" data-type-marker=""></span>{{end}}</td>
<td class="lines-code lines-code-old">
Expand All @@ -27,7 +27,7 @@
<code class="code-inner"></code>
{{- end -}}
</td>
<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{$.FileNameHash}}R{{$line.RightIdx}}{{end}}"></span></td>
<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{$.FileNameHash}}R{{$line.RightIdx}}{{end}}"{{if $line.RightIdx}} id="diff-{{$.FileNameHash}}R{{$line.RightIdx}}"{{end}}></span></td>
<td class="lines-escape lines-escape-new">{{if and $line.RightIdx $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}</td>
<td class="lines-type-marker lines-type-marker-new">{{if $line.RightIdx}}<span class="tw-font-mono" data-type-marker=""></span>{{end}}</td>
<td class="lines-code lines-code-new">
Expand Down Expand Up @@ -65,8 +65,8 @@
{{if eq .GetType 4}}
<td colspan="2" class="lines-num">{{$line.RenderBlobExcerptButtons $.FileNameHash $diffBlobExcerptData}}</td>
{{else}}
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"><span rel="{{if $line.LeftIdx}}diff-{{$.FileNameHash}}L{{$line.LeftIdx}}{{end}}"></span></td>
<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{$.FileNameHash}}R{{$line.RightIdx}}{{end}}"></span></td>
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"><span rel="{{if $line.LeftIdx}}diff-{{$.FileNameHash}}L{{$line.LeftIdx}}{{end}}"{{if $line.LeftIdx}} id="diff-{{$.FileNameHash}}L{{$line.LeftIdx}}"{{end}}></span></td>
<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{$.FileNameHash}}R{{$line.RightIdx}}{{end}}"{{if $line.RightIdx}} id="diff-{{$.FileNameHash}}R{{$line.RightIdx}}"{{end}}></span></td>
{{end}}
{{$inlineDiff := $.section.GetComputedInlineDiffFor $line ctx.Locale}}
<td class="lines-escape">{{if $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}</td>
Expand Down
8 changes: 4 additions & 4 deletions templates/repo/diff/section_split.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
{{$match := index $section.Lines $line.Match}}
{{- $leftDiff := ""}}{{if $line.LeftIdx}}{{$leftDiff = $section.GetComputedInlineDiffFor $line ctx.Locale}}{{end}}
{{- $rightDiff := ""}}{{if $match.RightIdx}}{{$rightDiff = $section.GetComputedInlineDiffFor $match ctx.Locale}}{{end}}
<td class="lines-num lines-num-old del-code" data-line-num="{{$line.LeftIdx}}"><span rel="diff-{{$file.NameHash}}L{{$line.LeftIdx}}"></span></td>
<td class="lines-num lines-num-old del-code" data-line-num="{{$line.LeftIdx}}"><span rel="diff-{{$file.NameHash}}L{{$line.LeftIdx}}" id="diff-{{$file.NameHash}}L{{$line.LeftIdx}}"></span></td>
<td class="lines-escape del-code lines-escape-old">{{if $line.LeftIdx}}{{if $leftDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $leftDiff}}"></button>{{end}}{{end}}</td>
<td class="lines-type-marker lines-type-marker-old del-code"><span class="tw-font-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span></td>
<td class="lines-code lines-code-old del-code">
Expand All @@ -39,7 +39,7 @@
<code class="code-inner"></code>
{{- end -}}
</td>
<td class="lines-num lines-num-new add-code" data-line-num="{{if $match.RightIdx}}{{$match.RightIdx}}{{end}}"><span rel="{{if $match.RightIdx}}diff-{{$file.NameHash}}R{{$match.RightIdx}}{{end}}"></span></td>
<td class="lines-num lines-num-new add-code" data-line-num="{{if $match.RightIdx}}{{$match.RightIdx}}{{end}}"><span rel="{{if $match.RightIdx}}diff-{{$file.NameHash}}R{{$match.RightIdx}}{{end}}"{{if $match.RightIdx}} id="diff-{{$file.NameHash}}R{{$match.RightIdx}}"{{end}}></span></td>
<td class="lines-escape add-code lines-escape-new">{{if $match.RightIdx}}{{if $rightDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $rightDiff}}"></button>{{end}}{{end}}</td>
<td class="lines-type-marker lines-type-marker-new add-code">{{if $match.RightIdx}}<span class="tw-font-mono" data-type-marker="{{$match.GetLineTypeMarker}}"></span>{{end}}</td>
<td class="lines-code lines-code-new add-code">
Expand All @@ -56,7 +56,7 @@
</td>
{{else}}
{{$inlineDiff := $section.GetComputedInlineDiffFor $line ctx.Locale}}
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"><span rel="{{if $line.LeftIdx}}diff-{{$file.NameHash}}L{{$line.LeftIdx}}{{end}}"></span></td>
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"><span rel="{{if $line.LeftIdx}}diff-{{$file.NameHash}}L{{$line.LeftIdx}}{{end}}"{{if $line.LeftIdx}} id="diff-{{$file.NameHash}}L{{$line.LeftIdx}}"{{end}}></span></td>
<td class="lines-escape lines-escape-old">{{if $line.LeftIdx}}{{if $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}{{end}}</td>
<td class="lines-type-marker lines-type-marker-old">{{if $line.LeftIdx}}<span class="tw-font-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span>{{end}}</td>
<td class="lines-code lines-code-old">
Expand All @@ -71,7 +71,7 @@
<code class="code-inner"></code>
{{- end -}}
</td>
<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{$file.NameHash}}R{{$line.RightIdx}}{{end}}"></span></td>
<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{$file.NameHash}}R{{$line.RightIdx}}{{end}}"{{if $line.RightIdx}} id="diff-{{$file.NameHash}}R{{$line.RightIdx}}"{{end}}></span></td>
<td class="lines-escape lines-escape-new">{{if $line.RightIdx}}{{if $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}{{end}}</td>
<td class="lines-type-marker lines-type-marker-new">{{if $line.RightIdx}}<span class="tw-font-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span>{{end}}</td>
<td class="lines-code lines-code-new">
Expand Down
4 changes: 2 additions & 2 deletions templates/repo/diff/section_unified.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
<td colspan="2" class="lines-num"></td>
{{end}}
{{else}}
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"><span rel="{{if $line.LeftIdx}}diff-{{$file.NameHash}}L{{$line.LeftIdx}}{{end}}"></span></td>
<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{$file.NameHash}}R{{$line.RightIdx}}{{end}}"></span></td>
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"><span rel="{{if $line.LeftIdx}}diff-{{$file.NameHash}}L{{$line.LeftIdx}}{{end}}"{{if $line.LeftIdx}} id="diff-{{$file.NameHash}}L{{$line.LeftIdx}}"{{end}}></span></td>
<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{$file.NameHash}}R{{$line.RightIdx}}{{end}}"{{if $line.RightIdx}} id="diff-{{$file.NameHash}}R{{$line.RightIdx}}"{{end}}></span></td>
{{end}}
{{$inlineDiff := $section.GetComputedInlineDiffFor $line ctx.Locale -}}
<td class="lines-escape">
Expand Down
34 changes: 34 additions & 0 deletions web_src/css/repo.css
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,14 @@ td .commit-summary {
text-align: right;
}

.repository .diff-file-box .code-diff .lines-num[data-line-num] {
cursor: pointer;
}

.repository .diff-file-box .code-diff .lines-num[data-line-num]:hover {
color: var(--color-text-dark);
}

.repository .diff-file-box .code-diff tbody tr .lines-type-marker {
width: 10px;
min-width: 10px;
Expand All @@ -996,6 +1004,32 @@ td .commit-summary {
display: inline-block;
}

.repository .diff-file-box .code-diff tr.active .lines-num,
.repository .diff-file-box .code-diff tr.active .lines-escape,
.repository .diff-file-box .code-diff tr.active .lines-type-marker,
.repository .diff-file-box .code-diff tr.active .lines-code {
background: var(--color-highlight-bg);
}

.repository .diff-file-box .code-diff tr.active .lines-num {
position: relative;
}

.repository .diff-file-box .code-diff tr.active .lines-num::after {
display: none;
}

.repository .diff-file-box .code-diff tr.active .lines-num:first-of-type::after {
content: "";
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 2px;
background: var(--color-highlight-fg);
display: block;
}

.repository .diff-file-box .code-diff-split .tag-code .lines-code code.code-inner {
padding-left: 10px !important;
}
Expand Down
241 changes: 241 additions & 0 deletions web_src/js/features/repo-diff-selection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
import {addDelegatedEventListener} from '../utils/dom.ts';
import {sleep} from '../utils.ts';
import {setFileFolding} from './file-fold.ts';

const diffLineNumberCellSelector = '#diff-file-boxes .code-diff td.lines-num[data-line-num]';
const diffAnchorSuffixRegex = /([LR])(\d+)$/;
const diffHashRangeRegex = /^(diff-[0-9a-f]+)([LR]\d+)(?:-([LR]\d+))?$/i;

type DiffAnchorSide = 'L' | 'R';
type DiffAnchorInfo = {anchor: string, fragment: string, side: DiffAnchorSide, line: number};
type DiffSelectionState = DiffAnchorInfo & {container: HTMLElement};
type DiffSelectionRange = {fragment: string, startSide: DiffAnchorSide, startLine: number, endSide: DiffAnchorSide, endLine: number};

let diffSelectionStart: DiffSelectionState | null = null;

function changeHash(hash: string) {
if (window.history.pushState) {
window.history.pushState(null, null, hash);
} else {
window.location.hash = hash;
}
}
Comment on lines +16 to +22
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it stays, it should be moved to web_src/js/utils/dom.ts as a general DOM utility.

But I think it's pointless to test for existance window.history.pushState, every browser released in the last 15 years should have that function, so we can just assume it's there, and in which case this function makes no sense and it should just use window.history.pushState directly.

Oh and please pass '' as second argument to it, typescript wants it that way.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh and likely this should use window.history.replaceState (see other comment below).


function parseDiffAnchor(anchor: string | null): DiffAnchorInfo | null {
if (!anchor || !anchor.startsWith('diff-')) return null;
const suffixMatch = diffAnchorSuffixRegex.exec(anchor);
if (!suffixMatch) return null;
const line = Number.parseInt(suffixMatch[2]);
if (Number.isNaN(line)) return null;
const fragment = anchor.slice(0, -suffixMatch[0].length);
const side = suffixMatch[1] as DiffAnchorSide;
return {anchor, fragment, side, line};
}

function applyDiffLineSelection(container: HTMLElement, range: DiffSelectionRange, options?: {updateHash?: boolean}): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the complex logic needs tests if it is testable in frontend unit test.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basic DOM interactions should be testable, thought I'm not sure if it's really worth it for this function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you can fully understand the logic and confirm its right, then not worth.

Copy link
Member

@silverwind silverwind Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replicating the DOM structure in a unit test will be hard. Ideally we should have e2e tests for this kind of stuff, but we don't (yet). If possible, extract testable functions here which ideally have no DOM interaction.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Purely logic code:

image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And DOM operations without layout & backend:

image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah such non-DOM logic is easily testable and should be tested.

// Find the start and end anchor elements
const startId = `${range.fragment}${range.startSide}${range.startLine}`;
const endId = `${range.fragment}${range.endSide}${range.endLine}`;
const startSpan = container.querySelector<HTMLElement>(`#${CSS.escape(startId)}`);
const endSpan = container.querySelector<HTMLElement>(`#${CSS.escape(endId)}`);

if (!startSpan || !endSpan) return false;

const startTr = startSpan.closest('tr');
const endTr = endSpan.closest('tr');
if (!startTr || !endTr) return false;

// Clear previous selection
for (const tr of document.querySelectorAll('.code-diff tr.active')) {
tr.classList.remove('active');
}

// Get all rows in the diff section
const allRows = Array.from(container.querySelectorAll<HTMLElement>('.code-diff tbody tr'));
const startIndex = allRows.indexOf(startTr);
const endIndex = allRows.indexOf(endTr);

if (startIndex === -1 || endIndex === -1) return false;

// Select all rows between start and end (inclusive)
const minIndex = Math.min(startIndex, endIndex);
const maxIndex = Math.max(startIndex, endIndex);

for (let i = minIndex; i <= maxIndex; i++) {
const row = allRows[i];
// Only select rows that are actual diff lines (not comment rows, expansion buttons, etc.)
// Skip rows with data-line-type="4" which are code expansion buttons
if (row.querySelector('td.lines-num') && row.getAttribute('data-line-type') !== '4') {
row.classList.add('active');
}
}

if (options?.updateHash !== false) {
const startAnchor = `${range.fragment}${range.startSide}${range.startLine}`;
const hashValue = (range.startSide === range.endSide && range.startLine === range.endLine) ?
startAnchor :
`${startAnchor}-${range.endSide}${range.endLine}`;
changeHash(`#${hashValue}`);
}
return true;
}

export function parseDiffHashRange(hashValue: string): DiffSelectionRange | null {
if (!hashValue.startsWith('diff-')) return null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not the first time see the same logic startsWith('diff-' ....

const match = diffHashRangeRegex.exec(hashValue);
if (!match) return null;
const startInfo = parseDiffAnchor(`${match[1]}${match[2]}`);
if (!startInfo) return null;
let endSide = startInfo.side;
let endLine = startInfo.line;
if (match[3]) {
const endInfo = parseDiffAnchor(`${match[1]}${match[3]}`);
if (!endInfo) {
return {fragment: startInfo.fragment, startSide: startInfo.side, startLine: startInfo.line, endSide: startInfo.side, endLine: startInfo.line};
}
endSide = endInfo.side;
endLine = endInfo.line;
}
return {
fragment: startInfo.fragment,
startSide: startInfo.side,
startLine: startInfo.line,
endSide,
endLine,
};
}

export async function highlightDiffSelectionFromHash(): Promise<boolean> {
const {hash} = window.location;
if (!hash || !hash.startsWith('#diff-')) return false;
const range = parseDiffHashRange(hash.substring(1));
if (!range) return false;
const targetId = `${range.fragment}${range.startSide}${range.startLine}`;

// Wait for the target element to be available (in case it needs to be loaded)
const targetSpan = document.querySelector<HTMLElement>(`#${CSS.escape(targetId)}`);
if (!targetSpan) {
// Target not found - it might need to be loaded via "show more files"
// Return false to let onLocationHashChange handle the loading
return false;
}

const container = targetSpan.closest<HTMLElement>('.diff-file-box');
if (!container) return false;

// Check if the file is collapsed and expand it if needed
if (container.getAttribute('data-folded') === 'true') {
const foldBtn = container.querySelector<HTMLElement>('.fold-file');
if (foldBtn) {
// Expand the file using the setFileFolding utility
setFileFolding(container, foldBtn, false);
// Wait a bit for the expansion animation
await sleep(100);
Copy link
Member

@silverwind silverwind Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can use https://developer.mozilla.org/en-US/docs/Web/API/Element/transitionend_event to detect when a transition has finished but it may be better to make setFileFolding return a Promise that resolves when the transition has ended.

}
}

if (!applyDiffLineSelection(container, range, {updateHash: false})) return false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateHash option is an over-design.

You can simply:

if (!applyDiffLineSelection(container, range)) return false;
updateHash(.....);

diffSelectionStart = {
anchor: targetId,
fragment: range.fragment,
side: range.startSide,
line: range.startLine,
container,
};

// Scroll to the first selected line (scroll to the tr element, not the span)
// The span is an inline element inside td, we need to scroll to the tr for better visibility
await sleep(10);
Comment on lines +146 to +148
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can't be right

Copy link
Member

@silverwind silverwind Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it needs a sleep(0) or requestAnimationFrame? Both should flush the JS event loop of pending tasks with the latter being faster.

const targetTr = targetSpan.closest('tr');
if (targetTr) {
targetTr.scrollIntoView({behavior: 'smooth', block: 'center'});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally hate smooth scrolling, maybe scroll instantly?

}
return true;
}

function handleDiffLineNumberClick(cell: HTMLElement, e: MouseEvent) {
let span = cell.querySelector<HTMLSpanElement>('span[id^="diff-"]');
let info = parseDiffAnchor(span?.id ?? null);

// If clicked cell has no line number (e.g., clicking on the empty side of a deletion/addition),
// try to find the line number from the sibling cell on the same row
if (!info) {
const row = cell.closest('tr');
if (!row) return;
// Find the other line number cell in the same row
const siblingCell = cell.classList.contains('lines-num-old') ?
row.querySelector<HTMLElement>('td.lines-num-new') :
row.querySelector<HTMLElement>('td.lines-num-old');
if (siblingCell) {
span = siblingCell.querySelector<HTMLSpanElement>('span[id^="diff-"]');
info = parseDiffAnchor(span?.id ?? null);
}
if (!info) return;
}

const container = cell.closest<HTMLElement>('.diff-file-box');
if (!container) return;

e.preventDefault();

// Check if clicking on a single already-selected line without shift key - deselect it
if (!e.shiftKey) {
const clickedRow = cell.closest('tr');
if (clickedRow?.classList.contains('active')) {
// Check if this is a single-line selection by checking if it's the only selected line
const selectedRows = container.querySelectorAll('.code-diff tr.active');
if (selectedRows.length === 1) {
// This is a single selected line, deselect it
clickedRow.classList.remove('active');
diffSelectionStart = null;
// Remove hash from URL completely
if (window.history.pushState) {
window.history.pushState(null, null, window.location.pathname + window.location.search);
} else {
window.location.hash = '';
}
Copy link
Member

@silverwind silverwind Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you actually want to use window.history.replaceState? I hate sites that push useless history entries because it pollutes the "back" stack, making the user hit the back button more than is necessary.

window.getSelection().removeAllRanges();
return;
}
}
}

let rangeStart: DiffAnchorInfo = info;
if (e.shiftKey && diffSelectionStart &&
diffSelectionStart.container === container &&
diffSelectionStart.fragment === info.fragment) {
rangeStart = diffSelectionStart;
}

const range: DiffSelectionRange = {
fragment: info.fragment,
startSide: rangeStart.side,
startLine: rangeStart.line,
endSide: info.side,
endLine: info.line,
};

if (applyDiffLineSelection(container, range)) {
if (!e.shiftKey || !diffSelectionStart || diffSelectionStart.container !== container || diffSelectionStart.fragment !== info.fragment) {
diffSelectionStart = {...info, container};
}
window.getSelection().removeAllRanges();
}
}

export function initDiffLineSelection() {
addDelegatedEventListener<HTMLElement, MouseEvent>(document, 'click', diffLineNumberCellSelector, (cell, e) => {
if (e.defaultPrevented) return;
// Ignore clicks on or inside code-expander-buttons
const target = e.target as HTMLElement;
if (target.closest('.code-expander-button') || target.closest('.code-expander-buttons') ||
target.closest('button, a, input, select, textarea, summary, [role="button"]')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where the list comes from? Won't it miss something?

return;
}
handleDiffLineNumberClick(cell, e);
});
window.addEventListener('hashchange', () => {
highlightDiffSelectionFromHash();
});
highlightDiffSelectionFromHash();
}
Loading