Compare commits

...

14 Commits

Author SHA1 Message Date
Emanuel Schütze 3b6cebc4ee
Merge pull request #4023 from emanuelschuetze/bugfixes
Several bug fixes for 2.3.x
2018-11-20 13:54:23 +01:00
Emanuel Schütze 82f8c3fc26 Several bug fixes for 2.3.x
- PDF export: Added missing alignment for pdf header logo (right).
  Hide event data if heder logo left _and_ right is set.
- Fixed typo in set motion block multi action.
- Fixed motion slide: project diff mode by default (if selected in config)
- Catched some JS errors (for undefined motion objects).
- Order recommendations by id in motion detail view.
2018-11-20 13:45:45 +01:00
Emanuel Schütze 66a06e4ccc
Merge pull request #4020 from CatoTH/v2.3.x-bugfix-diff3
Diff-Bugfix
2018-11-19 15:22:47 +01:00
Tobias Hößl 31d8258750 Diff-Bugfix: be more consistent about marking up block elements by 'inserted' instead of using <ins>-tags 2018-11-19 15:05:24 +01:00
Emanuel Schütze 34498af9c5
Merge pull request #3992 from emanuelschuetze/agendaSort
Allow to hide internal items in agenda sort view.
2018-11-08 11:47:28 +01:00
Emanuel Schütze 48d18c40a7 Allow to hide internal items in agenda sort view. 2018-11-07 10:02:19 +01:00
Emanuel Schütze 1b6d8baeaf
Merge pull request #3946 from CatoTH/v2.3.x-Bugfix-Diff2
Diff-Bugfixes regarding replaced list items
2018-10-26 10:11:42 +02:00
Tobias Hößl 46727238c7 Diff-Bugfixes regarding replaced list items 2018-10-26 10:03:15 +02:00
Emanuel Schütze 709182119c
Merge pull request #3943 from CatoTH/v2.3.x-Bugfix-Diff
Diff Bugfix: inserted/deleted list items
2018-10-23 09:27:57 +02:00
Tobias Hößl 023f398875 Diff Bugfix: inserted/deleted list items 2018-10-23 09:21:00 +02:00
Emanuel Schütze 81bd123af0
Merge pull request #3942 from emanuelschuetze/fixAmendmentCSVExport
Fixed missing submitters in amendment csv export
2018-10-23 09:16:14 +02:00
Emanuel Schütze 40c8d8869a Fixed missing data in amendment csv export
- add submitters
- add recommendation
- strip html tags for old and new text in csv
2018-10-23 09:10:09 +02:00
Emanuel Schütze 07b75584cf
Merge pull request #3932 from emanuelschuetze/bugfixes2.3
Minor bugfixes for 2.3.1
2018-10-19 15:11:36 +02:00
Emanuel Schütze 85d7b2ebe6 Minor bugfixes for 2.3.1
- Fixed sorting of users in dropdowns for speakers, submitters and candidates.
- Fixed assignment ballot paper layout (more space required for global 'no').
- Fixed missing output of special poll values. Use new filter 'textOrNumber'
  to return decimal number or string (e.g. 'majority').
2018-10-19 14:07:06 +02:00
20 changed files with 268 additions and 136 deletions

View File

@ -4,6 +4,26 @@
https://openslides.org/
Version 2.3.1 (unreleased)
==========================
`Milestone <https://github.com/OpenSlides/OpenSlides/milestones/2.3.1>`_
Bugfixes:
- Fixed image browser in CKEditor plugin to show uploaded images [#3889].
- Fixed sorting of users in dropdowns for speakers, submitters and candidates [#3932].
- Fixed election ballot paper layout (more space required for global 'no') [#3932].
- 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 [#3943, #3946, #4020].
- Allow to hide internal items in agenda sort view [#3992].
- Several minor bug fixes [#4023], especially:
- Fixed motion slide: project diff mode by default (if selected in config)
- PDF export: Added missing alignment for pdf header logo (right).
- Fixed set motion block multi action.
Version 2.3 (2018-09-20)
========================
`Release notes <https://github.com/OpenSlides/OpenSlides/wiki/OpenSlides-2.3>`_ ·

View File

@ -33,10 +33,10 @@
<!-- Nested node template -->
<script type="text/ng-template" id="nodes_renderer.html">
<div ui-tree-handle ng-if="!item.item.is_hidden">
<div ui-tree-handle ng-if="item.item.is_public">
{{ item.item.getListViewTitle() }}
</div>
<div ui-tree-handle ng-if="item.item.is_hidden && showInternalItems" class="internal">
<div ui-tree-handle ng-if="!item.item.is_public && showInternalItems" class="internal">
<i class="fa fa-ban"></i> &nbsp;
{{ item.item.getListViewTitle() }}
</div>

View File

@ -112,7 +112,7 @@
<select chosen
ng-model="speakerSelectBox.selected"
ng-change="addSpeaker(speakerSelectBox.selected)"
ng-options="user.id as user.get_full_name() for user in users"
ng-options="user.id as user.get_full_name() for user in users | orderBy: 'full_name'"
search-contains="true"
placeholder-text-single="'Select or search a participant ...' | translate"
no-results-text="'No results available ...' | translate"

View File

@ -120,7 +120,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
if (printLabel) {
voteVal += voteObject.label + ': ';
}
voteVal += $filter('number')(voteObject.value, precision);
voteVal += $filter('textOrNumber')(voteObject.value, precision);
if (voteObject.percentStr) {
voteVal += ' ' + voteObject.percentStr;
@ -435,18 +435,18 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
var rowsperpage;
var sheetend;
if (poll.pollmethod == 'votes') {
if (poll.options.length <= 4) {
if (poll.options.length <= 3) {
sheetend = 105;
rowsperpage = 4;
} else if (poll.options.length <= 8) {
} else if (poll.options.length <= 5) {
sheetend = 140;
rowsperpage = 3;
} else if (poll.options.length <= 12) {
} else if (poll.options.length <= 11) {
sheetend = 210;
rowsperpage = 2;
}
else { //works untill ~30 people
sheetend = 418;
sheetend = 417;
rowsperpage = 1;
}
} else {
@ -460,7 +460,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
sheetend = 210;
rowsperpage = 2;
} else {
sheetend = 418;
sheetend = 417;
rowsperpage = 1;
}
}
@ -473,10 +473,10 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
widths: ['50%', '50%'],
body: createTableBody(rowsperpage, sheetend),
pageBreak: 'after'
},
layout: PDFLayout.getBallotLayoutLines(),
rowsperpage: rowsperpage
});
},
layout: PDFLayout.getBallotLayoutLines(),
rowsperpage: rowsperpage
});
}
// fill the last page only partially
var lastpage_ballots = amount - (fullpages * page_entries);

