diff --git a/swh/web/assets/src/bundles/revision/diff-utils.js b/swh/web/assets/src/bundles/revision/diff-utils.js --- a/swh/web/assets/src/bundles/revision/diff-utils.js +++ b/swh/web/assets/src/bundles/revision/diff-utils.js @@ -65,11 +65,6 @@ return ret; } -function adjustCodeBlockLeftMargin(diffElt) { - let left = $(diffElt).find('.hljs-ln-numbers-container').width(); - $(diffElt).find('.hljs-ln-code-container').css('margin-left', left + 'px'); -} - // to compute diff and process it for display export function computeDiff(diffUrl, diffId) { @@ -144,10 +139,8 @@ let diffToStr = ''; let linesOffset = 0; - let codeLineElts = $(`#${diffId} .hljs-ln-code-container`).children(); - $(`#${diffId} .hljs-ln-numbers`).each((i, lnElt) => { - let lnText = $(codeLineElts[i]).text(); + let lnText = lnElt.nextSibling.innerText; let linesInfo = linesInfoRegExp.exec(lnText); let fromLine = ''; let toLine = ''; @@ -218,55 +211,45 @@ hljs.lineNumbersBlock(block); }); - function highlightDiffLines(diffId) { - let codeLineElts = $(`#${diffId} .hljs-ln-code-container`).children(); - $(`#${diffId} .hljs-ln-numbers`).each((i, lnElt) => { - let lnTextElt = codeLineElts[i]; - let lnText = $(lnTextElt).text(); + // hljs.lineNumbersBlock is asynchronous so we have to postpone our + // next treatments by adding it at the end of the current js events queue + setTimeout(() => { + // diff highlighting for added/removed lines on top of code highlighting + $(`.${diffId} .hljs-ln-numbers`).each((i, lnElt) => { + let lnText = lnElt.nextSibling.innerText; let linesInfo = linesInfoRegExp.exec(lnText); if (linesInfo) { - $(lnElt).addClass('swh-diff-lines-info'); - $(lnTextElt).addClass('swh-diff-lines-info'); - $(lnTextElt).text(''); - $(lnTextElt).append(`${lnText}`); + $(lnElt).parent().addClass('swh-diff-lines-info'); + let linesInfoText = $(lnElt).parent().find('.hljs-ln-code .hljs-ln-line').text(); + $(lnElt).parent().find('.hljs-ln-code .hljs-ln-line').children().remove(); + $(lnElt).parent().find('.hljs-ln-code .hljs-ln-line').text(''); + $(lnElt).parent().find('.hljs-ln-code .hljs-ln-line').append(`${linesInfoText}`); } else if (lnText.length > 0 && lnText[0] === '-') { - $(lnElt).addClass('swh-diff-removed-line'); - $(lnTextElt).addClass('swh-diff-removed-line'); + $(lnElt).parent().addClass('swh-diff-removed-line'); } else if (lnText.length > 0 && lnText[0] === '+') { - $(lnElt).addClass('swh-diff-added-line'); - $(lnTextElt).addClass('swh-diff-added-line'); + $(lnElt).parent().addClass('swh-diff-added-line'); } }); - } - - // hljs.lineNumbersBlock is asynchronous so we have to postpone our - // next treatments by adding it at the end of the current js events queue - setTimeout(() => { - - // diff highlighting for added/removed lines on top of code highlighting - highlightDiffLines(diffId); - highlightDiffLines(`${diffId}-from`); - highlightDiffLines(`${diffId}-to`); // set line numbers for unified diff - $(`#${diffId} .hljs-ln-numbers .hljs-ln-n`).each((i, lnElt) => { - $(lnElt).attr( + $(`#${diffId} .hljs-ln-numbers`).each((i, lnElt) => { + $(lnElt).children().attr( 'data-line-number', formatDiffLineNumbers(fromToLines[i][0], fromToLines[i][1], maxNumberChars)); }); // set line numbers for the from side-by-side diff - $(`#${diffId}-from .hljs-ln-numbers .hljs-ln-n`).each((i, lnElt) => { - $(lnElt).attr( + $(`#${diffId}-from .hljs-ln-numbers`).each((i, lnElt) => { + $(lnElt).children().attr( 'data-line-number', formatDiffLineNumbers(fromLines[i], null, maxNumberChars)); }); // set line numbers for the to side-by-side diff - $(`#${diffId}-to .hljs-ln-numbers .hljs-ln-n`).each((i, lnElt) => { - $(lnElt).attr( + $(`#${diffId}-to .hljs-ln-numbers`).each((i, lnElt) => { + $(lnElt).children().attr( 'data-line-number', formatDiffLineNumbers(null, toLines[i], maxNumberChars)); @@ -305,8 +288,6 @@ setDiffVisible(diffId); - adjustCodeBlockLeftMargin(`#${diffId}`); - }); }); } @@ -429,8 +410,6 @@ export function showSplittedDiff(event, diffId) { $(`#${diffId}-unified-diff`).css('display', 'none'); $(`#${diffId}-splitted-diff`).css('display', 'block'); - adjustCodeBlockLeftMargin(`#${diffId}-from`); - adjustCodeBlockLeftMargin(`#${diffId}-to`); } // callback when the user clicks on the 'Compute all diffs' button diff --git a/swh/web/assets/src/thirdparty/highlightjs-line-numbers/highlightjs-line-numbers.js b/swh/web/assets/src/thirdparty/highlightjs-line-numbers/highlightjs-line-numbers.js --- a/swh/web/assets/src/thirdparty/highlightjs-line-numbers/highlightjs-line-numbers.js +++ b/swh/web/assets/src/thirdparty/highlightjs-line-numbers/highlightjs-line-numbers.js @@ -5,16 +5,12 @@ var TABLE_NAME = 'hljs-ln', LINE_NAME = 'hljs-ln-line', - NUMBERS_CONTAINER_NAME = 'hljs-ln-numbers-container', - CODE_CONTAINER_NAME = 'hljs-ln-code-container', CODE_BLOCK_NAME = 'hljs-ln-code', NUMBERS_BLOCK_NAME = 'hljs-ln-numbers', NUMBER_LINE_NAME = 'hljs-ln-n', DATA_ATTR_NAME = 'data-line-number', BREAK_LINE_REGEXP = /\r\n|\r|\n/g; - var resizeHandlerSet = false; - if (w.hljs) { w.hljs.initLineNumbersOnLoad = initLineNumbersOnLoad; w.hljs.lineNumbersBlock = lineNumbersBlock; @@ -25,19 +21,124 @@ w.console.error('highlight.js not detected!'); } + function isHljsLnCodeDescendant(domElt) { + var curElt = domElt; + while (curElt) { + if (curElt.className && curElt.className.indexOf('hljs-ln-code') !== -1) { + return true; + } + curElt = curElt.parentNode; + } + return false; + } + + function getHljsLnTable(hljsLnDomElt) { + var curElt = hljsLnDomElt; + while (curElt.nodeName !== 'TABLE') { + curElt = curElt.parentNode; + } + return curElt; + } + + // Function to workaround a copy issue with Microsoft Edge. + // Due to hljs-ln wrapping the lines of code inside a element, + // itself wrapped inside a
 element, window.getSelection().toString()
