Switch language of CKEditor automatically
This commit is contained in:
+657
@@ -0,0 +1,657 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview Image plugin based on Widgets API
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
|
||||
CKEDITOR.dialog.add( 'image2', function( editor ) {
|
||||
|
||||
// RegExp: 123, 123px, empty string ""
|
||||
var regexGetSizeOrEmpty = /(^\s*(\d+)(px)?\s*$)|^$/i,
|
||||
|
||||
lockButtonId = CKEDITOR.tools.getNextId(),
|
||||
resetButtonId = CKEDITOR.tools.getNextId(),
|
||||
|
||||
lang = editor.lang.image2,
|
||||
commonLang = editor.lang.common,
|
||||
|
||||
lockResetStyle = 'margin-top:18px;width:40px;height:20px;',
|
||||
lockResetHtml = new CKEDITOR.template(
|
||||
'<div>' +
|
||||
'<a href="javascript:void(0)" tabindex="-1" title="' + lang.lockRatio + '" class="cke_btn_locked" id="{lockButtonId}" role="checkbox">' +
|
||||
'<span class="cke_icon"></span>' +
|
||||
'<span class="cke_label">' + lang.lockRatio + '</span>' +
|
||||
'</a>' +
|
||||
|
||||
'<a href="javascript:void(0)" tabindex="-1" title="' + lang.resetSize + '" class="cke_btn_reset" id="{resetButtonId}" role="button">' +
|
||||
'<span class="cke_label">' + lang.resetSize + '</span>' +
|
||||
'</a>' +
|
||||
'</div>' ).output( {
|
||||
lockButtonId: lockButtonId,
|
||||
resetButtonId: resetButtonId
|
||||
} ),
|
||||
|
||||
helpers = CKEDITOR.plugins.image2,
|
||||
|
||||
// Editor instance configuration.
|
||||
config = editor.config,
|
||||
|
||||
// Content restrictions defined by the widget which
|
||||
// impact on dialog structure and presence of fields.
|
||||
features = editor.widgets.registered.image.features,
|
||||
|
||||
// Functions inherited from image2 plugin.
|
||||
checkHasNaturalRatio = helpers.checkHasNaturalRatio,
|
||||
getNatural = helpers.getNatural,
|
||||
|
||||
// Global variables referring to the dialog's context.
|
||||
doc, widget, image,
|
||||
|
||||
// Global variable referring to this dialog's image pre-loader.
|
||||
preLoader,
|
||||
|
||||
// Global variables holding the original size of the image.
|
||||
domWidth, domHeight,
|
||||
|
||||
// Global variables related to image pre-loading.
|
||||
preLoadedWidth, preLoadedHeight, srcChanged,
|
||||
|
||||
// Global variables related to size locking.
|
||||
lockRatio, userDefinedLock,
|
||||
|
||||
// Global variables referring to dialog fields and elements.
|
||||
lockButton, resetButton, widthField, heightField,
|
||||
|
||||
natural;
|
||||
|
||||
// Validates dimension. Allowed values are:
|
||||
// "123px", "123", "" (empty string)
|
||||
function validateDimension() {
|
||||
var match = this.getValue().match( regexGetSizeOrEmpty ),
|
||||
isValid = !!( match && parseInt( match[ 1 ], 10 ) !== 0 );
|
||||
|
||||
if ( !isValid )
|
||||
alert( commonLang[ 'invalid' + CKEDITOR.tools.capitalize( this.id ) ] );
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// Creates a function that pre-loads images. The callback function passes
|
||||
// [image, width, height] or null if loading failed.
|
||||
//
|
||||
// @returns {Function}
|
||||
function createPreLoader() {
|
||||
var image = doc.createElement( 'img' ),
|
||||
listeners = [];
|
||||
|
||||
function addListener( event, callback ) {
|
||||
listeners.push( image.once( event, function( evt ) {
|
||||
removeListeners();
|
||||
callback( evt );
|
||||
} ) );
|
||||
}
|
||||
|
||||
function removeListeners() {
|
||||
var l;
|
||||
|
||||
while ( ( l = listeners.pop() ) )
|
||||
l.removeListener();
|
||||
}
|
||||
|
||||
// @param {String} src.
|
||||
// @param {Function} callback.
|
||||
return function( src, callback, scope ) {
|
||||
addListener( 'load', function() {
|
||||
// Don't use image.$.(width|height) since it's buggy in IE9-10 (#11159)
|
||||
var dimensions = getNatural( image );
|
||||
|
||||
callback.call( scope, image, dimensions.width, dimensions.height );
|
||||
} );
|
||||
|
||||
addListener( 'error', function() {
|
||||
callback( null );
|
||||
} );
|
||||
|
||||
addListener( 'abort', function() {
|
||||
callback( null );
|
||||
} );
|
||||
|
||||
image.setAttribute( 'src',
|
||||
( config.baseHref || '' ) + src + '?' + Math.random().toString( 16 ).substring( 2 ) );
|
||||
};
|
||||
}
|
||||
|
||||
// This function updates width and height fields once the
|
||||
// "src" field is altered. Along with dimensions, also the
|
||||
// dimensions lock is adjusted.
|
||||
function onChangeSrc() {
|
||||
var value = this.getValue();
|
||||
|
||||
toggleDimensions( false );
|
||||
|
||||
// Remember that src is different than default.
|
||||
if ( value !== widget.data.src ) {
|
||||
// Update dimensions of the image once it's preloaded.
|
||||
preLoader( value, function( image, width, height ) {
|
||||
// Re-enable width and height fields.
|
||||
toggleDimensions( true );
|
||||
|
||||
// There was problem loading the image. Unlock ratio.
|
||||
if ( !image )
|
||||
return toggleLockRatio( false );
|
||||
|
||||
// Fill width field with the width of the new image.
|
||||
widthField.setValue( width );
|
||||
|
||||
// Fill height field with the height of the new image.
|
||||
heightField.setValue( height );
|
||||
|
||||
// Cache the new width.
|
||||
preLoadedWidth = width;
|
||||
|
||||
// Cache the new height.
|
||||
preLoadedHeight = height;
|
||||
|
||||
// Check for new lock value if image exist.
|
||||
toggleLockRatio( helpers.checkHasNaturalRatio( image ) );
|
||||
} );
|
||||
|
||||
srcChanged = true;
|
||||
}
|
||||
|
||||
// Value is the same as in widget data but is was
|
||||
// modified back in time. Roll back dimensions when restoring
|
||||
// default src.
|
||||
else if ( srcChanged ) {
|
||||
// Re-enable width and height fields.
|
||||
toggleDimensions( true );
|
||||
|
||||
// Restore width field with cached width.
|
||||
widthField.setValue( domWidth );
|
||||
|
||||
// Restore height field with cached height.
|
||||
heightField.setValue( domHeight );
|
||||
|
||||
// Src equals default one back again.
|
||||
srcChanged = false;
|
||||
}
|
||||
|
||||
// Value is the same as in widget data and it hadn't
|
||||
// been modified.
|
||||
else {
|
||||
// Re-enable width and height fields.
|
||||
toggleDimensions( true );
|
||||
}
|
||||
}
|
||||
|
||||
function onChangeDimension() {
|
||||
// If ratio is un-locked, then we don't care what's next.
|
||||
if ( !lockRatio )
|
||||
return;
|
||||
|
||||
var value = this.getValue();
|
||||
|
||||
// No reason to auto-scale or unlock if the field is empty.
|
||||
if ( !value )
|
||||
return;
|
||||
|
||||
// If the value of the field is invalid (e.g. with %), unlock ratio.
|
||||
if ( !value.match( regexGetSizeOrEmpty ) )
|
||||
toggleLockRatio( false );
|
||||
|
||||
// No automatic re-scale when dimension is '0'.
|
||||
if ( value === '0' )
|
||||
return;
|
||||
|
||||
var isWidth = this.id == 'width',
|
||||
// If dialog opened for the new image, domWidth and domHeight
|
||||
// will be empty. Use dimensions from pre-loader in such case instead.
|
||||
width = domWidth || preLoadedWidth,
|
||||
height = domHeight || preLoadedHeight;
|
||||
|
||||
// If changing width, then auto-scale height.
|
||||
if ( isWidth )
|
||||
value = Math.round( height * ( value / width ) );
|
||||
|
||||
// If changing height, then auto-scale width.
|
||||
else
|
||||
value = Math.round( width * ( value / height ) );
|
||||
|
||||
// If the value is a number, apply it to the other field.
|
||||
if ( !isNaN( value ) )
|
||||
( isWidth ? heightField : widthField ).setValue( value );
|
||||
}
|
||||
|
||||
// Set-up function for lock and reset buttons:
|
||||
// * Adds lock and reset buttons to focusables. Check if button exist first
|
||||
// because it may be disabled e.g. due to ACF restrictions.
|
||||
// * Register mouseover and mouseout event listeners for UI manipulations.
|
||||
// * Register click event listeners for buttons.
|
||||
function onLoadLockReset() {
|
||||
var dialog = this.getDialog();
|
||||
|
||||
function setupMouseClasses( el ) {
|
||||
el.on( 'mouseover', function() {
|
||||
this.addClass( 'cke_btn_over' );
|
||||
}, el );
|
||||
|
||||
el.on( 'mouseout', function() {
|
||||
this.removeClass( 'cke_btn_over' );
|
||||
}, el );
|
||||
}
|
||||
|
||||
// Create references to lock and reset buttons for this dialog instance.
|
||||
lockButton = doc.getById( lockButtonId );
|
||||
resetButton = doc.getById( resetButtonId );
|
||||
|
||||
// Activate (Un)LockRatio button
|
||||
if ( lockButton ) {
|
||||
// Consider that there's an additional focusable field
|
||||
// in the dialog when the "browse" button is visible.
|
||||
dialog.addFocusable( lockButton, 4 + hasFileBrowser );
|
||||
|
||||
lockButton.on( 'click', function( evt ) {
|
||||
toggleLockRatio();
|
||||
evt.data && evt.data.preventDefault();
|
||||
}, this.getDialog() );
|
||||
|
||||
setupMouseClasses( lockButton );
|
||||
}
|
||||
|
||||
// Activate the reset size button.
|
||||
if ( resetButton ) {
|
||||
// Consider that there's an additional focusable field
|
||||
// in the dialog when the "browse" button is visible.
|
||||
dialog.addFocusable( resetButton, 5 + hasFileBrowser );
|
||||
|
||||
// Fills width and height fields with the original dimensions of the
|
||||
// image (stored in widget#data since widget#init).
|
||||
resetButton.on( 'click', function( evt ) {
|
||||
// If there's a new image loaded, reset button should revert
|
||||
// cached dimensions of pre-loaded DOM element.
|
||||
if ( srcChanged ) {
|
||||
widthField.setValue( preLoadedWidth );
|
||||
heightField.setValue( preLoadedHeight );
|
||||
}
|
||||
|
||||
// If the old image remains, reset button should revert
|
||||
// dimensions as loaded when the dialog was first shown.
|
||||
else {
|
||||
widthField.setValue( domWidth );
|
||||
heightField.setValue( domHeight );
|
||||
}
|
||||
|
||||
evt.data && evt.data.preventDefault();
|
||||
}, this );
|
||||
|
||||
setupMouseClasses( resetButton );
|
||||
}
|
||||
}
|
||||
|
||||
function toggleLockRatio( enable ) {
|
||||
// No locking if there's no radio (i.e. due to ACF).
|
||||
if ( !lockButton )
|
||||
return;
|
||||
|
||||
if ( typeof enable == 'boolean' ) {
|
||||
// If user explicitly wants to decide whether
|
||||
// to lock or not, don't do anything.
|
||||
if ( userDefinedLock )
|
||||
return;
|
||||
|
||||
lockRatio = enable;
|
||||
}
|
||||
|
||||
// Undefined. User changed lock value.
|
||||
else {
|
||||
var width = widthField.getValue(),
|
||||
height;
|
||||
|
||||
userDefinedLock = true;
|
||||
lockRatio = !lockRatio;
|
||||
|
||||
// Automatically adjust height to width to match
|
||||
// the original ratio (based on dom- dimensions).
|
||||
if ( lockRatio && width ) {
|
||||
height = domHeight / domWidth * width;
|
||||
|
||||
if ( !isNaN( height ) )
|
||||
heightField.setValue( Math.round( height ) );
|
||||
}
|
||||
}
|
||||
|
||||
lockButton[ lockRatio ? 'removeClass' : 'addClass' ]( 'cke_btn_unlocked' );
|
||||
lockButton.setAttribute( 'aria-checked', lockRatio );
|
||||
|
||||
// Ratio button hc presentation - WHITE SQUARE / BLACK SQUARE
|
||||
if ( CKEDITOR.env.hc ) {
|
||||
var icon = lockButton.getChild( 0 );
|
||||
icon.setHtml( lockRatio ? CKEDITOR.env.ie ? '\u25A0' : '\u25A3' : CKEDITOR.env.ie ? '\u25A1' : '\u25A2' );
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDimensions( enable ) {
|
||||
var method = enable ? 'enable' : 'disable';
|
||||
|
||||
widthField[ method ]();
|
||||
heightField[ method ]();
|
||||
}
|
||||
|
||||
var hasFileBrowser = !!( config.filebrowserImageBrowseUrl || config.filebrowserBrowseUrl ),
|
||||
srcBoxChildren = [
|
||||
{
|
||||
id: 'src',
|
||||
type: 'text',
|
||||
label: commonLang.url,
|
||||
onKeyup: onChangeSrc,
|
||||
onChange: onChangeSrc,
|
||||
setup: function( widget ) {
|
||||
this.setValue( widget.data.src );
|
||||
},
|
||||
commit: function( widget ) {
|
||||
widget.setData( 'src', this.getValue() );
|
||||
},
|
||||
validate: CKEDITOR.dialog.validate.notEmpty( lang.urlMissing )
|
||||
}
|
||||
];
|
||||
|
||||
// Render the "Browse" button on demand to avoid an "empty" (hidden child)
|
||||
// space in dialog layout that distorts the UI.
|
||||
if ( hasFileBrowser ) {
|
||||
srcBoxChildren.push( {
|
||||
type: 'button',
|
||||
id: 'browse',
|
||||
// v-align with the 'txtUrl' field.
|
||||
// TODO: We need something better than a fixed size here.
|
||||
style: 'display:inline-block;margin-top:14px;',
|
||||
align: 'center',
|
||||
label: editor.lang.common.browseServer,
|
||||
hidden: true,
|
||||
filebrowser: 'info:src'
|
||||
} );
|
||||
}
|
||||
|
||||
return {
|
||||
title: lang.title,
|
||||
minWidth: 250,
|
||||
minHeight: 100,
|
||||
onLoad: function() {
|
||||
// Create a "global" reference to the document for this dialog instance.
|
||||
doc = this._.element.getDocument();
|
||||
|
||||
// Create a pre-loader used for determining dimensions of new images.
|
||||
preLoader = createPreLoader();
|
||||
},
|
||||
onShow: function() {
|
||||
// Create a "global" reference to edited widget.
|
||||
widget = this.widget;
|
||||
|
||||
// Create a "global" reference to widget's image.
|
||||
image = widget.parts.image;
|
||||
|
||||
// Reset global variables.
|
||||
srcChanged = userDefinedLock = lockRatio = false;
|
||||
|
||||
// Natural dimensions of the image.
|
||||
natural = getNatural( image );
|
||||
|
||||
// Get the natural width of the image.
|
||||
preLoadedWidth = domWidth = natural.width;
|
||||
|
||||
// Get the natural height of the image.
|
||||
preLoadedHeight = domHeight = natural.height;
|
||||
},
|
||||
contents: [
|
||||
{
|
||||
id: 'info',
|
||||
label: lang.infoTab,
|
||||
elements: [
|
||||
{
|
||||
type: 'vbox',
|
||||
padding: 0,
|
||||
children: [
|
||||
{
|
||||
type: 'hbox',
|
||||
widths: [ '100%' ],
|
||||
children: srcBoxChildren
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'alt',
|
||||
type: 'text',
|
||||
label: lang.alt,
|
||||
setup: function( widget ) {
|
||||
this.setValue( widget.data.alt );
|
||||
},
|
||||
commit: function( widget ) {
|
||||
widget.setData( 'alt', this.getValue() );
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'hbox',
|
||||
widths: [ '25%', '25%', '50%' ],
|
||||
requiredContent: features.dimension.requiredContent,
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
width: '45px',
|
||||
id: 'width',
|
||||
label: commonLang.width,
|
||||
validate: validateDimension,
|
||||
onKeyUp: onChangeDimension,
|
||||
onLoad: function() {
|
||||
widthField = this;
|
||||
},
|
||||
setup: function( widget ) {
|
||||
this.setValue( widget.data.width );
|
||||
},
|
||||
commit: function( widget ) {
|
||||
widget.setData( 'width', this.getValue() );
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
id: 'height',
|
||||
width: '45px',
|
||||
label: commonLang.height,
|
||||
validate: validateDimension,
|
||||
onKeyUp: onChangeDimension,
|
||||
onLoad: function() {
|
||||
heightField = this;
|
||||
},
|
||||
setup: function( widget ) {
|
||||
this.setValue( widget.data.height );
|
||||
},
|
||||
commit: function( widget ) {
|
||||
widget.setData( 'height', this.getValue() );
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'lock',
|
||||
type: 'html',
|
||||
style: lockResetStyle,
|
||||
onLoad: onLoadLockReset,
|
||||
setup: function( widget ) {
|
||||
toggleLockRatio( widget.data.lock );
|
||||
},
|
||||
commit: function( widget ) {
|
||||
widget.setData( 'lock', lockRatio );
|
||||
},
|
||||
html: lockResetHtml
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'hbox',
|
||||
id: 'alignment',
|
||||
requiredContent: features.align.requiredContent,
|
||||
children: [
|
||||
{
|
||||
id: 'align',
|
||||
type: 'radio',
|
||||
items: [
|
||||
[ commonLang.alignNone, 'none' ],
|
||||
[ commonLang.alignLeft, 'left' ],
|
||||
[ commonLang.alignCenter, 'center' ],
|
||||
[ commonLang.alignRight, 'right' ] ],
|
||||
label: commonLang.align,
|
||||
setup: function( widget ) {
|
||||
this.setValue( widget.data.align );
|
||||
},
|
||||
commit: function( widget ) {
|
||||
widget.setData( 'align', this.getValue() );
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'hasCaption',
|
||||
type: 'checkbox',
|
||||
label: lang.captioned,
|
||||
requiredContent: features.caption.requiredContent,
|
||||
setup: function( widget ) {
|
||||
this.setValue( widget.data.hasCaption );
|
||||
},
|
||||
commit: function( widget ) {
|
||||
widget.setData( 'hasCaption', this.getValue() );
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'Upload',
|
||||
filebrowser: 'uploadButton',
|
||||
label: lang.uploadTab,
|
||||
elements: [
|
||||
{
|
||||
type: 'file',
|
||||
id: 'upload',
|
||||
label: lang.btnUpload,
|
||||
style: 'height:40px'
|
||||
},
|
||||
{
|
||||
type: 'fileButton',
|
||||
id: 'uploadButton',
|
||||
onLoad: function( a ) {
|
||||
CKEDITOR.document.getById( this.domId ).on( 'click', function() {
|
||||
|
||||
// grab the file(s) from the file input tag and upload it using AJAX
|
||||
var dialog = this.getDialog();
|
||||
var files = dialog.getContentElement("Upload", "upload").getInputElement().$.files;
|
||||
|
||||
var file_missing_msg = localize("Please select a file!");
|
||||
|
||||
// no files were selected
|
||||
if(files.length == 0) {
|
||||
alert(file_missing_msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the form data object for uploading with POST
|
||||
var formData = new FormData();
|
||||
|
||||
// add all the other attachments that were previously added
|
||||
$( "input[name^='attachment']" ).each(function(idx, el) {
|
||||
formData.append($(el).attr('name'), $(el).attr('value'));
|
||||
});
|
||||
|
||||
formData.append('drop-count', files.length); // number of files being uploaded
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
formData.append('next_attachment', parent.next_attachment);
|
||||
formData.append('encoding', "HTML");
|
||||
formData.append('attfile', files[i]);
|
||||
|
||||
parent.next_attachment += 1;
|
||||
}
|
||||
formData.append('cmd', "Upload"); // Command for server to recognize this as an file upload
|
||||
|
||||
var URL = 'upload.html?next_attachment=' + parent.next_attachment;
|
||||
|
||||
$.ajax({
|
||||
xhr: function()
|
||||
{
|
||||
var xhr = new window.XMLHttpRequest();
|
||||
|
||||
// Start the progress bar
|
||||
progressJs().start();
|
||||
|
||||
//Upload progress
|
||||
xhr.upload.addEventListener("progress", function(evt){
|
||||
if (evt.lengthComputable) {
|
||||
var percentComplete = evt.loaded / evt.total;
|
||||
//Update the progress bar
|
||||
progressJs().set(percentComplete);
|
||||
}
|
||||
}, false);
|
||||
|
||||
return xhr;
|
||||
},
|
||||
contentType: false,
|
||||
processData: false,
|
||||
type: 'POST',
|
||||
url: URL,
|
||||
data: formData,
|
||||
success: function(data) {
|
||||
|
||||
// Extract the last attachment section of the returned page
|
||||
var attch = $(".attachment", $(data)).last();
|
||||
|
||||
// Create a new attachment section that will replace the current one
|
||||
var attch_upload = $("#attachment_upload", $(data));
|
||||
|
||||
// The last img tag in this html snippet contains the source of the image which we need
|
||||
// We first split the html based on img tags
|
||||
var arr = attch.html().split("<img");
|
||||
|
||||
// Then extract the source from the last array element
|
||||
var tmp_str = arr[arr.length - 1];
|
||||
|
||||
// These are the start and end locations of the source
|
||||
var start = tmp_str.indexOf("src=");
|
||||
var end = tmp_str.indexOf("?thumb");
|
||||
|
||||
// Finally extract the img source
|
||||
var suffix = 'src="'.length;
|
||||
var src = tmp_str.substr(start + suffix, end - start - suffix);
|
||||
|
||||
if(src) { // check if we have the correct response
|
||||
dialog.getContentElement( 'info', 'src' ).setValue( src );
|
||||
} else {
|
||||
console.log("Couldn't find img source...");
|
||||
}
|
||||
|
||||
// add the new attachments to the current page
|
||||
$("#attachment_upload").before(attch.slice(-files.length));
|
||||
|
||||
// replace the attachment upload section
|
||||
$("#attachment_upload").replaceWith(attch_upload);
|
||||
|
||||
// End the progress bar
|
||||
progressJs().end();
|
||||
},
|
||||
fail: function() {
|
||||
// End the progress bar
|
||||
progressJs().end();
|
||||
console.log("Error uploading image...");
|
||||
}
|
||||
});
|
||||
|
||||
// Do not call the built-in click command
|
||||
return false;
|
||||
}, this );
|
||||
},
|
||||
label: lang.btnUpload,
|
||||
'for': [ 'Upload', 'upload' ]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
} );
|
||||
Reference in New Issue
Block a user