MDL-14739 uploading fresh new unmodified tinymce - it is in separate directory because we need to work around browser caching issues after upgrade; this also simplifies tracking of our additions and tweaks

This commit is contained in:
Petr Skoda
2010-07-04 13:53:17 +00:00
parent 55825877f1
commit ee00613bd3
303 changed files with 86534 additions and 0 deletions
@@ -0,0 +1,109 @@
/**
* AddOnManager.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
/**
* This class handles the loading of themes/plugins or other add-ons and their language packs.
*
* @class tinymce.AddOnManager
*/
tinymce.create('tinymce.AddOnManager', {
items : [],
urls : {},
lookup : {},
/**
* Fires when a item is added.
*
* @event onAdd
*/
onAdd : new Dispatcher(this),
/**
* Returns the specified add on by the short name.
*
* @method get
* @param {String} n Add-on to look for.
* @return {tinymce.Theme/tinymce.Plugin} Theme or plugin add-on instance or undefined.
*/
get : function(n) {
return this.lookup[n];
},
/**
* Loads a language pack for the specified add-on.
*
* @method requireLangPack
* @param {String} n Short name of the add-on.
*/
requireLangPack : function(n) {
var s = tinymce.settings;
if (s && s.language)
tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
},
/**
* Adds a instance of the add-on by it's short name.
*
* @method add
* @param {String} id Short name/id for the add-on.
* @param {tinymce.Theme/tinymce.Plugin} o Theme or plugin to add.
* @return {tinymce.Theme/tinymce.Plugin} The same theme or plugin instance that got passed in.
*/
add : function(id, o) {
this.items.push(o);
this.lookup[id] = o;
this.onAdd.dispatch(this, id, o);
return o;
},
/**
* Loads an add-on from a specific url.
*
* @method load
* @param {String} n Short name of the add-on that gets loaded.
* @param {String} u URL to the add-on that will get loaded.
* @param {function} cb Optional callback to execute ones the add-on is loaded.
* @param {Object} s Optional scope to execute the callback in.
*/
load : function(n, u, cb, s) {
var t = this;
if (t.urls[n])
return;
if (u.indexOf('/') != 0 && u.indexOf('://') == -1)
u = tinymce.baseURL + '/' + u;
t.urls[n] = u.substring(0, u.lastIndexOf('/'));
tinymce.ScriptLoader.add(u, cb, s);
}
});
// Create plugin and theme managers
tinymce.PluginManager = new tinymce.AddOnManager();
tinymce.ThemeManager = new tinymce.AddOnManager();
}(tinymce));
/**
* TinyMCE theme class.
*
* @class tinymce.Theme
*/
/**
* TinyMCE plugin class.
*
* @class tinymce.Plugin
*/
@@ -0,0 +1,57 @@
/**
* TinyMCE - ContentManager class.
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
function CommandManager() {
var execCommands = {}, queryStateCommands = {}, queryValueCommands = {};
function add(collection, cmd, func, scope) {
if (typeof(cmd) == 'string')
cmd = [cmd];
tinymce.each(cmd, function(cmd) {
collection[cmd.toLowerCase()] = {func : func, scope : scope};
});
};
tinymce.extend(this, {
add : function(cmd, func, scope) {
add(execCommands, cmd, func, scope);
},
addQueryStateHandler : function(cmd, func, scope) {
add(queryStateCommands, cmd, func, scope);
},
addQueryValueHandler : function(cmd, func, scope) {
add(queryValueCommands, cmd, func, scope);
},
execCommand : function(scope, cmd, ui, value, args) {
if (cmd = execCommands[cmd.toLowerCase()]) {
if (cmd.func.call(scope || cmd.scope, ui, value, args) !== false)
return true;
}
},
queryCommandValue : function() {
if (cmd = queryValueCommands[cmd.toLowerCase()])
return cmd.func.call(scope || cmd.scope, ui, value, args);
},
queryCommandState : function() {
if (cmd = queryStateCommands[cmd.toLowerCase()])
return cmd.func.call(scope || cmd.scope, ui, value, args);
}
});
};
tinymce.GlobalCommands = new CommandManager();
})(tinymce);
@@ -0,0 +1,507 @@
/**
* ControlManager.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
// Shorten names
var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
/**
* This class is responsible for managing UI control instances. It's both a factory and a collection for the controls.
* @class tinymce.ControlManager
*/
tinymce.create('tinymce.ControlManager', {
/**
* Constructs a new control manager instance.
* Consult the Wiki for more details on this class.
*
* @constructor
* @method ControlManager
* @param {tinymce.Editor} ed TinyMCE editor instance to add the control to.
* @param {Object} s Optional settings object for the control manager.
*/
ControlManager : function(ed, s) {
var t = this, i;
s = s || {};
t.editor = ed;
t.controls = {};
t.onAdd = new tinymce.util.Dispatcher(t);
t.onPostRender = new tinymce.util.Dispatcher(t);
t.prefix = s.prefix || ed.id + '_';
t._cls = {};
t.onPostRender.add(function() {
each(t.controls, function(c) {
c.postRender();
});
});
},
/**
* Returns a control by id or undefined it it wasn't found.
*
* @method get
* @param {String} id Control instance name.
* @return {tinymce.ui.Control} Control instance or undefined.
*/
get : function(id) {
return this.controls[this.prefix + id] || this.controls[id];
},
/**
* Sets the active state of a control by id.
*
* @method setActive
* @param {String} id Control id to set state on.
* @param {Boolean} s Active state true/false.
* @return {tinymce.ui.Control} Control instance that got activated or null if it wasn't found.
*/
setActive : function(id, s) {
var c = null;
if (c = this.get(id))
c.setActive(s);
return c;
},
/**
* Sets the dsiabled state of a control by id.
*
* @method setDisabled
* @param {String} id Control id to set state on.
* @param {Boolean} s Active state true/false.
* @return {tinymce.ui.Control} Control instance that got disabled or null if it wasn't found.
*/
setDisabled : function(id, s) {
var c = null;
if (c = this.get(id))
c.setDisabled(s);
return c;
},
/**
* Adds a control to the control collection inside the manager.
*
* @method add
* @param {tinymce.ui.Control} Control instance to add to collection.
* @return {tinymce.ui.Control} Control instance that got passed in.
*/
add : function(c) {
var t = this;
if (c) {
t.controls[c.id] = c;
t.onAdd.dispatch(c, t);
}
return c;
},
/**
* Creates a control by name, when a control is created it will automatically add it to the control collection.
* It first ask all plugins for the specified control if the plugins didn't return a control then the default behavior
* will be used.
*
* @method createControl
* @param {String} n Control name to create for example "separator".
* @return {tinymce.ui.Control} Control instance that got created and added.
*/
createControl : function(n) {
var c, t = this, ed = t.editor;
each(ed.plugins, function(p) {
if (p.createControl) {
c = p.createControl(n, t);
if (c)
return false;
}
});
switch (n) {
case "|":
case "separator":
return t.createSeparator();
}
if (!c && ed.buttons && (c = ed.buttons[n]))
return t.createButton(n, c);
return t.add(c);
},
/**
* Creates a drop menu control instance by id.
*
* @method createDropMenu
* @param {String} id Unique id for the new dropdown instance. For example "some menu".
* @param {Object} s Optional settings object for the control.
* @param {Object} cc Optional control class to use instead of the default one.
* @return {tinymce.ui.Control} Control instance that got created and added.
*/
createDropMenu : function(id, s, cc) {
var t = this, ed = t.editor, c, bm, v, cls;
s = extend({
'class' : 'mceDropDown',
constrain : ed.settings.constrain_menus
}, s);
s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
if (v = ed.getParam('skin_variant'))
s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
id = t.prefix + id;
cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
c = t.controls[id] = new cls(id, s);
c.onAddItem.add(function(c, o) {
var s = o.settings;
s.title = ed.getLang(s.title, s.title);
if (!s.onclick) {
s.onclick = function(v) {
if (s.cmd)
ed.execCommand(s.cmd, s.ui || false, s.value);
};
}
});
ed.onRemove.add(function() {
c.destroy();
});
// Fix for bug #1897785, #1898007
if (tinymce.isIE) {
c.onShowMenu.add(function() {
// IE 8 needs focus in order to store away a range with the current collapsed caret location
ed.focus();
bm = ed.selection.getBookmark(1);
});
c.onHideMenu.add(function() {
if (bm) {
ed.selection.moveToBookmark(bm);
bm = 0;
}
});
}
return t.add(c);
},
/**
* Creates a list box control instance by id. A list box is either a native select element or a DOM/JS based list box control. This
* depends on the use_native_selects settings state.
*
* @method createListBox
* @param {String} id Unique id for the new listbox instance. For example "styles".
* @param {Object} s Optional settings object for the control.
* @param {Object} cc Optional control class to use instead of the default one.
* @return {tinymce.ui.Control} Control instance that got created and added.
*/
createListBox : function(id, s, cc) {
var t = this, ed = t.editor, cmd, c, cls;
if (t.get(id))
return null;
s.title = ed.translate(s.title);
s.scope = s.scope || ed;
if (!s.onselect) {
s.onselect = function(v) {
ed.execCommand(s.cmd, s.ui || false, v || s.value);
};
}
s = extend({
title : s.title,
'class' : 'mce_' + id,
scope : s.scope,
control_manager : t
}, s);
id = t.prefix + id;
if (ed.settings.use_native_selects)
c = new tinymce.ui.NativeListBox(id, s);
else {
cls = cc || t._cls.listbox || tinymce.ui.ListBox;
c = new cls(id, s);
}
t.controls[id] = c;
// Fix focus problem in Safari
if (tinymce.isWebKit) {
c.onPostRender.add(function(c, n) {
// Store bookmark on mousedown
Event.add(n, 'mousedown', function() {
ed.bookmark = ed.selection.getBookmark(1);
});
// Restore on focus, since it might be lost
Event.add(n, 'focus', function() {
ed.selection.moveToBookmark(ed.bookmark);
ed.bookmark = null;
});
});
}
if (c.hideMenu)
ed.onMouseDown.add(c.hideMenu, c);
return t.add(c);
},
/**
* Creates a button control instance by id.
*
* @method createButton
* @param {String} id Unique id for the new button instance. For example "bold".
* @param {Object} s Optional settings object for the control.
* @param {Object} cc Optional control class to use instead of the default one.
* @return {tinymce.ui.Control} Control instance that got created and added.
*/
createButton : function(id, s, cc) {
var t = this, ed = t.editor, o, c, cls;
if (t.get(id))
return null;
s.title = ed.translate(s.title);
s.label = ed.translate(s.label);
s.scope = s.scope || ed;
if (!s.onclick && !s.menu_button) {
s.onclick = function() {
ed.execCommand(s.cmd, s.ui || false, s.value);
};
}
s = extend({
title : s.title,
'class' : 'mce_' + id,
unavailable_prefix : ed.getLang('unavailable', ''),
scope : s.scope,
control_manager : t
}, s);
id = t.prefix + id;
if (s.menu_button) {
cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
c = new cls(id, s);
ed.onMouseDown.add(c.hideMenu, c);
} else {
cls = t._cls.button || tinymce.ui.Button;
c = new cls(id, s);
}
return t.add(c);
},
/**
* Creates a menu button control instance by id.
*
* @method createMenuButton
* @param {String} id Unique id for the new menu button instance. For example "menu1".
* @param {Object} s Optional settings object for the control.
* @param {Object} cc Optional control class to use instead of the default one.
* @return {tinymce.ui.Control} Control instance that got created and added.
*/
createMenuButton : function(id, s, cc) {
s = s || {};
s.menu_button = 1;
return this.createButton(id, s, cc);
},
/**
* Creates a split button control instance by id.
*
* @method createSplitButton
* @param {String} id Unique id for the new split button instance. For example "spellchecker".
* @param {Object} s Optional settings object for the control.
* @param {Object} cc Optional control class to use instead of the default one.
* @return {tinymce.ui.Control} Control instance that got created and added.
*/
createSplitButton : function(id, s, cc) {
var t = this, ed = t.editor, cmd, c, cls;
if (t.get(id))
return null;
s.title = ed.translate(s.title);
s.scope = s.scope || ed;
if (!s.onclick) {
s.onclick = function(v) {
ed.execCommand(s.cmd, s.ui || false, v || s.value);
};
}
if (!s.onselect) {
s.onselect = function(v) {
ed.execCommand(s.cmd, s.ui || false, v || s.value);
};
}
s = extend({
title : s.title,
'class' : 'mce_' + id,
scope : s.scope,
control_manager : t
}, s);
id = t.prefix + id;
cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
c = t.add(new cls(id, s));
ed.onMouseDown.add(c.hideMenu, c);
return c;
},
/**
* Creates a color split button control instance by id.
*
* @method createColorSplitButton
* @param {String} id Unique id for the new color split button instance. For example "forecolor".
* @param {Object} s Optional settings object for the control.
* @param {Object} cc Optional control class to use instead of the default one.
* @return {tinymce.ui.Control} Control instance that got created and added.
*/
createColorSplitButton : function(id, s, cc) {
var t = this, ed = t.editor, cmd, c, cls, bm;
if (t.get(id))
return null;
s.title = ed.translate(s.title);
s.scope = s.scope || ed;
if (!s.onclick) {
s.onclick = function(v) {
if (tinymce.isIE)
bm = ed.selection.getBookmark(1);
ed.execCommand(s.cmd, s.ui || false, v || s.value);
};
}
if (!s.onselect) {
s.onselect = function(v) {
ed.execCommand(s.cmd, s.ui || false, v || s.value);
};
}
s = extend({
title : s.title,
'class' : 'mce_' + id,
'menu_class' : ed.getParam('skin') + 'Skin',
scope : s.scope,
more_colors_title : ed.getLang('more_colors')
}, s);
id = t.prefix + id;
cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
c = new cls(id, s);
ed.onMouseDown.add(c.hideMenu, c);
// Remove the menu element when the editor is removed
ed.onRemove.add(function() {
c.destroy();
});
// Fix for bug #1897785, #1898007
if (tinymce.isIE) {
c.onShowMenu.add(function() {
// IE 8 needs focus in order to store away a range with the current collapsed caret location
ed.focus();
bm = ed.selection.getBookmark(1);
});
c.onHideMenu.add(function() {
if (bm) {
ed.selection.moveToBookmark(bm);
bm = 0;
}
});
}
return t.add(c);
},
/**
* Creates a toolbar container control instance by id.
*
* @method createToolbar
* @param {String} id Unique id for the new toolbar container control instance. For example "toolbar1".
* @param {Object} s Optional settings object for the control.
* @param {Object} cc Optional control class to use instead of the default one.
* @return {tinymce.ui.Control} Control instance that got created and added.
*/
createToolbar : function(id, s, cc) {
var c, t = this, cls;
id = t.prefix + id;
cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
c = new cls(id, s);
if (t.get(id))
return null;
return t.add(c);
},
/**
* Creates a separator control instance.
*
* @method createSeparator
* @param {Object} cc Optional control class to use instead of the default one.
* @return {tinymce.ui.Control} Control instance that got created and added.
*/
createSeparator : function(cc) {
var cls = cc || this._cls.separator || tinymce.ui.Separator;
return new cls();
},
/**
* Overrides a specific control type with a custom class.
*
* @method setControlType
* @param {string} n Name of the control to override for example button or dropmenu.
* @param {function} c Class reference to use instead of the default one.
* @return {function} Same as the class reference.
*/
setControlType : function(n, c) {
return this._cls[n.toLowerCase()] = c;
},
/**
* Destroy.
*
* @method destroy
*/
destroy : function() {
each(this.controls, function(c) {
c.destroy();
});
this.controls = null;
}
});
})(tinymce);
@@ -0,0 +1,94 @@
/**
* Developer.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function() {
var EditorManager = tinymce.EditorManager, each = tinymce.each, DOM = tinymce.DOM;
/**
* This class patches in various development features. This class is only available for the dev version of TinyMCE.
*/
tinymce.create('static tinymce.Developer', {
_piggyBack : function() {
var t = this, em = tinymce.EditorManager, lo = false;
// Makes sure that XML language pack is used instead of JS files
t._runBefore(em, 'init', function(s) {
var par = new tinymce.xml.Parser({async : false}), lng = s.language || "en", i18n = tinymce.EditorManager.i18n, sl = tinymce.ScriptLoader;
if (!s.translate_mode)
return;
if (lo)
return;
lo = true;
// Common language loaded
sl.markDone(tinymce.baseURL + '/langs/' + lng + '.js');
// Theme languages loaded
sl.markDone(tinymce.baseURL + '/themes/simple/langs/' + lng + '.js');
sl.markDone(tinymce.baseURL + '/themes/advanced/langs/' + lng + '.js');
// All plugin packs loaded
each(s.plugins.split(','), function(p) {
sl.markDone(tinymce.baseURL + '/plugins/' + p + '/langs/' + lng + '.js');
});
// Load XML language pack
par.load(tinymce.baseURL + '/langs/' + lng + '.xml', function(doc, ex) {
var c;
if (!doc) {
alert(ex.message);
return;
}
if (doc.documentElement.nodeName == 'parsererror') {
alert('Parse error!!');
return;
}
c = doc.getElementsByTagName('language')[0].getAttribute("code");
each(doc.getElementsByTagName('group'), function(g) {
var gn = g.getAttribute("target"), o = {};
// Build object from XML items
each(g.getElementsByTagName('item'), function(it) {
var itn = it.getAttribute("name");
if (gn == "common")
i18n[c + '.' + itn] = par.getText(it);
else
i18n[c + '.' + gn + "." + itn] = par.getText(it);
});
});
}, {
async : false
});
});
},
_runBefore : function(o, n, f) {
var e = o[n];
o[n] = function() {
var s = f.apply(o, arguments);
if (s !== false)
return e.apply(o, arguments);
};
}
});
tinymce.Developer._piggyBack();
})();
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,446 @@
/**
* EditorCommands.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
// Added for compression purposes
var each = tinymce.each, undefined, TRUE = true, FALSE = false;
/**
* This class enables you to add custom editor commands and it contains
* overrides for native browser commands to address various bugs and issues.
*
* @class tinymce.EditorCommands
*/
tinymce.EditorCommands = function(editor) {
var dom = editor.dom,
selection = editor.selection,
commands = {state: {}, exec : {}, value : {}},
settings = editor.settings,
bookmark;
/**
* Executes the specified command.
*
* @method execCommand
* @param {String} command Command to execute.
* @param {Boolean} ui Optional user interface state.
* @param {Object} value Optional value for command.
* @return {Boolean} true/false if the command was found or not.
*/
function execCommand(command, ui, value) {
var func;
command = command.toLowerCase();
if (func = commands.exec[command]) {
func(command, ui, value);
return TRUE;
}
return FALSE;
};
/**
* Queries the current state for a command for example if the current selection is "bold".
*
* @method queryCommandState
* @param {String} command Command to check the state of.
* @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found.
*/
function queryCommandState(command) {
var func;
command = command.toLowerCase();
if (func = commands.state[command])
return func(command);
return -1;
};
/**
* Queries the command value for example the current fontsize.
*
* @method queryCommandValue
* @param {String} command Command to check the value of.
* @return {Object} Command value of false if it's not found.
*/
function queryCommandValue(command) {
var func;
command = command.toLowerCase();
if (func = commands.value[command])
return func(command);
return FALSE;
};
/**
* Adds commands to the command collection.
*
* @method addCommands
* @param {Object} command_list Name/value collection with commands to add, the names can also be comma separated.
* @param {String} type Optional type to add, defaults to exec. Can be value or state as well.
*/
function addCommands(command_list, type) {
type = type || 'exec';
each(command_list, function(callback, command) {
each(command.toLowerCase().split(','), function(command) {
commands[type][command] = callback;
});
});
};
// Expose public methods
tinymce.extend(this, {
execCommand : execCommand,
queryCommandState : queryCommandState,
queryCommandValue : queryCommandValue,
addCommands : addCommands
});
// Private methods
function execNativeCommand(command, ui, value) {
if (ui === undefined)
ui = FALSE;
if (value === undefined)
value = null;
return editor.getDoc().execCommand(command, ui, value);
};
function isFormatMatch(name) {
return editor.formatter.match(name);
};
function toggleFormat(name, value) {
editor.formatter.toggle(name, value ? {value : value} : undefined);
};
function storeSelection(type) {
bookmark = selection.getBookmark(type);
};
function restoreSelection() {
selection.moveToBookmark(bookmark);
};
// Add execCommand overrides
addCommands({
// Ignore these, added for compatibility
'mceResetDesignMode,mceBeginUndoLevel' : function() {},
// Add undo manager logic
'mceEndUndoLevel,mceAddUndoLevel' : function() {
editor.undoManager.add();
},
'Cut,Copy,Paste' : function(command) {
var doc = editor.getDoc(), failed;
// Try executing the native command
try {
execNativeCommand(command);
} catch (ex) {
// Command failed
failed = TRUE;
}
// Present alert message about clipboard access not being available
if (failed || !doc.queryCommandSupported(command)) {
if (tinymce.isGecko) {
editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
if (state)
open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
});
} else
editor.windowManager.alert(editor.getLang('clipboard_no_support'));
}
},
// Override unlink command
unlink : function(command) {
if (selection.isCollapsed())
selection.select(selection.getNode());
execNativeCommand(command);
selection.collapse(FALSE);
},
// Override justify commands to use the text formatter engine
'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
var align = command.substring(7);
// Remove all other alignments first
each('left,center,right,full'.split(','), function(name) {
if (align != name)
editor.formatter.remove('align' + name);
});
toggleFormat('align' + align);
},
// Override list commands to fix WebKit bug
'InsertUnorderedList,InsertOrderedList' : function(command) {
var listElm, listParent;
execNativeCommand(command);
// WebKit produces lists within block elements so we need to split them
// we will replace the native list creation logic to custom logic later on
// TODO: Remove this when the list creation logic is removed
listElm = dom.getParent(selection.getNode(), 'ol,ul');
if (listElm) {
listParent = listElm.parentNode;
// If list is within a text block then split that block
if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
storeSelection();
dom.split(listParent, listElm);
restoreSelection();
}
}
},
// Override commands to use the text formatter engine
'Bold,Italic,Underline,Strikethrough' : function(command) {
toggleFormat(command);
},
// Override commands to use the text formatter engine
'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
toggleFormat(command, value);
},
FontSize : function(command, ui, value) {
var fontClasses, fontSizes;
// Convert font size 1-7 to styles
if (value >= 1 && value <= 7) {
fontSizes = tinymce.explode(settings.font_size_style_values);
fontClasses = tinymce.explode(settings.font_size_classes);
if (fontClasses)
value = fontClasses[value - 1] || value;
else
value = fontSizes[value - 1] || value;
}
toggleFormat(command, value);
},
RemoveFormat : function(command) {
editor.formatter.remove(command);
},
mceBlockQuote : function(command) {
toggleFormat('blockquote');
},
FormatBlock : function(command, ui, value) {
return toggleFormat(value || 'p');
},
mceCleanup : function() {
var bookmark = selection.getBookmark();
editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
selection.moveToBookmark(bookmark);
},
mceRemoveNode : function(command, ui, value) {
var node = value || selection.getNode();
// Make sure that the body node isn't removed
if (node != editor.getBody()) {
storeSelection();
editor.dom.remove(node, TRUE);
restoreSelection();
}
},
mceSelectNodeDepth : function(command, ui, value) {
var counter = 0;
dom.getParent(selection.getNode(), function(node) {
if (node.nodeType == 1 && counter++ == value) {
selection.select(node);
return FALSE;
}
}, editor.getBody());
},
mceSelectNode : function(command, ui, value) {
selection.select(value);
},
mceInsertContent : function(command, ui, value) {
selection.setContent(value);
},
mceInsertRawHTML : function(command, ui, value) {
selection.setContent('tiny_mce_marker');
editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, value));
},
mceSetContent : function(command, ui, value) {
editor.setContent(value);
},
'Indent,Outdent' : function(command) {
var intentValue, indentUnit, value;
// Setup indent level
intentValue = settings.indentation;
indentUnit = /[a-z%]+$/i.exec(intentValue);
intentValue = parseInt(intentValue);
if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
each(selection.getSelectedBlocks(), function(element) {
if (command == 'outdent') {
value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
} else
dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
});
} else
execNativeCommand(command);
},
mceRepaint : function() {
var bookmark;
if (tinymce.isGecko) {
try {
storeSelection(TRUE);
if (selection.getSel())
selection.getSel().selectAllChildren(editor.getBody());
selection.collapse(TRUE);
restoreSelection();
} catch (ex) {
// Ignore
}
}
},
mceToggleFormat : function(command, ui, value) {
editor.formatter.toggle(value);
},
InsertHorizontalRule : function() {
selection.setContent('<hr />');
},
mceToggleVisualAid : function() {
editor.hasVisual = !editor.hasVisual;
editor.addVisual();
},
mceReplaceContent : function(command, ui, value) {
selection.setContent(value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
},
mceInsertLink : function(command, ui, value) {
var link = dom.getParent(selection.getNode(), 'a');
if (tinymce.is(value, 'string'))
value = {href : value};
if (!link) {
execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
each(dom.select('a[href=javascript:mctmp(0);]'), function(link) {
dom.setAttribs(link, value);
});
} else {
if (value.href)
dom.setAttribs(link, value);
else
editor.dom.remove(link, TRUE);
}
},
selectAll : function() {
var root = dom.getRoot(), rng = dom.createRng();
rng.setStart(root, 0);
rng.setEnd(root, root.childNodes.length);
editor.selection.setRng(rng);
}
});
// Add queryCommandState overrides
addCommands({
// Override justify commands
'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
return isFormatMatch('align' + command.substring(7));
},
'Bold,Italic,Underline,Strikethrough' : function(command) {
return isFormatMatch(command);
},
mceBlockQuote : function() {
return isFormatMatch('blockquote');
},
Outdent : function() {
var node;
if (settings.inline_styles) {
if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
return TRUE;
if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
return TRUE;
}
return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
},
'InsertUnorderedList,InsertOrderedList' : function(command) {
return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
}
}, 'state');
// Add queryCommandValue overrides
addCommands({
'FontSize,FontName' : function(command) {
var value = 0, parent;
if (parent = dom.getParent(selection.getNode(), 'span')) {
if (command == 'fontsize')
value = parent.style.fontSize;
else
value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
}
return value;
}
}, 'value');
// Add undo manager logic
if (settings.custom_undo_redo) {
addCommands({
Undo : function() {
editor.undoManager.undo();
},
Redo : function() {
editor.undoManager.redo();
}
});
}
};
})(tinymce);
@@ -0,0 +1,453 @@
/**
* EditorManager.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
/**
* @class 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,
Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
// Setup some URLs where the editor API is located and where the document is
tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
if (!/[\/\\]$/.test(tinymce.documentBaseURL))
tinymce.documentBaseURL += '/';
tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
/**
* Absolute baseURI for the installation path of TinyMCE.
*
* @property baseURI
* @type tinymce.util.URI
*/
tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
// 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
tinymce.onBeforeUnload = new Dispatcher(tinymce);
// Must be on window or IE will leak if the editor is placed in frame or iframe
Event.add(window, 'beforeunload', function(e) {
tinymce.onBeforeUnload.dispatch(tinymce, e);
});
/**
* Fires when a new editor instance is added to the tinymce collection.
*
* @event onAddEditor
* @param {tinymce} sender TinyMCE root class/namespace.
* @param {tinymce.Editor} editor Editor instance.
*/
tinymce.onAddEditor = new Dispatcher(tinymce);
/**
* Fires when an editor instance is removed from the tinymce collection.
*
* @event onRemoveEditor
* @param {tinymce} sender TinyMCE root class/namespace.
* @param {tinymce.Editor} editor Editor instance.
*/
tinymce.onRemoveEditor = new Dispatcher(tinymce);
tinymce.EditorManager = extend(tinymce, {
/**
* Collection of editor instances.
*
* @property editors
* @type Object
*/
editors : [],
/**
* Collection of language pack data.
*
* @property i18n
* @type Object
*/
i18n : {},
/**
* Currently active editor instance.
*
* @property activeEditor
* @type tinymce.Editor
*/
activeEditor : null,
/**
* Initializes a set of editors. This method will create a bunch of editors based in the input.
*
* @method init
* @param {Object} s Settings object to be passed to each editor instance.
*/
init : function(s) {
var t = this, pl, sl = tinymce.ScriptLoader, 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"
}, s);
t.settings = s;
// Legacy call
Event.add(document, 'init', function() {
var l, co;
execCallback(s, 'onpageload');
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 {
each(document.forms, function(f) {
each(f.elements, function(e) {
if (e.name === v) {
v = 'mce_editor_' + instanceCounter++;
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.
*
* @method get
* @param {String/Number} id Editor instance id or index to return.
* @return {tinymce.Editor} Editor instance to return.
*/
get : function(id) {
if (id === undefined)
return this.editors;
return this.editors[id];
},
/**
* Returns a editor instance by id. This method was added for compatibility with the 2.x branch.
*
* @method getInstanceById
* @param {String} id Editor instance id to return.
* @return {tinymce.Editor} Editor instance to return.
* @deprecated Use get method instead.
* @see #get
*/
getInstanceById : function(id) {
return this.get(id);
},
/**
* Adds an editor instance to the editor collection. This will also set it as the active editor.
*
* @method add
* @param {tinymce.Editor} editor Editor instance to add to the collection.
* @return {tinymce.Editor} The same instance that got passed in.
*/
add : function(editor) {
var self = this, editors = self.editors;
// Add named and index editor instance
editors[editor.id] = editor;
editors.push(editor);
self._setActive(editor);
self.onAddEditor.dispatch(self, editor);
// #ifdef jquery
// Patch the tinymce.Editor instance with jQuery adapter logic
if (tinymce.adapter)
tinymce.adapter.patchEditor(editor);
// #endif
return editor;
},
/**
* Removes a editor instance from the collection.
*
* @method remove
* @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(editor) {
var t = this, i, editors = t.editors;
// Not in the collection
if (!editors[editor.id])
return null;
delete editors[editor.id];
for (i = 0; i < editors.length; i++) {
if (editors[i] == editor) {
editors.splice(i, 1);
break;
}
}
// Select another editor since the active one was removed
if (t.activeEditor == editor)
t._setActive(editors[0]);
editor.destroy();
t.onRemoveEditor.dispatch(t, editor);
return editor;
},
/**
* Executes a specific command on the currently active editor.
*
* @method execCommand
* @param {String} c Command to perform for example Bold.
* @param {Boolean} 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 {Boolean} 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.
*
* @deprecated Use the execCommand method of a editor instance instead.
* @method execInstanceCommand
* @param {String} id Editor id to perform the command on.
* @param {String} c Command to perform for example Bold.
* @param {Boolean} 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 {Boolean} 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.
*
* @method triggerSave
*/
triggerSave : function() {
each(this.editors, function(e) {
e.save();
});
},
/**
* Adds a language pack, this gets called by the loaded language files like en.js.
*
* @method addI18n
* @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(editor) {
this.selectedInstance = this.activeEditor = editor;
}
});
})(tinymce);
/**
* Alternative name for tinymce added for 2.x compatibility.
*
* @member
* @property tinyMCE
* @type tinymce
*/
@@ -0,0 +1,751 @@
/**
* ForceBlocks.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
// Shorten names
var Event = tinymce.dom.Event,
isIE = tinymce.isIE,
isGecko = tinymce.isGecko,
isOpera = tinymce.isOpera,
each = tinymce.each,
extend = tinymce.extend,
TRUE = true,
FALSE = false;
function cloneFormats(node) {
var clone, temp, inner;
do {
if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
if (clone) {
temp = node.cloneNode(false);
temp.appendChild(clone);
clone = temp;
} else {
clone = inner = node.cloneNode(false);
}
clone.removeAttribute('id');
}
} while (node = node.parentNode);
if (clone)
return {wrapper : clone, inner : inner};
};
// Checks if the selection/caret is at the end of the specified block element
function isAtEnd(rng, par) {
var rng2 = par.ownerDocument.createRange();
rng2.setStart(rng.endContainer, rng.endOffset);
rng2.setEndAfter(par);
// Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element
return rng2.cloneContents().textContent.length == 0;
};
function isEmpty(n) {
n = n.innerHTML;
n = n.replace(/<(img|hr|table|input|select|textarea)[ \>]/gi, '-'); // Keep these convert them to - chars
n = n.replace(/<[^>]+>/g, ''); // Remove all tags
return n.replace(/[ \u00a0\t\r\n]+/g, '') == '';
};
function splitList(selection, dom, li) {
var listBlock, block;
if (isEmpty(li)) {
listBlock = dom.getParent(li, 'ul,ol');
if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
dom.split(listBlock, li);
block = dom.create('p', 0, '<br _mce_bogus="1" />');
dom.replace(block, li);
selection.select(block, 1);
}
return FALSE;
}
return TRUE;
};
/**
* This is a internal class and no method in this class should be called directly form the out side.
*/
tinymce.create('tinymce.ForceBlocks', {
ForceBlocks : function(ed) {
var t = this, s = ed.settings, elm;
t.editor = ed;
t.dom = ed.dom;
elm = (s.forced_root_block || 'p').toLowerCase();
s.element = elm.toUpperCase();
ed.onPreInit.add(t.setup, t);
t.reOpera = new RegExp('(\\u00a0|&#160;|&nbsp;)<\/' + elm + '>', 'gi');
t.rePadd = new RegExp('<p( )([^>]+)><\\\/p>|<p( )([^>]+)\\\/>|<p( )([^>]+)>\\s+<\\\/p>|<p><\\\/p>|<p\\\/>|<p>\\s+<\\\/p>'.replace(/p/g, elm), 'gi');
t.reNbsp2BR1 = new RegExp('<p( )([^>]+)>[\\s\\u00a0]+<\\\/p>|<p>[\\s\\u00a0]+<\\\/p>'.replace(/p/g, elm), 'gi');
t.reNbsp2BR2 = new RegExp('<%p()([^>]+)>(&nbsp;|&#160;)<\\\/%p>|<%p>(&nbsp;|&#160;)<\\\/%p>'.replace(/%p/g, elm), 'gi');
t.reBR2Nbsp = new RegExp('<p( )([^>]+)>\\s*<br \\\/>\\s*<\\\/p>|<p>\\s*<br \\\/>\\s*<\\\/p>'.replace(/p/g, elm), 'gi');
function padd(ed, o) {
if (isOpera)
o.content = o.content.replace(t.reOpera, '</' + elm + '>');
o.content = o.content.replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0</' + elm + '>');
if (!isIE && !isOpera && o.set) {
// Use &nbsp; instead of BR in padded paragraphs
o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2><br /></' + elm + '>');
o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2><br /></' + elm + '>');
} else
o.content = o.content.replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0</' + elm + '>');
};
ed.onBeforeSetContent.add(padd);
ed.onPostProcess.add(padd);
if (s.forced_root_block) {
ed.onInit.add(t.forceRoots, t);
ed.onSetContent.add(t.forceRoots, t);
ed.onBeforeGetContent.add(t.forceRoots, t);
}
},
setup : function() {
var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection;
// Force root blocks when typing and when getting output
if (s.forced_root_block) {
ed.onBeforeExecCommand.add(t.forceRoots, t);
ed.onKeyUp.add(t.forceRoots, t);
ed.onPreProcess.add(t.forceRoots, t);
}
if (s.force_br_newlines) {
// Force IE to produce BRs on enter
if (isIE) {
ed.onKeyPress.add(function(ed, e) {
var n;
if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
selection.setContent('<br id="__" /> ', {format : 'raw'});
n = dom.get('__');
n.removeAttribute('id');
selection.select(n);
selection.collapse();
return Event.cancel(e);
}
});
}
}
if (s.force_p_newlines) {
if (!isIE) {
ed.onKeyPress.add(function(ed, e) {
if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
Event.cancel(e);
});
} else {
// Ungly hack to for IE to preserve the formatting when you press
// enter at the end of a block element with formatted contents
// This logic overrides the browsers default logic with
// custom logic that enables us to control the output
tinymce.addUnload(function() {
t._previousFormats = 0; // Fix IE leak
});
ed.onKeyPress.add(function(ed, e) {
t._previousFormats = 0;
// Clone the current formats, this will later be applied to the new block contents
if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)
t._previousFormats = cloneFormats(ed.selection.getStart());
});
ed.onKeyUp.add(function(ed, e) {
// Let IE break the element and the wrap the new caret location in the previous formats
if (e.keyCode == 13 && !e.shiftKey) {
var parent = ed.selection.getStart(), fmt = t._previousFormats;
// Parent is an empty block
if (!parent.hasChildNodes()) {
parent = dom.getParent(parent, dom.isBlock);
if (parent) {
parent.innerHTML = '';
if (t._previousFormats) {
parent.appendChild(fmt.wrapper);
fmt.inner.innerHTML = '\uFEFF';
} else
parent.innerHTML = '\uFEFF';
selection.select(parent, 1);
ed.getDoc().execCommand('Delete', false, null);
}
}
}
});
}
if (isGecko) {
ed.onKeyDown.add(function(ed, e) {
if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
t.backspaceDelete(e, e.keyCode == 8);
});
}
}
// Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
if (tinymce.isWebKit) {
function insertBr(ed) {
var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
// Insert BR element
rng.insertNode(br = dom.create('br'));
// Place caret after BR
rng.setStartAfter(br);
rng.setEndAfter(br);
selection.setRng(rng);
// Could not place caret after BR then insert an nbsp entity and move the caret
if (selection.getSel().focusNode == br.previousSibling) {
selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
selection.collapse(TRUE);
}
// Create a temporary DIV after the BR and get the position as it
// seems like getPos() returns 0 for text nodes and BR elements.
dom.insertAfter(div, br);
divYPos = dom.getPos(div).y;
dom.remove(div);
// Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
ed.getWin().scrollTo(0, divYPos);
};
ed.onKeyPress.add(function(ed, e) {
if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
insertBr(ed);
Event.cancel(e);
}
});
}
// Padd empty inline elements within block elements
// For example: <p><strong><em></em></strong></p> becomes <p><strong><em>&nbsp;</em></strong></p>
ed.onPreProcess.add(function(ed, o) {
each(dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) {
if (isEmpty(p)) {
each(dom.select('span,em,strong,b,i', o.node), function(n) {
if (!n.hasChildNodes()) {
n.appendChild(ed.getDoc().createTextNode('\u00a0'));
return FALSE; // Break the loop one padding is enough
}
});
}
});
});
// IE specific fixes
if (isIE) {
// Replaces IE:s auto generated paragraphs with the specified element name
if (s.element != 'P') {
ed.onKeyPress.add(function(ed, e) {
t.lastElm = selection.getNode().nodeName;
});
ed.onKeyUp.add(function(ed, e) {
var bl, n = selection.getNode(), b = ed.getBody();
if (b.childNodes.length === 1 && n.nodeName == 'P') {
n = dom.rename(n, s.element);
selection.select(n);
selection.collapse();
ed.nodeChanged();
} else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
bl = dom.getParent(n, 'p');
if (bl) {
dom.rename(bl, s.element);
ed.nodeChanged();
}
}
});
}
}
},
find : function(n, t, s) {
var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1;
while (n = w.nextNode()) {
c++;
// Index by node
if (t == 0 && n == s)
return c;
// Node by index
if (t == 1 && c == s)
return n;
}
return -1;
},
forceRoots : function(ed, e) {
var t = this, ed = t.editor, b = ed.getBody(), d = ed.getDoc(), se = ed.selection, s = se.getSel(), r = se.getRng(), si = -2, ei, so, eo, tr, c = -0xFFFFFF;
var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid;
// Fix for bug #1863847
//if (e && e.keyCode == 13)
// return TRUE;
// Wrap non blocks into blocks
for (i = nl.length - 1; i >= 0; i--) {
nx = nl[i];
// Ignore internal elements
if (nx.nodeType === 1 && nx.getAttribute('_mce_type')) {
bl = null;
continue;
}
// Is text or non block element
if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) {
if (!bl) {
// Create new block but ignore whitespace
if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) {
// Store selection
if (si == -2 && r) {
if (!isIE) {
// If selection is element then mark it
if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) {
// Save the id of the selected element
eid = n.getAttribute("id");
n.setAttribute("id", "__mce");
} else {
// If element is inside body, might not be the case in contentEdiable mode
if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) {
so = r.startOffset;
eo = r.endOffset;
si = t.find(b, 0, r.startContainer);
ei = t.find(b, 0, r.endContainer);
}
}
} else {
// Force control range into text range
if (r.item) {
tr = d.body.createTextRange();
tr.moveToElementText(r.item(0));
r = tr;
}
tr = d.body.createTextRange();
tr.moveToElementText(b);
tr.collapse(1);
bp = tr.move('character', c) * -1;
tr = r.duplicate();
tr.collapse(1);
sp = tr.move('character', c) * -1;
tr = r.duplicate();
tr.collapse(0);
le = (tr.move('character', c) * -1) - sp;
si = sp - bp;
ei = le;
}
}
// Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE
// See: http://support.microsoft.com/kb/829907
bl = ed.dom.create(ed.settings.forced_root_block);
nx.parentNode.replaceChild(bl, nx);
bl.appendChild(nx);
}
} else {
if (bl.hasChildNodes())
bl.insertBefore(nx, bl.firstChild);
else
bl.appendChild(nx);
}
} else
bl = null; // Time to create new block
}
// Restore selection
if (si != -2) {
if (!isIE) {
bl = b.getElementsByTagName(ed.settings.element)[0];
r = d.createRange();
// Select last location or generated block
if (si != -1)
r.setStart(t.find(b, 1, si), so);
else
r.setStart(bl, 0);
// Select last location or generated block
if (ei != -1)
r.setEnd(t.find(b, 1, ei), eo);
else
r.setEnd(bl, 0);
if (s) {
s.removeAllRanges();
s.addRange(r);
}
} else {
try {
r = s.createRange();
r.moveToElementText(b);
r.collapse(1);
r.moveStart('character', si);
r.moveEnd('character', ei);
r.select();
} catch (ex) {
// Ignore
}
}
} else if (!isIE && (n = ed.dom.get('__mce'))) {
// Restore the id of the selected element
if (eid)
n.setAttribute('id', eid);
else
n.removeAttribute('id');
// Move caret before selected element
r = d.createRange();
r.setStartBefore(n);
r.setEndBefore(n);
se.setRng(r);
}
},
getParentBlock : function(n) {
var d = this.dom;
return d.getParent(n, d.isBlock);
},
insertPara : function(e) {
var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body;
var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car;
// If root blocks are forced then use Operas default behavior since it's really good
// Removed due to bug: #1853816
// if (se.forced_root_block && isOpera)
// return TRUE;
// Setup before range
rb = d.createRange();
// If is before the first block element and in body, then move it into first block element
rb.setStart(s.anchorNode, s.anchorOffset);
rb.collapse(TRUE);
// Setup after range
ra = d.createRange();
// If is before the first block element and in body, then move it into first block element
ra.setStart(s.focusNode, s.focusOffset);
ra.collapse(TRUE);
// Setup start/end points
dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
sn = dir ? s.anchorNode : s.focusNode;
so = dir ? s.anchorOffset : s.focusOffset;
en = dir ? s.focusNode : s.anchorNode;
eo = dir ? s.focusOffset : s.anchorOffset;
// If selection is in empty table cell
if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
if (sn.firstChild.nodeName == 'BR')
dom.remove(sn.firstChild); // Remove BR
// Create two new block elements
if (sn.childNodes.length == 0) {
ed.dom.add(sn, se.element, null, '<br />');
aft = ed.dom.add(sn, se.element, null, '<br />');
} else {
n = sn.innerHTML;
sn.innerHTML = '';
ed.dom.add(sn, se.element, null, n);
aft = ed.dom.add(sn, se.element, null, '<br />');
}
// Move caret into the last one
r = d.createRange();
r.selectNodeContents(aft);
r.collapse(1);
ed.selection.setRng(r);
return FALSE;
}
// If the caret is in an invalid location in FF we need to move it into the first block
if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
sn = en = sn.firstChild;
so = eo = 0;
rb = d.createRange();
rb.setStart(sn, 0);
ra = d.createRange();
ra.setStart(en, 0);
}
// Never use body as start or end node
sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
en = en.nodeName == "BODY" ? en.firstChild : en;
// Get start and end blocks
sb = t.getParentBlock(sn);
eb = t.getParentBlock(en);
bn = sb ? sb.nodeName : se.element; // Get block name to create
// Return inside list use default browser behavior
if (n = t.dom.getParent(sb, 'li,pre')) {
if (n.nodeName == 'LI')
return splitList(ed.selection, t.dom, n);
return TRUE;
}
// If caption or absolute layers then always generate new blocks within
if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
bn = se.element;
sb = null;
}
// If caption or absolute layers then always generate new blocks within
if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
bn = se.element;
eb = null;
}
// Use P instead
if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
bn = se.element;
sb = eb = null;
}
// Setup new before and after blocks
bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
// Remove id from after clone
aft.removeAttribute('id');
// Is header and cursor is at the end, then force paragraph under
if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
aft = ed.dom.create(se.element);
// Find start chop node
n = sc = sn;
do {
if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
break;
sc = n;
} while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
// Find end chop node
n = ec = en;
do {
if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
break;
ec = n;
} while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
// Place first chop part into before block element
if (sc.nodeName == bn)
rb.setStart(sc, 0);
else
rb.setStartBefore(sc);
rb.setEnd(sn, so);
bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
// Place secnd chop part within new block element
try {
ra.setEndAfter(ec);
} catch(ex) {
//console.debug(s.focusNode, s.focusOffset);
}
ra.setStart(en, eo);
aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
// Create range around everything
r = d.createRange();
if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
r.setStartBefore(sc.parentNode);
} else {
if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
r.setStartBefore(rb.startContainer);
else
r.setStart(rb.startContainer, rb.startOffset);
}
if (!ec.nextSibling && ec.parentNode.nodeName == bn)
r.setEndAfter(ec.parentNode);
else
r.setEnd(ra.endContainer, ra.endOffset);
// Delete and replace it with new block elements
r.deleteContents();
if (isOpera)
ed.getWin().scrollTo(0, vp.y);
// Never wrap blocks in blocks
if (bef.firstChild && bef.firstChild.nodeName == bn)
bef.innerHTML = bef.firstChild.innerHTML;
if (aft.firstChild && aft.firstChild.nodeName == bn)
aft.innerHTML = aft.firstChild.innerHTML;
// Padd empty blocks
if (isEmpty(bef))
bef.innerHTML = '<br />';
function appendStyles(e, en) {
var nl = [], nn, n, i;
e.innerHTML = '';
// Make clones of style elements
if (se.keep_styles) {
n = en;
do {
// We only want style specific elements
if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
nn = n.cloneNode(FALSE);
dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
nl.push(nn);
}
} while (n = n.parentNode);
}
// Append style elements to aft
if (nl.length > 0) {
for (i = nl.length - 1, nn = e; i >= 0; i--)
nn = nn.appendChild(nl[i]);
// Padd most inner style element
nl[0].innerHTML = isOpera ? '&nbsp;' : '<br />'; // Extra space for Opera so that the caret can move there
return nl[0]; // Move caret to most inner element
} else
e.innerHTML = isOpera ? '&nbsp;' : '<br />'; // Extra space for Opera so that the caret can move there
};
// Fill empty afterblook with current style
if (isEmpty(aft))
car = appendStyles(aft, en);
// Opera needs this one backwards for older versions
if (isOpera && parseFloat(opera.version()) < 9.5) {
r.insertNode(bef);
r.insertNode(aft);
} else {
r.insertNode(aft);
r.insertNode(bef);
}
// Normalize
aft.normalize();
bef.normalize();
function first(n) {
return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n;
};
// Move cursor and scroll into view
r = d.createRange();
r.selectNodeContents(isGecko ? first(car || aft) : car || aft);
r.collapse(1);
s.removeAllRanges();
s.addRange(r);
// scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs
y = ed.dom.getPos(aft).y;
ch = aft.clientHeight;
// Is element within viewport
if (y < vp.y || y + ch > vp.y + vp.h) {
ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
//console.debug('SCROLL!', 'vp.y: ' + vp.y, 'y' + y, 'vp.h' + vp.h, 'clientHeight' + aft.clientHeight, 'yyy: ' + (y < vp.y ? y : y - vp.h + aft.clientHeight));
}
return FALSE;
},
backspaceDelete : function(e, bs) {
var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn, walker;
// Delete when caret is behind a element doesn't work correctly on Gecko see #3011651
if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {
walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);
// Walk the dom backwards until we find a text node
for (n = sc.lastChild; n; n = walker.prev()) {
if (n.nodeType == 3) {
r.setStart(n, n.nodeValue.length);
r.collapse(true);
se.setRng(r);
return;
}
}
}
// The caret sometimes gets stuck in Gecko if you delete empty paragraphs
// This workaround removes the element by hand and moves the caret to the previous element
if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
// Find previous block element
n = sc;
while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
if (n) {
if (sc != b.firstChild) {
// Find last text node
w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
while (tn = w.nextNode())
n = tn;
// Place caret at the end of last text node
r = ed.getDoc().createRange();
r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
se.setRng(r);
// Remove the target container
ed.dom.remove(sc);
}
return Event.cancel(e);
}
}
}
}
});
})(tinymce);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,62 @@
/**
* LegacyInput.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
tinymce.onAddEditor.add(function(tinymce, ed) {
var filters, fontSizes, dom, settings = ed.settings;
if (settings.inline_styles) {
fontSizes = tinymce.explode(settings.font_size_style_values);
function replaceWithSpan(node, styles) {
dom.replace(dom.create('span', {
style : styles
}), node, 1);
};
filters = {
font : function(dom, node) {
replaceWithSpan(node, {
backgroundColor : node.style.backgroundColor,
color : node.color,
fontFamily : node.face,
fontSize : fontSizes[parseInt(node.size) - 1]
});
},
u : function(dom, node) {
replaceWithSpan(node, {
textDecoration : 'underline'
});
},
strike : function(dom, node) {
replaceWithSpan(node, {
textDecoration : 'line-through'
});
}
};
function convert(editor, params) {
dom = editor.dom;
if (settings.convert_fonts_to_spans) {
tinymce.each(dom.select('font,u,strike', params.node), function(node) {
filters[node.nodeName.toLowerCase()](ed.dom, node);
});
}
};
ed.onPreProcess.add(convert);
ed.onInit.add(function() {
ed.selection.onSetContent.add(convert);
});
}
});
@@ -0,0 +1,438 @@
/**
* Popup.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
// Some global instances
var tinymce = null, tinyMCEPopup, tinyMCE;
/**
* TinyMCE popup/dialog helper class. This gives you easy access to the
* parent editor instance and a bunch of other things. It's higly recommended
* that you load this script into your dialogs.
*
* @static
* @class tinyMCEPopup
*/
tinyMCEPopup = {
/**
* Initializes the popup this will be called automatically.
*
* @method init
*/
init : function() {
var t = this, w, ti;
// Find window & API
w = t.getWin();
tinymce = w.tinymce;
tinyMCE = w.tinyMCE;
t.editor = tinymce.EditorManager.activeEditor;
t.params = t.editor.windowManager.params;
t.features = t.editor.windowManager.features;
// Setup local DOM
t.dom = t.editor.windowManager.createInstance('tinymce.dom.DOMUtils', document);
// Enables you to skip loading the default css
if (t.features.popup_css !== false)
t.dom.loadCSS(t.features.popup_css || t.editor.settings.popup_css);
// Setup on init listeners
t.listeners = [];
t.onInit = {
add : function(f, s) {
t.listeners.push({func : f, scope : s});
}
};
t.isWindow = !t.getWindowArg('mce_inline');
t.id = t.getWindowArg('mce_window_id');
t.editor.windowManager.onOpen.dispatch(t.editor.windowManager, window);
},
/**
* Returns the reference to the parent window that opened the dialog.
*
* @method getWin
* @return {Window} Reference to the parent window that opened the dialog.
*/
getWin : function() {
// Added frameElement check to fix bug: #2817583
return (!window.frameElement && window.dialogArguments) || opener || parent || top;
},
/**
* Returns a window argument/parameter by name.
*
* @method getWindowArg
* @param {String} n Name of the window argument to retrive.
* @param {String} dv Optional default value to return.
* @return {String} Argument value or default value if it wasn't found.
*/
getWindowArg : function(n, dv) {
var v = this.params[n];
return tinymce.is(v) ? v : dv;
},
/**
* Returns a editor parameter/config option value.
*
* @method getParam
* @param {String} n Name of the editor config option to retrive.
* @param {String} dv Optional default value to return.
* @return {String} Parameter value or default value if it wasn't found.
*/
getParam : function(n, dv) {
return this.editor.getParam(n, dv);
},
/**
* Returns a language item by key.
*
* @method getLang
* @param {String} n Language item like mydialog.something.
* @param {String} dv Optional default value to return.
* @return {String} Language value for the item like "my string" or the default value if it wasn't found.
*/
getLang : function(n, dv) {
return this.editor.getLang(n, dv);
},
/**
* Executed a command on editor that opened the dialog/popup.
*
* @method execCommand
* @param {String} cmd Command to execute.
* @param {Boolean} ui Optional boolean value if the UI for the command should be presented or not.
* @param {Object} val Optional value to pass with the comman like an URL.
* @param {Object} a Optional arguments object.
*/
execCommand : function(cmd, ui, val, a) {
a = a || {};
a.skip_focus = 1;
this.restoreSelection();
return this.editor.execCommand(cmd, ui, val, a);
},
/**
* Resizes the dialog to the inner size of the window. This is needed since various browsers
* have different border sizes on windows.
*
* @method resizeToInnerSize
*/
resizeToInnerSize : function() {
var t = this;
// Detach it to workaround a Chrome specific bug
// https://sourceforge.net/tracker/?func=detail&atid=635682&aid=2926339&group_id=103281
setTimeout(function() {
var vp = t.dom.getViewPort(window);
t.editor.windowManager.resizeBy(
t.getWindowArg('mce_width') - vp.w,
t.getWindowArg('mce_height') - vp.h,
t.id || window
);
}, 0);
},
/**
* Will executed the specified string when the page has been loaded. This function
* was added for compatibility with the 2.x branch.
*
* @method executeOnLoad
* @param {String} s String to evalutate on init.
*/
executeOnLoad : function(s) {
this.onInit.add(function() {
eval(s);
});
},
/**
* Stores the current editor selection for later restoration. This can be useful since some browsers
* looses it's selection if a control element is selected/focused inside the dialogs.
*
* @method storeSelection
*/
storeSelection : function() {
this.editor.windowManager.bookmark = tinyMCEPopup.editor.selection.getBookmark(1);
},
/**
* Restores any stored selection. This can be useful since some browsers
* looses it's selection if a control element is selected/focused inside the dialogs.
*
* @method restoreSelection
*/
restoreSelection : function() {
var t = tinyMCEPopup;
if (!t.isWindow && tinymce.isIE)
t.editor.selection.moveToBookmark(t.editor.windowManager.bookmark);
},
/**
* Loads a specific dialog language pack. If you pass in plugin_url as a arugment
* when you open the window it will load the <plugin url>/langs/<code>_dlg.js lang pack file.
*
* @method requireLangPack
*/
requireLangPack : function() {
var t = this, u = t.getWindowArg('plugin_url') || t.getWindowArg('theme_url');
if (u && t.editor.settings.language && t.features.translate_i18n !== false) {
u += '/langs/' + t.editor.settings.language + '_dlg.js';
if (!tinymce.ScriptLoader.isDone(u)) {
document.write('<script type="text/javascript" src="' + tinymce._addVer(u) + '"></script>');
tinymce.ScriptLoader.markDone(u);
}
}
},
/**
* Executes a color picker on the specified element id. When the user
* then selects a color it will be set as the value of the specified element.
*
* @method pickColor
* @param {DOMEvent} e DOM event object.
* @param {string} element_id Element id to be filled with the color value from the picker.
*/
pickColor : function(e, element_id) {
this.execCommand('mceColorPicker', true, {
color : document.getElementById(element_id).value,
func : function(c) {
document.getElementById(element_id).value = c;
try {
document.getElementById(element_id).onchange();
} catch (ex) {
// Try fire event, ignore errors
}
}
});
},
/**
* Opens a filebrowser/imagebrowser this will set the output value from
* the browser as a value on the specified element.
*
* @method openBrowser
* @param {string} element_id Id of the element to set value in.
* @param {string} type Type of browser to open image/file/flash.
* @param {string} option Option name to get the file_broswer_callback function name from.
*/
openBrowser : function(element_id, type, option) {
tinyMCEPopup.restoreSelection();
this.editor.execCallback('file_browser_callback', element_id, document.getElementById(element_id).value, type, window);
},
/**
* Creates a confirm dialog. Please don't use the blocking behavior of this
* native version use the callback method instead then it can be extended.
*
* @method confirm
* @param {String} t Title for the new confirm dialog.
* @param {function} cb Callback function to be executed after the user has selected ok or cancel.
* @param {Object} s Optional scope to execute the callback in.
*/
confirm : function(t, cb, s) {
this.editor.windowManager.confirm(t, cb, s, window);
},
/**
* Creates a alert dialog. Please don't use the blocking behavior of this
* native version use the callback method instead then it can be extended.
*
* @method alert
* @param {String} t Title for the new alert dialog.
* @param {function} cb Callback function to be executed after the user has selected ok.
* @param {Object} s Optional scope to execute the callback in.
*/
alert : function(tx, cb, s) {
this.editor.windowManager.alert(tx, cb, s, window);
},
/**
* Closes the current window.
*
* @method close
*/
close : function() {
var t = this;
// To avoid domain relaxing issue in Opera
function close() {
t.editor.windowManager.close(window);
tinymce = tinyMCE = t.editor = t.params = t.dom = t.dom.doc = null; // Cleanup
};
if (tinymce.isOpera)
t.getWin().setTimeout(close, 0);
else
close();
},
// Internal functions
_restoreSelection : function() {
var e = window.event.srcElement;
if (e.nodeName == 'INPUT' && (e.type == 'submit' || e.type == 'button'))
tinyMCEPopup.restoreSelection();
},
/* _restoreSelection : function() {
var e = window.event.srcElement;
// If user focus a non text input or textarea
if ((e.nodeName != 'INPUT' && e.nodeName != 'TEXTAREA') || e.type != 'text')
tinyMCEPopup.restoreSelection();
},*/
_onDOMLoaded : function() {
var t = tinyMCEPopup, ti = document.title, bm, h, nv;
if (t.domLoaded)
return;
t.domLoaded = 1;
// Translate page
if (t.features.translate_i18n !== false) {
h = document.body.innerHTML;
// Replace a=x with a="x" in IE
if (tinymce.isIE)
h = h.replace(/ (value|title|alt)=([^"][^\s>]+)/gi, ' $1="$2"')
document.dir = t.editor.getParam('directionality','');
if ((nv = t.editor.translate(h)) && nv != h)
document.body.innerHTML = nv;
if ((nv = t.editor.translate(ti)) && nv != ti)
document.title = ti = nv;
}
document.body.style.display = '';
// Restore selection in IE when focus is placed on a non textarea or input element of the type text
if (tinymce.isIE) {
document.attachEvent('onmouseup', tinyMCEPopup._restoreSelection);
// Add base target element for it since it would fail with modal dialogs
t.dom.add(t.dom.select('head')[0], 'base', {target : '_self'});
}
t.restoreSelection();
t.resizeToInnerSize();
// Set inline title
if (!t.isWindow)
t.editor.windowManager.setTitle(window, ti);
else
window.focus();
if (!tinymce.isIE && !t.isWindow) {
tinymce.dom.Event._add(document, 'focus', function() {
t.editor.windowManager.focus(t.id);
});
}
// Patch for accessibility
tinymce.each(t.dom.select('select'), function(e) {
e.onkeydown = tinyMCEPopup._accessHandler;
});
// Call onInit
// Init must be called before focus so the selection won't get lost by the focus call
tinymce.each(t.listeners, function(o) {
o.func.call(o.scope, t.editor);
});
// Move focus to window
if (t.getWindowArg('mce_auto_focus', true)) {
window.focus();
// Focus element with mceFocus class
tinymce.each(document.forms, function(f) {
tinymce.each(f.elements, function(e) {
if (t.dom.hasClass(e, 'mceFocus') && !e.disabled) {
e.focus();
return false; // Break loop
}
});
});
}
document.onkeyup = tinyMCEPopup._closeWinKeyHandler;
},
_accessHandler : function(e) {
e = e || window.event;
if (e.keyCode == 13 || e.keyCode == 32) {
e = e.target || e.srcElement;
if (e.onchange)
e.onchange();
return tinymce.dom.Event.cancel(e);
}
},
_closeWinKeyHandler : function(e) {
e = e || window.event;
if (e.keyCode == 27)
tinyMCEPopup.close();
},
_wait : function() {
// Use IE method
if (document.attachEvent) {
document.attachEvent("onreadystatechange", function() {
if (document.readyState === "complete") {
document.detachEvent("onreadystatechange", arguments.callee);
tinyMCEPopup._onDOMLoaded();
}
});
if (document.documentElement.doScroll && window == window.top) {
(function() {
if (tinyMCEPopup.domLoaded)
return;
try {
// If IE is used, use the trick by Diego Perini
// http://javascript.nwbox.com/IEContentLoaded/
document.documentElement.doScroll("left");
} catch (ex) {
setTimeout(arguments.callee, 0);
return;
}
tinyMCEPopup._onDOMLoaded();
})();
}
document.attachEvent('onload', tinyMCEPopup._onDOMLoaded);
} else if (document.addEventListener) {
window.addEventListener('DOMContentLoaded', tinyMCEPopup._onDOMLoaded, false);
window.addEventListener('load', tinyMCEPopup._onDOMLoaded, false);
}
}
};
tinyMCEPopup.init();
tinyMCEPopup._wait(); // Wait for DOM Content Loaded
@@ -0,0 +1,163 @@
/**
* UndoManager.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
var Dispatcher = tinymce.util.Dispatcher;
/**
* This class handles the undo/redo history levels for the editor. Since the build in undo/redo has major drawbacks a custom one was needed.
*
* @class tinymce.UndoManager
*/
tinymce.UndoManager = function(editor) {
var self, index = 0, data = [];
function getContent() {
return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));
};
return self = {
typing : 0,
onAdd : new Dispatcher(self),
onUndo : new Dispatcher(self),
onRedo : new Dispatcher(self),
/**
* Adds a new undo level/snapshot to the undo list.
*
* @method add
* @param {Object} l Optional undo level object to add.
* @return {Object} Undo level that got added or null it a level wasn't needed.
*/
add : function(level) {
var i, settings = editor.settings, lastLevel;
level = level || {};
level.content = getContent();
// Add undo level if needed
lastLevel = data[index];
if (lastLevel && lastLevel.content == level.content) {
if (index > 0 || data.length == 1)
return null;
}
// Time to compress
if (settings.custom_undo_redo_levels) {
if (data.length > settings.custom_undo_redo_levels) {
for (i = 0; i < data.length - 1; i++)
data[i] = data[i + 1];
data.length--;
index = data.length;
}
}
// Get a non intrusive normalized bookmark
level.bookmark = editor.selection.getBookmark(2, true);
// Crop array if needed
if (index < data.length - 1) {
// Treat first level as initial
if (index == 0)
data = [];
else
data.length = index + 1;
}
data.push(level);
index = data.length - 1;
self.onAdd.dispatch(self, level);
editor.isNotDirty = 0;
return level;
},
/**
* Undoes the last action.
*
* @method undo
* @return {Object} Undo level or null if no undo was performed.
*/
undo : function() {
var level, i;
if (self.typing) {
self.add();
self.typing = 0;
}
if (index > 0) {
level = data[--index];
editor.setContent(level.content, {format : 'raw'});
editor.selection.moveToBookmark(level.bookmark);
self.onUndo.dispatch(self, level);
}
return level;
},
/**
* Redoes the last action.
*
* @method redo
* @return {Object} Redo level or null if no redo was performed.
*/
redo : function() {
var level;
if (index < data.length - 1) {
level = data[++index];
editor.setContent(level.content, {format : 'raw'});
editor.selection.moveToBookmark(level.bookmark);
self.onRedo.dispatch(self, level);
}
return level;
},
/**
* Removes all undo levels.
*
* @method clear
*/
clear : function() {
data = [];
index = self.typing = 0;
},
/**
* Returns true/false if the undo manager has any undo levels.
*
* @method hasUndo
* @return {Boolean} true/false if the undo manager has any undo levels.
*/
hasUndo : function() {
return index > 0 || self.typing;
},
/**
* Returns true/false if the undo manager has any redo levels.
*
* @method hasRedo
* @return {Boolean} true/false if the undo manager has any redo levels.
*/
hasRedo : function() {
return index < data.length - 1;
}
};
};
})(tinymce);
@@ -0,0 +1,183 @@
/**
* WindowManager.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
/**
* This class handles the creation of native windows and dialogs. This class can be extended to provide for example inline dialogs.
* @class tinymce.WindowManager
*/
tinymce.create('tinymce.WindowManager', {
/**
* Constructs a new window manager instance.
*
* @constructor
* @method WindowManager
* @param {tinymce.Editor} ed Editor instance that the windows are bound to.
*/
WindowManager : function(ed) {
var t = this;
t.editor = ed;
t.onOpen = new Dispatcher(t);
t.onClose = new Dispatcher(t);
t.params = {};
t.features = {};
},
/**
* Opens a new window.
*
* @method open
* @param {Object} s Optional name/value settings collection contains things like width/height/url etc.
* @param {Object} p Optional parameters/arguments collection can be used by the dialogs to retrive custom parameters.
*/
open : function(s, p) {
var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
// Default some options
s = s || {};
p = p || {};
sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
sh = isOpera ? vp.h : screen.height;
s.name = s.name || 'mc_' + new Date().getTime();
s.width = parseInt(s.width || 320);
s.height = parseInt(s.height || 240);
s.resizable = true;
s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
p.inline = false;
p.mce_width = s.width;
p.mce_height = s.height;
p.mce_auto_focus = s.auto_focus;
if (mo) {
if (isIE) {
s.center = true;
s.help = false;
s.dialogWidth = s.width + 'px';
s.dialogHeight = s.height + 'px';
s.scroll = s.scrollbars || false;
}
}
// Build features string
each(s, function(v, k) {
if (tinymce.is(v, 'boolean'))
v = v ? 'yes' : 'no';
if (!/^(name|url)$/.test(k)) {
if (isIE && mo)
f += (f ? ';' : '') + k + ':' + v;
else
f += (f ? ',' : '') + k + '=' + v;
}
});
t.features = s;
t.params = p;
t.onOpen.dispatch(t, s, p);
u = s.url || s.file;
u = tinymce._addVer(u);
try {
if (isIE && mo) {
w = 1;
window.showModalDialog(u, window, f);
} else
w = window.open(u, s.name, f);
} catch (ex) {
// Ignore
}
if (!w)
alert(t.editor.getLang('popup_blocked'));
},
/**
* Closes the specified window. This will also dispatch out a onClose event.
*
* @method close
* @param {Window} w Native window object to close.
*/
close : function(w) {
w.close();
this.onClose.dispatch(this);
},
/**
* Creates a instance of a class. This method was needed since IE can't create instances
* of classes from a parent window due to some reference problem. Any arguments passed after the class name
* will be passed as arguments to the constructor.
*
* @method createInstance
* @param {String} cl Class name to create an instance of.
* @return {Object} Instance of the specified class.
*/
createInstance : function(cl, a, b, c, d, e) {
var f = tinymce.resolve(cl);
return new f(a, b, c, d, e);
},
/**
* Creates a confirm dialog. Please don't use the blocking behavior of this
* native version use the callback method instead then it can be extended.
*
* @method confirm
* @param {String} t Title for the new confirm dialog.
* @param {function} cb Callback function to be executed after the user has selected ok or cancel.
* @param {Object} s Optional scope to execute the callback in.
*/
confirm : function(t, cb, s, w) {
w = w || window;
cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
},
/**
* Creates a alert dialog. Please don't use the blocking behavior of this
* native version use the callback method instead then it can be extended.
*
* @method alert
* @param {String} t Title for the new alert dialog.
* @param {function} cb Callback function to be executed after the user has selected ok.
* @param {Object} s Optional scope to execute the callback in.
*/
alert : function(tx, cb, s, w) {
var t = this;
w = w || window;
w.alert(t._decode(t.editor.getLang(tx, tx)));
if (cb)
cb.call(s || t);
},
/**
* Resizes the specified window or id.
*
* @param {Number} dw Delta width.
* @param {Number} dh Delta height.
* @param {window/id} win Window if the dialog isn't inline. Id if the dialog is inline.
*/
resizeBy : function(dw, dh, win) {
win.resizeBy(dw, dh);
},
// Internal functions
_decode : function(s) {
return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
}
});
}(tinymce));
@@ -0,0 +1,336 @@
/**
* adapter.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
// #ifdef jquery_adapter
(function($, tinymce) {
var is = tinymce.is, attrRegExp = /^(href|src|style)$/i, undefined;
// jQuery is undefined
if (!$)
return alert("Load jQuery first!");
// Stick jQuery into the tinymce namespace
tinymce.$ = $;
// Setup adapter
tinymce.adapter = {
patchEditor : function(editor) {
var fn = $.fn;
// Adapt the css function to make sure that the _mce_style
// attribute gets updated with the new style information
function css(name, value) {
var self = this;
// Remove _mce_style when set operation occurs
if (value)
self.removeAttr('_mce_style');
return fn.css.apply(self, arguments);
};
// Apapt the attr function to make sure that it uses the _mce_ prefixed variants
function attr(name, value) {
var self = this;
// Update/retrive _mce_ attribute variants
if (attrRegExp.test(name)) {
if (value !== undefined) {
// Use TinyMCE behavior when setting the specifc attributes
self.each(function(i, node) {
editor.dom.setAttrib(node, name, value);
});
return self;
} else
return self.attr('_mce_' + name);
}
// Default behavior
return fn.attr.apply(self, arguments);
};
function htmlPatchFunc(func) {
// Returns a modified function that processes
// the HTML before executing the action this makes sure
// that href/src etc gets moved into the _mce_ variants
return function(content) {
if (content)
content = editor.dom.processHTML(content);
return func.call(this, content);
};
};
// Patch various jQuery functions to handle tinymce specific attribute and content behavior
// we don't patch the jQuery.fn directly since it will most likely break compatibility
// with other jQuery logic on the page. Only instances created by TinyMCE should be patched.
function patch(jq) {
// Patch some functions, only patch the object once
if (jq.css !== css) {
// Patch css/attr to use the _mce_ prefixed attribute variants
jq.css = css;
jq.attr = attr;
// Patch HTML functions to use the DOMUtils.processHTML filter logic
jq.html = htmlPatchFunc(fn.html);
jq.append = htmlPatchFunc(fn.append);
jq.prepend = htmlPatchFunc(fn.prepend);
jq.after = htmlPatchFunc(fn.after);
jq.before = htmlPatchFunc(fn.before);
jq.replaceWith = htmlPatchFunc(fn.replaceWith);
jq.tinymce = editor;
// Each pushed jQuery instance needs to be patched
// as well for example when traversing the DOM
jq.pushStack = function() {
return patch(fn.pushStack.apply(this, arguments));
};
}
return jq;
};
// Add a $ function on each editor instance this one is scoped for the editor document object
// this way you can do chaining like this tinymce.get(0).$('p').append('text').css('color', 'red');
editor.$ = function(selector, scope) {
var doc = editor.getDoc();
return patch($(selector || doc, doc || scope));
};
}
};
// Patch in core NS functions
tinymce.extend = $.extend;
tinymce.extend(tinymce, {
map : $.map,
grep : function(a, f) {return $.grep(a, f || function(){return 1;});},
inArray : function(a, v) {return $.inArray(v, a || []);}
/* Didn't iterate stylesheets
each : function(o, cb, s) {
if (!o)
return 0;
var r = 1;
$.each(o, function(nr, el){
if (cb.call(s, el, nr, o) === false) {
r = 0;
return false;
}
});
return r;
}*/
});
// Patch in functions in various clases
// Add a "#ifndefjquery" statement around each core API function you add below
var patches = {
'tinymce.dom.DOMUtils' : {
/*
addClass : function(e, c) {
if (is(e, 'array') && is(e[0], 'string'))
e = e.join(',#');
return (e && $(is(e, 'string') ? '#' + e : e)
.addClass(c)
.attr('class')) || false;
},
hasClass : function(n, c) {
return $(is(n, 'string') ? '#' + n : n).hasClass(c);
},
removeClass : function(e, c) {
if (!e)
return false;
var r = [];
$(is(e, 'string') ? '#' + e : e)
.removeClass(c)
.each(function(){
r.push(this.className);
});
return r.length == 1 ? r[0] : r;
},
*/
select : function(pattern, scope) {
var t = this;
return $.find(pattern, t.get(scope) || t.get(t.settings.root_element) || t.doc, []);
},
is : function(n, patt) {
return $(this.get(n)).is(patt);
}
/*
show : function(e) {
if (is(e, 'array') && is(e[0], 'string'))
e = e.join(',#');
$(is(e, 'string') ? '#' + e : e).css('display', 'block');
},
hide : function(e) {
if (is(e, 'array') && is(e[0], 'string'))
e = e.join(',#');
$(is(e, 'string') ? '#' + e : e).css('display', 'none');
},
isHidden : function(e) {
return $(is(e, 'string') ? '#' + e : e).is(':hidden');
},
insertAfter : function(n, e) {
return $(is(e, 'string') ? '#' + e : e).after(n);
},
replace : function(o, n, k) {
n = $(is(n, 'string') ? '#' + n : n);
if (k)
n.children().appendTo(o);
n.replaceWith(o);
},
setStyle : function(n, na, v) {
if (is(n, 'array') && is(n[0], 'string'))
n = n.join(',#');
$(is(n, 'string') ? '#' + n : n).css(na, v);
},
getStyle : function(n, na, c) {
return $(is(n, 'string') ? '#' + n : n).css(na);
},
setStyles : function(e, o) {
if (is(e, 'array') && is(e[0], 'string'))
e = e.join(',#');
$(is(e, 'string') ? '#' + e : e).css(o);
},
setAttrib : function(e, n, v) {
var t = this, s = t.settings;
if (is(e, 'array') && is(e[0], 'string'))
e = e.join(',#');
e = $(is(e, 'string') ? '#' + e : e);
switch (n) {
case "style":
e.each(function(i, v){
if (s.keep_values)
$(v).attr('_mce_style', v);
v.style.cssText = v;
});
break;
case "class":
e.each(function(){
this.className = v;
});
break;
case "src":
case "href":
e.each(function(i, v){
if (s.keep_values) {
if (s.url_converter)
v = s.url_converter.call(s.url_converter_scope || t, v, n, v);
t.setAttrib(v, '_mce_' + n, v);
}
});
break;
}
if (v !== null && v.length !== 0)
e.attr(n, '' + v);
else
e.removeAttr(n);
},
setAttribs : function(e, o) {
var t = this;
$.each(o, function(n, v){
t.setAttrib(e,n,v);
});
}
*/
}
/*
'tinymce.dom.Event' : {
add : function (o, n, f, s) {
var lo, cb;
cb = function(e) {
e.target = e.target || this;
f.call(s || this, e);
};
if (is(o, 'array') && is(o[0], 'string'))
o = o.join(',#');
o = $(is(o, 'string') ? '#' + o : o);
if (n == 'init') {
o.ready(cb, s);
} else {
if (s) {
o.bind(n, s, cb);
} else {
o.bind(n, cb);
}
}
lo = this._jqLookup || (this._jqLookup = []);
lo.push({func : f, cfunc : cb});
return cb;
},
remove : function(o, n, f) {
// Find cfunc
$(this._jqLookup).each(function() {
if (this.func === f)
f = this.cfunc;
});
if (is(o, 'array') && is(o[0], 'string'))
o = o.join(',#');
$(is(o, 'string') ? '#' + o : o).unbind(n,f);
return true;
}
}
*/
};
// Patch functions after a class is created
tinymce.onCreate = function(ty, c, p) {
tinymce.extend(p, patches[c]);
};
})(window.jQuery, tinymce);
// #endif
@@ -0,0 +1,332 @@
/**
* jquery.tinymce.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function($) {
var undefined,
lazyLoading,
delayedInits = [],
win = window;
$.fn.tinymce = function(settings) {
var self = this, url, ed, base, pos, lang, query = "", suffix = "";
// No match then just ignore the call
if (!self.length)
return self;
// Get editor instance
if (!settings)
return tinyMCE.get(self[0].id);
function init() {
var editors = [], initCount = 0;
// Apply patches to the jQuery object, only once
if (applyPatch) {
applyPatch();
applyPatch = null;
}
// Create an editor instance for each matched node
self.each(function(i, node) {
var ed, id = node.id, oninit = settings.oninit;
// Generate unique id for target element if needed
if (!id)
node.id = id = tinymce.DOM.uniqueId();
// Create editor instance and render it
ed = new tinymce.Editor(id, settings);
editors.push(ed);
// Add onInit event listener if the oninit setting is defined
// this logic will fire the oninit callback ones each
// matched editor instance is initialized
if (oninit) {
ed.onInit.add(function() {
var scope, func = oninit;
// Fire the oninit event ones each editor instance is initialized
if (++initCount == editors.length) {
if (tinymce.is(func, "string")) {
scope = (func.indexOf(".") === -1) ? null : tinymce.resolve(func.replace(/\.\w+$/, ""));
func = tinymce.resolve(func);
}
// Call the oninit function with the object
func.apply(scope || tinymce, editors);
}
});
}
});
// Render the editor instances in a separate loop since we
// need to have the full editors array used in the onInit calls
$.each(editors, function(i, ed) {
ed.render();
});
}
// Load TinyMCE on demand, if we need to
if (!win["tinymce"] && !lazyLoading && (url = settings.script_url)) {
lazyLoading = 1;
base = url.substring(0, url.lastIndexOf("/"));
// Check if it's a dev/src version they want to load then
// make sure that all plugins, themes etc are loaded in source mode aswell
if (/_(src|dev)\.js/g.test(url))
suffix = "_src";
// Parse out query part, this will be appended to all scripts, css etc to clear browser cache
pos = url.lastIndexOf("?");
if (pos != -1)
query = url.substring(pos + 1);
// Setup tinyMCEPreInit object this will later be used by the TinyMCE
// core script to locate other resources like CSS files, dialogs etc
// You can also predefined a tinyMCEPreInit object and then it will use that instead
win.tinyMCEPreInit = win.tinyMCEPreInit || {
base : base,
suffix : suffix,
query : query
};
// url contains gzip then we assume it's a compressor
if (url.indexOf('gzip') != -1) {
lang = settings.language || "en";
url = url + (/\?/.test(url) ? '&' : '?') + "js=true&core=true&suffix=" + escape(suffix) + "&themes=" + escape(settings.theme) + "&plugins=" + escape(settings.plugins) + "&languages=" + lang;
// Check if compressor script is already loaded otherwise setup a basic one
if (!win["tinyMCE_GZ"]) {
tinyMCE_GZ = {
start : function() {
tinymce.suffix = suffix;
function load(url) {
tinymce.ScriptLoader.markDone(tinyMCE.baseURI.toAbsolute(url));
}
// Add core languages
load("langs/" + lang + ".js");
// Add themes with languages
load("themes/" + settings.theme + "/editor_template" + suffix + ".js");
load("themes/" + settings.theme + "/langs/" + lang + ".js");
// Add plugins with languages
$.each(settings.plugins.split(","), function(i, name) {
if (name) {
load("plugins/" + name + "/editor_plugin" + suffix + ".js");
load("plugins/" + name + "/langs/" + lang + ".js");
}
});
},
end : function() {
}
}
}
}
// Load the script cached and execute the inits once it's done
$.ajax({
type : "GET",
url : url,
dataType : "script",
cache : true,
success : function() {
tinymce.dom.Event.domLoaded = 1;
lazyLoading = 2;
// Execute callback after mainscript has been loaded and before the initialization occurs
if (settings.script_loaded)
settings.script_loaded();
init();
$.each(delayedInits, function(i, init) {
init();
});
}
});
} else {
// Delay the init call until tinymce is loaded
if (lazyLoading === 1)
delayedInits.push(init);
else
init();
}
return self;
};
// Add :tinymce psuedo selector this will select elements that has been converted into editor instances
// it's now possible to use things like $('*:tinymce') to get all TinyMCE bound elements.
$.extend($.expr[":"], {
tinymce : function(e) {
return e.id && !!tinyMCE.get(e.id);
}
});
// This function patches internal jQuery functions so that if
// you for example remove an div element containing an editor it's
// automatically destroyed by the TinyMCE API
function applyPatch() {
// Removes any child editor instances by looking for editor wrapper elements
function removeEditors(name) {
// If the function is remove
if (name === "remove") {
this.each(function(i, node) {
var ed = tinyMCEInstance(node);
if (ed)
ed.remove();
});
}
this.find("span.mceEditor,div.mceEditor").each(function(i, node) {
var ed = tinyMCE.get(node.id.replace(/_parent$/, ""));
if (ed)
ed.remove();
});
}
// Loads or saves contents from/to textarea if the value
// argument is defined it will set the TinyMCE internal contents
function loadOrSave(value) {
var self = this, ed;
// Handle set value
if (value !== undefined) {
removeEditors.call(self);
// Saves the contents before get/set value of textarea/div
self.each(function(i, node) {
var ed;
if (ed = tinyMCE.get(node.id))
ed.setContent(value);
});
} else if (self.length > 0) {
// Handle get value
if (ed = tinyMCE.get(self[0].id))
return ed.getContent();
}
}
// Returns tinymce instance for the specified element or null if it wasn't found
function tinyMCEInstance(element) {
var ed = null;
(element) && (element.id) && (win["tinymce"]) && (ed = tinyMCE.get(element.id));
return ed;
}
// Checks if the specified set contains tinymce instances
function containsTinyMCE(matchedSet) {
return !!((matchedSet) && (matchedSet.length) && (win["tinymce"]) && (matchedSet.is(":tinymce")));
}
// Patch various jQuery functions
var jQueryFn = {};
// Patch some setter/getter functions these will
// now be able to set/get the contents of editor instances for
// example $('#editorid').html('Content'); will update the TinyMCE iframe instance
$.each(["text", "html", "val"], function(i, name) {
var origFn = jQueryFn[name] = $.fn[name],
textProc = (name === "text");
$.fn[name] = function(value) {
var self = this;
if (!containsTinyMCE(self))
return origFn.apply(self, arguments);
if (value !== undefined) {
loadOrSave.call(self.filter(":tinymce"), value);
origFn.apply(self.not(":tinymce"), arguments);
return self; // return original set for chaining
} else {
var ret = "";
var args = arguments;
(textProc ? self : self.eq(0)).each(function(i, node) {
var ed = tinyMCEInstance(node);
ret += ed ? (textProc ? ed.getContent().replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g, "") : ed.getContent()) : origFn.apply($(node), args);
});
return ret;
}
};
});
// Makes it possible to use $('#id').append("content"); to append contents to the TinyMCE editor iframe
$.each(["append", "prepend"], function(i, name) {
var origFn = jQueryFn[name] = $.fn[name],
prepend = (name === "prepend");
$.fn[name] = function(value) {
var self = this;
if (!containsTinyMCE(self))
return origFn.apply(self, arguments);
if (value !== undefined) {
self.filter(":tinymce").each(function(i, node) {
var ed = tinyMCEInstance(node);
ed && ed.setContent(prepend ? value + ed.getContent() : ed.getContent() + value);
});
origFn.apply(self.not(":tinymce"), arguments);
return self; // return original set for chaining
}
};
});
// Makes sure that the editor instance gets properly destroyed when the parent element is removed
$.each(["remove", "replaceWith", "replaceAll", "empty"], function(i, name) {
var origFn = jQueryFn[name] = $.fn[name];
$.fn[name] = function() {
removeEditors.call(this, name);
return origFn.apply(this, arguments);
};
});
jQueryFn.attr = $.fn.attr;
// Makes sure that $('#tinymce_id').attr('value') gets the editors current HTML contents
$.fn.attr = function(name, value, type) {
var self = this;
if ((!name) || (name !== "value") || (!containsTinyMCE(self)))
return jQueryFn.attr.call(self, name, value, type);
if (value !== undefined) {
loadOrSave.call(self.filter(":tinymce"), value);
jQueryFn.attr.call(self.not(":tinymce"), name, value, type);
return self; // return original set for chaining
} else {
var node = self[0], ed = tinyMCEInstance(node);
return ed ? ed.getContent() : jQueryFn.attr.call($(node), name, value, type);
}
};
}
})(jQuery);
@@ -0,0 +1,39 @@
/**
* adapter.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
// #ifdef prototype_adapter
(function() {
if (!window.Prototype)
return alert("Load prototype first!");
// Patch in core NS functions
tinymce.extend(tinymce, {
trim : function(s) {return s ? s.strip() : '';},
inArray : function(a, v) {return a && a.indexOf ? a.indexOf(v) : -1;}
});
// Patch in functions in various clases
// Add a "#ifndefjquery" statement around each core API function you add below
var patches = {
'tinymce.util.JSON' : {
/*serialize : function(o) {
return o.toJSON();
}*/
},
};
// Patch functions after a class is created
tinymce.onCreate = function(ty, c, p) {
tinymce.extend(p, patches[c]);
};
})();
// #endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,189 @@
/**
* Element.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
/**
* Element class, this enables element blocking in IE. Element blocking is a method to block out select blockes that
* gets visible though DIVs on IE 6 it uses a iframe for this blocking. This class also shortens the length of some DOM API calls
* since it's bound to an element.
*
* @class tinymce.dom.Element
*/
/**
* Constructs a new Element instance. Consult the Wiki for more details on this class.
*
* @constructor
* @method Element
* @param {String} id Element ID to bind/execute methods on.
* @param {Object} settings Optional settings name/value collection.
*/
tinymce.dom.Element = function(id, settings) {
var t = this, dom, el;
t.settings = settings = settings || {};
t.id = id;
t.dom = dom = settings.dom || tinymce.DOM;
// Only IE leaks DOM references, this is a lot faster
if (!tinymce.isIE)
el = dom.get(t.id);
tinymce.each(
('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' +
'setAttrib,setAttribs,getAttrib,addClass,removeClass,' +
'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' +
'isHidden,setHTML,get').split(/,/)
, function(k) {
t[k] = function() {
var a = [id], i;
for (i = 0; i < arguments.length; i++)
a.push(arguments[i]);
a = dom[k].apply(dom, a);
t.update(k);
return a;
};
});
tinymce.extend(t, {
/**
* Adds a event handler to the element.
*
* @method on
* @param {String} n Event name like for example "click".
* @param {function} f Function to execute on the specified event.
* @param {Object} s Optional scope to execute function on.
* @return {function} Event handler function the same as the input function.
*/
on : function(n, f, s) {
return tinymce.dom.Event.add(t.id, n, f, s);
},
/**
* Returns the absolute X, Y cordinate of the element.
*
* @method getXY
* @return {Object} Objext with x, y cordinate fields.
*/
getXY : function() {
return {
x : parseInt(t.getStyle('left')),
y : parseInt(t.getStyle('top'))
};
},
/**
* Returns the size of the element by a object with w and h fields.
*
* @method getSize
* @return {Object} Object with element size with a w and h field.
*/
getSize : function() {
var n = dom.get(t.id);
return {
w : parseInt(t.getStyle('width') || n.clientWidth),
h : parseInt(t.getStyle('height') || n.clientHeight)
};
},
/**
* Moves the element to a specific absolute position.
*
* @method moveTo
* @param {Number} x X cordinate of element position.
* @param {Number} y Y cordinate of element position.
*/
moveTo : function(x, y) {
t.setStyles({left : x, top : y});
},
/**
* Moves the element relative to the current position.
*
* @method moveBy
* @param {Number} x Relative X cordinate of element position.
* @param {Number} y Relative Y cordinate of element position.
*/
moveBy : function(x, y) {
var p = t.getXY();
t.moveTo(p.x + x, p.y + y);
},
/**
* Resizes the element to a specific size.
*
* @method resizeTo
* @param {Number} w New width of element.
* @param {Numner} h New height of element.
*/
resizeTo : function(w, h) {
t.setStyles({width : w, height : h});
},
/**
* Resizes the element relative to the current sizeto a specific size.
*
* @method resizeBy
* @param {Number} w Relative width of element.
* @param {Numner} h Relative height of element.
*/
resizeBy : function(w, h) {
var s = t.getSize();
t.resizeTo(s.w + w, s.h + h);
},
/**
* Updates the element blocker in IE6 based on the style information of the element.
*
* @method update
* @param {String} k Optional function key. Used internally.
*/
update : function(k) {
var b;
if (tinymce.isIE6 && settings.blocker) {
k = k || '';
// Ignore getters
if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
return;
// Remove blocker on remove
if (k == 'remove') {
dom.remove(t.blocker);
return;
}
if (!t.blocker) {
t.blocker = dom.uniqueId();
b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
dom.setStyle(b, 'opacity', 0);
} else
b = dom.get(t.blocker);
dom.setStyles(b, {
left : t.getStyle('left', 1),
top : t.getStyle('top', 1),
width : t.getStyle('width', 1),
height : t.getStyle('height', 1),
display : t.getStyle('display', 1),
zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
});
}
}
});
};
})(tinymce);
@@ -0,0 +1,363 @@
/**
* EventUtils.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
// Shorten names
var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
/**
* This class handles DOM events in a cross platform fasion it also keeps track of element
* and handler references to be able to clean elements to reduce IE memory leaks.
*
* @class tinymce.dom.EventUtils
*/
tinymce.create('tinymce.dom.EventUtils', {
/**
* Constructs a new EventUtils instance.
*
* @constructor
* @method EventUtils
*/
EventUtils : function() {
this.inits = [];
this.events = [];
},
/**
* Adds an event handler to the specified object.
*
* @method add
* @param {Element/Document/Window/Array/String} o Object or element id string to add event handler to or an array of elements/ids/documents.
* @param {String/Array} n Name of event handler to add for example: click.
* @param {function} f Function to execute when the event occurs.
* @param {Object} s Optional scope to execute the function in.
* @return {function} Function callback handler the same as the one passed in.
*/
add : function(o, n, f, s) {
var cb, t = this, el = t.events, r;
if (n instanceof Array) {
r = [];
each(n, function(n) {
r.push(t.add(o, n, f, s));
});
return r;
}
// Handle array
if (o && o.hasOwnProperty && o instanceof Array) {
r = [];
each(o, function(o) {
o = DOM.get(o);
r.push(t.add(o, n, f, s));
});
return r;
}
o = DOM.get(o);
if (!o)
return;
// Setup event callback
cb = function(e) {
// Is all events disabled
if (t.disabled)
return;
e = e || window.event;
// Patch in target, preventDefault and stopPropagation in IE it's W3C valid
if (e && isIE) {
if (!e.target)
e.target = e.srcElement;
// Patch in preventDefault, stopPropagation methods for W3C compatibility
tinymce.extend(e, t._stoppers);
}
if (!s)
return f(e);
return f.call(s, e);
};
if (n == 'unload') {
tinymce.unloads.unshift({func : cb});
return cb;
}
if (n == 'init') {
if (t.domLoaded)
cb();
else
t.inits.push(cb);
return cb;
}
// Store away listener reference
el.push({
obj : o,
name : n,
func : f,
cfunc : cb,
scope : s
});
t._add(o, n, cb);
return f;
},
/**
* Removes the specified event handler by name and function from a element or collection of elements.
*
* @method remove
* @param {String/Element/Array} o Element ID string or HTML element or an array of elements or ids to remove handler from.
* @param {String} n Event handler name like for example: "click"
* @param {function} f Function to remove.
* @return {bool/Array} Bool state if true if the handler was removed or an array with states if multiple elements where passed in.
*/
remove : function(o, n, f) {
var t = this, a = t.events, s = false, r;
// Handle array
if (o && o.hasOwnProperty && o instanceof Array) {
r = [];
each(o, function(o) {
o = DOM.get(o);
r.push(t.remove(o, n, f));
});
return r;
}
o = DOM.get(o);
each(a, function(e, i) {
if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
a.splice(i, 1);
t._remove(o, n, e.cfunc);
s = true;
return false;
}
});
return s;
},
/**
* Clears all events of a specific object.
*
* @method clear
* @param {Object} o DOM element or object to remove all events from.
*/
clear : function(o) {
var t = this, a = t.events, i, e;
if (o) {
o = DOM.get(o);
for (i = a.length - 1; i >= 0; i--) {
e = a[i];
if (e.obj === o) {
t._remove(e.obj, e.name, e.cfunc);
e.obj = e.cfunc = null;
a.splice(i, 1);
}
}
}
},
/**
* Cancels an event for both bubbeling and the default browser behavior.
*
* @method cancel
* @param {Event} e Event object to cancel.
* @return {Boolean} Always false.
*/
cancel : function(e) {
if (!e)
return false;
this.stop(e);
return this.prevent(e);
},
/**
* Stops propogation/bubbeling of an event.
*
* @method stop
* @param {Event} e Event to cancel bubbeling on.
* @return {Boolean} Always false.
*/
stop : function(e) {
if (e.stopPropagation)
e.stopPropagation();
else
e.cancelBubble = true;
return false;
},
/**
* Prevent default browser behvaior of an event.
*
* @method prevent
* @param {Event} e Event to prevent default browser behvaior of an event.
* @return {Boolean} Always false.
*/
prevent : function(e) {
if (e.preventDefault)
e.preventDefault();
else
e.returnValue = false;
return false;
},
/**
* Destroys the instance.
*
* @method destroy
*/
destroy : function() {
var t = this;
each(t.events, function(e, i) {
t._remove(e.obj, e.name, e.cfunc);
e.obj = e.cfunc = null;
});
t.events = [];
t = null;
},
_add : function(o, n, f) {
if (o.attachEvent)
o.attachEvent('on' + n, f);
else if (o.addEventListener)
o.addEventListener(n, f, false);
else
o['on' + n] = f;
},
_remove : function(o, n, f) {
if (o) {
try {
if (o.detachEvent)
o.detachEvent('on' + n, f);
else if (o.removeEventListener)
o.removeEventListener(n, f, false);
else
o['on' + n] = null;
} catch (ex) {
// Might fail with permission denined on IE so we just ignore that
}
}
},
_pageInit : function(win) {
var t = this;
// Keep it from running more than once
if (t.domLoaded)
return;
t.domLoaded = true;
each(t.inits, function(c) {
c();
});
t.inits = [];
},
_wait : function(win) {
var t = this, doc = win.document;
// No need since the document is already loaded
if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
t.domLoaded = 1;
return;
}
// Use IE method
if (doc.attachEvent) {
doc.attachEvent("onreadystatechange", function() {
if (doc.readyState === "complete") {
doc.detachEvent("onreadystatechange", arguments.callee);
t._pageInit(win);
}
});
if (doc.documentElement.doScroll && win == win.top) {
(function() {
if (t.domLoaded)
return;
try {
// If IE is used, use the trick by Diego Perini
// http://javascript.nwbox.com/IEContentLoaded/
doc.documentElement.doScroll("left");
} catch (ex) {
setTimeout(arguments.callee, 0);
return;
}
t._pageInit(win);
})();
}
} else if (doc.addEventListener) {
t._add(win, 'DOMContentLoaded', function() {
t._pageInit(win);
});
}
t._add(win, 'load', function() {
t._pageInit(win);
});
},
_stoppers : {
preventDefault : function() {
this.returnValue = false;
},
stopPropagation : function() {
this.cancelBubble = true;
}
}
});
/**
* Instance of EventUtils for the current document.
*
* @property Event
* @member tinymce.dom
* @type tinymce.dom.EventUtils
*/
Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
// Dispatch DOM content loaded event for IE and Safari
Event._wait(window);
tinymce.addUnload(function() {
Event.destroy();
});
})(tinymce);
@@ -0,0 +1,686 @@
/**
* Range.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(ns) {
// Range constructor
function Range(dom) {
var t = this,
doc = dom.doc,
EXTRACT = 0,
CLONE = 1,
DELETE = 2,
TRUE = true,
FALSE = false,
START_OFFSET = 'startOffset',
START_CONTAINER = 'startContainer',
END_CONTAINER = 'endContainer',
END_OFFSET = 'endOffset',
extend = tinymce.extend,
nodeIndex = dom.nodeIndex;
extend(t, {
// Inital states
startContainer : doc,
startOffset : 0,
endContainer : doc,
endOffset : 0,
collapsed : TRUE,
commonAncestorContainer : doc,
// Range constants
START_TO_START : 0,
START_TO_END : 1,
END_TO_END : 2,
END_TO_START : 3,
// Public methods
setStart : setStart,
setEnd : setEnd,
setStartBefore : setStartBefore,
setStartAfter : setStartAfter,
setEndBefore : setEndBefore,
setEndAfter : setEndAfter,
collapse : collapse,
selectNode : selectNode,
selectNodeContents : selectNodeContents,
compareBoundaryPoints : compareBoundaryPoints,
deleteContents : deleteContents,
extractContents : extractContents,
cloneContents : cloneContents,
insertNode : insertNode,
surroundContents : surroundContents,
cloneRange : cloneRange
});
function setStart(n, o) {
_setEndPoint(TRUE, n, o);
};
function setEnd(n, o) {
_setEndPoint(FALSE, n, o);
};
function setStartBefore(n) {
setStart(n.parentNode, nodeIndex(n));
};
function setStartAfter(n) {
setStart(n.parentNode, nodeIndex(n) + 1);
};
function setEndBefore(n) {
setEnd(n.parentNode, nodeIndex(n));
};
function setEndAfter(n) {
setEnd(n.parentNode, nodeIndex(n) + 1);
};
function collapse(ts) {
if (ts) {
t[END_CONTAINER] = t[START_CONTAINER];
t[END_OFFSET] = t[START_OFFSET];
} else {
t[START_CONTAINER] = t[END_CONTAINER];
t[START_OFFSET] = t[END_OFFSET];
}
t.collapsed = TRUE;
};
function selectNode(n) {
setStartBefore(n);
setEndAfter(n);
};
function selectNodeContents(n) {
setStart(n, 0);
setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
};
function compareBoundaryPoints(h, r) {
var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET];
// Check START_TO_START
if (h === 0)
return _compareBoundaryPoints(sc, so, sc, so);
// Check START_TO_END
if (h === 1)
return _compareBoundaryPoints(sc, so, ec, eo);
// Check END_TO_END
if (h === 2)
return _compareBoundaryPoints(ec, eo, ec, eo);
// Check END_TO_START
if (h === 3)
return _compareBoundaryPoints(ec, eo, sc, so);
};
function deleteContents() {
_traverse(DELETE);
};
function extractContents() {
return _traverse(EXTRACT);
};
function cloneContents() {
return _traverse(CLONE);
};
function insertNode(n) {
var startContainer = this[START_CONTAINER],
startOffset = this[START_OFFSET], nn, o;
// Node is TEXT_NODE or CDATA
if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
if (!startOffset) {
// At the start of text
startContainer.parentNode.insertBefore(n, startContainer);
} else if (startOffset >= startContainer.nodeValue.length) {
// At the end of text
dom.insertAfter(n, startContainer);
} else {
// Middle, need to split
nn = startContainer.splitText(startOffset);
startContainer.parentNode.insertBefore(n, nn);
}
} else {
// Insert element node
if (startContainer.childNodes.length > 0)
o = startContainer.childNodes[startOffset];
if (o)
startContainer.insertBefore(n, o);
else
startContainer.appendChild(n);
}
};
function surroundContents(n) {
var f = t.extractContents();
t.insertNode(n);
n.appendChild(f);
t.selectNode(n);
};
function cloneRange() {
return extend(new Range(dom), {
startContainer : t[START_CONTAINER],
startOffset : t[START_OFFSET],
endContainer : t[END_CONTAINER],
endOffset : t[END_OFFSET],
collapsed : t.collapsed,
commonAncestorContainer : t.commonAncestorContainer
});
};
// Private methods
function _getSelectedNode(container, offset) {
var child;
if (container.nodeType == 3 /* TEXT_NODE */)
return container;
if (offset < 0)
return container;
child = container.firstChild;
while (child && offset > 0) {
--offset;
child = child.nextSibling;
}
if (child)
return child;
return container;
};
function _isCollapsed() {
return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
};
function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
var c, offsetC, n, cmnRoot, childA, childB;
// In the first case the boundary-points have the same container. A is before B
// if its offset is less than the offset of B, A is equal to B if its offset is
// equal to the offset of B, and A is after B if its offset is greater than the
// offset of B.
if (containerA == containerB) {
if (offsetA == offsetB)
return 0; // equal
if (offsetA < offsetB)
return -1; // before
return 1; // after
}
// In the second case a child node C of the container of A is an ancestor
// container of B. In this case, A is before B if the offset of A is less than or
// equal to the index of the child node C and A is after B otherwise.
c = containerB;
while (c && c.parentNode != containerA)
c = c.parentNode;
if (c) {
offsetC = 0;
n = containerA.firstChild;
while (n != c && offsetC < offsetA) {
offsetC++;
n = n.nextSibling;
}
if (offsetA <= offsetC)
return -1; // before
return 1; // after
}
// In the third case a child node C of the container of B is an ancestor container
// of A. In this case, A is before B if the index of the child node C is less than
// the offset of B and A is after B otherwise.
c = containerA;
while (c && c.parentNode != containerB) {
c = c.parentNode;
}
if (c) {
offsetC = 0;
n = containerB.firstChild;
while (n != c && offsetC < offsetB) {
offsetC++;
n = n.nextSibling;
}
if (offsetC < offsetB)
return -1; // before
return 1; // after
}
// In the fourth case, none of three other cases hold: the containers of A and B
// are siblings or descendants of sibling nodes. In this case, A is before B if
// the container of A is before the container of B in a pre-order traversal of the
// Ranges' context tree and A is after B otherwise.
cmnRoot = dom.findCommonAncestor(containerA, containerB);
childA = containerA;
while (childA && childA.parentNode != cmnRoot)
childA = childA.parentNode;
if (!childA)
childA = cmnRoot;
childB = containerB;
while (childB && childB.parentNode != cmnRoot)
childB = childB.parentNode;
if (!childB)
childB = cmnRoot;
if (childA == childB)
return 0; // equal
n = cmnRoot.firstChild;
while (n) {
if (n == childA)
return -1; // before
if (n == childB)
return 1; // after
n = n.nextSibling;
}
};
function _setEndPoint(st, n, o) {
var ec, sc;
if (st) {
t[START_CONTAINER] = n;
t[START_OFFSET] = o;
} else {
t[END_CONTAINER] = n;
t[END_OFFSET] = o;
}
// If one boundary-point of a Range is set to have a root container
// other than the current one for the Range, the Range is collapsed to
// the new position. This enforces the restriction that both boundary-
// points of a Range must have the same root container.
ec = t[END_CONTAINER];
while (ec.parentNode)
ec = ec.parentNode;
sc = t[START_CONTAINER];
while (sc.parentNode)
sc = sc.parentNode;
if (sc == ec) {
// The start position of a Range is guaranteed to never be after the
// end position. To enforce this restriction, if the start is set to
// be at a position after the end, the Range is collapsed to that
// position.
if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
t.collapse(st);
} else
t.collapse(st);
t.collapsed = _isCollapsed();
t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
};
function _traverse(how) {
var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
if (t[START_CONTAINER] == t[END_CONTAINER])
return _traverseSameContainer(how);
for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
if (p == t[START_CONTAINER])
return _traverseCommonStartContainer(c, how);
++endContainerDepth;
}
for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
if (p == t[END_CONTAINER])
return _traverseCommonEndContainer(c, how);
++startContainerDepth;
}
depthDiff = startContainerDepth - endContainerDepth;
startNode = t[START_CONTAINER];
while (depthDiff > 0) {
startNode = startNode.parentNode;
depthDiff--;
}
endNode = t[END_CONTAINER];
while (depthDiff < 0) {
endNode = endNode.parentNode;
depthDiff++;
}
// ascend the ancestor hierarchy until we have a common parent.
for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
startNode = sp;
endNode = ep;
}
return _traverseCommonAncestors(startNode, endNode, how);
};
function _traverseSameContainer(how) {
var frag, s, sub, n, cnt, sibling, xferNode;
if (how != DELETE)
frag = doc.createDocumentFragment();
// If selection is empty, just return the fragment
if (t[START_OFFSET] == t[END_OFFSET])
return frag;
// Text node needs special case handling
if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
// get the substring
s = t[START_CONTAINER].nodeValue;
sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
// set the original text node to its new value
if (how != CLONE) {
t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
// Nothing is partially selected, so collapse to start point
t.collapse(TRUE);
}
if (how == DELETE)
return;
frag.appendChild(doc.createTextNode(sub));
return frag;
}
// Copy nodes between the start/end offsets.
n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
cnt = t[END_OFFSET] - t[START_OFFSET];
while (cnt > 0) {
sibling = n.nextSibling;
xferNode = _traverseFullySelected(n, how);
if (frag)
frag.appendChild( xferNode );
--cnt;
n = sibling;
}
// Nothing is partially selected, so collapse to start point
if (how != CLONE)
t.collapse(TRUE);
return frag;
};
function _traverseCommonStartContainer(endAncestor, how) {
var frag, n, endIdx, cnt, sibling, xferNode;
if (how != DELETE)
frag = doc.createDocumentFragment();
n = _traverseRightBoundary(endAncestor, how);
if (frag)
frag.appendChild(n);
endIdx = nodeIndex(endAncestor);
cnt = endIdx - t[START_OFFSET];
if (cnt <= 0) {
// Collapse to just before the endAncestor, which
// is partially selected.
if (how != CLONE) {
t.setEndBefore(endAncestor);
t.collapse(FALSE);
}
return frag;
}
n = endAncestor.previousSibling;
while (cnt > 0) {
sibling = n.previousSibling;
xferNode = _traverseFullySelected(n, how);
if (frag)
frag.insertBefore(xferNode, frag.firstChild);
--cnt;
n = sibling;
}
// Collapse to just before the endAncestor, which
// is partially selected.
if (how != CLONE) {
t.setEndBefore(endAncestor);
t.collapse(FALSE);
}
return frag;
};
function _traverseCommonEndContainer(startAncestor, how) {
var frag, startIdx, n, cnt, sibling, xferNode;
if (how != DELETE)
frag = doc.createDocumentFragment();
n = _traverseLeftBoundary(startAncestor, how);
if (frag)
frag.appendChild(n);
startIdx = nodeIndex(startAncestor);
++startIdx; // Because we already traversed it....
cnt = t[END_OFFSET] - startIdx;
n = startAncestor.nextSibling;
while (cnt > 0) {
sibling = n.nextSibling;
xferNode = _traverseFullySelected(n, how);
if (frag)
frag.appendChild(xferNode);
--cnt;
n = sibling;
}
if (how != CLONE) {
t.setStartAfter(startAncestor);
t.collapse(TRUE);
}
return frag;
};
function _traverseCommonAncestors(startAncestor, endAncestor, how) {
var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
if (how != DELETE)
frag = doc.createDocumentFragment();
n = _traverseLeftBoundary(startAncestor, how);
if (frag)
frag.appendChild(n);
commonParent = startAncestor.parentNode;
startOffset = nodeIndex(startAncestor);
endOffset = nodeIndex(endAncestor);
++startOffset;
cnt = endOffset - startOffset;
sibling = startAncestor.nextSibling;
while (cnt > 0) {
nextSibling = sibling.nextSibling;
n = _traverseFullySelected(sibling, how);
if (frag)
frag.appendChild(n);
sibling = nextSibling;
--cnt;
}
n = _traverseRightBoundary(endAncestor, how);
if (frag)
frag.appendChild(n);
if (how != CLONE) {
t.setStartAfter(startAncestor);
t.collapse(TRUE);
}
return frag;
};
function _traverseRightBoundary(root, how) {
var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
if (next == root)
return _traverseNode(next, isFullySelected, FALSE, how);
parent = next.parentNode;
clonedParent = _traverseNode(parent, FALSE, FALSE, how);
while (parent) {
while (next) {
prevSibling = next.previousSibling;
clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
if (how != DELETE)
clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
isFullySelected = TRUE;
next = prevSibling;
}
if (parent == root)
return clonedParent;
next = parent.previousSibling;
parent = parent.parentNode;
clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
if (how != DELETE)
clonedGrandParent.appendChild(clonedParent);
clonedParent = clonedGrandParent;
}
};
function _traverseLeftBoundary(root, how) {
var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
if (next == root)
return _traverseNode(next, isFullySelected, TRUE, how);
parent = next.parentNode;
clonedParent = _traverseNode(parent, FALSE, TRUE, how);
while (parent) {
while (next) {
nextSibling = next.nextSibling;
clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
if (how != DELETE)
clonedParent.appendChild(clonedChild);
isFullySelected = TRUE;
next = nextSibling;
}
if (parent == root)
return clonedParent;
next = parent.nextSibling;
parent = parent.parentNode;
clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
if (how != DELETE)
clonedGrandParent.appendChild(clonedParent);
clonedParent = clonedGrandParent;
}
};
function _traverseNode(n, isFullySelected, isLeft, how) {
var txtValue, newNodeValue, oldNodeValue, offset, newNode;
if (isFullySelected)
return _traverseFullySelected(n, how);
if (n.nodeType == 3 /* TEXT_NODE */) {
txtValue = n.nodeValue;
if (isLeft) {
offset = t[START_OFFSET];
newNodeValue = txtValue.substring(offset);
oldNodeValue = txtValue.substring(0, offset);
} else {
offset = t[END_OFFSET];
newNodeValue = txtValue.substring(0, offset);
oldNodeValue = txtValue.substring(offset);
}
if (how != CLONE)
n.nodeValue = oldNodeValue;
if (how == DELETE)
return;
newNode = n.cloneNode(FALSE);
newNode.nodeValue = newNodeValue;
return newNode;
}
if (how == DELETE)
return;
return n.cloneNode(FALSE);
};
function _traverseFullySelected(n, how) {
if (how != DELETE)
return how == CLONE ? n.cloneNode(TRUE) : n;
n.parentNode.removeChild(n);
};
};
ns.Range = Range;
})(tinymce.dom);
@@ -0,0 +1,228 @@
/**
* Range.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
tinymce.dom.RangeUtils = function(dom) {
var INVISIBLE_CHAR = '\uFEFF';
/**
* Walks the specified range like object and executes the callback for each sibling collection it finds.
*
* @param {Object} rng Range like object.
* @param {function} callback Callback function to execute for each sibling collection.
*/
this.walk = function(rng, callback) {
var startContainer = rng.startContainer,
startOffset = rng.startOffset,
endContainer = rng.endContainer,
endOffset = rng.endOffset,
ancestor, startPoint,
endPoint, node, parent, siblings, nodes;
// Handle table cell selection the table plugin enables
// you to fake select table cells and perform formatting actions on them
nodes = dom.select('td.mceSelected,th.mceSelected');
if (nodes.length > 0) {
tinymce.each(nodes, function(node) {
callback([node]);
});
return;
}
/**
* Collects siblings
*
* @private
* @param {Node} node Node to collect siblings from.
* @param {String} name Name of the sibling to check for.
* @return {Array} Array of collected siblings.
*/
function collectSiblings(node, name, end_node) {
var siblings = [];
for (; node && node != end_node; node = node[name])
siblings.push(node);
return siblings;
};
/**
* Find an end point this is the node just before the common ancestor root.
*
* @private
* @param {Node} node Node to start at.
* @param {Node} root Root/ancestor element to stop just before.
* @return {Node} Node just before the root element.
*/
function findEndPoint(node, root) {
do {
if (node.parentNode == root)
return node;
node = node.parentNode;
} while(node);
};
function walkBoundary(start_node, end_node, next) {
var siblingName = next ? 'nextSibling' : 'previousSibling';
for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
parent = node.parentNode;
siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
if (siblings.length) {
if (!next)
siblings.reverse();
callback(siblings);
}
}
};
// If index based start position then resolve it
if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
startContainer = startContainer.childNodes[startOffset];
// If index based end position then resolve it
if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
endContainer = endContainer.childNodes[Math.min(startOffset == endOffset ? endOffset : endOffset - 1, endContainer.childNodes.length - 1)];
// Find common ancestor and end points
ancestor = dom.findCommonAncestor(startContainer, endContainer);
// Same container
if (startContainer == endContainer)
return callback([startContainer]);
// Process left side
for (node = startContainer; node; node = node.parentNode) {
if (node == endContainer)
return walkBoundary(startContainer, ancestor, true);
if (node == ancestor)
break;
}
// Process right side
for (node = endContainer; node; node = node.parentNode) {
if (node == startContainer)
return walkBoundary(endContainer, ancestor);
if (node == ancestor)
break;
}
// Find start/end point
startPoint = findEndPoint(startContainer, ancestor) || startContainer;
endPoint = findEndPoint(endContainer, ancestor) || endContainer;
// Walk left leaf
walkBoundary(startContainer, startPoint, true);
// Walk the middle from start to end point
siblings = collectSiblings(
startPoint == startContainer ? startPoint : startPoint.nextSibling,
'nextSibling',
endPoint == endContainer ? endPoint.nextSibling : endPoint
);
if (siblings.length)
callback(siblings);
// Walk right leaf
walkBoundary(endContainer, endPoint);
};
/**
* Splits the specified range at it's start/end points.
*
* @param {Range/RangeObject} rng Range to split.
* @return {Object} Range position object.
*/
/* this.split = function(rng) {
var startContainer = rng.startContainer,
startOffset = rng.startOffset,
endContainer = rng.endContainer,
endOffset = rng.endOffset;
function splitText(node, offset) {
if (offset == node.nodeValue.length)
node.appendData(INVISIBLE_CHAR);
node = node.splitText(offset);
if (node.nodeValue === INVISIBLE_CHAR)
node.nodeValue = '';
return node;
};
// Handle single text node
if (startContainer == endContainer) {
if (startContainer.nodeType == 3) {
if (startOffset != 0)
startContainer = endContainer = splitText(startContainer, startOffset);
if (endOffset - startOffset != startContainer.nodeValue.length)
splitText(startContainer, endOffset - startOffset);
}
} else {
// Split startContainer text node if needed
if (startContainer.nodeType == 3 && startOffset != 0) {
startContainer = splitText(startContainer, startOffset);
startOffset = 0;
}
// Split endContainer text node if needed
if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
endContainer = splitText(endContainer, endOffset).previousSibling;
endOffset = endContainer.nodeValue.length;
}
}
return {
startContainer : startContainer,
startOffset : startOffset,
endContainer : endContainer,
endOffset : endOffset
};
};
*/
};
/**
* Compares two ranges and checks if they are equal.
*
* @static
* @param {DOMRange} rng1 First range to compare.
* @param {DOMRange} rng2 First range to compare.
* @return {Boolean} true/false if the ranges are equal.
*/
tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
if (rng1 && rng2) {
// Compare native IE ranges
if (rng1.item || rng1.duplicate) {
// Both are control ranges and the selected element matches
if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
return true;
// Both are text ranges and the range matches
if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
return true;
} else {
// Compare w3c ranges
return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
}
}
return false;
};
})(tinymce);
@@ -0,0 +1,185 @@
/**
* Schema.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function() {
var transitional = {};
/**
* Unpacks the specified lookup and string data it will also parse it into an object
* map with sub object for it's children. This will later also include the attributes.
*/
function unpack(lookup, data) {
var key;
function replace(value) {
return value.replace(/[A-Z]+/g, function(key) {
return replace(lookup[key]);
});
};
// Unpack lookup
for (key in lookup) {
if (lookup.hasOwnProperty(key))
lookup[key] = replace(lookup[key]);
}
// Unpack and parse data into object map
replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]/g, function(str, name, children) {
var i, map = {};
children = children.split(/\|/);
for (i = children.length - 1; i >= 0; i--)
map[children[i]] = 1;
transitional[name] = map;
});
};
// This is the XHTML 1.0 transitional elements with it's children packed to reduce it's size
// we will later include the attributes here and use it as a default for valid elements but it
// requires us to rewrite the serializer engine
unpack({
Z : '#|H|K|N|O|P',
Y : '#|X|form|R|Q',
X : 'p|T|div|U|W|isindex|fieldset|table',
W : 'pre|hr|blockquote|address|center|noframes',
U : 'ul|ol|dl|menu|dir',
ZC : '#|p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
T : 'h1|h2|h3|h4|h5|h6',
ZB : '#|X|S|Q',
S : 'R|P',
ZA : '#|a|G|J|M|O|P',
R : '#|a|H|K|N|O',
Q : 'noscript|P',
P : 'ins|del|script',
O : 'input|select|textarea|label|button',
N : 'M|L',
M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
L : 'sub|sup',
K : 'J|I',
J : 'tt|i|b|u|s|strike',
I : 'big|small|font|basefont',
H : 'G|F',
G : 'br|span|bdo',
F : 'object|applet|img|map|iframe'
}, 'script[]' +
'style[]' +
'object[#|param|X|form|a|H|K|N|O|Q]' +
'param[]' +
'p[S]' +
'a[Z]' +
'br[]' +
'span[S]' +
'bdo[S]' +
'applet[#|param|X|form|a|H|K|N|O|Q]' +
'h1[S]' +
'img[]' +
'map[X|form|Q|area]' +
'h2[S]' +
'iframe[#|X|form|a|H|K|N|O|Q]' +
'h3[S]' +
'tt[S]' +
'i[S]' +
'b[S]' +
'u[S]' +
's[S]' +
'strike[S]' +
'big[S]' +
'small[S]' +
'font[S]' +
'basefont[]' +
'em[S]' +
'strong[S]' +
'dfn[S]' +
'code[S]' +
'q[S]' +
'samp[S]' +
'kbd[S]' +
'var[S]' +
'cite[S]' +
'abbr[S]' +
'acronym[S]' +
'sub[S]' +
'sup[S]' +
'input[]' +
'select[optgroup|option]' +
'optgroup[option]' +
'option[]' +
'textarea[]' +
'label[S]' +
'button[#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' +
'h4[S]' +
'ins[#|X|form|a|H|K|N|O|Q]' +
'h5[S]' +
'del[#|X|form|a|H|K|N|O|Q]' +
'h6[S]' +
'div[#|X|form|a|H|K|N|O|Q]' +
'ul[li]' +
'li[#|X|form|a|H|K|N|O|Q]' +
'ol[li]' +
'dl[dt|dd]' +
'dt[S]' +
'dd[#|X|form|a|H|K|N|O|Q]' +
'menu[li]' +
'dir[li]' +
'pre[ZA]' +
'hr[]' +
'blockquote[#|X|form|a|H|K|N|O|Q]' +
'address[S|p]' +
'center[#|X|form|a|H|K|N|O|Q]' +
'noframes[#|X|form|a|H|K|N|O|Q]' +
'isindex[]' +
'fieldset[#|legend|X|form|a|H|K|N|O|Q]' +
'legend[S]' +
'table[caption|col|colgroup|thead|tfoot|tbody|tr]' +
'caption[S]' +
'col[]' +
'colgroup[col]' +
'thead[tr]' +
'tr[th|td]' +
'th[#|X|form|a|H|K|N|O|Q]' +
'form[#|X|a|H|K|N|O|Q]' +
'noscript[#|X|form|a|H|K|N|O|Q]' +
'td[#|X|form|a|H|K|N|O|Q]' +
'tfoot[tr]' +
'tbody[tr]' +
'area[]' +
'base[]' +
'body[#|X|form|a|H|K|N|O|Q]'
);
/**
* Schema validator class.
*
* @class tinymce.dom.Schema
* @example
* if (tinymce.activeEditor.schema.isValid('p', 'span'))
* alert('span is valid child of p.');
*/
tinymce.dom.Schema = function() {
var t = this, elements = transitional;
/**
* Returns true/false if the specified element and optionally it's child is valid or not
* according to the XHTML transitional DTD.
*
* @method isValid
* @param {String} name Element name to check for.
* @param {String} child_name Element child name to check for.
* @return {boolean} true/false if the element is valid or not.
*/
t.isValid = function(name, child_name) {
var element = elements[name];
return !!(element && (!child_name || element[child_name]));
};
};
})();
@@ -0,0 +1,239 @@
/**
* ScriptLoader.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
tinymce.dom.ScriptLoader = function(settings) {
var QUEUED = 0,
LOADING = 1,
LOADED = 2,
states = {},
queue = [],
scriptLoadedCallbacks = {},
queueLoadedCallbacks = [],
loading = 0,
undefined;
/**
* Loads a specific script directly without adding it to the load queue.
*
* @method load
* @param {String} url Absolute URL to script to add.
* @param {function} callback Optional callback function to execute ones this script gets loaded.
* @param {Object} scope Optional scope to execute callback in.
*/
function loadScript(url, callback) {
var t = this, dom = tinymce.DOM, elm, uri, loc, id;
// Execute callback when script is loaded
function done() {
dom.remove(id);
if (elm)
elm.onreadystatechange = elm.onload = elm = null;
callback();
};
id = dom.uniqueId();
if (tinymce.isIE6) {
uri = new tinymce.util.URI(url);
loc = location;
// If script is from same domain and we
// use IE 6 then use XHR since it's more reliable
if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol) {
tinymce.util.XHR.send({
url : tinymce._addVer(uri.getURI()),
success : function(content) {
// Create new temp script element
var script = dom.create('script', {
type : 'text/javascript'
});
// Evaluate script in global scope
script.text = content;
document.getElementsByTagName('head')[0].appendChild(script);
dom.remove(script);
done();
}
});
return;
}
}
// Create new script element
elm = dom.create('script', {
id : id,
type : 'text/javascript',
src : tinymce._addVer(url)
});
// Add onload and readystate listeners
elm.onload = done;
elm.onreadystatechange = function() {
var state = elm.readyState;
// Loaded state is passed on IE 6 however there
// are known issues with this method but we can't use
// XHR in a cross domain loading
if (state == 'complete' || state == 'loaded')
done();
};
// Most browsers support this feature so we report errors
// for those at least to help users track their missing plugins etc
// todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
/*elm.onerror = function() {
alert('Failed to load: ' + url);
};*/
// Add script to document
(document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
};
/**
* Returns true/false if a script has been loaded or not.
*
* @method isDone
* @param {String} url URL to check for.
* @return [Boolean} true/false if the URL is loaded.
*/
this.isDone = function(url) {
return states[url] == LOADED;
};
/**
* Marks a specific script to be loaded. This can be useful if a script got loaded outside
* the script loader or to skip it from loading some script.
*
* @method markDone
* @param {string} u Absolute URL to the script to mark as loaded.
*/
this.markDone = function(url) {
states[url] = LOADED;
};
/**
* Adds a specific script to the load queue of the script loader.
*
* @method add
* @param {String} url Absolute URL to script to add.
* @param {function} callback Optional callback function to execute ones this script gets loaded.
* @param {Object} scope Optional scope to execute callback in.
*/
this.add = this.load = function(url, callback, scope) {
var item, state = states[url];
// Add url to load queue
if (state == undefined) {
queue.push(url);
states[url] = QUEUED;
}
if (callback) {
// Store away callback for later execution
if (!scriptLoadedCallbacks[url])
scriptLoadedCallbacks[url] = [];
scriptLoadedCallbacks[url].push({
func : callback,
scope : scope || this
});
}
};
/**
* Starts the loading of the queue.
*
* @method loadQueue
* @param {function} callback Optional callback to execute when all queued items are loaded.
* @param {Object} scope Optional scope to execute the callback in.
*/
this.loadQueue = function(callback, scope) {
this.loadScripts(queue, callback, scope);
};
/**
* Loads the specified queue of files and executes the callback ones they are loaded.
* This method is generally not used outside this class but it might be useful in some scenarios.
*
* @method loadScripts
* @param {Array} scripts Array of queue items to load.
* @param {function} callback Optional callback to execute ones all items are loaded.
* @param {Object} scope Optional scope to execute callback in.
*/
this.loadScripts = function(scripts, callback, scope) {
var loadScripts;
function execScriptLoadedCallbacks(url) {
// Execute URL callback functions
tinymce.each(scriptLoadedCallbacks[url], function(callback) {
callback.func.call(callback.scope);
});
scriptLoadedCallbacks[url] = undefined;
};
queueLoadedCallbacks.push({
func : callback,
scope : scope || this
});
loadScripts = function() {
var loadingScripts = tinymce.grep(scripts);
// Current scripts has been handled
scripts.length = 0;
// Load scripts that needs to be loaded
tinymce.each(loadingScripts, function(url) {
// Script is already loaded then execute script callbacks directly
if (states[url] == LOADED) {
execScriptLoadedCallbacks(url);
return;
}
// Is script not loading then start loading it
if (states[url] != LOADING) {
states[url] = LOADING;
loading++;
loadScript(url, function() {
states[url] = LOADED;
loading--;
execScriptLoadedCallbacks(url);
// Load more scripts if they where added by the recently loaded script
loadScripts();
});
}
});
// No scripts are currently loading then execute all pending queue loaded callbacks
if (!loading) {
tinymce.each(queueLoadedCallbacks, function(callback) {
callback.func.call(callback.scope);
});
queueLoadedCallbacks.length = 0;
}
};
loadScripts();
};
};
// Global script loader
tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
})(tinymce);
@@ -0,0 +1,766 @@
/**
* Selection.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
function trimNl(s) {
return s.replace(/[\n\r]+/g, '');
};
// Shorten names
var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
/**
* This class handles text and control selection it's an crossbrowser utility class.
* Consult the TinyMCE Wiki API for more details and examples on how to use this class.
* @class tinymce.dom.Selection
*/
tinymce.create('tinymce.dom.Selection', {
/**
* Constructs a new selection instance.
*
* @constructor
* @method Selection
* @param {tinymce.dom.DOMUtils} dom DOMUtils object reference.
* @param {Window} win Window to bind the selection object to.
* @param {tinymce.dom.Serializer} serializer DOM serialization class to use for getContent.
*/
Selection : function(dom, win, serializer) {
var t = this;
t.dom = dom;
t.win = win;
t.serializer = serializer;
// Add events
each([
'onBeforeSetContent',
'onBeforeGetContent',
'onSetContent',
'onGetContent'
], function(e) {
t[e] = new tinymce.util.Dispatcher(t);
});
// No W3C Range support
if (!t.win.getSelection)
t.tridentSel = new tinymce.dom.TridentSelection(t);
// Prevent leaks
tinymce.addUnload(t.destroy, t);
},
/**
* Returns the selected contents using the DOM serializer passed in to this class.
*
* @method getContent
* @param {Object} s Optional settings class with for example output format text or html.
* @return {String} Selected contents in for example HTML format.
*/
getContent : function(s) {
var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
s = s || {};
wb = wa = '';
s.get = true;
s.format = s.format || 'html';
t.onBeforeGetContent.dispatch(t, s);
if (s.format == 'text')
return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
if (r.cloneContents) {
n = r.cloneContents();
if (n)
e.appendChild(n);
} else if (is(r.item) || is(r.htmlText))
e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
else
e.innerHTML = r.toString();
// Keep whitespace before and after
if (/^\s/.test(e.innerHTML))
wb = ' ';
if (/\s+$/.test(e.innerHTML))
wa = ' ';
s.getInner = true;
s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
t.onGetContent.dispatch(t, s);
return s.content;
},
/**
* Sets the current selection to the specified content. If any contents is selected it will be replaced
* with the contents passed in to this function. If there is no selection the contents will be inserted
* where the caret is placed in the editor/page.
*
* @method setContent
* @param {String} h HTML contents to set could also be other formats depending on settings.
* @param {Object} s Optional settings object with for example data format.
*/
setContent : function(h, s) {
var t = this, r = t.getRng(), c, d = t.win.document;
s = s || {format : 'html'};
s.set = true;
h = s.content = t.dom.processHTML(h);
// Dispatch before set content event
t.onBeforeSetContent.dispatch(t, s);
h = s.content;
if (r.insertNode) {
// Make caret marker since insertNode places the caret in the beginning of text after insert
h += '<span id="__caret">_</span>';
// Delete and insert new node
if (r.startContainer == d && r.endContainer == d) {
// WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
d.body.innerHTML = h;
} else {
r.deleteContents();
if (d.body.childNodes.length == 0) {
d.body.innerHTML = h;
} else {
r.insertNode(r.createContextualFragment(h));
}
}
// Move to caret marker
c = t.dom.get('__caret');
// Make sure we wrap it compleatly, Opera fails with a simple select call
r = d.createRange();
r.setStartBefore(c);
r.setEndBefore(c);
t.setRng(r);
// Remove the caret position
t.dom.remove('__caret');
} else {
if (r.item) {
// Delete content and get caret text selection
d.execCommand('Delete', false, null);
r = t.getRng();
}
r.pasteHTML(h);
}
// Dispatch set content event
t.onSetContent.dispatch(t, s);
},
/**
* Returns the start element of a selection range. If the start is in a text
* node the parent element will be returned.
*
* @method getStart
* @return {Element} Start element of selection range.
*/
getStart : function() {
var rng = this.getRng(), startElement, parentElement, checkRng, node;
if (rng.duplicate || rng.item) {
// Control selection, return first item
if (rng.item)
return rng.item(0);
// Get start element
checkRng = rng.duplicate();
checkRng.collapse(1);
startElement = checkRng.parentElement();
// Check if range parent is inside the start element, then return the inner parent element
// This will fix issues when a single element is selected, IE would otherwise return the wrong start element
parentElement = node = rng.parentElement();
while (node = node.parentNode) {
if (node == startElement) {
startElement = parentElement;
break;
}
}
// If start element is body element try to move to the first child if it exists
if (startElement && startElement.nodeName == 'BODY')
return startElement.firstChild || startElement;
return startElement;
} else {
startElement = rng.startContainer;
if (startElement.nodeType == 1 && startElement.hasChildNodes())
startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
if (startElement && startElement.nodeType == 3)
return startElement.parentNode;
return startElement;
}
},
/**
* Returns the end element of a selection range. If the end is in a text
* node the parent element will be returned.
*
* @method getEnd
* @return {Element} End element of selection range.
*/
getEnd : function() {
var t = this, r = t.getRng(), e, eo;
if (r.duplicate || r.item) {
if (r.item)
return r.item(0);
r = r.duplicate();
r.collapse(0);
e = r.parentElement();
if (e && e.nodeName == 'BODY')
return e.lastChild || e;
return e;
} else {
e = r.endContainer;
eo = r.endOffset;
if (e.nodeType == 1 && e.hasChildNodes())
e = e.childNodes[eo > 0 ? eo - 1 : eo];
if (e && e.nodeType == 3)
return e.parentNode;
return e;
}
},
/**
* Returns a bookmark location for the current selection. This bookmark object
* can then be used to restore the selection after some content modification to the document.
*
* @method getBookmark
* @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
* @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
* @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
*/
getBookmark : function(type, normalized) {
var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
function findIndex(name, element) {
var index = 0;
each(dom.select(name), function(node, i) {
if (node == element)
index = i;
});
return index;
};
if (type == 2) {
function getLocation() {
var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
function getPoint(rng, start) {
var container = rng[start ? 'startContainer' : 'endContainer'],
offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
if (container.nodeType == 3) {
if (normalized) {
for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
offset += node.nodeValue.length;
}
point.push(offset);
} else {
childNodes = container.childNodes;
if (offset >= childNodes.length && childNodes.length) {
after = 1;
offset = Math.max(0, childNodes.length - 1);
}
point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
}
for (; container && container != root; container = container.parentNode)
point.push(t.dom.nodeIndex(container, normalized));
return point;
};
bookmark.start = getPoint(rng, true);
if (!t.isCollapsed())
bookmark.end = getPoint(rng);
return bookmark;
};
return getLocation();
}
// Handle simple range
if (type)
return {rng : t.getRng()};
rng = t.getRng();
id = dom.uniqueId();
collapsed = tinyMCE.activeEditor.selection.isCollapsed();
styles = 'overflow:hidden;line-height:0px';
// Explorer method
if (rng.duplicate || rng.item) {
// Text selection
if (!rng.item) {
rng2 = rng.duplicate();
// Insert start marker
rng.collapse();
rng.pasteHTML('<span _mce_type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
// Insert end marker
if (!collapsed) {
rng2.collapse(false);
rng2.pasteHTML('<span _mce_type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
}
} else {
// Control selection
element = rng.item(0);
name = element.nodeName;
return {name : name, index : findIndex(name, element)};
}
} else {
element = t.getNode();
name = element.nodeName;
if (name == 'IMG')
return {name : name, index : findIndex(name, element)};
// W3C method
rng2 = rng.cloneRange();
// Insert end marker
if (!collapsed) {
rng2.collapse(false);
rng2.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_end', style : styles}, chr));
}
rng.collapse(true);
rng.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_start', style : styles}, chr));
}
t.moveToBookmark({id : id, keep : 1});
return {id : id};
},
/**
* Restores the selection to the specified bookmark.
*
* @method moveToBookmark
* @param {Object} bookmark Bookmark to restore selection from.
* @return {Boolean} true/false if it was successful or not.
*/
moveToBookmark : function(bookmark) {
var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
// Clear selection cache
if (t.tridentSel)
t.tridentSel.destroy();
if (bookmark) {
if (bookmark.start) {
rng = dom.createRng();
root = dom.getRoot();
function setEndPoint(start) {
var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
if (point) {
// Find container node
for (node = root, i = point.length - 1; i >= 1; i--) {
children = node.childNodes;
if (children.length)
node = children[point[i]];
}
// Set offset within container node
if (start)
rng.setStart(node, point[0]);
else
rng.setEnd(node, point[0]);
}
};
setEndPoint(true);
setEndPoint();
t.setRng(rng);
} else if (bookmark.id) {
function restoreEndPoint(suffix) {
var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
if (marker) {
node = marker.parentNode;
if (suffix == 'start') {
if (!keep) {
idx = dom.nodeIndex(marker);
} else {
node = marker.firstChild;
idx = 1;
}
startContainer = endContainer = node;
startOffset = endOffset = idx;
} else {
if (!keep) {
idx = dom.nodeIndex(marker);
} else {
node = marker.firstChild;
idx = 1;
}
endContainer = node;
endOffset = idx;
}
if (!keep) {
prev = marker.previousSibling;
next = marker.nextSibling;
// Remove all marker text nodes
each(tinymce.grep(marker.childNodes), function(node) {
if (node.nodeType == 3)
node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
});
// Remove marker but keep children if for example contents where inserted into the marker
// Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
while (marker = dom.get(bookmark.id + '_' + suffix))
dom.remove(marker, 1);
// If siblings are text nodes then merge them
if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3) {
idx = prev.nodeValue.length;
prev.appendData(next.nodeValue);
dom.remove(next);
if (suffix == 'start') {
startContainer = endContainer = prev;
startOffset = endOffset = idx;
} else {
endContainer = prev;
endOffset = idx;
}
}
}
}
};
function addBogus(node) {
// Adds a bogus BR element for empty block elements
// on non IE browsers just to have a place to put the caret
if (!isIE && dom.isBlock(node) && !node.innerHTML)
node.innerHTML = '<br _mce_bogus="1" />';
return node;
};
// Restore start/end points
restoreEndPoint('start');
restoreEndPoint('end');
rng = dom.createRng();
rng.setStart(addBogus(startContainer), startOffset);
rng.setEnd(addBogus(endContainer), endOffset);
t.setRng(rng);
} else if (bookmark.name) {
t.select(dom.select(bookmark.name)[bookmark.index]);
} else if (bookmark.rng)
t.setRng(bookmark.rng);
}
},
/**
* Selects the specified element. This will place the start and end of the selection range around the element.
*
* @method select
* @param {Element} node HMTL DOM element to select.
* @param {Boolean} content Optional bool state if the contents should be selected or not on non IE browser.
* @return {Element} Selected element the same element as the one that got passed in.
*/
select : function(node, content) {
var t = this, dom = t.dom, rng = dom.createRng(), idx;
idx = dom.nodeIndex(node);
rng.setStart(node.parentNode, idx);
rng.setEnd(node.parentNode, idx + 1);
// Find first/last text node or BR element
if (content) {
function setPoint(node, start) {
var walker = new tinymce.dom.TreeWalker(node, node);
do {
// Text node
if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
if (start)
rng.setStart(node, 0);
else
rng.setEnd(node, node.nodeValue.length);
return;
}
// BR element
if (node.nodeName == 'BR') {
if (start)
rng.setStartBefore(node);
else
rng.setEndBefore(node);
return;
}
} while (node = (start ? walker.next() : walker.prev()));
};
setPoint(node, 1);
setPoint(node);
}
t.setRng(rng);
return node;
},
/**
* Returns true/false if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection.
*
* @method isCollapsed
* @return {Boolean} true/false state if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection.
*/
isCollapsed : function() {
var t = this, r = t.getRng(), s = t.getSel();
if (!r || r.item)
return false;
if (r.compareEndPoints)
return r.compareEndPoints('StartToEnd', r) === 0;
return !s || r.collapsed;
},
/**
* Collapse the selection to start or end of range.
*
* @method collapse
* @param {Boolean} b Optional boolean state if to collapse to end or not. Defaults to start.
*/
collapse : function(b) {
var t = this, r = t.getRng(), n;
// Control range on IE
if (r.item) {
n = r.item(0);
r = this.win.document.body.createTextRange();
r.moveToElementText(n);
}
r.collapse(!!b);
t.setRng(r);
},
/**
* Returns the browsers internal selection object.
*
* @method getSel
* @return {Selection} Internal browser selection object.
*/
getSel : function() {
var t = this, w = this.win;
return w.getSelection ? w.getSelection() : w.document.selection;
},
/**
* Returns the browsers internal range object.
*
* @method getRng
* @param {Boolean} w3c Forces a compatible W3C range on IE.
* @return {Range} Internal browser range object.
*/
getRng : function(w3c) {
var t = this, s, r;
// Found tridentSel object then we need to use that one
if (w3c && t.tridentSel)
return t.tridentSel.getRangeAt(0);
try {
if (s = t.getSel())
r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : t.win.document.createRange());
} catch (ex) {
// IE throws unspecified error here if TinyMCE is placed in a frame/iframe
}
// No range found then create an empty one
// This can occur when the editor is placed in a hidden container element on Gecko
// Or on IE when there was an exception
if (!r)
r = t.win.document.createRange ? t.win.document.createRange() : t.win.document.body.createTextRange();
if (t.selectedRange && t.explicitRange) {
if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
// Safari, Opera and Chrome only ever select text which causes the range to change.
// This lets us use the originally set range if the selection hasn't been changed by the user.
r = t.explicitRange;
} else {
t.selectedRange = null;
t.explicitRange = null;
}
}
return r;
},
/**
* Changes the selection to the specified DOM range.
*
* @method setRng
* @param {Range} r Range to select.
*/
setRng : function(r) {
var s, t = this;
if (!t.tridentSel) {
s = t.getSel();
if (s) {
t.explicitRange = r;
s.removeAllRanges();
s.addRange(r);
t.selectedRange = s.getRangeAt(0);
}
} else {
// Is W3C Range
if (r.cloneRange) {
t.tridentSel.addRange(r);
return;
}
// Is IE specific range
try {
r.select();
} catch (ex) {
// Needed for some odd IE bug #1843306
}
}
},
/**
* Sets the current selection to the specified DOM element.
*
* @method setNode
* @param {Element} n Element to set as the contents of the selection.
* @return {Element} Returns the element that got passed in.
*/
setNode : function(n) {
var t = this;
t.setContent(t.dom.getOuterHTML(n));
return n;
},
/**
* Returns the currently selected element or the common ancestor element for both start and end of the selection.
*
* @method getNode
* @return {Element} Currently selected element or common ancestor element.
*/
getNode : function() {
var t = this, rng = t.getRng(), sel = t.getSel(), elm;
if (rng.setStart) {
// Range maybe lost after the editor is made visible again
if (!rng)
return t.dom.getRoot();
elm = rng.commonAncestorContainer;
// Handle selection a image or other control like element such as anchors
if (!rng.collapsed) {
if (rng.startContainer == rng.endContainer) {
if (rng.startOffset - rng.endOffset < 2) {
if (rng.startContainer.hasChildNodes())
elm = rng.startContainer.childNodes[rng.startOffset];
}
}
// If the anchor node is a element instead of a text node then return this element
if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
return sel.anchorNode.childNodes[sel.anchorOffset];
}
if (elm && elm.nodeType == 3)
return elm.parentNode;
return elm;
}
return rng.item ? rng.item(0) : rng.parentElement();
},
getSelectedBlocks : function(st, en) {
var t = this, dom = t.dom, sb, eb, n, bl = [];
sb = dom.getParent(st || t.getStart(), dom.isBlock);
eb = dom.getParent(en || t.getEnd(), dom.isBlock);
if (sb)
bl.push(sb);
if (sb && eb && sb != eb) {
n = sb;
while ((n = n.nextSibling) && n != eb) {
if (dom.isBlock(n))
bl.push(n);
}
}
if (eb && sb != eb)
bl.push(eb);
return bl;
},
destroy : function(s) {
var t = this;
t.win = null;
if (t.tridentSel)
t.tridentSel.destroy();
// Manual destroy then remove unload handler
if (!s)
tinymce.removeUnload(t.destroy);
}
});
})(tinymce);
@@ -0,0 +1,946 @@
/**
* Serializer.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
// Shorten names
var extend = tinymce.extend, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, isIE = tinymce.isIE, isGecko = tinymce.isGecko;
function wildcardToRE(s) {
return s.replace(/([?+*])/g, '.$1');
};
/**
* This class is used to serialize DOM trees into a string.
* Consult the TinyMCE Wiki API for more details and examples on how to use this class.
* @class tinymce.dom.Serializer
*/
tinymce.create('tinymce.dom.Serializer', {
/**
* Constucts a new DOM serializer class.
*
* @constructor
* @method Serializer
* @param {Object} s Optional name/Value collection of settings for the serializer.
*/
Serializer : function(s) {
var t = this;
t.key = 0;
t.onPreProcess = new Dispatcher(t);
t.onPostProcess = new Dispatcher(t);
try {
t.writer = new tinymce.dom.XMLWriter();
} catch (ex) {
// IE might throw exception if ActiveX is disabled so we then switch to the slightly slower StringWriter
t.writer = new tinymce.dom.StringWriter();
}
// Default settings
t.settings = s = extend({
dom : tinymce.DOM,
valid_nodes : 0,
node_filter : 0,
attr_filter : 0,
invalid_attrs : /^(_mce_|_moz_|sizset|sizcache)/,
closed : /^(br|hr|input|meta|img|link|param|area)$/,
entity_encoding : 'named',
entities : '160,nbsp,161,iexcl,162,cent,163,pound,164,curren,165,yen,166,brvbar,167,sect,168,uml,169,copy,170,ordf,171,laquo,172,not,173,shy,174,reg,175,macr,176,deg,177,plusmn,178,sup2,179,sup3,180,acute,181,micro,182,para,183,middot,184,cedil,185,sup1,186,ordm,187,raquo,188,frac14,189,frac12,190,frac34,191,iquest,192,Agrave,193,Aacute,194,Acirc,195,Atilde,196,Auml,197,Aring,198,AElig,199,Ccedil,200,Egrave,201,Eacute,202,Ecirc,203,Euml,204,Igrave,205,Iacute,206,Icirc,207,Iuml,208,ETH,209,Ntilde,210,Ograve,211,Oacute,212,Ocirc,213,Otilde,214,Ouml,215,times,216,Oslash,217,Ugrave,218,Uacute,219,Ucirc,220,Uuml,221,Yacute,222,THORN,223,szlig,224,agrave,225,aacute,226,acirc,227,atilde,228,auml,229,aring,230,aelig,231,ccedil,232,egrave,233,eacute,234,ecirc,235,euml,236,igrave,237,iacute,238,icirc,239,iuml,240,eth,241,ntilde,242,ograve,243,oacute,244,ocirc,245,otilde,246,ouml,247,divide,248,oslash,249,ugrave,250,uacute,251,ucirc,252,uuml,253,yacute,254,thorn,255,yuml,402,fnof,913,Alpha,914,Beta,915,Gamma,916,Delta,917,Epsilon,918,Zeta,919,Eta,920,Theta,921,Iota,922,Kappa,923,Lambda,924,Mu,925,Nu,926,Xi,927,Omicron,928,Pi,929,Rho,931,Sigma,932,Tau,933,Upsilon,934,Phi,935,Chi,936,Psi,937,Omega,945,alpha,946,beta,947,gamma,948,delta,949,epsilon,950,zeta,951,eta,952,theta,953,iota,954,kappa,955,lambda,956,mu,957,nu,958,xi,959,omicron,960,pi,961,rho,962,sigmaf,963,sigma,964,tau,965,upsilon,966,phi,967,chi,968,psi,969,omega,977,thetasym,978,upsih,982,piv,8226,bull,8230,hellip,8242,prime,8243,Prime,8254,oline,8260,frasl,8472,weierp,8465,image,8476,real,8482,trade,8501,alefsym,8592,larr,8593,uarr,8594,rarr,8595,darr,8596,harr,8629,crarr,8656,lArr,8657,uArr,8658,rArr,8659,dArr,8660,hArr,8704,forall,8706,part,8707,exist,8709,empty,8711,nabla,8712,isin,8713,notin,8715,ni,8719,prod,8721,sum,8722,minus,8727,lowast,8730,radic,8733,prop,8734,infin,8736,ang,8743,and,8744,or,8745,cap,8746,cup,8747,int,8756,there4,8764,sim,8773,cong,8776,asymp,8800,ne,8801,equiv,8804,le,8805,ge,8834,sub,8835,sup,8836,nsub,8838,sube,8839,supe,8853,oplus,8855,otimes,8869,perp,8901,sdot,8968,lceil,8969,rceil,8970,lfloor,8971,rfloor,9001,lang,9002,rang,9674,loz,9824,spades,9827,clubs,9829,hearts,9830,diams,338,OElig,339,oelig,352,Scaron,353,scaron,376,Yuml,710,circ,732,tilde,8194,ensp,8195,emsp,8201,thinsp,8204,zwnj,8205,zwj,8206,lrm,8207,rlm,8211,ndash,8212,mdash,8216,lsquo,8217,rsquo,8218,sbquo,8220,ldquo,8221,rdquo,8222,bdquo,8224,dagger,8225,Dagger,8240,permil,8249,lsaquo,8250,rsaquo,8364,euro',
valid_elements : '*[*]',
extended_valid_elements : 0,
invalid_elements : 0,
fix_table_elements : 1,
fix_list_elements : true,
fix_content_duplication : true,
convert_fonts_to_spans : false,
font_size_classes : 0,
apply_source_formatting : 0,
indent_mode : 'simple',
indent_char : '\t',
indent_levels : 1,
remove_linebreaks : 1,
remove_redundant_brs : 1,
element_format : 'xhtml'
}, s);
t.dom = s.dom;
t.schema = s.schema;
// Use raw entities if no entities are defined
if (s.entity_encoding == 'named' && !s.entities)
s.entity_encoding = 'raw';
if (s.remove_redundant_brs) {
t.onPostProcess.add(function(se, o) {
// Remove single BR at end of block elements since they get rendered
o.content = o.content.replace(/(<br \/>\s*)+<\/(p|h[1-6]|div|li)>/gi, function(a, b, c) {
// Check if it's a single element
if (/^<br \/>\s*<\//.test(a))
return '</' + c + '>';
return a;
});
});
}
// Remove XHTML element endings i.e. produce crap :) XHTML is better
if (s.element_format == 'html') {
t.onPostProcess.add(function(se, o) {
o.content = o.content.replace(/<([^>]+) \/>/g, '<$1>');
});
}
if (s.fix_list_elements) {
t.onPreProcess.add(function(se, o) {
var nl, x, a = ['ol', 'ul'], i, n, p, r = /^(OL|UL)$/, np;
function prevNode(e, n) {
var a = n.split(','), i;
while ((e = e.previousSibling) != null) {
for (i=0; i<a.length; i++) {
if (e.nodeName == a[i])
return e;
}
}
return null;
};
for (x=0; x<a.length; x++) {
nl = t.dom.select(a[x], o.node);
for (i=0; i<nl.length; i++) {
n = nl[i];
p = n.parentNode;
if (r.test(p.nodeName)) {
np = prevNode(n, 'LI');
if (!np) {
np = t.dom.create('li');
np.innerHTML = '&nbsp;';
np.appendChild(n);
p.insertBefore(np, p.firstChild);
} else
np.appendChild(n);
}
}
}
});
}
if (s.fix_table_elements) {
t.onPreProcess.add(function(se, o) {
// Since Opera will crash if you attach the node to a dynamic document we need to brrowser sniff a specific build
// so Opera users with an older version will have to live with less compaible output not much we can do here
if (!tinymce.isOpera || opera.buildNumber() >= 1767) {
each(t.dom.select('p table', o.node).reverse(), function(n) {
var parent = t.dom.getParent(n.parentNode, 'table,p');
if (parent.nodeName != 'TABLE') {
try {
t.dom.split(parent, n);
} catch (ex) {
// IE can sometimes fire an unknown runtime error so we just ignore it
}
}
});
}
});
}
},
/**
* Sets a list of entities to use for the named entity encoded.
*
* @method setEntities
* @param {String} s List of entities in the following format: number,name,....
*/
setEntities : function(s) {
var t = this, a, i, l = {}, v;
// No need to setup more than once
if (t.entityLookup)
return;
// Build regex and lookup array
a = s.split(',');
for (i = 0; i < a.length; i += 2) {
v = a[i];
// Don't add default &amp; &quot; etc.
if (v == 34 || v == 38 || v == 60 || v == 62)
continue;
l[String.fromCharCode(a[i])] = a[i + 1];
v = parseInt(a[i]).toString(16);
}
t.entityLookup = l;
},
/**
* Sets the valid elements rules of the serializer this enables you to specify things like what elements should be
* outputted and what attributes specific elements might have.
* Consult the Wiki for more details on this format.
*
* @method setRules
* @param {String} s Valid elements rules string.
*/
setRules : function(s) {
var t = this;
t._setup();
t.rules = {};
t.wildRules = [];
t.validElements = {};
return t.addRules(s);
},
/**
* Adds valid elements rules to the serializer this enables you to specify things like what elements should be
* outputted and what attributes specific elements might have.
* Consult the Wiki for more details on this format.
*
* @method addRules
* @param {String} s Valid elements rules string to add.
*/
addRules : function(s) {
var t = this, dr;
if (!s)
return;
t._setup();
each(s.split(','), function(s) {
var p = s.split(/\[|\]/), tn = p[0].split('/'), ra, at, wat, va = [];
// Extend with default rules
if (dr)
at = tinymce.extend([], dr.attribs);
// Parse attributes
if (p.length > 1) {
each(p[1].split('|'), function(s) {
var ar = {}, i;
at = at || [];
// Parse attribute rule
s = s.replace(/::/g, '~');
s = /^([!\-])?([\w*.?~_\-]+|)([=:<])?(.+)?$/.exec(s);
s[2] = s[2].replace(/~/g, ':');
// Add required attributes
if (s[1] == '!') {
ra = ra || [];
ra.push(s[2]);
}
// Remove inherited attributes
if (s[1] == '-') {
for (i = 0; i <at.length; i++) {
if (at[i].name == s[2]) {
at.splice(i, 1);
return;
}
}
}
switch (s[3]) {
// Add default attrib values
case '=':
ar.defaultVal = s[4] || '';
break;
// Add forced attrib values
case ':':
ar.forcedVal = s[4];
break;
// Add validation values
case '<':
ar.validVals = s[4].split('?');
break;
}
if (/[*.?]/.test(s[2])) {
wat = wat || [];
ar.nameRE = new RegExp('^' + wildcardToRE(s[2]) + '$');
wat.push(ar);
} else {
ar.name = s[2];
at.push(ar);
}
va.push(s[2]);
});
}
// Handle element names
each(tn, function(s, i) {
var pr = s.charAt(0), x = 1, ru = {};
// Extend with default rule data
if (dr) {
if (dr.noEmpty)
ru.noEmpty = dr.noEmpty;
if (dr.fullEnd)
ru.fullEnd = dr.fullEnd;
if (dr.padd)
ru.padd = dr.padd;
}
// Handle prefixes
switch (pr) {
case '-':
ru.noEmpty = true;
break;
case '+':
ru.fullEnd = true;
break;
case '#':
ru.padd = true;
break;
default:
x = 0;
}
tn[i] = s = s.substring(x);
t.validElements[s] = 1;
// Add element name or element regex
if (/[*.?]/.test(tn[0])) {
ru.nameRE = new RegExp('^' + wildcardToRE(tn[0]) + '$');
t.wildRules = t.wildRules || {};
t.wildRules.push(ru);
} else {
ru.name = tn[0];
// Store away default rule
if (tn[0] == '@')
dr = ru;
t.rules[s] = ru;
}
ru.attribs = at;
if (ra)
ru.requiredAttribs = ra;
if (wat) {
// Build valid attributes regexp
s = '';
each(va, function(v) {
if (s)
s += '|';
s += '(' + wildcardToRE(v) + ')';
});
ru.validAttribsRE = new RegExp('^' + s.toLowerCase() + '$');
ru.wildAttribs = wat;
}
});
});
// Build valid elements regexp
s = '';
each(t.validElements, function(v, k) {
if (s)
s += '|';
if (k != '@')
s += k;
});
t.validElementsRE = new RegExp('^(' + wildcardToRE(s.toLowerCase()) + ')$');
//console.debug(t.validElementsRE.toString());
//console.dir(t.rules);
//console.dir(t.wildRules);
},
/**
* Finds a rule object by name.
*
* @method findRule
* @param {String} n Name to look for in rules collection.
* @return {Object} Rule object found or null if it wasn't found.
*/
findRule : function(n) {
var t = this, rl = t.rules, i, r;
t._setup();
// Exact match
r = rl[n];
if (r)
return r;
// Try wildcards
rl = t.wildRules;
for (i = 0; i < rl.length; i++) {
if (rl[i].nameRE.test(n))
return rl[i];
}
return null;
},
/**
* Finds an attribute rule object by name.
*
* @method findAttribRule
* @param {Object} ru Rule object to search though.
* @param {String} n Name of the rule to retrive.
* @return {Object} Rule object of the specified attribute.
*/
findAttribRule : function(ru, n) {
var i, wa = ru.wildAttribs;
for (i = 0; i < wa.length; i++) {
if (wa[i].nameRE.test(n))
return wa[i];
}
return null;
},
/**
* Serializes the specified node into a HTML string.
*
* @method serialize
* @param {Element} n Element/Node to serialize.
* @param {Object} o Object to add serialized contents to, this object will also be passed to the event listeners.
* @return {String} Serialized HTML contents.
*/
serialize : function(n, o) {
var h, t = this, doc, oldDoc, impl, selected;
t._setup();
o = o || {};
o.format = o.format || 'html';
t.processObj = o;
// IE looses the selected attribute on option elements so we need to store it
// See: http://support.microsoft.com/kb/829907
if (isIE) {
selected = [];
each(n.getElementsByTagName('option'), function(n) {
var v = t.dom.getAttrib(n, 'selected');
selected.push(v ? v : null);
});
}
n = n.cloneNode(true);
// IE looses the selected attribute on option elements so we need to restore it
if (isIE) {
each(n.getElementsByTagName('option'), function(n, i) {
t.dom.setAttrib(n, 'selected', selected[i]);
});
}
// Nodes needs to be attached to something in WebKit/Opera
// Older builds of Opera crashes if you attach the node to an document created dynamically
// and since we can't feature detect a crash we need to sniff the acutal build number
// This fix will make DOM ranges and make Sizzle happy!
impl = n.ownerDocument.implementation;
if (impl.createHTMLDocument && (tinymce.isOpera && opera.buildNumber() >= 1767)) {
// Create an empty HTML document
doc = impl.createHTMLDocument("");
// Add the element or it's children if it's a body element to the new document
each(n.nodeName == 'BODY' ? n.childNodes : [n], function(node) {
doc.body.appendChild(doc.importNode(node, true));
});
// Grab first child or body element for serialization
if (n.nodeName != 'BODY')
n = doc.body.firstChild;
else
n = doc.body;
// set the new document in DOMUtils so createElement etc works
oldDoc = t.dom.doc;
t.dom.doc = doc;
}
t.key = '' + (parseInt(t.key) + 1);
// Pre process
if (!o.no_events) {
o.node = n;
t.onPreProcess.dispatch(t, o);
}
// Serialize HTML DOM into a string
t.writer.reset();
t._info = o;
t._serializeNode(n, o.getInner);
// Post process
o.content = t.writer.getContent();
// Restore the old document if it was changed
if (oldDoc)
t.dom.doc = oldDoc;
if (!o.no_events)
t.onPostProcess.dispatch(t, o);
t._postProcess(o);
o.node = null;
return tinymce.trim(o.content);
},
// Internal functions
/**
* Indents the specified content object.
*
* @param {Object} o Content object to indent.
*/
_postProcess : function(o) {
var t = this, s = t.settings, h = o.content, sc = [], p;
if (o.format == 'html') {
// Protect some elements
p = t._protect({
content : h,
patterns : [
{pattern : /(<script[^>]*>)(.*?)(<\/script>)/g},
{pattern : /(<noscript[^>]*>)(.*?)(<\/noscript>)/g},
{pattern : /(<style[^>]*>)(.*?)(<\/style>)/g},
{pattern : /(<pre[^>]*>)(.*?)(<\/pre>)/g, encode : 1},
{pattern : /(<!--\[CDATA\[)(.*?)(\]\]-->)/g}
]
});
h = p.content;
// Entity encode
if (s.entity_encoding !== 'raw')
h = t._encode(h);
// Use BR instead of &nbsp; padded P elements inside editor and use <p>&nbsp;</p> outside editor
/* if (o.set)
h = h.replace(/<p>\s+(&nbsp;|&#160;|\u00a0|<br \/>)\s+<\/p>/g, '<p><br /></p>');
else
h = h.replace(/<p>\s+(&nbsp;|&#160;|\u00a0|<br \/>)\s+<\/p>/g, '<p>$1</p>');*/
// Since Gecko and Safari keeps whitespace in the DOM we need to
// remove it inorder to match other browsers. But I think Gecko and Safari is right.
// This process is only done when getting contents out from the editor.
if (!o.set) {
// We need to replace paragraph whitespace with an nbsp before indentation to keep the \u00a0 char
h = h.replace(/<p>\s+<\/p>|<p([^>]+)>\s+<\/p>/g, s.entity_encoding == 'numeric' ? '<p$1>&#160;</p>' : '<p$1>&nbsp;</p>');
if (s.remove_linebreaks) {
h = h.replace(/\r?\n|\r/g, ' ');
h = h.replace(/(<[^>]+>)\s+/g, '$1 ');
h = h.replace(/\s+(<\/[^>]+>)/g, ' $1');
h = h.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object) ([^>]+)>\s+/g, '<$1 $2>'); // Trim block start
h = h.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>\s+/g, '<$1>'); // Trim block start
h = h.replace(/\s+<\/(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>/g, '</$1>'); // Trim block end
}
// Simple indentation
if (s.apply_source_formatting && s.indent_mode == 'simple') {
// Add line breaks before and after block elements
h = h.replace(/<(\/?)(ul|hr|table|meta|link|tbody|tr|object|body|head|html|map)(|[^>]+)>\s*/g, '\n<$1$2$3>\n');
h = h.replace(/\s*<(p|h[1-6]|blockquote|div|title|style|pre|script|td|li|area)(|[^>]+)>/g, '\n<$1$2>');
h = h.replace(/<\/(p|h[1-6]|blockquote|div|title|style|pre|script|td|li)>\s*/g, '</$1>\n');
h = h.replace(/\n\n/g, '\n');
}
}
h = t._unprotect(h, p);
// Restore CDATA sections
h = h.replace(/<!--\[CDATA\[([\s\S]+)\]\]-->/g, '<![CDATA[$1]]>');
// Restore the \u00a0 character if raw mode is enabled
if (s.entity_encoding == 'raw')
h = h.replace(/<p>&nbsp;<\/p>|<p([^>]+)>&nbsp;<\/p>/g, '<p$1>\u00a0</p>');
// Restore noscript elements
h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
return '<noscript' + attribs + '>' + t.dom.decode(text.replace(/<!--|-->/g, '')) + '</noscript>';
});
}
o.content = h;
},
_serializeNode : function(n, inner) {
var t = this, s = t.settings, w = t.writer, hc, el, cn, i, l, a, at, no, v, nn, ru, ar, iv, closed, keep, type, scopeName;
if (!s.node_filter || s.node_filter(n)) {
switch (n.nodeType) {
case 1: // Element
if (n.hasAttribute ? n.hasAttribute('_mce_bogus') : n.getAttribute('_mce_bogus'))
return;
iv = keep = false;
hc = n.hasChildNodes();
nn = n.getAttribute('_mce_name') || n.nodeName.toLowerCase();
// Get internal type
type = n.getAttribute('_mce_type');
if (type) {
if (!t._info.cleanup) {
iv = true;
return;
} else
keep = 1;
}
// Add correct prefix on IE
if (isIE) {
scopeName = n.scopeName;
if (scopeName && scopeName !== 'HTML' && scopeName !== 'html')
nn = scopeName + ':' + nn;
}
// Remove mce prefix on IE needed for the abbr element
if (nn.indexOf('mce:') === 0)
nn = nn.substring(4);
// Check if valid
if (!keep) {
if (!t.validElementsRE || !t.validElementsRE.test(nn) || (t.invalidElementsRE && t.invalidElementsRE.test(nn)) || inner) {
iv = true;
break;
}
}
if (isIE) {
// Fix IE content duplication (DOM can have multiple copies of the same node)
if (s.fix_content_duplication) {
if (n._mce_serialized == t.key)
return;
n._mce_serialized = t.key;
}
// IE sometimes adds a / infront of the node name
if (nn.charAt(0) == '/')
nn = nn.substring(1);
} else if (isGecko) {
// Ignore br elements
if (n.nodeName === 'BR' && n.getAttribute('type') == '_moz')
return;
}
// Check if valid child
if (s.validate_children) {
if (t.elementName && !t.schema.isValid(t.elementName, nn)) {
iv = true;
break;
}
t.elementName = nn;
}
ru = t.findRule(nn);
// No valid rule for this element could be found then skip it
if (!ru) {
iv = true;
break;
}
nn = ru.name || nn;
closed = s.closed.test(nn);
// Skip empty nodes or empty node name in IE
if ((!hc && ru.noEmpty) || (isIE && !nn)) {
iv = true;
break;
}
// Check required
if (ru.requiredAttribs) {
a = ru.requiredAttribs;
for (i = a.length - 1; i >= 0; i--) {
if (this.dom.getAttrib(n, a[i]) !== '')
break;
}
// None of the required was there
if (i == -1) {
iv = true;
break;
}
}
w.writeStartElement(nn);
// Add ordered attributes
if (ru.attribs) {
for (i=0, at = ru.attribs, l = at.length; i<l; i++) {
a = at[i];
v = t._getAttrib(n, a);
if (v !== null)
w.writeAttribute(a.name, v);
}
}
// Add wild attributes
if (ru.validAttribsRE) {
at = t.dom.getAttribs(n);
for (i=at.length-1; i>-1; i--) {
no = at[i];
if (no.specified) {
a = no.nodeName.toLowerCase();
if (s.invalid_attrs.test(a) || !ru.validAttribsRE.test(a))
continue;
ar = t.findAttribRule(ru, a);
v = t._getAttrib(n, ar, a);
if (v !== null)
w.writeAttribute(a, v);
}
}
}
// Keep type attribute
if (type && keep)
w.writeAttribute('_mce_type', type);
// Write text from script
if (nn === 'script' && tinymce.trim(n.innerHTML)) {
w.writeText('// '); // Padd it with a comment so it will parse on older browsers
w.writeCDATA(n.innerHTML.replace(/<!--|-->|<\[CDATA\[|\]\]>/g, '')); // Remove comments and cdata stuctures
hc = false;
break;
}
// Padd empty nodes with a &nbsp;
if (ru.padd) {
// If it has only one bogus child, padd it anyway workaround for <td><br /></td> bug
if (hc && (cn = n.firstChild) && cn.nodeType === 1 && n.childNodes.length === 1) {
if (cn.hasAttribute ? cn.hasAttribute('_mce_bogus') : cn.getAttribute('_mce_bogus'))
w.writeText('\u00a0');
} else if (!hc)
w.writeText('\u00a0'); // No children then padd it
}
break;
case 3: // Text
// Check if valid child
if (s.validate_children && t.elementName && !t.schema.isValid(t.elementName, '#text'))
return;
return w.writeText(n.nodeValue);
case 4: // CDATA
return w.writeCDATA(n.nodeValue);
case 8: // Comment
return w.writeComment(n.nodeValue);
}
} else if (n.nodeType == 1)
hc = n.hasChildNodes();
if (hc && !closed) {
cn = n.firstChild;
while (cn) {
t._serializeNode(cn);
t.elementName = nn;
cn = cn.nextSibling;
}
}
// Write element end
if (!iv) {
if (!closed)
w.writeFullEndElement();
else
w.writeEndElement();
}
},
_protect : function(o) {
var t = this;
o.items = o.items || [];
function enc(s) {
return s.replace(/[\r\n\\]/g, function(c) {
if (c === '\n')
return '\\n';
else if (c === '\\')
return '\\\\';
return '\\r';
});
};
function dec(s) {
return s.replace(/\\[\\rn]/g, function(c) {
if (c === '\\n')
return '\n';
else if (c === '\\\\')
return '\\';
return '\r';
});
};
each(o.patterns, function(p) {
o.content = dec(enc(o.content).replace(p.pattern, function(x, a, b, c) {
b = dec(b);
if (p.encode)
b = t._encode(b);
o.items.push(b);
return a + '<!--mce:' + (o.items.length - 1) + '-->' + c;
}));
});
return o;
},
_unprotect : function(h, o) {
h = h.replace(/\<!--mce:([0-9]+)--\>/g, function(a, b) {
return o.items[parseInt(b)];
});
o.items = [];
return h;
},
_encode : function(h) {
var t = this, s = t.settings, l;
// Entity encode
if (s.entity_encoding !== 'raw') {
if (s.entity_encoding.indexOf('named') != -1) {
t.setEntities(s.entities);
l = t.entityLookup;
h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
var v;
if (v = l[a])
a = '&' + v + ';';
return a;
});
}
if (s.entity_encoding.indexOf('numeric') != -1) {
h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
return '&#' + a.charCodeAt(0) + ';';
});
}
}
return h;
},
_setup : function() {
var t = this, s = this.settings;
if (t.done)
return;
t.done = 1;
t.setRules(s.valid_elements);
t.addRules(s.extended_valid_elements);
if (s.invalid_elements)
t.invalidElementsRE = new RegExp('^(' + wildcardToRE(s.invalid_elements.replace(/,/g, '|').toLowerCase()) + ')$');
if (s.attrib_value_filter)
t.attribValueFilter = s.attribValueFilter;
},
_getAttrib : function(n, a, na) {
var i, v;
na = na || a.name;
if (a.forcedVal && (v = a.forcedVal)) {
if (v === '{$uid}')
return this.dom.uniqueId();
return v;
}
v = this.dom.getAttrib(n, na);
switch (na) {
case 'rowspan':
case 'colspan':
// Whats the point? Remove usless attribute value
if (v == '1')
v = '';
break;
}
if (this.attribValueFilter)
v = this.attribValueFilter(na, v, n);
if (a.validVals) {
for (i = a.validVals.length - 1; i >= 0; i--) {
if (v == a.validVals[i])
break;
}
if (i == -1)
return null;
}
if (v === '' && typeof(a.defaultVal) != 'undefined') {
v = a.defaultVal;
if (v === '{$uid}')
return this.dom.uniqueId();
return v;
} else {
// Remove internal mceItemXX classes when content is extracted from editor
if (na == 'class' && this.processObj.get)
v = v.replace(/\s?mceItem\w+\s?/g, '');
}
if (v === '')
return null;
return v;
}
});
})(tinymce);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,212 @@
/**
* StringWriter.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
/**
* This class writes nodes into a string.
* @class tinymce.dom.StringWriter
*/
tinymce.create('tinymce.dom.StringWriter', {
str : null,
tags : null,
count : 0,
settings : null,
indent : null,
/**
* Constructs a new StringWriter.
*
* @constructor
* @method StringWriter
* @param {Object} s Optional settings object.
*/
StringWriter : function(s) {
this.settings = tinymce.extend({
indent_char : ' ',
indentation : 0
}, s);
this.reset();
},
/**
* Resets the writer so it can be reused the contents of the writer is cleared.
*
* @method reset
*/
reset : function() {
this.indent = '';
this.str = "";
this.tags = [];
this.count = 0;
},
/**
* Writes the start of an element like for example: <tag.
*
* @method writeStartElement
* @param {String} n Name of element to write.
*/
writeStartElement : function(n) {
this._writeAttributesEnd();
this.writeRaw('<' + n);
this.tags.push(n);
this.inAttr = true;
this.count++;
this.elementCount = this.count;
},
/**
* Writes an attrubute like for example: myattr="valie"
*
* @method writeAttribute
* @param {String} n Attribute name to write.
* @param {String} v Attribute value to write.
*/
writeAttribute : function(n, v) {
var t = this;
t.writeRaw(" " + t.encode(n) + '="' + t.encode(v) + '"');
},
/**
* Write the end of a element. This will add a short end for elements with out children like for example a img element.
*
* @method writeEndElement
*/
writeEndElement : function() {
var n;
if (this.tags.length > 0) {
n = this.tags.pop();
if (this._writeAttributesEnd(1))
this.writeRaw('</' + n + '>');
if (this.settings.indentation > 0)
this.writeRaw('\n');
}
},
/**
* Writes the end of a element. This will add a full end to the element even if it didn't have any children.
*
* @method writeFullEndElement
*/
writeFullEndElement : function() {
if (this.tags.length > 0) {
this._writeAttributesEnd();
this.writeRaw('</' + this.tags.pop() + '>');
if (this.settings.indentation > 0)
this.writeRaw('\n');
}
},
/**
* Writes a text node value.
*
* @method writeText
* @param {String} v Value to append as a text node.
*/
writeText : function(v) {
this._writeAttributesEnd();
this.writeRaw(this.encode(v));
this.count++;
},
/**
* Writes a CDATA section.
*
* @method writeCDATA
* @param {String} v Value to write in CDATA.
*/
writeCDATA : function(v) {
this._writeAttributesEnd();
this.writeRaw('<![CDATA[' + v + ']]>');
this.count++;
},
/**
* Writes a comment.
*
* @method writeComment
* @param {String} v Value of the comment.
*/
writeComment : function(v) {
this._writeAttributesEnd();
this.writeRaw('<!-- ' + v + '-->');
this.count++;
},
/**
* String writer specific function. Enables you to write raw contents to the string.
*
* @method writeRaw
* @param {String} v String with raw contents to write.
*/
writeRaw : function(v) {
this.str += v;
},
/**
* String writer specific method. This encodes the raw entities of a string.
*
* @method encode
* @param {String} s String to encode.
* @return {String} String with entity encoding of the raw elements like <>&".
*/
encode : function(s) {
return s.replace(/[<>&"]/g, function(v) {
switch (v) {
case '<':
return '&lt;';
case '>':
return '&gt;';
case '&':
return '&amp;';
case '"':
return '&quot;';
}
return v;
});
},
/**
* Returns a string representation of the elements/nodes written.
*
* @method getContent
* @return {String} String representation of the written elements/nodes.
*/
getContent : function() {
return this.str;
},
_writeAttributesEnd : function(s) {
if (!this.inAttr)
return;
this.inAttr = false;
if (s && this.elementCount == this.count) {
this.writeRaw(' />');
return false;
}
this.writeRaw('>');
return true;
}
});
})(tinymce);
@@ -0,0 +1,64 @@
/**
* TreeWalker.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
tinymce.dom.TreeWalker = function(start_node, root_node) {
var node = start_node;
function findSibling(node, start_name, sibling_name, shallow) {
var sibling, parent;
if (node) {
// Walk into nodes if it has a start
if (!shallow && node[start_name])
return node[start_name];
// Return the sibling if it has one
if (node != root_node) {
sibling = node[sibling_name];
if (sibling)
return sibling;
// Walk up the parents to look for siblings
for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
sibling = parent[sibling_name];
if (sibling)
return sibling;
}
}
}
};
/**
* Returns the current node.
*
* @return {Node} Current node where the walker is.
*/
this.current = function() {
return node;
};
/**
* Walks to the next node in tree.
*
* @return {Node} Current node where the walker is after moving to the next node.
*/
this.next = function(shallow) {
return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
};
/**
* Walks to the previous node in tree.
*
* @return {Node} Current node where the walker is after moving to the previous node.
*/
this.prev = function(shallow) {
return (node = findSibling(node, 'lastChild', 'lastSibling', shallow));
};
};
@@ -0,0 +1,310 @@
/**
* TridentSelection.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function() {
function Selection(selection) {
var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false;
// Returns a W3C DOM compatible range object by using the IE Range API
function getRange() {
var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed;
// If selection is outside the current document just return an empty range
element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
if (element.ownerDocument != dom.doc)
return domRange;
// Handle control selection or text selection of a image
if (ieRange.item || !element.hasChildNodes()) {
domRange.setStart(element.parentNode, dom.nodeIndex(element));
domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
return domRange;
}
collapsed = selection.isCollapsed();
function findEndPoint(start) {
var marker, container, offset, nodes, startIndex = 0, endIndex, index, parent, checkRng, position;
// Setup temp range and collapse it
checkRng = ieRange.duplicate();
checkRng.collapse(start);
// Create marker and insert it at the end of the endpoints parent
marker = dom.create('a');
parent = checkRng.parentElement();
// If parent doesn't have any children then set the container to that parent and the index to 0
if (!parent.hasChildNodes()) {
domRange[start ? 'setStart' : 'setEnd'](parent, 0);
return;
}
parent.appendChild(marker);
checkRng.moveToElementText(marker);
position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
if (position > 0) {
// The position is after the end of the parent element.
// This is the case where IE puts the caret to the left edge of a table.
domRange[start ? 'setStartAfter' : 'setEndAfter'](parent);
dom.remove(marker);
return;
}
// Setup node list and endIndex
nodes = tinymce.grep(parent.childNodes);
endIndex = nodes.length - 1;
// Perform a binary search for the position
while (startIndex <= endIndex) {
index = Math.floor((startIndex + endIndex) / 2);
// Insert marker and check it's position relative to the selection
parent.insertBefore(marker, nodes[index]);
checkRng.moveToElementText(marker);
position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
if (position > 0) {
// Marker is to the right
startIndex = index + 1;
} else if (position < 0) {
// Marker is to the left
endIndex = index - 1;
} else {
// Maker is where we are
found = true;
break;
}
}
// Setup container
container = position > 0 || index == 0 ? marker.nextSibling : marker.previousSibling;
// Handle element selection
if (container.nodeType == 1) {
dom.remove(marker);
// Find offset and container
offset = dom.nodeIndex(container);
container = container.parentNode;
// Move the offset if we are setting the end or the position is after an element
if (!start || index > 0)
offset++;
} else {
// Calculate offset within text node
if (position > 0 || index == 0) {
checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
offset = checkRng.text.length;
} else {
checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
offset = container.nodeValue.length - checkRng.text.length;
}
dom.remove(marker);
}
domRange[start ? 'setStart' : 'setEnd'](container, offset);
};
// Find start point
findEndPoint(true);
// Find end point if needed
if (!collapsed)
findEndPoint();
return domRange;
};
this.addRange = function(rng) {
var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;
function setEndPoint(start) {
var container, offset, marker, tmpRng, nodes;
marker = dom.create('a');
container = start ? startContainer : endContainer;
offset = start ? startOffset : endOffset;
tmpRng = ieRng.duplicate();
if (container == doc) {
container = body;
offset = 0;
}
if (container.nodeType == 3) {
container.parentNode.insertBefore(marker, container);
tmpRng.moveToElementText(marker);
tmpRng.moveStart('character', offset);
dom.remove(marker);
ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
} else {
nodes = container.childNodes;
if (nodes.length) {
if (offset >= nodes.length) {
dom.insertAfter(marker, nodes[nodes.length - 1]);
} else {
container.insertBefore(marker, nodes[offset]);
}
tmpRng.moveToElementText(marker);
} else {
// Empty node selection for example <div>|</div>
marker = doc.createTextNode(invisibleChar);
container.appendChild(marker);
tmpRng.moveToElementText(marker.parentNode);
tmpRng.collapse(TRUE);
}
ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
dom.remove(marker);
}
}
// Destroy cached range
this.destroy();
// Setup some shorter versions
startContainer = rng.startContainer;
startOffset = rng.startOffset;
endContainer = rng.endContainer;
endOffset = rng.endOffset;
ieRng = body.createTextRange();
// If single element selection then try making a control selection out of it
if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) {
if (startOffset == endOffset - 1) {
try {
ctrlRng = body.createControlRange();
ctrlRng.addElement(startContainer.childNodes[startOffset]);
ctrlRng.select();
ctrlRng.scrollIntoView();
return;
} catch (ex) {
// Ignore
}
}
}
// Set start/end point of selection
setEndPoint(true);
setEndPoint();
// Select the new range and scroll it into view
ieRng.select();
ieRng.scrollIntoView();
};
this.getRangeAt = function() {
// Setup new range if the cache is empty
if (!range || !tinymce.dom.RangeUtils.compareRanges(lastIERng, selection.getRng())) {
range = getRange();
// Store away text range for next call
lastIERng = selection.getRng();
}
// IE will say that the range is equal then produce an invalid argument exception
// if you perform specific operations in a keyup event. For example Ctrl+Del.
// This hack will invalidate the range cache if the exception occurs
try {
range.startContainer.nextSibling;
} catch (ex) {
range = getRange();
lastIERng = null;
}
// Return cached range
return range;
};
this.destroy = function() {
// Destroy cached range and last IE range to avoid memory leaks
lastIERng = range = null;
};
// IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
if (selection.dom.boxModel) {
(function() {
var doc = dom.doc, body = doc.body, started, startRng;
// Make HTML element unselectable since we are going to handle selection by hand
doc.documentElement.unselectable = TRUE;
// Return range from point or null if it failed
function rngFromPoint(x, y) {
var rng = body.createTextRange();
try {
rng.moveToPoint(x, y);
} catch (ex) {
// IE sometimes throws and exception, so lets just ignore it
rng = null;
}
return rng;
};
// Fires while the selection is changing
function selectionChange(e) {
var pointRng;
// Check if the button is down or not
if (e.button) {
// Create range from mouse position
pointRng = rngFromPoint(e.x, e.y);
if (pointRng) {
// Check if pointRange is before/after selection then change the endPoint
if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
pointRng.setEndPoint('StartToStart', startRng);
else
pointRng.setEndPoint('EndToEnd', startRng);
pointRng.select();
}
} else
endSelection();
}
// Removes listeners
function endSelection() {
dom.unbind(doc, 'mouseup', endSelection);
dom.unbind(doc, 'mousemove', selectionChange);
started = 0;
};
// Detect when user selects outside BODY
dom.bind(doc, 'mousedown', function(e) {
if (e.target.nodeName === 'HTML') {
if (started)
endSelection();
started = 1;
// Setup start position
startRng = rngFromPoint(e.x, e.y);
if (startRng) {
// Listen for selection change events
dom.bind(doc, 'mouseup', endSelection);
dom.bind(doc, 'mousemove', selectionChange);
startRng.select();
}
}
});
})();
}
};
// Expose the selection object
tinymce.dom.TridentSelection = Selection;
})();
@@ -0,0 +1,165 @@
/**
* XMLWriter.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
/**
* This class writes nodes into a XML document structure. This structure can then be
* serialized down to a HTML string later on.
* @class tinymce.dom.XMLWriter
*/
tinymce.create('tinymce.dom.XMLWriter', {
node : null,
/**
* Constructs a new XMLWriter.
*
* @constructor
* @method XMLWriter
* @param {Object} s Optional settings object.
*/
XMLWriter : function(s) {
// Get XML document
function getXML() {
var i = document.implementation;
if (!i || !i.createDocument) {
// Try IE objects
try {return new ActiveXObject('MSXML2.DOMDocument');} catch (ex) {}
try {return new ActiveXObject('Microsoft.XmlDom');} catch (ex) {}
} else
return i.createDocument('', '', null);
};
this.doc = getXML();
// Since Opera and WebKit doesn't escape > into &gt; we need to do it our self to normalize the output for all browsers
this.valid = tinymce.isOpera || tinymce.isWebKit;
this.reset();
},
/**
* Resets the writer so it can be reused the contents of the writer is cleared.
*
* @method reset
*/
reset : function() {
var t = this, d = t.doc;
if (d.firstChild)
d.removeChild(d.firstChild);
t.node = d.appendChild(d.createElement("html"));
},
/**
* Writes the start of an element like for example: <tag.
*
* @method writeStartElement
* @param {String} n Name of element to write.
*/
writeStartElement : function(n) {
var t = this;
t.node = t.node.appendChild(t.doc.createElement(n));
},
/**
* Writes an attrubute like for example: myattr="valie"
*
* @method writeAttribute
* @param {String} n Attribute name to write.
* @param {String} v Attribute value to write.
*/
writeAttribute : function(n, v) {
if (this.valid)
v = v.replace(/>/g, '%MCGT%');
this.node.setAttribute(n, v);
},
/**
* Write the end of a element. This will add a short end for elements with out children like for example a img element.
*
* @method writeEndElement
*/
writeEndElement : function() {
this.node = this.node.parentNode;
},
/**
* Writes the end of a element. This will add a full end to the element even if it didn't have any children.
*
* @method writeFullEndElement
*/
writeFullEndElement : function() {
var t = this, n = t.node;
n.appendChild(t.doc.createTextNode(""));
t.node = n.parentNode;
},
/**
* Writes a text node value.
*
* @method writeText
* @param {String} v Value to append as a text node.
*/
writeText : function(v) {
if (this.valid)
v = v.replace(/>/g, '%MCGT%');
this.node.appendChild(this.doc.createTextNode(v));
},
/**
* Writes a CDATA section.
*
* @method writeCDATA
* @param {String} v Value to write in CDATA.
*/
writeCDATA : function(v) {
this.node.appendChild(this.doc.createCDATASection(v));
},
/**
* Writes a comment.
*
* @method writeComment
* @param {String} v Value of the comment.
*/
writeComment : function(v) {
// Fix for bug #2035694
if (tinymce.isIE)
v = v.replace(/^\-|\-$/g, ' ');
this.node.appendChild(this.doc.createComment(v.replace(/\-\-/g, ' ')));
},
/**
* Returns a string representation of the elements/nodes written.
*
* @method getContent
* @return {String} String representation of the written elements/nodes.
*/
getContent : function() {
var h;
h = this.doc.xml || new XMLSerializer().serializeToString(this.doc);
h = h.replace(/<\?[^?]+\?>|<html>|<\/html>|<html\/>|<!DOCTYPE[^>]+>/g, '');
h = h.replace(/ ?\/>/g, ' />');
if (this.valid)
h = h.replace(/\%MCGT%/g, '&gt;');
return h;
}
});
})(tinymce);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,664 @@
/**
* tinymce.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(win) {
var whiteSpaceRe = /^\s*|\s*$/g,
undefined;
/**
* Core namespace with core functionality for the TinyMCE API all sub classes will be added to this namespace/object.
*
* @static
* @class tinymce
* @example
* // Using each method
* tinymce.each([1, 2, 3], function(v, i) {
* console.log(i + '=' + v);
* });
*
* // Checking for a specific browser
* if (tinymce.isIE)
* console.log("IE");
*/
var tinymce = {
/**
* Major version of TinyMCE build.
*
* @property majorVersion
* @type String
*/
majorVersion : '@@tinymce_major_version@@',
/**
* Major version of TinyMCE build.
*
* @property minorVersion
* @type String
*/
minorVersion : '@@tinymce_minor_version@@',
/**
* Release date of TinyMCE build.
*
* @property minorVersion
* @type String
*/
releaseDate : '@@tinymce_release_date@@',
/**
* Initializes the TinyMCE global namespace this will setup browser detection and figure out where TinyMCE is running from.
*/
_init : function() {
var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
/**
* Constant that is true if the browser is Opera.
*
* @property isOpera
* @type Boolean
* @final
*/
t.isOpera = win.opera && opera.buildNumber;
/**
* Constant that is true if the browser is WebKit (Safari/Chrome).
*
* @property isWebKit
* @type Boolean
* @final
*/
t.isWebKit = /WebKit/.test(ua);
/**
* Constant that is true if the browser is IE.
*
* @property isIE
* @type Boolean
* @final
*/
t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
/**
* Constant that is true if the browser is IE 6 or older.
*
* @property isIE6
* @type Boolean
* @final
*/
t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
/**
* Constant that is true if the browser is Gecko.
*
* @property isGecko
* @type Boolean
* @final
*/
t.isGecko = !t.isWebKit && /Gecko/.test(ua);
/**
* Constant that is true if the os is Mac OS.
*
* @property isMac
* @type Boolean
* @final
*/
t.isMac = ua.indexOf('Mac') != -1;
/**
* Constant that is true if the runtime is Adobe Air.
*
* @property isAir
* @type Boolean
* @final
*/
t.isAir = /adobeair/i.test(ua);
/**
* Constant that tells if the current browser is an iPhone or iPad.
*
* @property isIDevice
* @type Boolean
* @final
*/
t.isIDevice = /(iPad|iPhone)/.test(ua);
// TinyMCE .NET webcontrol might be setting the values for TinyMCE
if (win.tinyMCEPreInit) {
t.suffix = tinyMCEPreInit.suffix;
t.baseURL = tinyMCEPreInit.base;
t.query = tinyMCEPreInit.query;
return;
}
// Get suffix and base
t.suffix = '';
// If base element found, add that infront of baseURL
nl = d.getElementsByTagName('base');
for (i=0; i<nl.length; i++) {
if (v = nl[i].href) {
// Host only value like http://site.com or http://site.com:8008
if (/^https?:\/\/[^\/]+$/.test(v))
v += '/';
base = v ? v.match(/.*\//)[0] : ''; // Get only directory
}
}
function getBase(n) {
if (n.src && /tiny_mce(|_gzip|_jquery|_prototype)(_dev|_src)?.js/.test(n.src)) {
if (/_(src|dev)\.js/g.test(n.src))
t.suffix = '_src';
if ((p = n.src.indexOf('?')) != -1)
t.query = n.src.substring(p + 1);
t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
// If path to script is relative and a base href was found add that one infront
// the src property will always be an absolute one on non IE browsers and IE 8
// so this logic will basically only be executed on older IE versions
if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
t.baseURL = base + t.baseURL;
return t.baseURL;
}
return null;
};
// Check document
nl = d.getElementsByTagName('script');
for (i=0; i<nl.length; i++) {
if (getBase(nl[i]))
return;
}
// Check head
n = d.getElementsByTagName('head')[0];
if (n) {
nl = n.getElementsByTagName('script');
for (i=0; i<nl.length; i++) {
if (getBase(nl[i]))
return;
}
}
return;
},
/**
* Checks if a object is of a specific type for example an array.
*
* @method is
* @param {Object} o Object to check type of.
* @param {string} t Optional type to check for.
* @return {Boolean} true/false if the object is of the specified type.
*/
is : function(o, t) {
if (!t)
return o !== undefined;
if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
return true;
return typeof(o) == t;
},
/**
* Performs an iteration of all items in a collection such as an object or array. This method will execure the
* callback function for each item in the collection, if the callback returns false the iteration will terminate.
* The callback has the following format: cb(value, key_or_index).
*
* @method each
* @param {Object} o Collection to iterate.
* @param {function} cb Callback function to execute for each item.
* @param {Object} s Optional scope to execute the callback in.
* @example
* tinymce.each([1, 2, 3], function(v, i) {
* console.log(i + '=' + v);
* });
*/
each : function(o, cb, s) {
var n, l;
if (!o)
return 0;
s = s || o;
if (o.length !== undefined) {
// Indexed arrays, needed for Safari
for (n=0, l = o.length; n < l; n++) {
if (cb.call(s, o[n], n, o) === false)
return 0;
}
} else {
// Hashtables
for (n in o) {
if (o.hasOwnProperty(n)) {
if (cb.call(s, o[n], n, o) === false)
return 0;
}
}
}
return 1;
},
// #ifndef jquery
/**
* Creates a new array by the return value of each iteration function call. This enables you to convert
* one array list into another.
*
* @method map
* @param {Array} a Array of items to iterate.
* @param {function} f Function to call for each item. It's return value will be the new value.
* @return {Array} Array with new values based on function return values.
*/
map : function(a, f) {
var o = [];
tinymce.each(a, function(v) {
o.push(f(v));
});
return o;
},
/**
* Filters out items from the input array by calling the specified function for each item.
* If the function returns false the item will be excluded if it returns true it will be included.
*
* @method grep
* @param {Array} a Array of items to loop though.
* @param {function} f Function to call for each item. Include/exclude depends on it's return value.
* @return {Array} New array with values imported and filtered based in input.
*/
grep : function(a, f) {
var o = [];
tinymce.each(a, function(v) {
if (!f || f(v))
o.push(v);
});
return o;
},
/**
* Returns the index of a value in an array, this method will return -1 if the item wasn't found.
*
* @method inArray
* @param {Array} a Array/Object to search for value in.
* @param {Object} v Value to check for inside the array.
* @return {Number/String} Index of item inside the array inside an object. Or -1 if it wasn't found.
*/
inArray : function(a, v) {
var i, l;
if (a) {
for (i = 0, l = a.length; i < l; i++) {
if (a[i] === v)
return i;
}
}
return -1;
},
/**
* Extends an object with the specified other object(s).
*
* @method extend
* @param {Object} o Object to extend with new items.
* @param {Object} e..n Object(s) to extend the specified object with.
* @return {Object} o New extended object, same reference as the input object.
*/
extend : function(o, e) {
var i, l, a = arguments;
for (i = 1, l = a.length; i < l; i++) {
e = a[i];
tinymce.each(e, function(v, n) {
if (v !== undefined)
o[n] = v;
});
}
return o;
},
// #endif
/**
* Removes whitespace from the beginning and end of a string.
*
* @method trim
* @param {String} s String to remove whitespace from.
* @return {String} New string with removed whitespace.
*/
trim : function(s) {
return (s ? '' + s : '').replace(whiteSpaceRe, '');
},
/**
* Creates a class, subclass or static singleton.
* More details on this method can be found in the Wiki.
*
* @method create
* @param {String} s Class name, inheritage and prefix.
* @param {Object} o Collection of methods to add to the class.
*/
create : function(s, p) {
var t = this, sp, ns, cn, scn, c, de = 0;
// Parse : <prefix> <class>:<super class>
s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
// Create namespace for new class
ns = t.createNS(s[3].replace(/\.\w+$/, ''));
// Class already exists
if (ns[cn])
return;
// Make pure static class
if (s[2] == 'static') {
ns[cn] = p;
if (this.onCreate)
this.onCreate(s[2], s[3], ns[cn]);
return;
}
// Create default constructor
if (!p[cn]) {
p[cn] = function() {};
de = 1;
}
// Add constructor and methods
ns[cn] = p[cn];
t.extend(ns[cn].prototype, p);
// Extend
if (s[5]) {
sp = t.resolve(s[5]).prototype;
scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
// Extend constructor
c = ns[cn];
if (de) {
// Add passthrough constructor
ns[cn] = function() {
return sp[scn].apply(this, arguments);
};
} else {
// Add inherit constructor
ns[cn] = function() {
this.parent = sp[scn];
return c.apply(this, arguments);
};
}
ns[cn].prototype[cn] = ns[cn];
// Add super methods
t.each(sp, function(f, n) {
ns[cn].prototype[n] = sp[n];
});
// Add overridden methods
t.each(p, function(f, n) {
// Extend methods if needed
if (sp[n]) {
ns[cn].prototype[n] = function() {
this.parent = sp[n];
return f.apply(this, arguments);
};
} else {
if (n != cn)
ns[cn].prototype[n] = f;
}
});
}
// Add static methods
t.each(p['static'], function(f, n) {
ns[cn][n] = f;
});
if (this.onCreate)
this.onCreate(s[2], s[3], ns[cn].prototype);
},
/**
* Executed the specified function for each item in a object tree.
*
* @method walk
* @param {Object} o Object tree to walk though.
* @param {function} f Function to call for each item.
* @param {String} n Optional name of collection inside the objects to walk for example childNodes.
* @param {String} s Optional scope to execute the function in.
*/
walk : function(o, f, n, s) {
s = s || this;
if (o) {
if (n)
o = o[n];
tinymce.each(o, function(o, i) {
if (f.call(s, o, i, n) === false)
return false;
tinymce.walk(o, f, n, s);
});
}
},
/**
* Creates a namespace on a specific object.
*
* @method createNS
* @param {String} n Namespace to create for example a.b.c.d.
* @param {Object} o Optional object to add namespace to, defaults to window.
* @return {Object} New namespace object the last item in path.
*/
createNS : function(n, o) {
var i, v;
o = o || win;
n = n.split('.');
for (i=0; i<n.length; i++) {
v = n[i];
if (!o[v])
o[v] = {};
o = o[v];
}
return o;
},
/**
* Resolves a string and returns the object from a specific structure.
*
* @method resolve
* @param {String} n Path to resolve for example a.b.c.d.
* @param {Object} o Optional object to search though, defaults to window.
* @return {Object} Last object in path or null if it couldn't be resolved.
*/
resolve : function(n, o) {
var i, l;
o = o || win;
n = n.split('.');
for (i = 0, l = n.length; i < l; i++) {
o = o[n[i]];
if (!o)
break;
}
return o;
},
/**
* Adds an unload handler to the document. This handler will be executed when the document gets unloaded.
* This method is useful for dealing with browser memory leaks where it might be vital to remove DOM references etc.
*
* @method addUnload
* @param {function} f Function to execute before the document gets unloaded.
* @param {Object} s Optional scope to execute the function in.
* @return {function} Returns the specified unload handler function.
*/
addUnload : function(f, s) {
var t = this;
f = {func : f, scope : s || this};
if (!t.unloads) {
function unload() {
var li = t.unloads, o, n;
if (li) {
// Call unload handlers
for (n in li) {
o = li[n];
if (o && o.func)
o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
}
// Detach unload function
if (win.detachEvent) {
win.detachEvent('onbeforeunload', fakeUnload);
win.detachEvent('onunload', unload);
} else if (win.removeEventListener)
win.removeEventListener('unload', unload, false);
// Destroy references
t.unloads = o = li = w = unload = 0;
// Run garbarge collector on IE
if (win.CollectGarbage)
CollectGarbage();
}
};
function fakeUnload() {
var d = document;
// Is there things still loading, then do some magic
if (d.readyState == 'interactive') {
function stop() {
// Prevent memory leak
d.detachEvent('onstop', stop);
// Call unload handler
if (unload)
unload();
d = 0;
};
// Fire unload when the currently loading page is stopped
if (d)
d.attachEvent('onstop', stop);
// Remove onstop listener after a while to prevent the unload function
// to execute if the user presses cancel in an onbeforeunload
// confirm dialog and then presses the browser stop button
win.setTimeout(function() {
if (d)
d.detachEvent('onstop', stop);
}, 0);
}
};
// Attach unload handler
if (win.attachEvent) {
win.attachEvent('onunload', unload);
win.attachEvent('onbeforeunload', fakeUnload);
} else if (win.addEventListener)
win.addEventListener('unload', unload, false);
// Setup initial unload handler array
t.unloads = [f];
} else
t.unloads.push(f);
return f;
},
/**
* Removes the specified function form the unload handler list.
*
* @method removeUnload
* @param {function} f Function to remove from unload handler list.
* @return {function} Removed function name or null if it wasn't found.
*/
removeUnload : function(f) {
var u = this.unloads, r = null;
tinymce.each(u, function(o, i) {
if (o && o.func == f) {
u.splice(i, 1);
r = f;
return false;
}
});
return r;
},
/**
* Splits a string but removes the whitespace before and after each value.
*
* @method explode
* @param {string} s String to split.
* @param {string} d Delimiter to split by.
*/
explode : function(s, d) {
return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
},
_addVer : function(u) {
var v;
if (!this.query)
return u;
v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
if (u.indexOf('#') == -1)
return u + v;
return u.replace('#', v + '#');
}
/**#@-*/
};
// Initialize the API
tinymce._init();
// Expose tinymce namespace to the global namespace (window)
win.tinymce = win.tinyMCE = tinymce;
})(window);
@@ -0,0 +1,71 @@
/**
* Button.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
var DOM = tinymce.DOM;
/**
* This class is used to create a UI button. A button is basically a link
* that is styled to look like a button or icon.
*
* @class tinymce.ui.Button
* @extends tinymce.ui.Control
*/
tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
/**
* Constructs a new button control instance.
*
* @constructor
* @method Button
* @param {String} id Control id for the button.
* @param {Object} s Optional name/value settings object.
*/
Button : function(id, s) {
this.parent(id, s);
this.classPrefix = 'mceButton';
},
/**
* Renders the button as a HTML string. This method is much faster than using the DOM and when
* creating a whole toolbar with buttons it does make a lot of difference.
*
* @method renderHTML
* @return {String} HTML for the button control element.
*/
renderHTML : function() {
var cp = this.classPrefix, s = this.settings, h, l;
l = DOM.encode(s.label || '');
h = '<a id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" title="' + DOM.encode(s.title) + '">';
if (s.image)
h += '<img class="mceIcon" src="' + s.image + '" />' + l + '</a>';
else
h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '') + '</a>';
return h;
},
/**
* Post render handler. This function will be called after the UI has been
* rendered so that events can be added.
*
* @method postRender
*/
postRender : function() {
var t = this, s = t.settings;
tinymce.dom.Event.add(t.id, 'click', function(e) {
if (!t.isDisabled())
return s.onclick.call(s.scope, e);
});
}
});
})(tinymce);
@@ -0,0 +1,249 @@
/**
* ColorSplitButton.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
/**
* This class is used to create UI color split button. A color split button will present show a small color picker
* when you press the open menu.
*
* @class tinymce.ui.ColorSplitButton
* @extends tinymce.ui.SplitButton
*/
tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
/**
* Constructs a new color split button control instance.
*
* @constructor
* @method ColorSplitButton
* @param {String} id Control id for the color split button.
* @param {Object} s Optional name/value settings object.
*/
ColorSplitButton : function(id, s) {
var t = this;
t.parent(id, s);
/**
* Settings object.
*
* @property settings
* @type Object
*/
t.settings = s = tinymce.extend({
colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
grid_width : 8,
default_color : '#888888'
}, t.settings);
/**
* Fires when the menu is shown.
*
* @event onShowMenu
*/
t.onShowMenu = new tinymce.util.Dispatcher(t);
/**
* Fires when the menu is hidden.
*
* @event onHideMenu
*/
t.onHideMenu = new tinymce.util.Dispatcher(t);
/**
* Current color value.
*
* @property value
* @type String
*/
t.value = s.default_color;
},
/**
* Shows the color menu. The color menu is a layer places under the button
* and displays a table of colors for the user to pick from.
*
* @method showMenu
*/
showMenu : function() {
var t = this, r, p, e, p2;
if (t.isDisabled())
return;
if (!t.isMenuRendered) {
t.renderMenu();
t.isMenuRendered = true;
}
if (t.isMenuVisible)
return t.hideMenu();
e = DOM.get(t.id);
DOM.show(t.id + '_menu');
DOM.addClass(e, 'mceSplitButtonSelected');
p2 = DOM.getPos(e);
DOM.setStyles(t.id + '_menu', {
left : p2.x,
top : p2.y + e.clientHeight,
zIndex : 200000
});
e = 0;
Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
t.onShowMenu.dispatch(t);
if (t._focused) {
t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
if (e.keyCode == 27)
t.hideMenu();
});
DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
}
t.isMenuVisible = 1;
},
/**
* Hides the color menu. The optional event parameter is used to check where the event occured so it
* doesn't close them menu if it was a event inside the menu.
*
* @method hideMenu
* @param {Event} e Optional event object.
*/
hideMenu : function(e) {
var t = this;
// Prevent double toogles by canceling the mouse click event to the button
if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
return;
if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
DOM.removeClass(t.id, 'mceSplitButtonSelected');
Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
DOM.hide(t.id + '_menu');
}
t.onHideMenu.dispatch(t);
t.isMenuVisible = 0;
},
/**
* Renders the menu to the DOM.
*
* @method renderMenu
*/
renderMenu : function() {
var t = this, m, i = 0, s = t.settings, n, tb, tr, w;
w = DOM.add(s.menu_container, 'div', {id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
DOM.add(m, 'span', {'class' : 'mceMenuLine'});
n = DOM.add(m, 'table', {'class' : 'mceColorSplitMenu'});
tb = DOM.add(n, 'tbody');
// Generate color grid
i = 0;
each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
c = c.replace(/^#/, '');
if (!i--) {
tr = DOM.add(tb, 'tr');
i = s.grid_width - 1;
}
n = DOM.add(tr, 'td');
n = DOM.add(n, 'a', {
href : 'javascript:;',
style : {
backgroundColor : '#' + c
},
_mce_color : '#' + c
});
});
if (s.more_colors_func) {
n = DOM.add(tb, 'tr');
n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
n = DOM.add(n, 'a', {id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
Event.add(n, 'click', function(e) {
s.more_colors_func.call(s.more_colors_scope || this);
return Event.cancel(e); // Cancel to fix onbeforeunload problem
});
}
DOM.addClass(m, 'mceColorSplitMenu');
Event.add(t.id + '_menu', 'click', function(e) {
var c;
e = e.target;
if (e.nodeName == 'A' && (c = e.getAttribute('_mce_color')))
t.setColor(c);
return Event.cancel(e); // Prevent IE auto save warning
});
return w;
},
/**
* Sets the current color for the control and hides the menu if it should be visible.
*
* @method setColor
* @param {String} c Color code value in hex for example: #FF00FF
*/
setColor : function(c) {
var t = this;
DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
t.value = c;
t.hideMenu();
t.settings.onselect(c);
},
/**
* Post render event. This will be executed after the control has been rendered and can be used to
* set states, add events to the control etc. It's recommended for subclasses of the control to call this method by using this.parent().
*
* @method postRender
*/
postRender : function() {
var t = this, id = t.id;
t.parent();
DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
},
/**
* Destroys the control. This means it will be removed from the DOM and any
* events tied to it will also be removed.
*
* @method destroy
*/
destroy : function() {
this.parent();
Event.clear(this.id + '_menu');
Event.clear(this.id + '_more');
DOM.remove(this.id + '_menu');
}
});
})(tinymce);
@@ -0,0 +1,66 @@
/**
* Container.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
/**
* This class is the base class for all container controls like toolbars. This class should not
* be instantiated directly other container controls should inherit from this one.
*
* @class tinymce.ui.Container
* @extends tinymce.ui.Control
*/
tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
/**
* Base contrustor a new container control instance.
*
* @constructor
* @method Container
* @param {String} id Control id to use for the container.
* @param {Object} s Optional name/value settings object.
*/
Container : function(id, s) {
this.parent(id, s);
/**
* Array of controls added to the container.
*
* @property controls
* @type Array
*/
this.controls = [];
this.lookup = {};
},
/**
* Adds a control to the collection of controls for the container.
*
* @method add
* @param {tinymce.ui.Control} c Control instance to add to the container.
* @return {tinymce.ui.Control} Same control instance that got passed in.
*/
add : function(c) {
this.lookup[c.id] = c;
this.controls.push(c);
return c;
},
/**
* Returns a control by id from the containers collection.
*
* @method get
* @param {String} n Id for the control to retrive.
* @return {tinymce.ui.Control} Control instance by the specified name or undefined if it wasn't found.
*/
get : function(n) {
return this.lookup[n];
}
});
@@ -0,0 +1,196 @@
/**
* Control.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
// Shorten class names
var DOM = tinymce.DOM, is = tinymce.is;
/**
* This class is the base class for all controls like buttons, toolbars, containers. This class should not
* be instantiated directly other controls should inherit from this one.
*
* @class tinymce.ui.Control
*/
tinymce.create('tinymce.ui.Control', {
/**
* Constructs a new control instance.
*
* @constructor
* @method Control
* @param {String} id Control id.
* @param {Object} s Optional name/value settings object.
*/
Control : function(id, s) {
this.id = id;
this.settings = s = s || {};
this.rendered = false;
this.onRender = new tinymce.util.Dispatcher(this);
this.classPrefix = '';
this.scope = s.scope || this;
this.disabled = 0;
this.active = 0;
},
/**
* Sets the disabled state for the control. This will add CSS classes to the
* element that contains the control. So that it can be disabled visually.
*
* @method setDisabled
* @param {Boolean} s Boolean state if the control should be disabled or not.
*/
setDisabled : function(s) {
var e;
if (s != this.disabled) {
e = DOM.get(this.id);
// Add accessibility title for unavailable actions
if (e && this.settings.unavailable_prefix) {
if (s) {
this.prevTitle = e.title;
e.title = this.settings.unavailable_prefix + ": " + e.title;
} else
e.title = this.prevTitle;
}
this.setState('Disabled', s);
this.setState('Enabled', !s);
this.disabled = s;
}
},
/**
* Returns true/false if the control is disabled or not. This is a method since you can then
* choose to check some class or some internal bool state in subclasses.
*
* @method isDisabled
* @return {Boolean} true/false if the control is disabled or not.
*/
isDisabled : function() {
return this.disabled;
},
/**
* Sets the activated state for the control. This will add CSS classes to the
* element that contains the control. So that it can be activated visually.
*
* @method setActive
* @param {Boolean} s Boolean state if the control should be activated or not.
*/
setActive : function(s) {
if (s != this.active) {
this.setState('Active', s);
this.active = s;
}
},
/**
* Returns true/false if the control is disabled or not. This is a method since you can then
* choose to check some class or some internal bool state in subclasses.
*
* @method isActive
* @return {Boolean} true/false if the control is disabled or not.
*/
isActive : function() {
return this.active;
},
/**
* Sets the specified class state for the control.
*
* @method setState
* @param {String} c Class name to add/remove depending on state.
* @param {Boolean} s True/false state if the class should be removed or added.
*/
setState : function(c, s) {
var n = DOM.get(this.id);
c = this.classPrefix + c;
if (s)
DOM.addClass(n, c);
else
DOM.removeClass(n, c);
},
/**
* Returns true/false if the control has been rendered or not.
*
* @method isRendered
* @return {Boolean} State if the control has been rendered or not.
*/
isRendered : function() {
return this.rendered;
},
/**
* Renders the control as a HTML string. This method is much faster than using the DOM and when
* creating a whole toolbar with buttons it does make a lot of difference.
*
* @method renderHTML
* @return {String} HTML for the button control element.
*/
renderHTML : function() {
},
/**
* Renders the control to the specified container element.
*
* @method renderTo
* @param {Element} n HTML DOM element to add control to.
*/
renderTo : function(n) {
DOM.setHTML(n, this.renderHTML());
},
/**
* Post render event. This will be executed after the control has been rendered and can be used to
* set states, add events to the control etc. It's recommended for subclasses of the control to call this method by using this.parent().
*
* @method postRender
*/
postRender : function() {
var t = this, b;
// Set pending states
if (is(t.disabled)) {
b = t.disabled;
t.disabled = -1;
t.setDisabled(b);
}
if (is(t.active)) {
b = t.active;
t.active = -1;
t.setActive(b);
}
},
/**
* Removes the control. This means it will be removed from the DOM and any
* events tied to it will also be removed.
*
* @method remove
*/
remove : function() {
DOM.remove(this.id);
this.destroy();
},
/**
* Destroys the control will free any memory by removing event listeners etc.
*
* @method destroy
*/
destroy : function() {
tinymce.dom.Event.clear(this.id);
}
});
})(tinymce);
@@ -0,0 +1,409 @@
/**
* DropMenu.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
/**
* This class is used to create drop menus, a drop menu can be a
* context menu, or a menu for a list box or a menu bar.
*
* @class tinymce.ui.DropMenu
* @extends tinymce.ui.Menu
*/
tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
/**
* Constructs a new drop menu control instance.
*
* @constructor
* @method DropMenu
* @param {String} id Button control id for the button.
* @param {Object} s Optional name/value settings object.
*/
DropMenu : function(id, s) {
s = s || {};
s.container = s.container || DOM.doc.body;
s.offset_x = s.offset_x || 0;
s.offset_y = s.offset_y || 0;
s.vp_offset_x = s.vp_offset_x || 0;
s.vp_offset_y = s.vp_offset_y || 0;
if (is(s.icons) && !s.icons)
s['class'] += ' mceNoIcons';
this.parent(id, s);
this.onShowMenu = new tinymce.util.Dispatcher(this);
this.onHideMenu = new tinymce.util.Dispatcher(this);
this.classPrefix = 'mceMenu';
},
/**
* Created a new sub menu for the drop menu control.
*
* @method createMenu
* @param {Object} s Optional name/value settings object.
* @return {tinymce.ui.DropMenu} New drop menu instance.
*/
createMenu : function(s) {
var t = this, cs = t.settings, m;
s.container = s.container || cs.container;
s.parent = t;
s.constrain = s.constrain || cs.constrain;
s['class'] = s['class'] || cs['class'];
s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
return m;
},
/**
* Repaints the menu after new items have been added dynamically.
*
* @method update
*/
update : function() {
var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
if (!DOM.boxModel)
t.element.setStyles({width : tw + 2, height : th + 2});
else
t.element.setStyles({width : tw, height : th});
if (s.max_width)
DOM.setStyle(co, 'width', tw);
if (s.max_height) {
DOM.setStyle(co, 'height', th);
if (tb.clientHeight < s.max_height)
DOM.setStyle(co, 'overflow', 'hidden');
}
},
/**
* Displays the menu at the specified cordinate.
*
* @method showMenu
* @param {Number} x Horizontal position of the menu.
* @param {Number} y Vertical position of the menu.
* @param {Numner} px Optional parent X position used when menus are cascading.
*/
showMenu : function(x, y, px) {
var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
t.collapse(1);
if (t.isMenuVisible)
return;
if (!t.rendered) {
co = DOM.add(t.settings.container, t.renderNode());
each(t.items, function(o) {
o.postRender();
});
t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
} else
co = DOM.get('menu_' + t.id);
// Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
if (!tinymce.isOpera)
DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
DOM.show(co);
t.update();
x += s.offset_x || 0;
y += s.offset_y || 0;
vp.w -= 4;
vp.h -= 4;
// Move inside viewport if not submenu
if (s.constrain) {
w = co.clientWidth - ot;
h = co.clientHeight - ot;
mx = vp.x + vp.w;
my = vp.y + vp.h;
if ((x + s.vp_offset_x + w) > mx)
x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
if ((y + s.vp_offset_y + h) > my)
y = Math.max(0, (my - s.vp_offset_y) - h);
}
DOM.setStyles(co, {left : x , top : y});
t.element.update();
t.isMenuVisible = 1;
t.mouseClickFunc = Event.add(co, 'click', function(e) {
var m;
e = e.target;
if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
m = t.items[e.id];
if (m.isDisabled())
return;
dm = t;
while (dm) {
if (dm.hideMenu)
dm.hideMenu();
dm = dm.settings.parent;
}
if (m.settings.onclick)
m.settings.onclick(e);
return Event.cancel(e); // Cancel to fix onbeforeunload problem
}
});
if (t.hasMenus()) {
t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
var m, r, mi;
e = e.target;
if (e && (e = DOM.getParent(e, 'tr'))) {
m = t.items[e.id];
if (t.lastMenu)
t.lastMenu.collapse(1);
if (m.isDisabled())
return;
if (e && DOM.hasClass(e, cp + 'ItemSub')) {
//p = DOM.getPos(s.container);
r = DOM.getRect(e);
m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
t.lastMenu = m;
DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
}
}
});
}
t.onShowMenu.dispatch(t);
if (s.keyboard_focus) {
Event.add(co, 'keydown', t._keyHandler, t);
DOM.select('a', 'menu_' + t.id)[0].focus(); // Select first link
t._focusIdx = 0;
}
},
/**
* Hides the displayed menu.
*
* @method hideMenu
*/
hideMenu : function(c) {
var t = this, co = DOM.get('menu_' + t.id), e;
if (!t.isMenuVisible)
return;
Event.remove(co, 'mouseover', t.mouseOverFunc);
Event.remove(co, 'click', t.mouseClickFunc);
Event.remove(co, 'keydown', t._keyHandler);
DOM.hide(co);
t.isMenuVisible = 0;
if (!c)
t.collapse(1);
if (t.element)
t.element.hide();
if (e = DOM.get(t.id))
DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
t.onHideMenu.dispatch(t);
},
/**
* Adds a new menu, menu item or sub classes of them to the drop menu.
*
* @method add
* @param {tinymce.ui.Control} o Menu or menu item to add to the drop menu.
* @return {tinymce.ui.Control} Same as the input control, the menu or menu item.
*/
add : function(o) {
var t = this, co;
o = t.parent(o);
if (t.isRendered && (co = DOM.get('menu_' + t.id)))
t._add(DOM.select('tbody', co)[0], o);
return o;
},
/**
* Collapses the menu, this will hide the menu and all menu items.
*
* @method collapse
* @param {Boolean} d Optional deep state. If this is set to true all children will be collapsed as well.
*/
collapse : function(d) {
this.parent(d);
this.hideMenu(1);
},
/**
* Removes a specific sub menu or menu item from the drop menu.
*
* @method remove
* @param {tinymce.ui.Control} o Menu item or menu to remove from drop menu.
* @return {tinymce.ui.Control} Control instance or null if it wasn't found.
*/
remove : function(o) {
DOM.remove(o.id);
this.destroy();
return this.parent(o);
},
/**
* Destroys the menu. This will remove the menu from the DOM and any events added to it etc.
*
* @method destroy
*/
destroy : function() {
var t = this, co = DOM.get('menu_' + t.id);
Event.remove(co, 'mouseover', t.mouseOverFunc);
Event.remove(co, 'click', t.mouseClickFunc);
if (t.element)
t.element.remove();
DOM.remove(co);
},
/**
* Renders the specified menu node to the dom.
*
* @method renderNode
* @return {Element} Container element for the drop menu.
*/
renderNode : function() {
var t = this, s = t.settings, n, tb, co, w;
w = DOM.create('div', {id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000'});
co = DOM.add(w, 'div', {id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
if (s.menu_line)
DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
// n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
n = DOM.add(co, 'table', {id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
tb = DOM.add(n, 'tbody');
each(t.items, function(o) {
t._add(tb, o);
});
t.rendered = true;
return w;
},
// Internal functions
_keyHandler : function(e) {
var t = this, kc = e.keyCode;
function focus(d) {
var i = t._focusIdx + d, e = DOM.select('a', 'menu_' + t.id)[i];
if (e) {
t._focusIdx = i;
e.focus();
}
};
switch (kc) {
case 38:
focus(-1); // Select first link
return;
case 40:
focus(1);
return;
case 13:
return;
case 27:
return this.hideMenu();
}
},
_add : function(tb, o) {
var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
if (s.separator) {
ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
if (n = ro.previousSibling)
DOM.addClass(n, 'mceLast');
return;
}
n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
n = it = DOM.add(n, 'td');
n = a = DOM.add(n, 'a', {href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
DOM.addClass(it, s['class']);
// n = DOM.add(n, 'span', {'class' : 'item'});
ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
if (s.icon_src)
DOM.add(ic, 'img', {src : s.icon_src});
n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
if (o.settings.style)
DOM.setAttrib(n, 'style', o.settings.style);
if (tb.childNodes.length == 1)
DOM.addClass(ro, 'mceFirst');
if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
DOM.addClass(ro, 'mceFirst');
if (o.collapse)
DOM.addClass(ro, cp + 'ItemSub');
if (n = ro.previousSibling)
DOM.removeClass(n, 'mceLast');
DOM.addClass(ro, 'mceLast');
}
});
})(tinymce);
@@ -0,0 +1,382 @@
/**
* ListBox.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
/**
* This class is used to create list boxes/select list. This one will generate
* a non native control. This one has the benefits of having visual items added.
*
* @class tinymce.ui.ListBox
* @extends tinymce.ui.Control
*/
tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
/**
* Constructs a new listbox control instance.
*
* @constructor
* @method ListBox
* @param {String} id Control id for the list box.
* @param {Object} s Optional name/value settings object.
*/
ListBox : function(id, s) {
var t = this;
t.parent(id, s);
/**
* Array of ListBox items.
*
* @property items
* @type Array
*/
t.items = [];
/**
* Fires when the selection has been changed.
*
* @event onChange
*/
t.onChange = new Dispatcher(t);
/**
* Fires after the element has been rendered to DOM.
*
* @event onPostRender
*/
t.onPostRender = new Dispatcher(t);
/**
* Fires when a new item is added.
*
* @event onAdd
*/
t.onAdd = new Dispatcher(t);
/**
* Fires when the menu gets rendered.
*
* @event onRenderMenu
*/
t.onRenderMenu = new tinymce.util.Dispatcher(this);
t.classPrefix = 'mceListBox';
},
/**
* Selects a item/option by value. This will both add a visual selection to the
* item and change the title of the control to the title of the option.
*
* @method select
* @param {String/function} va Value to look for inside the list box or a function selector.
*/
select : function(va) {
var t = this, fv, f;
if (va == undefined)
return t.selectByIndex(-1);
// Is string or number make function selector
if (va && va.call)
f = va;
else {
f = function(v) {
return v == va;
};
}
// Do we need to do something?
if (va != t.selectedValue) {
// Find item
each(t.items, function(o, i) {
if (f(o.value)) {
fv = 1;
t.selectByIndex(i);
return false;
}
});
if (!fv)
t.selectByIndex(-1);
}
},
/**
* Selects a item/option by index. This will both add a visual selection to the
* item and change the title of the control to the title of the option.
*
* @method selectByIndex
* @param {String} idx Index to select, pass -1 to select menu/title of select box.
*/
selectByIndex : function(idx) {
var t = this, e, o;
if (idx != t.selectedIndex) {
e = DOM.get(t.id + '_text');
o = t.items[idx];
if (o) {
t.selectedValue = o.value;
t.selectedIndex = idx;
DOM.setHTML(e, DOM.encode(o.title));
DOM.removeClass(e, 'mceTitle');
} else {
DOM.setHTML(e, DOM.encode(t.settings.title));
DOM.addClass(e, 'mceTitle');
t.selectedValue = t.selectedIndex = null;
}
e = 0;
}
},
/**
* Adds a option item to the list box.
*
* @method add
* @param {String} n Title for the new option.
* @param {String} v Value for the new option.
* @param {Object} o Optional object with settings like for example class.
*/
add : function(n, v, o) {
var t = this;
o = o || {};
o = tinymce.extend(o, {
title : n,
value : v
});
t.items.push(o);
t.onAdd.dispatch(t, o);
},
/**
* Returns the number of items inside the list box.
*
* @method getLength
* @param {Number} Number of items inside the list box.
*/
getLength : function() {
return this.items.length;
},
/**
* Renders the list box as a HTML string. This method is much faster than using the DOM and when
* creating a whole toolbar with buttons it does make a lot of difference.
*
* @method renderHTML
* @return {String} HTML for the list box control element.
*/
renderHTML : function() {
var h = '', t = this, s = t.settings, cp = t.classPrefix;
h = '<table id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
h += '<td>' + DOM.createHTML('a', {id : t.id + '_text', href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span></span>') + '</td>';
h += '</tr></tbody></table>';
return h;
},
/**
* Displays the drop menu with all items.
*
* @method showMenu
*/
showMenu : function() {
var t = this, p1, p2, e = DOM.get(this.id), m;
if (t.isDisabled() || t.items.length == 0)
return;
if (t.menu && t.menu.isMenuVisible)
return t.hideMenu();
if (!t.isMenuRendered) {
t.renderMenu();
t.isMenuRendered = true;
}
p1 = DOM.getPos(this.settings.menu_container);
p2 = DOM.getPos(e);
m = t.menu;
m.settings.offset_x = p2.x;
m.settings.offset_y = p2.y;
m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
// Select in menu
if (t.oldID)
m.items[t.oldID].setSelected(0);
each(t.items, function(o) {
if (o.value === t.selectedValue) {
m.items[o.id].setSelected(1);
t.oldID = o.id;
}
});
m.showMenu(0, e.clientHeight);
Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
DOM.addClass(t.id, t.classPrefix + 'Selected');
//DOM.get(t.id + '_text').focus();
},
/**
* Hides the drop menu.
*
* @method hideMenu
*/
hideMenu : function(e) {
var t = this;
if (t.menu && t.menu.isMenuVisible) {
// Prevent double toogles by canceling the mouse click event to the button
if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
return;
if (!e || !DOM.getParent(e.target, '.mceMenu')) {
DOM.removeClass(t.id, t.classPrefix + 'Selected');
Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
t.menu.hideMenu();
}
}
},
/**
* Renders the menu to the DOM.
*
* @method renderMenu
*/
renderMenu : function() {
var t = this, m;
m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
menu_line : 1,
'class' : t.classPrefix + 'Menu mceNoIcons',
max_width : 150,
max_height : 150
});
m.onHideMenu.add(t.hideMenu, t);
m.add({
title : t.settings.title,
'class' : 'mceMenuItemTitle',
onclick : function() {
if (t.settings.onselect('') !== false)
t.select(''); // Must be runned after
}
});
each(t.items, function(o) {
// No value then treat it as a title
if (o.value === undefined) {
m.add({
title : o.title,
'class' : 'mceMenuItemTitle',
onclick : function() {
if (t.settings.onselect('') !== false)
t.select(''); // Must be runned after
}
});
} else {
o.id = DOM.uniqueId();
o.onclick = function() {
if (t.settings.onselect(o.value) !== false)
t.select(o.value); // Must be runned after
};
m.add(o);
}
});
t.onRenderMenu.dispatch(t, m);
t.menu = m;
},
/**
* Post render event. This will be executed after the control has been rendered and can be used to
* set states, add events to the control etc. It's recommended for subclasses of the control to call this method by using this.parent().
*
* @method postRender
*/
postRender : function() {
var t = this, cp = t.classPrefix;
Event.add(t.id, 'click', t.showMenu, t);
Event.add(t.id + '_text', 'focus', function() {
if (!t._focused) {
t.keyDownHandler = Event.add(t.id + '_text', 'keydown', function(e) {
var idx = -1, v, kc = e.keyCode;
// Find current index
each(t.items, function(v, i) {
if (t.selectedValue == v.value)
idx = i;
});
// Move up/down
if (kc == 38)
v = t.items[idx - 1];
else if (kc == 40)
v = t.items[idx + 1];
else if (kc == 13) {
// Fake select on enter
v = t.selectedValue;
t.selectedValue = null; // Needs to be null to fake change
t.settings.onselect(v);
return Event.cancel(e);
}
if (v) {
t.hideMenu();
t.select(v.value);
}
});
}
t._focused = 1;
});
Event.add(t.id + '_text', 'blur', function() {Event.remove(t.id + '_text', 'keydown', t.keyDownHandler); t._focused = 0;});
// Old IE doesn't have hover on all elements
if (tinymce.isIE6 || !DOM.boxModel) {
Event.add(t.id, 'mouseover', function() {
if (!DOM.hasClass(t.id, cp + 'Disabled'))
DOM.addClass(t.id, cp + 'Hover');
});
Event.add(t.id, 'mouseout', function() {
if (!DOM.hasClass(t.id, cp + 'Disabled'))
DOM.removeClass(t.id, cp + 'Hover');
});
}
t.onPostRender.dispatch(t, DOM.get(t.id));
},
/**
* Destroys the ListBox i.e. clear memory and events.
*
* @method destroy
*/
destroy : function() {
this.parent();
Event.clear(this.id + '_text');
Event.clear(this.id + '_open');
}
});
})(tinymce);
@@ -0,0 +1,186 @@
/**
* Menu.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
/**
* This class is base class for all menu types like DropMenus etc. This class should not
* be instantiated directly other menu controls should inherit from this one.
*
* @class tinymce.ui.Menu
* @extends tinymce.ui.MenuItem
*/
tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
/**
* Constructs a new button control instance.
*
* @constructor
* @method Menu
* @param {String} id Button control id for the button.
* @param {Object} s Optional name/value settings object.
*/
Menu : function(id, s) {
var t = this;
t.parent(id, s);
t.items = {};
t.collapsed = false;
t.menuCount = 0;
t.onAddItem = new tinymce.util.Dispatcher(this);
},
/**
* Expands the menu, this will show them menu and all menu items.
*
* @method expand
* @param {Boolean} d Optional deep state. If this is set to true all children will be expanded as well.
*/
expand : function(d) {
var t = this;
if (d) {
walk(t, function(o) {
if (o.expand)
o.expand();
}, 'items', t);
}
t.collapsed = false;
},
/**
* Collapses the menu, this will hide the menu and all menu items.
*
* @method collapse
* @param {Boolean} d Optional deep state. If this is set to true all children will be collapsed as well.
*/
collapse : function(d) {
var t = this;
if (d) {
walk(t, function(o) {
if (o.collapse)
o.collapse();
}, 'items', t);
}
t.collapsed = true;
},
/**
* Returns true/false if the menu has been collapsed or not.
*
* @method isCollapsed
* @return {Boolean} True/false state if the menu has been collapsed or not.
*/
isCollapsed : function() {
return this.collapsed;
},
/**
* Adds a new menu, menu item or sub classes of them to the drop menu.
*
* @method add
* @param {tinymce.ui.Control} o Menu or menu item to add to the drop menu.
* @return {tinymce.ui.Control} Same as the input control, the menu or menu item.
*/
add : function(o) {
if (!o.settings)
o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
this.onAddItem.dispatch(this, o);
return this.items[o.id] = o;
},
/**
* Adds a menu separator between the menu items.
*
* @method addSeparator
* @return {tinymce.ui.MenuItem} Menu item instance for the separator.
*/
addSeparator : function() {
return this.add({separator : true});
},
/**
* Adds a sub menu to the menu.
*
* @method addMenu
* @param {Object} o Menu control or a object with settings to be created into an control.
* @return {tinymce.ui.Menu} Menu control instance passed in or created.
*/
addMenu : function(o) {
if (!o.collapse)
o = this.createMenu(o);
this.menuCount++;
return this.add(o);
},
/**
* Returns true/false if the menu has sub menus or not.
*
* @method hasMenus
* @return {Boolean} True/false state if the menu has sub menues or not.
*/
hasMenus : function() {
return this.menuCount !== 0;
},
/**
* Removes a specific sub menu or menu item from the menu.
*
* @method remove
* @param {tinymce.ui.Control} o Menu item or menu to remove from menu.
* @return {tinymce.ui.Control} Control instance or null if it wasn't found.
*/
remove : function(o) {
delete this.items[o.id];
},
/**
* Removes all menu items and sub menu items from the menu.
*
* @method removeAll
*/
removeAll : function() {
var t = this;
walk(t, function(o) {
if (o.removeAll)
o.removeAll();
else
o.remove();
o.destroy();
}, 'items', t);
t.items = {};
},
/**
* Created a new sub menu for the menu control.
*
* @method createMenu
* @param {Object} s Optional name/value settings object.
* @return {tinymce.ui.Menu} New drop menu instance.
*/
createMenu : function(o) {
var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
return m;
}
});
})(tinymce);
@@ -0,0 +1,142 @@
/**
* MenuButton.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
/**
* This class is used to create a UI button. A button is basically a link
* that is styled to look like a button or icon.
*
* @class tinymce.ui.MenuButton
* @extends tinymce.ui.Control
*/
tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
/**
* Constructs a new split button control instance.
*
* @constructor
* @method MenuButton
* @param {String} id Control id for the split button.
* @param {Object} s Optional name/value settings object.
*/
MenuButton : function(id, s) {
this.parent(id, s);
/**
* Fires when the menu is rendered.
*
* @event onRenderMenu
*/
this.onRenderMenu = new tinymce.util.Dispatcher(this);
s.menu_container = s.menu_container || DOM.doc.body;
},
/**
* Shows the menu.
*
* @method showMenu
*/
showMenu : function() {
var t = this, p1, p2, e = DOM.get(t.id), m;
if (t.isDisabled())
return;
if (!t.isMenuRendered) {
t.renderMenu();
t.isMenuRendered = true;
}
if (t.isMenuVisible)
return t.hideMenu();
p1 = DOM.getPos(t.settings.menu_container);
p2 = DOM.getPos(e);
m = t.menu;
m.settings.offset_x = p2.x;
m.settings.offset_y = p2.y;
m.settings.vp_offset_x = p2.x;
m.settings.vp_offset_y = p2.y;
m.settings.keyboard_focus = t._focused;
m.showMenu(0, e.clientHeight);
Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
t.setState('Selected', 1);
t.isMenuVisible = 1;
},
/**
* Renders the menu to the DOM.
*
* @method renderMenu
*/
renderMenu : function() {
var t = this, m;
m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
menu_line : 1,
'class' : this.classPrefix + 'Menu',
icons : t.settings.icons
});
m.onHideMenu.add(t.hideMenu, t);
t.onRenderMenu.dispatch(t, m);
t.menu = m;
},
/**
* Hides the menu. The optional event parameter is used to check where the event occured so it
* doesn't close them menu if it was a event inside the menu.
*
* @method hideMenu
* @param {Event} e Optional event object.
*/
hideMenu : function(e) {
var t = this;
// Prevent double toogles by canceling the mouse click event to the button
if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
return;
if (!e || !DOM.getParent(e.target, '.mceMenu')) {
t.setState('Selected', 0);
Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
if (t.menu)
t.menu.hideMenu();
}
t.isMenuVisible = 0;
},
/**
* Post render handler. This function will be called after the UI has been
* rendered so that events can be added.
*
* @method postRender
*/
postRender : function() {
var t = this, s = t.settings;
Event.add(t.id, 'click', function() {
if (!t.isDisabled()) {
if (s.onclick)
s.onclick(t.value);
t.showMenu();
}
});
}
});
})(tinymce);
@@ -0,0 +1,73 @@
/**
* MenuItem.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
/**
* This class is base class for all menu item types like DropMenus items etc. This class should not
* be instantiated directly other menu items should inherit from this one.
*
* @class tinymce.ui.MenuItem
* @extends tinymce.ui.Control
*/
tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
/**
* Constructs a new button control instance.
*
* @constructor
* @method MenuItem
* @param {String} id Button control id for the button.
* @param {Object} s Optional name/value settings object.
*/
MenuItem : function(id, s) {
this.parent(id, s);
this.classPrefix = 'mceMenuItem';
},
/**
* Sets the selected state for the control. This will add CSS classes to the
* element that contains the control. So that it can be selected visually.
*
* @method setSelected
* @param {Boolean} s Boolean state if the control should be selected or not.
*/
setSelected : function(s) {
this.setState('Selected', s);
this.selected = s;
},
/**
* Returns true/false if the control is selected or not.
*
* @method isSelected
* @return {Boolean} true/false if the control is selected or not.
*/
isSelected : function() {
return this.selected;
},
/**
* Post render handler. This function will be called after the UI has been
* rendered so that events can be added.
*
* @method postRender
*/
postRender : function() {
var t = this;
t.parent();
// Set pending state
if (is(t.selected))
t.setSelected(t.selected);
}
});
})(tinymce);
@@ -0,0 +1,208 @@
/**
* NativeListBox.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
/**
* This class is used to create list boxes/select list. This one will generate
* a native control the way that the browser produces them by default.
*
* @class tinymce.ui.NativeListBox
* @extends tinymce.ui.ListBox
*/
tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
/**
* Constructs a new button control instance.
*
* @constructor
* @method NativeListBox
* @param {String} id Button control id for the button.
* @param {Object} s Optional name/value settings object.
*/
NativeListBox : function(id, s) {
this.parent(id, s);
this.classPrefix = 'mceNativeListBox';
},
/**
* Sets the disabled state for the control. This will add CSS classes to the
* element that contains the control. So that it can be disabled visually.
*
* @method setDisabled
* @param {Boolean} s Boolean state if the control should be disabled or not.
*/
setDisabled : function(s) {
DOM.get(this.id).disabled = s;
},
/**
* Returns true/false if the control is disabled or not. This is a method since you can then
* choose to check some class or some internal bool state in subclasses.
*
* @method isDisabled
* @return {Boolean} true/false if the control is disabled or not.
*/
isDisabled : function() {
return DOM.get(this.id).disabled;
},
/**
* Selects a item/option by value. This will both add a visual selection to the
* item and change the title of the control to the title of the option.
*
* @method select
* @param {String/function} va Value to look for inside the list box or a function selector.
*/
select : function(va) {
var t = this, fv, f;
if (va == undefined)
return t.selectByIndex(-1);
// Is string or number make function selector
if (va && va.call)
f = va;
else {
f = function(v) {
return v == va;
};
}
// Do we need to do something?
if (va != t.selectedValue) {
// Find item
each(t.items, function(o, i) {
if (f(o.value)) {
fv = 1;
t.selectByIndex(i);
return false;
}
});
if (!fv)
t.selectByIndex(-1);
}
},
/**
* Selects a item/option by index. This will both add a visual selection to the
* item and change the title of the control to the title of the option.
*
* @method selectByIndex
* @param {String} idx Index to select, pass -1 to select menu/title of select box.
*/
selectByIndex : function(idx) {
DOM.get(this.id).selectedIndex = idx + 1;
this.selectedValue = this.items[idx] ? this.items[idx].value : null;
},
/**
* Adds a option item to the list box.
*
* @method add
* @param {String} n Title for the new option.
* @param {String} v Value for the new option.
* @param {Object} o Optional object with settings like for example class.
*/
add : function(n, v, a) {
var o, t = this;
a = a || {};
a.value = v;
if (t.isRendered())
DOM.add(DOM.get(this.id), 'option', a, n);
o = {
title : n,
value : v,
attribs : a
};
t.items.push(o);
t.onAdd.dispatch(t, o);
},
/**
* Executes the specified callback function for the menu item. In this case when the user clicks the menu item.
*
* @method getLength
*/
getLength : function() {
return this.items.length;
},
/**
* Renders the list box as a HTML string. This method is much faster than using the DOM and when
* creating a whole toolbar with buttons it does make a lot of difference.
*
* @method renderHTML
* @return {String} HTML for the list box control element.
*/
renderHTML : function() {
var h, t = this;
h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
each(t.items, function(it) {
h += DOM.createHTML('option', {value : it.value}, it.title);
});
h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox'}, h);
return h;
},
/**
* Post render handler. This function will be called after the UI has been
* rendered so that events can be added.
*
* @method postRender
*/
postRender : function() {
var t = this, ch;
t.rendered = true;
function onChange(e) {
var v = t.items[e.target.selectedIndex - 1];
if (v && (v = v.value)) {
t.onChange.dispatch(t, v);
if (t.settings.onselect)
t.settings.onselect(v);
}
};
Event.add(t.id, 'change', onChange);
// Accessibility keyhandler
Event.add(t.id, 'keydown', function(e) {
var bf;
Event.remove(t.id, 'change', ch);
bf = Event.add(t.id, 'blur', function() {
Event.add(t.id, 'change', onChange);
Event.remove(t.id, 'blur', bf);
});
if (e.keyCode == 13 || e.keyCode == 32) {
onChange(e);
return Event.cancel(e);
}
});
t.onPostRender.dispatch(t, DOM.get(t.id));
}
});
})(tinymce);
@@ -0,0 +1,41 @@
/**
* Separator.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
/**
* This class is used to create vertical separator between other controls.
*
* @class tinymce.ui.Separator
* @extends tinymce.ui.Control
*/
tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
/**
* Separator constructor.
*
* @constructor
* @method Separator
* @param {String} id Control id to use for the Separator.
* @param {Object} s Optional name/value settings object.
*/
Separator : function(id, s) {
this.parent(id, s);
this.classPrefix = 'mceSeparator';
},
/**
* Renders the separator as a HTML string. This method is much faster than using the DOM and when
* creating a whole toolbar with buttons it does make a lot of difference.
*
* @method renderHTML
* @return {String} HTML for the separator control element.
*/
renderHTML : function() {
return tinymce.DOM.createHTML('span', {'class' : this.classPrefix});
}
});
@@ -0,0 +1,102 @@
/**
* SplitButton.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
/**
* This class is used to create a split button. A button with a menu attached to it.
*
* @class tinymce.ui.SplitButton
* @extends tinymce.ui.Button
*/
tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
/**
* Constructs a new split button control instance.
*
* @constructor
* @method SplitButton
* @param {String} id Control id for the split button.
* @param {Object} s Optional name/value settings object.
*/
SplitButton : function(id, s) {
this.parent(id, s);
this.classPrefix = 'mceSplitButton';
},
/**
* Renders the split button as a HTML string. This method is much faster than using the DOM and when
* creating a whole toolbar with buttons it does make a lot of difference.
*
* @method renderHTML
* @return {String} HTML for the split button control element.
*/
renderHTML : function() {
var h, t = this, s = t.settings, h1;
h = '<tbody><tr>';
if (s.image)
h1 = DOM.createHTML('img ', {src : s.image, 'class' : 'mceAction ' + s['class']});
else
h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
h += '<td>' + DOM.createHTML('a', {id : t.id + '_action', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']});
h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
h += '</tr></tbody>';
return DOM.createHTML('table', {id : t.id, 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', onmousedown : 'return false;', title : s.title}, h);
},
/**
* Post render handler. This function will be called after the UI has been
* rendered so that events can be added.
*
* @method postRender
*/
postRender : function() {
var t = this, s = t.settings;
if (s.onclick) {
Event.add(t.id + '_action', 'click', function() {
if (!t.isDisabled())
s.onclick(t.value);
});
}
Event.add(t.id + '_open', 'click', t.showMenu, t);
Event.add(t.id + '_open', 'focus', function() {t._focused = 1;});
Event.add(t.id + '_open', 'blur', function() {t._focused = 0;});
// Old IE doesn't have hover on all elements
if (tinymce.isIE6 || !DOM.boxModel) {
Event.add(t.id, 'mouseover', function() {
if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
DOM.addClass(t.id, 'mceSplitButtonHover');
});
Event.add(t.id, 'mouseout', function() {
if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
DOM.removeClass(t.id, 'mceSplitButtonHover');
});
}
},
destroy : function() {
this.parent();
Event.clear(this.id + '_action');
Event.clear(this.id + '_open');
}
});
})(tinymce);
@@ -0,0 +1,85 @@
/**
* Toolbar.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
/**
* This class is used to create toolbars a toolbar is a container for other controls like buttons etc.
*
* @class tinymce.ui.Toolbar
* @extends tinymce.ui.Container
*/
tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
/**
* Renders the toolbar as a HTML string. This method is much faster than using the DOM and when
* creating a whole toolbar with buttons it does make a lot of difference.
*
* @method renderHTML
* @return {String} HTML for the toolbar control.
*/
renderHTML : function() {
var t = this, h = '', c, co, dom = tinymce.DOM, s = t.settings, i, pr, nx, cl;
cl = t.controls;
for (i=0; i<cl.length; i++) {
// Get current control, prev control, next control and if the control is a list box or not
co = cl[i];
pr = cl[i - 1];
nx = cl[i + 1];
// Add toolbar start
if (i === 0) {
c = 'mceToolbarStart';
if (co.Button)
c += ' mceToolbarStartButton';
else if (co.SplitButton)
c += ' mceToolbarStartSplitButton';
else if (co.ListBox)
c += ' mceToolbarStartListBox';
h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
}
// Add toolbar end before list box and after the previous button
// This is to fix the o2k7 editor skins
if (pr && co.ListBox) {
if (pr.Button || pr.SplitButton)
h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
}
// Render control HTML
// IE 8 quick fix, needed to propertly generate a hit area for anchors
if (dom.stdMode)
h += '<td style="position: relative">' + co.renderHTML() + '</td>';
else
h += '<td>' + co.renderHTML() + '</td>';
// Add toolbar start after list box and before the next button
// This is to fix the o2k7 editor skins
if (nx && co.ListBox) {
if (nx.Button || nx.SplitButton)
h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
}
}
c = 'mceToolbarEnd';
if (co.Button)
c += ' mceToolbarEndButton';
else if (co.SplitButton)
c += ' mceToolbarEndSplitButton';
else if (co.ListBox)
c += ' mceToolbarEndListBox';
h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || ''}, '<tbody><tr>' + h + '</tr></tbody>');
}
});
@@ -0,0 +1,129 @@
/**
* Cookie.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function() {
var each = tinymce.each;
/**
* This class contains simple cookie manangement functions.
*
* @class tinymce.util.Cookie
* @static
*/
tinymce.create('static tinymce.util.Cookie', {
/**
* Parses the specified query string into an name/value object.
*
* @method getHash
* @param {String} n String to parse into a n Hashtable object.
* @return {Object} Name/Value object with items parsed from querystring.
*/
getHash : function(n) {
var v = this.get(n), h;
if (v) {
each(v.split('&'), function(v) {
v = v.split('=');
h = h || {};
h[unescape(v[0])] = unescape(v[1]);
});
}
return h;
},
/**
* Sets a hashtable name/value object to a cookie.
*
* @method setHash
* @param {String} n Name of the cookie.
* @param {Object} v Hashtable object to set as cookie.
* @param {Date} e Optional date object for the expiration of the cookie.
* @param {String} p Optional path to restrict the cookie to.
* @param {String} d Optional domain to restrict the cookie to.
* @param {String} s Is the cookie secure or not.
*/
setHash : function(n, v, e, p, d, s) {
var o = '';
each(v, function(v, k) {
o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
});
this.set(n, o, e, p, d, s);
},
/**
* Gets the raw data of a cookie by name.
*
* @method get
* @param {String} n Name of cookie to retrive.
* @return {String} Cookie data string.
*/
get : function(n) {
var c = document.cookie, e, p = n + "=", b;
// Strict mode
if (!c)
return;
b = c.indexOf("; " + p);
if (b == -1) {
b = c.indexOf(p);
if (b != 0)
return null;
} else
b += 2;
e = c.indexOf(";", b);
if (e == -1)
e = c.length;
return unescape(c.substring(b + p.length, e));
},
/**
* Sets a raw cookie string.
*
* @method set
* @param {String} n Name of the cookie.
* @param {String} v Raw cookie data.
* @param {Date} e Optional date object for the expiration of the cookie.
* @param {String} p Optional path to restrict the cookie to.
* @param {String} d Optional domain to restrict the cookie to.
* @param {String} s Is the cookie secure or not.
*/
set : function(n, v, e, p, d, s) {
document.cookie = n + "=" + escape(v) +
((e) ? "; expires=" + e.toGMTString() : "") +
((p) ? "; path=" + escape(p) : "") +
((d) ? "; domain=" + d : "") +
((s) ? "; secure" : "");
},
/**
* Removes/deletes a cookie by name.
*
* @method remove
* @param {String} n Cookie name to remove/delete.
* @param {Strong} p Optional path to remove the cookie from.
*/
remove : function(n, p) {
var d = new Date();
d.setTime(d.getTime() - 1000);
this.set(n, '', d, p, d);
}
});
})();
@@ -0,0 +1,106 @@
/**
* Dispatcher.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
/**
* This class is used to dispatch event to observers/listeners.
* All internal events inside TinyMCE uses this class.
*
* @class tinymce.util.Dispatcher
*/
tinymce.create('tinymce.util.Dispatcher', {
scope : null,
listeners : null,
/**
* Constructs a new event dispatcher object.
*
* @constructor
* @method Dispatcher
* @param {Object} s Optional default execution scope for all observer functions.
*/
Dispatcher : function(s) {
this.scope = s || this;
this.listeners = [];
},
/**
* Add an observer function to be executed when a dispatch call is done.
*
* @method add
* @param {function} cb Callback function to execute when a dispatch event occurs.
* @param {Object} s Optional execution scope, defaults to the one specified in the class constructor.
* @return {function} Returns the same function as the one passed on.
*/
add : function(cb, s) {
this.listeners.push({cb : cb, scope : s || this.scope});
return cb;
},
/**
* Add an observer function to be executed to the top of the list of observers.
*
* @method addToTop
* @param {function} cb Callback function to execute when a dispatch event occurs.
* @param {Object} s Optional execution scope, defaults to the one specified in the class constructor.
* @return {function} Returns the same function as the one passed on.
*/
addToTop : function(cb, s) {
this.listeners.unshift({cb : cb, scope : s || this.scope});
return cb;
},
/**
* Removes an observer function.
*
* @method remove
* @param {function} cb Observer function to remove.
* @return {function} The same function that got passed in or null if it wasn't found.
*/
remove : function(cb) {
var l = this.listeners, o = null;
tinymce.each(l, function(c, i) {
if (cb == c.cb) {
o = cb;
l.splice(i, 1);
return false;
}
});
return o;
},
/**
* Dispatches an event to all observers/listeners.
*
* @method dispatch
* @param {Object} .. Any number of arguments to dispatch.
* @return {Object} Last observer functions return value.
*/
dispatch : function() {
var s, a = arguments, i, li = this.listeners, c;
// Needs to be a real loop since the listener count might change while looping
// And this is also more efficient
for (i = 0; i<li.length; i++) {
c = li[i];
s = c.cb.apply(c.scope, a);
if (s === false)
break;
}
return s;
}
/**#@-*/
});
@@ -0,0 +1,83 @@
/**
* JSON.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
/**
* JSON parser and serializer class.
*
* @class tinymce.util.JSON
* @static
*/
tinymce.create('static tinymce.util.JSON', {
/**
* Serializes the specified object as a JSON string.
*
* @method serialize
* @param {Object} o Object to serialize as a JSON string.
* @return {string} JSON string serialized from input.
*/
serialize : function(o) {
var i, v, s = tinymce.util.JSON.serialize, t;
if (o == null)
return 'null';
t = typeof o;
if (t == 'string') {
v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
return '"' + o.replace(/([\u0080-\uFFFF\x00-\x1f\"])/g, function(a, b) {
i = v.indexOf(b);
if (i + 1)
return '\\' + v.charAt(i + 1);
a = b.charCodeAt().toString(16);
return '\\u' + '0000'.substring(a.length) + a;
}) + '"';
}
if (t == 'object') {
if (o.hasOwnProperty && o instanceof Array) {
for (i=0, v = '['; i<o.length; i++)
v += (i > 0 ? ',' : '') + s(o[i]);
return v + ']';
}
v = '{';
for (i in o)
v += typeof o[i] != 'function' ? (v.length > 1 ? ',"' : '"') + i + '":' + s(o[i]) : '';
return v + '}';
}
return '' + o;
},
/**
* Unserializes/parses the specified JSON string into a object.
*
* @method parse
* @param {string} s JSON String to parse into a JavaScript object.
* @return {Object} Object from input JSON string or undefined if it failed.
*/
parse : function(s) {
try {
return eval('(' + s + ')');
} catch (ex) {
// Ignore
}
}
/**#@-*/
});
@@ -0,0 +1,28 @@
/**
* JSONP.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
tinymce.create('static tinymce.util.JSONP', {
callbacks : {},
count : 0,
send : function(o) {
var t = this, dom = tinymce.DOM, count = o.count !== undefined ? o.count : t.count, id = 'tinymce_jsonp_' + count;
t.callbacks[count] = function(json) {
dom.remove(id);
delete t.callbacks[count];
o.callback(json);
};
dom.add(dom.doc.body, 'script', {id : id , src : o.url, type : 'text/javascript'});
t.count++;
}
});
@@ -0,0 +1,89 @@
/**
* JSONRequest.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function() {
var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
/**
* This class enables you to use JSON-RPC to call backend methods.
*
* @class tinymce.util.JSONRequest
*/
tinymce.create('tinymce.util.JSONRequest', {
/**
* Constructs a new JSONRequest instance.
*
* @constructor
* @method JSONRequest
* @param {Object} s Optional settings object.
*/
JSONRequest : function(s) {
this.settings = extend({
}, s);
this.count = 0;
},
/**
* Sends a JSON-RPC call. Consult the Wiki API documentation for more details on what you can pass to this function.
*
* @method send
* @param {Object} o Call object where there are three field id, method and params this object should also contain callbacks etc.
*/
send : function(o) {
var ecb = o.error, scb = o.success;
o = extend(this.settings, o);
o.success = function(c, x) {
c = JSON.parse(c);
if (typeof(c) == 'undefined') {
c = {
error : 'JSON Parse error.'
};
}
if (c.error)
ecb.call(o.error_scope || o.scope, c.error, x);
else
scb.call(o.success_scope || o.scope, c.result);
};
o.error = function(ty, x) {
ecb.call(o.error_scope || o.scope, ty, x);
};
o.data = JSON.serialize({
id : o.id || 'c' + (this.count++),
method : o.method,
params : o.params
});
// JSON content type for Ruby on rails. Bug: #1883287
o.content_type = 'application/json';
XHR.send(o);
},
'static' : {
/**
* Simple helper function to send a JSON-RPC request without the need to initialize an object.
* Consult the Wiki API documentation for more details on what you can pass to this function.
*
* @method sendRPC
* @static
* @param {Object} o Call object where there are three field id, method and params this object should also contain callbacks etc.
*/
sendRPC : function(o) {
return new tinymce.util.JSONRequest().send(o);
}
}
});
}());
@@ -0,0 +1,303 @@
/**
* URI.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function() {
var each = tinymce.each;
/**
* This class handles parsing, modification and serialization of URI/URL strings.
* @class tinymce.util.URI
*/
tinymce.create('tinymce.util.URI', {
/**
* Constucts a new URI instance.
*
* @constructor
* @method URI
* @param {String} u URI string to parse.
* @param {Object} s Optional settings object.
*/
URI : function(u, s) {
var t = this, o, a, b;
// Trim whitespace
u = tinymce.trim(u);
// Default settings
s = t.settings = s || {};
// Strange app protocol or local anchor
if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
t.source = u;
return;
}
// Absolute path with no host, fake host and protocol
if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
// Relative path http:// or protocol relative //path
if (!/^\w*:?\/\//.test(u))
u = (s.base_uri.protocol || 'http') + '://mce_host' + t.toAbsPath(s.base_uri.path, u);
// Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
var s = u[i];
// Zope 3 workaround, they use @@something
if (s)
s = s.replace(/\(mce_at\)/g, '@@');
t[v] = s;
});
if (b = s.base_uri) {
if (!t.protocol)
t.protocol = b.protocol;
if (!t.userInfo)
t.userInfo = b.userInfo;
if (!t.port && t.host == 'mce_host')
t.port = b.port;
if (!t.host || t.host == 'mce_host')
t.host = b.host;
t.source = '';
}
//t.path = t.path || '/';
},
/**
* Sets the internal path part of the URI.
*
* @method setPath
* @param {string} p Path string to set.
*/
setPath : function(p) {
var t = this;
p = /^(.*?)\/?(\w+)?$/.exec(p);
// Update path parts
t.path = p[0];
t.directory = p[1];
t.file = p[2];
// Rebuild source
t.source = '';
t.getURI();
},
/**
* Converts the specified URI into a relative URI based on the current URI instance location.
*
* @method toRelative
* @param {String} u URI to convert into a relative path/URI.
* @return {String} Relative URI from the point specified in the current URI instance.
*/
toRelative : function(u) {
var t = this, o;
if (u === "./")
return u;
u = new tinymce.util.URI(u, {base_uri : t});
// Not on same domain/port or protocol
if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
return u.getURI();
o = t.toRelPath(t.path, u.path);
// Add query
if (u.query)
o += '?' + u.query;
// Add anchor
if (u.anchor)
o += '#' + u.anchor;
return o;
},
/**
* Converts the specified URI into a absolute URI based on the current URI instance location.
*
* @method toAbsolute
* @param {String} u URI to convert into a relative path/URI.
* @param {Boolean} nh No host and protocol prefix.
* @return {String} Absolute URI from the point specified in the current URI instance.
*/
toAbsolute : function(u, nh) {
var u = new tinymce.util.URI(u, {base_uri : this});
return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
},
/**
* Converts a absolute path into a relative path.
*
* @method toRelPath
* @param {String} base Base point to convert the path from.
* @param {String} path Absolute path to convert into a relative path.
*/
toRelPath : function(base, path) {
var items, bp = 0, out = '', i, l;
// Split the paths
base = base.substring(0, base.lastIndexOf('/'));
base = base.split('/');
items = path.split('/');
if (base.length >= items.length) {
for (i = 0, l = base.length; i < l; i++) {
if (i >= items.length || base[i] != items[i]) {
bp = i + 1;
break;
}
}
}
if (base.length < items.length) {
for (i = 0, l = items.length; i < l; i++) {
if (i >= base.length || base[i] != items[i]) {
bp = i + 1;
break;
}
}
}
if (bp == 1)
return path;
for (i = 0, l = base.length - (bp - 1); i < l; i++)
out += "../";
for (i = bp - 1, l = items.length; i < l; i++) {
if (i != bp - 1)
out += "/" + items[i];
else
out += items[i];
}
return out;
},
/**
* Converts a relative path into a absolute path.
*
* @method toAbsPath
* @param {String} base Base point to convert the path from.
* @param {String} path Relative path to convert into an absolute path.
*/
toAbsPath : function(base, path) {
var i, nb = 0, o = [], tr, outPath;
// Split paths
tr = /\/$/.test(path) ? '/' : '';
base = base.split('/');
path = path.split('/');
// Remove empty chunks
each(base, function(k) {
if (k)
o.push(k);
});
base = o;
// Merge relURLParts chunks
for (i = path.length - 1, o = []; i >= 0; i--) {
// Ignore empty or .
if (path[i].length == 0 || path[i] == ".")
continue;
// Is parent
if (path[i] == '..') {
nb++;
continue;
}
// Move up
if (nb > 0) {
nb--;
continue;
}
o.push(path[i]);
}
i = base.length - nb;
// If /a/b/c or /
if (i <= 0)
outPath = o.reverse().join('/');
else
outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
// Add front / if it's needed
if (outPath.indexOf('/') !== 0)
outPath = '/' + outPath;
// Add traling / if it's needed
if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
outPath += tr;
return outPath;
},
/**
* Returns the full URI of the internal structure.
*
* @method getURI
* @param {Boolean} nh Optional no host and protocol part. Defaults to false.
*/
getURI : function(nh) {
var s, t = this;
// Rebuild source
if (!t.source || nh) {
s = '';
if (!nh) {
if (t.protocol)
s += t.protocol + '://';
if (t.userInfo)
s += t.userInfo + '@';
if (t.host)
s += t.host;
if (t.port)
s += ':' + t.port;
}
if (t.path)
s += t.path;
if (t.query)
s += '?' + t.query;
if (t.anchor)
s += '#' + t.anchor;
t.source = s;
}
return t.source;
}
});
})();
@@ -0,0 +1,80 @@
/**
* XHR.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
/**
* This class enables you to send XMLHTTPRequests cross browser.
* @class tinymce.util.XHR
* @static
*/
tinymce.create('static tinymce.util.XHR', {
/**
* Sends a XMLHTTPRequest.
* Consult the Wiki for details on what settings this method takes.
*
* @method send
* @param {Object} o Object will target URL, callbacks and other info needed to make the request.
*/
send : function(o) {
var x, t, w = window, c = 0;
// Default settings
o.scope = o.scope || this;
o.success_scope = o.success_scope || o.scope;
o.error_scope = o.error_scope || o.scope;
o.async = o.async === false ? false : true;
o.data = o.data || '';
function get(s) {
x = 0;
try {
x = new ActiveXObject(s);
} catch (ex) {
}
return x;
};
x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
if (x) {
if (x.overrideMimeType)
x.overrideMimeType(o.content_type);
x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
if (o.content_type)
x.setRequestHeader('Content-Type', o.content_type);
x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
x.send(o.data);
function ready() {
if (!o.async || x.readyState == 4 || c++ > 10000) {
if (o.success && c < 10000 && x.status == 200)
o.success.call(o.success_scope, '' + x.responseText, x, o);
else if (o.error)
o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
x = null;
} else
w.setTimeout(ready, 10);
};
// Syncronous request
if (!o.async)
return ready();
// Wait for response, onReadyStateChange can not be used since it leaks memory in IE
t = w.setTimeout(ready, 10);
}
}
});
@@ -0,0 +1,129 @@
/**
* Parser.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function() {
/**
* XML Parser class. This class is only available for the dev version of TinyMCE.
*/
tinymce.create('tinymce.xml.Parser', {
/**
* Constucts a new XML parser instance.
*
* @param {Object} Optional settings object.
*/
Parser : function(s) {
this.settings = tinymce.extend({
async : true
}, s);
},
/**
* Parses the specified document and executed the callback ones it's parsed.
*
* @param {String} u URL to XML file to parse.
* @param {function} cb Optional callback to execute ones the XML file is loaded.
* @param {Object} s Optional scope for the callback execution.
*/
load : function(u, cb, s) {
var doc, t, w = window, c = 0;
s = s || this;
// Explorer, use XMLDOM since it can be used on local fs
if (window.ActiveXObject) {
doc = new ActiveXObject("Microsoft.XMLDOM");
doc.async = this.settings.async;
// Wait for response
if (doc.async) {
function check() {
if (doc.readyState == 4 || c++ > 10000)
return cb.call(s, doc);
w.setTimeout(check, 10);
};
t = w.setTimeout(check, 10);
}
doc.load(u);
if (!doc.async)
cb.call(s, doc);
return;
}
// W3C using XMLHttpRequest
if (window.XMLHttpRequest) {
try {
doc = new window.XMLHttpRequest();
doc.open('GET', u, this.settings.async);
doc.async = this.settings.async;
doc.onload = function() {
cb.call(s, doc.responseXML);
};
doc.send('');
} catch (ex) {
cb.call(s, null, ex);
}
}
},
/**
* Parses the specified XML string.
*
* @param {String} xml XML String to parse.
* @return {Document} XML Document instance.
*/
loadXML : function(xml) {
var doc;
// W3C
if (window.DOMParser)
return new DOMParser().parseFromString(xml, "text/xml");
// Explorer
if (window.ActiveXObject) {
doc = new ActiveXObject("Microsoft.XMLDOM");
doc.async = "false";
doc.loadXML(xml);
return doc;
}
},
/**
* Returns all string contents of a element concated together.
*
* @param {XMLNode} el XML element to retrive text from.
* @return {string} XML element text contents.
*/
getText : function(el) {
var o = '';
if (!el)
return '';
if (el.hasChildNodes()) {
el = el.firstChild;
do {
if (el.nodeType == 3 || el.nodeType == 4)
o += el.nodeValue;
} while(el = el.nextSibling);
}
return o;
}
});
})();
@@ -0,0 +1 @@
(function(b){var e,d,a=[],c=window;b.fn.tinymce=function(j){var p=this,g,k,h,m,i,l="",n="";if(!p.length){return p}if(!j){return tinyMCE.get(p[0].id)}function o(){var r=[],q=0;if(f){f();f=null}p.each(function(t,u){var s,w=u.id,v=j.oninit;if(!w){u.id=w=tinymce.DOM.uniqueId()}s=new tinymce.Editor(w,j);r.push(s);if(v){s.onInit.add(function(){var x,y=v;if(++q==r.length){if(tinymce.is(y,"string")){x=(y.indexOf(".")===-1)?null:tinymce.resolve(y.replace(/\.\w+$/,""));y=tinymce.resolve(y)}y.apply(x||tinymce,r)}})}});b.each(r,function(t,s){s.render()})}if(!c.tinymce&&!d&&(g=j.script_url)){d=1;h=g.substring(0,g.lastIndexOf("/"));if(/_(src|dev)\.js/g.test(g)){n="_src"}m=g.lastIndexOf("?");if(m!=-1){l=g.substring(m+1)}c.tinyMCEPreInit=c.tinyMCEPreInit||{base:h,suffix:n,query:l};if(g.indexOf("gzip")!=-1){i=j.language||"en";g=g+(/\?/.test(g)?"&":"?")+"js=true&core=true&suffix="+escape(n)+"&themes="+escape(j.theme)+"&plugins="+escape(j.plugins)+"&languages="+i;if(!c.tinyMCE_GZ){tinyMCE_GZ={start:function(){tinymce.suffix=n;function q(r){tinymce.ScriptLoader.markDone(tinyMCE.baseURI.toAbsolute(r))}q("langs/"+i+".js");q("themes/"+j.theme+"/editor_template"+n+".js");q("themes/"+j.theme+"/langs/"+i+".js");b.each(j.plugins.split(","),function(s,r){if(r){q("plugins/"+r+"/editor_plugin"+n+".js");q("plugins/"+r+"/langs/"+i+".js")}})},end:function(){}}}}b.ajax({type:"GET",url:g,dataType:"script",cache:true,success:function(){tinymce.dom.Event.domLoaded=1;d=2;if(j.script_loaded){j.script_loaded()}o();b.each(a,function(q,r){r()})}})}else{if(d===1){a.push(o)}else{o()}}return p};b.extend(b.expr[":"],{tinymce:function(g){return g.id&&!!tinyMCE.get(g.id)}});function f(){function i(l){if(l==="remove"){this.each(function(n,o){var m=h(o);if(m){m.remove()}})}this.find("span.mceEditor,div.mceEditor").each(function(n,o){var m=tinyMCE.get(o.id.replace(/_parent$/,""));if(m){m.remove()}})}function k(n){var m=this,l;if(n!==e){i.call(m);m.each(function(p,q){var o;if(o=tinyMCE.get(q.id)){o.setContent(n)}})}else{if(m.length>0){if(l=tinyMCE.get(m[0].id)){return l.getContent()}}}}function h(m){var l=null;(m)&&(m.id)&&(c.tinymce)&&(l=tinyMCE.get(m.id));return l}function g(l){return !!((l)&&(l.length)&&(c.tinymce)&&(l.is(":tinymce")))}var j={};b.each(["text","html","val"],function(n,l){var o=j[l]=b.fn[l],m=(l==="text");b.fn[l]=function(s){var p=this;if(!g(p)){return o.apply(p,arguments)}if(s!==e){k.call(p.filter(":tinymce"),s);o.apply(p.not(":tinymce"),arguments);return p}else{var r="";var q=arguments;(m?p:p.eq(0)).each(function(u,v){var t=h(v);r+=t?(m?t.getContent().replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g,""):t.getContent()):o.apply(b(v),q)});return r}}});b.each(["append","prepend"],function(n,m){var o=j[m]=b.fn[m],l=(m==="prepend");b.fn[m]=function(q){var p=this;if(!g(p)){return o.apply(p,arguments)}if(q!==e){p.filter(":tinymce").each(function(s,t){var r=h(t);r&&r.setContent(l?q+r.getContent():r.getContent()+q)});o.apply(p.not(":tinymce"),arguments);return p}}});b.each(["remove","replaceWith","replaceAll","empty"],function(m,l){var n=j[l]=b.fn[l];b.fn[l]=function(){i.call(this,l);return n.apply(this,arguments)}});j.attr=b.fn.attr;b.fn.attr=function(n,q,o){var m=this;if((!n)||(n!=="value")||(!g(m))){return j.attr.call(m,n,q,o)}if(q!==e){k.call(m.filter(":tinymce"),q);j.attr.call(m.not(":tinymce"),n,q,o);return m}else{var p=m[0],l=h(p);return l?l.getContent():j.attr.call(b(p),n,q,o)}}}})(jQuery);
@@ -0,0 +1,170 @@
tinyMCE.addI18n({en:{
common:{
edit_confirm:"Do you want to use the WYSIWYG mode for this textarea?",
apply:"Apply",
insert:"Insert",
update:"Update",
cancel:"Cancel",
close:"Close",
browse:"Browse",
class_name:"Class",
not_set:"-- Not set --",
clipboard_msg:"Copy/Cut/Paste is not available in Mozilla and Firefox.\nDo you want more information about this issue?",
clipboard_no_support:"Currently not supported by your browser, use keyboard shortcuts instead.",
popup_blocked:"Sorry, but we have noticed that your popup-blocker has disabled a window that provides application functionality. You will need to disable popup blocking on this site in order to fully utilize this tool.",
invalid_data:"Error: Invalid values entered, these are marked in red.",
more_colors:"More colors"
},
contextmenu:{
align:"Alignment",
left:"Left",
center:"Center",
right:"Right",
full:"Full"
},
insertdatetime:{
date_fmt:"%Y-%m-%d",
time_fmt:"%H:%M:%S",
insertdate_desc:"Insert date",
inserttime_desc:"Insert time",
months_long:"January,February,March,April,May,June,July,August,September,October,November,December",
months_short:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
day_long:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday",
day_short:"Sun,Mon,Tue,Wed,Thu,Fri,Sat,Sun"
},
print:{
print_desc:"Print"
},
preview:{
preview_desc:"Preview"
},
directionality:{
ltr_desc:"Direction left to right",
rtl_desc:"Direction right to left"
},
layer:{
insertlayer_desc:"Insert new layer",
forward_desc:"Move forward",
backward_desc:"Move backward",
absolute_desc:"Toggle absolute positioning",
content:"New layer..."
},
save:{
save_desc:"Save",
cancel_desc:"Cancel all changes"
},
nonbreaking:{
nonbreaking_desc:"Insert non-breaking space character"
},
iespell:{
iespell_desc:"Run spell checking",
download:"ieSpell not detected. Do you want to install it now?"
},
advhr:{
advhr_desc:"Horizontal rule"
},
emotions:{
emotions_desc:"Emotions"
},
searchreplace:{
search_desc:"Find",
replace_desc:"Find/Replace"
},
advimage:{
image_desc:"Insert/edit image"
},
advlink:{
link_desc:"Insert/edit link"
},
xhtmlxtras:{
cite_desc:"Citation",
abbr_desc:"Abbreviation",
acronym_desc:"Acronym",
del_desc:"Deletion",
ins_desc:"Insertion",
attribs_desc:"Insert/Edit Attributes"
},
style:{
desc:"Edit CSS Style"
},
paste:{
paste_text_desc:"Paste as Plain Text",
paste_word_desc:"Paste from Word",
selectall_desc:"Select All",
plaintext_mode_sticky:"Paste is now in plain text mode. Click again to toggle back to regular paste mode. After you paste something you will be returned to regular paste mode.",
plaintext_mode:"Paste is now in plain text mode. Click again to toggle back to regular paste mode."
},
paste_dlg:{
text_title:"Use CTRL+V on your keyboard to paste the text into the window.",
text_linebreaks:"Keep linebreaks",
word_title:"Use CTRL+V on your keyboard to paste the text into the window."
},
table:{
desc:"Inserts a new table",
row_before_desc:"Insert row before",
row_after_desc:"Insert row after",
delete_row_desc:"Delete row",
col_before_desc:"Insert column before",
col_after_desc:"Insert column after",
delete_col_desc:"Remove column",
split_cells_desc:"Split merged table cells",
merge_cells_desc:"Merge table cells",
row_desc:"Table row properties",
cell_desc:"Table cell properties",
props_desc:"Table properties",
paste_row_before_desc:"Paste table row before",
paste_row_after_desc:"Paste table row after",
cut_row_desc:"Cut table row",
copy_row_desc:"Copy table row",
del:"Delete table",
row:"Row",
col:"Column",
cell:"Cell"
},
autosave:{
unload_msg:"The changes you made will be lost if you navigate away from this page.",
restore_content:"Restore auto-saved content.",
warning_message:"If you restore the saved content, you will lose all the content that is currently in the editor.\n\nAre you sure you want to restore the saved content?."
},
fullscreen:{
desc:"Toggle fullscreen mode"
},
media:{
desc:"Insert / edit embedded media",
edit:"Edit embedded media"
},
fullpage:{
desc:"Document properties"
},
template:{
desc:"Insert predefined template content"
},
visualchars:{
desc:"Visual control characters on/off."
},
spellchecker:{
desc:"Toggle spellchecker",
menu:"Spellchecker settings",
ignore_word:"Ignore word",
ignore_words:"Ignore all",
langs:"Languages",
wait:"Please wait...",
sug:"Suggestions",
no_sug:"No suggestions",
no_mpell:"No misspellings found."
},
pagebreak:{
desc:"Insert page break."
},
advlist:{
types:"Types",
def:"Default",
lower_alpha:"Lower alpha",
lower_greek:"Lower greek",
lower_roman:"Lower roman",
upper_alpha:"Upper alpha",
upper_roman:"Upper roman",
circle:"Circle",
disc:"Disc",
square:"Square"
}}});
@@ -0,0 +1,504 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!
@@ -0,0 +1,5 @@
input.radio {border:1px none #000; background:transparent; vertical-align:middle;}
.panel_wrapper div.current {height:80px;}
#width {width:50px; vertical-align:middle;}
#width2 {width:50px; vertical-align:middle;}
#size {width:100px;}
@@ -0,0 +1 @@
(function(){tinymce.create("tinymce.plugins.AdvancedHRPlugin",{init:function(a,b){a.addCommand("mceAdvancedHr",function(){a.windowManager.open({file:b+"/rule.htm",width:250+parseInt(a.getLang("advhr.delta_width",0)),height:160+parseInt(a.getLang("advhr.delta_height",0)),inline:1},{plugin_url:b})});a.addButton("advhr",{title:"advhr.advhr_desc",cmd:"mceAdvancedHr"});a.onNodeChange.add(function(d,c,e){c.setActive("advhr",e.nodeName=="HR")});a.onClick.add(function(c,d){d=d.target;if(d.nodeName==="HR"){c.selection.select(d)}})},getInfo:function(){return{longname:"Advanced HR",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advhr",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("advhr",tinymce.plugins.AdvancedHRPlugin)})();
@@ -0,0 +1,57 @@
/**
* editor_plugin_src.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function() {
tinymce.create('tinymce.plugins.AdvancedHRPlugin', {
init : function(ed, url) {
// Register commands
ed.addCommand('mceAdvancedHr', function() {
ed.windowManager.open({
file : url + '/rule.htm',
width : 250 + parseInt(ed.getLang('advhr.delta_width', 0)),
height : 160 + parseInt(ed.getLang('advhr.delta_height', 0)),
inline : 1
}, {
plugin_url : url
});
});
// Register buttons
ed.addButton('advhr', {
title : 'advhr.advhr_desc',
cmd : 'mceAdvancedHr'
});
ed.onNodeChange.add(function(ed, cm, n) {
cm.setActive('advhr', n.nodeName == 'HR');
});
ed.onClick.add(function(ed, e) {
e = e.target;
if (e.nodeName === 'HR')
ed.selection.select(e);
});
},
getInfo : function() {
return {
longname : 'Advanced HR',
author : 'Moxiecode Systems AB',
authorurl : 'http://tinymce.moxiecode.com',
infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advhr',
version : tinymce.majorVersion + "." + tinymce.minorVersion
};
}
});
// Register plugin
tinymce.PluginManager.add('advhr', tinymce.plugins.AdvancedHRPlugin);
})();
@@ -0,0 +1,43 @@
var AdvHRDialog = {
init : function(ed) {
var dom = ed.dom, f = document.forms[0], n = ed.selection.getNode(), w;
w = dom.getAttrib(n, 'width');
f.width.value = w ? parseInt(w) : (dom.getStyle('width') || '');
f.size.value = dom.getAttrib(n, 'size') || parseInt(dom.getStyle('height')) || '';
f.noshade.checked = !!dom.getAttrib(n, 'noshade') || !!dom.getStyle('border-width');
selectByValue(f, 'width2', w.indexOf('%') != -1 ? '%' : 'px');
},
update : function() {
var ed = tinyMCEPopup.editor, h, f = document.forms[0], st = '';
h = '<hr';
if (f.size.value) {
h += ' size="' + f.size.value + '"';
st += ' height:' + f.size.value + 'px;';
}
if (f.width.value) {
h += ' width="' + f.width.value + (f.width2.value == '%' ? '%' : '') + '"';
st += ' width:' + f.width.value + (f.width2.value == '%' ? '%' : 'px') + ';';
}
if (f.noshade.checked) {
h += ' noshade="noshade"';
st += ' border-width: 1px; border-style: solid; border-color: #CCCCCC; color: #ffffff;';
}
if (ed.settings.inline_styles)
h += ' style="' + tinymce.trim(st) + '"';
h += ' />';
ed.execCommand("mceInsertContent", false, h);
tinyMCEPopup.close();
}
};
tinyMCEPopup.requireLangPack();
tinyMCEPopup.onInit.add(AdvHRDialog.init, AdvHRDialog);
@@ -0,0 +1,5 @@
tinyMCE.addI18n('en.advhr_dlg',{
width:"Width",
size:"Height",
noshade:"No shadow"
});
@@ -0,0 +1,57 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>{#advhr.advhr_desc}</title>
<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
<script type="text/javascript" src="js/rule.js"></script>
<script type="text/javascript" src="../../utils/mctabs.js"></script>
<script type="text/javascript" src="../../utils/form_utils.js"></script>
<link href="css/advhr.css" rel="stylesheet" type="text/css" />
</head>
<body>
<form onsubmit="AdvHRDialog.update();return false;" action="#">
<div class="tabs">
<ul>
<li id="general_tab" class="current"><span><a href="javascript:mcTabs.displayTab('general_tab','general_panel');" onmousedown="return false;">{#advhr.advhr_desc}</a></span></li>
</ul>
</div>
<div class="panel_wrapper">
<div id="general_panel" class="panel current">
<table border="0" cellpadding="4" cellspacing="0">
<tr>
<td><label for="width">{#advhr_dlg.width}</label></td>
<td class="nowrap">
<input id="width" name="width" type="text" value="" class="mceFocus" />
<select name="width2" id="width2">
<option value="">px</option>
<option value="%">%</option>
</select>
</td>
</tr>
<tr>
<td><label for="size">{#advhr_dlg.size}</label></td>
<td><select id="size" name="size">
<option value="">Normal</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
</select></td>
</tr>
<tr>
<td><label for="noshade">{#advhr_dlg.noshade}</label></td>
<td><input type="checkbox" name="noshade" id="noshade" class="radio" /></td>
</tr>
</table>
</div>
</div>
<div class="mceActionPanel">
<input type="submit" id="insert" name="insert" value="{#insert}" />
<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
</div>
</form>
</body>
</html>
@@ -0,0 +1,13 @@
#src_list, #over_list, #out_list {width:280px;}
.mceActionPanel {margin-top:7px;}
.alignPreview {border:1px solid #000; width:140px; height:140px; overflow:hidden; padding:5px;}
.checkbox {border:0;}
.panel_wrapper div.current {height:305px;}
#prev {margin:0; border:1px solid #000; width:428px; height:150px; overflow:auto;}
#align, #classlist {width:150px;}
#width, #height {vertical-align:middle; width:50px; text-align:center;}
#vspace, #hspace, #border {vertical-align:middle; width:30px; text-align:center;}
#class_list {width:180px;}
input {width: 280px;}
#constrain, #onmousemovecheck {width:auto;}
#id, #dir, #lang, #usemap, #longdesc {width:200px;}
@@ -0,0 +1 @@
(function(){tinymce.create("tinymce.plugins.AdvancedImagePlugin",{init:function(a,b){a.addCommand("mceAdvImage",function(){if(a.dom.getAttrib(a.selection.getNode(),"class").indexOf("mceItem")!=-1){return}a.windowManager.open({file:b+"/image.htm",width:480+parseInt(a.getLang("advimage.delta_width",0)),height:385+parseInt(a.getLang("advimage.delta_height",0)),inline:1},{plugin_url:b})});a.addButton("image",{title:"advimage.image_desc",cmd:"mceAdvImage"})},getInfo:function(){return{longname:"Advanced image",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advimage",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("advimage",tinymce.plugins.AdvancedImagePlugin)})();
@@ -0,0 +1,50 @@
/**
* editor_plugin_src.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function() {
tinymce.create('tinymce.plugins.AdvancedImagePlugin', {
init : function(ed, url) {
// Register commands
ed.addCommand('mceAdvImage', function() {
// Internal image object like a flash placeholder
if (ed.dom.getAttrib(ed.selection.getNode(), 'class').indexOf('mceItem') != -1)
return;
ed.windowManager.open({
file : url + '/image.htm',
width : 480 + parseInt(ed.getLang('advimage.delta_width', 0)),
height : 385 + parseInt(ed.getLang('advimage.delta_height', 0)),
inline : 1
}, {
plugin_url : url
});
});
// Register buttons
ed.addButton('image', {
title : 'advimage.image_desc',
cmd : 'mceAdvImage'
});
},
getInfo : function() {
return {
longname : 'Advanced image',
author : 'Moxiecode Systems AB',
authorurl : 'http://tinymce.moxiecode.com',
infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advimage',
version : tinymce.majorVersion + "." + tinymce.minorVersion
};
}
});
// Register plugin
tinymce.PluginManager.add('advimage', tinymce.plugins.AdvancedImagePlugin);
})();
@@ -0,0 +1,232 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>{#advimage_dlg.dialog_title}</title>
<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
<script type="text/javascript" src="../../utils/mctabs.js"></script>
<script type="text/javascript" src="../../utils/form_utils.js"></script>
<script type="text/javascript" src="../../utils/validate.js"></script>
<script type="text/javascript" src="../../utils/editable_selects.js"></script>
<script type="text/javascript" src="js/image.js"></script>
<link href="css/advimage.css" rel="stylesheet" type="text/css" />
</head>
<body id="advimage" style="display: none">
<form onsubmit="ImageDialog.insert();return false;" action="#">
<div class="tabs">
<ul>
<li id="general_tab" class="current"><span><a href="javascript:mcTabs.displayTab('general_tab','general_panel');" onmousedown="return false;">{#advimage_dlg.tab_general}</a></span></li>
<li id="appearance_tab"><span><a href="javascript:mcTabs.displayTab('appearance_tab','appearance_panel');" onmousedown="return false;">{#advimage_dlg.tab_appearance}</a></span></li>
<li id="advanced_tab"><span><a href="javascript:mcTabs.displayTab('advanced_tab','advanced_panel');" onmousedown="return false;">{#advimage_dlg.tab_advanced}</a></span></li>
</ul>
</div>
<div class="panel_wrapper">
<div id="general_panel" class="panel current">
<fieldset>
<legend>{#advimage_dlg.general}</legend>
<table class="properties">
<tr>
<td class="column1"><label id="srclabel" for="src">{#advimage_dlg.src}</label></td>
<td colspan="2"><table border="0" cellspacing="0" cellpadding="0">
<tr>
<td><input name="src" type="text" id="src" value="" class="mceFocus" onchange="ImageDialog.showPreviewImage(this.value);" /></td>
<td id="srcbrowsercontainer">&nbsp;</td>
</tr>
</table></td>
</tr>
<tr>
<td><label for="src_list">{#advimage_dlg.image_list}</label></td>
<td><select id="src_list" name="src_list" onchange="document.getElementById('src').value=this.options[this.selectedIndex].value;document.getElementById('alt').value=this.options[this.selectedIndex].text;document.getElementById('title').value=this.options[this.selectedIndex].text;ImageDialog.showPreviewImage(this.options[this.selectedIndex].value);"><option value=""></option></select></td>
</tr>
<tr>
<td class="column1"><label id="altlabel" for="alt">{#advimage_dlg.alt}</label></td>
<td colspan="2"><input id="alt" name="alt" type="text" value="" /></td>
</tr>
<tr>
<td class="column1"><label id="titlelabel" for="title">{#advimage_dlg.title}</label></td>
<td colspan="2"><input id="title" name="title" type="text" value="" /></td>
</tr>
</table>
</fieldset>
<fieldset>
<legend>{#advimage_dlg.preview}</legend>
<div id="prev"></div>
</fieldset>
</div>
<div id="appearance_panel" class="panel">
<fieldset>
<legend>{#advimage_dlg.tab_appearance}</legend>
<table border="0" cellpadding="4" cellspacing="0">
<tr>
<td class="column1"><label id="alignlabel" for="align">{#advimage_dlg.align}</label></td>
<td><select id="align" name="align" onchange="ImageDialog.updateStyle('align');ImageDialog.changeAppearance();">
<option value="">{#not_set}</option>
<option value="baseline">{#advimage_dlg.align_baseline}</option>
<option value="top">{#advimage_dlg.align_top}</option>
<option value="middle">{#advimage_dlg.align_middle}</option>
<option value="bottom">{#advimage_dlg.align_bottom}</option>
<option value="text-top">{#advimage_dlg.align_texttop}</option>
<option value="text-bottom">{#advimage_dlg.align_textbottom}</option>
<option value="left">{#advimage_dlg.align_left}</option>
<option value="right">{#advimage_dlg.align_right}</option>
</select>
</td>
<td rowspan="6" valign="top">
<div class="alignPreview">
<img id="alignSampleImg" src="img/sample.gif" alt="{#advimage_dlg.example_img}" />
Lorem ipsum, Dolor sit amet, consectetuer adipiscing loreum ipsum edipiscing elit, sed diam
nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.Loreum ipsum
edipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam
erat volutpat.
</div>
</td>
</tr>
<tr>
<td class="column1"><label id="widthlabel" for="width">{#advimage_dlg.dimensions}</label></td>
<td class="nowrap">
<input name="width" type="text" id="width" value="" size="5" maxlength="5" class="size" onchange="ImageDialog.changeHeight();" /> x
<input name="height" type="text" id="height" value="" size="5" maxlength="5" class="size" onchange="ImageDialog.changeWidth();" /> px
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td><table border="0" cellpadding="0" cellspacing="0">
<tr>
<td><input id="constrain" type="checkbox" name="constrain" class="checkbox" /></td>
<td><label id="constrainlabel" for="constrain">{#advimage_dlg.constrain_proportions}</label></td>
</tr>
</table></td>
</tr>
<tr>
<td class="column1"><label id="vspacelabel" for="vspace">{#advimage_dlg.vspace}</label></td>
<td><input name="vspace" type="text" id="vspace" value="" size="3" maxlength="3" class="number" onchange="ImageDialog.updateStyle('vspace');ImageDialog.changeAppearance();" onblur="ImageDialog.updateStyle('vspace');ImageDialog.changeAppearance();" />
</td>
</tr>
<tr>
<td class="column1"><label id="hspacelabel" for="hspace">{#advimage_dlg.hspace}</label></td>
<td><input name="hspace" type="text" id="hspace" value="" size="3" maxlength="3" class="number" onchange="ImageDialog.updateStyle('hspace');ImageDialog.changeAppearance();" onblur="ImageDialog.updateStyle('hspace');ImageDialog.changeAppearance();" /></td>
</tr>
<tr>
<td class="column1"><label id="borderlabel" for="border">{#advimage_dlg.border}</label></td>
<td><input id="border" name="border" type="text" value="" size="3" maxlength="3" class="number" onchange="ImageDialog.updateStyle('border');ImageDialog.changeAppearance();" onblur="ImageDialog.updateStyle('border');ImageDialog.changeAppearance();" /></td>
</tr>
<tr>
<td><label for="class_list">{#class_name}</label></td>
<td colspan="2"><select id="class_list" name="class_list" class="mceEditableSelect"><option value=""></option></select></td>
</tr>
<tr>
<td class="column1"><label id="stylelabel" for="style">{#advimage_dlg.style}</label></td>
<td colspan="2"><input id="style" name="style" type="text" value="" onchange="ImageDialog.changeAppearance();" /></td>
</tr>
<!-- <tr>
<td class="column1"><label id="classeslabel" for="classes">{#advimage_dlg.classes}</label></td>
<td colspan="2"><input id="classes" name="classes" type="text" value="" onchange="selectByValue(this.form,'classlist',this.value,true);" /></td>
</tr> -->
</table>
</fieldset>
</div>
<div id="advanced_panel" class="panel">
<fieldset>
<legend>{#advimage_dlg.swap_image}</legend>
<input type="checkbox" id="onmousemovecheck" name="onmousemovecheck" class="checkbox" onclick="ImageDialog.setSwapImage(this.checked);" />
<label id="onmousemovechecklabel" for="onmousemovecheck">{#advimage_dlg.alt_image}</label>
<table border="0" cellpadding="4" cellspacing="0" width="100%">
<tr>
<td class="column1"><label id="onmouseoversrclabel" for="onmouseoversrc">{#advimage_dlg.mouseover}</label></td>
<td><table border="0" cellspacing="0" cellpadding="0">
<tr>
<td><input id="onmouseoversrc" name="onmouseoversrc" type="text" value="" /></td>
<td id="onmouseoversrccontainer">&nbsp;</td>
</tr>
</table></td>
</tr>
<tr>
<td><label for="over_list">{#advimage_dlg.image_list}</label></td>
<td><select id="over_list" name="over_list" onchange="document.getElementById('onmouseoversrc').value=this.options[this.selectedIndex].value;"><option value=""></option></select></td>
</tr>
<tr>
<td class="column1"><label id="onmouseoutsrclabel" for="onmouseoutsrc">{#advimage_dlg.mouseout}</label></td>
<td class="column2"><table border="0" cellspacing="0" cellpadding="0">
<tr>
<td><input id="onmouseoutsrc" name="onmouseoutsrc" type="text" value="" /></td>
<td id="onmouseoutsrccontainer">&nbsp;</td>
</tr>
</table></td>
</tr>
<tr>
<td><label for="out_list">{#advimage_dlg.image_list}</label></td>
<td><select id="out_list" name="out_list" onchange="document.getElementById('onmouseoutsrc').value=this.options[this.selectedIndex].value;"><option value=""></option></select></td>
</tr>
</table>
</fieldset>
<fieldset>
<legend>{#advimage_dlg.misc}</legend>
<table border="0" cellpadding="4" cellspacing="0">
<tr>
<td class="column1"><label id="idlabel" for="id">{#advimage_dlg.id}</label></td>
<td><input id="id" name="id" type="text" value="" /></td>
</tr>
<tr>
<td class="column1"><label id="dirlabel" for="dir">{#advimage_dlg.langdir}</label></td>
<td>
<select id="dir" name="dir" onchange="ImageDialog.changeAppearance();">
<option value="">{#not_set}</option>
<option value="ltr">{#advimage_dlg.ltr}</option>
<option value="rtl">{#advimage_dlg.rtl}</option>
</select>
</td>
</tr>
<tr>
<td class="column1"><label id="langlabel" for="lang">{#advimage_dlg.langcode}</label></td>
<td>
<input id="lang" name="lang" type="text" value="" />
</td>
</tr>
<tr>
<td class="column1"><label id="usemaplabel" for="usemap">{#advimage_dlg.map}</label></td>
<td>
<input id="usemap" name="usemap" type="text" value="" />
</td>
</tr>
<tr>
<td class="column1"><label id="longdesclabel" for="longdesc">{#advimage_dlg.long_desc}</label></td>
<td><table border="0" cellspacing="0" cellpadding="0">
<tr>
<td><input id="longdesc" name="longdesc" type="text" value="" /></td>
<td id="longdesccontainer">&nbsp;</td>
</tr>
</table></td>
</tr>
</table>
</fieldset>
</div>
</div>
<div class="mceActionPanel">
<input type="submit" id="insert" name="insert" value="{#insert}" />
<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
</div>
</form>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,443 @@
var ImageDialog = {
preInit : function() {
var url;
tinyMCEPopup.requireLangPack();
if (url = tinyMCEPopup.getParam("external_image_list_url"))
document.write('<script language="javascript" type="text/javascript" src="' + tinyMCEPopup.editor.documentBaseURI.toAbsolute(url) + '"></script>');
},
init : function(ed) {
var f = document.forms[0], nl = f.elements, ed = tinyMCEPopup.editor, dom = ed.dom, n = ed.selection.getNode();
tinyMCEPopup.resizeToInnerSize();
this.fillClassList('class_list');
this.fillFileList('src_list', 'tinyMCEImageList');
this.fillFileList('over_list', 'tinyMCEImageList');
this.fillFileList('out_list', 'tinyMCEImageList');
TinyMCE_EditableSelects.init();
if (n.nodeName == 'IMG') {
nl.src.value = dom.getAttrib(n, 'src');
nl.width.value = dom.getAttrib(n, 'width');
nl.height.value = dom.getAttrib(n, 'height');
nl.alt.value = dom.getAttrib(n, 'alt');
nl.title.value = dom.getAttrib(n, 'title');
nl.vspace.value = this.getAttrib(n, 'vspace');
nl.hspace.value = this.getAttrib(n, 'hspace');
nl.border.value = this.getAttrib(n, 'border');
selectByValue(f, 'align', this.getAttrib(n, 'align'));
selectByValue(f, 'class_list', dom.getAttrib(n, 'class'), true, true);
nl.style.value = dom.getAttrib(n, 'style');
nl.id.value = dom.getAttrib(n, 'id');
nl.dir.value = dom.getAttrib(n, 'dir');
nl.lang.value = dom.getAttrib(n, 'lang');
nl.usemap.value = dom.getAttrib(n, 'usemap');
nl.longdesc.value = dom.getAttrib(n, 'longdesc');
nl.insert.value = ed.getLang('update');
if (/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/.test(dom.getAttrib(n, 'onmouseover')))
nl.onmouseoversrc.value = dom.getAttrib(n, 'onmouseover').replace(/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/, '$1');
if (/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/.test(dom.getAttrib(n, 'onmouseout')))
nl.onmouseoutsrc.value = dom.getAttrib(n, 'onmouseout').replace(/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/, '$1');
if (ed.settings.inline_styles) {
// Move attribs to styles
if (dom.getAttrib(n, 'align'))
this.updateStyle('align');
if (dom.getAttrib(n, 'hspace'))
this.updateStyle('hspace');
if (dom.getAttrib(n, 'border'))
this.updateStyle('border');
if (dom.getAttrib(n, 'vspace'))
this.updateStyle('vspace');
}
}
// Setup browse button
document.getElementById('srcbrowsercontainer').innerHTML = getBrowserHTML('srcbrowser','src','image','theme_advanced_image');
if (isVisible('srcbrowser'))
document.getElementById('src').style.width = '260px';
// Setup browse button
document.getElementById('onmouseoversrccontainer').innerHTML = getBrowserHTML('overbrowser','onmouseoversrc','image','theme_advanced_image');
if (isVisible('overbrowser'))
document.getElementById('onmouseoversrc').style.width = '260px';
// Setup browse button
document.getElementById('onmouseoutsrccontainer').innerHTML = getBrowserHTML('outbrowser','onmouseoutsrc','image','theme_advanced_image');
if (isVisible('outbrowser'))
document.getElementById('onmouseoutsrc').style.width = '260px';
// If option enabled default contrain proportions to checked
if (ed.getParam("advimage_constrain_proportions", true))
f.constrain.checked = true;
// Check swap image if valid data
if (nl.onmouseoversrc.value || nl.onmouseoutsrc.value)
this.setSwapImage(true);
else
this.setSwapImage(false);
this.changeAppearance();
this.showPreviewImage(nl.src.value, 1);
},
insert : function(file, title) {
var ed = tinyMCEPopup.editor, t = this, f = document.forms[0];
if (f.src.value === '') {
if (ed.selection.getNode().nodeName == 'IMG') {
ed.dom.remove(ed.selection.getNode());
ed.execCommand('mceRepaint');
}
tinyMCEPopup.close();
return;
}
if (tinyMCEPopup.getParam("accessibility_warnings", 1)) {
if (!f.alt.value) {
tinyMCEPopup.confirm(tinyMCEPopup.getLang('advimage_dlg.missing_alt'), function(s) {
if (s)
t.insertAndClose();
});
return;
}
}
t.insertAndClose();
},
insertAndClose : function() {
var ed = tinyMCEPopup.editor, f = document.forms[0], nl = f.elements, v, args = {}, el;
tinyMCEPopup.restoreSelection();
// Fixes crash in Safari
if (tinymce.isWebKit)
ed.getWin().focus();
if (!ed.settings.inline_styles) {
args = {
vspace : nl.vspace.value,
hspace : nl.hspace.value,
border : nl.border.value,
align : getSelectValue(f, 'align')
};
} else {
// Remove deprecated values
args = {
vspace : '',
hspace : '',
border : '',
align : ''
};
}
tinymce.extend(args, {
src : nl.src.value,
width : nl.width.value,
height : nl.height.value,
alt : nl.alt.value,
title : nl.title.value,
'class' : getSelectValue(f, 'class_list'),
style : nl.style.value,
id : nl.id.value,
dir : nl.dir.value,
lang : nl.lang.value,
usemap : nl.usemap.value,
longdesc : nl.longdesc.value
});
args.onmouseover = args.onmouseout = '';
if (f.onmousemovecheck.checked) {
if (nl.onmouseoversrc.value)
args.onmouseover = "this.src='" + nl.onmouseoversrc.value + "';";
if (nl.onmouseoutsrc.value)
args.onmouseout = "this.src='" + nl.onmouseoutsrc.value + "';";
}
el = ed.selection.getNode();
if (el && el.nodeName == 'IMG') {
ed.dom.setAttribs(el, args);
} else {
ed.execCommand('mceInsertContent', false, '<img id="__mce_tmp" />', {skip_undo : 1});
ed.dom.setAttribs('__mce_tmp', args);
ed.dom.setAttrib('__mce_tmp', 'id', '');
ed.undoManager.add();
}
tinyMCEPopup.close();
},
getAttrib : function(e, at) {
var ed = tinyMCEPopup.editor, dom = ed.dom, v, v2;
if (ed.settings.inline_styles) {
switch (at) {
case 'align':
if (v = dom.getStyle(e, 'float'))
return v;
if (v = dom.getStyle(e, 'vertical-align'))
return v;
break;
case 'hspace':
v = dom.getStyle(e, 'margin-left')
v2 = dom.getStyle(e, 'margin-right');
if (v && v == v2)
return parseInt(v.replace(/[^0-9]/g, ''));
break;
case 'vspace':
v = dom.getStyle(e, 'margin-top')
v2 = dom.getStyle(e, 'margin-bottom');
if (v && v == v2)
return parseInt(v.replace(/[^0-9]/g, ''));
break;
case 'border':
v = 0;
tinymce.each(['top', 'right', 'bottom', 'left'], function(sv) {
sv = dom.getStyle(e, 'border-' + sv + '-width');
// False or not the same as prev
if (!sv || (sv != v && v !== 0)) {
v = 0;
return false;
}
if (sv)
v = sv;
});
if (v)
return parseInt(v.replace(/[^0-9]/g, ''));
break;
}
}
if (v = dom.getAttrib(e, at))
return v;
return '';
},
setSwapImage : function(st) {
var f = document.forms[0];
f.onmousemovecheck.checked = st;
setBrowserDisabled('overbrowser', !st);
setBrowserDisabled('outbrowser', !st);
if (f.over_list)
f.over_list.disabled = !st;
if (f.out_list)
f.out_list.disabled = !st;
f.onmouseoversrc.disabled = !st;
f.onmouseoutsrc.disabled = !st;
},
fillClassList : function(id) {
var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl;
if (v = tinyMCEPopup.getParam('theme_advanced_styles')) {
cl = [];
tinymce.each(v.split(';'), function(v) {
var p = v.split('=');
cl.push({'title' : p[0], 'class' : p[1]});
});
} else
cl = tinyMCEPopup.editor.dom.getClasses();
if (cl.length > 0) {
lst.options.length = 0;
lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('not_set'), '');
tinymce.each(cl, function(o) {
lst.options[lst.options.length] = new Option(o.title || o['class'], o['class']);
});
} else
dom.remove(dom.getParent(id, 'tr'));
},
fillFileList : function(id, l) {
var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl;
l = window[l];
lst.options.length = 0;
if (l && l.length > 0) {
lst.options[lst.options.length] = new Option('', '');
tinymce.each(l, function(o) {
lst.options[lst.options.length] = new Option(o[0], o[1]);
});
} else
dom.remove(dom.getParent(id, 'tr'));
},
resetImageData : function() {
var f = document.forms[0];
f.elements.width.value = f.elements.height.value = '';
},
updateImageData : function(img, st) {
var f = document.forms[0];
if (!st) {
f.elements.width.value = img.width;
f.elements.height.value = img.height;
}
this.preloadImg = img;
},
changeAppearance : function() {
var ed = tinyMCEPopup.editor, f = document.forms[0], img = document.getElementById('alignSampleImg');
if (img) {
if (ed.getParam('inline_styles')) {
ed.dom.setAttrib(img, 'style', f.style.value);
} else {
img.align = f.align.value;
img.border = f.border.value;
img.hspace = f.hspace.value;
img.vspace = f.vspace.value;
}
}
},
changeHeight : function() {
var f = document.forms[0], tp, t = this;
if (!f.constrain.checked || !t.preloadImg) {
return;
}
if (f.width.value == "" || f.height.value == "")
return;
tp = (parseInt(f.width.value) / parseInt(t.preloadImg.width)) * t.preloadImg.height;
f.height.value = tp.toFixed(0);
},
changeWidth : function() {
var f = document.forms[0], tp, t = this;
if (!f.constrain.checked || !t.preloadImg) {
return;
}
if (f.width.value == "" || f.height.value == "")
return;
tp = (parseInt(f.height.value) / parseInt(t.preloadImg.height)) * t.preloadImg.width;
f.width.value = tp.toFixed(0);
},
updateStyle : function(ty) {
var dom = tinyMCEPopup.dom, st, v, f = document.forms[0], img = dom.create('img', {style : dom.get('style').value});
if (tinyMCEPopup.editor.settings.inline_styles) {
// Handle align
if (ty == 'align') {
dom.setStyle(img, 'float', '');
dom.setStyle(img, 'vertical-align', '');
v = getSelectValue(f, 'align');
if (v) {
if (v == 'left' || v == 'right')
dom.setStyle(img, 'float', v);
else
img.style.verticalAlign = v;
}
}
// Handle border
if (ty == 'border') {
dom.setStyle(img, 'border', '');
v = f.border.value;
if (v || v == '0') {
if (v == '0')
img.style.border = '0';
else
img.style.border = v + 'px solid black';
}
}
// Handle hspace
if (ty == 'hspace') {
dom.setStyle(img, 'marginLeft', '');
dom.setStyle(img, 'marginRight', '');
v = f.hspace.value;
if (v) {
img.style.marginLeft = v + 'px';
img.style.marginRight = v + 'px';
}
}
// Handle vspace
if (ty == 'vspace') {
dom.setStyle(img, 'marginTop', '');
dom.setStyle(img, 'marginBottom', '');
v = f.vspace.value;
if (v) {
img.style.marginTop = v + 'px';
img.style.marginBottom = v + 'px';
}
}
// Merge
dom.get('style').value = dom.serializeStyle(dom.parseStyle(img.style.cssText), 'img');
}
},
changeMouseMove : function() {
},
showPreviewImage : function(u, st) {
if (!u) {
tinyMCEPopup.dom.setHTML('prev', '');
return;
}
if (!st && tinyMCEPopup.getParam("advimage_update_dimensions_onchange", true))
this.resetImageData();
u = tinyMCEPopup.editor.documentBaseURI.toAbsolute(u);
if (!st)
tinyMCEPopup.dom.setHTML('prev', '<img id="previewImg" src="' + u + '" border="0" onload="ImageDialog.updateImageData(this);" onerror="ImageDialog.resetImageData();" />');
else
tinyMCEPopup.dom.setHTML('prev', '<img id="previewImg" src="' + u + '" border="0" onload="ImageDialog.updateImageData(this, 1);" />');
}
};
ImageDialog.preInit();
tinyMCEPopup.onInit.add(ImageDialog.init, ImageDialog);
@@ -0,0 +1,43 @@
tinyMCE.addI18n('en.advimage_dlg',{
tab_general:"General",
tab_appearance:"Appearance",
tab_advanced:"Advanced",
general:"General",
title:"Title",
preview:"Preview",
constrain_proportions:"Constrain proportions",
langdir:"Language direction",
langcode:"Language code",
long_desc:"Long description link",
style:"Style",
classes:"Classes",
ltr:"Left to right",
rtl:"Right to left",
id:"Id",
map:"Image map",
swap_image:"Swap image",
alt_image:"Alternative image",
mouseover:"for mouse over",
mouseout:"for mouse out",
misc:"Miscellaneous",
example_img:"Appearance preview image",
missing_alt:"Are you sure you want to continue without including an Image Description? Without it the image may not be accessible to some users with disabilities, or to those using a text browser, or browsing the Web with images turned off.",
dialog_title:"Insert/edit image",
src:"Image URL",
alt:"Image description",
list:"Image list",
border:"Border",
dimensions:"Dimensions",
vspace:"Vertical space",
hspace:"Horizontal space",
align:"Alignment",
align_baseline:"Baseline",
align_top:"Top",
align_middle:"Middle",
align_bottom:"Bottom",
align_texttop:"Text top",
align_textbottom:"Text bottom",
align_left:"Left",
align_right:"Right",
image_list:"Image list"
});
@@ -0,0 +1,8 @@
.mceLinkList, .mceAnchorList, #targetlist {width:280px;}
.mceActionPanel {margin-top:7px;}
.panel_wrapper div.current {height:320px;}
#classlist, #title, #href {width:280px;}
#popupurl, #popupname {width:200px;}
#popupwidth, #popupheight, #popupleft, #popuptop {width:30px;vertical-align:middle;text-align:center;}
#id, #style, #classes, #target, #dir, #hreflang, #lang, #charset, #type, #rel, #rev, #tabindex, #accesskey {width:200px;}
#events_panel input {width:200px;}
@@ -0,0 +1 @@
(function(){tinymce.create("tinymce.plugins.AdvancedLinkPlugin",{init:function(a,b){this.editor=a;a.addCommand("mceAdvLink",function(){var c=a.selection;if(c.isCollapsed()&&!a.dom.getParent(c.getNode(),"A")){return}a.windowManager.open({file:b+"/link.htm",width:480+parseInt(a.getLang("advlink.delta_width",0)),height:400+parseInt(a.getLang("advlink.delta_height",0)),inline:1},{plugin_url:b})});a.addButton("link",{title:"advlink.link_desc",cmd:"mceAdvLink"});a.addShortcut("ctrl+k","advlink.advlink_desc","mceAdvLink");a.onNodeChange.add(function(d,c,f,e){c.setDisabled("link",e&&f.nodeName!="A");c.setActive("link",f.nodeName=="A"&&!f.name)})},getInfo:function(){return{longname:"Advanced link",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advlink",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("advlink",tinymce.plugins.AdvancedLinkPlugin)})();
@@ -0,0 +1,61 @@
/**
* editor_plugin_src.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function() {
tinymce.create('tinymce.plugins.AdvancedLinkPlugin', {
init : function(ed, url) {
this.editor = ed;
// Register commands
ed.addCommand('mceAdvLink', function() {
var se = ed.selection;
// No selection and not in link
if (se.isCollapsed() && !ed.dom.getParent(se.getNode(), 'A'))
return;
ed.windowManager.open({
file : url + '/link.htm',
width : 480 + parseInt(ed.getLang('advlink.delta_width', 0)),
height : 400 + parseInt(ed.getLang('advlink.delta_height', 0)),
inline : 1
}, {
plugin_url : url
});
});
// Register buttons
ed.addButton('link', {
title : 'advlink.link_desc',
cmd : 'mceAdvLink'
});
ed.addShortcut('ctrl+k', 'advlink.advlink_desc', 'mceAdvLink');
ed.onNodeChange.add(function(ed, cm, n, co) {
cm.setDisabled('link', co && n.nodeName != 'A');
cm.setActive('link', n.nodeName == 'A' && !n.name);
});
},
getInfo : function() {
return {
longname : 'Advanced link',
author : 'Moxiecode Systems AB',
authorurl : 'http://tinymce.moxiecode.com',
infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advlink',
version : tinymce.majorVersion + "." + tinymce.minorVersion
};
}
});
// Register plugin
tinymce.PluginManager.add('advlink', tinymce.plugins.AdvancedLinkPlugin);
})();
@@ -0,0 +1,528 @@
/* Functions for the advlink plugin popup */
tinyMCEPopup.requireLangPack();
var templates = {
"window.open" : "window.open('${url}','${target}','${options}')"
};
function preinit() {
var url;
if (url = tinyMCEPopup.getParam("external_link_list_url"))
document.write('<script language="javascript" type="text/javascript" src="' + tinyMCEPopup.editor.documentBaseURI.toAbsolute(url) + '"></script>');
}
function changeClass() {
var f = document.forms[0];
f.classes.value = getSelectValue(f, 'classlist');
}
function init() {
tinyMCEPopup.resizeToInnerSize();
var formObj = document.forms[0];
var inst = tinyMCEPopup.editor;
var elm = inst.selection.getNode();
var action = "insert";
var html;
document.getElementById('hrefbrowsercontainer').innerHTML = getBrowserHTML('hrefbrowser','href','file','advlink');
document.getElementById('popupurlbrowsercontainer').innerHTML = getBrowserHTML('popupurlbrowser','popupurl','file','advlink');
document.getElementById('linklisthrefcontainer').innerHTML = getLinkListHTML('linklisthref','href');
document.getElementById('anchorlistcontainer').innerHTML = getAnchorListHTML('anchorlist','href');
document.getElementById('targetlistcontainer').innerHTML = getTargetListHTML('targetlist','target');
// Link list
html = getLinkListHTML('linklisthref','href');
if (html == "")
document.getElementById("linklisthrefrow").style.display = 'none';
else
document.getElementById("linklisthrefcontainer").innerHTML = html;
// Resize some elements
if (isVisible('hrefbrowser'))
document.getElementById('href').style.width = '260px';
if (isVisible('popupurlbrowser'))
document.getElementById('popupurl').style.width = '180px';
elm = inst.dom.getParent(elm, "A");
if (elm != null && elm.nodeName == "A")
action = "update";
formObj.insert.value = tinyMCEPopup.getLang(action, 'Insert', true);
setPopupControlsDisabled(true);
if (action == "update") {
var href = inst.dom.getAttrib(elm, 'href');
var onclick = inst.dom.getAttrib(elm, 'onclick');
// Setup form data
setFormValue('href', href);
setFormValue('title', inst.dom.getAttrib(elm, 'title'));
setFormValue('id', inst.dom.getAttrib(elm, 'id'));
setFormValue('style', inst.dom.getAttrib(elm, "style"));
setFormValue('rel', inst.dom.getAttrib(elm, 'rel'));
setFormValue('rev', inst.dom.getAttrib(elm, 'rev'));
setFormValue('charset', inst.dom.getAttrib(elm, 'charset'));
setFormValue('hreflang', inst.dom.getAttrib(elm, 'hreflang'));
setFormValue('dir', inst.dom.getAttrib(elm, 'dir'));
setFormValue('lang', inst.dom.getAttrib(elm, 'lang'));
setFormValue('tabindex', inst.dom.getAttrib(elm, 'tabindex', typeof(elm.tabindex) != "undefined" ? elm.tabindex : ""));
setFormValue('accesskey', inst.dom.getAttrib(elm, 'accesskey', typeof(elm.accesskey) != "undefined" ? elm.accesskey : ""));
setFormValue('type', inst.dom.getAttrib(elm, 'type'));
setFormValue('onfocus', inst.dom.getAttrib(elm, 'onfocus'));
setFormValue('onblur', inst.dom.getAttrib(elm, 'onblur'));
setFormValue('onclick', onclick);
setFormValue('ondblclick', inst.dom.getAttrib(elm, 'ondblclick'));
setFormValue('onmousedown', inst.dom.getAttrib(elm, 'onmousedown'));
setFormValue('onmouseup', inst.dom.getAttrib(elm, 'onmouseup'));
setFormValue('onmouseover', inst.dom.getAttrib(elm, 'onmouseover'));
setFormValue('onmousemove', inst.dom.getAttrib(elm, 'onmousemove'));
setFormValue('onmouseout', inst.dom.getAttrib(elm, 'onmouseout'));
setFormValue('onkeypress', inst.dom.getAttrib(elm, 'onkeypress'));
setFormValue('onkeydown', inst.dom.getAttrib(elm, 'onkeydown'));
setFormValue('onkeyup', inst.dom.getAttrib(elm, 'onkeyup'));
setFormValue('target', inst.dom.getAttrib(elm, 'target'));
setFormValue('classes', inst.dom.getAttrib(elm, 'class'));
// Parse onclick data
if (onclick != null && onclick.indexOf('window.open') != -1)
parseWindowOpen(onclick);
else
parseFunction(onclick);
// Select by the values
selectByValue(formObj, 'dir', inst.dom.getAttrib(elm, 'dir'));
selectByValue(formObj, 'rel', inst.dom.getAttrib(elm, 'rel'));
selectByValue(formObj, 'rev', inst.dom.getAttrib(elm, 'rev'));
selectByValue(formObj, 'linklisthref', href);
if (href.charAt(0) == '#')
selectByValue(formObj, 'anchorlist', href);
addClassesToList('classlist', 'advlink_styles');
selectByValue(formObj, 'classlist', inst.dom.getAttrib(elm, 'class'), true);
selectByValue(formObj, 'targetlist', inst.dom.getAttrib(elm, 'target'), true);
} else
addClassesToList('classlist', 'advlink_styles');
}
function checkPrefix(n) {
if (n.value && Validator.isEmail(n) && !/^\s*mailto:/i.test(n.value) && confirm(tinyMCEPopup.getLang('advlink_dlg.is_email')))
n.value = 'mailto:' + n.value;
if (/^\s*www\./i.test(n.value) && confirm(tinyMCEPopup.getLang('advlink_dlg.is_external')))
n.value = 'http://' + n.value;
}
function setFormValue(name, value) {
document.forms[0].elements[name].value = value;
}
function parseWindowOpen(onclick) {
var formObj = document.forms[0];
// Preprocess center code
if (onclick.indexOf('return false;') != -1) {
formObj.popupreturn.checked = true;
onclick = onclick.replace('return false;', '');
} else
formObj.popupreturn.checked = false;
var onClickData = parseLink(onclick);
if (onClickData != null) {
formObj.ispopup.checked = true;
setPopupControlsDisabled(false);
var onClickWindowOptions = parseOptions(onClickData['options']);
var url = onClickData['url'];
formObj.popupname.value = onClickData['target'];
formObj.popupurl.value = url;
formObj.popupwidth.value = getOption(onClickWindowOptions, 'width');
formObj.popupheight.value = getOption(onClickWindowOptions, 'height');
formObj.popupleft.value = getOption(onClickWindowOptions, 'left');
formObj.popuptop.value = getOption(onClickWindowOptions, 'top');
if (formObj.popupleft.value.indexOf('screen') != -1)
formObj.popupleft.value = "c";
if (formObj.popuptop.value.indexOf('screen') != -1)
formObj.popuptop.value = "c";
formObj.popuplocation.checked = getOption(onClickWindowOptions, 'location') == "yes";
formObj.popupscrollbars.checked = getOption(onClickWindowOptions, 'scrollbars') == "yes";
formObj.popupmenubar.checked = getOption(onClickWindowOptions, 'menubar') == "yes";
formObj.popupresizable.checked = getOption(onClickWindowOptions, 'resizable') == "yes";
formObj.popuptoolbar.checked = getOption(onClickWindowOptions, 'toolbar') == "yes";
formObj.popupstatus.checked = getOption(onClickWindowOptions, 'status') == "yes";
formObj.popupdependent.checked = getOption(onClickWindowOptions, 'dependent') == "yes";
buildOnClick();
}
}
function parseFunction(onclick) {
var formObj = document.forms[0];
var onClickData = parseLink(onclick);
// TODO: Add stuff here
}
function getOption(opts, name) {
return typeof(opts[name]) == "undefined" ? "" : opts[name];
}
function setPopupControlsDisabled(state) {
var formObj = document.forms[0];
formObj.popupname.disabled = state;
formObj.popupurl.disabled = state;
formObj.popupwidth.disabled = state;
formObj.popupheight.disabled = state;
formObj.popupleft.disabled = state;
formObj.popuptop.disabled = state;
formObj.popuplocation.disabled = state;
formObj.popupscrollbars.disabled = state;
formObj.popupmenubar.disabled = state;
formObj.popupresizable.disabled = state;
formObj.popuptoolbar.disabled = state;
formObj.popupstatus.disabled = state;
formObj.popupreturn.disabled = state;
formObj.popupdependent.disabled = state;
setBrowserDisabled('popupurlbrowser', state);
}
function parseLink(link) {
link = link.replace(new RegExp('&#39;', 'g'), "'");
var fnName = link.replace(new RegExp("\\s*([A-Za-z0-9\.]*)\\s*\\(.*", "gi"), "$1");
// Is function name a template function
var template = templates[fnName];
if (template) {
// Build regexp
var variableNames = template.match(new RegExp("'?\\$\\{[A-Za-z0-9\.]*\\}'?", "gi"));
var regExp = "\\s*[A-Za-z0-9\.]*\\s*\\(";
var replaceStr = "";
for (var i=0; i<variableNames.length; i++) {
// Is string value
if (variableNames[i].indexOf("'${") != -1)
regExp += "'(.*)'";
else // Number value
regExp += "([0-9]*)";
replaceStr += "$" + (i+1);
// Cleanup variable name
variableNames[i] = variableNames[i].replace(new RegExp("[^A-Za-z0-9]", "gi"), "");
if (i != variableNames.length-1) {
regExp += "\\s*,\\s*";
replaceStr += "<delim>";
} else
regExp += ".*";
}
regExp += "\\);?";
// Build variable array
var variables = [];
variables["_function"] = fnName;
var variableValues = link.replace(new RegExp(regExp, "gi"), replaceStr).split('<delim>');
for (var i=0; i<variableNames.length; i++)
variables[variableNames[i]] = variableValues[i];
return variables;
}
return null;
}
function parseOptions(opts) {
if (opts == null || opts == "")
return [];
// Cleanup the options
opts = opts.toLowerCase();
opts = opts.replace(/;/g, ",");
opts = opts.replace(/[^0-9a-z=,]/g, "");
var optionChunks = opts.split(',');
var options = [];
for (var i=0; i<optionChunks.length; i++) {
var parts = optionChunks[i].split('=');
if (parts.length == 2)
options[parts[0]] = parts[1];
}
return options;
}
function buildOnClick() {
var formObj = document.forms[0];
if (!formObj.ispopup.checked) {
formObj.onclick.value = "";
return;
}
var onclick = "window.open('";
var url = formObj.popupurl.value;
onclick += url + "','";
onclick += formObj.popupname.value + "','";
if (formObj.popuplocation.checked)
onclick += "location=yes,";
if (formObj.popupscrollbars.checked)
onclick += "scrollbars=yes,";
if (formObj.popupmenubar.checked)
onclick += "menubar=yes,";
if (formObj.popupresizable.checked)
onclick += "resizable=yes,";
if (formObj.popuptoolbar.checked)
onclick += "toolbar=yes,";
if (formObj.popupstatus.checked)
onclick += "status=yes,";
if (formObj.popupdependent.checked)
onclick += "dependent=yes,";
if (formObj.popupwidth.value != "")
onclick += "width=" + formObj.popupwidth.value + ",";
if (formObj.popupheight.value != "")
onclick += "height=" + formObj.popupheight.value + ",";
if (formObj.popupleft.value != "") {
if (formObj.popupleft.value != "c")
onclick += "left=" + formObj.popupleft.value + ",";
else
onclick += "left='+(screen.availWidth/2-" + (formObj.popupwidth.value/2) + ")+',";
}
if (formObj.popuptop.value != "") {
if (formObj.popuptop.value != "c")
onclick += "top=" + formObj.popuptop.value + ",";
else
onclick += "top='+(screen.availHeight/2-" + (formObj.popupheight.value/2) + ")+',";
}
if (onclick.charAt(onclick.length-1) == ',')
onclick = onclick.substring(0, onclick.length-1);
onclick += "');";
if (formObj.popupreturn.checked)
onclick += "return false;";
// tinyMCE.debug(onclick);
formObj.onclick.value = onclick;
if (formObj.href.value == "")
formObj.href.value = url;
}
function setAttrib(elm, attrib, value) {
var formObj = document.forms[0];
var valueElm = formObj.elements[attrib.toLowerCase()];
var dom = tinyMCEPopup.editor.dom;
if (typeof(value) == "undefined" || value == null) {
value = "";
if (valueElm)
value = valueElm.value;
}
// Clean up the style
if (attrib == 'style')
value = dom.serializeStyle(dom.parseStyle(value), 'a');
dom.setAttrib(elm, attrib, value);
}
function getAnchorListHTML(id, target) {
var inst = tinyMCEPopup.editor;
var nodes = inst.dom.select('a.mceItemAnchor,img.mceItemAnchor'), name, i;
var html = "";
html += '<select id="' + id + '" name="' + id + '" class="mceAnchorList" o2nfocus="tinyMCE.addSelectAccessibility(event, this, window);" onchange="this.form.' + target + '.value=';
html += 'this.options[this.selectedIndex].value;">';
html += '<option value="">---</option>';
for (i=0; i<nodes.length; i++) {
if ((name = inst.dom.getAttrib(nodes[i], "name")) != "")
html += '<option value="#' + name + '">' + name + '</option>';
}
html += '</select>';
return html;
}
function insertAction() {
var inst = tinyMCEPopup.editor;
var elm, elementArray, i;
elm = inst.selection.getNode();
checkPrefix(document.forms[0].href);
elm = inst.dom.getParent(elm, "A");
// Remove element if there is no href
if (!document.forms[0].href.value) {
tinyMCEPopup.execCommand("mceBeginUndoLevel");
i = inst.selection.getBookmark();
inst.dom.remove(elm, 1);
inst.selection.moveToBookmark(i);
tinyMCEPopup.execCommand("mceEndUndoLevel");
tinyMCEPopup.close();
return;
}
tinyMCEPopup.execCommand("mceBeginUndoLevel");
// Create new anchor elements
if (elm == null) {
inst.getDoc().execCommand("unlink", false, null);
tinyMCEPopup.execCommand("CreateLink", false, "#mce_temp_url#", {skip_undo : 1});
elementArray = tinymce.grep(inst.dom.select("a"), function(n) {return inst.dom.getAttrib(n, 'href') == '#mce_temp_url#';});
for (i=0; i<elementArray.length; i++)
setAllAttribs(elm = elementArray[i]);
} else
setAllAttribs(elm);
// Don't move caret if selection was image
if (elm.childNodes.length != 1 || elm.firstChild.nodeName != 'IMG') {
inst.focus();
inst.selection.select(elm);
inst.selection.collapse(0);
tinyMCEPopup.storeSelection();
}
tinyMCEPopup.execCommand("mceEndUndoLevel");
tinyMCEPopup.close();
}
function setAllAttribs(elm) {
var formObj = document.forms[0];
var href = formObj.href.value;
var target = getSelectValue(formObj, 'targetlist');
setAttrib(elm, 'href', href);
setAttrib(elm, 'title');
setAttrib(elm, 'target', target == '_self' ? '' : target);
setAttrib(elm, 'id');
setAttrib(elm, 'style');
setAttrib(elm, 'class', getSelectValue(formObj, 'classlist'));
setAttrib(elm, 'rel');
setAttrib(elm, 'rev');
setAttrib(elm, 'charset');
setAttrib(elm, 'hreflang');
setAttrib(elm, 'dir');
setAttrib(elm, 'lang');
setAttrib(elm, 'tabindex');
setAttrib(elm, 'accesskey');
setAttrib(elm, 'type');
setAttrib(elm, 'onfocus');
setAttrib(elm, 'onblur');
setAttrib(elm, 'onclick');
setAttrib(elm, 'ondblclick');
setAttrib(elm, 'onmousedown');
setAttrib(elm, 'onmouseup');
setAttrib(elm, 'onmouseover');
setAttrib(elm, 'onmousemove');
setAttrib(elm, 'onmouseout');
setAttrib(elm, 'onkeypress');
setAttrib(elm, 'onkeydown');
setAttrib(elm, 'onkeyup');
// Refresh in old MSIE
if (tinyMCE.isMSIE5)
elm.outerHTML = elm.outerHTML;
}
function getSelectValue(form_obj, field_name) {
var elm = form_obj.elements[field_name];
if (!elm || elm.options == null || elm.selectedIndex == -1)
return "";
return elm.options[elm.selectedIndex].value;
}
function getLinkListHTML(elm_id, target_form_element, onchange_func) {
if (typeof(tinyMCELinkList) == "undefined" || tinyMCELinkList.length == 0)
return "";
var html = "";
html += '<select id="' + elm_id + '" name="' + elm_id + '"';
html += ' class="mceLinkList" onfoc2us="tinyMCE.addSelectAccessibility(event, this, window);" onchange="this.form.' + target_form_element + '.value=';
html += 'this.options[this.selectedIndex].value;';
if (typeof(onchange_func) != "undefined")
html += onchange_func + '(\'' + target_form_element + '\',this.options[this.selectedIndex].text,this.options[this.selectedIndex].value);';
html += '"><option value="">---</option>';
for (var i=0; i<tinyMCELinkList.length; i++)
html += '<option value="' + tinyMCELinkList[i][1] + '">' + tinyMCELinkList[i][0] + '</option>';
html += '</select>';
return html;
// tinyMCE.debug('-- image list start --', html, '-- image list end --');
}
function getTargetListHTML(elm_id, target_form_element) {
var targets = tinyMCEPopup.getParam('theme_advanced_link_targets', '').split(';');
var html = '';
html += '<select id="' + elm_id + '" name="' + elm_id + '" onf2ocus="tinyMCE.addSelectAccessibility(event, this, window);" onchange="this.form.' + target_form_element + '.value=';
html += 'this.options[this.selectedIndex].value;">';
html += '<option value="_self">' + tinyMCEPopup.getLang('advlink_dlg.target_same') + '</option>';
html += '<option value="_blank">' + tinyMCEPopup.getLang('advlink_dlg.target_blank') + ' (_blank)</option>';
html += '<option value="_parent">' + tinyMCEPopup.getLang('advlink_dlg.target_parent') + ' (_parent)</option>';
html += '<option value="_top">' + tinyMCEPopup.getLang('advlink_dlg.target_top') + ' (_top)</option>';
for (var i=0; i<targets.length; i++) {
var key, value;
if (targets[i] == "")
continue;
key = targets[i].split('=')[0];
value = targets[i].split('=')[1];
html += '<option value="' + key + '">' + value + ' (' + key + ')</option>';
}
html += '</select>';
return html;
}
// While loading
preinit();
tinyMCEPopup.onInit.add(init);
@@ -0,0 +1,52 @@
tinyMCE.addI18n('en.advlink_dlg',{
title:"Insert/edit link",
url:"Link URL",
target:"Target",
titlefield:"Title",
is_email:"The URL you entered seems to be an email address, do you want to add the required mailto: prefix?",
is_external:"The URL you entered seems to external link, do you want to add the required http:// prefix?",
list:"Link list",
general_tab:"General",
popup_tab:"Popup",
events_tab:"Events",
advanced_tab:"Advanced",
general_props:"General properties",
popup_props:"Popup properties",
event_props:"Events",
advanced_props:"Advanced properties",
popup_opts:"Options",
anchor_names:"Anchors",
target_same:"Open in this window / frame",
target_parent:"Open in parent window / frame",
target_top:"Open in top frame (replaces all frames)",
target_blank:"Open in new window",
popup:"Javascript popup",
popup_url:"Popup URL",
popup_name:"Window name",
popup_return:"Insert 'return false'",
popup_scrollbars:"Show scrollbars",
popup_statusbar:"Show status bar",
popup_toolbar:"Show toolbars",
popup_menubar:"Show menu bar",
popup_location:"Show location bar",
popup_resizable:"Make window resizable",
popup_dependent:"Dependent (Mozilla/Firefox only)",
popup_size:"Size",
popup_position:"Position (X/Y)",
id:"Id",
style:"Style",
classes:"Classes",
target_name:"Target name",
langdir:"Language direction",
target_langcode:"Target language",
langcode:"Language code",
encoding:"Target character encoding",
mime:"Target MIME type",
rel:"Relationship page to target",
rev:"Relationship target to page",
tabindex:"Tabindex",
accesskey:"Accesskey",
ltr:"Left to right",
rtl:"Right to left",
link_list:"Link list"
});
@@ -0,0 +1,333 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>{#advlink_dlg.title}</title>
<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
<script type="text/javascript" src="../../utils/mctabs.js"></script>
<script type="text/javascript" src="../../utils/form_utils.js"></script>
<script type="text/javascript" src="../../utils/validate.js"></script>
<script type="text/javascript" src="js/advlink.js"></script>
<link href="css/advlink.css" rel="stylesheet" type="text/css" />
</head>
<body id="advlink" style="display: none">
<form onsubmit="insertAction();return false;" action="#">
<div class="tabs">
<ul>
<li id="general_tab" class="current"><span><a href="javascript:mcTabs.displayTab('general_tab','general_panel');" onmousedown="return false;">{#advlink_dlg.general_tab}</a></span></li>
<li id="popup_tab"><span><a href="javascript:mcTabs.displayTab('popup_tab','popup_panel');" onmousedown="return false;">{#advlink_dlg.popup_tab}</a></span></li>
<li id="events_tab"><span><a href="javascript:mcTabs.displayTab('events_tab','events_panel');" onmousedown="return false;">{#advlink_dlg.events_tab}</a></span></li>
<li id="advanced_tab"><span><a href="javascript:mcTabs.displayTab('advanced_tab','advanced_panel');" onmousedown="return false;">{#advlink_dlg.advanced_tab}</a></span></li>
</ul>
</div>
<div class="panel_wrapper">
<div id="general_panel" class="panel current">
<fieldset>
<legend>{#advlink_dlg.general_props}</legend>
<table border="0" cellpadding="4" cellspacing="0">
<tr>
<td class="nowrap"><label id="hreflabel" for="href">{#advlink_dlg.url}</label></td>
<td><table border="0" cellspacing="0" cellpadding="0">
<tr>
<td><input id="href" name="href" type="text" class="mceFocus" value="" onchange="selectByValue(this.form,'linklisthref',this.value);" /></td>
<td id="hrefbrowsercontainer">&nbsp;</td>
</tr>
</table></td>
</tr>
<tr id="linklisthrefrow">
<td class="column1"><label for="linklisthref">{#advlink_dlg.list}</label></td>
<td colspan="2" id="linklisthrefcontainer"><select id="linklisthref"><option value=""></option></select></td>
</tr>
<tr>
<td class="column1"><label for="anchorlist">{#advlink_dlg.anchor_names}</label></td>
<td colspan="2" id="anchorlistcontainer"><select id="anchorlist"><option value=""></option></select></td>
</tr>
<tr>
<td><label id="targetlistlabel" for="targetlist">{#advlink_dlg.target}</label></td>
<td id="targetlistcontainer"><select id="targetlist"><option value=""></option></select></td>
</tr>
<tr>
<td class="nowrap"><label id="titlelabel" for="title">{#advlink_dlg.titlefield}</label></td>
<td><input id="title" name="title" type="text" value="" /></td>
</tr>
<tr>
<td><label id="classlabel" for="classlist">{#class_name}</label></td>
<td>
<select id="classlist" name="classlist" onchange="changeClass();">
<option value="" selected="selected">{#not_set}</option>
</select>
</td>
</tr>
</table>
</fieldset>
</div>
<div id="popup_panel" class="panel">
<fieldset>
<legend>{#advlink_dlg.popup_props}</legend>
<input type="checkbox" id="ispopup" name="ispopup" class="radio" onclick="setPopupControlsDisabled(!this.checked);buildOnClick();" />
<label id="ispopuplabel" for="ispopup">{#advlink_dlg.popup}</label>
<table border="0" cellpadding="0" cellspacing="4">
<tr>
<td class="nowrap"><label for="popupurl">{#advlink_dlg.popup_url}</label>&nbsp;</td>
<td>
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td><input type="text" name="popupurl" id="popupurl" value="" onchange="buildOnClick();" /></td>
<td id="popupurlbrowsercontainer">&nbsp;</td>
</tr>
</table>
</td>
</tr>
<tr>
<td class="nowrap"><label for="popupname">{#advlink_dlg.popup_name}</label>&nbsp;</td>
<td><input type="text" name="popupname" id="popupname" value="" onchange="buildOnClick();" /></td>
</tr>
<tr>
<td class="nowrap"><label>{#advlink_dlg.popup_size}</label>&nbsp;</td>
<td class="nowrap">
<input type="text" id="popupwidth" name="popupwidth" value="" onchange="buildOnClick();" /> x
<input type="text" id="popupheight" name="popupheight" value="" onchange="buildOnClick();" /> px
</td>
</tr>
<tr>
<td class="nowrap" id="labelleft"><label>{#advlink_dlg.popup_position}</label>&nbsp;</td>
<td class="nowrap">
<input type="text" id="popupleft" name="popupleft" value="" onchange="buildOnClick();" /> /
<input type="text" id="popuptop" name="popuptop" value="" onchange="buildOnClick();" /> (c /c = center)
</td>
</tr>
</table>
<fieldset>
<legend>{#advlink_dlg.popup_opts}</legend>
<table border="0" cellpadding="0" cellspacing="4">
<tr>
<td><input type="checkbox" id="popuplocation" name="popuplocation" class="checkbox" onchange="buildOnClick();" /></td>
<td class="nowrap"><label id="popuplocationlabel" for="popuplocation">{#advlink_dlg.popup_location}</label></td>
<td><input type="checkbox" id="popupscrollbars" name="popupscrollbars" class="checkbox" onchange="buildOnClick();" /></td>
<td class="nowrap"><label id="popupscrollbarslabel" for="popupscrollbars">{#advlink_dlg.popup_scrollbars}</label></td>
</tr>
<tr>
<td><input type="checkbox" id="popupmenubar" name="popupmenubar" class="checkbox" onchange="buildOnClick();" /></td>
<td class="nowrap"><label id="popupmenubarlabel" for="popupmenubar">{#advlink_dlg.popup_menubar}</label></td>
<td><input type="checkbox" id="popupresizable" name="popupresizable" class="checkbox" onchange="buildOnClick();" /></td>
<td class="nowrap"><label id="popupresizablelabel" for="popupresizable">{#advlink_dlg.popup_resizable}</label></td>
</tr>
<tr>
<td><input type="checkbox" id="popuptoolbar" name="popuptoolbar" class="checkbox" onchange="buildOnClick();" /></td>
<td class="nowrap"><label id="popuptoolbarlabel" for="popuptoolbar">{#advlink_dlg.popup_toolbar}</label></td>
<td><input type="checkbox" id="popupdependent" name="popupdependent" class="checkbox" onchange="buildOnClick();" /></td>
<td class="nowrap"><label id="popupdependentlabel" for="popupdependent">{#advlink_dlg.popup_dependent}</label></td>
</tr>
<tr>
<td><input type="checkbox" id="popupstatus" name="popupstatus" class="checkbox" onchange="buildOnClick();" /></td>
<td class="nowrap"><label id="popupstatuslabel" for="popupstatus">{#advlink_dlg.popup_statusbar}</label></td>
<td><input type="checkbox" id="popupreturn" name="popupreturn" class="checkbox" onchange="buildOnClick();" checked="checked" /></td>
<td class="nowrap"><label id="popupreturnlabel" for="popupreturn">{#advlink_dlg.popup_return}</label></td>
</tr>
</table>
</fieldset>
</fieldset>
</div>
<div id="advanced_panel" class="panel">
<fieldset>
<legend>{#advlink_dlg.advanced_props}</legend>
<table border="0" cellpadding="0" cellspacing="4">
<tr>
<td class="column1"><label id="idlabel" for="id">{#advlink_dlg.id}</label></td>
<td><input id="id" name="id" type="text" value="" /></td>
</tr>
<tr>
<td><label id="stylelabel" for="style">{#advlink_dlg.style}</label></td>
<td><input type="text" id="style" name="style" value="" /></td>
</tr>
<tr>
<td><label id="classeslabel" for="classes">{#advlink_dlg.classes}</label></td>
<td><input type="text" id="classes" name="classes" value="" onchange="selectByValue(this.form,'classlist',this.value,true);" /></td>
</tr>
<tr>
<td><label id="targetlabel" for="target">{#advlink_dlg.target_name}</label></td>
<td><input type="text" id="target" name="target" value="" onchange="selectByValue(this.form,'targetlist',this.value,true);" /></td>
</tr>
<tr>
<td class="column1"><label id="dirlabel" for="dir">{#advlink_dlg.langdir}</label></td>
<td>
<select id="dir" name="dir">
<option value="">{#not_set}</option>
<option value="ltr">{#advlink_dlg.ltr}</option>
<option value="rtl">{#advlink_dlg.rtl}</option>
</select>
</td>
</tr>
<tr>
<td><label id="hreflanglabel" for="hreflang">{#advlink_dlg.target_langcode}</label></td>
<td><input type="text" id="hreflang" name="hreflang" value="" /></td>
</tr>
<tr>
<td class="column1"><label id="langlabel" for="lang">{#advlink_dlg.langcode}</label></td>
<td>
<input id="lang" name="lang" type="text" value="" />
</td>
</tr>
<tr>
<td><label id="charsetlabel" for="charset">{#advlink_dlg.encoding}</label></td>
<td><input type="text" id="charset" name="charset" value="" /></td>
</tr>
<tr>
<td><label id="typelabel" for="type">{#advlink_dlg.mime}</label></td>
<td><input type="text" id="type" name="type" value="" /></td>
</tr>
<tr>
<td><label id="rellabel" for="rel">{#advlink_dlg.rel}</label></td>
<td><select id="rel" name="rel">
<option value="">{#not_set}</option>
<option value="lightbox">Lightbox</option>
<option value="alternate">Alternate</option>
<option value="designates">Designates</option>
<option value="stylesheet">Stylesheet</option>
<option value="start">Start</option>
<option value="next">Next</option>
<option value="prev">Prev</option>
<option value="contents">Contents</option>
<option value="index">Index</option>
<option value="glossary">Glossary</option>
<option value="copyright">Copyright</option>
<option value="chapter">Chapter</option>
<option value="subsection">Subsection</option>
<option value="appendix">Appendix</option>
<option value="help">Help</option>
<option value="bookmark">Bookmark</option>
<option value="nofollow">No Follow</option>
<option value="tag">Tag</option>
</select>
</td>
</tr>
<tr>
<td><label id="revlabel" for="rev">{#advlink_dlg.rev}</label></td>
<td><select id="rev" name="rev">
<option value="">{#not_set}</option>
<option value="alternate">Alternate</option>
<option value="designates">Designates</option>
<option value="stylesheet">Stylesheet</option>
<option value="start">Start</option>
<option value="next">Next</option>
<option value="prev">Prev</option>
<option value="contents">Contents</option>
<option value="index">Index</option>
<option value="glossary">Glossary</option>
<option value="copyright">Copyright</option>
<option value="chapter">Chapter</option>
<option value="subsection">Subsection</option>
<option value="appendix">Appendix</option>
<option value="help">Help</option>
<option value="bookmark">Bookmark</option>
</select>
</td>
</tr>
<tr>
<td><label id="tabindexlabel" for="tabindex">{#advlink_dlg.tabindex}</label></td>
<td><input type="text" id="tabindex" name="tabindex" value="" /></td>
</tr>
<tr>
<td><label id="accesskeylabel" for="accesskey">{#advlink_dlg.accesskey}</label></td>
<td><input type="text" id="accesskey" name="accesskey" value="" /></td>
</tr>
</table>
</fieldset>
</div>
<div id="events_panel" class="panel">
<fieldset>
<legend>{#advlink_dlg.event_props}</legend>
<table border="0" cellpadding="0" cellspacing="4">
<tr>
<td class="column1"><label for="onfocus">onfocus</label></td>
<td><input id="onfocus" name="onfocus" type="text" value="" /></td>
</tr>
<tr>
<td class="column1"><label for="onblur">onblur</label></td>
<td><input id="onblur" name="onblur" type="text" value="" /></td>
</tr>
<tr>
<td class="column1"><label for="onclick">onclick</label></td>
<td><input id="onclick" name="onclick" type="text" value="" /></td>
</tr>
<tr>
<td class="column1"><label for="ondblclick">ondblclick</label></td>
<td><input id="ondblclick" name="ondblclick" type="text" value="" /></td>
</tr>
<tr>
<td class="column1"><label for="onmousedown">onmousedown</label></td>
<td><input id="onmousedown" name="onmousedown" type="text" value="" /></td>
</tr>
<tr>
<td class="column1"><label for="onmouseup">onmouseup</label></td>
<td><input id="onmouseup" name="onmouseup" type="text" value="" /></td>
</tr>
<tr>
<td class="column1"><label for="onmouseover">onmouseover</label></td>
<td><input id="onmouseover" name="onmouseover" type="text" value="" /></td>
</tr>
<tr>
<td class="column1"><label for="onmousemove">onmousemove</label></td>
<td><input id="onmousemove" name="onmousemove" type="text" value="" /></td>
</tr>
<tr>
<td class="column1"><label for="onmouseout">onmouseout</label></td>
<td><input id="onmouseout" name="onmouseout" type="text" value="" /></td>
</tr>
<tr>
<td class="column1"><label for="onkeypress">onkeypress</label></td>
<td><input id="onkeypress" name="onkeypress" type="text" value="" /></td>
</tr>
<tr>
<td class="column1"><label for="onkeydown">onkeydown</label></td>
<td><input id="onkeydown" name="onkeydown" type="text" value="" /></td>
</tr>
<tr>
<td class="column1"><label for="onkeyup">onkeyup</label></td>
<td><input id="onkeyup" name="onkeyup" type="text" value="" /></td>
</tr>
</table>
</fieldset>
</div>
</div>
<div class="mceActionPanel">
<input type="submit" id="insert" name="insert" value="{#insert}" />
<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
</div>
</form>
</body>
</html>
@@ -0,0 +1 @@
(function(){var a=tinymce.each;tinymce.create("tinymce.plugins.AdvListPlugin",{init:function(b,c){var d=this;d.editor=b;function e(g){var f=[];a(g.split(/,/),function(h){f.push({title:"advlist."+(h=="default"?"def":h.replace(/-/g,"_")),styles:{listStyleType:h=="default"?"":h}})});return f}d.numlist=b.getParam("advlist_number_styles")||e("default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman");d.bullist=b.getParam("advlist_bullet_styles")||e("default,circle,disc,square")},createControl:function(d,b){var f=this,e,h;if(d=="numlist"||d=="bullist"){if(f[d][0].title=="advlist.def"){h=f[d][0]}function c(i,k){var j=true;a(k.styles,function(m,l){if(f.editor.dom.getStyle(i,l)!=m){j=false;return false}});return j}function g(){var k,i=f.editor,l=i.dom,j=i.selection;k=l.getParent(j.getNode(),"ol,ul");if(!k||k.nodeName==(d=="bullist"?"OL":"UL")||c(k,h)){i.execCommand(d=="bullist"?"InsertUnorderedList":"InsertOrderedList")}if(h){k=l.getParent(j.getNode(),"ol,ul");if(k){l.setStyles(k,h.styles);k.removeAttribute("_mce_style")}}}e=b.createSplitButton(d,{title:"advanced."+d+"_desc","class":"mce_"+d,onclick:function(){g()}});e.onRenderMenu.add(function(i,j){j.onShowMenu.add(function(){var m=f.editor.dom,l=m.getParent(f.editor.selection.getNode(),"ol,ul"),k;if(l||h){k=f[d];a(j.items,function(n){var o=true;n.setSelected(0);if(l&&!n.isDisabled()){a(k,function(p){if(p.id==n.id){if(!c(l,p)){o=false;return false}}});if(o){n.setSelected(1)}}});if(!l){j.items[h.id].setSelected(1)}}});j.add({id:f.editor.dom.uniqueId(),title:"advlist.types","class":"mceMenuItemTitle"}).setDisabled(1);a(f[d],function(k){k.id=f.editor.dom.uniqueId();j.add({id:k.id,title:k.title,onclick:function(){h=k;g()}})})});return e}},getInfo:function(){return{longname:"Advanced lists",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advlist",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("advlist",tinymce.plugins.AdvListPlugin)})();
@@ -0,0 +1,154 @@
/**
* editor_plugin_src.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function() {
var each = tinymce.each;
tinymce.create('tinymce.plugins.AdvListPlugin', {
init : function(ed, url) {
var t = this;
t.editor = ed;
function buildFormats(str) {
var formats = [];
each(str.split(/,/), function(type) {
formats.push({
title : 'advlist.' + (type == 'default' ? 'def' : type.replace(/-/g, '_')),
styles : {
listStyleType : type == 'default' ? '' : type
}
});
});
return formats;
};
// Setup number formats from config or default
t.numlist = ed.getParam("advlist_number_styles") || buildFormats("default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman");
t.bullist = ed.getParam("advlist_bullet_styles") || buildFormats("default,circle,disc,square");
},
createControl: function(name, cm) {
var t = this, btn, format;
if (name == 'numlist' || name == 'bullist') {
// Default to first item if it's a default item
if (t[name][0].title == 'advlist.def')
format = t[name][0];
function hasFormat(node, format) {
var state = true;
each(format.styles, function(value, name) {
// Format doesn't match
if (t.editor.dom.getStyle(node, name) != value) {
state = false;
return false;
}
});
return state;
};
function applyListFormat() {
var list, ed = t.editor, dom = ed.dom, sel = ed.selection;
// Check for existing list element
list = dom.getParent(sel.getNode(), 'ol,ul');
// Switch/add list type if needed
if (!list || list.nodeName == (name == 'bullist' ? 'OL' : 'UL') || hasFormat(list, format))
ed.execCommand(name == 'bullist' ? 'InsertUnorderedList' : 'InsertOrderedList');
// Append styles to new list element
if (format) {
list = dom.getParent(sel.getNode(), 'ol,ul');
if (list) {
dom.setStyles(list, format.styles);
list.removeAttribute('_mce_style');
}
}
};
btn = cm.createSplitButton(name, {
title : 'advanced.' + name + '_desc',
'class' : 'mce_' + name,
onclick : function() {
applyListFormat();
}
});
btn.onRenderMenu.add(function(btn, menu) {
menu.onShowMenu.add(function() {
var dom = t.editor.dom, list = dom.getParent(t.editor.selection.getNode(), 'ol,ul'), fmtList;
if (list || format) {
fmtList = t[name];
// Unselect existing items
each(menu.items, function(item) {
var state = true;
item.setSelected(0);
if (list && !item.isDisabled()) {
each(fmtList, function(fmt) {
if (fmt.id == item.id) {
if (!hasFormat(list, fmt)) {
state = false;
return false;
}
}
});
if (state)
item.setSelected(1);
}
});
// Select the current format
if (!list)
menu.items[format.id].setSelected(1);
}
});
menu.add({id : t.editor.dom.uniqueId(), title : 'advlist.types', 'class' : 'mceMenuItemTitle'}).setDisabled(1);
each(t[name], function(item) {
item.id = t.editor.dom.uniqueId();
menu.add({id : item.id, title : item.title, onclick : function() {
format = item;
applyListFormat();
}});
});
});
return btn;
}
},
getInfo : function() {
return {
longname : 'Advanced lists',
author : 'Moxiecode Systems AB',
authorurl : 'http://tinymce.moxiecode.com',
infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advlist',
version : tinymce.majorVersion + "." + tinymce.minorVersion
};
}
});
// Register plugin
tinymce.PluginManager.add('advlist', tinymce.plugins.AdvListPlugin);
})();
@@ -0,0 +1 @@
(function(){tinymce.create("tinymce.plugins.AutoResizePlugin",{init:function(a,c){var d=this;if(a.getParam("fullscreen_is_enabled")){return}function b(){var h=a.getDoc(),e=h.body,j=h.documentElement,g=tinymce.DOM,i=d.autoresize_min_height,f;f=tinymce.isIE?e.scrollHeight:j.offsetHeight;if(f>d.autoresize_min_height){i=f}g.setStyle(g.get(a.id+"_ifr"),"height",i+"px");if(d.throbbing){a.setProgressState(false);a.setProgressState(true)}}d.editor=a;d.autoresize_min_height=a.getElement().offsetHeight;a.onChange.add(b);a.onSetContent.add(b);a.onPaste.add(b);a.onKeyUp.add(b);a.onPostRender.add(b);if(a.getParam("autoresize_on_init",true)){a.onInit.add(function(f,e){f.setProgressState(true);d.throbbing=true;f.getBody().style.overflowY="hidden"});a.onLoadContent.add(function(f,e){b();setTimeout(function(){b();f.setProgressState(false);d.throbbing=false},1250)})}a.addCommand("mceAutoResize",b)},getInfo:function(){return{longname:"Auto Resize",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autoresize",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("autoresize",tinymce.plugins.AutoResizePlugin)})();
@@ -0,0 +1,119 @@
/**
* editor_plugin_src.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function() {
/**
* Auto Resize
*
* This plugin automatically resizes the content area to fit its content height.
* It will retain a minimum height, which is the height of the content area when
* it's initialized.
*/
tinymce.create('tinymce.plugins.AutoResizePlugin', {
/**
* Initializes the plugin, this will be executed after the plugin has been created.
* This call is done before the editor instance has finished it's initialization so use the onInit event
* of the editor instance to intercept that event.
*
* @param {tinymce.Editor} ed Editor instance that the plugin is initialized in.
* @param {string} url Absolute URL to where the plugin is located.
*/
init : function(ed, url) {
var t = this;
if (ed.getParam('fullscreen_is_enabled'))
return;
/**
* This method gets executed each time the editor needs to resize.
*/
function resize() {
var d = ed.getDoc(), b = d.body, de = d.documentElement, DOM = tinymce.DOM, resizeHeight = t.autoresize_min_height, myHeight;
// Get height differently depending on the browser used
myHeight = tinymce.isIE ? b.scrollHeight : de.offsetHeight;
// Don't make it smaller than the minimum height
if (myHeight > t.autoresize_min_height)
resizeHeight = myHeight;
// Resize content element
DOM.setStyle(DOM.get(ed.id + '_ifr'), 'height', resizeHeight + 'px');
// if we're throbbing, we'll re-throb to match the new size
if (t.throbbing) {
ed.setProgressState(false);
ed.setProgressState(true);
}
};
t.editor = ed;
// Define minimum height
t.autoresize_min_height = ed.getElement().offsetHeight;
// Add appropriate listeners for resizing content area
ed.onChange.add(resize);
ed.onSetContent.add(resize);
ed.onPaste.add(resize);
ed.onKeyUp.add(resize);
ed.onPostRender.add(resize);
if (ed.getParam('autoresize_on_init', true)) {
// Things to do when the editor is ready
ed.onInit.add(function(ed, l) {
// Show throbber until content area is resized properly
ed.setProgressState(true);
t.throbbing = true;
// Hide scrollbars
ed.getBody().style.overflowY = "hidden";
});
ed.onLoadContent.add(function(ed, l) {
resize();
// Because the content area resizes when its content CSS loads,
// and we can't easily add a listener to its onload event,
// we'll just trigger a resize after a short loading period
setTimeout(function() {
resize();
// Disable throbber
ed.setProgressState(false);
t.throbbing = false;
}, 1250);
});
}
// Register the command so that it can be invoked by using tinyMCE.activeEditor.execCommand('mceExample');
ed.addCommand('mceAutoResize', resize);
},
/**
* Returns information about the plugin as a name/value array.
* The current keys are longname, author, authorurl, infourl and version.
*
* @return {Object} Name/value array containing information about the plugin.
*/
getInfo : function() {
return {
longname : 'Auto Resize',
author : 'Moxiecode Systems AB',
authorurl : 'http://tinymce.moxiecode.com',
infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autoresize',
version : tinymce.majorVersion + "." + tinymce.minorVersion
};
}
});
// Register plugin
tinymce.PluginManager.add('autoresize', tinymce.plugins.AutoResizePlugin);
})();
@@ -0,0 +1 @@
(function(e){var c="autosave",g="restoredraft",b=true,f,d,a=e.util.Dispatcher;e.create("tinymce.plugins.AutoSave",{init:function(i,j){var h=this,l=i.settings;h.editor=i;function k(n){var m={s:1000,m:60000};n=/^(\d+)([ms]?)$/.exec(""+n);return(n[2]?m[n[2]]:1)*parseInt(n)}e.each({ask_before_unload:b,interval:"30s",retention:"20m",minlength:50},function(n,m){m=c+"_"+m;if(l[m]===f){l[m]=n}});l.autosave_interval=k(l.autosave_interval);l.autosave_retention=k(l.autosave_retention);i.addButton(g,{title:c+".restore_content",onclick:function(){if(i.getContent({draft:true}).replace(/\s|&nbsp;|<\/?p[^>]*>|<br[^>]*>/gi,"").length>0){i.windowManager.confirm(c+".warning_message",function(m){if(m){h.restoreDraft()}})}else{h.restoreDraft()}}});i.onNodeChange.add(function(){var m=i.controlManager;if(m.get(g)){m.setDisabled(g,!h.hasDraft())}});i.onInit.add(function(){if(i.controlManager.get(g)){h.setupStorage(i);setInterval(function(){h.storeDraft();i.nodeChanged()},l.autosave_interval)}});h.onStoreDraft=new a(h);h.onRestoreDraft=new a(h);h.onRemoveDraft=new a(h);if(!d){window.onbeforeunload=e.plugins.AutoSave._beforeUnloadHandler;d=b}},getInfo:function(){return{longname:"Auto save",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autosave",version:e.majorVersion+"."+e.minorVersion}},getExpDate:function(){return new Date(new Date().getTime()+this.editor.settings.autosave_retention).toUTCString()},setupStorage:function(i){var h=this,k=c+"_test",j="OK";h.key=c+i.id;e.each([function(){if(localStorage){localStorage.setItem(k,j);if(localStorage.getItem(k)===j){localStorage.removeItem(k);return localStorage}}},function(){if(sessionStorage){sessionStorage.setItem(k,j);if(sessionStorage.getItem(k)===j){sessionStorage.removeItem(k);return sessionStorage}}},function(){if(e.isIE){i.getElement().style.behavior="url('#default#userData')";return{autoExpires:b,setItem:function(l,n){var m=i.getElement();m.setAttribute(l,n);m.expires=h.getExpDate();m.save("TinyMCE")},getItem:function(l){var m=i.getElement();m.load("TinyMCE");return m.getAttribute(l)},removeItem:function(l){i.getElement().removeAttribute(l)}}}},],function(l){try{h.storage=l();if(h.storage){return false}}catch(m){}})},storeDraft:function(){var i=this,l=i.storage,j=i.editor,h,k;if(l){if(!l.getItem(i.key)&&!j.isDirty()){return}k=j.getContent({draft:true});if(k.length>j.settings.autosave_minlength){h=i.getExpDate();if(!i.storage.autoExpires){i.storage.setItem(i.key+"_expires",h)}i.storage.setItem(i.key,k);i.onStoreDraft.dispatch(i,{expires:h,content:k})}}},restoreDraft:function(){var h=this,i=h.storage;if(i){content=i.getItem(h.key);if(content){h.editor.setContent(content);h.onRestoreDraft.dispatch(h,{content:content})}}},hasDraft:function(){var h=this,k=h.storage,i,j;if(k){j=!!k.getItem(h.key);if(j){if(!h.storage.autoExpires){i=new Date(k.getItem(h.key+"_expires"));if(new Date().getTime()<i.getTime()){return b}h.removeDraft()}else{return b}}}return false},removeDraft:function(){var h=this,k=h.storage,i=h.key,j;if(k){j=k.getItem(i);k.removeItem(i);k.removeItem(i+"_expires");if(j){h.onRemoveDraft.dispatch(h,{content:j})}}},"static":{_beforeUnloadHandler:function(h){var i;e.each(tinyMCE.editors,function(j){if(j.plugins.autosave){j.plugins.autosave.storeDraft()}if(j.getParam("fullscreen_is_enabled")){return}if(!i&&j.isDirty()&&j.getParam("autosave_ask_before_unload")){i=j.getLang("autosave.unload_msg")}});return i}}});e.PluginManager.add("autosave",e.plugins.AutoSave)})(tinymce);
@@ -0,0 +1,422 @@
/**
* editor_plugin_src.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*
* Adds auto-save capability to the TinyMCE text editor to rescue content
* inadvertently lost. This plugin was originally developed by Speednet
* and that project can be found here: http://code.google.com/p/tinyautosave/
*
* TECHNOLOGY DISCUSSION:
*
* The plugin attempts to use the most advanced features available in the current browser to save
* as much content as possible. There are a total of four different methods used to autosave the
* content. In order of preference, they are:
*
* 1. localStorage - A new feature of HTML 5, localStorage can store megabytes of data per domain
* on the client computer. Data stored in the localStorage area has no expiration date, so we must
* manage expiring the data ourselves. localStorage is fully supported by IE8, and it is supposed
* to be working in Firefox 3 and Safari 3.2, but in reality is is flaky in those browsers. As
* HTML 5 gets wider support, the AutoSave plugin will use it automatically. In Windows Vista/7,
* localStorage is stored in the following folder:
* C:\Users\[username]\AppData\Local\Microsoft\Internet Explorer\DOMStore\[tempFolder]
*
* 2. sessionStorage - A new feature of HTML 5, sessionStorage works similarly to localStorage,
* except it is designed to expire after a certain amount of time. Because the specification
* around expiration date/time is very loosely-described, it is preferrable to use locaStorage and
* manage the expiration ourselves. sessionStorage has similar storage characteristics to
* localStorage, although it seems to have better support by Firefox 3 at the moment. (That will
* certainly change as Firefox continues getting better at HTML 5 adoption.)
*
* 3. UserData - A very under-exploited feature of Microsoft Internet Explorer, UserData is a
* way to store up to 128K of data per "document", or up to 1MB of data per domain, on the client
* computer. The feature is available for IE 5+, which makes it available for every version of IE
* supported by TinyMCE. The content is persistent across browser restarts and expires on the
* date/time specified, just like a cookie. However, the data is not cleared when the user clears
* cookies on the browser, which makes it well-suited for rescuing autosaved content. UserData,
* like other Microsoft IE browser technologies, is implemented as a behavior attached to a
* specific DOM object, so in this case we attach the behavior to the same DOM element that the
* TinyMCE editor instance is attached to.
*/
(function(tinymce) {
// Setup constants to help the compressor to reduce script size
var PLUGIN_NAME = 'autosave',
RESTORE_DRAFT = 'restoredraft',
TRUE = true,
undefined,
unloadHandlerAdded,
Dispatcher = tinymce.util.Dispatcher;
/**
* This plugin adds auto-save capability to the TinyMCE text editor to rescue content
* inadvertently lost. By using localStorage.
*
* @class tinymce.plugins.AutoSave
*/
tinymce.create('tinymce.plugins.AutoSave', {
/**
* Initializes the plugin, this will be executed after the plugin has been created.
* This call is done before the editor instance has finished it's initialization so use the onInit event
* of the editor instance to intercept that event.
*
* @method init
* @param {tinymce.Editor} ed Editor instance that the plugin is initialized in.
* @param {string} url Absolute URL to where the plugin is located.
*/
init : function(ed, url) {
var self = this, settings = ed.settings;
self.editor = ed;
// Parses the specified time string into a milisecond number 10m, 10s etc.
function parseTime(time) {
var multipels = {
s : 1000,
m : 60000
};
time = /^(\d+)([ms]?)$/.exec('' + time);
return (time[2] ? multipels[time[2]] : 1) * parseInt(time);
};
// Default config
tinymce.each({
ask_before_unload : TRUE,
interval : '30s',
retention : '20m',
minlength : 50
}, function(value, key) {
key = PLUGIN_NAME + '_' + key;
if (settings[key] === undefined)
settings[key] = value;
});
// Parse times
settings.autosave_interval = parseTime(settings.autosave_interval);
settings.autosave_retention = parseTime(settings.autosave_retention);
// Register restore button
ed.addButton(RESTORE_DRAFT, {
title : PLUGIN_NAME + ".restore_content",
onclick : function() {
if (ed.getContent({draft: true}).replace(/\s|&nbsp;|<\/?p[^>]*>|<br[^>]*>/gi, "").length > 0) {
// Show confirm dialog if the editor isn't empty
ed.windowManager.confirm(
PLUGIN_NAME + ".warning_message",
function(ok) {
if (ok)
self.restoreDraft();
}
);
} else
self.restoreDraft();
}
});
// Enable/disable restoredraft button depending on if there is a draft stored or not
ed.onNodeChange.add(function() {
var controlManager = ed.controlManager;
if (controlManager.get(RESTORE_DRAFT))
controlManager.setDisabled(RESTORE_DRAFT, !self.hasDraft());
});
ed.onInit.add(function() {
// Check if the user added the restore button, then setup auto storage logic
if (ed.controlManager.get(RESTORE_DRAFT)) {
// Setup storage engine
self.setupStorage(ed);
// Auto save contents each interval time
setInterval(function() {
self.storeDraft();
ed.nodeChanged();
}, settings.autosave_interval);
}
});
/**
* This event gets fired when a draft is stored to local storage.
*
* @event onStoreDraft
* @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event.
* @param {Object} draft Draft object containing the HTML contents of the editor.
*/
self.onStoreDraft = new Dispatcher(self);
/**
* This event gets fired when a draft is restored from local storage.
*
* @event onStoreDraft
* @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event.
* @param {Object} draft Draft object containing the HTML contents of the editor.
*/
self.onRestoreDraft = new Dispatcher(self);
/**
* This event gets fired when a draft removed/expired.
*
* @event onRemoveDraft
* @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event.
* @param {Object} draft Draft object containing the HTML contents of the editor.
*/
self.onRemoveDraft = new Dispatcher(self);
// Add ask before unload dialog only add one unload handler
if (!unloadHandlerAdded) {
window.onbeforeunload = tinymce.plugins.AutoSave._beforeUnloadHandler;
unloadHandlerAdded = TRUE;
}
},
/**
* Returns information about the plugin as a name/value array.
* The current keys are longname, author, authorurl, infourl and version.
*
* @method getInfo
* @return {Object} Name/value array containing information about the plugin.
*/
getInfo : function() {
return {
longname : 'Auto save',
author : 'Moxiecode Systems AB',
authorurl : 'http://tinymce.moxiecode.com',
infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autosave',
version : tinymce.majorVersion + "." + tinymce.minorVersion
};
},
/**
* Returns an expiration date UTC string.
*
* @method getExpDate
* @return {String} Expiration date UTC string.
*/
getExpDate : function() {
return new Date(
new Date().getTime() + this.editor.settings.autosave_retention
).toUTCString();
},
/**
* This method will setup the storage engine. If the browser has support for it.
*
* @method setupStorage
*/
setupStorage : function(ed) {
var self = this, testKey = PLUGIN_NAME + '_test', testVal = "OK";
self.key = PLUGIN_NAME + ed.id;
// Loop though each storage engine type until we find one that works
tinymce.each([
function() {
// Try HTML5 Local Storage
if (localStorage) {
localStorage.setItem(testKey, testVal);
if (localStorage.getItem(testKey) === testVal) {
localStorage.removeItem(testKey);
return localStorage;
}
}
},
function() {
// Try HTML5 Session Storage
if (sessionStorage) {
sessionStorage.setItem(testKey, testVal);
if (sessionStorage.getItem(testKey) === testVal) {
sessionStorage.removeItem(testKey);
return sessionStorage;
}
}
},
function() {
// Try IE userData
if (tinymce.isIE) {
ed.getElement().style.behavior = "url('#default#userData')";
// Fake localStorage on old IE
return {
autoExpires : TRUE,
setItem : function(key, value) {
var userDataElement = ed.getElement();
userDataElement.setAttribute(key, value);
userDataElement.expires = self.getExpDate();
userDataElement.save("TinyMCE");
},
getItem : function(key) {
var userDataElement = ed.getElement();
userDataElement.load("TinyMCE");
return userDataElement.getAttribute(key);
},
removeItem : function(key) {
ed.getElement().removeAttribute(key);
}
};
}
},
], function(setup) {
// Try executing each function to find a suitable storage engine
try {
self.storage = setup();
if (self.storage)
return false;
} catch (e) {
// Ignore
}
});
},
/**
* This method will store the current contents in the the storage engine.
*
* @method storeDraft
*/
storeDraft : function() {
var self = this, storage = self.storage, editor = self.editor, expires, content;
// Is the contents dirty
if (storage) {
// If there is no existing key and the contents hasn't been changed since
// it's original value then there is no point in saving a draft
if (!storage.getItem(self.key) && !editor.isDirty())
return;
// Store contents if the contents if longer than the minlength of characters
content = editor.getContent({draft: true});
if (content.length > editor.settings.autosave_minlength) {
expires = self.getExpDate();
// Store expiration date if needed IE userData has auto expire built in
if (!self.storage.autoExpires)
self.storage.setItem(self.key + "_expires", expires);
self.storage.setItem(self.key, content);
self.onStoreDraft.dispatch(self, {
expires : expires,
content : content
});
}
}
},
/**
* This method will restore the contents from the storage engine back to the editor.
*
* @method restoreDraft
*/
restoreDraft : function() {
var self = this, storage = self.storage;
if (storage) {
content = storage.getItem(self.key);
if (content) {
self.editor.setContent(content);
self.onRestoreDraft.dispatch(self, {
content : content
});
}
}
},
/**
* This method will return true/false if there is a local storage draft available.
*
* @method hasDraft
* @return {boolean} true/false state if there is a local draft.
*/
hasDraft : function() {
var self = this, storage = self.storage, expDate, exists;
if (storage) {
// Does the item exist at all
exists = !!storage.getItem(self.key);
if (exists) {
// Storage needs autoexpire
if (!self.storage.autoExpires) {
expDate = new Date(storage.getItem(self.key + "_expires"));
// Contents hasn't expired
if (new Date().getTime() < expDate.getTime())
return TRUE;
// Remove it if it has
self.removeDraft();
} else
return TRUE;
}
}
return false;
},
/**
* Removes the currently stored draft.
*
* @method removeDraft
*/
removeDraft : function() {
var self = this, storage = self.storage, key = self.key, content;
if (storage) {
// Get current contents and remove the existing draft
content = storage.getItem(key);
storage.removeItem(key);
storage.removeItem(key + "_expires");
// Dispatch remove event if we had any contents
if (content) {
self.onRemoveDraft.dispatch(self, {
content : content
});
}
}
},
"static" : {
// Internal unload handler will be called before the page is unloaded
_beforeUnloadHandler : function(e) {
var msg;
tinymce.each(tinyMCE.editors, function(ed) {
// Store a draft for each editor instance
if (ed.plugins.autosave)
ed.plugins.autosave.storeDraft();
// Never ask in fullscreen mode
if (ed.getParam("fullscreen_is_enabled"))
return;
// Setup a return message if the editor is dirty
if (!msg && ed.isDirty() && ed.getParam("autosave_ask_before_unload"))
msg = ed.getLang("autosave.unload_msg");
});
return msg;
}
}
});
tinymce.PluginManager.add('autosave', tinymce.plugins.AutoSave);
})(tinymce);
@@ -0,0 +1,4 @@
tinyMCE.addI18n('en.autosave',{
restore_content: "Restore auto-saved content",
warning_message: "If you restore the saved content, you will lose all the content that is currently in the editor.\n\nAre you sure you want to restore the saved content?"
});
@@ -0,0 +1 @@
(function(){tinymce.create("tinymce.plugins.BBCodePlugin",{init:function(a,b){var d=this,c=a.getParam("bbcode_dialect","punbb").toLowerCase();a.onBeforeSetContent.add(function(e,f){f.content=d["_"+c+"_bbcode2html"](f.content)});a.onPostProcess.add(function(e,f){if(f.set){f.content=d["_"+c+"_bbcode2html"](f.content)}if(f.get){f.content=d["_"+c+"_html2bbcode"](f.content)}})},getInfo:function(){return{longname:"BBCode Plugin",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/bbcode",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_punbb_html2bbcode:function(a){a=tinymce.trim(a);function b(c,d){a=a.replace(c,d)}b(/<a.*?href=\"(.*?)\".*?>(.*?)<\/a>/gi,"[url=$1]$2[/url]");b(/<font.*?color=\"(.*?)\".*?class=\"codeStyle\".*?>(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]");b(/<font.*?color=\"(.*?)\".*?class=\"quoteStyle\".*?>(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]");b(/<font.*?class=\"codeStyle\".*?color=\"(.*?)\".*?>(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]");b(/<font.*?class=\"quoteStyle\".*?color=\"(.*?)\".*?>(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]");b(/<span style=\"color: ?(.*?);\">(.*?)<\/span>/gi,"[color=$1]$2[/color]");b(/<font.*?color=\"(.*?)\".*?>(.*?)<\/font>/gi,"[color=$1]$2[/color]");b(/<span style=\"font-size:(.*?);\">(.*?)<\/span>/gi,"[size=$1]$2[/size]");b(/<font>(.*?)<\/font>/gi,"$1");b(/<img.*?src=\"(.*?)\".*?\/>/gi,"[img]$1[/img]");b(/<span class=\"codeStyle\">(.*?)<\/span>/gi,"[code]$1[/code]");b(/<span class=\"quoteStyle\">(.*?)<\/span>/gi,"[quote]$1[/quote]");b(/<strong class=\"codeStyle\">(.*?)<\/strong>/gi,"[code][b]$1[/b][/code]");b(/<strong class=\"quoteStyle\">(.*?)<\/strong>/gi,"[quote][b]$1[/b][/quote]");b(/<em class=\"codeStyle\">(.*?)<\/em>/gi,"[code][i]$1[/i][/code]");b(/<em class=\"quoteStyle\">(.*?)<\/em>/gi,"[quote][i]$1[/i][/quote]");b(/<u class=\"codeStyle\">(.*?)<\/u>/gi,"[code][u]$1[/u][/code]");b(/<u class=\"quoteStyle\">(.*?)<\/u>/gi,"[quote][u]$1[/u][/quote]");b(/<\/(strong|b)>/gi,"[/b]");b(/<(strong|b)>/gi,"[b]");b(/<\/(em|i)>/gi,"[/i]");b(/<(em|i)>/gi,"[i]");b(/<\/u>/gi,"[/u]");b(/<span style=\"text-decoration: ?underline;\">(.*?)<\/span>/gi,"[u]$1[/u]");b(/<u>/gi,"[u]");b(/<blockquote[^>]*>/gi,"[quote]");b(/<\/blockquote>/gi,"[/quote]");b(/<br \/>/gi,"\n");b(/<br\/>/gi,"\n");b(/<br>/gi,"\n");b(/<p>/gi,"");b(/<\/p>/gi,"\n");b(/&nbsp;/gi," ");b(/&quot;/gi,'"');b(/&lt;/gi,"<");b(/&gt;/gi,">");b(/&amp;/gi,"&");return a},_punbb_bbcode2html:function(a){a=tinymce.trim(a);function b(c,d){a=a.replace(c,d)}b(/\n/gi,"<br />");b(/\[b\]/gi,"<strong>");b(/\[\/b\]/gi,"</strong>");b(/\[i\]/gi,"<em>");b(/\[\/i\]/gi,"</em>");b(/\[u\]/gi,"<u>");b(/\[\/u\]/gi,"</u>");b(/\[url=([^\]]+)\](.*?)\[\/url\]/gi,'<a href="$1">$2</a>');b(/\[url\](.*?)\[\/url\]/gi,'<a href="$1">$1</a>');b(/\[img\](.*?)\[\/img\]/gi,'<img src="$1" />');b(/\[color=(.*?)\](.*?)\[\/color\]/gi,'<font color="$1">$2</font>');b(/\[code\](.*?)\[\/code\]/gi,'<span class="codeStyle">$1</span>&nbsp;');b(/\[quote.*?\](.*?)\[\/quote\]/gi,'<span class="quoteStyle">$1</span>&nbsp;');return a}});tinymce.PluginManager.add("bbcode",tinymce.plugins.BBCodePlugin)})();
@@ -0,0 +1,120 @@
/**
* editor_plugin_src.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function() {
tinymce.create('tinymce.plugins.BBCodePlugin', {
init : function(ed, url) {
var t = this, dialect = ed.getParam('bbcode_dialect', 'punbb').toLowerCase();
ed.onBeforeSetContent.add(function(ed, o) {
o.content = t['_' + dialect + '_bbcode2html'](o.content);
});
ed.onPostProcess.add(function(ed, o) {
if (o.set)
o.content = t['_' + dialect + '_bbcode2html'](o.content);
if (o.get)
o.content = t['_' + dialect + '_html2bbcode'](o.content);
});
},
getInfo : function() {
return {
longname : 'BBCode Plugin',
author : 'Moxiecode Systems AB',
authorurl : 'http://tinymce.moxiecode.com',
infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/bbcode',
version : tinymce.majorVersion + "." + tinymce.minorVersion
};
},
// Private methods
// HTML -> BBCode in PunBB dialect
_punbb_html2bbcode : function(s) {
s = tinymce.trim(s);
function rep(re, str) {
s = s.replace(re, str);
};
// example: <strong> to [b]
rep(/<a.*?href=\"(.*?)\".*?>(.*?)<\/a>/gi,"[url=$1]$2[/url]");
rep(/<font.*?color=\"(.*?)\".*?class=\"codeStyle\".*?>(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]");
rep(/<font.*?color=\"(.*?)\".*?class=\"quoteStyle\".*?>(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]");
rep(/<font.*?class=\"codeStyle\".*?color=\"(.*?)\".*?>(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]");
rep(/<font.*?class=\"quoteStyle\".*?color=\"(.*?)\".*?>(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]");
rep(/<span style=\"color: ?(.*?);\">(.*?)<\/span>/gi,"[color=$1]$2[/color]");
rep(/<font.*?color=\"(.*?)\".*?>(.*?)<\/font>/gi,"[color=$1]$2[/color]");
rep(/<span style=\"font-size:(.*?);\">(.*?)<\/span>/gi,"[size=$1]$2[/size]");
rep(/<font>(.*?)<\/font>/gi,"$1");
rep(/<img.*?src=\"(.*?)\".*?\/>/gi,"[img]$1[/img]");
rep(/<span class=\"codeStyle\">(.*?)<\/span>/gi,"[code]$1[/code]");
rep(/<span class=\"quoteStyle\">(.*?)<\/span>/gi,"[quote]$1[/quote]");
rep(/<strong class=\"codeStyle\">(.*?)<\/strong>/gi,"[code][b]$1[/b][/code]");
rep(/<strong class=\"quoteStyle\">(.*?)<\/strong>/gi,"[quote][b]$1[/b][/quote]");
rep(/<em class=\"codeStyle\">(.*?)<\/em>/gi,"[code][i]$1[/i][/code]");
rep(/<em class=\"quoteStyle\">(.*?)<\/em>/gi,"[quote][i]$1[/i][/quote]");
rep(/<u class=\"codeStyle\">(.*?)<\/u>/gi,"[code][u]$1[/u][/code]");
rep(/<u class=\"quoteStyle\">(.*?)<\/u>/gi,"[quote][u]$1[/u][/quote]");
rep(/<\/(strong|b)>/gi,"[/b]");
rep(/<(strong|b)>/gi,"[b]");
rep(/<\/(em|i)>/gi,"[/i]");
rep(/<(em|i)>/gi,"[i]");
rep(/<\/u>/gi,"[/u]");
rep(/<span style=\"text-decoration: ?underline;\">(.*?)<\/span>/gi,"[u]$1[/u]");
rep(/<u>/gi,"[u]");
rep(/<blockquote[^>]*>/gi,"[quote]");
rep(/<\/blockquote>/gi,"[/quote]");
rep(/<br \/>/gi,"\n");
rep(/<br\/>/gi,"\n");
rep(/<br>/gi,"\n");
rep(/<p>/gi,"");
rep(/<\/p>/gi,"\n");
rep(/&nbsp;/gi," ");
rep(/&quot;/gi,"\"");
rep(/&lt;/gi,"<");
rep(/&gt;/gi,">");
rep(/&amp;/gi,"&");
return s;
},
// BBCode -> HTML from PunBB dialect
_punbb_bbcode2html : function(s) {
s = tinymce.trim(s);
function rep(re, str) {
s = s.replace(re, str);
};
// example: [b] to <strong>
rep(/\n/gi,"<br />");
rep(/\[b\]/gi,"<strong>");
rep(/\[\/b\]/gi,"</strong>");
rep(/\[i\]/gi,"<em>");
rep(/\[\/i\]/gi,"</em>");
rep(/\[u\]/gi,"<u>");
rep(/\[\/u\]/gi,"</u>");
rep(/\[url=([^\]]+)\](.*?)\[\/url\]/gi,"<a href=\"$1\">$2</a>");
rep(/\[url\](.*?)\[\/url\]/gi,"<a href=\"$1\">$1</a>");
rep(/\[img\](.*?)\[\/img\]/gi,"<img src=\"$1\" />");
rep(/\[color=(.*?)\](.*?)\[\/color\]/gi,"<font color=\"$1\">$2</font>");
rep(/\[code\](.*?)\[\/code\]/gi,"<span class=\"codeStyle\">$1</span>&nbsp;");
rep(/\[quote.*?\](.*?)\[\/quote\]/gi,"<span class=\"quoteStyle\">$1</span>&nbsp;");
return s;
}
});
// Register plugin
tinymce.PluginManager.add('bbcode', tinymce.plugins.BBCodePlugin);
})();
@@ -0,0 +1 @@
(function(){var a=tinymce.dom.Event,c=tinymce.each,b=tinymce.DOM;tinymce.create("tinymce.plugins.ContextMenu",{init:function(d){var f=this,g;f.editor=d;f.onContextMenu=new tinymce.util.Dispatcher(this);d.onContextMenu.add(function(h,i){if(!i.ctrlKey){if(g){h.selection.setRng(g)}f._getMenu(h).showMenu(i.clientX,i.clientY);a.add(h.getDoc(),"click",function(j){e(h,j)});a.cancel(i)}});d.onRemove.add(function(){if(f._menu){f._menu.removeAll()}});function e(h,i){g=null;if(i&&i.button==2){g=h.selection.getRng();return}if(f._menu){f._menu.removeAll();f._menu.destroy();a.remove(h.getDoc(),"click",e)}}d.onMouseDown.add(e);d.onKeyDown.add(e)},getInfo:function(){return{longname:"Contextmenu",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/contextmenu",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_getMenu:function(h){var l=this,f=l._menu,i=h.selection,e=i.isCollapsed(),d=i.getNode()||h.getBody(),g,k,j;if(f){f.removeAll();f.destroy()}k=b.getPos(h.getContentAreaContainer());j=b.getPos(h.getContainer());f=h.controlManager.createDropMenu("contextmenu",{offset_x:k.x+h.getParam("contextmenu_offset_x",0),offset_y:k.y+h.getParam("contextmenu_offset_y",0),constrain:1});l._menu=f;f.add({title:"advanced.cut_desc",icon:"cut",cmd:"Cut"}).setDisabled(e);f.add({title:"advanced.copy_desc",icon:"copy",cmd:"Copy"}).setDisabled(e);f.add({title:"advanced.paste_desc",icon:"paste",cmd:"Paste"});if((d.nodeName=="A"&&!h.dom.getAttrib(d,"name"))||!e){f.addSeparator();f.add({title:"advanced.link_desc",icon:"link",cmd:h.plugins.advlink?"mceAdvLink":"mceLink",ui:true});f.add({title:"advanced.unlink_desc",icon:"unlink",cmd:"UnLink"})}f.addSeparator();f.add({title:"advanced.image_desc",icon:"image",cmd:h.plugins.advimage?"mceAdvImage":"mceImage",ui:true});f.addSeparator();g=f.addMenu({title:"contextmenu.align"});g.add({title:"contextmenu.left",icon:"justifyleft",cmd:"JustifyLeft"});g.add({title:"contextmenu.center",icon:"justifycenter",cmd:"JustifyCenter"});g.add({title:"contextmenu.right",icon:"justifyright",cmd:"JustifyRight"});g.add({title:"contextmenu.full",icon:"justifyfull",cmd:"JustifyFull"});l.onContextMenu.dispatch(l,f,d,e);return f}});tinymce.PluginManager.add("contextmenu",tinymce.plugins.ContextMenu)})();
@@ -0,0 +1,147 @@
/**
* editor_plugin_src.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function() {
var Event = tinymce.dom.Event, each = tinymce.each, DOM = tinymce.DOM;
/**
* This plugin a context menu to TinyMCE editor instances.
*
* @class tinymce.plugins.ContextMenu
*/
tinymce.create('tinymce.plugins.ContextMenu', {
/**
* Initializes the plugin, this will be executed after the plugin has been created.
* This call is done before the editor instance has finished it's initialization so use the onInit event
* of the editor instance to intercept that event.
*
* @method init
* @param {tinymce.Editor} ed Editor instance that the plugin is initialized in.
* @param {string} url Absolute URL to where the plugin is located.
*/
init : function(ed) {
var t = this, lastRng;
t.editor = ed;
/**
* This event gets fired when the context menu is shown.
*
* @event onContextMenu
* @param {tinymce.plugins.ContextMenu} sender Plugin instance sending the event.
* @param {tinymce.ui.DropMenu} menu Drop down menu to fill with more items if needed.
*/
t.onContextMenu = new tinymce.util.Dispatcher(this);
ed.onContextMenu.add(function(ed, e) {
if (!e.ctrlKey) {
// Restore the last selection since it was removed
if (lastRng)
ed.selection.setRng(lastRng);
t._getMenu(ed).showMenu(e.clientX, e.clientY);
Event.add(ed.getDoc(), 'click', function(e) {
hide(ed, e);
});
Event.cancel(e);
}
});
ed.onRemove.add(function() {
if (t._menu)
t._menu.removeAll();
});
function hide(ed, e) {
lastRng = null;
// Since the contextmenu event moves
// the selection we need to store it away
if (e && e.button == 2) {
lastRng = ed.selection.getRng();
return;
}
if (t._menu) {
t._menu.removeAll();
t._menu.destroy();
Event.remove(ed.getDoc(), 'click', hide);
}
};
ed.onMouseDown.add(hide);
ed.onKeyDown.add(hide);
},
/**
* Returns information about the plugin as a name/value array.
* The current keys are longname, author, authorurl, infourl and version.
*
* @method getInfo
* @return {Object} Name/value array containing information about the plugin.
*/
getInfo : function() {
return {
longname : 'Contextmenu',
author : 'Moxiecode Systems AB',
authorurl : 'http://tinymce.moxiecode.com',
infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/contextmenu',
version : tinymce.majorVersion + "." + tinymce.minorVersion
};
},
_getMenu : function(ed) {
var t = this, m = t._menu, se = ed.selection, col = se.isCollapsed(), el = se.getNode() || ed.getBody(), am, p1, p2;
if (m) {
m.removeAll();
m.destroy();
}
p1 = DOM.getPos(ed.getContentAreaContainer());
p2 = DOM.getPos(ed.getContainer());
m = ed.controlManager.createDropMenu('contextmenu', {
offset_x : p1.x + ed.getParam('contextmenu_offset_x', 0),
offset_y : p1.y + ed.getParam('contextmenu_offset_y', 0),
constrain : 1
});
t._menu = m;
m.add({title : 'advanced.cut_desc', icon : 'cut', cmd : 'Cut'}).setDisabled(col);
m.add({title : 'advanced.copy_desc', icon : 'copy', cmd : 'Copy'}).setDisabled(col);
m.add({title : 'advanced.paste_desc', icon : 'paste', cmd : 'Paste'});
if ((el.nodeName == 'A' && !ed.dom.getAttrib(el, 'name')) || !col) {
m.addSeparator();
m.add({title : 'advanced.link_desc', icon : 'link', cmd : ed.plugins.advlink ? 'mceAdvLink' : 'mceLink', ui : true});
m.add({title : 'advanced.unlink_desc', icon : 'unlink', cmd : 'UnLink'});
}
m.addSeparator();
m.add({title : 'advanced.image_desc', icon : 'image', cmd : ed.plugins.advimage ? 'mceAdvImage' : 'mceImage', ui : true});
m.addSeparator();
am = m.addMenu({title : 'contextmenu.align'});
am.add({title : 'contextmenu.left', icon : 'justifyleft', cmd : 'JustifyLeft'});
am.add({title : 'contextmenu.center', icon : 'justifycenter', cmd : 'JustifyCenter'});
am.add({title : 'contextmenu.right', icon : 'justifyright', cmd : 'JustifyRight'});
am.add({title : 'contextmenu.full', icon : 'justifyfull', cmd : 'JustifyFull'});
t.onContextMenu.dispatch(t, m, el, col);
return m;
}
});
// Register plugin
tinymce.PluginManager.add('contextmenu', tinymce.plugins.ContextMenu);
})();
@@ -0,0 +1 @@
(function(){tinymce.create("tinymce.plugins.Directionality",{init:function(a,b){var c=this;c.editor=a;a.addCommand("mceDirectionLTR",function(){var d=a.dom.getParent(a.selection.getNode(),a.dom.isBlock);if(d){if(a.dom.getAttrib(d,"dir")!="ltr"){a.dom.setAttrib(d,"dir","ltr")}else{a.dom.setAttrib(d,"dir","")}}a.nodeChanged()});a.addCommand("mceDirectionRTL",function(){var d=a.dom.getParent(a.selection.getNode(),a.dom.isBlock);if(d){if(a.dom.getAttrib(d,"dir")!="rtl"){a.dom.setAttrib(d,"dir","rtl")}else{a.dom.setAttrib(d,"dir","")}}a.nodeChanged()});a.addButton("ltr",{title:"directionality.ltr_desc",cmd:"mceDirectionLTR"});a.addButton("rtl",{title:"directionality.rtl_desc",cmd:"mceDirectionRTL"});a.onNodeChange.add(c._nodeChange,c)},getInfo:function(){return{longname:"Directionality",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/directionality",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_nodeChange:function(b,a,e){var d=b.dom,c;e=d.getParent(e,d.isBlock);if(!e){a.setDisabled("ltr",1);a.setDisabled("rtl",1);return}c=d.getAttrib(e,"dir");a.setActive("ltr",c=="ltr");a.setDisabled("ltr",0);a.setActive("rtl",c=="rtl");a.setDisabled("rtl",0)}});tinymce.PluginManager.add("directionality",tinymce.plugins.Directionality)})();
@@ -0,0 +1,82 @@
/**
* editor_plugin_src.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function() {
tinymce.create('tinymce.plugins.Directionality', {
init : function(ed, url) {
var t = this;
t.editor = ed;
ed.addCommand('mceDirectionLTR', function() {
var e = ed.dom.getParent(ed.selection.getNode(), ed.dom.isBlock);
if (e) {
if (ed.dom.getAttrib(e, "dir") != "ltr")
ed.dom.setAttrib(e, "dir", "ltr");
else
ed.dom.setAttrib(e, "dir", "");
}
ed.nodeChanged();
});
ed.addCommand('mceDirectionRTL', function() {
var e = ed.dom.getParent(ed.selection.getNode(), ed.dom.isBlock);
if (e) {
if (ed.dom.getAttrib(e, "dir") != "rtl")
ed.dom.setAttrib(e, "dir", "rtl");
else
ed.dom.setAttrib(e, "dir", "");
}
ed.nodeChanged();
});
ed.addButton('ltr', {title : 'directionality.ltr_desc', cmd : 'mceDirectionLTR'});
ed.addButton('rtl', {title : 'directionality.rtl_desc', cmd : 'mceDirectionRTL'});
ed.onNodeChange.add(t._nodeChange, t);
},
getInfo : function() {
return {
longname : 'Directionality',
author : 'Moxiecode Systems AB',
authorurl : 'http://tinymce.moxiecode.com',
infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/directionality',
version : tinymce.majorVersion + "." + tinymce.minorVersion
};
},
// Private methods
_nodeChange : function(ed, cm, n) {
var dom = ed.dom, dir;
n = dom.getParent(n, dom.isBlock);
if (!n) {
cm.setDisabled('ltr', 1);
cm.setDisabled('rtl', 1);
return;
}
dir = dom.getAttrib(n, 'dir');
cm.setActive('ltr', dir == "ltr");
cm.setDisabled('ltr', 0);
cm.setActive('rtl', dir == "rtl");
cm.setDisabled('rtl', 0);
}
});
// Register plugin
tinymce.PluginManager.add('directionality', tinymce.plugins.Directionality);
})();
@@ -0,0 +1 @@
(function(a){a.create("tinymce.plugins.EmotionsPlugin",{init:function(b,c){b.addCommand("mceEmotion",function(){b.windowManager.open({file:c+"/emotions.htm",width:250+parseInt(b.getLang("emotions.delta_width",0)),height:160+parseInt(b.getLang("emotions.delta_height",0)),inline:1},{plugin_url:c})});b.addButton("emotions",{title:"emotions.emotions_desc",cmd:"mceEmotion"})},getInfo:function(){return{longname:"Emotions",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/emotions",version:a.majorVersion+"."+a.minorVersion}}});a.PluginManager.add("emotions",a.plugins.EmotionsPlugin)})(tinymce);
@@ -0,0 +1,43 @@
/**
* editor_plugin_src.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
tinymce.create('tinymce.plugins.EmotionsPlugin', {
init : function(ed, url) {
// Register commands
ed.addCommand('mceEmotion', function() {
ed.windowManager.open({
file : url + '/emotions.htm',
width : 250 + parseInt(ed.getLang('emotions.delta_width', 0)),
height : 160 + parseInt(ed.getLang('emotions.delta_height', 0)),
inline : 1
}, {
plugin_url : url
});
});
// Register buttons
ed.addButton('emotions', {title : 'emotions.emotions_desc', cmd : 'mceEmotion'});
},
getInfo : function() {
return {
longname : 'Emotions',
author : 'Moxiecode Systems AB',
authorurl : 'http://tinymce.moxiecode.com',
infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/emotions',
version : tinymce.majorVersion + "." + tinymce.minorVersion
};
}
});
// Register plugin
tinymce.PluginManager.add('emotions', tinymce.plugins.EmotionsPlugin);
})(tinymce);
@@ -0,0 +1,40 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>{#emotions_dlg.title}</title>
<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
<script type="text/javascript" src="js/emotions.js"></script>
</head>
<body style="display: none">
<div align="center">
<div class="title">{#emotions_dlg.title}:<br /><br /></div>
<table border="0" cellspacing="0" cellpadding="4">
<tr>
<td><a href="javascript:EmotionsDialog.insert('smiley-cool.gif','emotions_dlg.cool');"><img src="img/smiley-cool.gif" width="18" height="18" border="0" alt="{#emotions_dlg.cool}" title="{#emotions_dlg.cool}" /></a></td>
<td><a href="javascript:EmotionsDialog.insert('smiley-cry.gif','emotions_dlg.cry');"><img src="img/smiley-cry.gif" width="18" height="18" border="0" alt="{#emotions_dlg.cry}" title="{#emotions_dlg.cry}" /></a></td>
<td><a href="javascript:EmotionsDialog.insert('smiley-embarassed.gif','emotions_dlg.embarassed');"><img src="img/smiley-embarassed.gif" width="18" height="18" border="0" alt="{#emotions_dlg.embarassed}" title="{#emotions_dlg.embarassed}" /></a></td>
<td><a href="javascript:EmotionsDialog.insert('smiley-foot-in-mouth.gif','emotions_dlg.foot_in_mouth');"><img src="img/smiley-foot-in-mouth.gif" width="18" height="18" border="0" alt="{#emotions_dlg.foot_in_mouth}" title="{#emotions_dlg.foot_in_mouth}" /></a></td>
</tr>
<tr>
<td><a href="javascript:EmotionsDialog.insert('smiley-frown.gif','emotions_dlg.frown');"><img src="img/smiley-frown.gif" width="18" height="18" border="0" alt="{#emotions_dlg.frown}" title="{#emotions_dlg.frown}" /></a></td>
<td><a href="javascript:EmotionsDialog.insert('smiley-innocent.gif','emotions_dlg.innocent');"><img src="img/smiley-innocent.gif" width="18" height="18" border="0" alt="{#emotions_dlg.innocent}" title="{#emotions_dlg.innocent}" /></a></td>
<td><a href="javascript:EmotionsDialog.insert('smiley-kiss.gif','emotions_dlg.kiss');"><img src="img/smiley-kiss.gif" width="18" height="18" border="0" alt="{#emotions_dlg.kiss}" title="{#emotions_dlg.kiss}" /></a></td>
<td><a href="javascript:EmotionsDialog.insert('smiley-laughing.gif','emotions_dlg.laughing');"><img src="img/smiley-laughing.gif" width="18" height="18" border="0" alt="{#emotions_dlg.laughing}" title="{#emotions_dlg.laughing}" /></a></td>
</tr>
<tr>
<td><a href="javascript:EmotionsDialog.insert('smiley-money-mouth.gif','emotions_dlg.money_mouth');"><img src="img/smiley-money-mouth.gif" width="18" height="18" border="0" alt="{#emotions_dlg.money_mouth}" title="{#emotions_dlg.money_mouth}" /></a></td>
<td><a href="javascript:EmotionsDialog.insert('smiley-sealed.gif','emotions_dlg.sealed');"><img src="img/smiley-sealed.gif" width="18" height="18" border="0" alt="{#emotions_dlg.sealed}" title="{#emotions_dlg.sealed}" /></a></td>
<td><a href="javascript:EmotionsDialog.insert('smiley-smile.gif','emotions_dlg.smile');"><img src="img/smiley-smile.gif" width="18" height="18" border="0" alt="{#emotions_dlg.smile}" title="{#emotions_dlg.smile}" /></a></td>
<td><a href="javascript:EmotionsDialog.insert('smiley-surprised.gif','emotions_dlg.surprised');"><img src="img/smiley-surprised.gif" width="18" height="18" border="0" alt="{#emotions_dlg.surprised}" title="{#emotions_dlg.surprised}" /></a></td>
</tr>
<tr>
<td><a href="javascript:EmotionsDialog.insert('smiley-tongue-out.gif','emotions_dlg.tongue_out');"><img src="img/smiley-tongue-out.gif" width="18" height="18" border="0" alt="{#emotions_dlg.tongue-out}" title="{#emotions_dlg.tongue_out}" /></a></td>
<td><a href="javascript:EmotionsDialog.insert('smiley-undecided.gif','emotions_dlg.undecided');"><img src="img/smiley-undecided.gif" width="18" height="18" border="0" alt="{#emotions_dlg.undecided}" title="{#emotions_dlg.undecided}" /></a></td>
<td><a href="javascript:EmotionsDialog.insert('smiley-wink.gif','emotions_dlg.wink');"><img src="img/smiley-wink.gif" width="18" height="18" border="0" alt="{#emotions_dlg.wink}" title="{#emotions_dlg.wink}" /></a></td>
<td><a href="javascript:EmotionsDialog.insert('smiley-yell.gif','emotions_dlg.yell');"><img src="img/smiley-yell.gif" width="18" height="18" border="0" alt="{#emotions_dlg.yell}" title="{#emotions_dlg.yell}" /></a></td>
</tr>
</table>
</div>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

Some files were not shown because too many files have changed in this diff Show More