diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 56c9a3d3b..6a20ef9e6 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -15,8 +15,7 @@ Bugfixes:
- Fixed missing output of special poll values (for motions and elections) [#3932].
- Fixed amendment csv export (added missing submitters and recommendation, removed
html tags for old and new text) [#3942].
- - Fixed motion/amendment diff bug: does not delete a paragraph/list item
- before an new one was inserted [#3943, #3946].
+ - Fixed motion/amendment diff bug [#3943, #3946, #4020].
- Allow to hide internal items in agenda sort view [#3992].
diff --git a/openslides/motions/static/js/motions/diff.js b/openslides/motions/static/js/motions/diff.js
index d0bb9958a..e7d3e015b 100644
--- a/openslides/motions/static/js/motions/diff.js
+++ b/openslides/motions/static/js/motions/diff.js
@@ -1337,6 +1337,32 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi
return node.innerHTML;
};
+ /**
+ * Add the CSS-class to the existing "class"-attribute, or add one.
+ * Works on strings, not nodes
+ *
+ * @param {string} tagStr
+ * @param {string} className
+ * @returns {string}
+ * @private
+ */
+ this._addClassToHtmlTag = function (tagStr, className) {
+ return tagStr.replace(/<(\w+)( [^>]*)?>/gi, function(whole, tag, tagArguments) {
+ tagArguments = (tagArguments ? tagArguments : '');
+ if (tagArguments.match(/class="/gi)) {
+ // class="someclass" => class="someclass insert"
+ tagArguments = tagArguments.replace(/(class\s*=\s*)(["'])([^\2]*)\2/gi,
+ function (classWhole, attr, para, content) {
+ return attr + para + content + ' ' + className + para;
+ }
+ );
+ } else {
+ tagArguments += ' class="' + className + '"';
+ }
+ return '<' + tag + tagArguments + '>';
+ });
+ };
+
/**
* This function removes color-Attributes from the styles of this node or a descendant,
* as they interfer with the green/red color in HTML and PDF
@@ -1469,36 +1495,9 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi
}
);
+ // Merging individual insert/delete statements into bigger blocks
diffUnnormalized = diffUnnormalized.replace(/<\/ins>/gi, '').replace(/<\/del> ]*)?>[\s\S]*?<\/p>)(\s*)<\/ins>/gim,
- function(match, whiteBefore, inner, tagInner, whiteAfter) {
- return whiteBefore +
- inner
- .replace(/ ]*)?>/gi, function(match) {
- return match + "";
- })
- .replace(/<\/p>/gi, "/gi, '');
- // Move whitespaces around inserted P's out of the INS-tag
- diffUnnormalized = diffUnnormalized.replace(
- /(\s*)(
More inserted text
- // into: Inserted Text\nMore inserted text
- diffUnnormalized = diffUnnormalized.replace( - /)/gi, "
$1"); - } - ); - // If only a few characters of a word have changed, don't display this as a replacement of the whole word, // but only of these specific characters diffUnnormalized = diffUnnormalized.replace(/...
...... - if (content.match(/<(ins|del)>/gi)) { - return whole; - } - - // Add the CSS-class to the existing "class"-attribute, or add one - var newArguments = blockArguments; - var modificationClass = (insDel.toLowerCase() === 'ins' ? 'insert' : 'delete'); - if (newArguments.match(/class="/gi)) { - // class="someclass" => class="someclass insert" - newArguments = newArguments.replace(/(class\s*=\s*)(["'])([^\2]*)\2/gi, - function(classWhole, attr, para, content) { - return attr + para + content + ' ' + modificationClass + para; - } - ); - } else { - newArguments += ' class="' + modificationClass + '"'; - } - - return '<' + block + newArguments + '>' + content + '' + block + '>'; + /Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu.
"; var diff = diffService.diff(before, after); expect(diff).toBe("Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
\n" + - "Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu.
"); + "Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu.
"); }); it('does not result in separate paragraphs when only the first word has changed', function () { @@ -527,9 +527,10 @@ describe('linenumbering', function () { after = "rief sie alle sieben herbei und sprach 'liebe Kinder, ich will hinaus in den Wald, seid Noch
" + "Test 123
", expected = "rief sie alle sieben herbei und sprach 'liebe Kinder, ich will hinaus in den Wald, seid Noch
" + - "Test 123
"; + "Test 123
"; var diff = diffService.diff(before, after); + expect(diff).toBe(expected); }); @@ -540,7 +541,7 @@ describe('linenumbering', function () { "\n" + "Stet clita kasd gubergren, no sea takimata sanctus est.
", expected = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
\n" + - "Stet clita kasd gubergren, no sea takimata sanctus est.
"; + "Stet clita kasd gubergren, no sea takimata sanctus est.
"; var diff = diffService.diff(before, after); expect(diff).toBe(expected); @@ -552,8 +553,8 @@ describe('linenumbering', function () { 'Inserting this line should not make any troubles, especially not affect the first line
' + 'Neither should this line
', expected = "This is a random first line that remains unchanged.
" + - 'Inserting this line should not make any troubles, especially not affect the first line
' + - 'Neither should this line
'; + 'Inserting this line should not make any troubles, especially not affect the first line
' + + 'Neither should this line
'; var diff = diffService.diff(before, after); expect(diff).toBe(expected); @@ -653,6 +654,13 @@ describe('linenumbering', function () { expect(diff).toBe('Lorem ipsum dolor sit amet, consetetur sadipscing elitr. Sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua..
Bavaria ipsum dolor sit amet o’ha wea nia ausgähdkummt nia hoam i hob di narrisch geanAutonomie erfährt ihre Grenzen
This is the text before
', + after = "This is the text before
\nThis is one added line
\nAnother added line
"; + var diff = diffService.diff(before, after); + expect(diff).toBe("This is the text before
\nThis is one added line
\nAnother added line
"); + }); + it('does not a change in a very specific case', function() { // See diff._fixWrongChangeDetection var inHtml = 'Test 123
wir strikt ab. lehnen wir ' + brMarkup(1486) + 'ab.
' + noMarkup(1487) + 'Gegenüber
" + noMarkup(2) + "their grammar, their pronunciation and their most common words. Everyone " + brMarkup(3) + "realizes why a
\n" + - "NEW PARAGRAPH 2.
"); + "NEW PARAGRAPH 2.
"); }); it('works with two inserted paragraphs', function () { @@ -711,8 +719,8 @@ describe('linenumbering', function () { 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" + + "NEW PARAGRAPH 1.
\n" + + "NEW PARAGRAPH 2.
\n" + "