Skip to content

Conversation

@lunny
Copy link
Member

@lunny lunny commented Nov 24, 2025

This PR allows select multiple lines on the pull request files view page.

image

@lunny lunny added this to the 1.26.0 milestone Nov 24, 2025
@lunny lunny added type/enhancement An improvement of existing functionality topic/ui Change the appearance of the Gitea UI labels Nov 24, 2025
@GiteaBot GiteaBot added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Nov 24, 2025
@github-actions github-actions bot added modifies/templates This PR modifies the template files modifies/frontend labels Nov 24, 2025
@lunny lunny marked this pull request as draft November 24, 2025 01:40
@lunny lunny marked this pull request as ready for review November 24, 2025 18:41
@GiteaBot GiteaBot added lgtm/need 1 This PR needs approval from one additional maintainer to be merged. and removed lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. labels Nov 25, 2025
@GiteaBot GiteaBot added lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. and removed lgtm/need 1 This PR needs approval from one additional maintainer to be merged. labels Nov 25, 2025
{{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=?

{{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 not have a clear defination like diff-{hash}-{L|R}xxx?

Comment on lines +16 to +22
function changeHash(hash: string) {
if (window.history.pushState) {
window.history.pushState(null, null, hash);
} else {
window.location.hash = hash;
}
}
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).

}

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-' ....

}
}

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(.....);

Comment on lines +146 to +148
// 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);
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.

// 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?

initImageDiff();
initDiffHeaderPopup();
// Re-apply hash selection in case the target was just loaded
await highlightDiffSelectionFromHash();
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you should follow the TODO, but not keep adding unwanted code.

const range = parseDiffHashRange(hashValue);
if (range) {
// This is a line selection hash, try to highlight it first
const success = await highlightDiffSelectionFromHash();
Copy link
Contributor

Choose a reason for hiding this comment

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

You should merge the old logic targetElement.scrollIntoView(); into highlightDiffSelectionFromHash

Comment on lines +248 to +255
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;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

It doesn't seem right.

What if then "endLine" is still not loaded.

@wxiaoguang wxiaoguang marked this pull request as draft November 25, 2025 23:33
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.

// 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.

await sleep(10);
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?

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. modifies/frontend modifies/templates This PR modifies the template files topic/ui Change the appearance of the Gitea UI type/enhancement An improvement of existing functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants