Merge pull request #4022 from CatoTH/Bugfix-Diff-Port

OS3-Port of #4020
This commit is contained in:
Sean 2018-11-21 13:27:37 +01:00 committed by GitHub
commit 224f98bf49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 82 additions and 61 deletions

View File

@ -669,7 +669,7 @@ describe('DiffService', () => {
const diff = service.diff(before, after); const diff = service.diff(before, after);
expect(diff).toBe( expect(diff).toBe(
'<p><span class="line-number-5 os-line-number" contenteditable="false" data-line-number="5">&nbsp;</span>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</p>\n' + '<p><span class="line-number-5 os-line-number" contenteditable="false" data-line-number="5">&nbsp;</span>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</p>\n' +
'<p><ins>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.</ins></p>' '<p class="insert">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.</p>'
); );
} }
)); ));
@ -762,7 +762,7 @@ describe('DiffService', () => {
'<p>Test 123</p>', '<p>Test 123</p>',
expected = expected =
"<p>rief sie alle sieben herbei und sprach 'liebe Kinder, ich will hinaus in den Wald, seid<ins> Noch</ins></p>" + "<p>rief sie alle sieben herbei und sprach 'liebe Kinder, ich will hinaus in den Wald, seid<ins> Noch</ins></p>" +
'<p><ins>Test 123</ins></p>'; '<p class="insert">Test 123</p>';
const diff = service.diff(before, after); const diff = service.diff(before, after);
expect(diff).toBe(expected); expect(diff).toBe(expected);
@ -778,7 +778,7 @@ describe('DiffService', () => {
'<p>Stet clita kasd gubergren, no sea takimata sanctus est.</p>', '<p>Stet clita kasd gubergren, no sea takimata sanctus est.</p>',
expected = expected =
'<p><span class="line-number-1 os-line-number" contenteditable="false" data-line-number="1">&nbsp;</span>Lorem ipsum dolor sit amet, consetetur sadipscing elitr,<ins> 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.</ins></p>\n' + '<p><span class="line-number-1 os-line-number" contenteditable="false" data-line-number="1">&nbsp;</span>Lorem ipsum dolor sit amet, consetetur sadipscing elitr,<ins> 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.</ins></p>\n' +
'<p class="os-split-after"><ins>Stet clita kasd gubergren, no sea takimata sanctus est.</ins></p>'; '<p class="insert os-split-after">Stet clita kasd gubergren, no sea takimata sanctus est.</p>';
const diff = service.diff(before, after); const diff = service.diff(before, after);
expect(diff).toBe(expected); expect(diff).toBe(expected);
@ -792,8 +792,8 @@ describe('DiffService', () => {
'<p style="text-align: justify;"><span style="color: #000000;">Neither should this line</span></p>', '<p style="text-align: justify;"><span style="color: #000000;">Neither should this line</span></p>',
expected = expected =
'<p>This is a random first line that remains unchanged.</p>' + '<p>This is a random first line that remains unchanged.</p>' +
'<p style="text-align: justify;"><ins><span style="color: #000000;">Inserting this line should not make any troubles, especially not affect the first line</span></ins></p>' + '<p style="text-align: justify;" class="insert"><span style="color: #000000;">Inserting this line should not make any troubles, especially not affect the first line</span></p>' +
'<p style="text-align: justify;"><ins><span style="color: #000000;">Neither should this line</span></ins></p>'; '<p style="text-align: justify;" class="insert"><span style="color: #000000;">Neither should this line</span></p>';
const diff = service.diff(before, after); const diff = service.diff(before, after);
expect(diff).toBe(expected); expect(diff).toBe(expected);
@ -935,6 +935,16 @@ describe('DiffService', () => {
} }
)); ));
it('works with multiple inserted paragraphs', inject(
[DiffService],
(service: DiffService) => {
const before = '<p>This is the text before</p>',
after = "<p>This is the text before</p>\n<p>This is one added line</p>\n<p>Another added line</p>";
const diff = service.diff(before, after);
expect(diff).toBe("<p>This is the text before</p>\n<p class=\"insert\">This is one added line</p>\n<p class=\"insert\">Another added line</p>");
}
));
it('does not a change in a very specific case', inject([DiffService], (service: DiffService) => { it('does not a change in a very specific case', inject([DiffService], (service: DiffService) => {
// See diff._fixWrongChangeDetection // See diff._fixWrongChangeDetection
const inHtml = const inHtml =
@ -1007,7 +1017,7 @@ describe('DiffService', () => {
'their grammar, their pronunciation and their most common words. Everyone ' + 'their grammar, their pronunciation and their most common words. Everyone ' +
brMarkup(3) + brMarkup(3) +
'realizes why a</p>\n' + 'realizes why a</p>\n' +
'<p><ins>NEW PARAGRAPH 2.</ins></p>' '<p class="insert">NEW PARAGRAPH 2.</p>'
); );
} }
)); ));
@ -1033,8 +1043,8 @@ describe('DiffService', () => {
'their grammar, their pronunciation and their most common words. Everyone ' + 'their grammar, their pronunciation and their most common words. Everyone ' +
brMarkup(3) + brMarkup(3) +
'realizes why a</p>\n' + 'realizes why a</p>\n' +
'<p><ins>NEW PARAGRAPH 1.</ins></p>\n' + '<p class="insert">NEW PARAGRAPH 1.</p>\n' +
'<p><ins>NEW PARAGRAPH 2.</ins></p>\n' + '<p class="insert">NEW PARAGRAPH 2.</p>\n' +
'<div>' + '<div>' +
noMarkup(4) + noMarkup(4) +
'Go on</div>' 'Go on</div>'

View File

@ -1012,6 +1012,34 @@ export class DiffService {
} }
} }
/**
* 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 addClassToHtmlTag(tagStr: string, className: string): string {
return tagStr.replace(
/<(\w+)( [^>]*)?>/gi,
(whole: string, tag: string, tagArguments: string): string => {
tagArguments = (tagArguments ? tagArguments : '');
if (tagArguments.match(/class="/gi)) {
// class="someclass" => class="someclass insert"
tagArguments = tagArguments.replace(/(class\s*=\s*)(["'])([^\2]*)\2/gi,
(classWhole: string, attr: string, para: string, content: string): string => {
return attr + para + content + ' ' + className + para;
}
);
} else {
tagArguments += ' class="' + className + '"';
}
return '<' + tag + tagArguments + '>';
}
);
};
/** /**
* This fixes a very specific, really weird bug that is tested in the test case "does not a change in a very specific case". * This fixes a very specific, really weird bug that is tested in the test case "does not a change in a very specific case".
* *
@ -1831,41 +1859,9 @@ export class DiffService {
} }
); );
// Merging individual insert/delete statements into bigger blocks
diffUnnormalized = diffUnnormalized.replace(/<\/ins><ins>/gi, '').replace(/<\/del><del>/gi, ''); diffUnnormalized = diffUnnormalized.replace(/<\/ins><ins>/gi, '').replace(/<\/del><del>/gi, '');
// Move whitespaces around inserted P's out of the INS-tag
diffUnnormalized = diffUnnormalized.replace(
/<ins>(\s*)(<p( [^>]*)?>[\s\S]*?<\/p>)(\s*)<\/ins>/gim,
(match: string, whiteBefore: string, inner: string, tagInner: string, whiteAfter: string): string => {
return (
whiteBefore +
inner
.replace(
/<p( [^>]*)?>/gi,
(match2: string): string => {
return match2 + '<ins>';
}
)
.replace(/<\/p>/gi, '</ins></p>') +
whiteAfter
);
}
);
// Fixes HTML produced by the diff like this:
// from: <del></P></del><ins> Inserted Text</P>\n<P>More inserted text</P></ins>
// into: <ins> Inserted Text</ins></P>\n<P>More inserted text</ins></P>
diffUnnormalized = diffUnnormalized.replace(
/<del><\/p><\/del><ins>([\s\S]*?)<\/p><\/ins>/gim,
'<ins>$1</ins></p>'
);
diffUnnormalized = diffUnnormalized.replace(
/<ins>[\s\S]*?<\/ins>/gim,
(match: string): string => {
return match.replace(/(<\/p>\s*<p>)/gi, '</ins>$1<ins>');
}
);
// If only a few characters of a word have changed, don't display this as a replacement of the whole word, // 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 // but only of these specific characters
diffUnnormalized = diffUnnormalized.replace( diffUnnormalized = diffUnnormalized.replace(
@ -1919,28 +1915,43 @@ export class DiffService {
} }
); );
// <ins><li>...</li></ins> => <li class="insert">...</li> // If larger inserted HTML text contains block elements, we separate the inserted text into
// inline <ins> elements and "insert"-class-based block elements.
// <ins>...<div>...</div>...</ins> => <ins>...</ins><div class="insert">...</div><ins>...</ins>
diffUnnormalized = diffUnnormalized.replace( diffUnnormalized = diffUnnormalized.replace(
/<(ins|del)><(p|div|blockquote|li)([^>]*)>([\s\S]*)<\/\2><\/\1>/gi, /<(ins|del)>([\s\S]*?)<\/\1>/gi,
(whole: string, insDel: string, block: string, blockArguments: string, content: string): string => { (whole: string, insDel: string): string => {
// Prevent accidental matches like <ins><p>...</p>...</ins><ins>...<p></p></ins>
if (content.match(/<(ins|del)>/gi)) {
return whole;
}
// Add the CSS-class to the existing "class"-attribute, or add one
let newArguments = blockArguments;
const modificationClass = (insDel.toLowerCase() === 'ins' ? 'insert' : 'delete'); const modificationClass = (insDel.toLowerCase() === 'ins' ? 'insert' : 'delete');
if (newArguments.match(/class="/gi)) { return whole.replace(
// class="someclass" => class="someclass insert" /(<(p|div|blockquote|li)[^>]*>)([\s\S]*?)(<\/\2>)/gi,
newArguments = newArguments.replace(/(class\s*=\s*)(["'])([^\2]*)\2/gi, (whole2: string, opening: string, blockTag: string, content: string, closing: string): string => {
(classWhole: string, attr: string, para: string, classContent: string): string => { const modifiedTag = this.addClassToHtmlTag(opening, modificationClass);
return attr + para + classContent + ' ' + modificationClass + para; return '</' + insDel + '>' + modifiedTag + content + closing + '<' + insDel + '>';
} }
); );
} else {
newArguments += ' class="' + modificationClass + '"';
} }
return '<' + block + newArguments + '>' + content + '</' + block + '>'; );
// Cleanup leftovers from the operation above, when <ins></ins>-tags ore <ins> </ins>-tags are left
// around block tags. It should be safe to remove them and just leave the whitespaces.
diffUnnormalized = diffUnnormalized.replace(
/<(ins|del)>(\s*)<\/\1>/gi,
(whole: string, insDel: string, space: string): string => space
);
// <del></p><ins> Added text</p></ins> -> <ins> Added text</ins></p>
diffUnnormalized = diffUnnormalized.replace(
/<del><\/(p|div|blockquote|li)><\/del><ins>([\s\S]*?)<\/\1>(\s*)<\/ins>/gi,
(whole: string, blockTag: string, content: string, space: string): string => {
return '<ins>' + content + '</ins></' + blockTag + '>' + space;
}
);
// </p> </ins> -> </ins></p>
diffUnnormalized = diffUnnormalized.replace(
/(<\/(p|div|blockquote|li)>)(\s*)<\/(ins|del)>/gi,
(whole: string, ending: string, blockTag: string, space: string, insdel: string): string => {
return '</' + insdel + '>' + ending + space;
} }
); );