Marks split list items with CSS-classes and hides the bullet points - fixes #3269
This commit is contained in:
parent
3c747c01e2
commit
7b62c60350
@ -34,6 +34,8 @@ Motions:
|
||||
- Added config value for customize sorting of category list in
|
||||
pdf/docx export [#3329].
|
||||
- Added config value for pagenumber alignment in PDF [#3327].
|
||||
- Bugfix: Several bugfixes regarding splitting list items in
|
||||
change recommendations [#3288]
|
||||
|
||||
Users:
|
||||
- User without permission to see users can now see agenda item speakers,
|
||||
|
@ -109,6 +109,18 @@ h4 {
|
||||
p {
|
||||
padding-bottom: 14px;
|
||||
}
|
||||
p.os-split-after {
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
p.os-split-before {
|
||||
padding-top: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
ul.os-split-after, ol.os-split-after {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
@ -607,6 +619,10 @@ img {
|
||||
|
||||
/** Diff view */
|
||||
|
||||
.motion-text-holder li.os-split-before {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.change-recommendation-overview {
|
||||
background-color: #eee;
|
||||
border: solid 1px #ddd;
|
||||
@ -657,19 +673,24 @@ img {
|
||||
border: solid 1px #eee;
|
||||
border-radius: 3px;
|
||||
margin-bottom: 0;
|
||||
margin-top: -24px;
|
||||
padding-top: 0;
|
||||
padding-right: 155px;
|
||||
}
|
||||
.diff-box-transparent {
|
||||
margin-top: -24px;
|
||||
}
|
||||
.motion-text-with-diffs li.os-split-before {
|
||||
list-style-type: none;
|
||||
}
|
||||
.motion-text-with-diffs .original-text {
|
||||
min-height: 30px; /* Spacer between .diff-box, in case .original-text is empty */
|
||||
}
|
||||
.motion-text-with-diffs .original-text ul:last-child {
|
||||
.motion-text-with-diffs .original-text ul:last-child, .motion-text-with-diffs .original-text ol:last-child {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
.motion-text-with-diffs .original-text ul.os-split-after:last-child, .motion-text-with-diffs .original-text ol.os-split-after:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.motion-text-with-diffs.line-numbers-inline .diff-box, .motion-text-with-diffs.line-numbers-none .diff-box {
|
||||
margin-right: -220px;
|
||||
}
|
||||
@ -713,9 +734,6 @@ img {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.motion-text-diff p {
|
||||
padding-bottom: 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.motion-text-diff.line-numbers-outside .insert .os-line-number {
|
||||
display: none;
|
||||
|
@ -538,7 +538,9 @@ tr.elected td {
|
||||
.diff-box {
|
||||
margin-left: 25px;
|
||||
padding-top: 0;
|
||||
margin-top: -10px;
|
||||
}
|
||||
.motion-text-with-diffs li.os-split-before {
|
||||
list-style-type: none;
|
||||
}
|
||||
.motion-text-with-diffs .original-text {
|
||||
min-height: 30px; // Spacer between .diff-box, in case .original-text is empty
|
||||
@ -546,6 +548,16 @@ tr.elected td {
|
||||
.motion-text-with-diffs .original-text ul:last-child {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
.motion-text-with-diffs .original-text ul.os-split-after:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
ol.os-split-after, ul.os-split-after {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
p.os-split-after {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.motion-text-with-diffs.line-numbers-inline .diff-box, .motion-text-with-diffs.line-numbers-none .diff-box {
|
||||
margin-right: -220px;
|
||||
}
|
||||
@ -560,8 +572,11 @@ tr.elected td {
|
||||
color: green;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.motion-text-diff p {
|
||||
padding-bottom: 0;
|
||||
.motion-text-diff p.os-split-before {
|
||||
padding-top: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
.motion-text-diff p.os-split-after {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
@ -920,13 +920,13 @@ angular.module('OpenSlidesApp.core', [
|
||||
allowedContent:
|
||||
'h1 h2 h3 b i u strike sup sub strong em;' +
|
||||
'blockquote p pre table' +
|
||||
'(text-align-left,text-align-center,text-align-right,text-align-justify){text-align};' +
|
||||
'(text-align-left,text-align-center,text-align-right,text-align-justify,os-split-before,os-split-after){text-align};' +
|
||||
'a[!href];' +
|
||||
'img[!src,alt]{width,height,float};' +
|
||||
'tr th td caption;' +
|
||||
'li; ol[start]{list-style-type};' +
|
||||
'ul{list-style};' +
|
||||
'span[!*]{color,background-color}(os-line-number,line-number-*);' +
|
||||
'li(os-split-before,os-split-after); ol(os-split-before,os-split-after)[start]{list-style-type};' +
|
||||
'ul(os-split-before,os-split-after){list-style};' +
|
||||
'span[!*]{color,background-color}(os-split-before,os-split-after,os-line-number,line-number-*);' +
|
||||
'br(os-line-break);',
|
||||
|
||||
// there seems to be an error in CKeditor that parses spaces in extraPlugins as part of the plugin name.
|
||||
|
@ -308,6 +308,25 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi
|
||||
return fragment;
|
||||
};
|
||||
|
||||
/**
|
||||
* When a <li> with a os-split-before-class (set by extractRangeByLineNumbers) is edited when creating a
|
||||
* change recommendation and is split again in CKEditor, the second list items also gets that class.
|
||||
* This is not correct however, as the second one actually is a new list item. So we need to remove it again.
|
||||
*
|
||||
* @param {string} html
|
||||
* @returns {string}
|
||||
*/
|
||||
this.removeDuplicateClassesInsertedByCkeditor = function(html) {
|
||||
var fragment = this.htmlToFragment(html);
|
||||
var items = fragment.querySelectorAll('li.os-split-before');
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
if (!this._isFirstNonemptyChild(items[i].parentNode, items[i])) {
|
||||
this.removeCSSClass(items[i], 'os-split-before');
|
||||
}
|
||||
}
|
||||
return this._serializeDom(fragment, false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the HTML snippet between two given line numbers.
|
||||
*
|
||||
@ -338,8 +357,18 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi
|
||||
* - followingHtml: The HTML after the selected area
|
||||
* - followingHtmlStartSnippet: A HTML snippet that opens all HTML tags necessary to render "followingHtml"
|
||||
*
|
||||
*
|
||||
* In some cases, the returned HTML tags receive additional CSS classes, providing information both for
|
||||
* rendering it and for merging it again correctly.
|
||||
* - os-split-*: These classes are set for all HTML Tags that have been split into two by this process,
|
||||
* e.g. if the fromLine- or toLine-line-break was somewhere in the middle of this tag.
|
||||
* If a tag is split, the first one receives "os-split-after", and the second one "os-split-before".
|
||||
* For example, for the following string <p>Line 1<br>Line 2<br>Line 3</p>:
|
||||
* - extracting line 1 to 2 results in <p class="os-split-after">Line 1</p>
|
||||
* - extracting line 2 to 3 results in <p class="os-split-after os-split-before">Line 2</p>
|
||||
* - extracting line 3 to null/4 results in <p class="os-split-before">Line 3</p>
|
||||
*/
|
||||
this.extractRangeByLineNumbers = function(htmlIn, fromLine, toLine, debug) {
|
||||
this.extractRangeByLineNumbers = function(htmlIn, fromLine, toLine) {
|
||||
if (typeof(htmlIn) !== 'string') {
|
||||
throw 'Invalid call - extractRangeByLineNumbers expects a string as first argument';
|
||||
}
|
||||
@ -384,27 +413,51 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi
|
||||
toChildTraceAbs.shift();
|
||||
var followingHtml = this._serializePartialDomFromChild(fragment, toChildTraceAbs, false);
|
||||
|
||||
var currNode = fromLineNode.parentNode;
|
||||
var currNode = fromLineNode,
|
||||
isSplit = false;
|
||||
while (currNode.parentNode) {
|
||||
previousHtmlEndSnippet += '</' + currNode.nodeName + '>';
|
||||
if (!this._isFirstNonemptyChild(currNode.parentNode, currNode)) {
|
||||
isSplit = true;
|
||||
}
|
||||
if (isSplit) {
|
||||
this.addCSSClass(currNode.parentNode, 'os-split-before');
|
||||
}
|
||||
if (currNode.nodeName !== 'OS-LINEBREAK') {
|
||||
previousHtmlEndSnippet += '</' + currNode.nodeName + '>';
|
||||
}
|
||||
currNode = currNode.parentNode;
|
||||
}
|
||||
currNode = toLineNode.parentNode;
|
||||
|
||||
currNode = toLineNode;
|
||||
isSplit = false;
|
||||
while (currNode.parentNode) {
|
||||
followingHtmlStartSnippet = this._serializeTag(currNode) + followingHtmlStartSnippet;
|
||||
if (!this._isFirstNonemptyChild(currNode.parentNode, currNode)) {
|
||||
isSplit = true;
|
||||
}
|
||||
if (isSplit) {
|
||||
this.addCSSClass(currNode.parentNode, 'os-split-after');
|
||||
}
|
||||
followingHtmlStartSnippet = this._serializeTag(currNode.parentNode) + followingHtmlStartSnippet;
|
||||
currNode = currNode.parentNode;
|
||||
}
|
||||
|
||||
var found = false;
|
||||
isSplit = false;
|
||||
for (var i = 0; i < fromChildTraceRel.length && !found; i++) {
|
||||
if (fromChildTraceRel[i].nodeName === 'OS-LINEBREAK') {
|
||||
found = true;
|
||||
} else {
|
||||
if (!this._isFirstNonemptyChild(fromChildTraceRel[i], fromChildTraceRel[i + 1])) {
|
||||
isSplit = true;
|
||||
}
|
||||
if (fromChildTraceRel[i].nodeName === 'OL') {
|
||||
fakeOl = fromChildTraceRel[i].cloneNode(false);
|
||||
fakeOl.setAttribute('start', this._isWithinNthLIOfOL(fromChildTraceRel[i], fromLineNode));
|
||||
innerContextStart += this._serializeTag(fakeOl);
|
||||
} else {
|
||||
if (i < (fromChildTraceRel.length - 1) && isSplit) {
|
||||
this.addCSSClass(fromChildTraceRel[i], 'os-split-before');
|
||||
}
|
||||
innerContextStart += this._serializeTag(fromChildTraceRel[i]);
|
||||
}
|
||||
}
|
||||
@ -512,28 +565,28 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi
|
||||
|
||||
var lastNode = nodes1[nodes1.length - 1],
|
||||
firstNode = nodes2[0];
|
||||
if (lastNode.nodeType == TEXT_NODE && firstNode.nodeType == TEXT_NODE) {
|
||||
if (lastNode.nodeType === TEXT_NODE && firstNode.nodeType === TEXT_NODE) {
|
||||
var newTextNode = lastNode.ownerDocument.createTextNode(lastNode.nodeValue + firstNode.nodeValue);
|
||||
out.push(newTextNode);
|
||||
} else if (lastNode.nodeName == firstNode.nodeName) {
|
||||
} else if (lastNode.nodeName === firstNode.nodeName) {
|
||||
var newNode = lastNode.ownerDocument.createElement(lastNode.nodeName);
|
||||
for (i = 0; i < lastNode.attributes.length; i++) {
|
||||
var attr = lastNode.attributes[i];
|
||||
newNode.setAttribute(attr.name, attr.value);
|
||||
}
|
||||
|
||||
// Remove #text nodes inside of List elements, as they are confusing
|
||||
// Remove #text nodes inside of List elements (OL/UL), as they are confusing
|
||||
var lastChildren, firstChildren;
|
||||
if (lastNode.nodeName == 'OL' || lastNode.nodeName == 'UL') {
|
||||
if (lastNode.nodeName === 'OL' || lastNode.nodeName === 'UL') {
|
||||
lastChildren = [];
|
||||
firstChildren = [];
|
||||
for (i = 0; i < firstNode.childNodes.length; i++) {
|
||||
if (firstNode.childNodes[i].nodeType == ELEMENT_NODE) {
|
||||
if (firstNode.childNodes[i].nodeType === ELEMENT_NODE) {
|
||||
firstChildren.push(firstNode.childNodes[i]);
|
||||
}
|
||||
}
|
||||
for (i = 0; i < lastNode.childNodes.length; i++) {
|
||||
if (lastNode.childNodes[i].nodeType == ELEMENT_NODE) {
|
||||
if (lastNode.childNodes[i].nodeType === ELEMENT_NODE) {
|
||||
lastChildren.push(lastNode.childNodes[i]);
|
||||
}
|
||||
}
|
||||
@ -546,12 +599,13 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi
|
||||
for (i = 0; i < children.length; i++) {
|
||||
newNode.appendChild(children[i]);
|
||||
}
|
||||
|
||||
out.push(newNode);
|
||||
} else {
|
||||
if (lastNode.nodeName != 'TEMPLATE') {
|
||||
if (lastNode.nodeName !== 'TEMPLATE') {
|
||||
out.push(lastNode);
|
||||
}
|
||||
if (firstNode.nodeName != 'TEMPLATE') {
|
||||
if (firstNode.nodeName !== 'TEMPLATE') {
|
||||
out.push(firstNode);
|
||||
}
|
||||
}
|
||||
@ -570,9 +624,23 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi
|
||||
* @private
|
||||
*/
|
||||
this._normalizeHtmlForDiff = function (html) {
|
||||
// Convert all HTML tags to uppercase, strip trailing whitespaces
|
||||
html = html.replace(/<[^>]+>/g, function (tag) {
|
||||
return tag.toUpperCase();
|
||||
// Convert all HTML tags to uppercase, but leave the values of attributes unchanged
|
||||
html = html.replace(/<(\/?[a-z]*)( [^>]*)?>/ig, function (html, tag, attributes) {
|
||||
var tagNormalized = tag.toUpperCase();
|
||||
if (attributes === undefined) {
|
||||
attributes = "";
|
||||
}
|
||||
attributes = attributes.replace(/( [^"'=]*)(= *((["'])(.*?)\4))?/gi, function (attr, attrName, attrRest, attrRest2, quot, attrValue) {
|
||||
var attrNormalized = attrName.toUpperCase();
|
||||
if (attrRest !== undefined) {
|
||||
if (attrNormalized === ' CLASS') {
|
||||
attrValue = attrValue.split(' ').sort().join(' ');
|
||||
}
|
||||
attrNormalized += "=" + quot + attrValue + quot;
|
||||
}
|
||||
return attrNormalized;
|
||||
});
|
||||
return "<" + tagNormalized + attributes + ">";
|
||||
});
|
||||
|
||||
var entities = {
|
||||
@ -649,7 +717,7 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi
|
||||
followingFragment = this.htmlToFragment(followingHtml),
|
||||
newFragment = this.htmlToFragment(newHTML);
|
||||
|
||||
if (data.html.length > 0 && data.html.substr(-1) == ' ') {
|
||||
if (data.html.length > 0 && data.html.substr(-1) === ' ') {
|
||||
this._insertDanglingSpace(newFragment);
|
||||
}
|
||||
|
||||
@ -667,21 +735,46 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi
|
||||
el.parentNode.removeChild(el);
|
||||
}
|
||||
|
||||
var forgottenSplitClasses = mergedFragment.querySelectorAll(".os-split-before, .os-split-after");
|
||||
for (i = 0; i < forgottenSplitClasses.length; i++) {
|
||||
this.removeCSSClass(forgottenSplitClasses[i], 'os-split-before');
|
||||
this.removeCSSClass(forgottenSplitClasses[i], 'os-split-after');
|
||||
}
|
||||
|
||||
return this._serializeDom(mergedFragment, true);
|
||||
};
|
||||
|
||||
this.addCSSClass = function (node, className) {
|
||||
if (node.nodeType != ELEMENT_NODE) {
|
||||
if (node.nodeType !== ELEMENT_NODE) {
|
||||
return;
|
||||
}
|
||||
var classes = node.getAttribute('class');
|
||||
classes = (classes ? classes.split(' ') : []);
|
||||
if (classes.indexOf(className) == -1) {
|
||||
if (classes.indexOf(className) === -1) {
|
||||
classes.push(className);
|
||||
}
|
||||
node.setAttribute('class', classes.join(' '));
|
||||
};
|
||||
|
||||
this.removeCSSClass = function (node, className) {
|
||||
if (node.nodeType !== ELEMENT_NODE) {
|
||||
return;
|
||||
}
|
||||
var classes = node.getAttribute('class'),
|
||||
newClasses = [];
|
||||
classes = (classes ? classes.split(' ') : []);
|
||||
for (var i = 0; i < classes.length; i++) {
|
||||
if (classes[i] !== className) {
|
||||
newClasses.push(classes[i]);
|
||||
}
|
||||
}
|
||||
if (newClasses.length === 0) {
|
||||
node.removeAttribute('class');
|
||||
} else {
|
||||
node.setAttribute('class', newClasses.join(' '));
|
||||
}
|
||||
};
|
||||
|
||||
this.addDiffMarkup = function (originalHTML, newHTML, fromLine, toLine, diffFormatterCb) {
|
||||
var data = this.extractRangeByLineNumbers(originalHTML, fromLine, toLine),
|
||||
previousHtml = data.previousHtml + '<TEMPLATE></TEMPLATE>' + data.previousHtmlEndSnippet,
|
||||
@ -1151,19 +1244,14 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi
|
||||
|
||||
// Remove <del> tags that only delete line numbers
|
||||
diffUnnormalized = diffUnnormalized.replace(
|
||||
/<del>((<BR CLASS="OS-LINE-BREAK">)?<span[^>]+OS-LINE-NUMBER[^>]+?>\s*<\/span>)<\/del>/gi,
|
||||
/<del>((<BR CLASS="os-line-break">)?<span[^>]+os-line-number[^>]+?>\s*<\/span>)<\/del>/gi,
|
||||
function(found,tag) {
|
||||
return tag;
|
||||
}
|
||||
);
|
||||
|
||||
// Lowercase line number markup
|
||||
diffUnnormalized = diffUnnormalized.replace(
|
||||
/<BR CLASS="OS-LINE-BREAK">/gi,
|
||||
'<br class="os-line-break">'
|
||||
);
|
||||
diffUnnormalized = diffUnnormalized.replace(
|
||||
/<span[^>]+OS-LINE-NUMBER[^>]+?>\s*<\/span>/gi,
|
||||
/<span[^>]+os-line-number[^>]+?>\s*<\/span>/gi,
|
||||
function(found) {
|
||||
return found.toLowerCase().replace(/> <\/span/gi, "> </span");
|
||||
}
|
||||
|
@ -1550,9 +1550,10 @@ angular.module('OpenSlidesApp.motions.site', [
|
||||
'$scope',
|
||||
'MotionChangeRecommendation',
|
||||
'ChangeRecommendationForm',
|
||||
'diffService',
|
||||
'change',
|
||||
'ErrorMessage',
|
||||
function ($scope, MotionChangeRecommendation, ChangeRecommendationForm, change, ErrorMessage) {
|
||||
function ($scope, MotionChangeRecommendation, ChangeRecommendationForm, diffService, change, ErrorMessage) {
|
||||
$scope.alert = {};
|
||||
$scope.model = angular.copy(change);
|
||||
|
||||
@ -1560,6 +1561,7 @@ angular.module('OpenSlidesApp.motions.site', [
|
||||
$scope.formFields = ChangeRecommendationForm.getFormFields(change.line_from, change.line_to);
|
||||
// save motion
|
||||
$scope.save = function (change) {
|
||||
change.text = diffService.removeDuplicateClassesInsertedByCkeditor(change.text);
|
||||
// inject the changed change recommendation (copy) object back into DS store
|
||||
MotionChangeRecommendation.inject(change);
|
||||
// save changed change recommendation object on server
|
||||
@ -1607,6 +1609,7 @@ angular.module('OpenSlidesApp.motions.site', [
|
||||
$scope.formFields = ChangeRecommendationForm.getFormFields(lineFrom, lineTo);
|
||||
// save motion
|
||||
$scope.save = function (motion) {
|
||||
motion.text = diffService.removeDuplicateClassesInsertedByCkeditor(motion.text);
|
||||
MotionChangeRecommendation.create(motion).then(
|
||||
function(success) {
|
||||
$scope.closeThisDialog();
|
||||
|
@ -5,10 +5,10 @@ describe('linenumbering', function () {
|
||||
var diffService, baseHtml1, baseHtmlDom1, baseHtml2, baseHtmlDom2, baseHtml3, baseHtmlDom3,
|
||||
brMarkup = function (no) {
|
||||
return '<br class="os-line-break">' +
|
||||
'<span class="os-line-number line-number-' + no + '" data-line-number="' + no + '" contenteditable="false"> </span>';
|
||||
'<span class="line-number-' + no + ' os-line-number" data-line-number="' + no + '" contenteditable="false"> </span>';
|
||||
},
|
||||
noMarkup = function (no) {
|
||||
return '<span class="os-line-number line-number-' + no + '" data-line-number="' + no + '" contenteditable="false"> </span>';
|
||||
return '<span class="line-number-' + no + ' os-line-number" data-line-number="' + no + '" contenteditable="false"> </span>';
|
||||
};
|
||||
|
||||
beforeEach(inject(function (_diffService_, _lineNumberingService_) {
|
||||
@ -127,21 +127,52 @@ describe('linenumbering', function () {
|
||||
|
||||
it('extracts a single line', function () {
|
||||
var diff = diffService.extractRangeByLineNumbers(baseHtml1, 1, 2);
|
||||
expect(diff.html).toBe('<P>Line 1 ');
|
||||
expect(diff.html).toBe('<P class="os-split-after">Line 1 ');
|
||||
expect(diff.outerContextStart).toBe('');
|
||||
expect(diff.outerContextEnd).toBe('');
|
||||
});
|
||||
|
||||
it('extracts lines from nested UL/LI-structures', function () {
|
||||
var diff = diffService.extractRangeByLineNumbers(baseHtml1, 7, 9);
|
||||
expect(diff.html).toBe('Line 7</LI><LI class="li-class"><UL><LI>Level 2 LI 8</LI>');
|
||||
expect(diff.html).toBe('Line 7</LI><LI class="li-class os-split-after"><UL class="os-split-after"><LI>Level 2 LI 8</LI>');
|
||||
expect(diff.ancestor.nodeName).toBe('UL');
|
||||
expect(diff.outerContextStart).toBe('<UL class="ul-class">');
|
||||
expect(diff.outerContextStart).toBe('<UL class="ul-class os-split-before os-split-after">');
|
||||
expect(diff.outerContextEnd).toBe('</UL>');
|
||||
expect(diff.innerContextStart).toBe('<LI class="li-class">');
|
||||
expect(diff.innerContextStart).toBe('<LI class="li-class os-split-before">');
|
||||
expect(diff.innerContextEnd).toBe('</UL></LI>');
|
||||
expect(diff.previousHtmlEndSnippet).toBe('</LI></UL>');
|
||||
expect(diff.followingHtmlStartSnippet).toBe('<UL class="ul-class"><LI class="li-class"><UL>');
|
||||
expect(diff.followingHtmlStartSnippet).toBe('<UL class="ul-class os-split-before os-split-after"><LI class="li-class os-split-after"><UL class="os-split-after">');
|
||||
});
|
||||
|
||||
it('extracts lines from double-nested UL/LI-structures (1)', function () {
|
||||
var html = '<p>' + noMarkup(1) + 'Line 1</p>' +
|
||||
'<ul><li><p>' + noMarkup(2) + 'Line 2' + brMarkup(3) + 'Line 3' + brMarkup(4) + 'Line 5</p></li></ul>';
|
||||
var diff = diffService.extractRangeByLineNumbers(html, 3, 4);
|
||||
expect(diff.html).toBe('Line 3');
|
||||
expect(diff.ancestor.nodeName).toBe('P');
|
||||
expect(diff.outerContextStart).toBe('<UL class="os-split-before os-split-after"><LI class="os-split-before os-split-after"><P class="os-split-before os-split-after">');
|
||||
expect(diff.outerContextEnd).toBe('</P></LI></UL>');
|
||||
expect(diff.innerContextStart).toBe('');
|
||||
expect(diff.innerContextEnd).toBe('');
|
||||
expect(diff.previousHtmlEndSnippet).toBe('</P></LI></UL>');
|
||||
expect(diff.followingHtmlStartSnippet).toBe('<UL class="os-split-before os-split-after"><LI class="os-split-before os-split-after"><P class="os-split-before os-split-after">');
|
||||
});
|
||||
|
||||
it('extracts lines from double-nested UL/LI-structures (2)', function () {
|
||||
var html = '<p>' + noMarkup(1) + 'Line 1</p>' +
|
||||
'<ul><li><p>' + noMarkup(2) + 'Line 2' + brMarkup(3) + 'Line 3' + brMarkup(4) + '</p></li></ul>';
|
||||
var diff = diffService.extractRangeByLineNumbers(html, 2, 3);
|
||||
expect(diff.html).toBe('<UL class="os-split-after"><LI class="os-split-after"><P class="os-split-after">Line 2');
|
||||
expect(diff.outerContextStart).toBe('');
|
||||
expect(diff.outerContextEnd).toBe('');
|
||||
expect(diff.innerContextStart).toBe('');
|
||||
expect(diff.innerContextEnd).toBe('</P></LI></UL>');
|
||||
expect(diff.previousHtmlEndSnippet).toBe('');
|
||||
|
||||
// @TODO in followingHtmlStartSnippet, os-split-li is not set yet in this case.
|
||||
// This is not entirely correct, but as this field is never actually used, it's not bothering (yet)
|
||||
// This comment remains to document a potential pitfall in the future
|
||||
// expect(diff.followingHtmlStartSnippet).toBe('<UL><LI class="os-split-li"><P>');
|
||||
});
|
||||
|
||||
it('extracts a single line right before a UL/LI', function () {
|
||||
@ -155,14 +186,14 @@ describe('linenumbering', function () {
|
||||
it('extracts lines from a more complex example', function () {
|
||||
var diff = diffService.extractRangeByLineNumbers(baseHtml2, 6, 11);
|
||||
|
||||
expect(diff.html).toBe('owe. Dahoam gscheckate middn Spuiratz des is a gmahde Wiesn. Des is schee so Obazda san da, Haferl pfenningguat schoo griasd eich midnand.</P><UL><LI>Auffi Gamsbart nimma de Sepp Ledahosn Ohrwaschl um Godds wujn Wiesn Deandlgwand Mongdratzal! Jo leck mi Mamalad i daad mechad?</LI><LI>Do nackata Wurscht i hob di narrisch gean, Diandldrahn Deandlgwand vui huift vui woaß?</LI>');
|
||||
expect(diff.html).toBe('owe. Dahoam gscheckate middn Spuiratz des is a gmahde Wiesn. Des is schee so Obazda san da, Haferl pfenningguat schoo griasd eich midnand.</P><UL class="os-split-after"><LI>Auffi Gamsbart nimma de Sepp Ledahosn Ohrwaschl um Godds wujn Wiesn Deandlgwand Mongdratzal! Jo leck mi Mamalad i daad mechad?</LI><LI>Do nackata Wurscht i hob di narrisch gean, Diandldrahn Deandlgwand vui huift vui woaß?</LI>');
|
||||
expect(diff.ancestor.nodeName).toBe('#document-fragment');
|
||||
expect(diff.outerContextStart).toBe('');
|
||||
expect(diff.outerContextEnd).toBe('');
|
||||
expect(diff.innerContextStart).toBe('<P>');
|
||||
expect(diff.innerContextStart).toBe('<P class="os-split-before">');
|
||||
expect(diff.innerContextEnd).toBe('</UL>');
|
||||
expect(diff.previousHtmlEndSnippet).toBe('</P>');
|
||||
expect(diff.followingHtmlStartSnippet).toBe('<UL>');
|
||||
expect(diff.followingHtmlStartSnippet).toBe('<UL class="os-split-after">');
|
||||
});
|
||||
|
||||
it('extracts the end of a section', function () {
|
||||
@ -172,7 +203,7 @@ describe('linenumbering', function () {
|
||||
expect(diff.ancestor.nodeName).toBe('#document-fragment');
|
||||
expect(diff.outerContextStart).toBe('');
|
||||
expect(diff.outerContextEnd).toBe('');
|
||||
expect(diff.innerContextStart).toBe('<P>');
|
||||
expect(diff.innerContextStart).toBe('<P class="os-split-before">');
|
||||
expect(diff.innerContextEnd).toBe('');
|
||||
expect(diff.previousHtmlEndSnippet).toBe('</P>');
|
||||
expect(diff.followingHtml).toBe('');
|
||||
@ -184,7 +215,7 @@ describe('linenumbering', function () {
|
||||
|
||||
expect(diff.html).toBe('<LI>Line 3.3</LI></OL></LI><LI> Line 4</LI></OL>');
|
||||
expect(diff.ancestor.nodeName).toBe('#document-fragment');
|
||||
expect(diff.innerContextStart).toBe('<OL start="3"><LI><OL start="3">');
|
||||
expect(diff.innerContextStart).toBe('<OL class="os-split-before" start="3"><LI class="os-split-before"><OL class="os-split-before" start="3">');
|
||||
expect(diff.innerContextEnd).toBe('');
|
||||
expect(diff.previousHtmlEndSnippet).toBe('</OL></LI></OL>');
|
||||
});
|
||||
@ -192,9 +223,9 @@ describe('linenumbering', function () {
|
||||
it('preserves the numbering of OLs (2)', function () {
|
||||
var diff = diffService.extractRangeByLineNumbers(baseHtml3, 3, 5);
|
||||
|
||||
expect(diff.html).toBe('<LI><OL><LI>Line 3.1</LI><LI>Line 3.2</LI>');
|
||||
expect(diff.html).toBe('<LI class="os-split-after"><OL class="os-split-after"><LI>Line 3.1</LI><LI>Line 3.2</LI>');
|
||||
expect(diff.ancestor.nodeName).toBe('OL');
|
||||
expect(diff.outerContextStart).toBe('<OL start="3">');
|
||||
expect(diff.outerContextStart).toBe('<OL class="os-split-before os-split-after" start="3">');
|
||||
expect(diff.outerContextEnd).toBe('</OL>');
|
||||
});
|
||||
|
||||
@ -203,6 +234,23 @@ describe('linenumbering', function () {
|
||||
var diff = diffService.extractRangeByLineNumbers(inHtml, 1, 2);
|
||||
expect(diff.html).toBe('<H2>Looks like a <p> tag </p></H2>');
|
||||
});
|
||||
|
||||
it('marks split list items', function () {
|
||||
var html = '<ol><li>' + noMarkup(1) + 'Line 1' + brMarkup(2) + 'Line 2' + brMarkup(3) + 'Line 3</li></ol>'
|
||||
var diff = diffService.extractRangeByLineNumbers(html, 2, 3);
|
||||
expect(diff.outerContextStart.toLowerCase()).toBe('<ol class="os-split-before os-split-after" start="1"><li class="os-split-before os-split-after">');
|
||||
|
||||
diff = diffService.extractRangeByLineNumbers(html, 3, null);
|
||||
expect(diff.innerContextStart.toLowerCase()).toBe('<ol class="os-split-before" start="1"><li class="os-split-before">');
|
||||
});
|
||||
|
||||
it('does not mark the second list item as being split', function () {
|
||||
var html = '<ol><li>' + noMarkup(1) + 'Line 1</li><li>' + noMarkup(2) + 'Line 2' + brMarkup(3) + 'Line 3</li></ol>';
|
||||
var diff = diffService.extractRangeByLineNumbers(html, 2, 3);
|
||||
expect(diff.outerContextStart.toLowerCase()).toBe('<ol class="os-split-before os-split-after" start="2">');
|
||||
expect(diff.innerContextStart.toLowerCase()).toBe('');
|
||||
expect(diff.html.toLowerCase()).toBe('<li class="os-split-after">line 2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('merging two sections', function () {
|
||||
@ -319,7 +367,41 @@ describe('linenumbering', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('the core diff algorithm', function() {
|
||||
describe('diff normalization', function () {
|
||||
it('uppercases normal HTML tags', function () {
|
||||
var unnormalized = 'The <strong>brown</strong> fox',
|
||||
normalized = diffService._normalizeHtmlForDiff(unnormalized);
|
||||
expect(normalized).toBe('The <STRONG>brown</STRONG> fox')
|
||||
});
|
||||
|
||||
it('uppercases the names of html attributes, but not the values', function () {
|
||||
var unnormalized = 'This is our cool <a href="https://www.openslides.de/">home page</a> - have a look! ' +
|
||||
'<input type="checkbox" checked title=\'A title with "s\'>',
|
||||
normalized = diffService._normalizeHtmlForDiff(unnormalized);
|
||||
expect(normalized).toBe('This is our cool <A HREF="https://www.openslides.de/">home page</A> - have a look! ' +
|
||||
'<INPUT TYPE="checkbox" CHECKED TITLE=\'A title with "s\'>')
|
||||
});
|
||||
|
||||
it('strips unnecessary spaces', function () {
|
||||
var unnormalized = "<ul> <li>Test</li>\n</ul>",
|
||||
normalized = diffService._normalizeHtmlForDiff(unnormalized);
|
||||
expect(normalized).toBe('<UL><LI>Test</LI></UL>');
|
||||
});
|
||||
|
||||
it('normalizes html entities', function () {
|
||||
var unnormalized = "German characters like ß or ö",
|
||||
normalized = diffService._normalizeHtmlForDiff(unnormalized);
|
||||
expect(normalized).toBe('German characters like ß or ö');
|
||||
});
|
||||
|
||||
it('sorts css classes', function () {
|
||||
var unnormalized = "<P class='os-split-before os-split-after'>Test</P>",
|
||||
normalized = diffService._normalizeHtmlForDiff(unnormalized);
|
||||
expect(normalized).toBe("<P CLASS='os-split-after os-split-before'>Test</P>");
|
||||
});
|
||||
});
|
||||
|
||||
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.";
|
||||
@ -529,11 +611,11 @@ describe('linenumbering', function () {
|
||||
});
|
||||
|
||||
it('detects broken HTML and lowercases class names', function () {
|
||||
var before = "<p><span class=\"os-line-number line-number-3\" data-line-number=\"3\" contenteditable=\"false\"> </span>holen, da rief sie alle sieben herbei und sprach:</p>\n\n<p><span class=\"os-line-number line-number-4\" data-line-number=\"4\" contenteditable=\"false\"> </span><span style=\"color: #000000;\">\"Liebe Kinder, ich will hinaus in den Wald, seid auf der Hut vor dem Wolf! Wenn er <br class=\"os-line-break\"><span class=\"os-line-number line-number-5\" data-line-number=\"5\" contenteditable=\"false\"> </span>hereinkommt, frisst er euch alle mit Haut und Haar. Der Bösewicht verstellt sich oft, aber <br class=\"os-line-break\"><span class=\"os-line-number line-number-6\" data-line-number=\"6\" contenteditable=\"false\"> </span>an der rauen Stimme und an seinen schwarzen Füßen werdet ihr ihn schon erkennen.\"</span></p>\n\n<p><span class=\"os-line-number line-number-7\" data-line-number=\"7\" contenteditable=\"false\"> </span>Die Geißlein sagten: \" Liebe Mutter, wir wollen uns schon in acht nehmen, du kannst ohne </p>",
|
||||
var before = "<p><span class=\"line-number-3 os-line-number\" data-line-number=\"3\" contenteditable=\"false\"> </span>holen, da rief sie alle sieben herbei und sprach:</p>\n\n<p><span class=\"line-number-4 os-line-number\" data-line-number=\"4\" contenteditable=\"false\"> </span><span style=\"color: #000000;\">\"Liebe Kinder, ich will hinaus in den Wald, seid auf der Hut vor dem Wolf! Wenn er <br class=\"os-line-break\"><span class=\"line-number-5 os-line-number\" data-line-number=\"5\" contenteditable=\"false\"> </span>hereinkommt, frisst er euch alle mit Haut und Haar. Der Bösewicht verstellt sich oft, aber <br class=\"os-line-break\"><span class=\"line-number-6 os-line-number\" data-line-number=\"6\" contenteditable=\"false\"> </span>an der rauen Stimme und an seinen schwarzen Füßen werdet ihr ihn schon erkennen.\"</span></p>\n\n<p><span class=\"line-number-7 os-line-number\" data-line-number=\"7\" contenteditable=\"false\"> </span>Die Geißlein sagten: \" Liebe Mutter, wir wollen uns schon in acht nehmen, du kannst ohne </p>",
|
||||
after = "<p>holen, da rief sie alle sieben herbei und sprach:</p>\n\n<p><span style=\"color: #000000;\">Hello</span></p>\n\n<p><span style=\"color: #000000;\">World</span></p>\n\n<p><span style=\"color: #000000;\">Ya</span></p>\n\n<p>Die Geißlein sagten: \" Liebe Mutter, wir wollen uns schon in acht nehmen, du kannst ohne</p>";
|
||||
var diff = diffService.diff(before, after);
|
||||
expect(diff).toBe("<P class=\"delete\"><SPAN class=\"os-line-number line-number-3\" data-line-number=\"3\" contenteditable=\"false\"> </SPAN>holen, da rief sie alle sieben herbei und sprach:</P><DEL>\n\n</DEL>" +
|
||||
"<P class=\"delete\"><SPAN class=\"os-line-number line-number-4\" data-line-number=\"4\" contenteditable=\"false\"> </SPAN><SPAN>\"Liebe Kinder, ich will hinaus in den Wald, seid auf der Hut vor dem Wolf! Wenn er <BR class=\"os-line-break\"><SPAN class=\"os-line-number line-number-5\" data-line-number=\"5\" contenteditable=\"false\"> </SPAN>hereinkommt, frisst er euch alle mit Haut und Haar. Der Bösewicht verstellt sich oft, aber <BR class=\"os-line-break\"><SPAN class=\"os-line-number line-number-6\" data-line-number=\"6\" contenteditable=\"false\"> </SPAN>an der rauen Stimme und an seinen schwarzen Füßen werdet ihr ihn schon erkennen.\"</SPAN></P><DEL>\n\n</DEL><P class=\"delete\"><SPAN class=\"os-line-number line-number-7\" data-line-number=\"7\" contenteditable=\"false\"> </SPAN>Die Geißlein sagten: \" Liebe Mutter, wir wollen uns schon in acht nehmen, du kannst ohne </P>" +
|
||||
expect(diff).toBe("<P class=\"delete\"><SPAN class=\"line-number-3 os-line-number\" data-line-number=\"3\" contenteditable=\"false\"> </SPAN>holen, da rief sie alle sieben herbei und sprach:</P><DEL>\n\n</DEL>" +
|
||||
"<P class=\"delete\"><SPAN class=\"line-number-4 os-line-number\" data-line-number=\"4\" contenteditable=\"false\"> </SPAN><SPAN>\"Liebe Kinder, ich will hinaus in den Wald, seid auf der Hut vor dem Wolf! Wenn er <BR class=\"os-line-break\"><SPAN class=\"line-number-5 os-line-number\" data-line-number=\"5\" contenteditable=\"false\"> </SPAN>hereinkommt, frisst er euch alle mit Haut und Haar. Der Bösewicht verstellt sich oft, aber <BR class=\"os-line-break\"><SPAN class=\"line-number-6 os-line-number\" data-line-number=\"6\" contenteditable=\"false\"> </SPAN>an der rauen Stimme und an seinen schwarzen Füßen werdet ihr ihn schon erkennen.\"</SPAN></P><DEL>\n\n</DEL><P class=\"delete\"><SPAN class=\"line-number-7 os-line-number\" data-line-number=\"7\" contenteditable=\"false\"> </SPAN>Die Geißlein sagten: \" Liebe Mutter, wir wollen uns schon in acht nehmen, du kannst ohne </P>" +
|
||||
"<P class=\"insert\">holen, da rief sie alle sieben herbei und sprach:</P><INS>\n\n</INS>" +
|
||||
"<P class=\"insert\"><SPAN>Hello</SPAN></P><INS>\n\n</INS>" +
|
||||
"<P class=\"insert\"><SPAN>World</SPAN></P><INS>\n\n</INS>" +
|
||||
@ -554,5 +636,13 @@ describe('linenumbering', function () {
|
||||
inserted = diffService.addCSSClassToFirstTag(strIn, "newClass");
|
||||
expect(inserted).toBe("<ol start='2' class=\"my-old-class newClass\"><li>")
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
describe('removeDuplicateClassesInsertedByCkeditor', function () {
|
||||
it('removes additional classes', function () {
|
||||
var strIn = '<ul class="os-split-before os-split-after"><li class="os-split-before"><ul class="os-split-before os-split-after"><li class="os-split-before">...here it goes on</li><li class="os-split-before">This has been added</li></ul></li></ul>',
|
||||
cleaned = diffService.removeDuplicateClassesInsertedByCkeditor(strIn);
|
||||
expect(cleaned).toBe('<UL class="os-split-before os-split-after"><LI class="os-split-before"><UL class="os-split-before os-split-after"><LI class="os-split-before">...here it goes on</LI><LI>This has been added</LI></UL></LI></UL>');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user