Custom CKEditor plugin for browsing mediafiles
This commit is contained in:
parent
3006cb388f
commit
b63262c943
@ -58,6 +58,7 @@ Core:
|
|||||||
|
|
||||||
Mediafiles:
|
Mediafiles:
|
||||||
- Fixed reloading of PDF on page change [#3274].
|
- Fixed reloading of PDF on page change [#3274].
|
||||||
|
- Custom CKEditor plugin for browsing mediafiles [#3337].
|
||||||
|
|
||||||
General:
|
General:
|
||||||
- Switched from npm to Yarn [#3188].
|
- Switched from npm to Yarn [#3188].
|
||||||
|
@ -1469,6 +1469,70 @@ img {
|
|||||||
display: none;
|
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: override ngdialog-theme-default */
|
||||||
|
|
||||||
.ngdialog.ngdialog-theme-default {
|
.ngdialog.ngdialog-theme-default {
|
||||||
|
@ -855,8 +855,21 @@ angular.module('OpenSlidesApp.core', [
|
|||||||
.factory('Editor', [
|
.factory('Editor', [
|
||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
function (gettextCatalog) {
|
function (gettextCatalog) {
|
||||||
|
var extraPlugins = [];
|
||||||
return {
|
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) {
|
getOptions: function (images) {
|
||||||
|
var extraPluginsString = 'colorbutton,find,sourcedialog,justify,showblocks';
|
||||||
|
var registeredPluginsString = extraPlugins.join(',');
|
||||||
|
if (registeredPluginsString) {
|
||||||
|
extraPluginsString += ',' + registeredPluginsString;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
on: {
|
on: {
|
||||||
instanceReady: function() {
|
instanceReady: function() {
|
||||||
@ -930,8 +943,8 @@ angular.module('OpenSlidesApp.core', [
|
|||||||
'br(os-line-break);',
|
'br(os-line-break);',
|
||||||
|
|
||||||
// there seems to be an error in CKeditor that parses spaces in extraPlugins as part of the plugin name.
|
// 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',
|
extraPlugins: extraPluginsString,
|
||||||
removePlugins: 'wsc,scayt,a11yhelp,filebrowser,sourcearea,liststyle,tabletools,contextmenu',
|
removePlugins: 'wsc,scayt,a11yhelp,filebrowser,sourcearea,liststyle,tabletools,contextmenu,image',
|
||||||
removeButtons: 'Scayt,Anchor,Styles,HorizontalRule',
|
removeButtons: 'Scayt,Anchor,Styles,HorizontalRule',
|
||||||
toolbarGroups: [
|
toolbarGroups: [
|
||||||
{ name: 'clipboard', groups: [ 'clipboard', 'undo' ] },
|
{ name: 'clipboard', groups: [ 'clipboard', 'undo' ] },
|
||||||
|
208
openslides/mediafiles/static/js/mediafiles/image-plugin.js
Normal file
208
openslides/mediafiles/static/js/mediafiles/image-plugin.js
Normal 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 + ' × ' + 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');
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
}());
|
@ -7,6 +7,7 @@ angular.module('OpenSlidesApp.mediafiles.site', [
|
|||||||
'OpenSlidesApp.mediafiles.list',
|
'OpenSlidesApp.mediafiles.list',
|
||||||
'OpenSlidesApp.mediafiles.states',
|
'OpenSlidesApp.mediafiles.states',
|
||||||
'OpenSlidesApp.mediafiles.update',
|
'OpenSlidesApp.mediafiles.update',
|
||||||
|
'OpenSlidesApp.mediafiles.image-plugin',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
@ -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>
|
Loading…
Reference in New Issue
Block a user