+  // does not contain any line breaks. So we need to get them back using the
+  // rendered code in the DOM as reference.
+  function edgeGetSelectedCodeLines(selection) {
+      // current selected text without line breaks
+      var selectionText = selection.toString();
+
+      // get the 
' + - '' + + '' + + '' + '', [ @@ -162,32 +225,13 @@ NUMBERS_BLOCK_NAME, NUMBER_LINE_NAME, DATA_ATTR_NAME, - i + 1 - ]); - - htmlCode += format( - '
{4}
', - [ - LINE_NAME, CODE_BLOCK_NAME, - DATA_ATTR_NAME, i + 1, lines[i].length > 0 ? lines[i] : ' ' ]); } - return format( - '
' + - '
element wrapping the first line of selected code + var tdAnchor = selection.anchorNode; + while (tdAnchor.nodeName !== 'TD') { + tdAnchor = tdAnchor.parentNode; + } + + // get the element wrapping the last line of selected code + var tdFocus = selection.focusNode; + while (tdFocus.nodeName !== 'TD') { + tdFocus = tdFocus.parentNode; + } + + // extract line numbers + var firstLineNumber = parseInt(tdAnchor.dataset.lineNumber); + var lastLineNumber = parseInt(tdFocus.dataset.lineNumber); + + // multi-lines copied case + if (firstLineNumber != lastLineNumber) { + + var firstLineText = tdAnchor.textContent; + var lastLineText = tdFocus.textContent; + + // if the selection was made backward, swap values + if (firstLineNumber > lastLineNumber) { + var tmp = firstLineNumber; + firstLineNumber = lastLineNumber; + lastLineNumber = tmp; + tmp = firstLineText; + firstLineText = lastLineText; + lastLineText = tmp; + } + + // discard not copied characters in first line + while (selectionText.indexOf(firstLineText) !== 0) { + firstLineText = firstLineText.slice(1); + } + + // discard not copied characters in last line + while (selectionText.lastIndexOf(lastLineText) === -1) { + lastLineText = lastLineText.slice(0, -1); + } + + // reconstruct and return the real copied text + var selectedText = firstLineText; + var hljsLnTable = getHljsLnTable(tdAnchor); + for (var i = firstLineNumber + 1 ; i < lastLineNumber ; ++i) { + var codeLineSel = format('.{0}[{1}="{2}"]', [CODE_BLOCK_NAME, DATA_ATTR_NAME, i]); + var codeLineElt = hljsLnTable.querySelector(codeLineSel); + selectedText += '\n' + codeLineElt.textContent; + } + selectedText += '\n' + lastLineText; + return selectedText; + // single copied line case + } else { + return selectionText; + } + } + + // ensure consistent code copy/paste behavior across all browsers + // (see https://github.com/wcoder/highlightjs-line-numbers.js/issues/51) + document.addEventListener('copy', function(e) { + // get current selection + var selection = window.getSelection(); + // override behavior when one wants to copy line of codes + if (isHljsLnCodeDescendant(selection.anchorNode)) { + var selectionText; + // workaround an issue with Microsoft Edge as copied line breaks + // are removed otherwise from the selection string + if (window.navigator.userAgent.indexOf("Edge") !== -1) { + selectionText = edgeGetSelectedCodeLines(selection); + } else { + // other browsers can directly use the selection string + selectionText = selection.toString(); + } + e.clipboardData.setData('text/plain', selectionText); + e.preventDefault(); + } + }); + function addStyles () { var css = d.createElement('style'); css.type = 'text/css'; css.innerHTML = format( - '.{0} table{float:left; border-collapse:collapse}' + - '.{0} table td{padding:0}' + - '.{1}::before{content:attr({2})}' + - '.{3}::before{content: "\\200B"}', // force display of empty lines of code + '.{0}{border-collapse:collapse}' + + '.{0} td{padding:0}' + + '.{1}:before{content:attr({2})}', [ TABLE_NAME, NUMBER_LINE_NAME, - DATA_ATTR_NAME, - CODE_BLOCK_NAME + DATA_ATTR_NAME ]); d.getElementsByTagName('head')[0].appendChild(css); } @@ -66,41 +167,11 @@ } } - function adjustLineNumbersHeights(element) { - var lnNumbers = element.querySelectorAll('.' + NUMBERS_BLOCK_NAME); - var lnCode = element.querySelectorAll('.' + CODE_BLOCK_NAME); - - for (var i = 0 ; i < lnNumbers.length; ++i) { - lnNumbers[i].style.height = lnCode[i].offsetHeight + 'px'; - } - } - function lineNumbersBlock (element, options) { if (typeof element !== 'object') return; + async(function () { element.innerHTML = lineNumbersInternal(element, options); - // adjust left margin of code div as line numbers is a float left dom element - var lineNumbersContainer = element.querySelector('.' + NUMBERS_CONTAINER_NAME); - if (lineNumbersContainer) { - var codeMargin = lineNumbersContainer.offsetWidth; - var codeContainerStyle = 'margin-left:' + codeMargin + 'px'; - var codeContainer = element.querySelector('.' + CODE_CONTAINER_NAME); - codeContainer.style.cssText = codeContainerStyle; - - // adjust each line number cell height to the one of the div containing - // the wrapped line of code and set a handler to execute this - // operation when the browser window gets resized. - adjustLineNumbersHeights(element); - if (!resizeHandlerSet) { - window.addEventListener('resize', function() { - var hljsLnElts = document.querySelectorAll('.' + TABLE_NAME); - for (var i = 0 ; i < hljsLnElts.length; ++i) { - adjustLineNumbersHeights(hljsLnElts[i]); - } - }); - resizeHandlerSet = true; - } - } }); } @@ -137,24 +208,16 @@ } if (lines.length > firstLineIndex) { - // Previous implementation was using a single table element - // to render the line numbers and the lines of code. - // But to overcome an annoying copy/paste behavior when using Firefox or Edge - // (see https://github.com/wcoder/highlightjs-line-numbers.js/issues/51) - // the following workaround is used while obtaining the exact same rendering - // as before: - // 1. render the lines number in a table with single column - // 2. render the lines of code in a div - // 3. wrap these in a div and make the table float left - // 4. adjust the left margin of the code div once inserted in the dom - var htmlLinesNumber = ''; - var htmlCode = ''; + var html = ''; for (var i = 0, l = lines.length; i < l; i++) { - htmlLinesNumber += format( - '
' + - '
' + + html += format( + '
' + + '
' + + '
' + + '{6}' + '
{2}
' + - '
{4}
' + - '', - [ - TABLE_NAME, - NUMBERS_CONTAINER_NAME, - htmlLinesNumber, - CODE_CONTAINER_NAME, - htmlCode - ]); + return format('{1}
', [ TABLE_NAME, html ]); } return inputHtml; diff --git a/swh/web/assets/src/utils/highlightjs.css b/swh/web/assets/src/utils/highlightjs.css --- a/swh/web/assets/src/utils/highlightjs.css +++ b/swh/web/assets/src/utils/highlightjs.css @@ -24,7 +24,7 @@ color: #aaa; border-right: 1px solid #ccc; vertical-align: top; - padding-right: 5px !important; + padding-right: 1px !important; } /* for block of code */