949 lines
24 KiB
JavaScript
949 lines
24 KiB
JavaScript
/**
|
||
* InputfieldTinyMCE.js
|
||
*
|
||
* ProcessWire 3.x, Copyright 2022 by Ryan Cramer
|
||
* https://processwire.com
|
||
*
|
||
* TinyMCE 6.x, Copyright (c) 2022 Ephox Corporation DBA Tiny Technologies, Inc.
|
||
* https://www.tiny.cloud/docs/tinymce/6/
|
||
*
|
||
*/
|
||
|
||
/**
|
||
* Handler for image uploads
|
||
*
|
||
* @param blobInfo
|
||
* @param progress
|
||
* @returns {Promise<unknown>}
|
||
*
|
||
*/
|
||
var InputfieldTinyMCEUploadHandler = (blobInfo, progress) => new Promise((resolve, reject) => {
|
||
|
||
var editor = tinymce.activeEditor;
|
||
var $inputfield = $('#' + editor.id).closest('.InputfieldTinyMCE');
|
||
var imageFieldName = $inputfield.attr('data-upload-field');
|
||
var $imageInputfield = $('#wrap_Inputfield_' + imageFieldName);
|
||
var pageId = $inputfield.attr('data-upload-page');
|
||
var uploadUrl = ProcessWire.config.urls.admin + 'page/edit/?id=' + pageId + '&InputfieldFileAjax=1&ckeupload=1';
|
||
|
||
const xhr = new XMLHttpRequest();
|
||
|
||
xhr.withCredentials = true;
|
||
xhr.upload.onprogress = (e) => { progress(e.loaded / e.total * 100); };
|
||
xhr.open('POST', uploadUrl);
|
||
|
||
xhr.onload = () => {
|
||
if(xhr.status === 403) {
|
||
reject({ message: 'HTTP Error: ' + xhr.status, remove: true });
|
||
return;
|
||
} else if(xhr.status < 200 || xhr.status >= 300) {
|
||
reject('HTTP Error: ' + xhr.status);
|
||
return;
|
||
}
|
||
|
||
var response = JSON.parse(xhr.responseText);
|
||
|
||
if(!response) {
|
||
reject('Invalid JSON in response: ' + xhr.responseText);
|
||
return;
|
||
}
|
||
|
||
resolve(response.url);
|
||
};
|
||
|
||
xhr.onerror = () => {
|
||
reject('Image upload failed due to a XHR Transport error. Code: ' + xhr.status);
|
||
};
|
||
|
||
$imageInputfield.trigger('pwimageupload', {
|
||
'name': blobInfo.filename(),
|
||
'file': blobInfo.blob(),
|
||
'xhr': xhr
|
||
});
|
||
});
|
||
|
||
/**
|
||
* InputfieldTinyMCE main
|
||
*
|
||
*/
|
||
var InputfieldTinyMCE = {
|
||
|
||
/**
|
||
* Debug mode?
|
||
*
|
||
*/
|
||
debug: false,
|
||
|
||
/**
|
||
* Are document events attached?
|
||
*
|
||
*/
|
||
eventsReady: false,
|
||
|
||
/**
|
||
* Is document ready?
|
||
*
|
||
*/
|
||
isDocumentReady: false,
|
||
|
||
/**
|
||
* Editor selectors to init at document ready
|
||
*
|
||
*/
|
||
editorIds: [],
|
||
|
||
/**
|
||
* Are we currently processing an editor init? (bool or string)
|
||
*
|
||
*/
|
||
initializing: false,
|
||
|
||
/**
|
||
* Allow lazy loaded editor init()? (adjusted by this class at runtime)
|
||
*
|
||
*/
|
||
allowLazy: true,
|
||
|
||
/**
|
||
* Ccallback functions
|
||
*
|
||
*/
|
||
callbacks: { onSetup: [], onConfig: [], onReady: [] },
|
||
|
||
/**
|
||
* Recognized class names
|
||
*
|
||
*/
|
||
cls: {
|
||
lazy: 'InputfieldTinyMCELazy',
|
||
inline: 'InputfieldTinyMCEInline',
|
||
normal: 'InputfieldTinyMCENormal',
|
||
loaded: 'InputfieldTinyMCELoaded',
|
||
editor: 'InputfieldTinyMCEEditor',
|
||
placeholder: 'InputfieldTinyMCEPlaceholder'
|
||
},
|
||
|
||
/**
|
||
* Console log
|
||
*
|
||
* @param a
|
||
* @param b
|
||
*
|
||
*/
|
||
log: function(a, b) {
|
||
if(!this.debug) return;
|
||
if(typeof b !== 'undefined') {
|
||
if(typeof a === 'string') a = 'TinyMCE ' + a;
|
||
console.log(a, b);
|
||
} else {
|
||
console.log('TinyMCE', a);
|
||
}
|
||
},
|
||
|
||
/**
|
||
* Add a setup callback function
|
||
*
|
||
* ~~~~~
|
||
* InputfieldTinyMCE.onSetup(function(editor) {
|
||
* // ...
|
||
* });
|
||
* ~~~~~
|
||
*
|
||
* @param callback
|
||
*
|
||
*/
|
||
onSetup: function(callback) {
|
||
this.callbacks.onSetup.push(callback);
|
||
},
|
||
|
||
/**
|
||
* Add a config callback function
|
||
*
|
||
* ~~~~~
|
||
* InputfieldTinyMCE.onConfig(function(settings, $editor, $inputfield) {
|
||
* // ...
|
||
* });
|
||
* ~~~~~
|
||
*
|
||
* @param callback
|
||
*
|
||
*/
|
||
onConfig: function(callback) {
|
||
this.callbacks.onConfig.push(callback);
|
||
},
|
||
|
||
/**
|
||
* Add a ready callback function
|
||
*
|
||
* ~~~~~
|
||
* InputfieldTinyMCE.onReady(function(editor) {
|
||
* // ...
|
||
* });
|
||
* ~~~~~
|
||
*
|
||
* @param callback
|
||
*
|
||
*/
|
||
onReady: function(callback) {
|
||
this.callbacks.onReady.push(callback);
|
||
},
|
||
|
||
/**
|
||
* Set editor initializing state
|
||
*
|
||
* @param {boolean|string} initializing Boolean or editor ID
|
||
*
|
||
*/
|
||
setInitializing: function(initializing) {
|
||
this.initializing = initializing;
|
||
},
|
||
|
||
/**
|
||
* Is editor initializing?
|
||
*
|
||
* @returns boolean|string False or editor id (string)
|
||
*
|
||
*/
|
||
isInitializing: function() {
|
||
return this.initializing;
|
||
},
|
||
|
||
/**
|
||
* Modify image dimensions
|
||
*
|
||
* @param editor
|
||
* @param img
|
||
* @param width
|
||
*
|
||
*/
|
||
imageResized: function(editor, img, width) {
|
||
var t = this;
|
||
var src = img.src;
|
||
var hidpi = img.className.indexOf('hidpi') > -1 ? 1 : 0;
|
||
var basename = src.substring(src.lastIndexOf('/')+1);
|
||
var path = src.substring(0, src.lastIndexOf('/')+1);
|
||
var dot1 = basename.indexOf('.');
|
||
var dot2 = basename.lastIndexOf('.');
|
||
var crop = '';
|
||
|
||
if(basename.indexOf('-cropx') > -1 || basename.indexOf('.cropx') > -1) {
|
||
// example: gonzo_the_great.205x183-cropx38y2-is.jpg
|
||
// use image file as-is
|
||
// @todo re-crop and resize from original?
|
||
} else if(dot1 !== dot2) {
|
||
// extract any existing resize data present to get original file
|
||
// i.e. file.123x456-is-hidpi.jpg => file.jpg
|
||
var basename2 = basename.substring(0, dot1) + basename.substring(dot2);
|
||
src = path + basename2;
|
||
}
|
||
|
||
var url = ProcessWire.config.urls.admin + 'page/image/resize' +
|
||
'?json=1' +
|
||
'&width=' + width +
|
||
'&hidpi=' + hidpi +
|
||
'&file=' + src;
|
||
|
||
t.log('Resizing image to width=' + width, url);
|
||
|
||
jQuery.getJSON(url, function(data) {
|
||
editor.dom.setAttrib(img, 'src', data.src);
|
||
// editor.dom.setAttrib(img, 'width', data.width);
|
||
// editor.dom.setAttrib(img, 'height', data.height);
|
||
t.log('Resized image to width=' + data.width, data.src);
|
||
});
|
||
},
|
||
|
||
/**
|
||
* Called when an element has an align class applied to it
|
||
*
|
||
* This function ensures only 1 align class is applied at a time.
|
||
*
|
||
* @param editor
|
||
*
|
||
*/
|
||
elementAligned: function(editor) {
|
||
var selection = editor.selection;
|
||
var node = selection.getNode();
|
||
var className = node.className;
|
||
var n;
|
||
|
||
// if only one align class then return now
|
||
if(className.indexOf('align') === className.lastIndexOf('align')) return;
|
||
|
||
var alignNames = [];
|
||
var classNames = className.split(' ');
|
||
|
||
for(n = 0; n < classNames.length; n++) {
|
||
if(classNames[n].indexOf('align') === 0) {
|
||
alignNames.push(classNames[n]);
|
||
}
|
||
}
|
||
|
||
// pop off last align class, which we will keep
|
||
alignNames.pop();
|
||
|
||
for(n = 0; n < alignNames.length; n++) {
|
||
className = className.replace(alignNames[n], '');
|
||
}
|
||
|
||
node.className = className.trim();
|
||
},
|
||
|
||
/**
|
||
* Lazy load placeholder click event
|
||
*
|
||
* @param e
|
||
* @returns {boolean}
|
||
*
|
||
*/
|
||
clickPlaceholder: function(e) {
|
||
var t = InputfieldTinyMCE;
|
||
var $placeholder = jQuery(this);
|
||
var $textarea = $placeholder.next('textarea');
|
||
$placeholder.remove();
|
||
t.log('placeholderClick', $placeholder);
|
||
if($textarea.length) t.init('#' + $textarea.attr('id'), 'event.' + e.type);
|
||
return false;
|
||
},
|
||
|
||
/**
|
||
* Init/populate lazy load placeholder elements within given target
|
||
*
|
||
* @param $placeholders Placeholders are wrapper that has them within
|
||
*
|
||
*/
|
||
initPlaceholders: function($placeholders) {
|
||
if(!$placeholders.length) return;
|
||
var t = this;
|
||
var $item = $placeholders.first();
|
||
if(!$item.hasClass(t.cls.placeholder)) $placeholders = $item.find('.' + t.cls.placeholder);
|
||
$placeholders.each(function() {
|
||
var $placeholder = jQuery(this);
|
||
var $textarea = $placeholder.next('textarea');
|
||
t.log('initPlaceholder', $placeholder);
|
||
$placeholder.children('.mce-content-body').html($textarea.val());
|
||
$placeholder.on('click touchstart', t.clickPlaceholder);
|
||
});
|
||
},
|
||
|
||
/**
|
||
* Init callback function
|
||
*
|
||
* @param editor
|
||
* @param features
|
||
*
|
||
*/
|
||
editorReady: function(editor, features) {
|
||
|
||
var t = this;
|
||
var $editor = jQuery('#' + editor.id);
|
||
var $inputfield = $editor.closest('.InputfieldTinyMCE');
|
||
var inputTimeout = null;
|
||
|
||
if(!$inputfield.length) $inputfield = $editor.closest('.Inputfield');
|
||
|
||
function changed() {
|
||
if(inputTimeout) clearTimeout(inputTimeout);
|
||
inputTimeout = setTimeout(function() {
|
||
$inputfield.trigger('change');
|
||
}, 500);
|
||
}
|
||
|
||
editor.on('Dirty', function() { changed() });
|
||
editor.on('SetContent', function() { changed() });
|
||
editor.on('input', function() { changed() });
|
||
|
||
// for image resizes
|
||
if(features.indexOf('imgResize') > -1) {
|
||
editor.on('ObjectResized', function(e, data) {
|
||
// @todo account for case where image in figure is resized, and figure needs its width updated with the image
|
||
if(e.target.nodeName === 'IMG') {
|
||
t.imageResized(editor, e.target, e.width);
|
||
changed();
|
||
}
|
||
});
|
||
}
|
||
|
||
for(var n = 0; n < t.callbacks.onReady.length; n++) {
|
||
t.callbacks.onReady[n](editor);
|
||
}
|
||
|
||
editor.on('ExecCommand', function(e, f) {
|
||
if(e.command === 'mceFocus') return;
|
||
t.log('command: ' + e.command, e);
|
||
if(e.command === 'mceToggleFormat') {
|
||
if(e.value && e.value.indexOf('align') === 0) {
|
||
var editor = this;
|
||
t.elementAligned(editor);
|
||
}
|
||
changed();
|
||
}
|
||
});
|
||
|
||
/*
|
||
* uncomment to show inline init effect
|
||
if(jQuery.ui) {
|
||
if($editor.hasClass('InputfieldTinyMCEInline')) {
|
||
$editor.effect('highlight', {}, 500);
|
||
}
|
||
}
|
||
*/
|
||
|
||
/*
|
||
editor.on('ResizeEditor', function(e) {
|
||
// editor resized
|
||
t.log('ResizeEditor');
|
||
});
|
||
*/
|
||
},
|
||
|
||
/**
|
||
* config.setup handler function
|
||
*
|
||
* @param editor
|
||
*
|
||
*/
|
||
setupEditor: function(editor) {
|
||
var t = InputfieldTinyMCE;
|
||
var $editor = jQuery('#' + editor.id);
|
||
|
||
if($editor.hasClass(t.cls.loaded)) {
|
||
t.log('mceInit called on input that is already loaded', editor.id);
|
||
} else {
|
||
$editor.addClass(t.cls.loaded);
|
||
}
|
||
|
||
for(var n = 0; n < t.callbacks.onSetup.length; n++) {
|
||
t.callbacks.onSetup[n](editor);
|
||
}
|
||
|
||
/*
|
||
editor.on('init', function() {
|
||
// var n = performance.now();
|
||
// t.log(editor.id + ': ' + (n - mceTimer) + ' ms');
|
||
});
|
||
*/
|
||
|
||
/*
|
||
editor.on('Load', function() {
|
||
// t.log('iframe loaded', editor.id);
|
||
});
|
||
*/
|
||
},
|
||
|
||
/**
|
||
* Destroy given editors
|
||
*
|
||
* @param $editors
|
||
*
|
||
*/
|
||
destroyEditors: function($editors) {
|
||
var t = this;
|
||
$editors.each(function() {
|
||
var $editor = jQuery(this);
|
||
if(!$editor.hasClass(t.cls.loaded)) return;
|
||
var editorId = $editor.attr('id');
|
||
var editor = tinymce.get(editorId);
|
||
$editor.removeClass(t.cls.loaded).removeClass(t.cls.lazy);
|
||
t.log('destroyEditor', editor.id);
|
||
// $editor.css('display', 'none');
|
||
editor.destroy();
|
||
});
|
||
},
|
||
|
||
/**
|
||
* Destroy editors in given wrapper
|
||
*
|
||
* @param $wrapper
|
||
*
|
||
*/
|
||
destroyEditorsIn($wrapper) {
|
||
this.destroyEditors($wrapper.find('.' + this.cls.loaded));
|
||
},
|
||
|
||
/**
|
||
* Reset given editors (destroy and re-init)
|
||
*
|
||
* @param $editors
|
||
*
|
||
*/
|
||
resetEditors: function($editors) {
|
||
var t = this;
|
||
t.allowLazy = false;
|
||
$editors.each(function() {
|
||
var $editor = jQuery(this);
|
||
if(!$editor.hasClass(t.cls.loaded)) return;
|
||
var editorId = $editor.attr('id');
|
||
var editor = tinymce.get(editorId);
|
||
editor.destroy();
|
||
$editor.removeClass(t.cls.loaded);
|
||
// t.init('#' + editorId, 'resetEditors');
|
||
});
|
||
t.initEditors($editors);
|
||
t.allowLazy = true;
|
||
},
|
||
|
||
/**
|
||
* Initialize given jQuery object editors
|
||
*
|
||
* @param $editors
|
||
*
|
||
*/
|
||
initEditors: function($editors) {
|
||
var t = this;
|
||
$editors.each(function() {
|
||
var $editor = jQuery(this);
|
||
var editorId = $editor.attr('id');
|
||
if($editor.hasClass(t.cls.loaded)) return;
|
||
//t.log('init', id);
|
||
t.init('#' + editorId, 'initEditors');
|
||
});
|
||
},
|
||
|
||
/**
|
||
* Find and initialize editors within a wrapper
|
||
*
|
||
* @param $wrapper
|
||
* @param selector Optional
|
||
*
|
||
*/
|
||
initEditorsIn: function($wrapper, selector) {
|
||
if(typeof selector === 'undefined') {
|
||
selector =
|
||
'.' + this.cls.lazy + ':visible, ' +
|
||
'.' + this.cls.editor +
|
||
':not(.' + this.cls.loaded + ')' +
|
||
':not(.' + this.cls.lazy + ')' +
|
||
':not(.' + this.cls.inline + ')';
|
||
}
|
||
var $placeholders = $wrapper.find('.' + this.cls.placeholder);
|
||
var $editors = $wrapper.find(selector);
|
||
if($placeholders.length) this.initPlaceholders($placeholders);
|
||
if($editors.length) this.initEditors($editors);
|
||
},
|
||
|
||
/**
|
||
* Get config (config + custom settings)
|
||
*
|
||
* @param $editor Editor Textarea (Regular) or div (Inline)
|
||
* @param $inputfield Editor Wrapping .Inputfield element
|
||
* @param features
|
||
* @returns {{}}
|
||
*
|
||
*/
|
||
getConfig: function($editor, $inputfield, features) {
|
||
|
||
var configName = $inputfield.attr('data-configName');
|
||
var globalConfig = ProcessWire.config.InputfieldTinyMCE;
|
||
var settings = globalConfig.settings.default;
|
||
var namedSettings = globalConfig.settings[configName];
|
||
var dataSettings = $inputfield.attr('data-settings');
|
||
|
||
if(typeof settings === 'undefined') {
|
||
settings = {};
|
||
} else {
|
||
settings = jQuery.extend(true, {}, settings);
|
||
}
|
||
|
||
if(typeof namedSettings === 'undefined') {
|
||
this.log('Can’t find ProcessWire.config.InputfieldTinyMCE.settings.' + configName);
|
||
} else {
|
||
jQuery.extend(settings, namedSettings);
|
||
}
|
||
|
||
if(typeof dataSettings === 'undefined') {
|
||
dataSettings = null;
|
||
} else if(dataSettings && dataSettings.length > 2) {
|
||
dataSettings = JSON.parse(dataSettings);
|
||
jQuery.extend(settings, dataSettings);
|
||
}
|
||
|
||
if(settings.inline) settings.content_css = null; // we load this separately for inline mode
|
||
|
||
for(var n = 0; n < this.callbacks.onConfig.length; n++) {
|
||
this.callbacks.onConfig[n](settings, $editor, $inputfield);
|
||
}
|
||
|
||
if(features.indexOf('pasteFilter') > -1) {
|
||
if(globalConfig.pasteFilter === 'text') {
|
||
settings.paste_as_text = true;
|
||
} else if(globalConfig.pasteFilter.length) {
|
||
settings.paste_preprocess = this.pastePreprocess;
|
||
}
|
||
}
|
||
/*
|
||
settings.paste_postprocess = function(editor, args) {
|
||
console.log(args.node);
|
||
args.node.setAttribute('id', '42');
|
||
};
|
||
*/
|
||
|
||
return settings;
|
||
},
|
||
|
||
/**
|
||
* Pre-process paste
|
||
*
|
||
* @param editor
|
||
* @param args
|
||
*
|
||
*/
|
||
pastePreprocess: function(editor, args) {
|
||
|
||
var t = InputfieldTinyMCE;
|
||
var allow = ',' + ProcessWire.config.InputfieldTinyMCE.pasteFilter + ',';
|
||
var regexTag = /<([a-z0-9]+)([^>]*)>/gi;
|
||
var regexAttr = /([-_a-z0-9]+)=["']([^"']*)["']/gi;
|
||
var html = args.content;
|
||
var matchTag, matchAttr;
|
||
var removals = [];
|
||
var finds = [];
|
||
var replaces = [];
|
||
var startLength = html.length;
|
||
|
||
if(args.internal) {
|
||
t.log('Skipping pasteFilter for interal copy/paste');
|
||
return; // skip filtering for internal copy/paste operations
|
||
}
|
||
|
||
if(allow === ',text,') {
|
||
t.log('Skipping pasteFilter since paste_as_text settingw will be used');
|
||
return; // will be processed by paste_as_text setting
|
||
}
|
||
|
||
while((matchTag = regexTag.exec(html)) !== null) {
|
||
|
||
var tagOpen = matchTag[0]; // i.e. <strong>, <img src="..">, <h2>, etc.
|
||
var tagName = matchTag[1]; // i.e. 'strong', 'img', 'h2', etc.
|
||
var tagClose = '</' + tagName + '>'; // i.e. </strong>, </h2>
|
||
var tagAttrs = matchTag[2]; // i.e. 'src="a.jpg" alt="alt"'
|
||
var allowAttrs = false;
|
||
|
||
// first see if we can match a tag replacement
|
||
var find = ',' + tagName + '='; // i.e. ',b=strong'
|
||
var pos = allow.indexOf(find);
|
||
|
||
if(pos > -1) {
|
||
// tag replacement
|
||
var rule = allow.substring(pos + 1); // i.e. b=strong,and,more
|
||
rule = rule.substring(0, rule.indexOf(',')); // i.e. b=strong
|
||
rule = rule.split('=');
|
||
var replaceTag = rule[1];
|
||
finds.push(tagOpen);
|
||
replaces.push('<' + replaceTag + '>');
|
||
finds.push(tagClose);
|
||
replaces.push('</' + replaceTag + '>');
|
||
}
|
||
|
||
if(allow.indexOf(',' + tagName + '[') > -1) {
|
||
// tag appears in whitelist with attributes
|
||
allowAttrs = true;
|
||
} else if(allow.indexOf(',' + tagName + ',') === -1) {
|
||
// tag does not appear in whitelist
|
||
removals.push(tagOpen);
|
||
removals.push(tagClose);
|
||
continue;
|
||
} else {
|
||
// tag appears in whitelist (no attributes)
|
||
}
|
||
|
||
if(tagAttrs.length) {
|
||
// tag has attributes
|
||
if(!allowAttrs) {
|
||
// attributes not allowed, replace with non-attribute tag
|
||
finds.push(tagOpen);
|
||
replaces.push('<' + tagName + '>');
|
||
continue;
|
||
}
|
||
} else {
|
||
// no attributes, nothing further to do
|
||
continue;
|
||
}
|
||
|
||
var attrRemoves = [];
|
||
|
||
while((matchAttr = regexAttr.exec(tagAttrs)) !== null) {
|
||
var attrStr = matchAttr[0]; // i.e. alt="hello"
|
||
var attrName = matchAttr[1]; // i.e. alt
|
||
var attrVal = matchAttr[2]; // i.e. hello
|
||
|
||
if(allow.indexOf(',' + tagName + '[' + attrName + ']') > -1) {
|
||
// matches whitelist of tag with allowed attribute
|
||
} else if(allow.indexOf(',' + tagName + '[' + attrName + '=' + attrVal + ']') > -1) {
|
||
// matches whitelist of tag with allowed attribute having allowed value
|
||
} else {
|
||
// attributes do not match whitelist
|
||
attrRemoves.push(attrStr);
|
||
}
|
||
}
|
||
|
||
if(attrRemoves.length) {
|
||
var replaceOpenTag = tagOpen;
|
||
for(var n = 0; n < attrRemoves.length; n++) {
|
||
replaceOpenTag = replaceOpenTag.replace(attrRemoves[n], '');
|
||
}
|
||
finds.push(tagOpen);
|
||
replaces.push(replaceOpenTag);
|
||
}
|
||
}
|
||
|
||
// console.log('removals', removals);
|
||
|
||
for(var n = 0; n < removals.length; n++) {
|
||
html = html.replace(removals[n], '');
|
||
}
|
||
|
||
for(var n = 0; n < finds.length; n++) {
|
||
html = html.replace(finds[n], replaces[n]);
|
||
// console.log(finds[n] + ' => ' + replaces[n]);
|
||
}
|
||
|
||
while(html.indexOf('< ') > -1) html = html.replace('< ', '<');
|
||
while(html.indexOf(' >') > -1) html = html.replace(' >', '>');
|
||
while(html.indexOf(' ') > -1) html = html.replace(' ', ' ', html);
|
||
|
||
html = html.replaceAll(/<([-a-z0-9]+)[^>]*>\s*<\/\1>/ig, ''); // remove empty tags
|
||
|
||
t.log('Completed pasteFilter ' + startLength + ' => ' + html.length + ' bytes');
|
||
|
||
args.content = html;
|
||
},
|
||
|
||
/**
|
||
* Document ready events
|
||
*
|
||
*/
|
||
initDocumentEvents: function() {
|
||
var t = this;
|
||
|
||
jQuery(document)
|
||
.on('click mouseover focus touchstart', '.' + t.cls.inline + ':not(.' + t.cls.loaded + ')', function(e) {
|
||
// 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
|
||
if(InputfieldTinyMCE.isInitializing() !== false) return;
|
||
t.init('#' + this.id, 'event.' + e.type);
|
||
})
|
||
.on('image-edit sort-stop', '.InputfieldTinyMCE', function(e) {
|
||
// all "normal" editors that are also "loaded"
|
||
var $editors = jQuery(this).find('.' + t.cls.normal + '.' + t.cls.loaded);
|
||
if($editors.length) {
|
||
t.log(e.type + '.resetEditors', $editors);
|
||
// force all loaded to reset
|
||
t.resetEditors($editors);
|
||
}
|
||
// all "normal" non-placeholder editors that are not yet "loaded"
|
||
var $editors = jQuery(this).find('.' + t.cls.normal + ':not(.' + t.cls.loaded + '):not(.' + t.cls.placeholder + ')');
|
||
if($editors.length) {
|
||
t.log(e.type + '.initEditors', $editors);
|
||
t.initEditors($editors);
|
||
}
|
||
})
|
||
.on('reload', '.Inputfield', function() {
|
||
var $inputfield = jQuery(this);
|
||
var $editors = $inputfield.find('.' + t.cls.loaded);
|
||
if($editors.length) {
|
||
t.log('reload', $inputfield.attr('id'));
|
||
t.destroyEditors($editors);
|
||
}
|
||
})
|
||
.on('reloaded', '.Inputfield', function(e) {
|
||
t.initEditorsIn(jQuery(this));
|
||
/*
|
||
var $inputfield = $(this);
|
||
var s = '.' + t.cls.editor + ':not(.' + t.cls.loaded + '):not(.' + t.cls.lazy + ')';
|
||
var $editors = $inputfield.find(s);
|
||
if($editors.length) {
|
||
t.log(e.type, $inputfield.attr('id'));
|
||
t.initEditors($editors);
|
||
}
|
||
var $placeholders = $inputfield.find('.' + t.cls.placeholder);
|
||
if($placeholders.length) t.initPlaceholders($placeholders);
|
||
return false;
|
||
*/
|
||
})
|
||
.on('sortstop', function(e) {
|
||
var $editors = jQuery(e.target).find('.' + t.cls.loaded);
|
||
if($editors.length) {
|
||
t.log('sortstop');
|
||
t.resetEditors($editors);
|
||
}
|
||
})
|
||
.on('opened', '.Inputfield', function(e) {
|
||
t.initEditorsIn(jQuery(this));
|
||
})
|
||
.on('clicklangtab wiretabclick', function(e, $newTab) {
|
||
t.initEditorsIn($newTab);
|
||
/*
|
||
var $placeholders = $newTab.find('.' + t.cls.placeholder);
|
||
var $editors = $newTab.find('.' + t.cls.lazy + ':visible');
|
||
t.log(e.type, $newTab.attr('id'));
|
||
if($placeholders.length) t.initPlaceholders($placeholders);
|
||
if($editors.length) t.initEditors($editors);
|
||
*/
|
||
});
|
||
|
||
/*
|
||
.on('sortstart', function() {
|
||
var $editors = $(e.target).find('.InputfieldTinyMCELoaded');
|
||
$editors.each(function() {
|
||
var $editor = $(this);
|
||
t.log('sortstart', $editor.attr('id'));
|
||
});
|
||
})
|
||
*/
|
||
|
||
this.eventsReady = true;
|
||
},
|
||
|
||
/**
|
||
* Document ready
|
||
*
|
||
*/
|
||
documentReady: function() {
|
||
this.debug = ProcessWire.config.InputfieldTinyMCE.debug;
|
||
this.isDocumentReady = true;
|
||
this.log('documentReady', this.editorIds);
|
||
while(this.editorIds.length > 0) {
|
||
var editorId = this.editorIds.shift();
|
||
this.init(editorId, 'documentReady');
|
||
}
|
||
this.initDocumentEvents();
|
||
var $placeholders = jQuery('.' + this.cls.placeholder + ':visible');
|
||
if($placeholders.length) this.initPlaceholders($placeholders);
|
||
if(this.debug) {
|
||
this.log('qty',
|
||
'normal=' + jQuery('.' + this.cls.normal).length + ', ' +
|
||
'inline=' + jQuery('.' + this.cls.inline).length + ', ' +
|
||
'lazy=' + jQuery('.' + this.cls.lazy).length + ', ' +
|
||
'loaded=' + jQuery('.' + this.cls.loaded).length + ', ' +
|
||
'placeholders=' + $placeholders.length
|
||
);
|
||
}
|
||
},
|
||
|
||
/**
|
||
* Initialize an editor
|
||
*
|
||
* ~~~~~
|
||
* InputfieldTinyMCE.init('#my-textarea');
|
||
* ~~~~~
|
||
*
|
||
* @param id Editor id or selector string
|
||
* @param caller Optional name of caller (for debugging purposes)
|
||
* @returns {boolean}
|
||
*
|
||
*/
|
||
init: function(id, caller) {
|
||
|
||
var $editor, config, features, $inputfield, isFront = false,
|
||
selector, isLazy, useLazy, _id = id, t = this;
|
||
|
||
if(!this.isDocumentReady) {
|
||
this.editorIds.push(id);
|
||
return true;
|
||
}
|
||
|
||
this.setInitializing(id);
|
||
|
||
caller = (t.debug && typeof caller !== 'undefined' ? ' (caller=' + caller + ')' : '');
|
||
|
||
if(typeof id === 'string') {
|
||
// literal id or selector string
|
||
if(id.indexOf('#') === 0 || id.indexOf('.') === 0) {
|
||
selector = id;
|
||
id = '';
|
||
} else {
|
||
selector = '#' + id;
|
||
}
|
||
$editor = jQuery(selector);
|
||
if(id === '') id = $editor.attr('id');
|
||
|
||
} else if(typeof id === 'object') {
|
||
// element or jQuery element
|
||
if(id instanceof jQuery) {
|
||
$editor = id;
|
||
} else {
|
||
$editor = jQuery(id);
|
||
}
|
||
id = $editor.attr('id');
|
||
selector = '#' + id;
|
||
}
|
||
|
||
if(!$editor.length) {
|
||
console.error('Cannot find element to init TinyMCE: ' + _id);
|
||
this.setInitializing(false);
|
||
return false;
|
||
}
|
||
|
||
if(id.indexOf('Inputfield_') === 0) {
|
||
$inputfield = jQuery('#wrap_' + id);
|
||
} else if($editor.hasClass('pw-edit-copy')) {
|
||
$inputfield = $editor.closest('.pw-edit-InputfieldTinyMCE');
|
||
isFront = true; // PageFrontEdit
|
||
} else {
|
||
$inputfield = jQuery('#wrap_Inputfield_' + id);
|
||
}
|
||
|
||
if(!$inputfield.length) {
|
||
$inputfield = $editor.closest('.InputfieldTinyMCE');
|
||
if(!$inputfield.length) $inputfield = $editor.closest('.Inputfield');
|
||
}
|
||
|
||
features = $inputfield.attr('data-features');
|
||
if(typeof features === 'undefined') features = '';
|
||
|
||
useLazy = t.allowLazy && features.indexOf('lazyMode') > -1;
|
||
isLazy = $editor.hasClass(t.cls.lazy);
|
||
|
||
if(useLazy && !isLazy && !$editor.is(':visible') && !$editor.hasClass(t.cls.inline)) {
|
||
$editor.addClass(t.cls.lazy);
|
||
this.log('init-lazy', id + caller);
|
||
this.setInitializing(false);
|
||
return true;
|
||
} else if(isLazy) {
|
||
$editor.removeClass(t.cls.lazy);
|
||
}
|
||
|
||
this.log('init', id + caller);
|
||
|
||
config = this.getConfig($editor, $inputfield, features);
|
||
config.selector = selector;
|
||
config.setup = this.setupEditor;
|
||
config.init_instance_callback = function(editor) {
|
||
t.setInitializing('');
|
||
setTimeout(function() { if(t.isInitializing() === '') t.setInitializing(false); }, 100);
|
||
t.log('ready', editor.id);
|
||
t.editorReady(editor, features);
|
||
}
|
||
|
||
if(isFront) {
|
||
// disable drag/drop image uploads when PageFrontEdit
|
||
config.images_upload_url = '';
|
||
config.automatic_uploads = false;
|
||
config.paste_data_images = false;
|
||
} else if(features.indexOf('imgUpload') > -1) {
|
||
config.images_upload_handler = InputfieldTinyMCEUploadHandler;
|
||
}
|
||
|
||
tinymce.init(config);
|
||
|
||
return true;
|
||
}
|
||
};
|
||
|
||
jQuery(document).ready(function() {
|
||
InputfieldTinyMCE.documentReady();
|
||
});
|
||
|
||
/*
|
||
InputfieldTinyMCE.onSetup(function(editor) {
|
||
editor.ui.registry.addButton('hello', {
|
||
icon: 'user',
|
||
text: 'Hello',
|
||
onAction: function() {
|
||
editor.insertContent('Hello World!')
|
||
}
|
||
});
|
||
});
|
||
*/
|