diff --git a/openslides/motions/static/js/motions/base.js b/openslides/motions/static/js/motions/base.js index 0d82c2dcf..2e2a9d858 100644 --- a/openslides/motions/static/js/motions/base.js +++ b/openslides/motions/static/js/motions/base.js @@ -756,7 +756,8 @@ angular.module('OpenSlidesApp.motions', [ oldText = data.outerContextStart + data.innerContextStart + data.html + data.innerContextEnd + data.outerContextEnd; - var diff = diffService.diff(oldText, this.text, lineLength, this.line_from); + oldText = lineNumberingService.insertLineNumbers(oldText, lineLength, null, null, this.line_from); + var diff = diffService.diff(oldText, this.text); if (highlight > 0) { diff = lineNumberingService.highlightLine(diff, highlight); diff --git a/openslides/motions/static/js/motions/diff.js b/openslides/motions/static/js/motions/diff.js index fce756f35..37a6025fa 100644 --- a/openslides/motions/static/js/motions/diff.js +++ b/openslides/motions/static/js/motions/diff.js @@ -816,15 +816,14 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi arr = splitArrayEntriesSplitSeparator(arr, "."); arr = splitArrayEntriesEmbedSeparator(arr, "\n", false); - return arr; - }; - - this._outputcharcode = function (pre, str) { - var arr = []; - for (var i = 0; i < str.length; i++) { - arr.push(str.charCodeAt(i)); + var arrWithoutEmptes = []; + for (var i = 0; i < arr.length; i++) { + if (arr[i] !== '') { + arrWithoutEmptes.push(arr[i]); + } } - console.log(str, pre, arr); + + return arrWithoutEmptes; }; /** @@ -842,13 +841,11 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi if (out.n.length === 0) { for (i = 0; i < out.o.length; i++) { - //this._outputcharcode('del', out.o[i]); str += '' + out.o[i] + ""; } } else { if (out.n[0].text === undefined) { for (var k = 0; k < out.o.length && out.o[k].text === undefined; k++) { - //this._outputcharcode('del', out.o[k]); str += '' + out.o[k] + ""; } } @@ -862,7 +859,6 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi var pre = ""; for (var j = out.n[i].row + 1; j < out.o.length && out.o[j].text === undefined; j++) { - //this._outputcharcode('del', out.o[j]); pre += '' + out.o[j] + ""; } str += out.n[i].text + pre; @@ -1005,10 +1001,33 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi return cached; } - var str = this._diffString(htmlOld, htmlNew), + // This fixes a really strange artefact with the diff that occures under the following conditions: + // - The first tag of the two texts is identical, e.g.

+ // - A change happens in the next tag, e.g. inserted text + // - The first tag occures a second time in the text, e.g. another

+ // In this condition, the first tag is deleted first and inserted afterwards again + // Test case: "does not break when an insertion followes a beginning tag occuring twice" + // The work around inserts to tags at the beginning and removes them afterwards again, + // to make sure this situation does not happen (and uses invisible pseudo-tags in case something goes wrong) + var workaroundPrepend = ""; + + var str = this._diffString(workaroundPrepend + htmlOld, workaroundPrepend + htmlNew), diffUnnormalized = str.replace(/^\s+/g, '').replace(/\s+$/g, '').replace(/ {2,}/g, ' ') .replace(/<\/ins>/gi, '').replace(/<\/del>/gi, ''); + diffUnnormalized = diffUnnormalized.replace( + /(\s*)(]*)?>[\s\S]*?<\/p>)(\s*)<\/ins>/gim, + function(match, whiteBefore, inner, tagInner, whiteAfter) { + return whiteBefore + + inner + .replace(/]*)?>/gi, function(match) { + return match + ""; + }) + .replace(/<\/p>/gi, "

") + + whiteAfter; + } + ); + diffUnnormalized = diffUnnormalized.replace(/([a-z0-9,_-]* ?)<\/del>([a-z0-9,_-]* ?)<\/ins>/gi, function (found, oldText, newText) { var foundDiff = false, commonStart = '', commonEnd = '', remainderOld = oldText, remainderNew = newText; @@ -1046,6 +1065,17 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi return out; }); + diffUnnormalized = diffUnnormalized.replace( + /((
)?]+OS-LINE-NUMBER.+?)<\/del>/gi, + function(found,tag) { + return tag.toLowerCase().replace(/> <\/span/gi, ">  '; }; - beforeEach(inject(function (_diffService_) { + beforeEach(inject(function (_diffService_, _lineNumberingService_) { diffService = _diffService_; + lineNumberingService = _lineNumberingService_; baseHtml1 = '

' + noMarkup(1) + 'Line 1 ' + brMarkup(2) + 'Line 2 ' + @@ -432,5 +433,53 @@ describe('linenumbering', function () { expect(diff).toBe("

...so frißt er Euch alle mit Haut und Haar und Augen und Därme und alles.

"); }); + + it('does not break when an insertion followes a beginning tag occuring twice', function () { + var before = "

...so frißt er Euch alle mit Haut und Haar.

\n

Test

", + after = "

Einfügung 1 ...so frißt er Euch alle mit Haut und Haar und Augen und Därme und alles.

\n

Test

"; + var diff = diffService.diff(before, after); + + expect(diff).toBe("

Einfügung 1 ...so frißt er Euch alle mit Haut und Haar und Augen und Därme und alles.

\n

Test

"); + }); + }); + + describe('ignoring line numbers', function () { + it('works despite line numbers, part 1', function () { + var before = "

...so frißt er Euch alle mit Haut und Haar.

", + after = "

...so frißt er Euch alle mit Haut und Haar und Augen und Därme und alles.

"; + before = lineNumberingService.insertLineNumbers(before, 15, null, null, 2); + var diff = diffService.diff(before, after); + + expect(diff).toBe("

" + noMarkup(2) + "...so frißt er " + brMarkup(3) + "Euch alle mit " + brMarkup(4) + "Haut und Haar und Augen und Därme und alles.

"); + }); + + it('works with an inserted paragraph', function () { + var before = "

their grammar, their pronunciation and their most common words. Everyone realizes why a

", + after = "

their grammar, their pronunciation and their most common words. Everyone realizes why a

\n" + + "

NEW PARAGRAPH 2.

"; + + before = lineNumberingService.insertLineNumbers(before, 80, null, null, 2); + var diff = diffService.diff(before, after); + expect(diff).toBe("

" + noMarkup(2) + "their grammar, their pronunciation and their most common words. Everyone " + brMarkup(3) + "realizes why a

\n" + + "

NEW PARAGRAPH 2.

"); + }); + + it('works with two inserted paragraphs', function () { + // Hint: If the last paragraph is a P again, the Diff still fails and falls back to paragraph-based diff + // This leaves room for future improvements + var before = "

their grammar, their pronunciation and their most common words. Everyone realizes why a

\n
Go on
", + after = "

their grammar, their pronunciation and their most common words. Everyone realizes why a

\n" + + "

NEW PARAGRAPH 1.

\n" + + "

NEW PARAGRAPH 2.

\n" + + "
Go on
"; + + before = lineNumberingService.insertLineNumbers(before, 80, null, null, 2); + var diff = diffService.diff(before, after); + expect(diff).toBe("

" + noMarkup(2) + "their grammar, their pronunciation and their most common words. Everyone " + brMarkup(3) + "realizes why a

\n" + + "

NEW PARAGRAPH 1.

\n" + + "

NEW PARAGRAPH 2.

\n" + + "
" + noMarkup(4) + "Go on
" + ); + }); }); });