Line breaks in inline diffs

This commit is contained in:
Tobias Hößl 2017-03-26 17:55:28 +02:00
parent c8f64783bc
commit 0d98fa3a88
3 changed files with 124 additions and 14 deletions

View File

@ -760,6 +760,11 @@ angular.module('OpenSlidesApp.motions', [
oldText = lineNumberingService.insertLineNumbers(oldText, lineLength, null, null, this.line_from);
var diff = diffService.diff(oldText, this.text);
// If an insertion makes the line longer than the line length limit, we need two line breaking runs:
// - First, for the official line numbers, ignoring insertions (that's been done some lines before)
// - Second, another one to prevent the displayed including insertions to exceed the page width
diff = lineNumberingService.insertLineBreaksWithoutNumbers(diff, lineLength, true);
if (highlight > 0) {
diff = lineNumberingService.highlightLine(diff, highlight);
}

View File

@ -25,6 +25,7 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
this._currentLineNumber = null;
this._prependLineNumberToFirstText = false;
this._ignoreNextRegularLineNumber = false;
this._ignoreInsertedText = false;
var lineNumberCache = $cacheFactory('linenumbering.service');
@ -46,7 +47,13 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
};
this._isIgnoredByLineNumbering = function (node) {
return (node.nodeName == 'INS');
if (node.nodeName === 'INS') {
return this._ignoreInsertedText;
} else if (this._isOsLineNumberNode(node)) {
return true;
} else {
return false;
}
};
this._isOsLineBreakNode = function (node) {
@ -142,7 +149,7 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
highlight = -1;
}
if (firstTextNode) {
if (highlight == service._currentLineNumber - 1) {
if (highlight === service._currentLineNumber - 1) {
node = document.createElement('span');
node.setAttribute('class', 'highlight');
node.innerHTML = text;
@ -151,7 +158,7 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
}
firstTextNode = false;
} else {
if (service._currentLineNumber == highlight) {
if (service._currentLineNumber === highlight && highlight !== null) {
node = document.createElement('span');
node.setAttribute('class', 'highlight');
node.innerHTML = text;
@ -159,7 +166,9 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
node = document.createTextNode(text);
}
out.push(service._createLineBreak());
out.push(service._createLineNumber());
if (service._currentLineNumber !== null) {
out.push(service._createLineNumber());
}
}
out.push(node);
};
@ -171,12 +180,14 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
// This happens if a previous inline element exactly stretches to the end of the line
if (this._currentInlineOffset >= length) {
out.push(service._createLineBreak());
out.push(service._createLineNumber());
if (this._currentLineNumber !== null) {
out.push(service._createLineNumber());
}
this._currentInlineOffset = 0;
} else if (this._prependLineNumberToFirstText) {
if (this._ignoreNextRegularLineNumber) {
this._ignoreNextRegularLineNumber = false;
} else {
} else if (service._currentLineNumber !== null) {
out.push(service._createLineNumber());
}
}
@ -270,7 +281,9 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
if (overlength && this._isInlineElement(oldChildren[i])) {
this._currentInlineOffset = 0;
node.appendChild(this._createLineBreak());
node.appendChild(this._createLineNumber());
if (this._currentLineNumber !== null) {
node.appendChild(this._createLineNumber());
}
}
var changedNode = this._insertLineNumbersToNode(oldChildren[i], length, highlight);
this._moveLeadingLineBreaksToOuterNode(changedNode, node);
@ -357,7 +370,9 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
if (overlength && this._isInlineElement(oldChildren[i]) && !this._isIgnoredByLineNumbering(oldChildren[i])) {
this._currentInlineOffset = 0;
node.appendChild(this._createLineBreak());
node.appendChild(this._createLineNumber());
if (this._currentLineNumber !== null) {
node.appendChild(this._createLineNumber());
}
}
var changedNode = this._insertLineNumbersToNode(oldChildren[i], length, highlight);
this._moveLeadingLineBreaksToOuterNode(changedNode, node);
@ -379,7 +394,7 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
throw 'This method may only be called for ELEMENT-nodes: ' + node.nodeValue;
}
if (this._isIgnoredByLineNumbering(node)) {
if (this._currentInlineOffset === 0) {
if (this._currentInlineOffset === 0 && this._currentLineNumber !== null) {
var lineNumberNode = this._createLineNumber();
if (lineNumberNode) {
node.insertBefore(lineNumberNode, node.firstChild);
@ -420,8 +435,8 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
*
* @param {string} html
* @param {number} lineLength
* @param {number} highlight - optional
* @param {number} firstLine
* @param {number|null} highlight - optional
* @param {number|null} firstLine
*/
this.insertLineNumbersNode = function (html, lineLength, highlight, firstLine) {
var root = document.createElement('div');
@ -429,12 +444,16 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
this._currentInlineOffset = 0;
if (firstLine) {
this._currentLineNumber = firstLine;
this._currentLineNumber = parseInt(firstLine);
} else {
this._currentLineNumber = 1;
}
if (highlight !== null) {
highlight = parseInt(highlight);
}
this._prependLineNumberToFirstText = true;
this._ignoreNextRegularLineNumber = false;
this._ignoreInsertedText = true;
return this._insertLineNumbersToNode(root, lineLength, highlight);
};
@ -443,7 +462,7 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
*
* @param {string} html
* @param {number} lineLength
* @param {string} highlight - optional
* @param {number|null} highlight - optional
* @param {function} callback
* @param {number} firstLine
* @returns {string}
@ -460,7 +479,7 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
newHtml = lineNumberCache.get(cacheKey);
if (angular.isUndefined(newHtml)) {
newRoot = this.insertLineNumbersNode(html, lineLength, highlight, firstLine);
newRoot = this.insertLineNumbersNode(html, lineLength, null, firstLine);
newHtml = newRoot.innerHTML;
lineNumberCache.put(cacheKey, newHtml);
}
@ -473,6 +492,26 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
return newHtml;
};
/**
* @param {string} html
* @param {number} lineLength
* @param {boolean} countInserted
*/
this.insertLineBreaksWithoutNumbers = function (html, lineLength, countInserted) {
var root = document.createElement('div');
root.innerHTML = html;
this._currentInlineOffset = 0;
this._currentLineNumber = null;
this._prependLineNumberToFirstText = true;
this._ignoreNextRegularLineNumber = false;
this._ignoreInsertedText = !countInserted;
var newRoot = this._insertLineNumbersToNode(root, lineLength, null);
return newRoot.innerHTML;
};
/**
* @param {string} html
* @returns {string}
@ -538,6 +577,7 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
* @return {string}
*/
this.highlightLine = function (html, lineNumber) {
lineNumber = parseInt(lineNumber);
var fragment = this._htmlToFragment(html),
lineNumberNode = this._getLineNumberNode(fragment, lineNumber);

View File

@ -111,6 +111,7 @@ describe('linenumbering', function () {
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 5);
expect(outHtml).toBe(noMarkup(1) + '<span>Test</span>');
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
it('breaks lines in a simple SPAN', function () {
@ -118,6 +119,7 @@ describe('linenumbering', function () {
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 5);
expect(outHtml).toBe(noMarkup(1) + '<span>Lorem ' + brMarkup(2) + 'ipsum ' + brMarkup(3) + 'dolor' + brMarkup(4) + 'sit ' + brMarkup(5) + 'amet</span>');
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
it('breaks lines in nested inline elements', function () {
@ -125,6 +127,7 @@ describe('linenumbering', function () {
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 5);
expect(outHtml).toBe(noMarkup(1) + '<span>Lorem ' + brMarkup(2) + '<strong>ipsum ' + brMarkup(3) + 'dolor' + brMarkup(4) + 'sit</strong> ' + brMarkup(5) + 'amet</span>');
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
it('counts within DEL nodes', function () {
@ -132,6 +135,7 @@ describe('linenumbering', function () {
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 10);
expect(outHtml).toBe(noMarkup(1) + '1234 <del>1234</del> ' + brMarkup(2) + '1234 1234');
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
it('handles STRIKE-tags', function () {
@ -139,6 +143,7 @@ describe('linenumbering', function () {
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 80);
expect(outHtml).toBe('<p>' + noMarkup(1) + 'et accusam et justo duo dolores et ea <span style="color: #ff0000;"><strike>rebum </strike></span><span style="color: #006400;">Inserted Text</span>. Stet clita kasd ' + brMarkup(2) + 'gubergren,</p>');
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
});
@ -149,6 +154,7 @@ describe('linenumbering', function () {
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 5);
expect(outHtml).toBe('<div>' + noMarkup(1) + 'Test</div>');
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
it('breaks a DIV containing only inline elements', function () {
@ -156,6 +162,7 @@ describe('linenumbering', function () {
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 5);
expect(outHtml).toBe('<div>' + noMarkup(1) + 'Test ' + brMarkup(2) + '<span>Test1' + brMarkup(3) + '234</span>56' + brMarkup(4) + '78 ' + brMarkup(5) + 'Test</div>');
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
it('handles a DIV within a DIV correctly', function () {
@ -163,6 +170,7 @@ describe('linenumbering', function () {
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 5);
expect(outHtml).toBe('<div>' + noMarkup(1) + 'Te<div>' + noMarkup(2) + 'Te ' + brMarkup(3) + 'Test</div>' + noMarkup(4) + 'Test</div>');
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
it('ignores white spaces between block element tags', function () {
@ -170,6 +178,7 @@ describe('linenumbering', function () {
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 80);
expect(outHtml).toBe("<ul>\n<li>" + noMarkup(1) + 'Test</li>\n</ul>');
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
});
@ -186,6 +195,7 @@ describe('linenumbering', function () {
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 80);
expect(outHtml).toBe(expected);
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
it('indents BLOCKQUOTE-elements', function () {
@ -199,6 +209,7 @@ describe('linenumbering', function () {
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 80);
expect(outHtml).toBe(expected);
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
it('shortens the line for H1-elements by 1/2', function () {
@ -208,6 +219,7 @@ describe('linenumbering', function () {
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 80);
expect(outHtml).toBe(expected);
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
it('shortens the line for H2-elements by 2/3', function () {
@ -217,6 +229,7 @@ describe('linenumbering', function () {
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 80);
expect(outHtml).toBe(expected);
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
it('indents Ps with 30px-padding by 6 characters', function () {
@ -226,6 +239,7 @@ describe('linenumbering', function () {
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 80);
expect(outHtml).toBe(expected);
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
it('breaks before an inline element, if the first word of the new inline element is longer than the remaining line (1)', function () {
@ -233,6 +247,7 @@ describe('linenumbering', function () {
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 80);
expect(outHtml).toBe('<p>' + noMarkup(1) + 'Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie ' + brMarkup(2) + '<strong>consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan ' + brMarkup(3) + 'et iusto odio</strong>.</p>');
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
it('breaks before an inline element, if the first word of the new inline element is longer than the remaining line (2)', function () {
@ -240,6 +255,7 @@ describe('linenumbering', function () {
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 80);
expect(outHtml).toBe('<p>' + noMarkup(1) + '<span>Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie ' + brMarkup(2) + '<strong>consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan ' + brMarkup(3) + 'et iusto odio</strong>.</span></p>');
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
it('does not fail in a weird case', function () {
@ -247,6 +263,7 @@ describe('linenumbering', function () {
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 80);
expect(outHtml).toBe(noMarkup(1) + '<ins>seid Noch</ins><p></p><p>' + noMarkup(2) + '<ins>Test 123</ins></p>');
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
});
@ -256,6 +273,7 @@ describe('linenumbering', function () {
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 10);
expect(outHtml).toBe(noMarkup(1) + '1234 <ins>1234</ins> 1234 ' + brMarkup(2) + '1234');
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
it('does not create a new line for a trailing INS', function () {
@ -263,6 +281,7 @@ describe('linenumbering', function () {
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 80);
expect(outHtml).toBe('<p>' + noMarkup(1) + 'et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata ' + brMarkup(2) + 'sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur<ins>dsfsdf23</ins></p>');
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
it('inserts the line number before the INS, if INS is the first element of the paragraph', function() {
@ -270,6 +289,52 @@ describe('linenumbering', function () {
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 80);
expect(outHtml).toBe("<p>" + noMarkup(1) + "<ins>lauthals </ins>'liebe Kinder, ich will hinaus in den Wald, seid auf der Hut vor dem Wolf!' Und " + brMarkup(2) + "noch etwas mehr Text bis zur nächsten Zeile</p>");
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
});
describe('line breaking without adding line numbers', function() {
var plainBr = '<br class="os-line-break">';
it('breaks a DIV containing only inline elements', function () {
var inHtml = "<div>Test <span>Test1234</span>5678 Test</div>";
var outHtml = lineNumberingService.insertLineBreaksWithoutNumbers(inHtml, 5);
expect(outHtml).toBe('<div>Test ' + plainBr + '<span>Test1' + plainBr + '234</span>56' + plainBr + '78 ' + plainBr + 'Test</div>');
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
it('indents BLOCKQUOTE-elements', function () {
var inHtml = '<div>' + longstr(100) + '<blockquote>' + longstr(100) + '</blockquote>' + longstr(100) + '</div>';
var expected = '<div>' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZAB' + plainBr + 'CDEFGHIJKLMNOPQRSTUV' +
'<blockquote>' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGH' + plainBr + 'IJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUV' +
'</blockquote>' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZAB' + plainBr + 'CDEFGHIJKLMNOPQRSTUV</div>';
var outHtml = lineNumberingService.insertLineBreaksWithoutNumbers(inHtml, 80);
expect(outHtml).toBe(expected);
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
it('DOES count within INS nodes', function () {
var inHtml = "1234 <ins>1234</ins> 1234 1234";
var outHtml = lineNumberingService.insertLineBreaksWithoutNumbers(inHtml, 10, true);
expect(outHtml).toBe('1234 <ins>1234</ins> ' + plainBr + '1234 1234');
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
it('does not create a new line for a trailing INS', function () {
var inHtml = "<p>et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur<ins>dsfsdf23</ins></p>";
var outHtml = lineNumberingService.insertLineBreaksWithoutNumbers(inHtml, 80, true);
expect(outHtml).toBe('<p>et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata ' +
plainBr + 'sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur' +
plainBr + '<ins>dsfsdf23</ins></p>');
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
it('ignores witespaces by previously added line numbers', function () {
var inHtml = "<p>" + noMarkup(1) + longstr(10) + "</p>";
var outHtml = lineNumberingService.insertLineBreaksWithoutNumbers(inHtml, 10, true);
expect(outHtml).toBe("<p>" + noMarkup(1) + longstr(10) + "</p>");
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
});