mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
456 lines
12 KiB
JavaScript
456 lines
12 KiB
JavaScript
|
/**
|
|||
|
* $Id: EditorManager.js 1045 2009-03-04 20:03:18Z spocke $
|
|||
|
*
|
|||
|
* @author Moxiecode
|
|||
|
* @copyright Copyright <EFBFBD> 2004-2008, Moxiecode Systems AB, All rights reserved.
|
|||
|
*/
|
|||
|
|
|||
|
(function(tinymce) {
|
|||
|
// Shorten names
|
|||
|
var each = tinymce.each, extend = tinymce.extend, DOM = tinymce.DOM, Event = tinymce.dom.Event, ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, explode = tinymce.explode;
|
|||
|
|
|||
|
/**#@+
|
|||
|
* @class This class is used to create multiple editor instances and contain them in a collection. So it's both a factory and a manager for editor instances.
|
|||
|
* @static
|
|||
|
* @member tinymce.EditorManager
|
|||
|
*/
|
|||
|
tinymce.create('static tinymce.EditorManager', {
|
|||
|
editors : {},
|
|||
|
i18n : {},
|
|||
|
activeEditor : null,
|
|||
|
|
|||
|
/**#@+
|
|||
|
* @method
|
|||
|
*/
|
|||
|
|
|||
|
/**
|
|||
|
* Preinitializes the EditorManager class. This method will be called automatically when the page loads and it
|
|||
|
* will setup some important paths and URIs and attach some document events.
|
|||
|
*/
|
|||
|
preInit : function() {
|
|||
|
var t = this, lo = window.location;
|
|||
|
|
|||
|
// Setup some URLs where the editor API is located and where the document is
|
|||
|
tinymce.documentBaseURL = lo.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
|
|||
|
if (!/[\/\\]$/.test(tinymce.documentBaseURL))
|
|||
|
tinymce.documentBaseURL += '/';
|
|||
|
|
|||
|
tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
|
|||
|
tinymce.EditorManager.baseURI = new tinymce.util.URI(tinymce.baseURL);
|
|||
|
|
|||
|
// User specified a document.domain value
|
|||
|
if (document.domain && lo.hostname != document.domain)
|
|||
|
tinymce.relaxedDomain = document.domain;
|
|||
|
|
|||
|
// Add before unload listener
|
|||
|
// This was required since IE was leaking memory if you added and removed beforeunload listeners
|
|||
|
// with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
|
|||
|
t.onBeforeUnload = new tinymce.util.Dispatcher(t);
|
|||
|
|
|||
|
// Must be on window or IE will leak if the editor is placed in frame or iframe
|
|||
|
Event.add(window, 'beforeunload', function(e) {
|
|||
|
t.onBeforeUnload.dispatch(t, e);
|
|||
|
});
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Initializes a set of editors. This method will create a bunch of editors based in the input.
|
|||
|
*
|
|||
|
* @param {Object} s Settings object to be passed to each editor instance.
|
|||
|
*/
|
|||
|
init : function(s) {
|
|||
|
var t = this, pl, sl = tinymce.ScriptLoader, c, e, el = [], ed;
|
|||
|
|
|||
|
function execCallback(se, n, s) {
|
|||
|
var f = se[n];
|
|||
|
|
|||
|
if (!f)
|
|||
|
return;
|
|||
|
|
|||
|
if (tinymce.is(f, 'string')) {
|
|||
|
s = f.replace(/\.\w+$/, '');
|
|||
|
s = s ? tinymce.resolve(s) : 0;
|
|||
|
f = tinymce.resolve(f);
|
|||
|
}
|
|||
|
|
|||
|
return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
|
|||
|
};
|
|||
|
|
|||
|
s = extend({
|
|||
|
theme : "simple",
|
|||
|
language : "en",
|
|||
|
strict_loading_mode : document.contentType == 'application/xhtml+xml'
|
|||
|
}, s);
|
|||
|
|
|||
|
t.settings = s;
|
|||
|
|
|||
|
// If page not loaded and strict mode isn't enabled then load them
|
|||
|
if (!Event.domLoaded && !s.strict_loading_mode) {
|
|||
|
// Load language
|
|||
|
if (s.language)
|
|||
|
sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
|
|||
|
|
|||
|
// Load theme
|
|||
|
if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
|
|||
|
ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
|
|||
|
|
|||
|
// Load plugins
|
|||
|
if (s.plugins) {
|
|||
|
pl = explode(s.plugins);
|
|||
|
|
|||
|
// Load compat2x first
|
|||
|
if (tinymce.inArray(pl, 'compat2x') != -1)
|
|||
|
PluginManager.load('compat2x', 'plugins/compat2x/editor_plugin' + tinymce.suffix + '.js');
|
|||
|
|
|||
|
// Load rest if plugins
|
|||
|
each(pl, function(v) {
|
|||
|
if (v && v.charAt(0) != '-' && !PluginManager.urls[v]) {
|
|||
|
// Skip safari plugin for other browsers
|
|||
|
if (!tinymce.isWebKit && v == 'safari')
|
|||
|
return;
|
|||
|
|
|||
|
PluginManager.load(v, 'plugins/' + v + '/editor_plugin' + tinymce.suffix + '.js');
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
sl.loadQueue();
|
|||
|
}
|
|||
|
|
|||
|
// Legacy call
|
|||
|
Event.add(document, 'init', function() {
|
|||
|
var l, co;
|
|||
|
|
|||
|
execCallback(s, 'onpageload');
|
|||
|
|
|||
|
// Verify that it's a valid browser
|
|||
|
if (s.browsers) {
|
|||
|
l = false;
|
|||
|
|
|||
|
each(explode(s.browsers), function(v) {
|
|||
|
switch (v) {
|
|||
|
case 'ie':
|
|||
|
case 'msie':
|
|||
|
if (tinymce.isIE)
|
|||
|
l = true;
|
|||
|
break;
|
|||
|
|
|||
|
case 'gecko':
|
|||
|
if (tinymce.isGecko)
|
|||
|
l = true;
|
|||
|
break;
|
|||
|
|
|||
|
case 'safari':
|
|||
|
case 'webkit':
|
|||
|
if (tinymce.isWebKit)
|
|||
|
l = true;
|
|||
|
break;
|
|||
|
|
|||
|
case 'opera':
|
|||
|
if (tinymce.isOpera)
|
|||
|
l = true;
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
// Not a valid one
|
|||
|
if (!l)
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
switch (s.mode) {
|
|||
|
case "exact":
|
|||
|
l = s.elements || '';
|
|||
|
|
|||
|
if(l.length > 0) {
|
|||
|
each(explode(l), function(v) {
|
|||
|
if (DOM.get(v)) {
|
|||
|
ed = new tinymce.Editor(v, s);
|
|||
|
el.push(ed);
|
|||
|
ed.render(1);
|
|||
|
} else {
|
|||
|
c = 0;
|
|||
|
|
|||
|
each(document.forms, function(f) {
|
|||
|
each(f.elements, function(e) {
|
|||
|
if (e.name === v) {
|
|||
|
v = 'mce_editor_' + c;
|
|||
|
DOM.setAttrib(e, 'id', v);
|
|||
|
|
|||
|
ed = new tinymce.Editor(v, s);
|
|||
|
el.push(ed);
|
|||
|
ed.render(1);
|
|||
|
}
|
|||
|
});
|
|||
|
});
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case "textareas":
|
|||
|
case "specific_textareas":
|
|||
|
function hasClass(n, c) {
|
|||
|
return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
|
|||
|
};
|
|||
|
|
|||
|
each(DOM.select('textarea'), function(v) {
|
|||
|
if (s.editor_deselector && hasClass(v, s.editor_deselector))
|
|||
|
return;
|
|||
|
|
|||
|
if (!s.editor_selector || hasClass(v, s.editor_selector)) {
|
|||
|
// Can we use the name
|
|||
|
e = DOM.get(v.name);
|
|||
|
if (!v.id && !e)
|
|||
|
v.id = v.name;
|
|||
|
|
|||
|
// Generate unique name if missing or already exists
|
|||
|
if (!v.id || t.get(v.id))
|
|||
|
v.id = DOM.uniqueId();
|
|||
|
|
|||
|
ed = new tinymce.Editor(v.id, s);
|
|||
|
el.push(ed);
|
|||
|
ed.render(1);
|
|||
|
}
|
|||
|
});
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
// Call onInit when all editors are initialized
|
|||
|
if (s.oninit) {
|
|||
|
l = co = 0;
|
|||
|
|
|||
|
each (el, function(ed) {
|
|||
|
co++;
|
|||
|
|
|||
|
if (!ed.initialized) {
|
|||
|
// Wait for it
|
|||
|
ed.onInit.add(function() {
|
|||
|
l++;
|
|||
|
|
|||
|
// All done
|
|||
|
if (l == co)
|
|||
|
execCallback(s, 'oninit');
|
|||
|
});
|
|||
|
} else
|
|||
|
l++;
|
|||
|
|
|||
|
// All done
|
|||
|
if (l == co)
|
|||
|
execCallback(s, 'oninit');
|
|||
|
});
|
|||
|
}
|
|||
|
});
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Returns a editor instance by id.
|
|||
|
*
|
|||
|
* @param {String} id Editor instance id to return.
|
|||
|
* @return {tinymce.Editor} Editor instance to return.
|
|||
|
*/
|
|||
|
get : function(id) {
|
|||
|
return this.editors[id];
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Returns a editor instance by id. This method was added for compatibility with the 2.x branch.
|
|||
|
*
|
|||
|
* @param {String} id Editor instance id to return.
|
|||
|
* @return {tinymce.Editor} Editor instance to return.
|
|||
|
*/
|
|||
|
getInstanceById : function(id) {
|
|||
|
return this.get(id);
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Adds an editor instance to the editor collection. This will also set it as the active editor.
|
|||
|
*
|
|||
|
* @param {tinymce.Editor} e Editor instance to add to the collection.
|
|||
|
* @return {tinymce.Editor} The same instance that got passed in.
|
|||
|
*/
|
|||
|
add : function(e) {
|
|||
|
this.editors[e.id] = e;
|
|||
|
this._setActive(e);
|
|||
|
|
|||
|
return e;
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Removes a editor instance from the collection.
|
|||
|
*
|
|||
|
* @param {tinymce.Editor} e Editor instance to remove.
|
|||
|
* @return {tinymce.Editor} The editor that got passed in will be return if it was found otherwise null.
|
|||
|
*/
|
|||
|
remove : function(e) {
|
|||
|
var t = this;
|
|||
|
|
|||
|
// Not in the collection
|
|||
|
if (!t.editors[e.id])
|
|||
|
return null;
|
|||
|
|
|||
|
delete t.editors[e.id];
|
|||
|
|
|||
|
// Select another editor since the active one was removed
|
|||
|
if (t.activeEditor == e) {
|
|||
|
each(t.editors, function(e) {
|
|||
|
t._setActive(e);
|
|||
|
return false; // Break
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
e.destroy();
|
|||
|
|
|||
|
return e;
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Executes a specific command on the currently active editor.
|
|||
|
*
|
|||
|
* @param {String} c Command to perform for example Bold.
|
|||
|
* @param {bool} u Optional boolean state if a UI should be presented for the command or not.
|
|||
|
* @param {String} v Optional value parameter like for example an URL to a link.
|
|||
|
* @return {bool} true/false if the command was executed or not.
|
|||
|
*/
|
|||
|
execCommand : function(c, u, v) {
|
|||
|
var t = this, ed = t.get(v), w;
|
|||
|
|
|||
|
// Manager commands
|
|||
|
switch (c) {
|
|||
|
case "mceFocus":
|
|||
|
ed.focus();
|
|||
|
return true;
|
|||
|
|
|||
|
case "mceAddEditor":
|
|||
|
case "mceAddControl":
|
|||
|
if (!t.get(v))
|
|||
|
new tinymce.Editor(v, t.settings).render();
|
|||
|
|
|||
|
return true;
|
|||
|
|
|||
|
case "mceAddFrameControl":
|
|||
|
w = v.window;
|
|||
|
|
|||
|
// Add tinyMCE global instance and tinymce namespace to specified window
|
|||
|
w.tinyMCE = tinyMCE;
|
|||
|
w.tinymce = tinymce;
|
|||
|
|
|||
|
tinymce.DOM.doc = w.document;
|
|||
|
tinymce.DOM.win = w;
|
|||
|
|
|||
|
ed = new tinymce.Editor(v.element_id, v);
|
|||
|
ed.render();
|
|||
|
|
|||
|
// Fix IE memory leaks
|
|||
|
if (tinymce.isIE) {
|
|||
|
function clr() {
|
|||
|
ed.destroy();
|
|||
|
w.detachEvent('onunload', clr);
|
|||
|
w = w.tinyMCE = w.tinymce = null; // IE leak
|
|||
|
};
|
|||
|
|
|||
|
w.attachEvent('onunload', clr);
|
|||
|
}
|
|||
|
|
|||
|
v.page_window = null;
|
|||
|
|
|||
|
return true;
|
|||
|
|
|||
|
case "mceRemoveEditor":
|
|||
|
case "mceRemoveControl":
|
|||
|
if (ed)
|
|||
|
ed.remove();
|
|||
|
|
|||
|
return true;
|
|||
|
|
|||
|
case 'mceToggleEditor':
|
|||
|
if (!ed) {
|
|||
|
t.execCommand('mceAddControl', 0, v);
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
if (ed.isHidden())
|
|||
|
ed.show();
|
|||
|
else
|
|||
|
ed.hide();
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
// Run command on active editor
|
|||
|
if (t.activeEditor)
|
|||
|
return t.activeEditor.execCommand(c, u, v);
|
|||
|
|
|||
|
return false;
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Executes a command on a specific editor by id. This method was added for compatibility with the 2.x branch.
|
|||
|
*
|
|||
|
* @param {String} id Editor id to perform the command on.
|
|||
|
* @param {String} c Command to perform for example Bold.
|
|||
|
* @param {bool} u Optional boolean state if a UI should be presented for the command or not.
|
|||
|
* @param {String} v Optional value parameter like for example an URL to a link.
|
|||
|
* @return {bool} true/false if the command was executed or not.
|
|||
|
*/
|
|||
|
execInstanceCommand : function(id, c, u, v) {
|
|||
|
var ed = this.get(id);
|
|||
|
|
|||
|
if (ed)
|
|||
|
return ed.execCommand(c, u, v);
|
|||
|
|
|||
|
return false;
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Calls the save method on all editor instances in the collection. This can be useful when a form is to be submitted.
|
|||
|
*/
|
|||
|
triggerSave : function() {
|
|||
|
each(this.editors, function(e) {
|
|||
|
e.save();
|
|||
|
});
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Adds a language pack, this gets called by the loaded language files like en.js.
|
|||
|
*
|
|||
|
* @param {String} p Prefix for the language items. For example en.myplugin
|
|||
|
* @param {Object} o Name/Value collection with items to add to the language group.
|
|||
|
*/
|
|||
|
addI18n : function(p, o) {
|
|||
|
var lo, i18n = this.i18n;
|
|||
|
|
|||
|
if (!tinymce.is(p, 'string')) {
|
|||
|
each(p, function(o, lc) {
|
|||
|
each(o, function(o, g) {
|
|||
|
each(o, function(o, k) {
|
|||
|
if (g === 'common')
|
|||
|
i18n[lc + '.' + k] = o;
|
|||
|
else
|
|||
|
i18n[lc + '.' + g + '.' + k] = o;
|
|||
|
});
|
|||
|
});
|
|||
|
});
|
|||
|
} else {
|
|||
|
each(o, function(o, k) {
|
|||
|
i18n[p + '.' + k] = o;
|
|||
|
});
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
// Private methods
|
|||
|
|
|||
|
_setActive : function(e) {
|
|||
|
this.selectedInstance = this.activeEditor = e;
|
|||
|
}
|
|||
|
|
|||
|
/**#@-*/
|
|||
|
});
|
|||
|
|
|||
|
tinymce.EditorManager.preInit();
|
|||
|
})(tinymce);
|
|||
|
|
|||
|
// Short for editor manager window.tinyMCE is needed when TinyMCE gets loaded though a XHR call
|
|||
|
var tinyMCE = window.tinyMCE = tinymce.EditorManager;
|