Merge pull request #3337 from FinnStutzenstein/CKEditorImages

Custom CKEditor plugin for browsing mediafiles
This commit is contained in:
Norman Jäckel 2017-08-16 11:41:55 +02:00 committed by GitHub
commit 0fe7b6bf7d
6 changed files with 312 additions and 2 deletions

View File

@ -69,6 +69,7 @@ Core:
Mediafiles:
- Fixed reloading of PDF on page change [#3274].
- Custom CKEditor plugin for browsing mediafiles [#3337].
General:
- Several bugfixes and minor improvements.

View File

@ -1487,6 +1487,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 {

View File

@ -880,8 +880,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() {
@ -955,8 +968,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' ] },

View File

@ -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 += '<div class="image" onclick="CKEDITOR.tools.imagebrowser.selectImage(\'' +
image.value + '\');" style="background-image:url(\'' +
image.value + '\');" data-image="' + image.value + '"></div>';
});
$('#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 + ' &times; ' + 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 = '<img src="' + url + '" data-cke-saved-src="' + url +
'" alt="' + url + '" style="width: ' + scale + '%;" />';
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: '<div id="imageBrowserContainer"></div>'
}
],
},
],
// 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');
}
]);
}());

View File

@ -7,6 +7,7 @@ angular.module('OpenSlidesApp.mediafiles.site', [
'OpenSlidesApp.mediafiles.list',
'OpenSlidesApp.mediafiles.states',
'OpenSlidesApp.mediafiles.update',
'OpenSlidesApp.mediafiles.image-plugin',
]);
}());

View File

@ -0,0 +1,23 @@
<table class="imageTable">
<tr>
<td style="width: 55%; border-right: 2px solid #ddd">
<div id="imageBrowser"></div>
</td>
<td style="width: 45%;">
<div id="imagePreviewSection" class="hidden">
<div id="imagePreview">
<div id="fullSizeContainer">
<img src=""/>
</div>
</div>
<div>
<span id="scaleLabel"></span>
</div>
<div>
<input type="number" id="image-scale" class="cke_dialog_ui_input_text"> %
</div>
<div id="originalSizeText"></div>
</div>
</td>
</tr>
</table>