mirror of
https://github.com/go-gitea/gitea.git
synced 2025-12-09 00:21:31 +01:00
Merge f8567d04a0e039a50ef7020303bbfed4492e3a36 into 98ef79d73a6a546241dd02959ae17f136369b604
This commit is contained in:
commit
2e2c54e8a3
@ -11,7 +11,7 @@
|
|||||||
</td>
|
</td>
|
||||||
{{else}}
|
{{else}}
|
||||||
{{$inlineDiff := $.section.GetComputedInlineDiffFor $line ctx.Locale}}
|
{{$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>
|
||||||
<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-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-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">
|
<td class="lines-code lines-code-old">
|
||||||
@ -27,7 +27,7 @@
|
|||||||
<code class="code-inner"></code>
|
<code class="code-inner"></code>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
</td>
|
</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-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-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">
|
<td class="lines-code lines-code-new">
|
||||||
@ -65,8 +65,8 @@
|
|||||||
{{if eq .GetType 4}}
|
{{if eq .GetType 4}}
|
||||||
<td colspan="2" class="lines-num">{{$line.RenderBlobExcerptButtons $.FileNameHash $diffBlobExcerptData}}</td>
|
<td colspan="2" class="lines-num">{{$line.RenderBlobExcerptButtons $.FileNameHash $diffBlobExcerptData}}</td>
|
||||||
{{else}}
|
{{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-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}}"></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}}
|
{{end}}
|
||||||
{{$inlineDiff := $.section.GetComputedInlineDiffFor $line ctx.Locale}}
|
{{$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>
|
<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>
|
||||||
|
|||||||
@ -24,7 +24,7 @@
|
|||||||
{{$match := index $section.Lines $line.Match}}
|
{{$match := index $section.Lines $line.Match}}
|
||||||
{{- $leftDiff := ""}}{{if $line.LeftIdx}}{{$leftDiff = $section.GetComputedInlineDiffFor $line ctx.Locale}}{{end}}
|
{{- $leftDiff := ""}}{{if $line.LeftIdx}}{{$leftDiff = $section.GetComputedInlineDiffFor $line ctx.Locale}}{{end}}
|
||||||
{{- $rightDiff := ""}}{{if $match.RightIdx}}{{$rightDiff = $section.GetComputedInlineDiffFor $match 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-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-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">
|
<td class="lines-code lines-code-old del-code">
|
||||||
@ -39,7 +39,7 @@
|
|||||||
<code class="code-inner"></code>
|
<code class="code-inner"></code>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
</td>
|
</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-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-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">
|
<td class="lines-code lines-code-new add-code">
|
||||||
@ -56,7 +56,7 @@
|
|||||||
</td>
|
</td>
|
||||||
{{else}}
|
{{else}}
|
||||||
{{$inlineDiff := $section.GetComputedInlineDiffFor $line ctx.Locale}}
|
{{$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-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-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">
|
<td class="lines-code lines-code-old">
|
||||||
@ -71,7 +71,7 @@
|
|||||||
<code class="code-inner"></code>
|
<code class="code-inner"></code>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
</td>
|
</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-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-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">
|
<td class="lines-code lines-code-new">
|
||||||
|
|||||||
@ -19,8 +19,8 @@
|
|||||||
<td colspan="2" class="lines-num"></td>
|
<td colspan="2" class="lines-num"></td>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{else}}
|
{{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-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}}"></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}}
|
{{end}}
|
||||||
{{$inlineDiff := $section.GetComputedInlineDiffFor $line ctx.Locale -}}
|
{{$inlineDiff := $section.GetComputedInlineDiffFor $line ctx.Locale -}}
|
||||||
<td class="lines-escape">
|
<td class="lines-escape">
|
||||||
|
|||||||
@ -986,6 +986,14 @@ td .commit-summary {
|
|||||||
text-align: right;
|
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 {
|
.repository .diff-file-box .code-diff tbody tr .lines-type-marker {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
min-width: 10px;
|
min-width: 10px;
|
||||||
@ -997,6 +1005,32 @@ td .commit-summary {
|
|||||||
display: inline-block;
|
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 {
|
.repository .diff-file-box .code-diff-split .tag-code .lines-code code.code-inner {
|
||||||
padding-left: 10px !important;
|
padding-left: 10px !important;
|
||||||
}
|
}
|
||||||
|
|||||||
241
web_src/js/features/repo-diff-selection.ts
Normal file
241
web_src/js/features/repo-diff-selection.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
// 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;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!applyDiffLineSelection(container, range, {updateHash: false})) return false;
|
||||||
|
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);
|
||||||
|
const targetTr = targetSpan.closest('tr');
|
||||||
|
if (targetTr) {
|
||||||
|
targetTr.scrollIntoView({behavior: 'smooth', block: 'center'});
|
||||||
|
}
|
||||||
|
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 = '';
|
||||||
|
}
|
||||||
|
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"]')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleDiffLineNumberClick(cell, e);
|
||||||
|
});
|
||||||
|
window.addEventListener('hashchange', () => {
|
||||||
|
highlightDiffSelectionFromHash();
|
||||||
|
});
|
||||||
|
highlightDiffSelectionFromHash();
|
||||||
|
}
|
||||||
@ -11,6 +11,7 @@ import {createTippy} from '../modules/tippy.ts';
|
|||||||
import {invertFileFolding} from './file-fold.ts';
|
import {invertFileFolding} from './file-fold.ts';
|
||||||
import {parseDom, sleep} from '../utils.ts';
|
import {parseDom, sleep} from '../utils.ts';
|
||||||
import {registerGlobalSelectorFunc} from '../modules/observer.ts';
|
import {registerGlobalSelectorFunc} from '../modules/observer.ts';
|
||||||
|
import {parseDiffHashRange, highlightDiffSelectionFromHash, initDiffLineSelection} from './repo-diff-selection.ts';
|
||||||
|
|
||||||
function initRepoDiffFileBox(el: HTMLElement) {
|
function initRepoDiffFileBox(el: HTMLElement) {
|
||||||
// switch between "rendered" and "source", for image and CSV files
|
// switch between "rendered" and "source", for image and CSV files
|
||||||
@ -149,12 +150,14 @@ function initDiffHeaderPopup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Will be called when the show more (files) button has been pressed
|
// Will be called when the show more (files) button has been pressed
|
||||||
function onShowMoreFiles() {
|
async function onShowMoreFiles() {
|
||||||
// TODO: replace these calls with the "observer.ts" methods
|
// TODO: replace these calls with the "observer.ts" methods
|
||||||
initRepoIssueContentHistory();
|
initRepoIssueContentHistory();
|
||||||
initViewedCheckboxListenerFor();
|
initViewedCheckboxListenerFor();
|
||||||
initImageDiff();
|
initImageDiff();
|
||||||
initDiffHeaderPopup();
|
initDiffHeaderPopup();
|
||||||
|
// Re-apply hash selection in case the target was just loaded
|
||||||
|
await highlightDiffSelectionFromHash();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadMoreFiles(btn: Element): Promise<boolean> {
|
async function loadMoreFiles(btn: Element): Promise<boolean> {
|
||||||
@ -225,20 +228,46 @@ async function onLocationHashChange() {
|
|||||||
const attrAutoScrollRunning = 'data-auto-scroll-running';
|
const attrAutoScrollRunning = 'data-auto-scroll-running';
|
||||||
if (document.body.hasAttribute(attrAutoScrollRunning)) return;
|
if (document.body.hasAttribute(attrAutoScrollRunning)) return;
|
||||||
|
|
||||||
const targetElementId = currentHash.substring(1);
|
// Check if this is a diff line selection hash (e.g., #diff-xxxL10 or #diff-xxxL10-R20)
|
||||||
while (currentHash === window.location.hash) {
|
const hashValue = currentHash.substring(1);
|
||||||
// use getElementById to avoid querySelector throws an error when the hash is invalid
|
const range = parseDiffHashRange(hashValue);
|
||||||
// eslint-disable-next-line unicorn/prefer-query-selector
|
if (range) {
|
||||||
const targetElement = document.getElementById(targetElementId);
|
// This is a line selection hash, try to highlight it first
|
||||||
if (targetElement) {
|
const success = await highlightDiffSelectionFromHash();
|
||||||
// need to change hash to re-trigger ":target" CSS selector, let's manually scroll to it
|
if (success) {
|
||||||
targetElement.scrollIntoView();
|
// Successfully highlighted and scrolled, we're done
|
||||||
document.body.setAttribute(attrAutoScrollRunning, 'true');
|
|
||||||
window.location.hash = '';
|
|
||||||
window.location.hash = currentHash;
|
|
||||||
setTimeout(() => document.body.removeAttribute(attrAutoScrollRunning), 0);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// If not successful, fall through to load more files
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetElementId = hashValue;
|
||||||
|
while (currentHash === window.location.hash) {
|
||||||
|
// For line selections, check the range-based target
|
||||||
|
let targetElement;
|
||||||
|
if (range) {
|
||||||
|
const targetId = `${range.fragment}${range.startSide}${range.startLine}`;
|
||||||
|
// eslint-disable-next-line unicorn/prefer-query-selector
|
||||||
|
targetElement = document.getElementById(targetId);
|
||||||
|
if (targetElement) {
|
||||||
|
// Try again to highlight and scroll now that the element is loaded
|
||||||
|
await highlightDiffSelectionFromHash();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// use getElementById to avoid querySelector throws an error when the hash is invalid
|
||||||
|
// eslint-disable-next-line unicorn/prefer-query-selector
|
||||||
|
targetElement = document.getElementById(targetElementId);
|
||||||
|
if (targetElement) {
|
||||||
|
// need to change hash to re-trigger ":target" CSS selector, let's manually scroll to it
|
||||||
|
targetElement.scrollIntoView();
|
||||||
|
document.body.setAttribute(attrAutoScrollRunning, 'true');
|
||||||
|
window.location.hash = '';
|
||||||
|
window.location.hash = currentHash;
|
||||||
|
setTimeout(() => document.body.removeAttribute(attrAutoScrollRunning), 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If looking for a hidden comment, try to expand the section that contains it
|
// If looking for a hidden comment, try to expand the section that contains it
|
||||||
const issueCommentPrefix = '#issuecomment-';
|
const issueCommentPrefix = '#issuecomment-';
|
||||||
@ -284,6 +313,7 @@ export function initRepoDiffView() {
|
|||||||
initDiffHeaderPopup();
|
initDiffHeaderPopup();
|
||||||
initViewedCheckboxListenerFor();
|
initViewedCheckboxListenerFor();
|
||||||
initExpandAndCollapseFilesButton();
|
initExpandAndCollapseFilesButton();
|
||||||
|
initDiffLineSelection();
|
||||||
initRepoDiffHashChangeListener();
|
initRepoDiffHashChangeListener();
|
||||||
|
|
||||||
registerGlobalSelectorFunc('#diff-file-boxes .diff-file-box', initRepoDiffFileBox);
|
registerGlobalSelectorFunc('#diff-file-boxes .diff-file-box', initRepoDiffFileBox);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user