/**
* Functions for HtmlEditorFields in the back end.
* Includes the JS for the ImageUpload forms.
*
* Relies on the jquery.form.js plugin to power the
* ajax / iframe submissions
*/
var ss = ss || {};
/**
* Wrapper for HTML WYSIWYG libraries, which abstracts library internals
* from interface concerns like inserting and editing links.
* Caution: Incomplete and unstable API.
*/
ss.editorWrappers = {};
ss.editorWrappers.tinyMCE = (function() {
var instance;
return {
init: function(config) {
if(!ss.editorWrappers.tinyMCE.initialized) {
tinyMCE.init(config);
ss.editorWrappers.tinyMCE.initialized = true;
}
},
/**
* @return Mixed Implementation specific object
*/
getInstance: function() {
return this.instance;
},
/**
* Invoked when a content-modifying UI is opened.
*/
onopen: function() {
},
/**
* Invoked when a content-modifying UI is closed.
*/
onclose: function() {
},
/**
* Write the HTML back to the original text area field.
*/
save: function() {
tinyMCE.triggerSave();
},
/**
* Create a new instance based on a textarea field.
*
* Please proxy the events from your editor implementation into JS events
* on the textarea field. For events that do not map directly, use the
* following naming scheme: editor.
*
* @param String
* @param Object Implementation specific configuration
* @param Function
*/
create: function(domID, config) {
this.instance = new tinymce.Editor(domID, config);
// Patch TinyMCE events into underlying textarea field.
this.instance.onInit.add(function(ed) {
if(!ss.editorWrappers.tinyMCE.patched) {
// Not ideal, but there's a memory leak we need to patch
var originalDestroy = tinymce.themes.AdvancedTheme.prototype.destroy;
tinymce.themes.AdvancedTheme.prototype.destroy = function() {
originalDestroy.apply(this, arguments);
if (this.statusKeyboardNavigation) {
this.statusKeyboardNavigation.destroy();
this.statusKeyboardNavigation = null;
}
};
ss.editorWrappers.tinyMCE.patched = true;
}
jQuery(ed.getElement()).trigger('editorinit');
// Periodically check for inline changes when focused,
// since TinyMCE's onChange only fires on certain actions
// like inserting a new paragraph, as opposed to any user input.
// This also works around an issue where the "save" button
// wouldn't trigger if the click is the cause of a "blur" event
// after an (undetected) inline change. This "blur" causes onChange
// to trigger, which will change the button markup to show "alternative" styles,
// effectively cancelling the original click event.
if(ed.settings.update_interval) {
var interval;
jQuery(ed.getBody()).on('focus', function() {
interval = setInterval(function() {
// Update underlying element as necessary
var element = jQuery(ed.getElement());
if(ed.isDirty()) {
// Set content without triggering editor content cleanup
element.val(ed.getContent({format : 'raw', no_events : 1}));
}
}, ed.settings.update_interval);
});
jQuery(ed.getBody()).on('blur', function() {
clearInterval(interval);
});
}
});
this.instance.onChange.add(function(ed, l) {
// Update underlying textarea on every change, so external handlers
// such as changetracker have a chance to trigger properly.
ed.save();
jQuery(ed.getElement()).trigger('change');
});
// Add more events here as needed.
this.instance.render();
},
/**
* Redraw the editor contents
*/
repaint: function() {
tinyMCE.execCommand("mceRepaint");
},
/**
* @return boolean
*/
isDirty: function() {
return this.getInstance().isDirty();
},
/**
* HTML representation of the edited content.
*
* Returns: {String}
*/
getContent: function() {
return this.getInstance().getContent();
},
/**
* DOM tree of the edited content
*
* Returns: DOMElement
*/
getDOM: function() {
return this.getInstance().dom;
},
/**
* Returns: DOMElement
*/
getContainer: function() {
return this.getInstance().getContainer();
},
/**
* Get the closest node matching the current selection.
*
* Returns: {jQuery} DOMElement
*/
getSelectedNode: function() {
return this.getInstance().selection.getNode();
},
/**
* Select the given node within the editor DOM
*
* Parameters: {DOMElement}
*/
selectNode: function(node) {
this.getInstance().selection.select(node);
},
/**
* Replace entire content
*
* @param String HTML
* @param Object opts
*/
setContent: function(html, opts) {
this.getInstance().execCommand('mceSetContent', false, html, opts);
},
/**
* Insert content at the current caret position
*
* @param String HTML
*/
insertContent: function(html, opts) {
this.getInstance().execCommand('mceInsertContent', false, html, opts);
},
/**
* Replace currently selected content
*
* @param {String} html
*/
replaceContent: function(html, opts) {
this.getInstance().execCommand('mceReplaceContent', false, html, opts);
},
/**
* Insert or update a link in the content area (based on current editor selection)
*
* Parameters: {Object} attrs
*/
insertLink: function(attrs, opts) {
this.getInstance().execCommand("mceInsertLink", false, attrs, opts);
},
/**
* Remove the link from the currently selected node (if any).
*/
removeLink: function() {
this.getInstance().execCommand('unlink', false);
},
/**
* Strip any editor-specific notation from link in order to make it presentable in the UI.
*
* Parameters:
* {Object}
* {DOMElement}
*/
cleanLink: function(href, node) {
var cb = tinyMCE.settings['urlconverter_callback'];
if(cb) href = eval(cb + "(href, node, true);");
// Turn into relative
if(href.match(new RegExp('^' + tinyMCE.settings['document_base_url'] + '(.*)$'))) {
href = RegExp.$1;
}
// Get rid of TinyMCE's temporary URLs
if(href.match(/^javascript:\s*mctmp/)) href = '';
return href;
},
/**
* Creates a bookmark for the currently selected range,
* which can be used to reselect this range at a later point.
* @return {mixed}
*/
createBookmark: function() {
return this.getInstance().selection.getBookmark();
},
/**
* Selects a bookmarked range previously saved through createBookmark().
* @param {mixed} bookmark
*/
moveToBookmark: function(bookmark) {
this.getInstance().selection.moveToBookmark(bookmark);
this.getInstance().focus();
},
/**
* Removes any selection & de-focuses this editor
*/
blur: function() {
this.getInstance().selection.collapse();
},
/**
* Add new undo point with the current DOM content.
*/
addUndo: function() {
this.getInstance().undoManager.add();
}
};
});
// Override this to switch editor wrappers
ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
(function($) {
$.entwine('ss', function($) {
/**
* Class: textarea.htmleditor
*
* Add tinymce to HtmlEditorFields within the CMS. Works in combination
* with a TinyMCE.init() call which is prepopulated with the used HTMLEditorConfig settings,
* and included in the page as an inline