Showing Diff inline
This commit is contained in:
parent
fb646df1fd
commit
2958a401ad
@ -399,6 +399,14 @@ img {
|
|||||||
.motion-text li {
|
.motion-text li {
|
||||||
margin-left: 30px;
|
margin-left: 30px;
|
||||||
}
|
}
|
||||||
|
.motion-text ins {
|
||||||
|
color: green;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.motion-text del {
|
||||||
|
color: red;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
.motion-text.line-numbers-outside {
|
.motion-text.line-numbers-outside {
|
||||||
padding-left: 40px;
|
padding-left: 40px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -394,7 +394,9 @@ angular.module('OpenSlidesApp.core.pdf', [])
|
|||||||
"h4": ["font-size:24"],
|
"h4": ["font-size:24"],
|
||||||
"h5": ["font-size:22"],
|
"h5": ["font-size:22"],
|
||||||
"h6": ["font-size:20"],
|
"h6": ["font-size:20"],
|
||||||
"a": ["color:blue", "text-decoration:underline"]
|
"a": ["color:blue", "text-decoration:underline"],
|
||||||
|
"del": ["color:red", "text-decoration:line-through"],
|
||||||
|
"ins": ["color:green", "text-decoration:underline"]
|
||||||
},
|
},
|
||||||
classStyles = {
|
classStyles = {
|
||||||
"delete": ["color:red", "text-decoration:line-through"],
|
"delete": ["color:red", "text-decoration:line-through"],
|
||||||
@ -541,6 +543,8 @@ angular.module('OpenSlidesApp.core.pdf', [])
|
|||||||
case "u":
|
case "u":
|
||||||
case "em":
|
case "em":
|
||||||
case "i":
|
case "i":
|
||||||
|
case "ins":
|
||||||
|
case "del":
|
||||||
parseChildren(alreadyConverted, element, currentParagraph, styles.concat(elementStyles[nodeName]), diff_mode);
|
parseChildren(alreadyConverted, element, currentParagraph, styles.concat(elementStyles[nodeName]), diff_mode);
|
||||||
break;
|
break;
|
||||||
case "table":
|
case "table":
|
||||||
|
@ -280,7 +280,7 @@ angular.module('OpenSlidesApp.motions', [
|
|||||||
text = '';
|
text = '';
|
||||||
for (var i = 0; i < changes.length; i++) {
|
for (var i = 0; i < changes.length; i++) {
|
||||||
text += this.getTextBetweenChangeRecommendations(versionId, (i === 0 ? null : changes[i - 1]), changes[i], highlight);
|
text += this.getTextBetweenChangeRecommendations(versionId, (i === 0 ? null : changes[i - 1]), changes[i], highlight);
|
||||||
text += changes[i].format(this, versionId, highlight);
|
text += changes[i].getDiff(this, versionId);
|
||||||
}
|
}
|
||||||
text += this.getTextRemainderAfterLastChangeRecommendation(versionId, changes);
|
text += this.getTextRemainderAfterLastChangeRecommendation(versionId, changes);
|
||||||
break;
|
break;
|
||||||
@ -584,37 +584,16 @@ angular.module('OpenSlidesApp.motions', [
|
|||||||
saveStatus: function() {
|
saveStatus: function() {
|
||||||
this.DSSave();
|
this.DSSave();
|
||||||
},
|
},
|
||||||
format: function(motion, version, highlight) {
|
getDiff: function(motion, version, highlight) {
|
||||||
var lineLength = Config.get('motions_line_length').value,
|
var lineLength = Config.get('motions_line_length').value,
|
||||||
html = lineNumberingService.insertLineNumbers(motion.getVersion(version).text, lineLength);
|
html = lineNumberingService.insertLineNumbers(motion.getVersion(version).text, lineLength);
|
||||||
|
|
||||||
var data = diffService.extractRangeByLineNumbers(html, this.line_from, this.line_to),
|
var data = diffService.extractRangeByLineNumbers(html, this.line_from, this.line_to),
|
||||||
oldText = data.outerContextStart + data.innerContextStart +
|
oldText = data.outerContextStart + data.innerContextStart +
|
||||||
data.html + data.innerContextEnd + data.outerContextEnd,
|
data.html + data.innerContextEnd + data.outerContextEnd;
|
||||||
oldTextWithBreaks = lineNumberingService.insertLineNumbersNode(oldText, lineLength, highlight, this.line_from),
|
|
||||||
newTextWithBreaks = lineNumberingService.insertLineNumbersNode(this.text, lineLength, null, this.line_from);
|
|
||||||
|
|
||||||
for (var i = 0; i < oldTextWithBreaks.childNodes.length; i++) {
|
var diff = diffService.diff(oldText, this.text, lineLength, this.line_from);
|
||||||
diffService.addCSSClass(oldTextWithBreaks.childNodes[i], 'delete');
|
return lineNumberingService.insertLineNumbers(diff, lineLength, highlight, null, this.line_from);
|
||||||
}
|
|
||||||
for (i = 0; i < newTextWithBreaks.childNodes.length; i++) {
|
|
||||||
diffService.addCSSClass(newTextWithBreaks.childNodes[i], 'insert');
|
|
||||||
}
|
|
||||||
|
|
||||||
var mergedFragment = document.createDocumentFragment(),
|
|
||||||
el;
|
|
||||||
while (oldTextWithBreaks.firstChild) {
|
|
||||||
el = oldTextWithBreaks.firstChild;
|
|
||||||
oldTextWithBreaks.removeChild(el);
|
|
||||||
mergedFragment.appendChild(el);
|
|
||||||
}
|
|
||||||
while (newTextWithBreaks.firstChild) {
|
|
||||||
el = newTextWithBreaks.firstChild;
|
|
||||||
newTextWithBreaks.removeChild(el);
|
|
||||||
mergedFragment.appendChild(el);
|
|
||||||
}
|
|
||||||
|
|
||||||
return diffService._serializeDom(mergedFragment);
|
|
||||||
},
|
},
|
||||||
getType: function(original_full_html) {
|
getType: function(original_full_html) {
|
||||||
var lineLength = Config.get('motions_line_length').value,
|
var lineLength = Config.get('motions_line_length').value,
|
||||||
|
@ -524,22 +524,49 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi
|
|||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} html
|
||||||
|
* @returns {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._normalizeHtmlForDiff = function (html) {
|
||||||
|
// Convert all HTML tags to uppercase, strip trailing whitespaces
|
||||||
|
html = html.replace(/<[^>]+>/g, function (tag) {
|
||||||
|
return tag.toUpperCase();
|
||||||
|
});
|
||||||
|
|
||||||
|
var entities = {
|
||||||
|
' ': ' ',
|
||||||
|
'–': '-',
|
||||||
|
'ä': 'ä',
|
||||||
|
'ö': 'ö',
|
||||||
|
'ü': 'ü',
|
||||||
|
'Ä': 'Ä',
|
||||||
|
'Ö': 'Ö',
|
||||||
|
'Ü': 'Ü',
|
||||||
|
'ß': 'ß'
|
||||||
|
};
|
||||||
|
|
||||||
|
html = html.replace(/\s+<\/P>/gi, '</P>').replace(/\s+<\/DIV>/gi, '</DIV>').replace(/\s+<\/LI>/gi, '</LI>');
|
||||||
|
html = html.replace(/\s+<LI>/gi, '<LI>').replace(/<\/LI>\s+/gi, '</LI>');
|
||||||
|
html = html.replace(/\u00A0/g, ' ');
|
||||||
|
html = html.replace(/\u2013/g, '-');
|
||||||
|
for (var ent in entities) {
|
||||||
|
html = html.replace(new RegExp(ent, 'g'), entities[ent]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return html;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} htmlOld
|
* @param {string} htmlOld
|
||||||
* @param {string} htmlNew
|
* @param {string} htmlNew
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
this.detectReplacementType = function (htmlOld, htmlNew) {
|
this.detectReplacementType = function (htmlOld, htmlNew) {
|
||||||
// Convert all HTML tags to uppercase, strip trailing whitespaces
|
htmlOld = this._normalizeHtmlForDiff(htmlOld);
|
||||||
var normalizeHtml = function(html) {
|
htmlNew = this._normalizeHtmlForDiff(htmlNew);
|
||||||
html = html.replace(/<[^>]+>/g, function(tag) { return tag.toUpperCase(); });
|
|
||||||
html = html.replace(/\s+<\/P>/gi, '</P>').replace(/\s+<\/DIV>/gi, '</DIV>').replace(/\s+<\/LI>/gi, '</LI>');
|
|
||||||
html = html.replace(/\s+<LI>/gi, '<LI>').replace(/<\/LI>\s+/gi, '</LI>');
|
|
||||||
html = html.replace(/ /gi, ' ').replace(/\u00A0/g, ' '); // non-breaking spaces
|
|
||||||
return html;
|
|
||||||
};
|
|
||||||
htmlOld = normalizeHtml(htmlOld);
|
|
||||||
htmlNew = normalizeHtml(htmlNew);
|
|
||||||
|
|
||||||
if (htmlOld == htmlNew) {
|
if (htmlOld == htmlNew) {
|
||||||
return this.TYPE_REPLACEMENT;
|
return this.TYPE_REPLACEMENT;
|
||||||
@ -609,7 +636,7 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi
|
|||||||
if (classes.indexOf(className) == -1) {
|
if (classes.indexOf(className) == -1) {
|
||||||
classes.push(className);
|
classes.push(className);
|
||||||
}
|
}
|
||||||
node.setAttribute('class', classes);
|
node.setAttribute('class', classes.join(' '));
|
||||||
};
|
};
|
||||||
|
|
||||||
this.addDiffMarkup = function (originalHTML, newHTML, fromLine, toLine, diffFormatterCb) {
|
this.addDiffMarkup = function (originalHTML, newHTML, fromLine, toLine, diffFormatterCb) {
|
||||||
@ -651,6 +678,274 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi
|
|||||||
|
|
||||||
return this._serializeDom(mergedFragment, true);
|
return this._serializeDom(mergedFragment, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapted from http://ejohn.org/projects/javascript-diff-algorithm/
|
||||||
|
* by John Resig, MIT License
|
||||||
|
* @param {array} oldArr
|
||||||
|
* @param {array} newArr
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
this._diff = function (oldArr, newArr) {
|
||||||
|
var ns = {},
|
||||||
|
os = {},
|
||||||
|
i;
|
||||||
|
|
||||||
|
for (i = 0; i < newArr.length; i++) {
|
||||||
|
if (ns[newArr[i]] === undefined)
|
||||||
|
ns[newArr[i]] = {rows: [], o: null};
|
||||||
|
ns[newArr[i]].rows.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < oldArr.length; i++) {
|
||||||
|
if (os[oldArr[i]] === undefined)
|
||||||
|
os[oldArr[i]] = {rows: [], n: null};
|
||||||
|
os[oldArr[i]].rows.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in ns) {
|
||||||
|
if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
|
||||||
|
newArr[ns[i].rows[0]] = {text: newArr[ns[i].rows[0]], row: os[i].rows[0]};
|
||||||
|
oldArr[os[i].rows[0]] = {text: oldArr[os[i].rows[0]], row: ns[i].rows[0]};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < newArr.length - 1; i++) {
|
||||||
|
if (newArr[i].text !== null && newArr[i + 1].text === undefined && newArr[i].row + 1 < oldArr.length &&
|
||||||
|
oldArr[newArr[i].row + 1].text === undefined && newArr[i + 1] == oldArr[newArr[i].row + 1]) {
|
||||||
|
newArr[i + 1] = {text: newArr[i + 1], row: newArr[i].row + 1};
|
||||||
|
oldArr[newArr[i].row + 1] = {text: oldArr[newArr[i].row + 1], row: i + 1};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = newArr.length - 1; i > 0; i--) {
|
||||||
|
if (newArr[i].text !== null && newArr[i - 1].text === undefined && newArr[i].row > 0 &&
|
||||||
|
oldArr[newArr[i].row - 1].text === undefined && newArr[i - 1] == oldArr[newArr[i].row - 1]) {
|
||||||
|
newArr[i - 1] = {text: newArr[i - 1], row: newArr[i].row - 1};
|
||||||
|
oldArr[newArr[i].row - 1] = {text: oldArr[newArr[i].row - 1], row: i - 1};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {o: oldArr, n: newArr};
|
||||||
|
};
|
||||||
|
|
||||||
|
this._tokenizeHtml = function (str) {
|
||||||
|
var splitArrayEntries = function (arr, by, prepend) {
|
||||||
|
var newArr = [];
|
||||||
|
for (var i = 0; i < arr.length; i++) {
|
||||||
|
if (arr[i][0] == '<' && (by == " " || by == "\n")) {
|
||||||
|
// Don't split HTML tags
|
||||||
|
newArr.push(arr[i]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parts = arr[i].split(by);
|
||||||
|
if (parts.length == 1) {
|
||||||
|
newArr.push(arr[i]);
|
||||||
|
} else {
|
||||||
|
var j;
|
||||||
|
if (prepend) {
|
||||||
|
if (parts[0] !== '') {
|
||||||
|
newArr.push(parts[0]);
|
||||||
|
}
|
||||||
|
for (j = 1; j < parts.length; j++) {
|
||||||
|
newArr.push(by + parts[j]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (j = 0; j < parts.length - 1; j++) {
|
||||||
|
newArr.push(parts[j] + by);
|
||||||
|
}
|
||||||
|
if (parts[parts.length - 1] !== '') {
|
||||||
|
newArr.push(parts[parts.length - 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newArr;
|
||||||
|
};
|
||||||
|
var arr = splitArrayEntries([str], '<', true);
|
||||||
|
arr = splitArrayEntries(arr, '>', false);
|
||||||
|
arr = splitArrayEntries(arr, " ", false);
|
||||||
|
arr = splitArrayEntries(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));
|
||||||
|
}
|
||||||
|
console.log(str, pre, arr);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} oldStr
|
||||||
|
* @param {string} newStr
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
this._diffString = function (oldStr, newStr) {
|
||||||
|
oldStr = this._normalizeHtmlForDiff(oldStr.replace(/\s+$/, '').replace(/^\s+/, ''));
|
||||||
|
newStr = this._normalizeHtmlForDiff(newStr.replace(/\s+$/, '').replace(/^\s+/, ''));
|
||||||
|
|
||||||
|
var out = this._diff(this._tokenizeHtml(oldStr), this._tokenizeHtml(newStr));
|
||||||
|
var str = "";
|
||||||
|
var i;
|
||||||
|
|
||||||
|
if (out.n.length === 0) {
|
||||||
|
for (i = 0; i < out.o.length; i++) {
|
||||||
|
//this._outputcharcode('del', out.o[i]);
|
||||||
|
str += '<del>' + out.o[i] + "</del>";
|
||||||
|
}
|
||||||
|
} 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 += '<del>' + out.o[k] + "</del>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < out.n.length; i++) {
|
||||||
|
if (out.n[i].text === undefined) {
|
||||||
|
//this._outputcharcode('ins', out.n[i]);
|
||||||
|
str += '<ins>' + out.n[i] + "</ins>";
|
||||||
|
} else {
|
||||||
|
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 += '<del>' + out.o[j] + "</del>";
|
||||||
|
}
|
||||||
|
str += out.n[i].text + pre;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str.replace(/^\s+/g, '').replace(/\s+$/g, '').replace(/ {2,}/g, ' ');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} html
|
||||||
|
* @returns {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._diffDetectBrokenDiffHtml = function(html) {
|
||||||
|
var match = html.match(/<(ins|del)><[^>]*><\/(ins|del)>/gi);
|
||||||
|
return (match !== null && match.length > 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
this._diffParagraphs = function(oldText, newText, lineLength, firstLineNumber) {
|
||||||
|
var oldTextWithBreaks, newTextWithBreaks;
|
||||||
|
|
||||||
|
if (lineLength !== undefined) {
|
||||||
|
oldTextWithBreaks = lineNumberingService.insertLineNumbersNode(oldText, lineLength, null, firstLineNumber);
|
||||||
|
newTextWithBreaks = lineNumberingService.insertLineNumbersNode(newText, lineLength, null, firstLineNumber);
|
||||||
|
} else {
|
||||||
|
oldTextWithBreaks = document.createElement('div');
|
||||||
|
oldTextWithBreaks.innerHTML = oldText;
|
||||||
|
newTextWithBreaks = document.createElement('div');
|
||||||
|
newTextWithBreaks.innerHTML = newText;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < oldTextWithBreaks.childNodes.length; i++) {
|
||||||
|
this.addCSSClass(oldTextWithBreaks.childNodes[i], 'delete');
|
||||||
|
}
|
||||||
|
for (i = 0; i < newTextWithBreaks.childNodes.length; i++) {
|
||||||
|
this.addCSSClass(newTextWithBreaks.childNodes[i], 'insert');
|
||||||
|
}
|
||||||
|
|
||||||
|
var mergedFragment = document.createDocumentFragment(),
|
||||||
|
el;
|
||||||
|
while (oldTextWithBreaks.firstChild) {
|
||||||
|
el = oldTextWithBreaks.firstChild;
|
||||||
|
oldTextWithBreaks.removeChild(el);
|
||||||
|
mergedFragment.appendChild(el);
|
||||||
|
}
|
||||||
|
while (newTextWithBreaks.firstChild) {
|
||||||
|
el = newTextWithBreaks.firstChild;
|
||||||
|
newTextWithBreaks.removeChild(el);
|
||||||
|
mergedFragment.appendChild(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._serializeDom(mergedFragment);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function calculates the diff between two strings and tries to fix problems with the resulting HTML.
|
||||||
|
* If lineLength and firstLineNumber is given, line numbers will be returned es well
|
||||||
|
*
|
||||||
|
* @param {number} lineLength
|
||||||
|
* @param {number} firstLineNumber
|
||||||
|
* @param {string} htmlOld
|
||||||
|
* @param {string} htmlNew
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
this.diff = function (htmlOld, htmlNew, lineLength, firstLineNumber) {
|
||||||
|
var cacheKey = lineLength + ' ' + firstLineNumber + ' ' +
|
||||||
|
lineNumberingService.djb2hash(htmlOld) + lineNumberingService.djb2hash(htmlNew),
|
||||||
|
cached = diffCache.get(cacheKey);
|
||||||
|
if (!angular.isUndefined(cached)) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
var str = this._diffString(htmlOld, htmlNew),
|
||||||
|
diffUnnormalized = str.replace(/^\s+/g, '').replace(/\s+$/g, '').replace(/ {2,}/g, ' ')
|
||||||
|
.replace(/<\/ins><ins>/gi, '').replace(/<\/del><del>/gi, '');
|
||||||
|
|
||||||
|
diffUnnormalized = diffUnnormalized.replace(/<del>([a-z0-9,_-]* ?)<\/del><ins>([a-z0-9,_-]* ?)<\/ins>/gi, function (found, oldText, newText) {
|
||||||
|
var foundDiff = false, commonStart = '', commonEnd = '',
|
||||||
|
remainderOld = oldText, remainderNew = newText;
|
||||||
|
|
||||||
|
while (remainderOld.length > 0 && remainderNew.length > 0 && !foundDiff) {
|
||||||
|
if (remainderOld[0] == remainderNew[0]) {
|
||||||
|
commonStart += remainderOld[0];
|
||||||
|
remainderOld = remainderOld.substr(1);
|
||||||
|
remainderNew = remainderNew.substr(1);
|
||||||
|
} else {
|
||||||
|
foundDiff = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foundDiff = false;
|
||||||
|
while (remainderOld.length > 0 && remainderNew.length > 0 && !foundDiff) {
|
||||||
|
if (remainderOld[remainderOld.length - 1] == remainderNew[remainderNew.length - 1]) {
|
||||||
|
commonEnd = remainderOld[remainderOld.length - 1] + commonEnd;
|
||||||
|
remainderNew = remainderNew.substr(0, remainderNew.length - 1);
|
||||||
|
remainderOld = remainderOld.substr(0, remainderOld.length - 1);
|
||||||
|
} else {
|
||||||
|
foundDiff = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var out = commonStart;
|
||||||
|
if (remainderOld !== '') {
|
||||||
|
out += '<del>' + remainderOld + '</del>';
|
||||||
|
}
|
||||||
|
if (remainderNew !== '') {
|
||||||
|
out += '<ins>' + remainderNew + '</ins>';
|
||||||
|
}
|
||||||
|
out += commonEnd;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
});
|
||||||
|
|
||||||
|
var diff;
|
||||||
|
if (this._diffDetectBrokenDiffHtml(diffUnnormalized)) {
|
||||||
|
diff = this._diffParagraphs(htmlOld, htmlNew, lineLength, firstLineNumber);
|
||||||
|
} else {
|
||||||
|
var node = document.createElement('div');
|
||||||
|
node.innerHTML = diffUnnormalized;
|
||||||
|
diff = node.innerHTML;
|
||||||
|
|
||||||
|
if (lineLength !== undefined && firstLineNumber !== undefined) {
|
||||||
|
node = lineNumberingService.insertLineNumbersNode(diff, lineLength, null, firstLineNumber);
|
||||||
|
diff = node.innerHTML;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diffCache.put(cacheKey, diff);
|
||||||
|
return diff;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
|
|||||||
*
|
*
|
||||||
* Only the following inline elements are supported:
|
* Only the following inline elements are supported:
|
||||||
* - 'SPAN', 'A', 'EM', 'S', 'B', 'I', 'STRONG', 'U', 'BIG', 'SMALL', 'SUB', 'SUP', 'TT'
|
* - 'SPAN', 'A', 'EM', 'S', 'B', 'I', 'STRONG', 'U', 'BIG', 'SMALL', 'SUB', 'SUP', 'TT'
|
||||||
|
* - 'INS' and 'DEL' are supported, but line numbering does not affect the content of 'INS'-elements
|
||||||
*
|
*
|
||||||
* Only other inline elements are allowed within inline elements.
|
* Only other inline elements are allowed within inline elements.
|
||||||
* No constructs like <a...><div></div></a> are allowed. CSS-attributes like 'display: block' are ignored.
|
* No constructs like <a...><div></div></a> are allowed. CSS-attributes like 'display: block' are ignored.
|
||||||
@ -37,11 +38,15 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
|
|||||||
|
|
||||||
this._isInlineElement = function (node) {
|
this._isInlineElement = function (node) {
|
||||||
var inlineElements = [
|
var inlineElements = [
|
||||||
'SPAN', 'A', 'EM', 'S', 'B', 'I', 'STRONG', 'U', 'BIG', 'SMALL', 'SUB', 'SUP', 'TT'
|
'SPAN', 'A', 'EM', 'S', 'B', 'I', 'STRONG', 'U', 'BIG', 'SMALL', 'SUB', 'SUP', 'TT', 'INS', 'DEL'
|
||||||
];
|
];
|
||||||
return (inlineElements.indexOf(node.nodeName) > -1);
|
return (inlineElements.indexOf(node.nodeName) > -1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this._isIgnoredByLineNumbering = function (node) {
|
||||||
|
return (node.nodeName == 'INS');
|
||||||
|
};
|
||||||
|
|
||||||
this._isOsLineBreakNode = function (node) {
|
this._isOsLineBreakNode = function (node) {
|
||||||
var isLineBreak = false;
|
var isLineBreak = false;
|
||||||
if (node && node.nodeType === ELEMENT_NODE && node.nodeName == 'BR' && node.hasAttribute('class')) {
|
if (node && node.nodeType === ELEMENT_NODE && node.nodeName == 'BR' && node.hasAttribute('class')) {
|
||||||
@ -304,7 +309,7 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
|
|||||||
} else if (oldChildren[i].nodeType == ELEMENT_NODE) {
|
} else if (oldChildren[i].nodeType == ELEMENT_NODE) {
|
||||||
var firstword = this._lengthOfFirstInlineWord(oldChildren[i]),
|
var firstword = this._lengthOfFirstInlineWord(oldChildren[i]),
|
||||||
overlength = ((this._currentInlineOffset + firstword) > length && this._currentInlineOffset > 0);
|
overlength = ((this._currentInlineOffset + firstword) > length && this._currentInlineOffset > 0);
|
||||||
if (overlength && this._isInlineElement(oldChildren[i])) {
|
if (overlength && this._isInlineElement(oldChildren[i]) && !this._isIgnoredByLineNumbering(oldChildren[i])) {
|
||||||
this._currentInlineOffset = 0;
|
this._currentInlineOffset = 0;
|
||||||
node.appendChild(this._createLineBreak());
|
node.appendChild(this._createLineBreak());
|
||||||
node.appendChild(this._createLineNumber());
|
node.appendChild(this._createLineNumber());
|
||||||
@ -327,7 +332,9 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
|
|||||||
if (node.nodeType !== ELEMENT_NODE) {
|
if (node.nodeType !== ELEMENT_NODE) {
|
||||||
throw 'This method may only be called for ELEMENT-nodes: ' + node.nodeValue;
|
throw 'This method may only be called for ELEMENT-nodes: ' + node.nodeValue;
|
||||||
}
|
}
|
||||||
if (this._isInlineElement(node)) {
|
if (this._isIgnoredByLineNumbering(node)) {
|
||||||
|
return node;
|
||||||
|
} else if (this._isInlineElement(node)) {
|
||||||
return this._insertLineNumbersToInlineNode(node, length, highlight);
|
return this._insertLineNumbersToInlineNode(node, length, highlight);
|
||||||
} else {
|
} else {
|
||||||
var newLength = this._calcBlockNodeLength(node, length);
|
var newLength = this._calcBlockNodeLength(node, length);
|
||||||
@ -357,6 +364,13 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
|
|||||||
return root.innerHTML;
|
return root.innerHTML;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} html
|
||||||
|
* @param {number} lineLength
|
||||||
|
* @param {number} highlight - optional
|
||||||
|
* @param {number} firstLine
|
||||||
|
*/
|
||||||
this.insertLineNumbersNode = function (html, lineLength, highlight, firstLine) {
|
this.insertLineNumbersNode = function (html, lineLength, highlight, firstLine) {
|
||||||
var root = document.createElement('div');
|
var root = document.createElement('div');
|
||||||
root.innerHTML = html;
|
root.innerHTML = html;
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="motion-text motion-text-diff line-numbers-{{ lineNumberMode }}"
|
<div class="motion-text motion-text-diff line-numbers-{{ lineNumberMode }}"
|
||||||
ng-bind-html="change.format(motion, version, highlight) | trusted"></div>
|
ng-bind-html="change.getDiff(motion, version, highlight) | trusted"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -287,4 +287,53 @@ describe('linenumbering', function () {
|
|||||||
expect(calculatedType).toBe(diffService.TYPE_REPLACEMENT);
|
expect(calculatedType).toBe(diffService.TYPE_REPLACEMENT);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('the core diff algorithm', function() {
|
||||||
|
it('acts as documented by the official documentation', function () {
|
||||||
|
var before = "The red brown fox jumped over the rolling log.",
|
||||||
|
after = "The brown spotted fox leaped over the rolling log.";
|
||||||
|
var diff = diffService.diff(before, after);
|
||||||
|
expect(diff).toBe('The <del>red </del>brown <ins>spotted </ins>fox <del>jum</del><ins>lea</ins>ped over the rolling log.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores changing cases in HTML tags', function () {
|
||||||
|
var before = "The <strong>brown</strong> spotted fox jumped over the rolling log.",
|
||||||
|
after = "The <STRONG>brown</STRONG> spotted fox leaped over the rolling log.";
|
||||||
|
var diff = diffService.diff(before, after);
|
||||||
|
|
||||||
|
expect(diff).toBe('The <strong>brown</strong> spotted fox <del>jum</del><ins>lea</ins>ped over the rolling log.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('merges multiple inserts and deletes', function () {
|
||||||
|
var before = "Test1 Test2 Test3 Test4 Test5 Test9",
|
||||||
|
after = "Test1 Test6 Test7 Test8 Test9";
|
||||||
|
var diff = diffService.diff(before, after);
|
||||||
|
|
||||||
|
expect(diff).toBe('Test1 <del>Test2 Test3 Test4 Test5 </del><ins>Test6 Test7 Test8 </ins>Test9');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects insertions and deletions in a word (1)', function () {
|
||||||
|
var before = "Test1 Test2 Test3 Test4 Test5 Test6 Test7",
|
||||||
|
after = "Test1 Test Test3 Test4addon Test5 Test6 Test7";
|
||||||
|
var diff = diffService.diff(before, after);
|
||||||
|
|
||||||
|
expect(diff).toBe('Test1 Test<del>2</del> Test3 Test4<ins>addon</ins> Test5 Test6 Test7');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects insertions and deletions in a word (2)', function () {
|
||||||
|
var before = "Test Test",
|
||||||
|
after = "Test Testappend";
|
||||||
|
var diff = diffService.diff(before, after);
|
||||||
|
|
||||||
|
expect(diff).toBe('Test Test<ins>append</ins>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cannot handle changing CSS-classes', function () {
|
||||||
|
var before = "<p class='p1'>Test1 Test2</p>",
|
||||||
|
after = "<p class='p2'>Test1 Test2</p>";
|
||||||
|
var diff = diffService.diff(before, after);
|
||||||
|
|
||||||
|
expect(diff).toBe("<P class=\"p1 delete\">Test1 Test2</P><P class=\"p2 insert\">Test1 Test2</P>");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -126,6 +126,13 @@ describe('linenumbering', function () {
|
|||||||
expect(outHtml).toBe(noMarkup(1) + '<span>Lorem ' + brMarkup(2) + '<strong>ipsum ' + brMarkup(3) + 'dolor' + brMarkup(4) + 'sit</strong> ' + brMarkup(5) + 'amet</span>');
|
expect(outHtml).toBe(noMarkup(1) + '<span>Lorem ' + brMarkup(2) + '<strong>ipsum ' + brMarkup(3) + 'dolor' + brMarkup(4) + 'sit</strong> ' + brMarkup(5) + 'amet</span>');
|
||||||
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
|
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('counts within DEL nodes', function () {
|
||||||
|
var inHtml = "1234 <del>1234</del> 1234 1234";
|
||||||
|
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 10);
|
||||||
|
expect(outHtml).toBe(noMarkup(1) + '1234 <del>1234</del> ' + brMarkup(2) + '1234 1234');
|
||||||
|
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -228,4 +235,20 @@ describe('linenumbering', function () {
|
|||||||
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
|
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('line numbering in regard to the inline diff', function() {
|
||||||
|
it('does not count within INS nodes', function () {
|
||||||
|
var inHtml = "1234 <ins>1234</ins> 1234 1234";
|
||||||
|
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 10);
|
||||||
|
expect(outHtml).toBe(noMarkup(1) + '1234 <ins>1234</ins> 1234 ' + brMarkup(2) + '1234');
|
||||||
|
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not create a new line for a trailing INS', function () {
|
||||||
|
var inHtml = "<p>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, consetetur<ins>dsfsdf23</ins></p>";
|
||||||
|
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 80);
|
||||||
|
expect(outHtml).toBe('<p>' + 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, consetetur<ins>dsfsdf23</ins></p>');
|
||||||
|
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user