Merge pull request #2720 from matakuka/ckeditor
Going back from tinymce to ckeditor
This commit is contained in:
commit
27c93690c0
@ -24,6 +24,8 @@ Core:
|
|||||||
- Added support for multiple projectors.
|
- Added support for multiple projectors.
|
||||||
- Added an overlay for the current list of speakers.
|
- Added an overlay for the current list of speakers.
|
||||||
- Added button to clear the chatbox.
|
- Added button to clear the chatbox.
|
||||||
|
- Switched editor back from TinyMCE to CKEditor which provides a
|
||||||
|
better copy/paste support from MS Word.
|
||||||
|
|
||||||
Motions:
|
Motions:
|
||||||
- Added origin field.
|
- Added origin field.
|
||||||
|
@ -178,6 +178,7 @@ OpenSlides uses the following projects or parts of them:
|
|||||||
* `angular-bootstrap <http://angular-ui.github.io/bootstrap>`_, License: MIT
|
* `angular-bootstrap <http://angular-ui.github.io/bootstrap>`_, License: MIT
|
||||||
* `angular-bootstrap-colorpicker <https://github.com/buberdds/angular-bootstrap-colorpicker>`_, License: MIT
|
* `angular-bootstrap-colorpicker <https://github.com/buberdds/angular-bootstrap-colorpicker>`_, License: MIT
|
||||||
* `angular-chosen-localytics <http://github.com/leocaseiro/angular-chosen>`_, License: MIT
|
* `angular-chosen-localytics <http://github.com/leocaseiro/angular-chosen>`_, License: MIT
|
||||||
|
* `angular-ckeditor <https://github.com/lemonde/angular-ckeditor/>`_, License: MIT
|
||||||
* `angular-csv-import <https://github.com/bahaaldine/angular-csv-import>`_, License: MIT
|
* `angular-csv-import <https://github.com/bahaaldine/angular-csv-import>`_, License: MIT
|
||||||
* `angular-formly <http://formly-js.github.io/angular-formly/>`_, License: MIT
|
* `angular-formly <http://formly-js.github.io/angular-formly/>`_, License: MIT
|
||||||
* `angular-formly-templates-bootstrap <https://github.com/formly-js/angular-formly-templates-bootstrap>`_, License: MIT
|
* `angular-formly-templates-bootstrap <https://github.com/formly-js/angular-formly-templates-bootstrap>`_, License: MIT
|
||||||
@ -189,13 +190,13 @@ OpenSlides uses the following projects or parts of them:
|
|||||||
* `angular-sanitize <http://angularjs.org>`_, License: MIT
|
* `angular-sanitize <http://angularjs.org>`_, License: MIT
|
||||||
* `angular-scroll-glue <https://github.com/Luegg/angularjs-scroll-glue>`_, License: MIT
|
* `angular-scroll-glue <https://github.com/Luegg/angularjs-scroll-glue>`_, License: MIT
|
||||||
* `angular-ui-router <http://angular-ui.github.io/ui-router/>`_, License: MIT
|
* `angular-ui-router <http://angular-ui.github.io/ui-router/>`_, License: MIT
|
||||||
* `angular-ui-tinymce <http://angular-ui.github.com>`_, License: MIT
|
|
||||||
* `angular-ui-tree <https://github.com/angular-ui-tree/angular-ui-tree>`_, License: MIT
|
* `angular-ui-tree <https://github.com/angular-ui-tree/angular-ui-tree>`_, License: MIT
|
||||||
* `angular-xeditable <https://github.com/vitalets/angular-xeditable>`_, License: MIT
|
* `angular-xeditable <https://github.com/vitalets/angular-xeditable>`_, License: MIT
|
||||||
* `api-check <https://github.com/kentcdodds/api-check>`_, License: MIT
|
* `api-check <https://github.com/kentcdodds/api-check>`_, License: MIT
|
||||||
* `bootstrap <http://getbootstrap.com>`_, License: MIT
|
* `bootstrap <http://getbootstrap.com>`_, License: MIT
|
||||||
* `bootstrap-ui-datetime-picker <https://github.com/Gillardo/bootstrap-ui-datetime-picker>`_, License: MIT
|
* `bootstrap-ui-datetime-picker <https://github.com/Gillardo/bootstrap-ui-datetime-picker>`_, License: MIT
|
||||||
* `chosen <http://harvesthq.github.io/chosen/>`_, License: MIT
|
* `chosen <http://harvesthq.github.io/chosen/>`_, License: MIT
|
||||||
|
* `ckeditor <http://ckeditor.com>`_, License: GPL 2+, LGPL 2.1+ or MPL 1.1.
|
||||||
* `font-awesome-bower <https://github.com/tdg5/font-awesome-bower>`_, License: MIT
|
* `font-awesome-bower <https://github.com/tdg5/font-awesome-bower>`_, License: MIT
|
||||||
* `jquery <https://jquery.com>`_, License: MIT
|
* `jquery <https://jquery.com>`_, License: MIT
|
||||||
* `jquery.cookie <https://plugins.jquery.com/cookie>`_, License: MIT
|
* `jquery.cookie <https://plugins.jquery.com/cookie>`_, License: MIT
|
||||||
@ -210,8 +211,6 @@ OpenSlides uses the following projects or parts of them:
|
|||||||
* `open-sans-fontface <https://github.com/FontFaceKit/open-sans>`_, License: Apache License version 2.0
|
* `open-sans-fontface <https://github.com/FontFaceKit/open-sans>`_, License: Apache License version 2.0
|
||||||
* `pdfjs-dist <http://mozilla.github.io/pdf.js/>`_, License: Apache-2.0
|
* `pdfjs-dist <http://mozilla.github.io/pdf.js/>`_, License: Apache-2.0
|
||||||
* `roboto-condensed <https://github.com/davidcunningham/roboto-condensed>`_, License: Apache 2.0
|
* `roboto-condensed <https://github.com/davidcunningham/roboto-condensed>`_, License: Apache 2.0
|
||||||
* `tinymce <http://www.tinymce.com>`_, License: LGPL-2.1
|
|
||||||
* `tinymce-i18n <https://github.com/OpenSlides/tinymce-i18n>`_, License: LGPL-2.1
|
|
||||||
|
|
||||||
|
|
||||||
License and authors
|
License and authors
|
||||||
|
14
bower.json
14
bower.json
@ -8,6 +8,7 @@
|
|||||||
"angular-bootstrap": "~2.1.3",
|
"angular-bootstrap": "~2.1.3",
|
||||||
"angular-bootstrap-colorpicker": "~3.0.25",
|
"angular-bootstrap-colorpicker": "~3.0.25",
|
||||||
"angular-chosen-localytics": "~1.5.0",
|
"angular-chosen-localytics": "~1.5.0",
|
||||||
|
"angular-ckeditor": "~1.0.3",
|
||||||
"angular-csv-import": "0.0.36",
|
"angular-csv-import": "0.0.36",
|
||||||
"angular-file-saver": "~1.1.2",
|
"angular-file-saver": "~1.1.2",
|
||||||
"angular-formly": "~8.4.0",
|
"angular-formly": "~8.4.0",
|
||||||
@ -19,11 +20,11 @@
|
|||||||
"angular-sanitize": "~1.5.8",
|
"angular-sanitize": "~1.5.8",
|
||||||
"angular-scroll-glue": "~2.0.7",
|
"angular-scroll-glue": "~2.0.7",
|
||||||
"angular-ui-router": "~0.3.1",
|
"angular-ui-router": "~0.3.1",
|
||||||
"angular-ui-tinymce": "~0.0.17",
|
|
||||||
"angular-ui-tree": "~2.22.0",
|
"angular-ui-tree": "~2.22.0",
|
||||||
"angular-xeditable": "~0.5.0",
|
"angular-xeditable": "~0.5.0",
|
||||||
"bootstrap-css-only": "~3.3.6",
|
"bootstrap-css-only": "~3.3.6",
|
||||||
"bootstrap-ui-datetime-picker": "~2.4.0",
|
"bootstrap-ui-datetime-picker": "~2.4.0",
|
||||||
|
"ckeditor": "~4.6.1",
|
||||||
"docxtemplater": "~2.1.5",
|
"docxtemplater": "~2.1.5",
|
||||||
"font-awesome-bower": "~4.5.0",
|
"font-awesome-bower": "~4.5.0",
|
||||||
"jquery.cookie": "~1.4.1",
|
"jquery.cookie": "~1.4.1",
|
||||||
@ -35,9 +36,7 @@
|
|||||||
"ngstorage": "~0.3.11",
|
"ngstorage": "~0.3.11",
|
||||||
"ngBootbox": "~0.1.3",
|
"ngBootbox": "~0.1.3",
|
||||||
"pdfmake": "bpampuch/pdfmake#214ec161c11fadb8f02c08f2e3bea0576ac4c9fb",
|
"pdfmake": "bpampuch/pdfmake#214ec161c11fadb8f02c08f2e3bea0576ac4c9fb",
|
||||||
"roboto-fontface": "~0.6.0",
|
"roboto-fontface": "~0.6.0"
|
||||||
"tinymce": "~4.4.3",
|
|
||||||
"tinymce-i18n": "OpenSlides/tinymce-i18n#a186ad61e0aa30fdf657e88f405f966d790f0805"
|
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"pdfmake": {
|
"pdfmake": {
|
||||||
@ -56,13 +55,6 @@
|
|||||||
"fonts/Roboto-Condensed/Roboto-Condensed-Regular.woff",
|
"fonts/Roboto-Condensed/Roboto-Condensed-Regular.woff",
|
||||||
"fonts/Roboto-Condensed/Roboto-Condensed-Light.woff"
|
"fonts/Roboto-Condensed/Roboto-Condensed-Light.woff"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"tinymce": {
|
|
||||||
"main": [
|
|
||||||
"tinymce.js",
|
|
||||||
"themes/modern/theme.js",
|
|
||||||
"plugins/*/plugin.js"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
87
gulpfile.js
87
gulpfile.js
@ -108,37 +108,59 @@ gulp.task('angular-chosen-img', function () {
|
|||||||
.pipe(gulp.dest(path.join(output_directory, 'css')));
|
.pipe(gulp.dest(path.join(output_directory, 'css')));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Catches all skins files for TinyMCE editor.
|
// CKEditor defaults
|
||||||
gulp.task('tinymce-skins', function () {
|
gulp.task('ckeditor-defaults', function () {
|
||||||
return gulp.src(path.join('bower_components', 'tinymce', '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([
|
return gulp.src([
|
||||||
'bower_components/tinymce-i18n/langs/en_GB.js',
|
path.join('bower_components', 'ckeditor', 'styles.js'),
|
||||||
'bower_components/tinymce-i18n/langs/cs.js',
|
path.join('bower_components', 'ckeditor', 'contents.css'),
|
||||||
'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) {
|
.pipe(gulp.dest(path.join(output_directory, 'ckeditor')));
|
||||||
if (path.basename === 'en_GB') {
|
|
||||||
path.basename = 'en';
|
|
||||||
} else if (path.basename === 'fr_FR') {
|
|
||||||
path.basename = 'fr';
|
|
||||||
} else if (path.basename === 'pt_PT') {
|
|
||||||
path.basename = 'pt';
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.pipe(gulpif(argv.production, uglify()))
|
|
||||||
.pipe(gulp.dest(path.join(output_directory, 'tinymce', 'i18n')));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Combines all TinyMCE related tasks.
|
// CKEditor skins
|
||||||
gulp.task('tinymce', ['tinymce-skins', 'tinymce-i18n'], function () {});
|
gulp.task('ckeditor-skins', function () {
|
||||||
|
return gulp.src([
|
||||||
|
path.join('bower_components', 'ckeditor', 'skins', 'moono-lisa', '**', '*'),
|
||||||
|
],{ base: path.join('bower_components', 'ckeditor', 'skins') })
|
||||||
|
.pipe(gulp.dest(path.join(output_directory, 'ckeditor', 'skins')));
|
||||||
|
});
|
||||||
|
|
||||||
|
// CKEditor plugins
|
||||||
|
gulp.task('ckeditor-plugins', function () {
|
||||||
|
return gulp.src([
|
||||||
|
path.join('bower_components', 'ckeditor', 'plugins', 'clipboard', '**', '*'),
|
||||||
|
path.join('bower_components', 'ckeditor', 'plugins', 'colorbutton', '**', '*'),
|
||||||
|
path.join('bower_components', 'ckeditor', 'plugins', 'dialog', '**', '*'),
|
||||||
|
path.join('bower_components', 'ckeditor', 'plugins', 'find', '**', '*'),
|
||||||
|
path.join('bower_components', 'ckeditor', 'plugins', 'image', '**', '*'),
|
||||||
|
path.join('bower_components', 'ckeditor', 'plugins', 'justify', '**', '*'),
|
||||||
|
path.join('bower_components', 'ckeditor', 'plugins', 'liststyle', '**', '*'),
|
||||||
|
path.join('bower_components', 'ckeditor', 'plugins', 'magicline', '**', '*'),
|
||||||
|
path.join('bower_components', 'ckeditor', 'plugins', 'pastefromword', '**', '*'),
|
||||||
|
path.join('bower_components', 'ckeditor', 'plugins', 'panelbutton', '**', '*'),
|
||||||
|
path.join('bower_components', 'ckeditor', 'plugins', 'showblocks', '**', '*'),
|
||||||
|
path.join('bower_components', 'ckeditor', 'plugins', 'sourcedialog', '**', '*'),
|
||||||
|
path.join('bower_components', 'ckeditor', 'plugins', 'table', '**', '*'),
|
||||||
|
path.join('bower_components', 'ckeditor', 'plugins', 'tabeltools', '**', '*'),
|
||||||
|
],{ base: path.join('bower_components', 'ckeditor', 'plugins') })
|
||||||
|
.pipe(gulp.dest(path.join(output_directory, 'ckeditor', 'plugins')));
|
||||||
|
});
|
||||||
|
|
||||||
|
// CKEditor languages
|
||||||
|
gulp.task('ckeditor-lang', function () {
|
||||||
|
return gulp.src([
|
||||||
|
path.join('bower_components', 'ckeditor', 'lang', 'en.js'),
|
||||||
|
path.join('bower_components', 'ckeditor', 'lang', 'de.js'),
|
||||||
|
path.join('bower_components', 'ckeditor', 'lang', 'pt.js'),
|
||||||
|
path.join('bower_components', 'ckeditor', 'lang', 'es.js'),
|
||||||
|
path.join('bower_components', 'ckeditor', 'lang', 'fr.js'),
|
||||||
|
path.join('bower_components', 'ckeditor', 'lang', 'cs.js'),
|
||||||
|
])
|
||||||
|
.pipe(gulp.dest(path.join(output_directory, 'ckeditor', 'lang')));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Combines all CKEditor related tasks.
|
||||||
|
gulp.task('ckeditor', ['ckeditor-defaults', 'ckeditor-skins', 'ckeditor-plugins', 'ckeditor-lang'], function () {});
|
||||||
|
|
||||||
// Compiles translation files (*.po) to *.json and saves them in the directory
|
// Compiles translation files (*.po) to *.json and saves them in the directory
|
||||||
// openslides/static/i18n/.
|
// openslides/static/i18n/.
|
||||||
@ -151,7 +173,16 @@ gulp.task('translations', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Gulp default task. Runs all other tasks before.
|
// Gulp default task. Runs all other tasks before.
|
||||||
gulp.task('default', ['js', 'js-libs', 'templates', 'css-libs', 'fonts-libs', 'tinymce', 'angular-chosen-img', 'translations'], function () {});
|
gulp.task('default', [
|
||||||
|
'js',
|
||||||
|
'js-libs',
|
||||||
|
'templates',
|
||||||
|
'css-libs',
|
||||||
|
'fonts-libs',
|
||||||
|
'ckeditor',
|
||||||
|
'angular-chosen-img',
|
||||||
|
'translations'
|
||||||
|
], function () {});
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -548,33 +548,113 @@ angular.module('OpenSlidesApp.core', [
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
// Options for TinyMCE editor used in various create and edit views.
|
|
||||||
|
// Configs for CKEditor which has to set while startup of OpenSlides
|
||||||
|
.config(
|
||||||
|
function() {
|
||||||
|
CKEDITOR.disableAutoInline = true;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Options for CKEditor used in various create and edit views.
|
||||||
// Required in core/base.js because MotionComment factory which used this
|
// Required in core/base.js because MotionComment factory which used this
|
||||||
// factory has to placed in motions/base.js.
|
// factory has to placed in motions/base.js.
|
||||||
.factory('Editor', [
|
.factory('Editor', [
|
||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
function (gettextCatalog) {
|
function (gettextCatalog) {
|
||||||
return {
|
return {
|
||||||
getOptions: function (images, inlineMode) {
|
getOptions: function (images) {
|
||||||
if (inlineMode === undefined) {
|
|
||||||
inlineMode = false;
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
language_url: '/static/tinymce/i18n/' + gettextCatalog.getCurrentLanguage() + '.js',
|
on: {
|
||||||
theme_url: '/static/js/openslides-libs.js',
|
instanceReady: function() {
|
||||||
skin_url: '/static/tinymce/skins/lightgray/',
|
// This adds a listener to ckeditor to remove unwanted blank lines on import.
|
||||||
inline: inlineMode,
|
// Clipboard content varies heavily in structure and html code, depending on the "sender".
|
||||||
browser_spellcheck: true,
|
// Here it is first parsed into a pseudo-DOM (two lines taken from a ckeditor
|
||||||
image_advtab: true,
|
// paste example on the ckeditor site).
|
||||||
image_list: images,
|
this.on('paste', function(evt) {
|
||||||
plugins: [
|
if (evt.data.type == 'html') {
|
||||||
'lists link autolink charmap preview searchreplace code fullscreen',
|
var fragment = CKEDITOR.htmlParser.fragment.fromHtml(evt.data.dataValue);
|
||||||
'paste textcolor colorpicker image imagetools wordcount'
|
var writer = new CKEDITOR.htmlParser.basicWriter();
|
||||||
],
|
// html content will now be in a dom-like structure inside 'fragment'.
|
||||||
menubar: '',
|
this.filter.applyTo(fragment);
|
||||||
toolbar: 'undo redo searchreplace | styleselect | bold italic underline strikethrough ' +
|
if (fragment.children) {
|
||||||
'forecolor backcolor removeformat | bullist numlist | outdent indent | ' +
|
// If this fragment is DOM-like, it may contain nested properties
|
||||||
'link image charmap table | code preview fullscreen'
|
// (being html nodes). Traverse the children and check if it is a
|
||||||
|
// child only containing empty <br> or <p>.
|
||||||
|
// new_content_children will finally contain all nodes that are
|
||||||
|
// not empty.
|
||||||
|
var new_content_children = [];
|
||||||
|
_.forEach(fragment.children, function (child) {
|
||||||
|
var empty = true;
|
||||||
|
if (child.children){
|
||||||
|
_.forEach(child.children, function(grandchild) {
|
||||||
|
if (grandchild.name != 'p' && grandchild.name != 'br') {
|
||||||
|
empty = false;
|
||||||
|
} else if (grandchild.isEmpty !== true) {
|
||||||
|
empty = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (empty === false) {
|
||||||
|
new_content_children.push(child);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (child.name != 'p' && child.name != 'br' &&
|
||||||
|
child.isEmpty !== true){
|
||||||
|
new_content_children.push(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fragment.children = new_content_children;
|
||||||
|
}
|
||||||
|
fragment.writeHtml(writer);
|
||||||
|
// Return the re-created fragment without the empty <p> and <br> into the
|
||||||
|
// editor import processing (same as at the begin of the function: by ckeditor)
|
||||||
|
evt.data.dataValue = writer.getHtml();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
customConfig: '',
|
||||||
|
disableNativeSpellChecker: false,
|
||||||
|
language_list: [
|
||||||
|
'fr:français',
|
||||||
|
'es:español',
|
||||||
|
'pt:português',
|
||||||
|
'en:english',
|
||||||
|
'de:deutsch',
|
||||||
|
'cs:čeština'],
|
||||||
|
language: gettextCatalog.getCurrentLanguage(),
|
||||||
|
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};' +
|
||||||
|
'a[!href];' +
|
||||||
|
'img[!src,alt]{width,height,float};' +
|
||||||
|
'tr th td caption;' +
|
||||||
|
'li; ol[start]{list-style-type};' +
|
||||||
|
'ul{list-style};' +
|
||||||
|
'span[data-line-number,contenteditable]{color,background-color}(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.
|
||||||
|
extraPlugins: 'colorbutton,find,liststyle,sourcedialog,justify,showblocks',
|
||||||
|
removePlugins: 'wsc,scayt,a11yhelp,filebrowser,sourcearea',
|
||||||
|
removeButtons: 'Scayt,Anchor,Styles,HorizontalRule',
|
||||||
|
toolbarGroups: [
|
||||||
|
{ name: 'clipboard', groups: [ 'clipboard', 'undo' ] },
|
||||||
|
{ name: 'editing', groups: [ 'find', 'selection', 'spellchecker', 'editing' ] },
|
||||||
|
{ name: 'links', groups: [ 'links' ] },
|
||||||
|
{ name: 'insert', groups: [ 'insert' ] },
|
||||||
|
{ name: 'tools', groups: [ 'tools' ] },
|
||||||
|
{ name: 'document', groups: [ 'mode' ] },
|
||||||
|
'/',
|
||||||
|
{ name: 'styles', groups: [ 'styles' ] },
|
||||||
|
{ name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
|
||||||
|
{ name: 'colors', groups: [ 'colors' ] },
|
||||||
|
{ name: 'paragraph', groups: [ 'list', 'indent' ] },
|
||||||
|
{ name: 'align'},
|
||||||
|
{ name: 'paragraph', groups: [ 'blocks' ] }
|
||||||
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -18,7 +18,7 @@ angular.module('OpenSlidesApp.core.site', [
|
|||||||
'ngMessages',
|
'ngMessages',
|
||||||
'ngCsvImport',
|
'ngCsvImport',
|
||||||
'ngStorage',
|
'ngStorage',
|
||||||
'ui.tinymce',
|
'ckeditor',
|
||||||
'luegg.directives',
|
'luegg.directives',
|
||||||
'xeditable',
|
'xeditable',
|
||||||
])
|
])
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
<!-- custom angular formly template for tinymce textarea field -->
|
<!-- custom angular formly template for ckeditor textarea field -->
|
||||||
<textarea ui-tinymce="options.data.tinymceOption" ng-model="model[options.key]" class="form-control"></textarea>
|
<textarea ckeditor="options.data.ckeditorOptions" ng-model="model[options.key]" class="form-control"></textarea>
|
@ -10,6 +10,10 @@
|
|||||||
<link rel="stylesheet" href="static/css/openslides-libs.css">
|
<link rel="stylesheet" href="static/css/openslides-libs.css">
|
||||||
<link rel="stylesheet" href="static/css/app.css">
|
<link rel="stylesheet" href="static/css/app.css">
|
||||||
<link rel="icon" href="/static/img/favicon.png">
|
<link rel="icon" href="/static/img/favicon.png">
|
||||||
|
<!-- TODO (#2787): move into openslides-libs-->
|
||||||
|
<script>
|
||||||
|
window.CKEDITOR_BASEPATH = '/static/ckeditor/';
|
||||||
|
</script>
|
||||||
<script src="static/js/openslides-libs.js"></script>
|
<script src="static/js/openslides-libs.js"></script>
|
||||||
<script src="static/js/openslides.js"></script>
|
<script src="static/js/openslides.js"></script>
|
||||||
<script src="static/js/openslides-templates.js"></script>
|
<script src="static/js/openslides-templates.js"></script>
|
||||||
|
@ -546,7 +546,7 @@ angular.module('OpenSlidesApp.motions', [
|
|||||||
label: field.name,
|
label: field.name,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
tinymceOption: Editor.getOptions()
|
ckeditorOptions: Editor.getOptions()
|
||||||
},
|
},
|
||||||
hide: !operator.hasPerms("motions.can_see_and_manage_comments")
|
hide: !operator.hasPerms("motions.can_see_and_manage_comments")
|
||||||
};
|
};
|
||||||
|
@ -82,7 +82,7 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
|
|||||||
node.setAttribute('class', 'os-line-number line-number-' + lineNumber);
|
node.setAttribute('class', 'os-line-number line-number-' + lineNumber);
|
||||||
node.setAttribute('data-line-number', lineNumber + '');
|
node.setAttribute('data-line-number', lineNumber + '');
|
||||||
node.setAttribute('contenteditable', 'false');
|
node.setAttribute('contenteditable', 'false');
|
||||||
node.innerHTML = ' '; // Prevent tinymce from stripping out empty span's
|
node.innerHTML = ' '; // Prevent ckeditor from stripping out empty span's
|
||||||
return node;
|
return node;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -302,6 +302,15 @@ angular.module('OpenSlidesApp.motions.lineNumbering', [])
|
|||||||
|
|
||||||
for (i = 0; i < oldChildren.length; i++) {
|
for (i = 0; i < oldChildren.length; i++) {
|
||||||
if (oldChildren[i].nodeType == TEXT_NODE) {
|
if (oldChildren[i].nodeType == TEXT_NODE) {
|
||||||
|
if (!oldChildren[i].nodeValue.match(/\S/)) {
|
||||||
|
// White space nodes between block elements should be ignored
|
||||||
|
var prevIsBlock = (i > 0 && !this._isInlineElement(oldChildren[i - 1]));
|
||||||
|
var nextIsBlock = (i < oldChildren.length - 1 && !this._isInlineElement(oldChildren[i + 1]));
|
||||||
|
if ((prevIsBlock && nextIsBlock) || (i === 0 && nextIsBlock) || (i === oldChildren.length - 1 && prevIsBlock)) {
|
||||||
|
node.appendChild(oldChildren[i]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
var ret = this._textNodeToLines(oldChildren[i], length, highlight);
|
var ret = this._textNodeToLines(oldChildren[i], length, highlight);
|
||||||
for (var j = 0; j < ret.length; j++) {
|
for (var j = 0; j < ret.length; j++) {
|
||||||
node.appendChild(ret[j]);
|
node.appendChild(ret[j]);
|
||||||
|
@ -61,13 +61,12 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions',
|
|||||||
'Motion',
|
'Motion',
|
||||||
'Config',
|
'Config',
|
||||||
'$timeout',
|
'$timeout',
|
||||||
function (Editor, Motion, Config, $timeout) {
|
'gettextCatalog',
|
||||||
|
function (Editor, Motion, Config, $timeout, gettextCatalog) {
|
||||||
var obj = {
|
var obj = {
|
||||||
active: false,
|
active: false,
|
||||||
changed: false,
|
changed: false,
|
||||||
trivialChange: false,
|
trivialChange: false,
|
||||||
editor: null,
|
|
||||||
lineBrokenText: null,
|
|
||||||
originalHtml: null
|
originalHtml: null
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -76,62 +75,77 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions',
|
|||||||
obj.init = function (_scope, _motion) {
|
obj.init = function (_scope, _motion) {
|
||||||
$scope = _scope;
|
$scope = _scope;
|
||||||
motion = _motion;
|
motion = _motion;
|
||||||
obj.lineBrokenText = motion.getTextWithLineBreaks($scope.version);
|
obj.ckeditorOptions = Editor.getOptions();
|
||||||
obj.originalHtml = obj.lineBrokenText;
|
obj.ckeditorOptions.readOnly = true;
|
||||||
};
|
obj.isEditable = false;
|
||||||
|
|
||||||
|
|
||||||
obj.tinymceOptions = Editor.getOptions(null, true);
|
|
||||||
obj.tinymceOptions.readonly = 1;
|
|
||||||
obj.tinymceOptions.setup = function (editor) {
|
|
||||||
obj.editor = editor;
|
|
||||||
editor.on('init', function () {
|
|
||||||
obj.lineBrokenText = motion.getTextWithLineBreaks($scope.version);
|
|
||||||
obj.editor.setContent(obj.lineBrokenText);
|
|
||||||
obj.originalHtml = obj.editor.getContent();
|
|
||||||
obj.changed = false;
|
obj.changed = false;
|
||||||
});
|
|
||||||
editor.on('change', function () {
|
|
||||||
obj.changed = (editor.getContent() != obj.originalHtml);
|
|
||||||
});
|
|
||||||
editor.on('undo', function () {
|
|
||||||
obj.changed = (editor.getContent() != obj.originalHtml);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
obj.setVersion = function (_motion, versionId) {
|
obj.setVersion = function (_motion, versionId) {
|
||||||
motion = _motion; // If this is not updated,
|
motion = _motion; // If this is not updated,
|
||||||
obj.lineBrokenText = motion.getTextWithLineBreaks(versionId);
|
obj.originalHtml = motion.getTextWithLineBreaks(versionId);
|
||||||
obj.changed = false;
|
obj.changed = false;
|
||||||
obj.active = false;
|
obj.editor.setReadOnly(true);
|
||||||
if (obj.editor) {
|
if (obj.editor) {
|
||||||
obj.editor.setContent(obj.lineBrokenText);
|
obj.editor.setData(obj.originalHtml);
|
||||||
obj.editor.setMode('readonly');
|
|
||||||
obj.originalHtml = obj.editor.getContent();
|
|
||||||
} else {
|
|
||||||
obj.originalHtml = obj.lineBrokenText;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
obj.enable = function () {
|
obj.enable = function () {
|
||||||
obj.editor.setMode('design');
|
if (motion.isAllowed('update')) {
|
||||||
obj.active = true;
|
obj.active = true;
|
||||||
|
obj.isEditable = true;
|
||||||
|
obj.ckeditorOptions.language = gettextCatalog.getCurrentLanguage();
|
||||||
|
obj.editor = CKEDITOR.inline('view-original-inline-editor', obj.ckeditorOptions);
|
||||||
|
obj.editor.on('change', function () {
|
||||||
|
$timeout(function() {
|
||||||
|
if (obj.editor.getData() != obj.originalHtml) {
|
||||||
|
obj.changed = true;
|
||||||
|
} else {
|
||||||
obj.changed = false;
|
obj.changed = false;
|
||||||
|
}
|
||||||
obj.lineBrokenText = motion.getTextWithLineBreaks($scope.version);
|
});
|
||||||
obj.editor.setContent(obj.lineBrokenText);
|
});
|
||||||
obj.originalHtml = obj.editor.getContent();
|
obj.revert();
|
||||||
$timeout(function () {
|
} else {
|
||||||
obj.editor.focus();
|
obj.disable();
|
||||||
}, 100);
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
obj.disable = function () {
|
obj.disable = function () {
|
||||||
obj.editor.setMode('readonly');
|
if (obj.editor) {
|
||||||
|
obj.editor.setReadOnly(true);
|
||||||
|
obj.editor.setData(obj.originalHtml, {
|
||||||
|
callback: function() {
|
||||||
|
obj.editor.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$timeout(function() {
|
||||||
obj.active = false;
|
obj.active = false;
|
||||||
obj.changed = false;
|
obj.changed = false;
|
||||||
obj.lineBrokenText = obj.originalHtml;
|
obj.isEditable = false;
|
||||||
obj.editor.setContent(obj.originalHtml);
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// sets editor content to the initial motion state
|
||||||
|
obj.revert = function() {
|
||||||
|
if (obj.editor) {
|
||||||
|
obj.originalHtml = motion.getTextWithLineBreaks($scope.version);
|
||||||
|
obj.editor.setData(
|
||||||
|
motion.getTextWithLineBreaks($scope.version), {
|
||||||
|
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 () {
|
obj.save = function () {
|
||||||
@ -139,7 +153,7 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions',
|
|||||||
throw 'No permission to update motion';
|
throw 'No permission to update motion';
|
||||||
}
|
}
|
||||||
|
|
||||||
motion.setTextStrippingLineBreaks(obj.editor.getContent());
|
motion.setTextStrippingLineBreaks(obj.editor.getData());
|
||||||
motion.disable_versioning = (obj.trivialChange && Config.get('motions_allow_disable_versioning').value);
|
motion.disable_versioning = (obj.trivialChange && Config.get('motions_allow_disable_versioning').value);
|
||||||
|
|
||||||
Motion.inject(motion);
|
Motion.inject(motion);
|
||||||
@ -147,11 +161,13 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions',
|
|||||||
Motion.save(motion, {method: 'PATCH'}).then(
|
Motion.save(motion, {method: 'PATCH'}).then(
|
||||||
function (success) {
|
function (success) {
|
||||||
$scope.showVersion(motion.getVersion(-1));
|
$scope.showVersion(motion.getVersion(-1));
|
||||||
|
obj.revert();
|
||||||
},
|
},
|
||||||
function (error) {
|
function (error) {
|
||||||
// save error: revert all changes by restore
|
// save error: revert all changes by restore
|
||||||
// (refresh) original motion object from server
|
// (refresh) original motion object from server
|
||||||
Motion.refresh(motion);
|
Motion.refresh(motion);
|
||||||
|
obj.revert();
|
||||||
var message = '';
|
var message = '';
|
||||||
for (var e in error.data) {
|
for (var e in error.data) {
|
||||||
message += e + ': ' + error.data[e] + ' ';
|
message += e + ': ' + error.data[e] + ' ';
|
||||||
|
@ -397,7 +397,7 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
tinymceOption: Editor.getOptions()
|
ckeditorOptions: Editor.getOptions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@ -492,7 +492,7 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
tinymceOption: Editor.getOptions(images)
|
ckeditorOptions: Editor.getOptions(images)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -502,7 +502,7 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
label: gettextCatalog.getString('Reason'),
|
label: gettextCatalog.getString('Reason'),
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
tinymceOption: Editor.getOptions(images)
|
ckeditorOptions: Editor.getOptions(images)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1,30 +1,23 @@
|
|||||||
<!-- Original view, Inline Editing is possible -->
|
<!-- Original view -->
|
||||||
<div ng-if="viewChangeRecommendations.mode == 'original' && motion.isAllowed('update') && version == motion.getVersion(-1).id">
|
<div ng-if="viewChangeRecommendations.mode == 'original' && version == motion.getVersion(-1).id">
|
||||||
<div ng-show="inlineEditing.active">
|
<div id="view-original-inline-editor" ng-bind-html="motion.getTextWithLineBreaks(version, highlight) | trusted"
|
||||||
<div ui-tinymce="inlineEditing.tinymceOptions" ng-model="inlineEditing.lineBrokenText"
|
class="motion-text motion-text-original line-numbers-{{ lineNumberMode }}"
|
||||||
class="motion-text line-numbers-{{ lineNumberMode }}"></div>
|
contenteditable= "{{ inlineEditing.isEditable }}">
|
||||||
</div>
|
</div>
|
||||||
<div ng-show="!inlineEditing.active" ng-bind-html="motion.getTextWithLineBreaks(version, highlight) | trusted"
|
|
||||||
class="motion-text motion-text-original line-numbers-{{ lineNumberMode }}"></div>
|
|
||||||
|
|
||||||
<div class="motion-save-toolbar" ng-class="{ 'visible': (inlineEditing.changed && inlineEditing.active) }">
|
<div class="motion-save-toolbar" ng-class="{ 'visible': (inlineEditing.changed && inlineEditing.active) }">
|
||||||
<div class="changed-hint" translate>The text has been changed.</div>
|
<div class="changed-hint" translate>The text has been changed.</div>
|
||||||
<button type="button" ng-click="inlineEditing.save()" class="btn btn-primary" translate>
|
<button type="button" ng-click="inlineEditing.save()" class="btn btn-primary" translate>
|
||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
|
<button type="button" ng-click="inlineEditing.revert()" class="btn btn-primary" translate>
|
||||||
|
Revert
|
||||||
|
</button>
|
||||||
<label ng-if="motion.state.versioning && config('motions_allow_disable_versioning')">
|
<label ng-if="motion.state.versioning && config('motions_allow_disable_versioning')">
|
||||||
<input type="checkbox" ng-model="inlineEditing.trivialChange" value="1">
|
<input type="checkbox" ng-model="inlineEditing.trivialChange" value="1">
|
||||||
<span translate>Trivial change</span>
|
<span translate>Trivial change</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Original view, Inline Editing is NOT possible -->
|
|
||||||
<div ng-if="viewChangeRecommendations.mode == 'original' && !(motion.isAllowed('update') && version == motion.getVersion(-1).id)">
|
|
||||||
<div ng-bind-html="motion.getTextWithLineBreaks(version, highlight) | trusted"
|
|
||||||
class="motion-text motion-text-original line-numbers-{{ lineNumberMode }}"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Original view, Change list -->
|
<!-- Original view, Change list -->
|
||||||
<ul ng-if="viewChangeRecommendations.mode == 'original'" ng-show="lineNumberMode != 'none'"
|
<ul ng-if="viewChangeRecommendations.mode == 'original'" ng-show="lineNumberMode != 'none'"
|
||||||
class="change-recommendation-list">
|
class="change-recommendation-list">
|
||||||
|
@ -118,7 +118,7 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics'])
|
|||||||
label: gettextCatalog.getString('Text')
|
label: gettextCatalog.getString('Text')
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
tinymceOption: Editor.getOptions(images)
|
ckeditorOptions: Editor.getOptions(images)
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
// attachments
|
// attachments
|
||||||
|
@ -446,7 +446,7 @@ angular.module('OpenSlidesApp.users.site', [
|
|||||||
label: gettextCatalog.getString('About me'),
|
label: gettextCatalog.getString('About me'),
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
tinymceOption: Editor.getOptions(images)
|
ckeditorOptions: Editor.getOptions(images)
|
||||||
},
|
},
|
||||||
hideExpression: '!model.more'
|
hideExpression: '!model.more'
|
||||||
}
|
}
|
||||||
@ -801,7 +801,7 @@ angular.module('OpenSlidesApp.users.site', [
|
|||||||
'user',
|
'user',
|
||||||
function($scope, $state, Editor, User, user) {
|
function($scope, $state, Editor, User, user) {
|
||||||
$scope.user = user; // autoupdate is not activated
|
$scope.user = user; // autoupdate is not activated
|
||||||
$scope.tinymceOption = Editor.getOptions();
|
$scope.ckeditorOptions = Editor.getOptions();
|
||||||
$scope.save = function (user) {
|
$scope.save = function (user) {
|
||||||
User.save(user).then(
|
User.save(user).then(
|
||||||
function(success) {
|
function(success) {
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="textAbout" translate>About me</label>
|
<label for="textAbout" translate>About me</label>
|
||||||
<textarea ng-model="user.about_me" ui-tinymce="tinymceOption" class="form-control" name="textAbout" />
|
<textarea ng-model="user.about_me" class="form-control" name="textAbout" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" ng-click="save(user)" class="btn btn-primary" translate>
|
<button type="submit" ng-click="save(user)" class="btn btn-primary" translate>
|
||||||
|
@ -251,4 +251,18 @@ describe('linenumbering', function () {
|
|||||||
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
|
expect(lineNumberingService.stripLineNumbers(outHtml)).toBe(inHtml);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('behavior regarding ckeditor', function() {
|
||||||
|
it('does not count empty lines, case 1', function () {
|
||||||
|
var inHtml = "<p>Line 1</p>\n\n<p>Line 2</p>";
|
||||||
|
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 80);
|
||||||
|
expect(outHtml).toBe('<p>' + noMarkup(1) + 'Line 1</p>' + "\n\n" + '<p>' + noMarkup(2) + 'Line 2</p>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not count empty lines, case 2', function () {
|
||||||
|
var inHtml = "<ul>\n\n<li>Point 1</li>\n\n</ul>";
|
||||||
|
var outHtml = lineNumberingService.insertLineNumbers(inHtml, 80);
|
||||||
|
expect(outHtml).toBe("<ul>\n\n<li>" + noMarkup(1) + "Point 1</li>\n\n</ul>");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user