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
This commit is contained in:
parent
ad7653fb76
commit
16f1ad5731
13
bower.json
13
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": {
|
||||
|
28
gulpfile.js
28
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 () {});
|
||||
|
||||
|
||||
/**
|
||||
|
@ -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) {
|
||||
|
@ -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([
|
||||
|
@ -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',
|
||||
|
@ -30,7 +30,7 @@
|
||||
</div>
|
||||
|
||||
<div class="details">
|
||||
<div ng-bind-html="customslide.text"></div>
|
||||
<div ng-bind-html="customslide.text | trusted"></div>
|
||||
<h3 ng-if="customslide.attachments.length > 0" translate>Attachments</h3>
|
||||
<ul>
|
||||
<li ng-repeat="attachment in customslide.attachments">
|
||||
|
2
openslides/core/static/templates/core/editor.html
Normal file
2
openslides/core/static/templates/core/editor.html
Normal file
@ -0,0 +1,2 @@
|
||||
<!-- custom angular formly template for tinymce textarea field -->
|
||||
<textarea ui-tinymce="options.data.tinymceOption" ng-model="model[options.key]" class="form-control"></textarea>
|
@ -1,4 +1,4 @@
|
||||
<div ng-controller="SlideCustomSlideCtrl" class="content scrollcontent">
|
||||
<h1>{{ customslide.agenda_item.getTitle() }}</h1>
|
||||
<div ng-bind-html="customslide.text"></div>
|
||||
<div ng-bind-html="customslide.text | trusted"></div>
|
||||
</div>
|
||||
|
@ -10,7 +10,6 @@
|
||||
<link rel="stylesheet" href="static/css/app.css">
|
||||
<link rel="icon" href="/static/img/favicon.png">
|
||||
<script src="static/js/openslides-libs.js"></script>
|
||||
<script src="static/ckeditor/ckeditor.js"></script>
|
||||
|
||||
<div id="wrapper" ng-cloak>
|
||||
|
||||
|
@ -12,6 +12,15 @@ angular.module('OpenSlidesApp.mediafiles', [])
|
||||
return DS.defineResource({
|
||||
name: name,
|
||||
useClass: jsDataModel,
|
||||
getAllImages: function () {
|
||||
var images = []
|
||||
angular.forEach(this.getAll(), function(file) {
|
||||
if (file.is_image) {
|
||||
images.push({title: file.title, value: file.mediafileUrl});
|
||||
}
|
||||
});
|
||||
return images;
|
||||
},
|
||||
methods: {
|
||||
getResourceName: function () {
|
||||
return name;
|
||||
@ -30,6 +39,10 @@ angular.module('OpenSlidesApp.mediafiles', [])
|
||||
var PRESENTABLE_FILE_TYPES = ['application/pdf'];
|
||||
return _.contains(PRESENTABLE_FILE_TYPES, filetype);
|
||||
}],
|
||||
is_image: ['filetype', function (filetype) {
|
||||
var IMAGE_FILE_TYPES = ['image/png', 'image/jpeg', 'image/gif'];
|
||||
return _.contains(IMAGE_FILE_TYPES, filetype);
|
||||
}],
|
||||
mediafileUrl: [function () {
|
||||
return this.media_url_prefix + this.mediafile.name;
|
||||
}],
|
||||
|
@ -1,4 +1,5 @@
|
||||
import random
|
||||
import re
|
||||
from html import escape
|
||||
from operator import attrgetter
|
||||
|
||||
@ -153,6 +154,23 @@ def motion_to_pdf(pdf, motion):
|
||||
def convert_html_to_reportlab(pdf, text):
|
||||
# parsing and replacing not supported html tags for reportlab...
|
||||
soup = BeautifulSoup(text, "html5lib")
|
||||
|
||||
# number ol list elements
|
||||
ols = soup.find_all('ol')
|
||||
for ol in ols:
|
||||
counter = 0
|
||||
for li in ol.children:
|
||||
if li.name == 'li':
|
||||
# if start attribute is available set counter for first list element
|
||||
if li.parent.get('start') and not li.find_previous_sibling():
|
||||
counter = int(ol.get('start'))
|
||||
else:
|
||||
counter += 1
|
||||
if li.get('value'):
|
||||
counter = li.get('value')
|
||||
else:
|
||||
li['value'] = counter
|
||||
|
||||
# read all list elements...
|
||||
for element in soup.find_all('li'):
|
||||
# ... and replace ul list elements with <para><bullet>•</bullet>...<para>
|
||||
@ -167,22 +185,32 @@ def convert_html_to_reportlab(pdf, text):
|
||||
bullet_tag = soup.new_tag("bullet")
|
||||
bullet_tag.string = u"•"
|
||||
element.insert(0, bullet_tag)
|
||||
# ... and replace ol list elements with <para><bullet><seq id="%id"></seq>.</bullet>...</para>
|
||||
# ... and replace ol list elements with <para><bullet><seqreset id="%id" base="value"><seq id="%id"></seq>.</bullet>...</para>
|
||||
if element.parent.name == "ol":
|
||||
counter = None
|
||||
# set list id if element is the first of numbered list
|
||||
if not element.find_previous_sibling():
|
||||
id = random.randrange(0, 101)
|
||||
if element.parent.get('start'):
|
||||
counter = element.parent.get('start')
|
||||
if element.get('value'):
|
||||
counter = element.get('value')
|
||||
# nested lists
|
||||
if element.ul or element.ol:
|
||||
for i in element.find_all('li'):
|
||||
element.insert_before(i)
|
||||
element.clear()
|
||||
else:
|
||||
element.name = "para"
|
||||
element.insert(0, soup.new_tag("bullet"))
|
||||
element.bullet.insert(0, soup.new_tag("seq"))
|
||||
element.bullet.seq['id'] = id
|
||||
element.bullet.insert(1, ".")
|
||||
nested_list = element.find_all('li')
|
||||
for i in reversed(nested_list):
|
||||
element.insert_after(i)
|
||||
|
||||
element.attrs = {}
|
||||
element.name = "para"
|
||||
element.insert(0, soup.new_tag("bullet"))
|
||||
element.bullet.insert(0, soup.new_tag("seq"))
|
||||
element.bullet.seq['id'] = id
|
||||
if counter:
|
||||
element.bullet.insert(0, soup.new_tag("seqreset"))
|
||||
element.bullet.seqreset['id'] = id
|
||||
element.bullet.seqreset['base'] = int(counter) - 1
|
||||
element.bullet.insert(2, ".")
|
||||
# remove tags which are not supported by reportlab (replace tags with their children tags)
|
||||
for tag in soup.find_all('ul'):
|
||||
tag.unwrap()
|
||||
@ -190,8 +218,60 @@ def convert_html_to_reportlab(pdf, text):
|
||||
tag.unwrap()
|
||||
for tag in soup.find_all('li'):
|
||||
tag.unwrap()
|
||||
|
||||
# use tags which are supported by reportlab
|
||||
# replace <s> to <strike>
|
||||
for tag in soup.find_all('s'):
|
||||
tag.name = "strike"
|
||||
|
||||
# replace <del> to <strike>
|
||||
for tag in soup.find_all('del'):
|
||||
tag.name = "strike"
|
||||
|
||||
for tag in soup.find_all('a'):
|
||||
# remove a tags without href attribute
|
||||
if not tag.get('href'):
|
||||
tag.extract()
|
||||
for tag in soup.find_all('img'):
|
||||
# remove img tags without src attribute
|
||||
if not tag.get('src'):
|
||||
tag.extract()
|
||||
|
||||
# replace style attributes in <span> tags
|
||||
for tag in soup.find_all('span'):
|
||||
tag.unwrap()
|
||||
if tag.get('style'):
|
||||
# replace style attribute "text-decoration: line-through;" to <strike> tag
|
||||
if 'text-decoration: line-through' in str(tag['style']):
|
||||
strike_tag = soup.new_tag("strike")
|
||||
strike_tag.string = tag.string
|
||||
tag.replace_with(strike_tag)
|
||||
# replace style attribute "text-decoration: underline;" to <u> tag
|
||||
elif 'text-decoration: underline' in str(tag['style']):
|
||||
u_tag = soup.new_tag("u")
|
||||
u_tag.string = tag.string
|
||||
tag.replace_with(u_tag)
|
||||
# replace style attribute "color: #xxxxxx;" to "<font backcolor='#xxxxxx'>...</font>"
|
||||
elif 'background-color: ' in str(tag['style']):
|
||||
font_tag = soup.new_tag("font")
|
||||
color = re.findall('background-color: (.*?);', str(tag['style']))
|
||||
if color:
|
||||
font_tag['backcolor'] = color
|
||||
if tag.string:
|
||||
font_tag.string = tag.string
|
||||
tag.replace_with(font_tag)
|
||||
# replace style attribute "color: #xxxxxx;" to "<font color='#xxxxxx'>...</font>"
|
||||
elif 'color: ' in str(tag['style']):
|
||||
font_tag = soup.new_tag("font")
|
||||
color = re.findall('color: (.*?);', str(tag['style']))
|
||||
if color:
|
||||
font_tag['color'] = color
|
||||
if tag.string:
|
||||
font_tag.string = tag.string
|
||||
tag.replace_with(font_tag)
|
||||
else:
|
||||
tag.unwrap()
|
||||
else:
|
||||
tag.unwrap()
|
||||
# print paragraphs with numbers
|
||||
text = soup.body.contents
|
||||
paragraph_number = 1
|
||||
|
@ -152,18 +152,20 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
.factory('MotionForm', [
|
||||
'gettextCatalog',
|
||||
'operator',
|
||||
'Editor',
|
||||
'Category',
|
||||
'Config',
|
||||
'Mediafile',
|
||||
'Tag',
|
||||
'User',
|
||||
'Workflow',
|
||||
function (gettextCatalog, operator, Category, Config, Mediafile, Tag, User, Workflow) {
|
||||
function (gettextCatalog, operator, Editor, Category, Config, Mediafile, Tag, User, Workflow) {
|
||||
return {
|
||||
// ngDialog for motion form
|
||||
getDialog: function (motion) {
|
||||
var resolve = {}
|
||||
if (motion) {
|
||||
var resolve = {
|
||||
resolve = {
|
||||
motion: function() {
|
||||
return motion;
|
||||
},
|
||||
@ -172,6 +174,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
}
|
||||
};
|
||||
}
|
||||
resolve.mediafiles = function(Mediafile) {return Mediafile.findAll();}
|
||||
return {
|
||||
template: 'static/templates/motions/motion-form.html',
|
||||
controller: (motion) ? 'MotionUpdateCtrl' : 'MotionCreateCtrl',
|
||||
@ -187,6 +190,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
angular.forEach(workflows, function(workflow) {
|
||||
workflow.name = gettextCatalog.getString(workflow.name);
|
||||
});
|
||||
var images = Mediafile.getAllImages();
|
||||
return [
|
||||
{
|
||||
key: 'identifier',
|
||||
@ -220,20 +224,24 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
},
|
||||
{
|
||||
key: 'text',
|
||||
type: 'textarea',
|
||||
type: 'editor',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Text'),
|
||||
required: true
|
||||
},
|
||||
ngModelElAttrs: {'ckeditor': 'CKEditorOptions'}
|
||||
data: {
|
||||
tinymceOption: Editor.getOptions(images)
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'reason',
|
||||
type: 'textarea',
|
||||
type: 'editor',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Reason')
|
||||
label: gettextCatalog.getString('Reason'),
|
||||
},
|
||||
ngModelElAttrs: {'ckeditor': 'CKEditorOptions'}
|
||||
data: {
|
||||
tinymceOption: Editor.getOptions(images)
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'disable_versioning',
|
||||
|
@ -218,12 +218,12 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<h3 translate>Text</h3>
|
||||
<div ng-bind-html="motion.getText(version)"></div>
|
||||
<div ng-bind-html="motion.getText(version) | trusted"></div>
|
||||
|
||||
<!-- reason -->
|
||||
<div ng-if="motion.getReason(version) != ''">
|
||||
<h3 translate>Reason</h3>
|
||||
<div ng-bind-html="motion.getReason()"></div>
|
||||
<div ng-bind-html="motion.getReason() | trusted"></div>
|
||||
</div>
|
||||
|
||||
<!-- attachments -->
|
||||
|
@ -15,4 +15,3 @@
|
||||
</button>
|
||||
</formly-form>
|
||||
</form>
|
||||
|
||||
|
@ -62,9 +62,9 @@
|
||||
</div>
|
||||
|
||||
<!-- Text -->
|
||||
<div ng-bind-html="motion.getText()"></div>
|
||||
<div ng-bind-html="motion.getText() | trusted"></div>
|
||||
|
||||
<!-- Reason -->
|
||||
<h3 ng-if="motion.getReason()" translate>Reason</h3>
|
||||
<div ng-bind-html="motion.getReason()"></div>
|
||||
<div ng-bind-html="motion.getReason() | trusted"></div>
|
||||
</div>
|
||||
|
@ -235,8 +235,10 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users'])
|
||||
.factory('UserForm', [
|
||||
'$http',
|
||||
'gettextCatalog',
|
||||
'Editor',
|
||||
'Group',
|
||||
function ($http, gettextCatalog, Group) {
|
||||
'Mediafile',
|
||||
function ($http, gettextCatalog, Editor, Group, Mediafile) {
|
||||
return {
|
||||
// ngDialog for user form
|
||||
getDialog: function (user) {
|
||||
@ -256,6 +258,7 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users'])
|
||||
},
|
||||
// angular-formly fields for user form
|
||||
getFormFields: function (hideOnCreateForm) {
|
||||
var images = Mediafile.getAllImages();
|
||||
return [
|
||||
{
|
||||
key: 'username',
|
||||
@ -334,12 +337,13 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users'])
|
||||
},
|
||||
{
|
||||
key: 'about_me',
|
||||
type: 'textarea',
|
||||
type: 'editor',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('About me'),
|
||||
description: gettextCatalog.getString('Profile text.')
|
||||
},
|
||||
ngModelElAttrs: {'ckeditor': 'CKEditorOptions'}
|
||||
data: {
|
||||
tinymceOption: Editor.getOptions(images)
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'is_present',
|
||||
@ -549,10 +553,12 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users'])
|
||||
.controller('UserProfileCtrl', [
|
||||
'$scope',
|
||||
'$state',
|
||||
'Editor',
|
||||
'User',
|
||||
'user',
|
||||
function($scope, $state, User, user) {
|
||||
function($scope, $state, Editor, User, user) {
|
||||
$scope.user = user; // autoupdate is not activated
|
||||
$scope.tinymceOption = Editor.getOptions();
|
||||
$scope.save = function (user) {
|
||||
User.save(user, { method: 'PATCH' }).then(
|
||||
function(success) {
|
||||
|
@ -37,7 +37,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="textAbout" translate>About me</label>
|
||||
<textarea ng-model="user.about_me" class="form-control" name="textAbout" />
|
||||
<textarea ng-model="user.about_me" ui-tinymce="tinymceOption" class="form-control" name="textAbout" />
|
||||
</div>
|
||||
|
||||
<button type="submit" ng-click="save(user)" class="btn btn-primary" translate>
|
||||
|
@ -13,6 +13,7 @@
|
||||
"gulp-cssnano": "~2.1.0",
|
||||
"gulp-if": "~2.0.0",
|
||||
"gulp-jshint": "~2.0.0",
|
||||
"gulp-rename": "~1.2.2",
|
||||
"gulp-uglify": "~1.5.2",
|
||||
"main-bower-files": "~2.11.1",
|
||||
"po2json": "~0.4.1",
|
||||
|
Loading…
Reference in New Issue
Block a user