diff --git a/CHANGELOG b/CHANGELOG index 74ce4610a..f326a1087 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -58,6 +58,7 @@ Core: Mediafiles: - Fixed reloading of PDF on page change [#3274]. +- Custom CKEditor plugin for browsing mediafiles [#3337]. General: - Switched from npm to Yarn [#3188]. diff --git a/openslides/core/static/css/app.css b/openslides/core/static/css/app.css index da95f4e76..59f0d65be 100644 --- a/openslides/core/static/css/app.css +++ b/openslides/core/static/css/app.css @@ -1469,6 +1469,70 @@ img { display: none; } +/* image plugin for CKEditor */ +#imageBrowserContainer .imageTable { + table-layout: fixed; + width: 100%; +} + +#imagePreviewSection { + position: absolute; + margin: 0px 20px 20px 20px; +} + +#imagePreviewSection input { + width: 65px; +} + +#imagePreviewSection .hidden { + display: none; +} + +#imagePreviewSection > div { + margin-bottom: 10px; +} + +#imagePreviewSection i { + font-size: 130%; + cursor: pointer; +} + +#imagePreview { + max-width: 400px; + max-height: 300px; + overflow: auto; + padding: 2px; + border: 3px solid #317796; +} + +#imagePreview img[src=""] { + display: none; +} + +#imageBrowser { + max-height: 500px; + overflow: auto; +} + +#imageBrowser .image { + position: relative; + float: left; + width: 75px; + height: 75px; + margin: 5px; + background-size: 125%; + background-repeat: no-repeat; + background-position: center center; + border: 2px solid #bed4de; + cursor: pointer; +} +#imageBrowser .image:hover { + border-color: #317796; +} +#imageBrowser .image.selected { + border-color: #317796; +} + /* ngDialog: override ngdialog-theme-default */ .ngdialog.ngdialog-theme-default { diff --git a/openslides/core/static/js/core/base.js b/openslides/core/static/js/core/base.js index 56c93d570..a4d9a5e6a 100644 --- a/openslides/core/static/js/core/base.js +++ b/openslides/core/static/js/core/base.js @@ -855,8 +855,21 @@ angular.module('OpenSlidesApp.core', [ .factory('Editor', [ 'gettextCatalog', function (gettextCatalog) { + var extraPlugins = []; return { + registerDialog: function (name, dialog) { + CKEDITOR.dialog.add(name, dialog); + }, + registerPlugin: function (name, plugin) { + CKEDITOR.plugins.add(name, plugin); + extraPlugins.push(name); + }, getOptions: function (images) { + var extraPluginsString = 'colorbutton,find,sourcedialog,justify,showblocks'; + var registeredPluginsString = extraPlugins.join(','); + if (registeredPluginsString) { + extraPluginsString += ',' + registeredPluginsString; + } return { on: { instanceReady: function() { @@ -930,8 +943,8 @@ angular.module('OpenSlidesApp.core', [ '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,sourcedialog,justify,showblocks', - removePlugins: 'wsc,scayt,a11yhelp,filebrowser,sourcearea,liststyle,tabletools,contextmenu', + extraPlugins: extraPluginsString, + removePlugins: 'wsc,scayt,a11yhelp,filebrowser,sourcearea,liststyle,tabletools,contextmenu,image', removeButtons: 'Scayt,Anchor,Styles,HorizontalRule', toolbarGroups: [ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] }, diff --git a/openslides/mediafiles/static/js/mediafiles/image-plugin.js b/openslides/mediafiles/static/js/mediafiles/image-plugin.js new file mode 100644 index 000000000..5ca5f2563 --- /dev/null +++ b/openslides/mediafiles/static/js/mediafiles/image-plugin.js @@ -0,0 +1,208 @@ +(function () { + +'use strict'; + +angular.module('OpenSlidesApp.mediafiles.image-plugin', [ + 'OpenSlidesApp.mediafiles.resources', + 'gettext', + 'OpenSlidesApp.core', +]) + +.factory('ImageBrowserPlugin', [ + '$templateCache', + 'Mediafile', + 'gettextCatalog', + 'Editor', + function ($templateCache, Mediafile, gettextCatalog, Editor) { + return { + getPlugin: function () { + return { + init: function (editor) { + CKEDITOR.tools.imagebrowser = {}; + + // Initialize this dialog, if it is opened. + editor.on('dialogShow', function (event) { + var dialog = event.data; + if (dialog.getName() === 'imagebrowser-dialog') { + CKEDITOR.dialog.getCurrent().disableButton('ok'); + + // Load the main plugin template and paste it into the container + var template = $templateCache.get('static/templates/mediafiles/image-plugin.html'); + if (!template) { + throw 'Template for image plugin not found!'; + } + $('#imageBrowserContainer').html(template); + + // Load all images. + var images = ''; + _.forEach(Mediafile.getAllImages(), function (image) { + images += '
'; + }); + $('#imageBrowser').html(images); + + // Translate some strings. Angular tags are not available in CKEditor. + $('#scaleLabel').html(gettextCatalog.getString('Scale')); + + // If the dialog was opened via double click, check the selected element. It + // may be an image, so preselect it. + var selectedElement = editor.getSelection().getStartElement(); + if (selectedElement.is('img')) { + // Check for given scale of this image. + var styleAttr = $(selectedElement).attr('style'); + var scale; + var scaleRegex = /width\s*:\s*(\d+)\s*%/g; + var scaleMatch = scaleRegex.exec(styleAttr); + if (scaleMatch) { + scale = parseInt(scaleMatch[1]); + } + CKEDITOR.tools.imagebrowser.selectImage( + selectedElement.getAttribute('src'), scale); + } + // Setup event listeners. + $('#image-scale').bind('keyup mouseup', function (event) { + var scale = parseInt($('#image-scale').val()); + if (scale !== CKEDITOR.tools.imagebrowser.scale) { + CKEDITOR.tools.imagebrowser.updateImageSize(scale); + } + }); + } + }); + // React on double clicks in the textarea. If an image was selected, open this dialog. + editor.on('doubleclick', function (event) { + var element = event.data.element; + if (!element.isReadOnly()) { + if (element.is('img')) { + event.data.dialog = 'imagebrowser-dialog'; + editor.getSelection().selectElement(element); + } + } + }); + // Set the toolbar icon to the default image icon. + CKEDITOR.on('instanceReady', function () { + var toolbarIcon = $('span.cke_button_icon.cke_button__image.browser_icon'); + toolbarIcon.removeClass('cke_button__image browser_icon'); + toolbarIcon.addClass('cke_button__image_icon'); + }); + // Handler for selecting an image. It may be called by clicking on a thumbnail or by + // just giving the url. The scale is optional. + CKEDITOR.tools.imagebrowser.selectImage = function (url, scale) { + var browser = $('#imageBrowser'); + _.forEach(browser.children(), function (child) { // check every available image + if (child.getAttribute('data-image') == url) { //match + child.classList.add('selected'); + var image = $('#imagePreview img'); + // Setup an load event handler, so we can get the size of the image when loaded. + image.on('load', function (event) { + var w = event.target.naturalWidth; + var h = event.target.naturalHeight; + $('#originalSizeText').html(gettextCatalog.getString('Original size') + + ': ' + w + ' × ' + h ); + $('#fullSizeContainer').width(w).height(h); + if (scale !== undefined) { + // Use custom scale. + CKEDITOR.tools.imagebrowser.updateImageSize(scale); + } else { + CKEDITOR.tools.imagebrowser.updateImageSize(100); + } + }); + // Set the url of the main preview image. + image.attr('src', url); + $('#imagePreviewSection').removeClass('hidden'); + CKEDITOR.tools.imagebrowser.selected = url; + } else { + // Wrong image, deselect it in the preview window. + child.classList.remove('selected'); + } + }); + }; + // Handler for updateing the image size. + CKEDITOR.tools.imagebrowser.updateImageSize = function (scale) { + if (isNaN(scale) || scale <= 0) { + CKEDITOR.dialog.getCurrent().disableButton('ok'); + } else { + CKEDITOR.dialog.getCurrent().enableButton('ok'); + CKEDITOR.tools.imagebrowser.scale = scale; + $('#imagePreview img').width(scale + '%'); + $('#image-scale').val(scale); + } + }; + // Insert the selected image into the textarea. + CKEDITOR.tools.imagebrowser.insertImage = function (url, scale) { + var editor = CKEDITOR.currentInstance; + var dialog = CKEDITOR.dialog.getCurrent(); + var html = '' + url + ''; + editor.config.allowedContent = true; + editor.insertHtml(html.trim()); + dialog.hide(); + }; + editor.addCommand('imagebrowser-open', new CKEDITOR.dialogCommand('imagebrowser-dialog')); + editor.ui.addButton(gettextCatalog.getString('Image browser'), { + label: gettextCatalog.getString('Open image browser'), + command: 'imagebrowser-open', + toolbar: 'insert', + }); + }, + }; + }, + getDialog: function () { + return function (editor) { + return { + title: gettextCatalog.getString('Image browser'), + minWidth: 1000, + minHeight: 400, + contents: [ + { + id: 'imagebrowser-tab1', + label: gettextCatalog.getString('Browse for images'), + elements: [ + { + type: 'html', + align: 'left', + id: 'titleid', + style: 'font-size: 20px; font-weight: bold;', + html: gettextCatalog.getString('Browse for images'), + }, { + type: 'html', + align: 'left', + id: 'msg', + style: '', + html: '
' + } + ], + }, + ], + // insert image on OK. + onOk: function (event) { + var url = CKEDITOR.tools.imagebrowser.selected; + if (url) { + var scale = CKEDITOR.tools.imagebrowser.scale; + CKEDITOR.tools.imagebrowser.insertImage(url, scale); + } + }, + }; + }; + }, + }; + } +]) + +.run([ + 'Editor', + 'ImageBrowserPlugin', + 'gettext', + function (Editor, ImageBrowserPlugin, gettext) { + Editor.registerDialog('imagebrowser-dialog', ImageBrowserPlugin.getDialog()); + Editor.registerPlugin('imagebrowser', ImageBrowserPlugin.getPlugin()); + + // mark all plugin strings + gettext('Original size'); + gettext('Scale'); + gettext('Image browser'); + gettext('Browse for images'); + } +]); + +}()); diff --git a/openslides/mediafiles/static/js/mediafiles/site.js b/openslides/mediafiles/static/js/mediafiles/site.js index 8eb4a33d6..cc1466875 100644 --- a/openslides/mediafiles/static/js/mediafiles/site.js +++ b/openslides/mediafiles/static/js/mediafiles/site.js @@ -7,6 +7,7 @@ angular.module('OpenSlidesApp.mediafiles.site', [ 'OpenSlidesApp.mediafiles.list', 'OpenSlidesApp.mediafiles.states', 'OpenSlidesApp.mediafiles.update', + 'OpenSlidesApp.mediafiles.image-plugin', ]); }()); diff --git a/openslides/mediafiles/static/templates/mediafiles/image-plugin.html b/openslides/mediafiles/static/templates/mediafiles/image-plugin.html new file mode 100644 index 000000000..f0f71af81 --- /dev/null +++ b/openslides/mediafiles/static/templates/mediafiles/image-plugin.html @@ -0,0 +1,23 @@ + + + + + +
+
+
+ +