From a3c7eeb0911490ef47e010a6fc30a361ae09be72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Ho=CC=88=C3=9Fl?= Date: Sat, 18 Mar 2017 15:15:03 +0100 Subject: [PATCH] Add an explicit line highlighting function for the diff --- openslides/motions/static/js/motions/base.js | 10 +- .../static/js/motions/linenumbering.js | 104 ++++++++++++++++++ .../motions/linenumbering.service.test.js | 38 +++++++ 3 files changed, 150 insertions(+), 2 deletions(-) diff --git a/openslides/motions/static/js/motions/base.js b/openslides/motions/static/js/motions/base.js index f0e1abaae..1bfcd0581 100644 --- a/openslides/motions/static/js/motions/base.js +++ b/openslides/motions/static/js/motions/base.js @@ -331,7 +331,7 @@ angular.module('OpenSlidesApp.motions', [ text = ''; for (var i = 0; i < changes.length; i++) { text += this.getTextBetweenChangeRecommendations(versionId, (i === 0 ? null : changes[i - 1]), changes[i], highlight); - text += changes[i].getDiff(this, versionId); + text += changes[i].getDiff(this, versionId, highlight); } text += this.getTextRemainderAfterLastChangeRecommendation(versionId, changes); break; @@ -740,7 +740,13 @@ angular.module('OpenSlidesApp.motions', [ oldText = data.outerContextStart + data.innerContextStart + data.html + data.innerContextEnd + data.outerContextEnd; - return diffService.diff(oldText, this.text, lineLength, this.line_from); + var diff = diffService.diff(oldText, this.text, lineLength, this.line_from); + + if (highlight > 0) { + diff = lineNumberingService.highlightLine(diff, highlight); + } + + return diff; }, getType: function(original_full_html) { return this.type; diff --git a/openslides/motions/static/js/motions/linenumbering.js b/openslides/motions/static/js/motions/linenumbering.js index 7c2aa4854..fdb09dbf3 100644 --- a/openslides/motions/static/js/motions/linenumbering.js +++ b/openslides/motions/static/js/motions/linenumbering.js @@ -71,6 +71,32 @@ angular.module('OpenSlidesApp.motions.lineNumbering', []) return isLineNumber; }; + this._getLineNumberNode = function(fragment, lineNumber) { + return fragment.querySelector('.os-line-number.line-number-' + lineNumber); + }; + + this._htmlToFragment = function(html) { + var fragment = document.createDocumentFragment(), + div = document.createElement('DIV'); + div.innerHTML = html; + while (div.childElementCount) { + var child = div.childNodes[0]; + div.removeChild(child); + fragment.appendChild(child); + } + return fragment; + }; + + this._fragmentToHtml = function(fragment) { + var div = document.createElement('DIV'); + while (fragment.firstChild) { + var child = fragment.firstChild; + fragment.removeChild(child); + div.appendChild(child); + } + return div.innerHTML; + }; + this._createLineBreak = function () { var br = document.createElement('br'); br.setAttribute('class', 'os-line-break'); @@ -413,6 +439,15 @@ angular.module('OpenSlidesApp.motions.lineNumbering', []) return this._insertLineNumbersToNode(root, lineLength, highlight); }; + /** + * + * @param {string} html + * @param {number} lineLength + * @param {string} highlight - optional + * @param {function} callback + * @param {number} firstLine + * @returns {string} + */ this.insertLineNumbers = function (html, lineLength, highlight, callback, firstLine) { var newHtml, newRoot; @@ -438,12 +473,81 @@ angular.module('OpenSlidesApp.motions.lineNumbering', []) return newHtml; }; + /** + * @param {string} html + * @returns {string} + */ this.stripLineNumbers = function (html) { var root = document.createElement('div'); root.innerHTML = html; this._stripLineNumbers(root); return root.innerHTML; }; + + /** + * Traverses up the DOM tree until it finds a node with a nextSibling, then returns that sibling + * + * @param node + * @private + */ + this._findNextAuntNode = function(node) { + if (node.nextSibling) { + return node.nextSibling; + } else if (node.parentNode) { + return this._findNextAuntNode(node.parentNode); + } else { + return null; + } + }; + + this._highlightUntilNextLine = function(lineNumberNode) { + var currentNode = lineNumberNode, + foundNextLineNumber = false; + + do { + var wasHighlighted = false; + if (currentNode.nodeType === TEXT_NODE) { + var node = document.createElement('span'); + node.setAttribute('class', 'highlight'); + node.innerHTML = currentNode.nodeValue; + currentNode.parentNode.insertBefore(node, currentNode); + currentNode.parentNode.removeChild(currentNode); + currentNode = node; + wasHighlighted = true; + } else { + wasHighlighted = false; + } + + if (currentNode.childNodes.length > 0 && !this._isOsLineNumberNode(currentNode) && !wasHighlighted) { + currentNode = currentNode.childNodes[0]; + } else if (currentNode.nextSibling) { + currentNode = currentNode.nextSibling; + } else { + currentNode = this._findNextAuntNode(currentNode); + } + + if (this._isOsLineNumberNode(currentNode)) { + foundNextLineNumber = true; + } + } while (!foundNextLineNumber && currentNode !== null); + }; + + /** + * @param {string} html + * @param {number} lineNumber + * @return {string} + */ + this.highlightLine = function (html, lineNumber) { + var fragment = this._htmlToFragment(html), + lineNumberNode = this._getLineNumberNode(fragment, lineNumber); + + if (lineNumberNode) { + this._highlightUntilNextLine(lineNumberNode); + html = this._fragmentToHtml(fragment); + } + + return html; + }; } ]); diff --git a/tests/karma/motions/linenumbering.service.test.js b/tests/karma/motions/linenumbering.service.test.js index 6ea7e18b8..2b840a996 100644 --- a/tests/karma/motions/linenumbering.service.test.js +++ b/tests/karma/motions/linenumbering.service.test.js @@ -286,4 +286,42 @@ describe('linenumbering', function () { expect(outHtml).toBe(""); }); }); + + describe('line highlighting', function() { + it('highlights a simple line', function () { + var inHtml = lineNumberingService.insertLineNumbers("Lorem ipsum dolorsit amet", 5); + var highlighted = lineNumberingService.highlightLine(inHtml, 2); + expect(highlighted).toBe(noMarkup(1) + 'Lorem ' + brMarkup(2) + 'ipsum ' + brMarkup(3) + 'dolor' + brMarkup(4) + 'sit ' + brMarkup(5) + 'amet'); + }); + + it('highlights a simple line with formattings', function () { + var inHtml = lineNumberingService.insertLineNumbers("Lorem ipsum dolorsit amet Lorem ipsum dolorsit amet Lorem ipsum dolorsit amet", 20); + expect(inHtml).toBe(noMarkup(1) + 'Lorem ipsum dolorsit ' + + brMarkup(2) + 'amet Lorem ipsum ' + + brMarkup(3) + 'dolorsit amet Lorem ' + brMarkup(4) + 'ipsum dolorsit amet'); + + var highlighted = lineNumberingService.highlightLine(inHtml, 2); + expect(highlighted).toBe(noMarkup(1) + 'Lorem ipsum dolorsit ' + + brMarkup(2) + 'amet Lorem ipsum ' + + brMarkup(3) + 'dolorsit amet Lorem ' + brMarkup(4) + 'ipsum dolorsit amet'); + }); + + it('highlights the last line', function () { + var inHtml = lineNumberingService.insertLineNumbers("Lorem ipsum dolorsit amet", 5); + var highlighted = lineNumberingService.highlightLine(inHtml, 5); + expect(highlighted).toBe(noMarkup(1) + 'Lorem ' + brMarkup(2) + 'ipsum ' + brMarkup(3) + 'dolor' + brMarkup(4) + 'sit ' + brMarkup(5) + 'amet'); + }); + + it('highlights the first line', function () { + var inHtml = lineNumberingService.insertLineNumbers("Lorem ipsum dolorsit amet", 5); + var highlighted = lineNumberingService.highlightLine(inHtml, 1); + expect(highlighted).toBe(noMarkup(1) + 'Lorem ' + brMarkup(2) + 'ipsum ' + brMarkup(3) + 'dolor' + brMarkup(4) + 'sit ' + brMarkup(5) + 'amet'); + }); + + it('does not change the string if the line number is not found', function () { + var inHtml = lineNumberingService.insertLineNumbers("Lorem ipsum dolorsit amet", 5); + var highlighted = lineNumberingService.highlightLine(inHtml, 8); + expect(highlighted).toBe(noMarkup(1) + 'Lorem ' + brMarkup(2) + 'ipsum ' + brMarkup(3) + 'dolor' + brMarkup(4) + 'sit ' + brMarkup(5) + 'amet'); + }); + }); });