389 lines
11 KiB
JavaScript
389 lines
11 KiB
JavaScript
|
/**
|
||
|
* InputfieldCKEditor.js
|
||
|
*
|
||
|
* Initialization for CKEditor
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Get ProcessWire config settings for given CKE editor object or name
|
||
|
*
|
||
|
* @param editor
|
||
|
* @returns {*}
|
||
|
*
|
||
|
*/
|
||
|
function ckeGetProcessWireConfig(editor) {
|
||
|
|
||
|
var editorName = typeof editor == "string" ? editor : editor.name;
|
||
|
var configName = editorName.replace('Inputfield_', 'InputfieldCKEditor_');
|
||
|
var $repeaterItem = '';
|
||
|
var settings = {};
|
||
|
|
||
|
configName = configName.replace('Inputfield_', 'InputfieldCKEditor_');
|
||
|
|
||
|
if(typeof ProcessWire.config[configName] == "undefined" && configName.indexOf('_repeater') > 0) {
|
||
|
configName = configName.replace(/_repeater[0-9]+/, '');
|
||
|
$repeaterItem = $('#' + editorName).closest('.InputfieldRepeaterItem');
|
||
|
}
|
||
|
if(typeof ProcessWire.config[configName] == "undefined" && configName.indexOf('_ckeditor') > 0) {
|
||
|
configName = configName.replace(/_ckeditor$/, ''); // inline only
|
||
|
}
|
||
|
if(typeof ProcessWire.config[configName] == "undefined" && configName.indexOf('__') > 0) {
|
||
|
configName = configName.replace(/__\d+$/, ''); // remove language-id
|
||
|
}
|
||
|
if(typeof ProcessWire.config[configName] == "undefined") {
|
||
|
settings.error = 'Cannot find CKEditor settings for ' + configName;
|
||
|
} else {
|
||
|
settings = ProcessWire.config[configName];
|
||
|
}
|
||
|
|
||
|
if($repeaterItem.length) {
|
||
|
settings['repeaterItem'] = $repeaterItem;
|
||
|
} else {
|
||
|
settings['repeaterItem'] = '';
|
||
|
}
|
||
|
|
||
|
return settings;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Add external plugins
|
||
|
*
|
||
|
* These are located in:
|
||
|
* /wire/modules/Inputfield/InputfieldCKEditor/plugins/[name]/plugin.js (core external plugins)
|
||
|
* /site/modules/InputfieldCKEditor/plugins/[name]/plugin.js (site external plugins)
|
||
|
*
|
||
|
*/
|
||
|
function ckeLoadPlugins() {
|
||
|
for(var name in ProcessWire.config.InputfieldCKEditor.plugins) {
|
||
|
var file = ProcessWire.config.InputfieldCKEditor.plugins[name];
|
||
|
CKEDITOR.plugins.addExternal(name, file, '');
|
||
|
}
|
||
|
}
|
||
|
ckeLoadPlugins();
|
||
|
|
||
|
/**
|
||
|
* Event called when an editor is blurred, so that we can check for changes
|
||
|
*
|
||
|
* @param event
|
||
|
*
|
||
|
*/
|
||
|
function ckeBlurEvent(event) {
|
||
|
var editor = event.editor;
|
||
|
var $textarea = $(editor.element.$);
|
||
|
if(editor.checkDirty()) {
|
||
|
// value changed
|
||
|
if($textarea.length) {
|
||
|
if($textarea.is("textarea")) $textarea.trigger('change');
|
||
|
$textarea.closest(".Inputfield").addClass('InputfieldStateChanged');
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Event called when an editor is focused
|
||
|
*
|
||
|
* @param event
|
||
|
*
|
||
|
*/
|
||
|
function ckeFocusEvent(event) {
|
||
|
var editor = event.editor;
|
||
|
var $textarea = $(editor.element.$);
|
||
|
$textarea.trigger('pw-focus');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Event called when an editor is resized vertically
|
||
|
*
|
||
|
* @param event
|
||
|
*
|
||
|
*/
|
||
|
function ckeResizeEvent(event) {
|
||
|
var editor = event.editor;
|
||
|
var $textarea = $(editor.element.$);
|
||
|
if($textarea.length) {
|
||
|
$textarea.closest(".Inputfield").trigger('heightChanged');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Handler for fileUploadRequest event
|
||
|
*
|
||
|
* @param event
|
||
|
*
|
||
|
*/
|
||
|
function ckeUploadEvent(event) {
|
||
|
|
||
|
var xhr = event.data.fileLoader.xhr;
|
||
|
var fileLoader = event.data.fileLoader;
|
||
|
var settings = ckeGetProcessWireConfig(event.editor);
|
||
|
var uploadFieldName = settings ? settings.pwUploadField : '_unknown';
|
||
|
var $imageInputfield = $('#Inputfield_' + uploadFieldName);
|
||
|
|
||
|
if(typeof settings.repeaterItem != "undefined" && settings.repeaterItem.length) {
|
||
|
var $repeaterImageField = settings.repeaterItem.find('.InputfieldImage:not(.InputfieldFileSingle)');
|
||
|
if($repeaterImageField.length) $imageInputfield = $repeaterImageField;
|
||
|
}
|
||
|
|
||
|
if($imageInputfield.length) {
|
||
|
xhr.open("POST", fileLoader.uploadUrl, true);
|
||
|
$imageInputfield.trigger('pwimageupload', {
|
||
|
'name': fileLoader.fileName,
|
||
|
'file': fileLoader.file,
|
||
|
'xhr': xhr
|
||
|
});
|
||
|
|
||
|
// Prevented the default behavior.
|
||
|
event.stop();
|
||
|
} else {
|
||
|
if(typeof settings.error != "undefined" && settings.error.length) {
|
||
|
ProcessWire.alert(settings.error);
|
||
|
} else {
|
||
|
ProcessWire.alert('Unable to find images field for upload');
|
||
|
}
|
||
|
event.stop();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Attach events common to all CKEditor instances
|
||
|
*
|
||
|
* @param editor CKEditor instance
|
||
|
*
|
||
|
*/
|
||
|
function ckeInitEvents(editor) {
|
||
|
|
||
|
editor.on('blur', ckeBlurEvent);
|
||
|
editor.on('focus', ckeFocusEvent);
|
||
|
editor.on('change', ckeBlurEvent);
|
||
|
editor.on('resize', ckeResizeEvent);
|
||
|
editor.on('fileUploadRequest', ckeUploadEvent, null, null, 4);
|
||
|
|
||
|
var $textarea = $(editor.element.$);
|
||
|
var $inputfield = $textarea.closest('.Inputfield.InputfieldColumnWidth');
|
||
|
|
||
|
if($inputfield.length) setTimeout(function() {
|
||
|
$inputfield.trigger('heightChanged');
|
||
|
}, 1000);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Called on saveReady or submit to copy inline contents to a form element in the POST request
|
||
|
*
|
||
|
* @param $inputfield
|
||
|
*
|
||
|
*/
|
||
|
function ckeSaveReadyInline($inputfield) {
|
||
|
if(!$inputfield.length) return;
|
||
|
var $inlines = $inputfield.hasClass('.InputfieldCKEditorInline') ? $inputfield : $inputfield.find(".InputfieldCKEditorInline");
|
||
|
if($inlines.length) $inlines.each(function() {
|
||
|
var $t = $(this);
|
||
|
var value;
|
||
|
if($t.hasClass('InputfieldCKEditorLoaded')) {
|
||
|
var editor = CKEDITOR.instances[$t.attr('id')];
|
||
|
// getData() ensures there are no CKE specific remnants in the markup
|
||
|
if(typeof editor != "undefined") {
|
||
|
if(editor.focusManager.hasFocus) {
|
||
|
// TMP: CKEditor 4.5.1 / 4.5.2 has documented bug that causes JS error on editor.getData() here
|
||
|
// this section of code can be removed after they fix it (presumably in 4.5.3)
|
||
|
editor.focusManager.focus(true);
|
||
|
editor.focus();
|
||
|
}
|
||
|
value = editor.getData();
|
||
|
}
|
||
|
} else {
|
||
|
value = $t.html();
|
||
|
}
|
||
|
var $input = $t.next('input');
|
||
|
$input.attr('value', value);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Called on saveReady event to force an editor.updateElement() to update original textarea
|
||
|
*
|
||
|
* @param $inputfield
|
||
|
*
|
||
|
*/
|
||
|
function ckeSaveReadyNormal($inputfield) {
|
||
|
var $normals = $inputfield.hasClass('InputfieldCKEditorNormal') ? $inputfield : $inputfield.find(".InputfieldCKEditorNormal");
|
||
|
$normals.each(function() {
|
||
|
var $t = $(this);
|
||
|
if(!$t.hasClass('InputfieldCKEditorLoaded')) return;
|
||
|
var editor = CKEDITOR.instances[$t.attr('id')];
|
||
|
editor.updateElement();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get CKEditor configuration data when $editor element is available
|
||
|
*
|
||
|
* @param $editor Regular editor <textarea> or inline editor <div> having data-configName attribute
|
||
|
* @returns {*}
|
||
|
*
|
||
|
*/
|
||
|
function ckeGetConfigData($editor) {
|
||
|
var configName = $editor.attr('data-configName');
|
||
|
if(typeof ProcessWire.config[configName] === 'undefined') {
|
||
|
// get from data-configdata attribute and populate to ProcessWire.config
|
||
|
if(typeof $editor.attr('data-configdata') !== 'undefined') {
|
||
|
ProcessWire.config[configName] = JSON.parse($editor.attr('data-configdata'));
|
||
|
}
|
||
|
}
|
||
|
var configData = ProcessWire.config[configName];
|
||
|
if(typeof configData === 'undefined') configData = {};
|
||
|
return configData;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Mouseover or focus event that activates inline CKEditor instances
|
||
|
*
|
||
|
* @param event
|
||
|
*
|
||
|
*/
|
||
|
function ckeInlineMouseoverEvent(event) {
|
||
|
|
||
|
// we initialize the inline editor only when moused over
|
||
|
// so that a page can handle lots of editors at once without
|
||
|
// them all being active
|
||
|
|
||
|
var $t = $(this);
|
||
|
if($t.hasClass("InputfieldCKEditorLoaded")) return;
|
||
|
$t.effect('highlight', {}, 500);
|
||
|
$t.attr('contenteditable', 'true');
|
||
|
if(event.type == 'focusin') {
|
||
|
CKEDITOR.once('instanceReady', function(event) {
|
||
|
$(':focus').trigger('blur');
|
||
|
event.editor.focus();
|
||
|
});
|
||
|
}
|
||
|
var editor = CKEDITOR.inline($t.attr('id'), ckeGetConfigData($t));
|
||
|
ckeInitEvents(editor);
|
||
|
$t.addClass("InputfieldCKEditorLoaded");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* CKEditors hidden in jQuery UI tabs sometimes don't work so this initializes them when they become visible
|
||
|
*
|
||
|
*/
|
||
|
function ckeInitTab(event, ui) {
|
||
|
var $t = ui.newTab;
|
||
|
var $a = $t.find('a');
|
||
|
if($a.hasClass('InputfieldCKEditor_init')) return;
|
||
|
var editorID = $a.attr('data-editorID');
|
||
|
var configName = $a.attr('data-configName');
|
||
|
var editor = CKEDITOR.replace(editorID, config[configName]);
|
||
|
ckeInitEvents(editor);
|
||
|
$a.addClass('InputfieldCKEditor_init');
|
||
|
ui.oldTab.find('a').addClass('InputfieldCKEditor_init'); // in case it was the starting one
|
||
|
var $editor = $("#" + editorID);
|
||
|
$editor.addClass('InputfieldCKEditorLoaded');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initialize a normal CKEditor instance for the given textarea ID
|
||
|
*
|
||
|
* @param editorID
|
||
|
*
|
||
|
*/
|
||
|
function ckeInitNormal(editorID) {
|
||
|
|
||
|
var $editor = $('#' + editorID);
|
||
|
var $parent = $editor.parent();
|
||
|
var configName;
|
||
|
|
||
|
if(typeof ProcessWire.config.InputfieldCKEditor.editors[editorID] != "undefined") {
|
||
|
configName = ProcessWire.config.InputfieldCKEditor.editors[editorID];
|
||
|
} else {
|
||
|
configName = $editor.attr('data-configName');
|
||
|
}
|
||
|
|
||
|
if($parent.hasClass('ui-tabs-panel') && $parent.css('display') == 'none') {
|
||
|
// CKEditor in a jQuery UI tab (like langTabs)
|
||
|
var parentID = $editor.parent().attr('id');
|
||
|
var $a = $parent.closest('.ui-tabs, .langTabs').find('a[href=#' + parentID + ']');
|
||
|
$a.attr('data-editorID', editorID).attr('data-configName', configName);
|
||
|
$parent.closest('.ui-tabs, .langTabs').on('tabsactivate', ckeInitTab);
|
||
|
} else {
|
||
|
// visible CKEditor
|
||
|
var configData = ckeGetConfigData($editor);
|
||
|
var editor = CKEDITOR.replace(editorID, configData);
|
||
|
if(editor) {
|
||
|
ckeInitEvents(editor);
|
||
|
$editor.addClass('InputfieldCKEditorLoaded');
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Prepare inline editors
|
||
|
*
|
||
|
*/
|
||
|
$(document).ready(function() {
|
||
|
|
||
|
/**
|
||
|
* Override ckeditor timestamp for cache busting
|
||
|
*
|
||
|
*/
|
||
|
CKEDITOR.timestamp = ProcessWire.config.InputfieldCKEditor.timestamp;
|
||
|
|
||
|
/**
|
||
|
* Regular editors
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
for(var editorID in ProcessWire.config.InputfieldCKEditor.editors) {
|
||
|
ckeInitNormal(editorID);
|
||
|
}
|
||
|
|
||
|
$(document).on('reloaded', '.InputfieldCKEditor', function() {
|
||
|
// reloaded event is sent to .Inputfield when the contents of the .Inputfield
|
||
|
// have been replaced with new markup
|
||
|
var $editor = $(this).find('.InputfieldCKEditorNormal:not(.InputfieldCKEditorLoaded)');
|
||
|
$editor.each(function() {
|
||
|
ckeInitNormal($(this).attr('id'));
|
||
|
});
|
||
|
return false;
|
||
|
});
|
||
|
|
||
|
$(document).on('image-edit sort-stop', '.InputfieldCKEditor', function() {
|
||
|
// re-initialize CKE when image-edit event triggers (from InputfieldImage.js) via rpsallis
|
||
|
var $editor = $(this).find('.InputfieldCKEditorNormal');
|
||
|
$editor.each(function() {
|
||
|
var editorID = $(this).attr('id');
|
||
|
if(typeof CKEDITOR.instances[editorID] !== 'undefined') CKEDITOR.instances[editorID].destroy();
|
||
|
ckeInitNormal(editorID);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* Inline editors
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
CKEDITOR.disableAutoInline = true;
|
||
|
$(document).on('mouseover focus', '.InputfieldCKEditorInlineEditor', ckeInlineMouseoverEvent);
|
||
|
$(document).on('submit', 'form.InputfieldForm', function() {
|
||
|
ckeSaveReadyInline($(this));
|
||
|
// note: not necessary for regular editors since CKE takes care
|
||
|
// of populating itself to the textarea on it's own during submit
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* saveReady event handler
|
||
|
*
|
||
|
* saveReady is sent by some form-to-ajax page save utils in ProcessWire
|
||
|
* found it was necessary for normal CKE instances because a cancelled submit
|
||
|
* event was not updating the original textarea, so we do it manually
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
$(document).on('saveReady', '.InputfieldCKEditor', function() {
|
||
|
ckeSaveReadyNormal($(this));
|
||
|
ckeSaveReadyInline($(this));
|
||
|
});
|
||
|
});
|