-
-
Notifications
You must be signed in to change notification settings - Fork 6.3k
Add pull request files line selections #36014
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
| {{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> |
There was a problem hiding this comment.
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=?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| {{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> |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| function changeHash(hash: string) { | ||
| if (window.history.pushState) { | ||
| window.history.pushState(null, null, hash); | ||
| } else { | ||
| window.location.hash = hash; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| } | ||
|
|
||
| export function parseDiffHashRange(hashValue: string): DiffSelectionRange | null { | ||
| if (!hashValue.startsWith('diff-')) return null; |
There was a problem hiding this comment.
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-' ....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| } | ||
| } | ||
|
|
||
| if (!applyDiffLineSelection(container, range, {updateHash: false})) return false; |
There was a problem hiding this comment.
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(.....);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // 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); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| return {anchor, fragment, side, line}; | ||
| } | ||
|
|
||
| function applyDiffLineSelection(container: HTMLElement, range: DiffSelectionRange, options?: {updateHash?: boolean}): boolean { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unit test added at 4f65ae3
| // Expand the file using the setFileFolding utility | ||
| setFileFolding(container, foldBtn, false); | ||
| // Wait a bit for the expansion animation | ||
| await sleep(100); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| await sleep(10); | ||
| const targetTr = targetSpan.closest('tr'); | ||
| if (targetTr) { | ||
| targetTr.scrollIntoView({behavior: 'smooth', block: 'center'}); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| window.history.pushState(null, null, window.location.pathname + window.location.search); | ||
| } else { | ||
| window.location.hash = ''; | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| return max; | ||
| } | ||
|
|
||
| function waitForTransitionEnd(element: Element): Promise<void> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is 100% garbage function .....
| // avoid reentrance when we are changing the hash to scroll and trigger ":target" selection | ||
| const attrAutoScrollRunning = 'data-auto-scroll-running'; | ||
| if (document.body.hasAttribute(attrAutoScrollRunning)) return; | ||
| if (document.body.hasAttribute(diffAutoScrollAttr)) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why it is called diffAutoScrollAttr? See the comment, the attrAutoScrollRunning was designed for "issue comment scrolling"
| let targetSpan = document.querySelector<HTMLElement>(`#${CSS.escape(targetId)}`); | ||
| if (!targetSpan) { | ||
| // Flush pending DOM mutations (htmx, folding animations, etc.) before giving up | ||
| await waitNextAnimationFrame(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why it needs to wait for the animation?
|
The logic is quite complex, I would suggest to carefully review every line and make sure every line is clear. Or you can also help to answer my questions. Thank you. @lafriks @Zettat123 |
| if (e.defaultPrevented) return; | ||
| handleDiffLineNumberClick(cell, e); | ||
| }); | ||
| window.addEventListener('hashchange', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why duplicate "hashchange" listeners? Won't it conflict with window.addEventListener('hashchange', onLocationHashChange);?
| window.location.hash = ''; | ||
| window.location.hash = currentHash; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think readers can understand why it needs these two lines?
When you copy code, why not also copy the comments? Or, why not introduce a common function for such requirement?
| if (targetElement) { | ||
| // Try again to highlight and scroll now that the element is loaded | ||
| const success = await highlightDiffSelectionFromHash(); | ||
| if (success) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this loop still right?
If the selected line are in the expanded area (blob excerpt), now you just keep "loading more files", but the selected range can never be really loaded.


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