diff --git a/openslides/motions/static/js/motions/base.js b/openslides/motions/static/js/motions/base.js
index fc6ddb7b5..10ec0033a 100644
--- a/openslides/motions/static/js/motions/base.js
+++ b/openslides/motions/static/js/motions/base.js
@@ -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);
}
diff --git a/openslides/motions/static/js/motions/linenumbering.js b/openslides/motions/static/js/motions/linenumbering.js
index fdb09dbf3..f7aa9a90a 100644
--- a/openslides/motions/static/js/motions/linenumbering.js
+++ b/openslides/motions/static/js/motions/linenumbering.js
@@ -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);
diff --git a/tests/karma/motions/linenumbering.service.test.js b/tests/karma/motions/linenumbering.service.test.js
index 2b840a996..e1b57d30e 100644
--- a/tests/karma/motions/linenumbering.service.test.js
+++ b/tests/karma/motions/linenumbering.service.test.js
@@ -111,6 +111,7 @@ describe('linenumbering', function () {
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 5);
expect(outHtml).toBe(noMarkup(1) + 'Test');
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) + 'Lorem ' + brMarkup(2) + 'ipsum ' + brMarkup(3) + 'dolor' + brMarkup(4) + 'sit ' + brMarkup(5) + 'amet');
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) + 'Lorem ' + brMarkup(2) + 'ipsum ' + brMarkup(3) + 'dolor' + brMarkup(4) + 'sit ' + brMarkup(5) + 'amet');
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 1234 ' + 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('
' + noMarkup(1) + 'et accusam et justo duo dolores et ea rebum Inserted Text. Stet clita kasd ' + brMarkup(2) + 'gubergren,
' + noMarkup(1) + 'Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie ' + brMarkup(2) + 'consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan ' + brMarkup(3) + 'et iusto odio.
'); 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('' + noMarkup(1) + 'Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie ' + brMarkup(2) + 'consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan ' + brMarkup(3) + 'et iusto odio.
'); 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) + 'seid Noch' + noMarkup(2) + 'Test 123
'); 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 1234 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('' + 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, conseteturdsfsdf23
'); 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("" + noMarkup(1) + "lauthals '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
"); expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml); + expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml); + }); + }); + + describe('line breaking without adding line numbers', function() { + var plainBr = '' + longstr(100) + '' + longstr(100) + '
' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGH' + plainBr + 'IJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUV' + + '' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZAB' + plainBr + 'CDEFGHIJKLMNOPQRSTUV
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, conseteturdsfsdf23
"; + var outHtml = lineNumberingService.insertLineBreaksWithoutNumbers(inHtml, 80, true); + expect(outHtml).toBe('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 + 'dsfsdf23
'); + expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml); + }); + + it('ignores witespaces by previously added line numbers', function () { + var inHtml = "" + noMarkup(1) + longstr(10) + "
"; + var outHtml = lineNumberingService.insertLineBreaksWithoutNumbers(inHtml, 10, true); + expect(outHtml).toBe("" + noMarkup(1) + longstr(10) + "
"); + expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml); }); });