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,

'); 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('
' + noMarkup(1) + 'Test
'); 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('
' + noMarkup(1) + 'Test ' + brMarkup(2) + 'Test1' + brMarkup(3) + '23456' + brMarkup(4) + '78 ' + brMarkup(5) + 'Test
'); 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('
' + noMarkup(1) + 'Te
' + noMarkup(2) + 'Te ' + brMarkup(3) + 'Test
' + noMarkup(4) + 'Test
'); 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("'); 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('

' + 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 = '
'; + + it('breaks a DIV containing only inline elements', function () { + var inHtml = "
Test Test12345678 Test
"; + var outHtml = lineNumberingService.insertLineBreaksWithoutNumbers(inHtml, 5); + expect(outHtml).toBe('
Test ' + plainBr + 'Test1' + plainBr + '23456' + plainBr + '78 ' + plainBr + 'Test
'); + expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml); + expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml); + }); + + it('indents BLOCKQUOTE-elements', function () { + var inHtml = '
' + longstr(100) + '
' + longstr(100) + '
' + longstr(100) + '
'; + var expected = '
' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZAB' + plainBr + 'CDEFGHIJKLMNOPQRSTUV' + + '
' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGH' + plainBr + 'IJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUV' + + '
' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZAB' + plainBr + 'CDEFGHIJKLMNOPQRSTUV
'; + 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 1234 1234 1234"; + var outHtml = lineNumberingService.insertLineBreaksWithoutNumbers(inHtml, 10, true); + expect(outHtml).toBe('1234 1234 ' + plainBr + '1234 1234'); + expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml); + }); + + it('does not create a new line for a trailing INS', function () { + var inHtml = "

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); }); });