From 16f1ad5731502ec59d84b429f43dd97556aaeffb Mon Sep 17 00:00:00 2001 From: Emanuel Schuetze Date: Fri, 12 Feb 2016 00:15:35 +0100 Subject: [PATCH] Use tinymce instead of CKEditor. - better integration of tinymce in bower and gulp - Improve support for html tags in reportlab's motion pdf. - Now paste from word works without problems (That was the main reason of switching to tinymce: The data loss problem with MS Word is still unfixed in CKEditor, see https://dev.ckeditor.com/ticket/13174) - The editor is now used for customslides (text), motions (text, reason) and users (about). - Use mediafile image list for tinymce. - Use own repository for tinymce-i18n: OpenSlides/tinymce-i18n --- bower.json | 13 ++- gulpfile.js | 28 ++++- openslides/agenda/static/js/agenda/site.js | 9 +- openslides/core/static/js/core/base.js | 37 ++----- openslides/core/static/js/core/site.js | 54 ++++++++-- .../templates/core/customslide-detail.html | 2 +- .../core/static/templates/core/editor.html | 2 + .../templates/core/slide_customslide.html | 2 +- openslides/core/static/templates/index.html | 1 - .../mediafiles/static/js/mediafiles/base.js | 13 +++ openslides/motions/pdf.py | 102 ++++++++++++++++-- openslides/motions/static/js/motions/site.js | 22 ++-- .../templates/motions/motion-detail.html | 4 +- .../static/templates/motions/motion-form.html | 1 - .../templates/motions/slide_motion.html | 4 +- openslides/users/static/js/users/site.js | 16 ++- .../templates/users/user-detail-profile.html | 2 +- package.json | 1 + 18 files changed, 232 insertions(+), 81 deletions(-) create mode 100644 openslides/core/static/templates/core/editor.html diff --git a/bower.json b/bower.json index 38edcca56..d4ba767ab 100644 --- a/bower.json +++ b/bower.json @@ -27,15 +27,22 @@ "js-data": "~2.8.2", "js-data-angular": "~3.1.0", "ng-file-upload": "~11.2.3", - "ckeditor": "4.5.6", - "angular-ckeditor": "~1.0.3", + "angular-ui-tinymce": "~0.0.13", "angular-pdf": "~1.3.0", "roboto-condensed": "~0.3.0", - "open-sans-fontface": "https://github.com/OpenSlides/open-sans.git#1.4.2.post1" + "open-sans-fontface": "https://github.com/OpenSlides/open-sans.git#1.4.2.post1", + "tinymce-i18n": "OpenSlides/tinymce-i18n" }, "overrides": { "pdfjs-dist": { "main": "build/pdf.combined.js" + }, + "tinymce-dist": { + "main": [ + "tinymce.js", + "themes/modern/theme.js", + "plugins/*/plugin.js" + ] } }, "resolutions": { diff --git a/gulpfile.js b/gulpfile.js index 53740d574..61f13018b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -25,6 +25,7 @@ var argv = require('yargs').argv, jshint = require('gulp-jshint'), mainBowerFiles = require('main-bower-files'), path = require('path'), + rename = require('gulp-rename'), through = require('through2'), uglify = require('gulp-uglify'), vsprintf = require('sprintf-js').vsprintf; @@ -67,10 +68,27 @@ gulp.task('fonts-libs', function() { .pipe(gulp.dest(path.join(output_directory, 'fonts'))); }); -// Extra task only for CKEditor -gulp.task('ckeditor', function () { - return gulp.src(path.join('bower_components', 'ckeditor', '**')) - .pipe(gulp.dest(path.join(output_directory, 'ckeditor'))); +// Catches all skins files for TinyMCE editor. +gulp.task('tinymce-skins', function () { + return gulp.src(path.join('bower_components', 'tinymce-dist', 'skins', '**')) + .pipe(gulp.dest(path.join(output_directory, 'tinymce', 'skins'))); +}); + +// Catches all required i18n files for TinyMCE editor. +gulp.task('tinymce-i18n', function () { + return gulp.src([ + 'bower_components/tinymce-i18n/langs/cs.js', + 'bower_components/tinymce-i18n/langs/de.js', + 'bower_components/tinymce-i18n/langs/es.js', + 'bower_components/tinymce-i18n/langs/fr_FR.js', + 'bower_components/tinymce-i18n/langs/pt_PT.js', + ]) + .pipe(rename(function (path) { + if (path.basename === 'pt_PT') {path.basename = 'pt'} + if (path.basename === 'fr_FR') {path.basename = 'fr'} + })) + .pipe(gulpif(argv.production, uglify())) + .pipe(gulp.dest(path.join(output_directory, 'tinymce', 'i18n'))); }); // Compiles translation files (*.po) to *.json and saves them in the directory @@ -84,7 +102,7 @@ gulp.task('translations', function () { }); // Gulp default task. Runs all other tasks before. -gulp.task('default', ['js-libs', 'css-libs', 'fonts-libs', 'ckeditor', 'translations'], function () {}); +gulp.task('default', ['js-libs', 'css-libs', 'fonts-libs', 'tinymce-skins', 'tinymce-i18n', 'translations'], function () {}); /** diff --git a/openslides/agenda/static/js/agenda/site.js b/openslides/agenda/static/js/agenda/site.js index f647c3c80..83b722f55 100644 --- a/openslides/agenda/static/js/agenda/site.js +++ b/openslides/agenda/static/js/agenda/site.js @@ -75,9 +75,10 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda']) 'operator', 'ngDialog', 'Agenda', + 'CustomslideForm', 'AgendaTree', 'Projector', - function($scope, $http, $state, DS, operator, ngDialog, Agenda, AgendaTree, Projector) { + function($scope, $http, $state, DS, operator, ngDialog, Agenda, CustomslideForm, AgendaTree, Projector) { // Bind agenda tree to the scope $scope.$watch(function () { return Agenda.lastModified(); @@ -110,11 +111,7 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda']) }; // open new dialog $scope.newDialog = function () { - ngDialog.open({ - template: 'static/templates/core/customslide-form.html', - controller: 'CustomslideCreateCtrl', - className: 'ngdialog-theme-default wide-form' - }); + ngDialog.open(CustomslideForm.getDialog()); }; // open edit dialog $scope.editDialog = function (item) { diff --git a/openslides/core/static/js/core/base.js b/openslides/core/static/js/core/base.js index 657e4b1c2..1d300ff2d 100644 --- a/openslides/core/static/js/core/base.js +++ b/openslides/core/static/js/core/base.js @@ -424,34 +424,15 @@ angular.module('OpenSlidesApp.core', [ } ]) -/* Options for CKEditor used in various create and edit views. */ -.value('CKEditorOptions', { - allowedContent: - 'h1 h2 h3 p pre b i u strike strong em blockquote;' + - 'a[!href];' + - 'img[!src,alt]{width,height,float};' + - 'table tr th td caption;' + - 'li ol ul{list-style};' + - 'span{color,background-color};', - extraPlugins: 'colorbutton', - toolbarGroups: [ - { name: 'clipboard', groups: [ 'clipboard', 'undo' ] }, - { name: 'editing', groups: [ 'find', 'selection', 'spellchecker', 'editing' ] }, - { name: 'forms', groups: [ 'forms' ] }, - { name: 'tools', groups: [ 'tools' ] }, - { name: 'about', groups: [ 'about' ] }, - { name: 'document', groups: [ 'mode', 'document', 'doctools' ] }, - { name: 'others', groups: [ 'others' ] }, - '/', - { name: 'styles', groups: [ 'styles' ] }, - { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] }, - { name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi', 'paragraph' ] }, - { name: 'links', groups: [ 'links' ] }, - { name: 'insert', groups: [ 'insert' ] }, - { name: 'colors', groups: [ 'colors' ] } - ], - removeButtons: 'Anchor,SpecialChar,Subscript,Superscript,Styles,RemoveFormat,HorizontalRule' -}) +// mark HTML as "trusted" +.filter('trusted', [ + '$sce', + function ($sce) { + return function(text) { + return $sce.trustAsHtml(text); + }; + } +]) // Make sure that the DS factories are loaded by making them a dependency .run([ diff --git a/openslides/core/static/js/core/site.js b/openslides/core/static/js/core/site.js index ae2f41375..a3d045bc9 100644 --- a/openslides/core/static/js/core/site.js +++ b/openslides/core/static/js/core/site.js @@ -14,8 +14,8 @@ angular.module('OpenSlidesApp.core.site', [ 'ngMessages', 'ngCsvImport', 'ui.select', + 'ui.tinymce', 'luegg.directives', - 'ckeditor', ]) // Provider to register entries for the main menu. @@ -337,10 +337,16 @@ angular.module('OpenSlidesApp.core.site', [ .run([ 'formlyConfig', function (formlyConfig) { - // NOTE: This next line is highly recommended. Otherwise Chrome's autocomplete will appear over your options! + // NOTE: This next line is highly recommended. Otherwise Chrome's autocomplete + // will appear over your options! formlyConfig.extras.removeChromeAutoComplete = true; // Configure custom types + formlyConfig.setType({ + name: 'editor', + extends: 'textarea', + templateUrl: 'static/templates/core/editor.html', + }); formlyConfig.setType({ name: 'ui-select-single', extends: 'select', @@ -354,6 +360,35 @@ angular.module('OpenSlidesApp.core.site', [ } ]) +// Options for TinyMCE editor used in various create and edit views. +.factory('Editor', [ + 'gettextCatalog', + function (gettextCatalog) { + return { + getOptions: function (images) { + return { + language_url: '/static/tinymce/i18n/' + gettextCatalog.getCurrentLanguage() + '.js', + theme_url: '/static/js/openslides-libs.js', + skin_url: '/static/tinymce/skins/lightgray/', + inline: false, + statusbar: false, + browser_spellcheck: true, + image_advtab: true, + image_list: images, + plugins: [ + 'lists link autolink charmap preview searchreplace code fullscreen', + 'paste textcolor colorpicker image imagetools' + ], + menubar: '', + toolbar: 'undo redo searchreplace | styleselect | bold italic underline strikethrough ' + + 'forecolor backcolor removeformat | bullist numlist | outdent indent | ' + + 'link image charmap table | code preview fullscreen' + }; + } + } + } +]) + // html-tag os-form-field to generate generic from fields // TODO: make it possible to use other fields then config fields .directive('osFormField', [ @@ -504,17 +539,19 @@ angular.module('OpenSlidesApp.core.site', [ // Provide generic customslide form fields for create and update view .factory('CustomslideForm', [ 'gettextCatalog', - 'CKEditorOptions', + 'Editor', 'Mediafile', - function (gettextCatalog, CKEditorOptions, Mediafile) { + function (gettextCatalog, Editor, Mediafile) { return { // ngDialog for customslide form getDialog: function (customslide) { + var resolve = {}; if (customslide) { - var resolve = { + resolve = { customslide: function(Customslide) {return Customslide.find(customslide.id);} }; } + resolve.mediafiles = function(Mediafile) {return Mediafile.findAll();} return { template: 'static/templates/core/customslide-form.html', controller: (customslide) ? 'CustomslideUpdateCtrl' : 'CustomslideCreateCtrl', @@ -525,6 +562,7 @@ angular.module('OpenSlidesApp.core.site', [ } }, getFormFields: function () { + var images = Mediafile.getAllImages(); return [ { key: 'title', @@ -536,11 +574,13 @@ angular.module('OpenSlidesApp.core.site', [ }, { key: 'text', - type: 'textarea', + type: 'editor', templateOptions: { label: gettextCatalog.getString('Text') }, - ngModelElAttrs: {'ckeditor': 'CKEditorOptions'} + data: { + tinymceOption: Editor.getOptions(images) + } }, { key: 'attachments_id', diff --git a/openslides/core/static/templates/core/customslide-detail.html b/openslides/core/static/templates/core/customslide-detail.html index 4f5ef9dfd..5beb233c9 100644 --- a/openslides/core/static/templates/core/customslide-detail.html +++ b/openslides/core/static/templates/core/customslide-detail.html @@ -30,7 +30,7 @@
-
+

Attachments