View File

@ -105,7 +105,7 @@
</div>
<div os-perms="assignments.can_nominate_other">
<select chosen ng-model="candidateSelectBox.selected" ng-change="addCandidate(candidateSelectBox.selected)"
ng-options="user.id as user.get_full_name() for user in users"
ng-options="user.id as user.get_full_name() for user in users | orderBy: 'full_name'"
search-contains="true"
placeholder-text-single="'Select or search a participant ...' | translate"
no-results-text="'No results available ...' | translate"
@ -234,7 +234,7 @@
<div ng-init="votes = option.getVotes()">
<div ng-repeat="vote in votes">
<span ng-if="poll.pollmethod == 'yna' || poll.pollmethod == 'yn'">{{ vote.label }}:</span>
{{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
{{ vote.value | textOrNumber:votesPrecision }} {{ vote.percentStr }}
<div ng-if="vote.percentNumber >= 0">
<uib-progressbar ng-if="$index == 0" value="vote.percentNumber" type="success"></uib-progressbar>
<uib-progressbar ng-if="$index == 1" value="vote.percentNumber" type="danger"></uib-progressbar>
@ -255,31 +255,31 @@
<td>
<translate>Abstain</translate>
<td>
{{ poll.getVote('votesabstain').value | number:votesPrecision }}
{{ poll.getVote('votesabstain').value | textOrNumber:votesPrecision }}
{{ poll.getVote('votesabstain').percentStr }}
<tr ng-if="poll.pollmethod === 'votes'">
<td>
<translate>No</translate>
<td>
{{ poll.getVote('votesno').value | number:votesPrecision }}
{{ poll.getVote('votesno').value | textOrNumber:votesPrecision }}
{{ poll.getVote('votesno').percentStr }}
<tr>
<td>
<translate>Valid ballots</translate>
<td>
{{ poll.getVote('votesvalid').value | number:votesPrecision }}
{{ poll.getVote('votesvalid').value | textOrNumber:votesPrecision }}
{{ poll.getVote('votesvalid').percentStr }}
<tr>
<td>
<translate>Invalid ballots</translate>
<td>
{{ poll.getVote('votesinvalid').value | number:votesPrecision }}
{{ poll.getVote('votesinvalid').value | textOrNumber:votesPrecision }}
{{ poll.getVote('votesinvalid').percentStr }}
<tr class="total bg-info">
<td>
<translate>Casted ballots</translate>
<td>
{{ poll.getVote('votescast').value | number:votesPrecision }}
{{ poll.getVote('votescast').value | textOrNumber:votesPrecision }}
{{ poll.getVote('votescast').percentStr }}
</table>

View File

@ -49,16 +49,16 @@
<div ng-init="votes = option.getVotes()">
<div ng-show="poll.pollmethod == 'yna' || poll.pollmethod == 'yn'">
<span ng-show="poll.pollmethod == 'yna'">
{{ votes[0].label | translate }}: {{ votes[0].value | number:votesPrecision }} {{ votes[0].percentStr }}<br>
{{ votes[1].label | translate }}: {{ votes[1].value | number:votesPrecision }} {{ votes[1].percentStr }}<br>
{{ votes[2].label | translate }}: {{ votes[2].value | number:votesPrecision }} {{ votes[2].percentStr }}</span>
{{ votes[0].label | translate }}: {{ votes[0].value | textOrNumber:votesPrecision }} {{ votes[0].percentStr }}<br>
{{ votes[1].label | translate }}: {{ votes[1].value | textOrNumber:votesPrecision }} {{ votes[1].percentStr }}<br>
{{ votes[2].label | translate }}: {{ votes[2].value | textOrNumber:votesPrecision }} {{ votes[2].percentStr }}</span>
<span ng-show="poll.pollmethod == 'yn'">
{{ votes[0].label | translate }}: {{ votes[0].value | number:votesPrecision }} {{ votes[0].percentStr }}<br>
{{ votes[1].label | translate }}: {{ votes[1].value | number:votesPrecision }} {{ votes[1].percentStr }}</span>
{{ votes[0].label | translate }}: {{ votes[0].value | textOrNumber:votesPrecision }} {{ votes[0].percentStr }}<br>
{{ votes[1].label | translate }}: {{ votes[1].value | textOrNumber:votesPrecision }} {{ votes[1].percentStr }}</span>
</div>
<div ng-show="poll.pollmethod == 'votes'">
<div ng-repeat="vote in votes">
{{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
{{ vote.value | textOrNumber:votesPrecision }} {{ vote.percentStr }}
<div style="float:right; width:200px;" ng-if="vote.percentNumber >= 0">
<uib-progressbar value="vote.percentNumber" type="success"></uib-progressbar>
</div>
@ -71,29 +71,29 @@
<td>
<translate>Abstain</translate>
<td ng-init="vote = poll.getVote('votesabstain')">
{{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
{{ vote.value | textOrNumber:votesPrecision }} {{ vote.percentStr }}
<tr class="total" ng-if="poll.has_votes && poll.pollmethod === 'votes' && poll.getVote('votesno').value !== null">
<td>
<translate>No</translate>
<td ng-init="vote = poll.getVote('votesno')">
{{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
{{ vote.value | textOrNumber:votesPrecision }} {{ vote.percentStr }}
<tr class="total" ng-if="poll.has_votes && poll.getVote('votesvalid').value !== null">
<td>
<translate>Valid ballots</translate>
<td ng-init="vote = poll.getVote('votesvalid')">
{{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
{{ vote.value | textOrNumber:votesPrecision }} {{ vote.percentStr }}
<tr class="total" ng-if="poll.has_votes && poll.getVote('votesinvalid').value !== null">
<td>
<translate>Invalid ballots</translate>
<td ng-init="vote = poll.getVote('votesinvalid')">
{{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
{{ vote.value | textOrNumber:votesPrecision }} {{ vote.percentStr }}
<tr class="total bg-info" ng-if="poll.has_votes && poll.getVote('votescast').value !== null">
<td>
<translate>Casted ballots</translate>
<td ng-init="vote = poll.getVote('votescast')">
{{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
{{ vote.value | textOrNumber:votesPrecision }} {{ vote.percentStr }}
</table>
</div>

View File

@ -1675,6 +1675,20 @@ angular.module('OpenSlidesApp.core', [
}
])
// Returns number with decimal places or (if not a number) string (e.g. majority)
.filter('textOrNumber', [
'$filter',
function ($filter) {
return function(input, votesPrecision) {
if(isNaN(input)) {
return input;
} else {
return $filter('number')(input, votesPrecision);
}
};
}
])
// Make sure that the DS factories are loaded by making them a dependency
.run([
'ChatMessage',

View File

@ -197,15 +197,20 @@ angular.module('OpenSlidesApp.core.pdf', [])
});
}
var line1 = [
Config.translate(Config.get('general_event_name').value),
Config.translate(Config.get('general_event_description').value)
].filter(Boolean).join(' ');
var line2 = [
Config.get('general_event_location').value,
Config.get('general_event_date').value
].filter(Boolean).join(', ');
var text = [line1, line2].join('\n');
var text;
if (logoHeaderLeftUrl && logoHeaderRightUrl) {
text = '';
} else {
var line1 = [
Config.translate(Config.get('general_event_name').value),
Config.translate(Config.get('general_event_description').value)
].filter(Boolean).join(' ');
var line2 = [
Config.get('general_event_location').value,
Config.get('general_event_date').value
].filter(Boolean).join(', ');
text = [line1, line2].join('\n');
}
columns.push({
text: text,
fontSize: 10,
@ -220,6 +225,7 @@ angular.module('OpenSlidesApp.core.pdf', [])
columns.push({
image: logoHeaderRightUrl,
fit: [180, 40],
alignment: 'right',
width: '20%'
});
}

View File

@ -29,6 +29,9 @@ angular.module('OpenSlidesApp.motions.csv', [])
if (params.include.motionBlock) {
headerline.push('Motion block');
}
if (params.include.recommendation) {
headerline.push('Recommendation');
}
return _.map(headerline, function (entry) {
return gettextCatalog.getString(entry);
});
@ -104,6 +107,11 @@ angular.module('OpenSlidesApp.motions.csv', [])
row.push('"' + blockTitle + '"');
}
// Recommendation
if (params.include.recommendation) {
var recommendation = motion.recommendation ? motion.getRecommendationName() : '';
row.push('"' + recommendation + '"');
}
csvRows.push(row);
});
CsvDownload(csvRows, params.filename);
@ -130,13 +138,14 @@ angular.module('OpenSlidesApp.motions.csv', [])
])
.factory('AmendmentCsvExport', [
'$filter',
'gettextCatalog',
'CsvDownload',
'lineNumberingService',
function (gettextCatalog, CsvDownload, lineNumberingService) {
function ($filter, gettextCatalog, CsvDownload, lineNumberingService) {
var makeHeaderline = function () {
var headerline = ['Identifier', 'Submitters', 'Category', 'Motion block',
'Leadmotion', 'Line', 'Old text', 'New text'];
'Leadmotion', 'Line', 'Old text', 'New text', 'Recommendation'];
return _.map(headerline, function (entry) {
return gettextCatalog.getString(entry);
});
@ -148,12 +157,17 @@ angular.module('OpenSlidesApp.motions.csv', [])
];
_.forEach(amendments, function (amendment) {
var row = [];
// Identifier and title
// Identifier
row.push('"' + amendment.identifier !== null ? amendment.identifier : '' + '"');
// Submitters
var submitters = [];
angular.forEach(amendment.submitters, function(user) {
var user_short_name = [user.title, user.first_name, user.last_name].join(' ').trim();
_.forEach($filter('orderBy')(amendment.submitters, 'weight'), function (user) {
var user_short_name = [
user.user.title,
user.user.first_name,
user.user.last_name
].join(' ').trim();
submitters.push(user_short_name);
});
row.push('"' + submitters.join('; ') + '"');
@ -191,14 +205,23 @@ angular.module('OpenSlidesApp.motions.csv', [])
//row.push('"' + p_new.text.html + '"');
// Work around: Export the full paragraphs instead of changed lines
row.push('"' + amendment.getAmendmentParagraphsByMode('original', null, false)[0].text + '"');
row.push('"' + amendment.getAmendmentParagraphsByMode('changed', null, false)[0].text + '"');
// Remove all HTML tags from old and new text
var oldText = document.createElement("DIV");
oldText.innerHTML = amendment.getAmendmentParagraphsByMode('original', null, false)[0].text;
var newText = document.createElement("DIV");
newText.innerHTML = amendment.getAmendmentParagraphsByMode('changed', null, false)[0].text;
row.push('"' + (oldText.textContent || oldText.innerText) + '"');
row.push('"' + (newText.textContent || newText.innerText) + '"');
} else {
row.push('""');
row.push('""');
row.push('"' + amendment.getText() + '"');
}
// Recommendation
var recommendation = amendment.recommendation ? amendment.getRecommendationName() : '';
row.push('"' + recommendation + '"');
csvRows.push(row);
});
CsvDownload(csvRows, 'amendments-export.csv');

View File

@ -1257,13 +1257,14 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi
if (lineLength !== undefined) {
oldTextWithBreaks = lineNumberingService.insertLineNumbersNode(oldText, lineLength, null, firstLineNumber);
newTextWithBreaks = lineNumberingService.insertLineNumbersNode(newText, lineLength, null, firstLineNumber);
newText = lineNumberingService.insertLineBreaksWithoutNumbers(newText, lineLength);
} else {
oldTextWithBreaks = document.createElement('div');
oldTextWithBreaks.innerHTML = oldText;
newTextWithBreaks = document.createElement('div');
newTextWithBreaks.innerHTML = newText;
}
newText = newText.replace(/^\s+/g, '').replace(/\s+$/g, '');
newTextWithBreaks = document.createElement('div');
newTextWithBreaks.innerHTML = newText;
for (var i = 0; i < oldTextWithBreaks.childNodes.length; i++) {
currChild = oldTextWithBreaks.childNodes[i];
@ -1336,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
@ -1468,36 +1495,9 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi
}
);
// Merging individual insert/delete statements into bigger blocks
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,
function(match, whiteBefore, inner, tagInner, whiteAfter) {
return whiteBefore +
inner
.replace(/<p( [^>]*)?>/gi, function(match) {
return match + "<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,
function(match) {
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,
// but only of these specific characters
diffUnnormalized = diffUnnormalized.replace(/<del>([a-z0-9,_-]* ?)<\/del><ins>([a-z0-9,_-]* ?)<\/ins>/gi, function (found, oldText, newText) {
@ -1545,6 +1545,36 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi
}
);
// 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>
var diffService = this;
diffUnnormalized = diffUnnormalized.replace(/<(ins|del)>([\s\S]*?)<\/\1>/gi, function(whole, insDel) {
var modificationClass = (insDel.toLowerCase() === 'ins' ? 'insert' : 'delete');
return whole.replace(/(<(p|div|blockquote|li)[^>]*>)([\s\S]*?)(<\/\2>)/gi, function(whole2, opening, blockTag, content, closing) {
var modifiedTag = diffService._addClassToHtmlTag(opening, modificationClass);
return '</' + insDel + '>' + modifiedTag + content + closing + '<' + insDel + '>';
});
});
// 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, function (whole, insDel, space) {
return 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,
function(whole, blockTag, content, space) {
return '<ins>' + content + '</ins></' + blockTag + '>' + space;
}
);
// </p> </ins> -> </ins></p>
diffUnnormalized = diffUnnormalized.replace(/(<\/(p|div|blockquote|li)>)(\s*)<\/(ins|del)>/gi, function (whole, ending, blockTag, space, insdel) {
return '</' + insdel + '>' + ending + space;
});
if (diffUnnormalized.substr(0, workaroundPrepend.length) === workaroundPrepend) {
diffUnnormalized = diffUnnormalized.substring(workaroundPrepend.length);
@ -1554,18 +1584,6 @@ angular.module('OpenSlidesApp.motions.diff', ['OpenSlidesApp.motions.lineNumberi
if (this._diffDetectBrokenDiffHtml(diffUnnormalized)) {
diff = this._diffParagraphs(htmlOld, htmlNew, lineLength, firstLineNumber);
} else {
diffUnnormalized = diffUnnormalized.replace(/<ins>.*?(\n.*?)*<\/ins>/gi, function (found) {
found = found.replace(/<(div|p|li)[^>]*>/gi, function(match) { return match + '<ins>'; });
found = found.replace(/<\/(div|p|li)[^>]*>/gi, function(match) { return '</ins>' + match; });
return found;
});
diffUnnormalized = diffUnnormalized.replace(/<del>.*?(\n.*?)*<\/del>/gi, function (found) {
found = found.replace(/<(div|p|li)[^>]*>/gi, function(match) { return match + '<del>'; });
found = found.replace(/<\/(div|p|li)[^>]*>/gi, function(match) { return '</del>' + match; });
return found;
});
diffUnnormalized = diffUnnormalized.replace(/^<del><p>(.*)<\/p><\/del>$/gi, function(match, inner) { return "<p>" + inner + "</p>"; });
var node = document.createElement('div');
node.innerHTML = diffUnnormalized;
diff = node.innerHTML;

View File

@ -68,6 +68,8 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
return this._ignoreInsertedText;
} else if (this._isOsLineNumberNode(node)) {
return true;
} else if (node.classList && node.classList.contains('insert')) {
return true;
} else {
return false;
}
@ -448,7 +450,7 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
throw 'This method may only be called for ELEMENT-nodes: ' + node.nodeValue;
}
if (this._isIgnoredByLineNumbering(node)) {
if (this._currentInlineOffset === 0 && this._currentLineNumber !== null) {
if (this._currentInlineOffset === 0 && this._currentLineNumber !== null && this._isInlineElement(node)) {
var lineNumberNode = this._createLineNumber();
if (lineNumberNode) {
node.insertBefore(lineNumberNode, node.firstChild);

View File

@ -609,17 +609,19 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions',
}, function () {
$scope.change_recommendations = [];
$scope.title_change_recommendation = null;
MotionChangeRecommendation.filter({
'where': {'motion_version_id': {'==': motion.active_version}}
}).forEach(function (change) {
if (change.isTextRecommendation()) {
$scope.change_recommendations.push(change);
}
if (change.isTitleRecommendation()) {
$scope.title_change_recommendation = change;
}
});
rebuild_amendments_crs();
if (motion) {
MotionChangeRecommendation.filter({
'where': {'motion_version_id': {'==': motion.active_version}}
}).forEach(function (change) {
if (change.isTextRecommendation()) {
$scope.change_recommendations.push(change);
}
if (change.isTitleRecommendation()) {
$scope.title_change_recommendation = change;
}
});
rebuild_amendments_crs();
}
});
$scope.$watch(function () {

View File

@ -191,37 +191,37 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
// yes
var yes = poll.getVote(poll.yes, 'yes');
column1.push(gettextCatalog.getString('Yes') + ':');
column2.push($filter('number')(yes.value, precision));
column2.push($filter('textOrNumber')(yes.value, precision));
column3.push(yes.percentStr);
// no
var no = poll.getVote(poll.no, 'no');
column1.push(gettextCatalog.getString('No') + ':');
column2.push($filter('number')(no.value, precision));
column2.push($filter('textOrNumber')(no.value, precision));
column3.push(no.percentStr);
// abstain
var abstain = poll.getVote(poll.abstain, 'abstain');
column1.push(gettextCatalog.getString('Abstain') + ':');
column2.push($filter('number')(abstain.value, precision));
column2.push($filter('textOrNumber')(abstain.value, precision));
column3.push(abstain.percentStr);
// votes valid
if (poll.votesvalid) {
var valid = poll.getVote(poll.votesvalid, 'votesvalid');
column1.push(gettextCatalog.getString('Valid votes') + ':');
column2.push($filter('number')(valid.value, precision));
column2.push($filter('textOrNumber')(valid.value, precision));
column3.push(valid.percentStr);
}
// votes invalid
if (poll.votesvalid) {
var invalid = poll.getVote(poll.votesinvalid, 'votesinvalid');
column1.push(gettextCatalog.getString('Invalid votes') + ':');
column2.push($filter('number')(invalid.value, precision));
column2.push($filter('textOrNumber')(invalid.value, precision));
column3.push(invalid.percentStr);
}
// votes cast
if (poll.votescast) {
var cast = poll.getVote(poll.votescast, 'votescast');
column1.push(gettextCatalog.getString('Votes cast') + ':');
column2.push($filter('number')(cast.value, precision));
column2.push($filter('textOrNumber')(cast.value, precision));
column3.push(cast.percentStr);
}
}

View File

@ -72,13 +72,16 @@ angular.module('OpenSlidesApp.motions.projector', [
return Motion.lastModified(motionId);
}, function () {
$scope.motion = Motion.get(motionId);
$scope.amendment_diff_paragraphs = $scope.motion.getAmendmentParagraphsLinesDiff();
$scope.viewChangeRecommendations.setVersion($scope.motion, $scope.motion.active_version);
_.forEach($scope.motion.polls, function (poll) {
MotionPollDecimalPlaces.getPlaces(poll, true).then(function (decimalPlaces) {
precisionCache[poll.id] = decimalPlaces;
if ($scope.motion) {
$scope.amendment_diff_paragraphs = $scope.motion.getAmendmentParagraphsLinesDiff();
$scope.viewChangeRecommendations.setVersion($scope.motion, $scope.motion.active_version);
_.forEach($scope.motion.polls, function (poll) {
MotionPollDecimalPlaces.getPlaces(poll, true).then(function (decimalPlaces) {
precisionCache[poll.id] = decimalPlaces;
});
});
});
$scope.viewChangeRecommendations.initProjector($scope, $scope.motion, $scope.mode);
}
});
var precisionCache = {};

View File

@ -234,7 +234,7 @@
<i class="fa fa-cog"></i>
</span>
<ul uib-dropdown-menu class="dropdown-menu" aria-labelledby="recommendation-dropdown">
<li ng-repeat="recommendation in motion.state.getRecommendations()">
<li ng-repeat="recommendation in motion.state.getRecommendations() | orderBy:'id'">
<a href ng-click="motion.setRecommendation(recommendation.id)">
{{ recommendation.recommendation_label | translate }}
<span ng-if="recommendation.show_recommendation_extension_field">...</span>
@ -404,7 +404,7 @@
<td ng-init="voteYes = poll.getVote(poll.yes, 'yes')">
<span class="result-label"><translate>Yes</translate>:</span>
<span class="result_value">
{{ voteYes.value | number:votesPrecision }} {{ voteYes.percentStr }}
{{ voteYes.value | textOrNumber:votesPrecision }} {{ voteYes.percentStr }}
</span>
<div ng-if="voteYes.percentNumber">
<uib-progressbar value="voteYes.percentNumber" type="success"></uib-progressbar>
@ -416,7 +416,7 @@
<td ng-init="voteNo = poll.getVote(poll.no, 'no')">
<span class="result-label"><translate>No</translate>:</span>
<span class="result_value" >
{{ voteNo.value | number:votesPrecision }} {{ voteNo.percentStr }}
{{ voteNo.value | textOrNumber:votesPrecision }} {{ voteNo.percentStr }}
</span>
<div ng-if="voteNo.percentNumber">
<uib-progressbar value="voteNo.percentNumber" type="danger"></uib-progressbar>
@ -428,7 +428,7 @@
<td ng-init="voteAbstain = poll.getVote(poll.abstain, 'abstain')">
<span class="result-label"><translate>Abstain</translate>:</span>
<span class="result_value">
{{ voteAbstain.value | number:votesPrecision }} {{ voteAbstain.percentStr }}
{{ voteAbstain.value | textOrNumber:votesPrecision }} {{ voteAbstain.percentStr }}
</span>
<div ng-if="voteAbstain.percentNumber">
<uib-progressbar value="voteAbstain.percentNumber" type="warning"></uib-progressbar>
@ -440,7 +440,7 @@
<td ng-init="votesValid = poll.getVote(poll.votesvalid, 'votesvalid')">
<span class="result-label"><translate>Valid votes</translate>:</span>
<span class="result_value">
{{ votesValid.value | number:votesPrecision }} {{ votesValid.percentStr }}
{{ votesValid.value | textOrNumber:votesPrecision }} {{ votesValid.percentStr }}
</span>
<!-- invalid votes -->
<tr ng-if="poll.votesinvalid !== null">
@ -449,7 +449,7 @@
<td ng-init="votesInvalid = poll.getVote(poll.votesinvalid, 'votesinvalid')">
<span class="result-label"><translate>Invalid votes</translate>:</span>
<span class="result_value">
{{ votesInvalid.value | number:votesPrecision }} {{ votesInvalid.percentStr }}
{{ votesInvalid.value | textOrNumber:votesPrecision }} {{ votesInvalid.percentStr }}
</span>
<!-- votes cast -->
<tr class="total" ng-if="poll.votescast !== null">
@ -458,7 +458,7 @@
<td ng-init="votesCast = poll.getVote(poll.votescast, 'votescast')">
<span class="result-label"><translate>Votes cast</translate>:</span>
<span class="result_value">
{{ votesCast.value | number:votesPrecision }} {{ votesCast.percentStr }}
{{ votesCast.value | textOrNumber:votesPrecision }} {{ votesCast.percentStr }}
</span>
<!-- majority calculation -->

View File

@ -116,7 +116,7 @@
</select>
<!-- set motion block button -->
<a ng-show="selectedAction == 'setMotionBlock' && selectedMotionBlock"
ng-click="setMotionBlockMultiple(motionsFilterd, selectedMotionBlock)" class="btn btn-default btn-sm">
ng-click="setMotionBlockMultiple(motionsFiltered, selectedMotionBlock)" class="btn btn-default btn-sm">
<translate>Set motion block</translate>
</a>
<!-- delete button -->

View File

@ -34,7 +34,7 @@
<select chosen
ng-model="submitterSelectBox.selected"
ng-change="addSubmitter(submitterSelectBox.selected)"
ng-options="user.id as user.get_full_name() for user in users"
ng-options="user.id as user.get_full_name() for user in users | orderBy: 'full_name'"
search-contains="true"
placeholder-text-single="'Select or search a participant ...' | translate"
no-results-text="'No results available ...' | translate"

View File

@ -31,7 +31,7 @@
<td ng-init="voteYes = poll.getVote(poll.yes, 'yes')">
<span class="result_label"><translate>Yes</translate>:</span>
<span class="result_value">
{{ voteYes.value | number:getPollVotesPrecision(poll) }} {{ voteYes.percentStr }}
{{ voteYes.value | textOrNumber:getPollVotesPrecision(poll) }} {{ voteYes.percentStr }}
</span>
<div ng-if="voteYes.percentNumber">
<uib-progressbar value="voteYes.percentNumber" type="success"></uib-progressbar>
@ -43,7 +43,7 @@
<td ng-init="voteNo = poll.getVote(poll.no, 'no')">
<span class="result_label"><translate>No</translate>:</span>
<span class="result_value" >
{{ voteNo.value | number:getPollVotesPrecision(poll) }} {{ voteNo.percentStr }}
{{ voteNo.value | textOrNumber:getPollVotesPrecision(poll) }} {{ voteNo.percentStr }}
</span>
<div ng-if="voteNo.percentNumber">
<uib-progressbar value="voteNo.percentNumber" type="danger"></uib-progressbar>
@ -55,7 +55,7 @@
<td ng-init="voteAbstain = poll.getVote(poll.abstain, 'abstain')">
<span class="result_label"><translate>Abstain</translate>:</span>
<span class="result_value">
{{ voteAbstain.value | number:getPollVotesPrecision(poll) }} {{ voteAbstain.percentStr }}
{{ voteAbstain.value | textOrNumber:getPollVotesPrecision(poll) }} {{ voteAbstain.percentStr }}
</span>
<div ng-if="voteAbstain.percentNumber">
<uib-progressbar value="voteAbstain.percentNumber" type="warning"></uib-progressbar>

View File

@ -454,7 +454,7 @@ describe('linenumbering', function () {
"<p>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>";
var diff = diffService.diff(before, after);
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><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>");
});
it('does not result in separate paragraphs when only the first word has changed', function () {
@ -527,9 +527,10 @@ describe('linenumbering', function () {
after = "<p>rief sie alle sieben herbei und sprach 'liebe Kinder, ich will hinaus in den Wald, seid Noch</p>" +
"<p>Test 123</p>",
expected = "<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>";
var diff = diffService.diff(before, after);
expect(diff).toBe(expected);
});
@ -540,7 +541,7 @@ describe('linenumbering', function () {
"\n" +
"<p>Stet clita kasd gubergren, no sea takimata sanctus est.</p>",
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 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>";
var diff = diffService.diff(before, after);
expect(diff).toBe(expected);
@ -552,8 +553,8 @@ describe('linenumbering', function () {
'<p style="text-align: justify;"><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;"><span style="color: #000000;">Neither should this line</span></p>',
expected = "<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;"><ins><span style="color: #000000;">Neither should this 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;" class="insert"><span style="color: #000000;">Neither should this line</span></p>';
var diff = diffService.diff(before, after);
expect(diff).toBe(expected);
@ -563,7 +564,7 @@ describe('linenumbering', function () {
var before = "<P>Ihr könnt ohne Sorge fortgehen.'Da meckerte die Alte und machte sich getrost auf den Weg.</P>",
after = "";
var diff = diffService.diff(before, after);
expect(diff).toBe("<P class=\"delete\">Ihr könnt ohne Sorge fortgehen.'Da meckerte die Alte und machte sich getrost auf den Weg.</P>");
expect(diff).toBe("<p class=\"delete\">Ihr könnt ohne Sorge fortgehen.'Da meckerte die Alte und machte sich getrost auf den Weg.</p>");
});
it('does not repeat the last word (1)', function () {
@ -653,6 +654,13 @@ describe('linenumbering', function () {
expect(diff).toBe('<p>Lorem ipsum dolor sit amet, consetetur <del><br></del>sadipscing elitr.<ins> Sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua..</ins><br>Bavaria ipsum dolor sit amet oha wea nia ausgähd<br><del>kummt nia hoam i hob di narrisch gean</del><ins>Autonomie erfährt ihre Grenzen</ins></p>');
});
it('works with multiple inserted paragraphs', function() {
var 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>";
var diff = diffService.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', function() {
// See diff._fixWrongChangeDetection
var inHtml = '<p>Test 123<br>wir strikt ab. lehnen wir ' + brMarkup(1486) + 'ab.<br>' + noMarkup(1487) + 'Gegenüber</p>',
@ -661,7 +669,21 @@ describe('linenumbering', function () {
'Gegenüber</p>';
var diff = diffService.diff(inHtml, outHtml);
expect(diff).toBe('<p>Test 123<br>wir strikt ab. lehnen wir ' + brMarkup(1486) + 'ab.<br>' + noMarkup(1487) + 'Gegenüber</p>')
});
});
it('does not delete a paragraph before an inserted one', function () {
var inHtml = '<ul class="os-split-before"><li>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.</li>\n' +
'</ul>',
outHtml = '<ul class="os-split-before">\n' +
'<li>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.</li>\n' +
'<li class="testclass">At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</li>\n' +
'</ul>';
var diff = diffService.diff(inHtml, outHtml);
expect(diff).toBe('<ul class="os-split-before">' +
'<li>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.</li>' +
'<li class="testclass insert">At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</li>' +
'</ul>');
});
});
describe('ignoring line numbers', function () {
@ -682,7 +704,7 @@ describe('linenumbering', function () {
before = lineNumberingService.insertLineNumbers(before, 80, null, null, 2);
var diff = diffService.diff(before, after);
expect(diff).toBe("<p>" + noMarkup(2) + "their grammar, their pronunciation and their most common words. Everyone " + brMarkup(3) + "realizes why a</p>\n" +
"<p><ins>NEW PARAGRAPH 2.</ins></p>");
"<p class=\"insert\">NEW PARAGRAPH 2.</p>");
});
it('works with two inserted paragraphs', function () {
@ -697,12 +719,26 @@ describe('linenumbering', function () {
before = lineNumberingService.insertLineNumbers(before, 80, null, null, 2);
var diff = diffService.diff(before, after);
expect(diff).toBe("<p>" + noMarkup(2) + "their grammar, their pronunciation and their most common words. Everyone " + brMarkup(3) + "realizes why a</p>\n" +
"<p><ins>NEW PARAGRAPH 1.</ins></p>\n" +
"<p><ins>NEW PARAGRAPH 2.</ins></p>\n" +
"<p class=\"insert\">NEW PARAGRAPH 1.</p>\n" +
"<p class=\"insert\">NEW PARAGRAPH 2.</p>\n" +
"<div>" + noMarkup(4) + "Go on</div>"
);
});
it('works with a replaced list item', function () {
var before = "<ul><li>Lorem ipsum <strong>dolor sit amet</strong>, consetetur sadipscing elitr, sed diam nonumy eirmod tempor.</li></ul>",
after = "<ul>\n<li>\n<p>At vero eos et accusam et justo duo dolores et ea rebum.</p>\n</li>\n</ul>\n",
expected = '<UL class="delete"><LI>' + noMarkup(1) + 'Lorem ipsum <STRONG>dolor sit amet</STRONG>, consetetur sadipscing elitr, sed diam nonumy ' + brMarkup(2) + 'eirmod tempor.</LI></UL>' +
"<UL class=\"insert\">\n<LI>\n<P>At vero eos et accusam et justo duo dolores et ea rebum.</P>\n</LI>\n</UL>";
var diff = diffService.diff(before, after, 80);
diff = diffService._normalizeHtmlForDiff(diff);
expected = diffService._normalizeHtmlForDiff(expected);
expect(diff.toLowerCase()).toBe(expected.toLowerCase());
});
it('detects broken HTML and lowercases class names', function () {
var before = "<p><span class=\"line-number-3 os-line-number\" data-line-number=\"3\" contenteditable=\"false\">&nbsp;</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\">&nbsp;</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\">&nbsp;</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\">&nbsp;</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\">&nbsp;</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>";

View File

@ -328,6 +328,14 @@ describe('linenumbering', function () {
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
it('does not count within .insert nodes', function () {
var inHtml = "<p>1234</p><ul class=\"insert\"><li>1234</li></ul><p>1234 1234</p>";
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 10);
expect(outHtml).toBe('<p>' + noMarkup(1) + '1234</p><ul class="insert"><li>1234</li></ul><p>' + noMarkup(2) + '1234 1234</p>');
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
expect(lineNumberingService.insertLineBreaksWithoutNumbers(outHtml, 80)).toBe(outHtml);
});
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);