-
-
Notifications
You must be signed in to change notification settings - Fork 6.7k
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?
Changes from all commits
1ec71e8
c144b64
373b530
89989c5
35b8f11
5580cc7
83d18d6
45d2fbc
f8567d0
41857b4
a5738ef
91fc220
ac5f96b
dbfa95b
3140aac
ff5544a
a9d887a
cd8130e
cf809d3
4f65ae3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,60 @@ | ||
| import {svg} from '../svg.ts'; | ||
|
|
||
| function parseTransitionValue(value: string): number { | ||
| let max = 0; | ||
| for (const current of value.split(',')) { | ||
| const trimmed = current.trim(); | ||
| if (!trimmed) continue; | ||
| const isMs = trimmed.endsWith('ms'); | ||
| const numericPortion = Number.parseFloat(trimmed.replace(/ms|s$/u, '')); | ||
| if (Number.isNaN(numericPortion)) continue; | ||
| const duration = numericPortion * (isMs ? 1 : 1000); | ||
| max = Math.max(max, duration); | ||
| } | ||
| return max; | ||
| } | ||
|
|
||
| function waitForTransitionEnd(element: Element): Promise<void> { | ||
| if (!(element instanceof HTMLElement)) return Promise.resolve(); | ||
| const transitionTarget = element.querySelector<HTMLElement>('.diff-file-body') ?? element; | ||
| const styles = window.getComputedStyle(transitionTarget); | ||
| const transitionDuration = parseTransitionValue(styles.transitionDuration); | ||
| const transitionDelay = parseTransitionValue(styles.transitionDelay); | ||
| const total = transitionDuration + transitionDelay; | ||
| if (total === 0) return Promise.resolve(); | ||
|
|
||
| return new Promise((resolve) => { | ||
| let resolved = false; | ||
| function cleanup() { | ||
| if (resolved) return; | ||
| resolved = true; | ||
| transitionTarget.removeEventListener('transitionend', onTransitionEnd); | ||
| resolve(); | ||
| } | ||
| function onTransitionEnd(event: TransitionEvent) { | ||
| if (event.target !== transitionTarget) return; | ||
| cleanup(); | ||
| } | ||
| transitionTarget.addEventListener('transitionend', onTransitionEnd); | ||
| window.setTimeout(cleanup, total + 50); | ||
| }); | ||
| } | ||
|
|
||
| // Hides the file if newFold is true, and shows it otherwise. The actual hiding is performed using CSS. | ||
| // | ||
| // The fold arrow is the icon displayed on the upper left of the file box, especially intended for components having the 'fold-file' class. | ||
| // The file content box is the box that should be hidden or shown, especially intended for components having the 'file-content' class. | ||
| // | ||
| export function setFileFolding(fileContentBox: Element, foldArrow: HTMLElement, newFold: boolean) { | ||
| export function setFileFolding(fileContentBox: Element, foldArrow: HTMLElement, newFold: boolean): Promise<void> { | ||
| foldArrow.innerHTML = svg(`octicon-chevron-${newFold ? 'right' : 'down'}`, 18); | ||
| fileContentBox.setAttribute('data-folded', String(newFold)); | ||
| if (newFold && fileContentBox.getBoundingClientRect().top < 0) { | ||
| fileContentBox.scrollIntoView(); | ||
| } | ||
| return waitForTransitionEnd(fileContentBox); | ||
| } | ||
|
|
||
| // Like `setFileFolding`, except that it automatically inverts the current file folding state. | ||
| export function invertFileFolding(fileContentBox:HTMLElement, foldArrow: HTMLElement) { | ||
| setFileFolding(fileContentBox, foldArrow, fileContentBox.getAttribute('data-folded') !== 'true'); | ||
| export function invertFileFolding(fileContentBox:HTMLElement, foldArrow: HTMLElement): Promise<void> { | ||
| return setFileFolding(fileContentBox, foldArrow, fileContentBox.getAttribute('data-folded') !== 'true'); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| import {applyDiffLineSelection} from './repo-diff-selection.ts'; | ||
|
|
||
| function createDiffRow(tbody: HTMLTableSectionElement, options: {id?: string, lineType?: string} = {}) { | ||
| const tr = document.createElement('tr'); | ||
| if (options.lineType) tr.setAttribute('data-line-type', options.lineType); | ||
|
|
||
| const numberCell = document.createElement('td'); | ||
| numberCell.classList.add('lines-num'); | ||
| const span = document.createElement('span'); | ||
| if (options.id) span.id = options.id; | ||
| numberCell.append(span); | ||
| tr.append(numberCell); | ||
|
|
||
| tr.append(document.createElement('td')); | ||
| tbody.append(tr); | ||
| return tr; | ||
| } | ||
|
|
||
| describe('applyDiffLineSelection', () => { | ||
| beforeEach(() => { | ||
| document.body.innerHTML = ''; | ||
| }); | ||
|
|
||
| test('selects contiguous diff rows, skips expansion rows, and clears previous selection', () => { | ||
| const fragment = 'diff-selection'; | ||
|
|
||
| const otherBox = document.createElement('div'); | ||
| const otherTable = document.createElement('table'); | ||
| otherTable.classList.add('code-diff'); | ||
| const otherTbody = document.createElement('tbody'); | ||
| const staleActiveRow = document.createElement('tr'); | ||
| staleActiveRow.classList.add('active'); | ||
| otherTbody.append(staleActiveRow); | ||
| otherTable.append(otherTbody); | ||
| otherBox.append(otherTable); | ||
|
|
||
| const container = document.createElement('div'); | ||
| container.classList.add('diff-file-box'); | ||
| const table = document.createElement('table'); | ||
| table.classList.add('code-diff'); | ||
| const tbody = document.createElement('tbody'); | ||
| table.append(tbody); | ||
| container.append(table); | ||
|
|
||
| const rows = [ | ||
| createDiffRow(tbody, {id: `${fragment}L1`}), | ||
| createDiffRow(tbody), | ||
| createDiffRow(tbody, {lineType: '4'}), | ||
| createDiffRow(tbody), | ||
| createDiffRow(tbody, {id: `${fragment}R5`}), | ||
| createDiffRow(tbody), | ||
| ]; | ||
|
|
||
| document.body.append(otherBox, container); | ||
|
|
||
| const range = {fragment, startSide: 'L' as const, startLine: 1, endSide: 'R' as const, endLine: 5}; | ||
| const applied = applyDiffLineSelection(container, range); | ||
|
|
||
| expect(applied).toBe(true); | ||
| expect(rows[0].classList.contains('active')).toBe(true); | ||
| expect(rows[1].classList.contains('active')).toBe(true); | ||
| expect(rows[2].classList.contains('active')).toBe(false); | ||
| expect(rows[3].classList.contains('active')).toBe(true); | ||
| expect(rows[4].classList.contains('active')).toBe(true); | ||
| expect(rows[5].classList.contains('active')).toBe(false); | ||
| expect(staleActiveRow.classList.contains('active')).toBe(false); | ||
| }); | ||
|
|
||
| test('returns false when either anchor is missing', () => { | ||
| const fragment = 'diff-missing'; | ||
| const container = document.createElement('div'); | ||
| container.classList.add('diff-file-box'); | ||
| const table = document.createElement('table'); | ||
| table.classList.add('code-diff'); | ||
| const tbody = document.createElement('tbody'); | ||
| table.append(tbody); | ||
| container.append(table); | ||
| document.body.append(container); | ||
|
|
||
| createDiffRow(tbody, {id: `${fragment}L1`}); | ||
|
|
||
| const applied = applyDiffLineSelection(container, {fragment, startSide: 'L' as const, startLine: 1, endSide: 'R' as const, endLine: 2}); | ||
| expect(applied).toBe(false); | ||
| expect(container.querySelectorAll('tr.active').length).toBe(0); | ||
| }); | ||
| }); | ||
|
Comment on lines
+1
to
+86
|
||
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 .....