Changeset View
Changeset View
Standalone View
Standalone View
swh/web/assets/src/bundles/revision/diff-utils.js
Show All 26 Lines | |||||
// to track the total number of added lines in files diffs | // to track the total number of added lines in files diffs | ||||
let nbAdditions = 0; | let nbAdditions = 0; | ||||
// to track the total number of deleted lines in files diffs | // to track the total number of deleted lines in files diffs | ||||
let nbDeletions = 0; | let nbDeletions = 0; | ||||
// to track the already computed diffs by id | // to track the already computed diffs by id | ||||
let computedDiffs = {}; | let computedDiffs = {}; | ||||
// map a diff id to its computation url | // map a diff id to its computation url | ||||
let diffsUrls = {}; | let diffsUrls = {}; | ||||
// to keep track of diff lines to highlight | |||||
let startLines = null; | |||||
let endLines = null; | |||||
// map max line numbers characters to diff | |||||
const diffMaxNumberChars = {}; | |||||
// focused diff for highlighting | |||||
let focusedDiff = null; | |||||
// highlighting color | |||||
const lineHighlightColor = '#fdf3da'; | |||||
// might contain diff lines to highlight parsed from URL fragment | |||||
let selectedDiffLinesInfo; | |||||
// to check if a DOM element is in the viewport | // to check if a DOM element is in the viewport | ||||
function isInViewport(elt) { | function isInViewport(elt) { | ||||
let elementTop = $(elt).offset().top; | let elementTop = $(elt).offset().top; | ||||
let elementBottom = elementTop + $(elt).outerHeight(); | let elementBottom = elementTop + $(elt).outerHeight(); | ||||
let viewportTop = $(window).scrollTop(); | let viewportTop = $(window).scrollTop(); | ||||
let viewportBottom = viewportTop + $(window).height(); | let viewportBottom = viewportTop + $(window).height(); | ||||
return elementBottom > viewportTop && elementTop < viewportBottom; | return elementBottom > viewportTop && elementTop < viewportBottom; | ||||
} | } | ||||
// to format the diffs line numbers | // to format the diffs line numbers | ||||
function formatDiffLineNumbers(fromLine, toLine, maxNumberChars) { | export function formatDiffLineNumbers(diffId, fromLine, toLine) { | ||||
const maxNumberChars = diffMaxNumberChars[diffId]; | |||||
const fromLineStr = toLnStr(fromLine); | |||||
const toLineStr = toLnStr(toLine); | |||||
let ret = ''; | let ret = ''; | ||||
if (fromLine != null) { | for (let i = 0; i < (maxNumberChars - fromLineStr.length); ++i) { | ||||
for (let i = 0; i < (maxNumberChars - fromLine.length); ++i) { | |||||
ret += ' '; | ret += ' '; | ||||
} | } | ||||
ret += fromLine; | ret += fromLineStr; | ||||
} | |||||
if (fromLine != null && toLine != null) { | |||||
ret += ' '; | ret += ' '; | ||||
} | for (let i = 0; i < (maxNumberChars - toLineStr.length); ++i) { | ||||
if (toLine != null) { | |||||
for (let i = 0; i < (maxNumberChars - toLine.length); ++i) { | |||||
ret += ' '; | ret += ' '; | ||||
} | } | ||||
ret += toLine; | ret += toLineStr; | ||||
} | |||||
return ret; | return ret; | ||||
} | } | ||||
function parseDiffHunkRangeIfAny(lineText) { | function parseDiffHunkRangeIfAny(lineText) { | ||||
let baseFromLine, baseToLine; | let baseFromLine, baseToLine; | ||||
if (lineText.startsWith('@@')) { | if (lineText.startsWith('@@')) { | ||||
let linesInfoRegExp = new RegExp(/^@@ -(\d+),(\d+) \+(\d+),(\d+) @@$/gm); | let linesInfoRegExp = new RegExp(/^@@ -(\d+),(\d+) \+(\d+),(\d+) @@$/gm); | ||||
let linesInfoRegExp2 = new RegExp(/^@@ -(\d+) \+(\d+),(\d+) @@$/gm); | let linesInfoRegExp2 = new RegExp(/^@@ -(\d+) \+(\d+),(\d+) @@$/gm); | ||||
Show All 19 Lines | function parseDiffHunkRangeIfAny(lineText) { | ||||
} | } | ||||
if (baseFromLine !== undefined) { | if (baseFromLine !== undefined) { | ||||
return [baseFromLine, baseToLine]; | return [baseFromLine, baseToLine]; | ||||
} else { | } else { | ||||
return null; | return null; | ||||
} | } | ||||
} | } | ||||
function toLnInt(lnStr) { | |||||
return lnStr ? parseInt(lnStr) : 0; | |||||
}; | |||||
function toLnStr(lnInt) { | |||||
return lnInt ? lnInt.toString() : ''; | |||||
}; | |||||
// parse diff line numbers to an int array [from, to] | |||||
export function parseDiffLineNumbers(lineNumbersStr, from, to) { | |||||
let lines; | |||||
if (!from && !to) { | |||||
lines = lineNumbersStr.replace(/[ ]+/g, ' ').split(' '); | |||||
if (lines.length > 2) { | |||||
lines.shift(); | |||||
} | |||||
lines = lines.map(x => toLnInt(x)); | |||||
} else { | |||||
let lineNumber = toLnInt(lineNumbersStr.trim()); | |||||
if (from) { | |||||
lines = [lineNumber, 0]; | |||||
} else if (to) { | |||||
lines = [0, lineNumber]; | |||||
} | |||||
} | |||||
return lines; | |||||
} | |||||
// serialize selected line numbers range to string for URL fragment | |||||
export function selectedDiffLinesToFragment(startLines, endLines, unified) { | |||||
let selectedLinesFragment = ''; | |||||
selectedLinesFragment += `F${startLines[0] || 0}`; | |||||
selectedLinesFragment += `T${startLines[1] || 0}`; | |||||
selectedLinesFragment += `-F${endLines[0] || 0}`; | |||||
selectedLinesFragment += `T${endLines[1] || 0}`; | |||||
if (unified) { | |||||
selectedLinesFragment += '-unified'; | |||||
} else { | |||||
selectedLinesFragment += '-split'; | |||||
} | |||||
return selectedLinesFragment; | |||||
} | |||||
// parse selected lines from URL fragment | |||||
export function fragmentToSelectedDiffLines(fragment) { | |||||
const RE_LINES = /F([0-9]+)T([0-9]+)-F([0-9]+)T([0-9]+)-([a-z]+)/; | |||||
const matchObj = RE_LINES.exec(fragment); | |||||
if (matchObj.length === 6) { | |||||
return { | |||||
startLines: [parseInt(matchObj[1]), parseInt(matchObj[2])], | |||||
endLines: [parseInt(matchObj[3]), parseInt(matchObj[4])], | |||||
unified: matchObj[5] === 'unified' | |||||
}; | |||||
} else { | |||||
return null; | |||||
} | |||||
} | |||||
// function to highlight a single diff line | |||||
function highlightDiffLine(diffId, i) { | |||||
let line = $(`#${diffId} .hljs-ln-line[data-line-number="${i}"]`); | |||||
let lineNumbers = $(`#${diffId} .hljs-ln-numbers[data-line-number="${i}"]`); | |||||
lineNumbers.css('color', 'black'); | |||||
lineNumbers.css('font-weight', 'bold'); | |||||
line.css('background-color', lineHighlightColor); | |||||
line.css('mix-blend-mode', 'multiply'); | |||||
return line; | |||||
} | |||||
// function to reset highlighting | |||||
function resetHighlightedDiffLines(resetVars = true) { | |||||
if (resetVars) { | |||||
focusedDiff = null; | |||||
startLines = null; | |||||
endLines = null; | |||||
} | |||||
$('.hljs-ln-line[data-line-number]').css('background-color', 'initial'); | |||||
$('.hljs-ln-line[data-line-number]').css('mix-blend-mode', 'initial'); | |||||
$('.hljs-ln-numbers[data-line-number]').css('color', '#aaa'); | |||||
$('.hljs-ln-numbers[data-line-number]').css('font-weight', 'initial'); | |||||
} | |||||
// highlight lines in a diff, return first highlighted line numbers element | |||||
function highlightDiffLines(diffId, startLines, endLines, unified) { | |||||
let firstHighlightedLine; | |||||
// unified diff case | |||||
if (unified) { | |||||
let start = formatDiffLineNumbers(diffId, startLines[0], startLines[1]); | |||||
let end = formatDiffLineNumbers(diffId, endLines[0], endLines[1]); | |||||
const startLine = $(`#${diffId} .hljs-ln-line[data-line-number="${start}"]`); | |||||
const endLine = $(`#${diffId} .hljs-ln-line[data-line-number="${end}"]`); | |||||
if ($(endLine).position().top < $(startLine).position().top) { | |||||
[start, end] = [end, start]; | |||||
firstHighlightedLine = endLine; | |||||
} else { | |||||
firstHighlightedLine = startLine; | |||||
} | |||||
const lineTd = highlightDiffLine(diffId, start); | |||||
let tr = $(lineTd).closest('tr'); | |||||
let lineNumbers = $(tr).children('.hljs-ln-line').data('line-number').toString(); | |||||
while (lineNumbers !== end) { | |||||
if (lineNumbers.trim()) { | |||||
highlightDiffLine(diffId, lineNumbers); | |||||
} | |||||
tr = $(tr).next(); | |||||
lineNumbers = $(tr).children('.hljs-ln-line').data('line-number').toString(); | |||||
} | |||||
highlightDiffLine(diffId, end); | |||||
// split diff case | |||||
} else { | |||||
// highlight only from part of the diff | |||||
if (startLines[0] && endLines[0]) { | |||||
const start = Math.min(startLines[0], endLines[0]); | |||||
const end = Math.max(startLines[0], endLines[0]); | |||||
for (let i = start; i <= end; ++i) { | |||||
highlightDiffLine(`${diffId}-from`, i); | |||||
} | |||||
firstHighlightedLine = $(`#${diffId}-from .hljs-ln-line[data-line-number="${start}"]`); | |||||
// highlight only to part of the diff | |||||
} else if (startLines[1] && endLines[1]) { | |||||
const start = Math.min(startLines[1], endLines[1]); | |||||
const end = Math.max(startLines[1], endLines[1]); | |||||
for (let i = start; i <= end; ++i) { | |||||
highlightDiffLine(`${diffId}-to`, i); | |||||
} | |||||
firstHighlightedLine = $(`#${diffId}-to .hljs-ln-line[data-line-number="${start}"]`); | |||||
// highlight both part of the diff | |||||
} else { | |||||
let left, right; | |||||
if (startLines[0] && endLines[1]) { | |||||
left = startLines[0]; | |||||
right = endLines[1]; | |||||
} else { | |||||
left = endLines[0]; | |||||
right = startLines[1]; | |||||
} | |||||
const leftLine = $(`#${diffId}-from .hljs-ln-line[data-line-number="${left}"]`); | |||||
const rightLine = $(`#${diffId}-to .hljs-ln-line[data-line-number="${right}"]`); | |||||
const leftLineAbove = $(leftLine).position().top < $(rightLine).position().top; | |||||
if (leftLineAbove) { | |||||
firstHighlightedLine = leftLine; | |||||
} else { | |||||
firstHighlightedLine = rightLine; | |||||
} | |||||
let fromTr = $(`#${diffId}-from tr`).first(); | |||||
let fromLn = $(fromTr).children('.hljs-ln-line').data('line-number'); | |||||
let toTr = $(`#${diffId}-to tr`).first(); | |||||
let toLn = $(toTr).children('.hljs-ln-line').data('line-number'); | |||||
let canHighlight = false; | |||||
while (true) { | |||||
if (leftLineAbove && fromLn === left) { | |||||
canHighlight = true; | |||||
} else if (!leftLineAbove && toLn === right) { | |||||
canHighlight = true; | |||||
} | |||||
if (canHighlight && fromLn) { | |||||
highlightDiffLine(`${diffId}-from`, fromLn); | |||||
} | |||||
if (canHighlight && toLn) { | |||||
highlightDiffLine(`${diffId}-to`, toLn); | |||||
} | |||||
if ((leftLineAbove && toLn === right) || (!leftLineAbove && fromLn === left)) { | |||||
break; | |||||
} | |||||
fromTr = $(fromTr).next(); | |||||
fromLn = $(fromTr).children('.hljs-ln-line').data('line-number'); | |||||
toTr = $(toTr).next(); | |||||
toLn = $(toTr).children('.hljs-ln-line').data('line-number'); | |||||
} | |||||
} | |||||
} | |||||
let selectedLinesFragment = selectedDiffLinesToFragment(startLines, endLines, unified); | |||||
window.location.hash = `diff_${diffId}+${selectedLinesFragment}`; | |||||
return firstHighlightedLine; | |||||
} | |||||
// callback to switch from side-by-side diff to unified one | |||||
export function showUnifiedDiff(diffId) { | |||||
$(`#${diffId}-split-diff`).css('display', 'none'); | |||||
$(`#${diffId}-unified-diff`).css('display', 'block'); | |||||
} | |||||
// callback to switch from unified diff to side-by-side one | |||||
export function showSplitDiff(diffId) { | |||||
$(`#${diffId}-unified-diff`).css('display', 'none'); | |||||
$(`#${diffId}-split-diff`).css('display', 'block'); | |||||
} | |||||
// to compute diff and process it for display | // to compute diff and process it for display | ||||
export function computeDiff(diffUrl, diffId) { | export function computeDiff(diffUrl, diffId) { | ||||
// force diff computation ? | // force diff computation ? | ||||
let force = diffUrl.indexOf('force=true') !== -1; | let force = diffUrl.indexOf('force=true') !== -1; | ||||
// it no forced computation and diff already computed, do nothing | // it no forced computation and diff already computed, do nothing | ||||
if (!force && computedDiffs.hasOwnProperty(diffId)) { | if (!force && computedDiffs.hasOwnProperty(diffId)) { | ||||
return; | return; | ||||
} | } | ||||
function setLineNumbers(lnElt, lineNumbers) { | |||||
$(lnElt).attr('data-line-number', lineNumbers || ''); | |||||
$(lnElt).children().attr('data-line-number', lineNumbers || ''); | |||||
$(lnElt).siblings().attr('data-line-number', lineNumbers || ''); | |||||
} | |||||
// mark diff computation as already requested | // mark diff computation as already requested | ||||
computedDiffs[diffId] = true; | computedDiffs[diffId] = true; | ||||
$(`#${diffId}-loading`).css('visibility', 'visible'); | $(`#${diffId}-loading`).css('visibility', 'visible'); | ||||
// set spinner visible while requesting diff | // set spinner visible while requesting diff | ||||
$(`#${diffId}-loading`).css('display', 'block'); | $(`#${diffId}-loading`).css('display', 'block'); | ||||
$(`#${diffId}-highlightjs`).css('display', 'none'); | $(`#${diffId}-highlightjs`).css('display', 'none'); | ||||
▲ Show 20 Lines • Show All 104 Lines • ▼ Show 20 Lines | .then(data => { | ||||
if (!baseToLine) { | if (!baseToLine) { | ||||
toLine = ''; | toLine = ''; | ||||
} | } | ||||
fromToLines[i] = [fromLine, toLine]; | fromToLines[i] = [fromLine, toLine]; | ||||
maxNumberChars = Math.max(maxNumberChars, fromLine.length); | maxNumberChars = Math.max(maxNumberChars, fromLine.length); | ||||
maxNumberChars = Math.max(maxNumberChars, toLine.length); | maxNumberChars = Math.max(maxNumberChars, toLine.length); | ||||
}); | }); | ||||
diffMaxNumberChars[diffId] = maxNumberChars; | |||||
// set side-by-side diffs text | // set side-by-side diffs text | ||||
$(`#${diffId}-from`).text(diffFromStr); | $(`#${diffId}-from`).text(diffFromStr); | ||||
$(`#${diffId}-to`).text(diffToStr); | $(`#${diffId}-to`).text(diffToStr); | ||||
// code highlighting for side-by-side diffs | // code highlighting for side-by-side diffs | ||||
$(`#${diffId}-from, #${diffId}-to`).each((i, block) => { | $(`#${diffId}-from, #${diffId}-to`).each((i, block) => { | ||||
hljs.highlightBlock(block); | hljs.highlightBlock(block); | ||||
hljs.lineNumbersBlockSync(block); | hljs.lineNumbersBlockSync(block); | ||||
Show All 12 Lines | .then(data => { | ||||
$(lnElt).parent().addClass('swh-diff-removed-line'); | $(lnElt).parent().addClass('swh-diff-removed-line'); | ||||
} else if (lnText.length > 0 && lnText[0] === '+') { | } else if (lnText.length > 0 && lnText[0] === '+') { | ||||
$(lnElt).parent().addClass('swh-diff-added-line'); | $(lnElt).parent().addClass('swh-diff-added-line'); | ||||
} | } | ||||
}); | }); | ||||
// set line numbers for unified diff | // set line numbers for unified diff | ||||
$(`#${diffId} .hljs-ln-numbers`).each((i, lnElt) => { | $(`#${diffId} .hljs-ln-numbers`).each((i, lnElt) => { | ||||
$(lnElt).children().attr( | const lineNumbers = formatDiffLineNumbers(diffId, fromToLines[i][0], fromToLines[i][1]); | ||||
'data-line-number', | setLineNumbers(lnElt, lineNumbers); | ||||
formatDiffLineNumbers(fromToLines[i][0], fromToLines[i][1], | |||||
maxNumberChars)); | |||||
}); | }); | ||||
// set line numbers for the from side-by-side diff | // set line numbers for the from side-by-side diff | ||||
$(`#${diffId}-from .hljs-ln-numbers`).each((i, lnElt) => { | $(`#${diffId}-from .hljs-ln-numbers`).each((i, lnElt) => { | ||||
$(lnElt).children().attr( | setLineNumbers(lnElt, fromLines[i]); | ||||
'data-line-number', | |||||
formatDiffLineNumbers(fromLines[i], null, | |||||
maxNumberChars)); | |||||
}); | }); | ||||
// set line numbers for the to side-by-side diff | // set line numbers for the to side-by-side diff | ||||
$(`#${diffId}-to .hljs-ln-numbers`).each((i, lnElt) => { | $(`#${diffId}-to .hljs-ln-numbers`).each((i, lnElt) => { | ||||
$(lnElt).children().attr( | setLineNumbers(lnElt, toLines[i]); | ||||
'data-line-number', | |||||
formatDiffLineNumbers(null, toLines[i], | |||||
maxNumberChars)); | |||||
}); | }); | ||||
// last processing: | // last processing: | ||||
// - remove the '+' and '-' at the beginning of the diff lines | // - remove the '+' and '-' at the beginning of the diff lines | ||||
// from code highlighting | // from code highlighting | ||||
// - add the "no new line at end of file marker" if needed | // - add the "no new line at end of file marker" if needed | ||||
$(`.${diffId} .hljs-ln-code`).each((i, lnElt) => { | $(`.${diffId} .hljs-ln-code`).each((i, lnElt) => { | ||||
if (lnElt.firstChild) { | if (lnElt.firstChild) { | ||||
Show All 14 Lines | .then(data => { | ||||
$(lnElt).append($(noNewLineMarker)); | $(lnElt).append($(noNewLineMarker)); | ||||
} | } | ||||
}); | }); | ||||
} | } | ||||
}); | }); | ||||
// hide the diff mode switch button in case of not generated diffs | // hide the diff mode switch button in case of not generated diffs | ||||
if (data.diff_str.indexOf('Diffs are not generated for non textual content') !== 0) { | if (data.diff_str.indexOf('Diffs are not generated for non textual content') !== 0) { | ||||
$(`#panel_${diffId} .diff-styles`).css('visibility', 'visible'); | $(`#diff_${diffId} .diff-styles`).css('visibility', 'visible'); | ||||
} | } | ||||
setDiffVisible(diffId); | setDiffVisible(diffId); | ||||
// highlight diff lines if provided in URL fragment | |||||
if (selectedDiffLinesInfo && | |||||
selectedDiffLinesInfo.diffPanelId.indexOf(diffId) !== -1) { | |||||
if (!selectedDiffLinesInfo.unified) { | |||||
showSplitDiff(diffId); | |||||
} | |||||
const firstHighlightedLine = highlightDiffLines( | |||||
diffId, selectedDiffLinesInfo.startLines, | |||||
selectedDiffLinesInfo.endLines, selectedDiffLinesInfo.unified); | |||||
$('html, body').animate( | |||||
{ | |||||
scrollTop: firstHighlightedLine.offset().top - 50 | |||||
}, | |||||
{ | |||||
duration: 500 | |||||
} | |||||
); | |||||
} | |||||
} | } | ||||
}); | }); | ||||
} | } | ||||
function setDiffVisible(diffId) { | function setDiffVisible(diffId) { | ||||
// set the unified diff visible by default | // set the unified diff visible by default | ||||
$(`#${diffId}-loading`).css('display', 'none'); | $(`#${diffId}-loading`).css('display', 'none'); | ||||
$(`#${diffId}-highlightjs`).css('display', 'block'); | $(`#${diffId}-highlightjs`).css('display', 'block'); | ||||
// update displayed counters | // update displayed counters | ||||
$('#swh-revision-lines-added').text(`${nbAdditions} additions`); | $('#swh-revision-lines-added').text(`${nbAdditions} additions`); | ||||
$('#swh-revision-lines-deleted').text(`${nbDeletions} deletions`); | $('#swh-revision-lines-deleted').text(`${nbDeletions} deletions`); | ||||
$('#swh-nb-diffs-computed').text(nbDiffsComputed); | $('#swh-nb-diffs-computed').text(nbDiffsComputed); | ||||
// refresh the waypoints triggering diffs computation as | // refresh the waypoints triggering diffs computation as | ||||
// the DOM layout has been updated | // the DOM layout has been updated | ||||
Waypoint.refreshAll(); | Waypoint.refreshAll(); | ||||
} | } | ||||
// to compute all visible diffs in the viewport | // to compute all visible diffs in the viewport | ||||
function computeVisibleDiffs() { | function computeVisibleDiffs() { | ||||
$('.swh-file-diff-panel').each((i, elt) => { | $('.swh-file-diff-panel').each((i, elt) => { | ||||
if (isInViewport(elt)) { | if (isInViewport(elt)) { | ||||
let diffId = elt.id.replace('panel_', ''); | let diffId = elt.id.replace('diff_', ''); | ||||
computeDiff(diffsUrls[diffId], diffId); | computeDiff(diffsUrls[diffId], diffId); | ||||
} | } | ||||
}); | }); | ||||
} | } | ||||
function genDiffPanel(diffData) { | function genDiffPanel(diffData) { | ||||
let diffPanelTitle = diffData.path; | let diffPanelTitle = diffData.path; | ||||
if (diffData.type === 'rename') { | if (diffData.type === 'rename') { | ||||
diffPanelTitle = `${diffData.from_path} → ${diffData.to_path}`; | diffPanelTitle = `${diffData.from_path} → ${diffData.to_path}`; | ||||
} | } | ||||
return diffPanelTemplate({ | return diffPanelTemplate({ | ||||
diffData: diffData, | diffData: diffData, | ||||
diffPanelTitle: diffPanelTitle, | diffPanelTitle: diffPanelTitle, | ||||
swhSpinnerSrc: swhSpinnerSrc | swhSpinnerSrc: swhSpinnerSrc | ||||
}); | }); | ||||
} | } | ||||
// setup waypoints to request diffs computation on the fly while scrolling | // setup waypoints to request diffs computation on the fly while scrolling | ||||
function setupWaypoints() { | function setupWaypoints() { | ||||
for (let i = 0; i < changes.length; ++i) { | for (let i = 0; i < changes.length; ++i) { | ||||
let diffData = changes[i]; | let diffData = changes[i]; | ||||
// create a waypoint that will trigger diff computation when | // create a waypoint that will trigger diff computation when | ||||
// the top of the diff panel hits the bottom of the viewport | // the top of the diff panel hits the bottom of the viewport | ||||
$(`#panel_${diffData.id}`).waypoint({ | $(`#diff_${diffData.id}`).waypoint({ | ||||
handler: function() { | handler: function() { | ||||
if (isInViewport(this.element)) { | if (isInViewport(this.element)) { | ||||
let diffId = this.element.id.replace('panel_', ''); | let diffId = this.element.id.replace('diff_', ''); | ||||
computeDiff(diffsUrls[diffId], diffId); | computeDiff(diffsUrls[diffId], diffId); | ||||
this.destroy(); | this.destroy(); | ||||
} | } | ||||
}, | }, | ||||
offset: '100%' | offset: '100%' | ||||
}); | }); | ||||
// create a waypoint that will trigger diff computation when | // create a waypoint that will trigger diff computation when | ||||
// the bottom of the diff panel hits the top of the viewport | // the bottom of the diff panel hits the top of the viewport | ||||
$(`#panel_${diffData.id}`).waypoint({ | $(`#diff_${diffData.id}`).waypoint({ | ||||
handler: function() { | handler: function() { | ||||
if (isInViewport(this.element)) { | if (isInViewport(this.element)) { | ||||
let diffId = this.element.id.replace('panel_', ''); | let diffId = this.element.id.replace('diff_', ''); | ||||
computeDiff(diffsUrls[diffId], diffId); | computeDiff(diffsUrls[diffId], diffId); | ||||
this.destroy(); | this.destroy(); | ||||
} | } | ||||
}, | }, | ||||
offset: function() { | offset: function() { | ||||
return -$(this.element).height(); | return -$(this.element).height(); | ||||
} | } | ||||
}); | }); | ||||
} | } | ||||
Waypoint.refreshAll(); | Waypoint.refreshAll(); | ||||
} | } | ||||
// callback to switch from side-by-side diff to unified one | function scrollToDiffPanel(diffPanelId, setHash = true) { | ||||
export function showUnifiedDiff(diffId) { | // disable waypoints while scrolling as we do not want to | ||||
$(`#${diffId}-splitted-diff`).css('display', 'none'); | // launch computation of diffs the user is not interested in | ||||
$(`#${diffId}-unified-diff`).css('display', 'block'); | // (file changes list can be large) | ||||
} | Waypoint.disableAll(); | ||||
// callback to switch from unified diff to side-by-side one | $('html, body').animate( | ||||
export function showSplittedDiff(diffId) { | { | ||||
$(`#${diffId}-unified-diff`).css('display', 'none'); | scrollTop: $(diffPanelId).offset().top | ||||
$(`#${diffId}-splitted-diff`).css('display', 'block'); | }, | ||||
{ | |||||
duration: 500, | |||||
complete: () => { | |||||
if (setHash) { | |||||
window.location.hash = diffPanelId; | |||||
} | |||||
// enable waypoints back after scrolling | |||||
Waypoint.enableAll(); | |||||
// compute diffs visible in the viewport | |||||
computeVisibleDiffs(); | |||||
} | |||||
}); | |||||
} | } | ||||
// callback when the user clicks on the 'Compute all diffs' button | // callback when the user clicks on the 'Compute all diffs' button | ||||
export function computeAllDiffs(event) { | export function computeAllDiffs(event) { | ||||
$(event.currentTarget).addClass('active'); | $(event.currentTarget).addClass('active'); | ||||
for (let diffId in diffsUrls) { | for (let diffId in diffsUrls) { | ||||
if (diffsUrls.hasOwnProperty(diffId)) { | if (diffsUrls.hasOwnProperty(diffId)) { | ||||
computeDiff(diffsUrls[diffId], diffId); | computeDiff(diffsUrls[diffId], diffId); | ||||
▲ Show 20 Lines • Show All 45 Lines • ▼ Show 20 Lines | if (e.currentTarget.text.trim() === 'Changes') { | ||||
for (let i = 0; i < changes.length; ++i) { | for (let i = 0; i < changes.length; ++i) { | ||||
let diffData = changes[i]; | let diffData = changes[i]; | ||||
diffsUrls[diffData.id] = diffData.diff_url; | diffsUrls[diffData.id] = diffData.diff_url; | ||||
$('#swh-revision-diffs').append(genDiffPanel(diffData)); | $('#swh-revision-diffs').append(genDiffPanel(diffData)); | ||||
} | } | ||||
setupWaypoints(); | setupWaypoints(); | ||||
computeVisibleDiffs(); | computeVisibleDiffs(); | ||||
if (selectedDiffLinesInfo) { | |||||
scrollToDiffPanel(selectedDiffLinesInfo.diffPanelId, false); | |||||
} | |||||
}); | }); | ||||
} else if (e.currentTarget.text.trim() === 'Files') { | } else if (e.currentTarget.text.trim() === 'Files') { | ||||
$('#readme-panel').css('display', 'block'); | $('#readme-panel').css('display', 'block'); | ||||
} | } | ||||
}); | }); | ||||
$(document).ready(() => { | $(document).ready(() => { | ||||
if (revisionMessageBody.length > 0) { | if (revisionMessageBody.length > 0) { | ||||
$('#swh-revision-message').addClass('in'); | $('#swh-revision-message').addClass('in'); | ||||
} else { | } else { | ||||
$('#swh-collapse-revision-message').attr('data-toggle', ''); | $('#swh-collapse-revision-message').attr('data-toggle', ''); | ||||
} | } | ||||
let $root = $('html, body'); | |||||
// callback when the user requests to scroll on a specific diff or back to top | // callback when the user requests to scroll on a specific diff or back to top | ||||
$('#swh-revision-changes-list a[href^="#"], #back-to-top a[href^="#"]').click(e => { | $('#swh-revision-changes-list a[href^="#"], #back-to-top a[href^="#"]').click(e => { | ||||
let href = $.attr(e.currentTarget, 'href'); | let href = $.attr(e.currentTarget, 'href'); | ||||
// disable waypoints while scrolling as we do not want to | scrollToDiffPanel(href); | ||||
// launch computation of diffs the user is not interested in | return false; | ||||
// (file changes list can be large) | }); | ||||
Waypoint.disableAll(); | |||||
$root.animate( | // click callback for highlighting diff lines | ||||
{ | $('body').click(evt => { | ||||
scrollTop: $(href).offset().top | if (evt.target.classList.contains('hljs-ln-n')) { | ||||
}, | |||||
{ | const diffId = $(evt.target).closest('code').prop('id'); | ||||
duration: 500, | |||||
complete: () => { | const from = diffId.indexOf('-from') !== -1; | ||||
window.location.hash = href; | const to = diffId.indexOf('-to') !== -1; | ||||
// enable waypoints back after scrolling | |||||
Waypoint.enableAll(); | const lineNumbers = $(evt.target).data('line-number').toString(); | ||||
// compute diffs visible in the viewport | |||||
computeVisibleDiffs(); | const currentDiff = diffId.replace('-from', '').replace('-to', ''); | ||||
if (!evt.shiftKey || currentDiff !== focusedDiff || !lineNumbers.trim()) { | |||||
resetHighlightedDiffLines(); | |||||
focusedDiff = currentDiff; | |||||
} | |||||
if (currentDiff === focusedDiff && lineNumbers.trim()) { | |||||
if (!evt.shiftKey) { | |||||
startLines = parseDiffLineNumbers(lineNumbers, from, to); | |||||
highlightDiffLines(currentDiff, startLines, startLines, !from && !to); | |||||
} else if (startLines) { | |||||
resetHighlightedDiffLines(false); | |||||
endLines = parseDiffLineNumbers(lineNumbers, from, to); | |||||
highlightDiffLines(currentDiff, startLines, endLines, !from && !to); | |||||
} | |||||
} | } | ||||
}); | |||||
return false; | } else { | ||||
resetHighlightedDiffLines(); | |||||
} | |||||
}); | }); | ||||
// if an URL fragment for highlighting a diff is present | |||||
// parse highlighting info and initiate diff loading | |||||
const fragment = window.location.hash; | |||||
if (fragment) { | |||||
const split = fragment.split('+'); | |||||
if (split.length === 2) { | |||||
selectedDiffLinesInfo = fragmentToSelectedDiffLines(split[1]); | |||||
if (selectedDiffLinesInfo) { | |||||
selectedDiffLinesInfo.diffPanelId = split[0]; | |||||
$('.nav-tabs a[href="#swh-revision-changes"]').tab('show'); | |||||
} | |||||
} | |||||
} | |||||
}); | }); | ||||
} | } |