(function () { "use strict"; angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions', 'OpenSlidesApp.motions.lineNumbering']) .factory('MotionPDFExport', [ 'HTMLValidizer', 'Motion', 'User', 'PdfMakeConverter', 'PdfMakeDocumentProvider', 'PdfMakeBallotPaperProvider', 'MotionContentProvider', 'PollContentProvider', 'gettextCatalog', '$http', 'PdfCreate', function (HTMLValidizer, Motion, User, PdfMakeConverter, PdfMakeDocumentProvider, PdfMakeBallotPaperProvider, MotionContentProvider, PollContentProvider, gettextCatalog, $http, PdfCreate) { var obj = {}; var $scope; obj.createMotion = function() { var text = $scope.motion.getTextByMode($scope.viewChangeRecommendations.mode, $scope.version); var content = HTMLValidizer.validize(text) + HTMLValidizer.validize($scope.motion.getReason($scope.version)); var map = Function.prototype.call.bind([].map); var image_sources = map($(content).find("img"), function(element) { return element.getAttribute("src"); }); $http.post('/core/encode_media/', JSON.stringify(image_sources)).then(function (success) { var converter = PdfMakeConverter.createInstance(success.data.images); var motionContentProvider = MotionContentProvider.createInstance(converter, $scope.motion, $scope, User, $http); var documentProvider = PdfMakeDocumentProvider.createInstance(motionContentProvider); var identifier = $scope.motion.identifier ? '-' + $scope.motion.identifier : ''; var filename = gettextCatalog.getString("Motion") + identifier + ".pdf"; PdfCreate.download(documentProvider.getDocument(), filename); }); }; //make PDF for polls obj.createPoll = function() { var id = $scope.motion.identifier.replace(" ", ""); var title = $scope.motion.getTitle($scope.version); var filename = gettextCatalog.getString("Motion") + "-" + id + "-" + gettextCatalog.getString("ballot-paper") + ".pdf"; var pollContentProvider = PollContentProvider.createInstance(title, id, gettextCatalog); var documentProvider = PdfMakeBallotPaperProvider.createInstance(pollContentProvider); PdfCreate.download(documentProvider.getDocument(), filename); }; obj.init = function (_scope) { $scope = _scope; }; return obj; } ]) .factory('MotionInlineEditing', [ 'Editor', 'Motion', '$timeout', 'gettextCatalog', function (Editor, Motion, $timeout, gettextCatalog) { var createInstance = function ($scope, motion, selector, versioning, getOriginalData, saveData) { var obj = { active: false, changed: false, isEditable: false, trivialChange: false, originalHtml: null, ckeditorOptions: Editor.getOptions(), }; obj.ckeditorOptions.readOnly = true; obj.setVersion = function (_motion, versionId) { motion = _motion; // If this is not updated, obj.originalHtml = motion.getTextWithLineBreaks(versionId); obj.changed = false; if (obj.editor) { obj.editor.setReadOnly(true); obj.editor.setData(obj.originalHtml); } }; obj.enable = function () { obj.active = true; obj.isEditable = true; obj.ckeditorOptions.language = gettextCatalog.getCurrentLanguage(); obj.editor = CKEDITOR.inline(selector, obj.ckeditorOptions); obj.editor.on('change', function () { $timeout(function() { if (obj.editor.getData() != obj.originalHtml) { obj.changed = true; } else { obj.changed = false; } }); }); obj.revert(); }; obj.disable = function () { if (obj.editor) { obj.editor.setReadOnly(true); obj.editor.setData(obj.originalHtml, { callback: function() { obj.editor.destroy(); } }); } $timeout(function() { obj.active = false; obj.changed = false; obj.isEditable = false; }); }; // sets editor content to the initial motion state obj.revert = function(originalData) { if (obj.editor) { obj.originalHtml = getOriginalData(obj); obj.editor.setData( getOriginalData(obj), { callback: function() { obj.originalHtml = obj.editor.getData(); obj.editor.setReadOnly(false); $timeout(function() { obj.changed = false; }); $timeout(function () { obj.editor.focus(); }, 100); } }); } }; obj.save = function () { saveData(obj); obj.disable(); Motion.inject(motion); // save change motion object on server Motion.save(motion, {method: 'PATCH'}).then( function (success) { if (versioning) { $scope.showVersion(motion.getVersion(-1)); } obj.revert(); }, function (error) { // save error: revert all changes by restore // (refresh) original motion object from server Motion.refresh(motion); obj.revert(); var message = ''; for (var e in error.data) { message += e + ': ' + error.data[e] + ' '; } $scope.alert = {type: 'danger', msg: message, show: true}; } ); }; return obj; }; return { createInstance: createInstance }; } ]) .factory('MotionCommentsInlineEditing', [ 'MotionInlineEditing', function (MotionInlineEditing) { var createInstances = function ($scope, motion) { var commentsInlineEditing = { editors: [] }; _.forEach($scope.commentsFields, function (field) { var inlineEditing = MotionInlineEditing.createInstance($scope, motion, 'view-original-comment-inline-editor-' + field.name, false, function (obj) { return motion['comment ' + field.name]; }, function (obj) { motion.title = motion.getTitle(-1); motion.text = motion.getText(-1); motion.reason = motion.getReason(-1); motion['comment ' + field.name] = obj.editor.getData(); } ); commentsInlineEditing.editors.push(inlineEditing); }); commentsInlineEditing.saveToolbarVisible = function () { return _.some(commentsInlineEditing.editors, function (instance) { return instance.changed && instance.active; }); }; commentsInlineEditing.active = function () { return _.some(commentsInlineEditing.editors, function (instance) { return instance.active; }); }; commentsInlineEditing.save = function () { _.forEach(commentsInlineEditing.editors, function (instance) { instance.save(); }); }; commentsInlineEditing.revert = function () { _.forEach(commentsInlineEditing.editors, function (instance) { instance.revert(); }); }; commentsInlineEditing.enable = function () { _.forEach(commentsInlineEditing.editors, function (instance) { instance.enable(); }); }; commentsInlineEditing.disable = function () { _.forEach(commentsInlineEditing.editors, function (instance) { instance.disable(); }); }; return commentsInlineEditing; }; return { createInstances: createInstances, }; } ]) .factory('ChangeRecommmendationCreate', [ 'ngDialog', 'ChangeRecommendationForm', function(ngDialog, ChangeRecommendationForm) { var MODE_INACTIVE = 0, MODE_SELECTING_FROM = 1, MODE_SELECTING_TO = 2; var obj = { mode: MODE_INACTIVE, lineFrom: 1, lineTo: 2, html: '', reviewingHtml: '' }; var $scope, motion, version; obj._getAffectedLineNumbers = function () { var changeRecommendations = motion.getChangeRecommendations(version), affectedLines = []; for (var i = 0; i < changeRecommendations.length; i++) { var change = changeRecommendations[i]; for (var j = change.line_from; j < change.line_to; j++) { affectedLines.push(j); } } return affectedLines; }; obj.startCreating = function () { if (obj.mode > MODE_SELECTING_FROM || !motion.isAllowed('can_manage')) { return; } $(".tt_change_recommendation_create_help").removeClass("opened"); var $lineNumbers = $(".motion-text-original .os-line-number"); if ($lineNumbers.filter(".selectable").length === 0) { obj.mode = MODE_SELECTING_FROM; var alreadyAffectedLines = obj._getAffectedLineNumbers(); $lineNumbers.each(function () { var $this = $(this), lineNumber = $this.data("line-number"); if (alreadyAffectedLines.indexOf(lineNumber) == -1) { $(this).addClass("selectable"); } }); } }; obj.cancelCreating = function (ev) { var $target = $(ev.target), query = ".line-numbers-outside .os-line-number.selectable"; if (!$target.is(query) && $target.parents(query).length === 0) { obj.mode = MODE_INACTIVE; obj.lineFrom = 0; obj.lineTo = 0; $(".motion-text-original .os-line-number").removeClass("selected selectable"); obj.startCreating(); } }; obj.setFromLine = function (line) { obj.mode = MODE_SELECTING_TO; obj.lineFrom = line; var alreadyAffectedLines = obj._getAffectedLineNumbers(), foundCollission = false; $(".motion-text-original .os-line-number").each(function () { var $this = $(this); if ($this.data("line-number") >= line && !foundCollission) { if (alreadyAffectedLines.indexOf($this.data("line-number")) == -1) { $(this).addClass("selectable"); } else { $(this).removeClass("selectable"); foundCollission = true; } } else { $(this).removeClass("selectable"); } }); var tt_pos = $(".motion-text-original .line-number-" + line).position().top - 45; $(".tt_change_recommendation_create_help").css("top", tt_pos).addClass("opened"); }; obj.setToLine = function (line) { if (line < obj.lineFrom) { return; } obj.mode = MODE_INACTIVE; obj.lineTo = line + 1; ngDialog.open(ChangeRecommendationForm.getCreateDialog( motion, version, obj.lineFrom, obj.lineTo )); obj.lineFrom = 0; obj.lineTo = 0; $(".motion-text-original .os-line-number").removeClass("selected selectable"); obj.startCreating(); }; obj.lineClicked = function (ev) { if (obj.mode == MODE_INACTIVE) { return; } if (obj.mode == MODE_SELECTING_FROM) { obj.setFromLine($(ev.target).data("line-number")); $(ev.target).addClass("selected"); } else if (obj.mode == MODE_SELECTING_TO) { obj.setToLine($(ev.target).data("line-number")); } }; obj.mouseOver = function (ev) { if (obj.mode != MODE_SELECTING_TO) { return; } var hoverLine = $(ev.target).data("line-number"); $(".motion-text-original .os-line-number").each(function () { var line = $(this).data("line-number"); if (line >= obj.lineFrom && line <= hoverLine) { $(this).addClass("selected"); } else { $(this).removeClass("selected"); } }); }; obj.setVersion = function (_motion, _version) { motion = _motion; version = _version; }; obj.editDialog = function(change_recommendation) { ngDialog.open(ChangeRecommendationForm.getEditDialog(change_recommendation)); }; obj.init = function (_scope, _motion) { $scope = _scope; motion = _motion; version = $scope.version; var $content = $("#content"); $content.on("click", ".line-numbers-outside .os-line-number.selectable", obj.lineClicked); $content.on("click", obj.cancelCreating); $content.on("mouseover", ".line-numbers-outside .os-line-number.selectable", obj.mouseOver); $content.on("mouseover", ".motion-text-original", obj.startCreating); $scope.$watch(function () { return $scope.change_recommendations.length; }, function () { if (obj.mode == MODE_INACTIVE || obj.mode == MODE_SELECTING_FROM) { // Recalculate the affected lines so we cannot select lines affected by a recommendation // that has just been created $(".motion-text-original .os-line-number").removeClass("selected selectable"); obj.startCreating(); } }); $scope.$on("$destroy", function () { obj.destroy(); }); }; obj.destroy = function () { var $content = $("#content"); $content.off("click", ".line-numbers-outside .os-line-number.selectable", obj.lineClicked); $content.off("click", obj.cancelCreating); $content.off("mouseover", ".line-numbers-outside .os-line-number.selectable", obj.mouseOver); $content.off("mouseover", ".motion-text-original", obj.startCreating); }; return obj; } ]) .factory('ChangeRecommmendationView', [ 'Motion', 'MotionChangeRecommendation', 'Config', 'lineNumberingService', 'diffService', '$interval', '$timeout', function (Motion, MotionChangeRecommendation, Config, lineNumberingService, diffService, $interval, $timeout) { var $scope; var obj = { mode: 'original' }; obj.diffFormatterCb = function (change, oldFragment, newFragment) { for (var i = 0; i < oldFragment.childNodes.length; i++) { diffService.addCSSClass(oldFragment.childNodes[i], 'delete'); } for (i = 0; i < newFragment.childNodes.length; i++) { diffService.addCSSClass(newFragment.childNodes[i], 'insert'); } var mergedFragment = document.createDocumentFragment(), diffSection = document.createElement('SECTION'), el; mergedFragment.appendChild(diffSection); diffSection.setAttribute('class', 'diff'); diffSection.setAttribute('data-change-id', change.id); while (oldFragment.firstChild) { el = oldFragment.firstChild; oldFragment.removeChild(el); diffSection.appendChild(el); } while (newFragment.firstChild) { el = newFragment.firstChild; newFragment.removeChild(el); diffSection.appendChild(el); } return mergedFragment; }; obj.delete = function (changeId) { MotionChangeRecommendation.destroy(changeId); }; obj.rejectAll = function (motion) { var changeRecommendations = MotionChangeRecommendation.filter({ 'where': {'motion_version_id': {'==': motion.active_version}} }); _.forEach(changeRecommendations, function(change) { change.rejected = true; change.saveStatus(); }); }; obj.repositionOriginalAnnotations = function () { var $changeRecommendationList = $('.change-recommendation-list'), $lineNumberReference = $('.motion-text-original'); $changeRecommendationList.children().each(function() { var $this = $(this), lineFrom = $this.data('line-from'), lineTo = ($this.data('line-to') - 1), $lineFrom = $lineNumberReference.find('.line-number-' + lineFrom), $lineTo = $lineNumberReference.find('.line-number-' + lineTo), fromTop = $lineFrom.position().top + 3, toTop = $lineTo.position().top + 20, height = (toTop - fromTop); if (height < 10) { height = 10; } // $lineFrom.position().top seems to depend on the scrolling position when the line numbers // have position: absolute. Maybe a bug in the used version of jQuery? // This cancels the effect. /* if ($lineNumberReference.hasClass('line-numbers-outside')) { fromTop += window.scrollY; } */ $this.css({ 'top': fromTop, 'height': height }); }); }; obj.newVersionIncludingChanges = function (motion, version) { if (!motion.isAllowed('update')) { throw 'No permission to update motion'; } var newHtml = motion.getTextByMode('agreed'); motion.setTextStrippingLineBreaks(newHtml); Motion.inject(motion); // save change motion object on server Motion.save(motion, {method: 'PATCH'}).then( function (success) { $scope.showVersion(motion.getVersion(-1)); }, function (error) { // save error: revert all changes by restore // (refresh) original motion object from server Motion.refresh(motion); var message = ''; for (var e in error.data) { message += e + ': ' + error.data[e] + ' '; } $scope.alert = {type: 'danger', msg: message, show: true}; } ); }; obj.scrollToDiffBox = function (changeId) { obj.mode = 'diff'; $timeout(function() { var $diffBox = $('.diff-box-' + changeId); $('html, body').animate({ scrollTop: $diffBox.offset().top - 50 }, 300); }, 0, false); }; obj.init = function (_scope, viewMode) { $scope = _scope; $scope.$evalAsync(function() { obj.repositionOriginalAnnotations(); }); $scope.$watch(function() { return $('.change-recommendation-list').children().length; }, obj.repositionOriginalAnnotations); $scope.$watch(function () { return $scope.change_recommendations.length; }, function () { if ($scope.change_recommendations.length === 0) { obj.mode = 'original'; } }); var sizeCheckerLastSize = null, sizeCheckerLastClass = null, sizeChecker = $interval(function() { var $holder = $(".motion-text-original"), newHeight = $holder.height(), classes = $holder.attr("class"); if (newHeight != sizeCheckerLastSize || sizeCheckerLastClass != classes) { sizeCheckerLastSize = newHeight; sizeCheckerLastClass = classes; obj.repositionOriginalAnnotations(); } }, 100, 0, false); $scope.$on('$destroy', function() { $interval.cancel(sizeChecker); }); obj.mode = viewMode; }; return obj; } ]); }());