diff --git a/design/standard/javascript/classes/AddOnManager.js b/design/standard/javascript/classes/AddOnManager.js index 18954e1a..dd18e7c4 100644 --- a/design/standard/javascript/classes/AddOnManager.js +++ b/design/standard/javascript/classes/AddOnManager.js @@ -1,11 +1,11 @@ /** * AddOnManager.js * - * Copyright 2009, Moxiecode Systems AB + * Copyright, Moxiecode Systems AB * Released under LGPL License. * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing */ (function(tinymce) { @@ -110,12 +110,12 @@ }, /** - * Add a set of components that will make up the add-on. Using the url of the add-on name as the base url. + * Add a set of components that will make up the add-on. Using the url of the add-on name as the base url. * This should be used in development mode. A new compressor/javascript munger process will ensure that the * components are put together into the editor_plugin.js file and compressed correctly. * @param pluginName {String} name of the plugin to load scripts from (will be used to get the base url for the plugins). * @param scripts {Array} Array containing the names of the scripts to load. - */ + */ addComponents: function(pluginName, scripts) { var pluginUrl = this.urls[pluginName]; tinymce.each(scripts, function(script){ @@ -164,7 +164,7 @@ if (typeof u === "object") url = u.prefix + u.resource + u.suffix; - if (url.indexOf('/') != 0 && url.indexOf('://') == -1) + if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) url = tinymce.baseURL + '/' + url; t.urls[n] = url.substring(0, url.lastIndexOf('/')); @@ -296,7 +296,7 @@ * return { * longname : 'Example plugin', * author : 'Some author', - * authorurl : 'http://tinymce.moxiecode.com', + * authorurl : 'http://www.tinymce.com', * infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/example', * version : "1.0" * }; diff --git a/design/standard/javascript/classes/ControlManager.js b/design/standard/javascript/classes/ControlManager.js index ba8ef74f..99c6ec81 100644 --- a/design/standard/javascript/classes/ControlManager.js +++ b/design/standard/javascript/classes/ControlManager.js @@ -1,11 +1,11 @@ /** * ControlManager.js * - * Copyright 2009, Moxiecode Systems AB + * Copyright, Moxiecode Systems AB * Released under LGPL License. * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing */ (function(tinymce) { @@ -113,31 +113,43 @@ * will be used. * * @method createControl - * @param {String} n Control name to create for example "separator". + * @param {String} name 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; + createControl : function(name) { + var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName; + + // Build control factory cache + if (!self.controlFactories) { + self.controlFactories = []; + each(editor.plugins, function(plugin) { + if (plugin.createControl) { + self.controlFactories.push(plugin); + } + }); + } - each(ed.plugins, function(p) { - if (p.createControl) { - c = p.createControl(n, t); + // Create controls by asking cached factories + factories = self.controlFactories; + for (i = 0, l = factories.length; i < l; i++) { + ctrl = factories[i].createControl(name, self); - if (c) - return false; + if (ctrl) { + return self.add(ctrl); } - }); + } - switch (n) { - case "|": - case "separator": - return t.createSeparator(); + // Create sepearator + if (name === "|" || name === "separator") { + return self.createSeparator(); } - if (!c && ed.buttons && (c = ed.buttons[n])) - return t.createButton(n, c); + // Create control from button collection + if (editor.buttons && (ctrl = editor.buttons[name])) { + return self.createButton(name, ctrl); + } - return t.add(c); + return self.add(ctrl); }, /** @@ -161,6 +173,8 @@ if (v = ed.getParam('skin_variant')) s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1); + s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : ''; + id = t.prefix + id; cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu; c = t.controls[id] = new cls(id, s); diff --git a/design/standard/javascript/classes/Editor.Events.js b/design/standard/javascript/classes/Editor.Events.js new file mode 100644 index 00000000..685690b4 --- /dev/null +++ b/design/standard/javascript/classes/Editor.Events.js @@ -0,0 +1,932 @@ +/** + * Editor.Events.js + * + * Copyright, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +(function(tinymce) { + var each = tinymce.each; + + /** + * Creates all event dispatcher instances for the editor instance and also adds + * passthoughs for legacy callback handlers. + */ + tinymce.Editor.prototype.setupEvents = function() { + var self = this, settings = self.settings; + + // Add events to the editor + each([ + /** + * Fires before the initialization of the editor. + * + * @event onPreInit + * @param {tinymce.Editor} sender Editor instance. + * @see #onInit + * @example + * // Adds an observer to the onPreInit event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onPreInit.add(function(ed) { + * console.debug('PreInit: ' + ed.id); + * }); + * } + * }); + */ + 'onPreInit', + + /** + * Fires before the initialization of the editor. + * + * @event onBeforeRenderUI + * @param {tinymce.Editor} sender Editor instance. + * @example + * // Adds an observer to the onBeforeRenderUI event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onBeforeRenderUI.add(function(ed, cm) { + * console.debug('Before render: ' + ed.id); + * }); + * } + * }); + */ + 'onBeforeRenderUI', + + /** + * Fires after the rendering has completed. + * + * @event onPostRender + * @param {tinymce.Editor} sender Editor instance. + * @example + * // Adds an observer to the onPostRender event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onPostRender.add(function(ed, cm) { + * console.debug('After render: ' + ed.id); + * }); + * } + * }); + */ + 'onPostRender', + + /** + * Fires when the onload event on the body occurs. + * + * @event onLoad + * @param {tinymce.Editor} sender Editor instance. + * @example + * // Adds an observer to the onLoad event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onLoad.add(function(ed, cm) { + * console.debug('Document loaded: ' + ed.id); + * }); + * } + * }); + */ + 'onLoad', + + /** + * Fires after the initialization of the editor is done. + * + * @event onInit + * @param {tinymce.Editor} sender Editor instance. + * @see #onPreInit + * @example + * // Adds an observer to the onInit event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onInit.add(function(ed) { + * console.debug('Editor is done: ' + ed.id); + * }); + * } + * }); + */ + 'onInit', + + /** + * Fires when the editor instance is removed from page. + * + * @event onRemove + * @param {tinymce.Editor} sender Editor instance. + * @example + * // Adds an observer to the onRemove event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onRemove.add(function(ed) { + * console.debug('Editor was removed: ' + ed.id); + * }); + * } + * }); + */ + 'onRemove', + + /** + * Fires when the editor is activated. + * + * @event onActivate + * @param {tinymce.Editor} sender Editor instance. + * @example + * // Adds an observer to the onActivate event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onActivate.add(function(ed) { + * console.debug('Editor was activated: ' + ed.id); + * }); + * } + * }); + */ + 'onActivate', + + /** + * Fires when the editor is deactivated. + * + * @event onDeactivate + * @param {tinymce.Editor} sender Editor instance. + * @example + * // Adds an observer to the onDeactivate event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onDeactivate.add(function(ed) { + * console.debug('Editor was deactivated: ' + ed.id); + * }); + * } + * }); + */ + 'onDeactivate', + + /** + * Fires when something in the body of the editor is clicked. + * + * @event onClick + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onClick event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onClick.add(function(ed, e) { + * console.debug('Editor was clicked: ' + e.target.nodeName); + * }); + * } + * }); + */ + 'onClick', + + /** + * Fires when a registered event is intercepted. + * + * @event onEvent + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onEvent event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onEvent.add(function(ed, e) { + * console.debug('Editor event occured: ' + e.target.nodeName); + * }); + * } + * }); + */ + 'onEvent', + + /** + * Fires when a mouseup event is intercepted inside the editor. + * + * @event onMouseUp + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onMouseUp event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onMouseUp.add(function(ed, e) { + * console.debug('Mouse up event: ' + e.target.nodeName); + * }); + * } + * }); + */ + 'onMouseUp', + + /** + * Fires when a mousedown event is intercepted inside the editor. + * + * @event onMouseDown + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onMouseDown event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onMouseDown.add(function(ed, e) { + * console.debug('Mouse down event: ' + e.target.nodeName); + * }); + * } + * }); + */ + 'onMouseDown', + + /** + * Fires when a dblclick event is intercepted inside the editor. + * + * @event onDblClick + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onDblClick event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onDblClick.add(function(ed, e) { + * console.debug('Double click event: ' + e.target.nodeName); + * }); + * } + * }); + */ + 'onDblClick', + + /** + * Fires when a keydown event is intercepted inside the editor. + * + * @event onKeyDown + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onKeyDown event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onKeyDown.add(function(ed, e) { + * console.debug('Key down event: ' + e.keyCode); + * }); + * } + * }); + */ + 'onKeyDown', + + /** + * Fires when a keydown event is intercepted inside the editor. + * + * @event onKeyUp + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onKeyUp event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onKeyUp.add(function(ed, e) { + * console.debug('Key up event: ' + e.keyCode); + * }); + * } + * }); + */ + 'onKeyUp', + + /** + * Fires when a keypress event is intercepted inside the editor. + * + * @event onKeyPress + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onKeyPress event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onKeyPress.add(function(ed, e) { + * console.debug('Key press event: ' + e.keyCode); + * }); + * } + * }); + */ + 'onKeyPress', + + /** + * Fires when a contextmenu event is intercepted inside the editor. + * + * @event onContextMenu + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onContextMenu event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onContextMenu.add(function(ed, e) { + * console.debug('Context menu event:' + e.target); + * }); + * } + * }); + */ + 'onContextMenu', + + /** + * Fires when a form submit event is intercepted. + * + * @event onSubmit + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onSubmit event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onSubmit.add(function(ed, e) { + * console.debug('Form submit:' + e.target); + * }); + * } + * }); + */ + 'onSubmit', + + /** + * Fires when a form reset event is intercepted. + * + * @event onReset + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onReset event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onReset.add(function(ed, e) { + * console.debug('Form reset:' + e.target); + * }); + * } + * }); + */ + 'onReset', + + /** + * Fires when a paste event is intercepted inside the editor. + * + * @event onPaste + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onPaste event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onPaste.add(function(ed, e) { + * console.debug('Pasted plain text'); + * }); + * } + * }); + */ + 'onPaste', + + /** + * Fires when the Serializer does a preProcess on the contents. + * + * @event onPreProcess + * @param {tinymce.Editor} sender Editor instance. + * @param {Object} obj PreProcess object. + * @option {Node} node DOM node for the item being serialized. + * @option {String} format The specified output format normally "html". + * @option {Boolean} get Is true if the process is on a getContent operation. + * @option {Boolean} set Is true if the process is on a setContent operation. + * @option {Boolean} cleanup Is true if the process is on a cleanup operation. + * @example + * // Adds an observer to the onPreProcess event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onPreProcess.add(function(ed, o) { + * // Add a class to each paragraph in the editor + * ed.dom.addClass(ed.dom.select('p', o.node), 'myclass'); + * }); + * } + * }); + */ + 'onPreProcess', + + /** + * Fires when the Serializer does a postProcess on the contents. + * + * @event onPostProcess + * @param {tinymce.Editor} sender Editor instance. + * @param {Object} obj PreProcess object. + * @example + * // Adds an observer to the onPostProcess event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onPostProcess.add(function(ed, o) { + * // Remove all paragraphs and replace with BR + * o.content = o.content.replace(/
]+>|
/g, '');
+ * o.content = o.content.replace(/<\/p>/g, '
');
+ * });
+ * }
+ * });
+ */
+ 'onPostProcess',
+
+ /**
+ * Fires before new contents is added to the editor. Using for example setContent.
+ *
+ * @event onBeforeSetContent
+ * @param {tinymce.Editor} sender Editor instance.
+ * @example
+ * // Adds an observer to the onBeforeSetContent event using tinyMCE.init
+ * tinyMCE.init({
+ * ...
+ * setup : function(ed) {
+ * ed.onBeforeSetContent.add(function(ed, o) {
+ * // Replaces all a characters with b characters
+ * o.content = o.content.replace(/a/g, 'b');
+ * });
+ * }
+ * });
+ */
+ 'onBeforeSetContent',
+
+ /**
+ * Fires before contents is extracted from the editor using for example getContent.
+ *
+ * @event onBeforeGetContent
+ * @param {tinymce.Editor} sender Editor instance.
+ * @param {Event} evt W3C DOM Event instance.
+ * @example
+ * // Adds an observer to the onBeforeGetContent event using tinyMCE.init
+ * tinyMCE.init({
+ * ...
+ * setup : function(ed) {
+ * ed.onBeforeGetContent.add(function(ed, o) {
+ * console.debug('Before get content.');
+ * });
+ * }
+ * });
+ */
+ 'onBeforeGetContent',
+
+ /**
+ * Fires after the contents has been added to the editor using for example onSetContent.
+ *
+ * @event onSetContent
+ * @param {tinymce.Editor} sender Editor instance.
+ * @example
+ * // Adds an observer to the onSetContent event using tinyMCE.init
+ * tinyMCE.init({
+ * ...
+ * setup : function(ed) {
+ * ed.onSetContent.add(function(ed, o) {
+ * // Replaces all a characters with b characters
+ * o.content = o.content.replace(/a/g, 'b');
+ * });
+ * }
+ * });
+ */
+ 'onSetContent',
+
+ /**
+ * Fires after the contents has been extracted from the editor using for example getContent.
+ *
+ * @event onGetContent
+ * @param {tinymce.Editor} sender Editor instance.
+ * @example
+ * // Adds an observer to the onGetContent event using tinyMCE.init
+ * tinyMCE.init({
+ * ...
+ * setup : function(ed) {
+ * ed.onGetContent.add(function(ed, o) {
+ * // Replace all a characters with b
+ * o.content = o.content.replace(/a/g, 'b');
+ * });
+ * }
+ * });
+ */
+ 'onGetContent',
+
+ /**
+ * Fires when the editor gets loaded with contents for example when the load method is executed.
+ *
+ * @event onLoadContent
+ * @param {tinymce.Editor} sender Editor instance.
+ * @example
+ * // Adds an observer to the onLoadContent event using tinyMCE.init
+ * tinyMCE.init({
+ * ...
+ * setup : function(ed) {
+ * ed.onLoadContent.add(function(ed, o) {
+ * // Output the element name
+ * console.debug(o.element.nodeName);
+ * });
+ * }
+ * });
+ */
+ 'onLoadContent',
+
+ /**
+ * Fires when the editor contents gets saved for example when the save method is executed.
+ *
+ * @event onSaveContent
+ * @param {tinymce.Editor} sender Editor instance.
+ * @example
+ * // Adds an observer to the onSaveContent event using tinyMCE.init
+ * tinyMCE.init({
+ * ...
+ * setup : function(ed) {
+ * ed.onSaveContent.add(function(ed, o) {
+ * // Output the element name
+ * console.debug(o.element.nodeName);
+ * });
+ * }
+ * });
+ */
+ 'onSaveContent',
+
+ /**
+ * Fires when the user changes node location using the mouse or keyboard.
+ *
+ * @event onNodeChange
+ * @param {tinymce.Editor} sender Editor instance.
+ * @example
+ * // Adds an observer to the onNodeChange event using tinyMCE.init
+ * tinyMCE.init({
+ * ...
+ * setup : function(ed) {
+ * ed.onNodeChange.add(function(ed, cm, e) {
+ * // Activates the link button when the caret is placed in a anchor element
+ * if (e.nodeName == 'A')
+ * cm.setActive('link', true);
+ * });
+ * }
+ * });
+ */
+ 'onNodeChange',
+
+ /**
+ * Fires when a new undo level is added to the editor.
+ *
+ * @event onChange
+ * @param {tinymce.Editor} sender Editor instance.
+ * @example
+ * // Adds an observer to the onChange event using tinyMCE.init
+ * tinyMCE.init({
+ * ...
+ * setup : function(ed) {
+ * ed.onChange.add(function(ed, l) {
+ * console.debug('Editor contents was modified. Contents: ' + l.content);
+ * });
+ * }
+ * });
+ */
+ 'onChange',
+
+ /**
+ * Fires before a command gets executed for example "Bold".
+ *
+ * @event onBeforeExecCommand
+ * @param {tinymce.Editor} sender Editor instance.
+ * @example
+ * // Adds an observer to the onBeforeExecCommand event using tinyMCE.init
+ * tinyMCE.init({
+ * ...
+ * setup : function(ed) {
+ * ed.onBeforeExecCommand.add(function(ed, cmd, ui, val) {
+ * console.debug('Command is to be executed: ' + cmd);
+ * });
+ * }
+ * });
+ */
+ 'onBeforeExecCommand',
+
+ /**
+ * Fires after a command is executed for example "Bold".
+ *
+ * @event onExecCommand
+ * @param {tinymce.Editor} sender Editor instance.
+ * @example
+ * // Adds an observer to the onExecCommand event using tinyMCE.init
+ * tinyMCE.init({
+ * ...
+ * setup : function(ed) {
+ * ed.onExecCommand.add(function(ed, cmd, ui, val) {
+ * console.debug('Command was executed: ' + cmd);
+ * });
+ * }
+ * });
+ */
+ 'onExecCommand',
+
+ /**
+ * Fires when the contents is undo:ed.
+ *
+ * @event onUndo
+ * @param {tinymce.Editor} sender Editor instance.
+ * @param {Object} level Undo level object.
+ * @ example
+ * // Adds an observer to the onUndo event using tinyMCE.init
+ * tinyMCE.init({
+ * ...
+ * setup : function(ed) {
+ * ed.onUndo.add(function(ed, level) {
+ * console.debug('Undo was performed: ' + level.content);
+ * });
+ * }
+ * });
+ */
+ 'onUndo',
+
+ /**
+ * Fires when the contents is redo:ed.
+ *
+ * @event onRedo
+ * @param {tinymce.Editor} sender Editor instance.
+ * @param {Object} level Undo level object.
+ * @example
+ * // Adds an observer to the onRedo event using tinyMCE.init
+ * tinyMCE.init({
+ * ...
+ * setup : function(ed) {
+ * ed.onRedo.add(function(ed, level) {
+ * console.debug('Redo was performed: ' +level.content);
+ * });
+ * }
+ * });
+ */
+ 'onRedo',
+
+ /**
+ * Fires when visual aids is enabled/disabled.
+ *
+ * @event onVisualAid
+ * @param {tinymce.Editor} sender Editor instance.
+ * @example
+ * // Adds an observer to the onVisualAid event using tinyMCE.init
+ * tinyMCE.init({
+ * ...
+ * setup : function(ed) {
+ * ed.onVisualAid.add(function(ed, e, s) {
+ * console.debug('onVisualAid event: ' + ed.id + ", State: " + s);
+ * });
+ * }
+ * });
+ */
+ 'onVisualAid',
+
+ /**
+ * Fires when the progress throbber is shown above the editor.
+ *
+ * @event onSetProgressState
+ * @param {tinymce.Editor} sender Editor instance.
+ * @example
+ * // Adds an observer to the onSetProgressState event using tinyMCE.init
+ * tinyMCE.init({
+ * ...
+ * setup : function(ed) {
+ * ed.onSetProgressState.add(function(ed, b) {
+ * if (b)
+ * console.debug('SHOW!');
+ * else
+ * console.debug('HIDE!');
+ * });
+ * }
+ * });
+ */
+ 'onSetProgressState',
+
+ /**
+ * Fires after an attribute is set using setAttrib.
+ *
+ * @event onSetAttrib
+ * @param {tinymce.Editor} sender Editor instance.
+ * @example
+ * // Adds an observer to the onSetAttrib event using tinyMCE.init
+ *tinyMCE.init({
+ * ...
+ * setup : function(ed) {
+ * ed.onSetAttrib.add(function(ed, node, attribute, attributeValue) {
+ * console.log('onSetAttrib tag');
+ * });
+ * }
+ * });
+ */
+ 'onSetAttrib'
+ ], function(name) {
+ self[name] = new tinymce.util.Dispatcher(self);
+ });
+
+ // Handle legacy cleanup_callback option
+ if (settings.cleanup_callback) {
+ self.onBeforeSetContent.add(function(ed, o) {
+ o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
+ });
+
+ self.onPreProcess.add(function(ed, o) {
+ if (o.set)
+ ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
+
+ if (o.get)
+ ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
+ });
+
+ self.onPostProcess.add(function(ed, o) {
+ if (o.set)
+ o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
+
+ if (o.get)
+ o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
+ });
+ }
+
+ // Handle legacy save_callback option
+ if (settings.save_callback) {
+ self.onGetContent.add(function(ed, o) {
+ if (o.save)
+ o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
+ });
+ }
+
+ // Handle legacy handle_event_callback option
+ if (settings.handle_event_callback) {
+ self.onEvent.add(function(ed, e, o) {
+ if (self.execCallback('handle_event_callback', e, ed, o) === false) {
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ });
+ }
+
+ // Handle legacy handle_node_change_callback option
+ if (settings.handle_node_change_callback) {
+ self.onNodeChange.add(function(ed, cm, n) {
+ ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed());
+ });
+ }
+
+ // Handle legacy save_callback option
+ if (settings.save_callback) {
+ self.onSaveContent.add(function(ed, o) {
+ var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
+
+ if (h)
+ o.content = h;
+ });
+ }
+
+ // Handle legacy onchange_callback option
+ if (settings.onchange_callback) {
+ self.onChange.add(function(ed, l) {
+ ed.execCallback('onchange_callback', ed, l);
+ });
+ }
+ };
+
+ /**
+ * Binds native DOM events and sends these out to the dispatchers.
+ */
+ tinymce.Editor.prototype.bindNativeEvents = function() {
+ // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
+ var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap;
+
+ nativeToDispatcherMap = {
+ mouseup : 'onMouseUp',
+ mousedown : 'onMouseDown',
+ click : 'onClick',
+ keyup : 'onKeyUp',
+ keydown : 'onKeyDown',
+ keypress : 'onKeyPress',
+ submit : 'onSubmit',
+ reset : 'onReset',
+ contextmenu : 'onContextMenu',
+ dblclick : 'onDblClick',
+ paste : 'onPaste' // Doesn't work in all browsers yet
+ };
+
+ // Handler that takes a native event and sends it out to a dispatcher like onKeyDown
+ function eventHandler(evt, args) {
+ var type = evt.type;
+
+ // Don't fire events when it's removed
+ if (self.removed)
+ return;
+
+ // Sends the native event out to a global dispatcher then to the specific event dispatcher
+ if (self.onEvent.dispatch(self, evt, args) !== false) {
+ self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args);
+ }
+ };
+
+ // Opera doesn't support focus event for contentEditable elements so we need to fake it
+ function doOperaFocus(e) {
+ self.focus(true);
+ };
+
+ function nodeChanged(ed, e) {
+ // Normalize selection for example a|a becomes a|a except for Ctrl+A since it selects everything
+ if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) {
+ self.selection.normalize();
+ }
+
+ self.nodeChanged();
+ }
+
+ // Add DOM events
+ each(nativeToDispatcherMap, function(dispatcherName, nativeName) {
+ var root = settings.content_editable ? self.getBody() : self.getDoc();
+
+ switch (nativeName) {
+ case 'contextmenu':
+ dom.bind(root, nativeName, eventHandler);
+ break;
+
+ case 'paste':
+ dom.bind(self.getBody(), nativeName, eventHandler);
+ break;
+
+ case 'submit':
+ case 'reset':
+ dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler);
+ break;
+
+ default:
+ dom.bind(root, nativeName, eventHandler);
+ }
+ });
+
+ // Set the editor as active when focused
+ dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) {
+ self.focus(true);
+ });
+
+ if (settings.content_editable && tinymce.isOpera) {
+ dom.bind(self.getBody(), 'click', doOperaFocus);
+ dom.bind(self.getBody(), 'keydown', doOperaFocus);
+ }
+
+ // Add node change handler
+ self.onMouseUp.add(nodeChanged);
+
+ self.onKeyUp.add(function(ed, e) {
+ var keyCode = e.keyCode;
+
+ if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey)
+ nodeChanged(ed, e);
+ });
+
+ // Add reset handler
+ self.onReset.add(function() {
+ self.setContent(self.startContent, {format : 'raw'});
+ });
+
+ // Add shortcuts
+ function handleShortcut(e, execute) {
+ if (e.altKey || e.ctrlKey || e.metaKey) {
+ each(self.shortcuts, function(shortcut) {
+ var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey;
+
+ if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey)
+ return;
+
+ if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
+ e.preventDefault();
+
+ if (execute) {
+ shortcut.func.call(shortcut.scope);
+ }
+
+ return true;
+ }
+ });
+ }
+ };
+
+ self.onKeyUp.add(function(ed, e) {
+ handleShortcut(e);
+ });
+
+ self.onKeyPress.add(function(ed, e) {
+ handleShortcut(e);
+ });
+
+ self.onKeyDown.add(function(ed, e) {
+ handleShortcut(e, true);
+ });
+
+ if (tinymce.isOpera) {
+ self.onClick.add(function(ed, e) {
+ e.preventDefault();
+ });
+ }
+ };
+})(tinymce);
\ No newline at end of file
diff --git a/design/standard/javascript/classes/Editor.js b/design/standard/javascript/classes/Editor.js
index eecc7137..8bc8c6d0 100644
--- a/design/standard/javascript/classes/Editor.js
+++ b/design/standard/javascript/classes/Editor.js
@@ -1,20 +1,20 @@
/**
* Editor.js
*
- * Copyright 2009, Moxiecode Systems AB
+ * Copyright, Moxiecode Systems AB
* Released under LGPL License.
*
- * License: http://tinymce.moxiecode.com/license
- * Contributing: http://tinymce.moxiecode.com/contributing
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
*/
(function(tinymce) {
// Shorten these names
var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
- Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
+ each = tinymce.each, isGecko = tinymce.isGecko,
isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
- inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode, VK = tinymce.VK;
+ explode = tinymce.explode;
/**
* This class contains the core logic for a TinyMCE editor.
@@ -46,11 +46,63 @@
* @constructor
* @method Editor
* @param {String} id Unique id for the editor.
- * @param {Object} s Optional settings string for the editor.
+ * @param {Object} settings Optional settings string for the editor.
* @author Moxiecode
*/
- Editor : function(id, s) {
- var t = this;
+ Editor : function(id, settings) {
+ var self = this, TRUE = true;
+
+ /**
+ * Name/value collection with editor settings.
+ *
+ * @property settings
+ * @type Object
+ * @example
+ * // Get the value of the theme setting
+ * tinyMCE.activeEditor.windowManager.alert("You are using the " + tinyMCE.activeEditor.settings.theme + " theme");
+ */
+ self.settings = settings = extend({
+ id : id,
+ language : 'en',
+ theme : 'advanced',
+ skin : 'default',
+ delta_width : 0,
+ delta_height : 0,
+ popup_css : '',
+ plugins : '',
+ document_base_url : tinymce.documentBaseURL,
+ add_form_submit_trigger : TRUE,
+ submit_patch : TRUE,
+ add_unload_trigger : TRUE,
+ convert_urls : TRUE,
+ relative_urls : TRUE,
+ remove_script_host : TRUE,
+ table_inline_editing : false,
+ object_resizing : TRUE,
+ accessibility_focus : TRUE,
+ doctype : tinymce.isIE6 ? '' : '', // Use old doctype on IE 6 to avoid horizontal scroll
+ visual : TRUE,
+ font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
+ font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
+ apply_source_formatting : TRUE,
+ directionality : 'ltr',
+ forced_root_block : 'p',
+ hidden_input : TRUE,
+ padd_empty_editor : TRUE,
+ render_ui : TRUE,
+ indentation : '30px',
+ fix_table_elements : TRUE,
+ inline_styles : TRUE,
+ convert_fonts_to_spans : TRUE,
+ indent : 'simple',
+ indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
+ indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
+ validate : TRUE,
+ entity_encoding : 'named',
+ url_converter : self.convertURL,
+ url_converter_scope : self,
+ ie7_compat : TRUE
+ }, settings);
/**
* Editor instance id, normally the same as the div/textarea that was replaced.
@@ -58,11 +110,7 @@
* @property id
* @type String
*/
- t.id = t.editorId = id;
-
- t.execCommands = {};
- t.queryStateCommands = {};
- t.queryValueCommands = {};
+ self.id = self.editorId = id;
/**
* State to force the editor to return false on a isDirty call.
@@ -79,7 +127,7 @@
* ed.isNotDirty = 1; // Force not dirty state
* }
*/
- t.isNotDirty = false;
+ self.isNotDirty = false;
/**
* Name/Value object containting plugin instances.
@@ -90,777 +138,7 @@
* // Execute a method inside a plugin directly
* tinyMCE.activeEditor.plugins.someplugin.someMethod();
*/
- t.plugins = {};
-
- // Add events to the editor
- each([
- /**
- * Fires before the initialization of the editor.
- *
- * @event onPreInit
- * @param {tinymce.Editor} sender Editor instance.
- * @see #onInit
- * @example
- * // Adds an observer to the onPreInit event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onPreInit.add(function(ed) {
- * console.debug('PreInit: ' + ed.id);
- * });
- * }
- * });
- */
- 'onPreInit',
-
- /**
- * Fires before the initialization of the editor.
- *
- * @event onBeforeRenderUI
- * @param {tinymce.Editor} sender Editor instance.
- * @example
- * // Adds an observer to the onBeforeRenderUI event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onBeforeRenderUI.add(function(ed, cm) {
- * console.debug('Before render: ' + ed.id);
- * });
- * }
- * });
- */
- 'onBeforeRenderUI',
-
- /**
- * Fires after the rendering has completed.
- *
- * @event onPostRender
- * @param {tinymce.Editor} sender Editor instance.
- * @example
- * // Adds an observer to the onPostRender event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onPostRender.add(function(ed, cm) {
- * console.debug('After render: ' + ed.id);
- * });
- * }
- * });
- */
- 'onPostRender',
-
- /**
- * Fires when the onload event on the body occurs.
- *
- * @event onLoad
- * @param {tinymce.Editor} sender Editor instance.
- * @example
- * // Adds an observer to the onLoad event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onLoad.add(function(ed, cm) {
- * console.debug('Document loaded: ' + ed.id);
- * });
- * }
- * });
- */
- 'onLoad',
-
- /**
- * Fires after the initialization of the editor is done.
- *
- * @event onInit
- * @param {tinymce.Editor} sender Editor instance.
- * @see #onPreInit
- * @example
- * // Adds an observer to the onInit event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onInit.add(function(ed) {
- * console.debug('Editor is done: ' + ed.id);
- * });
- * }
- * });
- */
- 'onInit',
-
- /**
- * Fires when the editor instance is removed from page.
- *
- * @event onRemove
- * @param {tinymce.Editor} sender Editor instance.
- * @example
- * // Adds an observer to the onRemove event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onRemove.add(function(ed) {
- * console.debug('Editor was removed: ' + ed.id);
- * });
- * }
- * });
- */
- 'onRemove',
-
- /**
- * Fires when the editor is activated.
- *
- * @event onActivate
- * @param {tinymce.Editor} sender Editor instance.
- * @example
- * // Adds an observer to the onActivate event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onActivate.add(function(ed) {
- * console.debug('Editor was activated: ' + ed.id);
- * });
- * }
- * });
- */
- 'onActivate',
-
- /**
- * Fires when the editor is deactivated.
- *
- * @event onDeactivate
- * @param {tinymce.Editor} sender Editor instance.
- * @example
- * // Adds an observer to the onDeactivate event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onDeactivate.add(function(ed) {
- * console.debug('Editor was deactivated: ' + ed.id);
- * });
- * }
- * });
- */
- 'onDeactivate',
-
- /**
- * Fires when something in the body of the editor is clicked.
- *
- * @event onClick
- * @param {tinymce.Editor} sender Editor instance.
- * @param {Event} evt W3C DOM Event instance.
- * @example
- * // Adds an observer to the onClick event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onClick.add(function(ed, e) {
- * console.debug('Editor was clicked: ' + e.target.nodeName);
- * });
- * }
- * });
- */
- 'onClick',
-
- /**
- * Fires when a registered event is intercepted.
- *
- * @event onEvent
- * @param {tinymce.Editor} sender Editor instance.
- * @param {Event} evt W3C DOM Event instance.
- * @example
- * // Adds an observer to the onEvent event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onEvent.add(function(ed, e) {
- * console.debug('Editor event occured: ' + e.target.nodeName);
- * });
- * }
- * });
- */
- 'onEvent',
-
- /**
- * Fires when a mouseup event is intercepted inside the editor.
- *
- * @event onMouseUp
- * @param {tinymce.Editor} sender Editor instance.
- * @param {Event} evt W3C DOM Event instance.
- * @example
- * // Adds an observer to the onMouseUp event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onMouseUp.add(function(ed, e) {
- * console.debug('Mouse up event: ' + e.target.nodeName);
- * });
- * }
- * });
- */
- 'onMouseUp',
-
- /**
- * Fires when a mousedown event is intercepted inside the editor.
- *
- * @event onMouseDown
- * @param {tinymce.Editor} sender Editor instance.
- * @param {Event} evt W3C DOM Event instance.
- * @example
- * // Adds an observer to the onMouseDown event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onMouseDown.add(function(ed, e) {
- * console.debug('Mouse down event: ' + e.target.nodeName);
- * });
- * }
- * });
- */
- 'onMouseDown',
-
- /**
- * Fires when a dblclick event is intercepted inside the editor.
- *
- * @event onDblClick
- * @param {tinymce.Editor} sender Editor instance.
- * @param {Event} evt W3C DOM Event instance.
- * @example
- * // Adds an observer to the onDblClick event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onDblClick.add(function(ed, e) {
- * console.debug('Double click event: ' + e.target.nodeName);
- * });
- * }
- * });
- */
- 'onDblClick',
-
- /**
- * Fires when a keydown event is intercepted inside the editor.
- *
- * @event onKeyDown
- * @param {tinymce.Editor} sender Editor instance.
- * @param {Event} evt W3C DOM Event instance.
- * @example
- * // Adds an observer to the onKeyDown event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onKeyDown.add(function(ed, e) {
- * console.debug('Key down event: ' + e.keyCode);
- * });
- * }
- * });
- */
- 'onKeyDown',
-
- /**
- * Fires when a keydown event is intercepted inside the editor.
- *
- * @event onKeyUp
- * @param {tinymce.Editor} sender Editor instance.
- * @param {Event} evt W3C DOM Event instance.
- * @example
- * // Adds an observer to the onKeyUp event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onKeyUp.add(function(ed, e) {
- * console.debug('Key up event: ' + e.keyCode);
- * });
- * }
- * });
- */
- 'onKeyUp',
-
- /**
- * Fires when a keypress event is intercepted inside the editor.
- *
- * @event onKeyPress
- * @param {tinymce.Editor} sender Editor instance.
- * @param {Event} evt W3C DOM Event instance.
- * @example
- * // Adds an observer to the onKeyPress event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onKeyPress.add(function(ed, e) {
- * console.debug('Key press event: ' + e.keyCode);
- * });
- * }
- * });
- */
- 'onKeyPress',
-
- /**
- * Fires when a contextmenu event is intercepted inside the editor.
- *
- * @event onContextMenu
- * @param {tinymce.Editor} sender Editor instance.
- * @param {Event} evt W3C DOM Event instance.
- * @example
- * // Adds an observer to the onContextMenu event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onContextMenu.add(function(ed, e) {
- * console.debug('Context menu event:' + e.target);
- * });
- * }
- * });
- */
- 'onContextMenu',
-
- /**
- * Fires when a form submit event is intercepted.
- *
- * @event onSubmit
- * @param {tinymce.Editor} sender Editor instance.
- * @param {Event} evt W3C DOM Event instance.
- * @example
- * // Adds an observer to the onSubmit event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onSubmit.add(function(ed, e) {
- * console.debug('Form submit:' + e.target);
- * });
- * }
- * });
- */
- 'onSubmit',
-
- /**
- * Fires when a form reset event is intercepted.
- *
- * @event onReset
- * @param {tinymce.Editor} sender Editor instance.
- * @param {Event} evt W3C DOM Event instance.
- * @example
- * // Adds an observer to the onReset event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onReset.add(function(ed, e) {
- * console.debug('Form reset:' + e.target);
- * });
- * }
- * });
- */
- 'onReset',
-
- /**
- * Fires when a paste event is intercepted inside the editor.
- *
- * @event onPaste
- * @param {tinymce.Editor} sender Editor instance.
- * @param {Event} evt W3C DOM Event instance.
- * @example
- * // Adds an observer to the onPaste event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onPaste.add(function(ed, e) {
- * console.debug('Pasted plain text');
- * });
- * }
- * });
- */
- 'onPaste',
-
- /**
- * Fires when the Serializer does a preProcess on the contents.
- *
- * @event onPreProcess
- * @param {tinymce.Editor} sender Editor instance.
- * @param {Object} obj PreProcess object.
- * @option {Node} node DOM node for the item being serialized.
- * @option {String} format The specified output format normally "html".
- * @option {Boolean} get Is true if the process is on a getContent operation.
- * @option {Boolean} set Is true if the process is on a setContent operation.
- * @option {Boolean} cleanup Is true if the process is on a cleanup operation.
- * @example
- * // Adds an observer to the onPreProcess event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onPreProcess.add(function(ed, o) {
- * // Add a class to each paragraph in the editor
- * ed.dom.addClass(ed.dom.select('p', o.node), 'myclass');
- * });
- * }
- * });
- */
- 'onPreProcess',
-
- /**
- * Fires when the Serializer does a postProcess on the contents.
- *
- * @event onPostProcess
- * @param {tinymce.Editor} sender Editor instance.
- * @param {Object} obj PreProcess object.
- * @example
- * // Adds an observer to the onPostProcess event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onPostProcess.add(function(ed, o) {
- * // Remove all paragraphs and replace with BR
- * o.content = o.content.replace(/
]+>|
/g, '');
- * o.content = o.content.replace(/<\/p>/g, '
');
- * });
- * }
- * });
- */
- 'onPostProcess',
-
- /**
- * Fires before new contents is added to the editor. Using for example setContent.
- *
- * @event onBeforeSetContent
- * @param {tinymce.Editor} sender Editor instance.
- * @example
- * // Adds an observer to the onBeforeSetContent event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onBeforeSetContent.add(function(ed, o) {
- * // Replaces all a characters with b characters
- * o.content = o.content.replace(/a/g, 'b');
- * });
- * }
- * });
- */
- 'onBeforeSetContent',
-
- /**
- * Fires before contents is extracted from the editor using for example getContent.
- *
- * @event onBeforeGetContent
- * @param {tinymce.Editor} sender Editor instance.
- * @param {Event} evt W3C DOM Event instance.
- * @example
- * // Adds an observer to the onBeforeGetContent event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onBeforeGetContent.add(function(ed, o) {
- * console.debug('Before get content.');
- * });
- * }
- * });
- */
- 'onBeforeGetContent',
-
- /**
- * Fires after the contents has been added to the editor using for example onSetContent.
- *
- * @event onSetContent
- * @param {tinymce.Editor} sender Editor instance.
- * @example
- * // Adds an observer to the onSetContent event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onSetContent.add(function(ed, o) {
- * // Replaces all a characters with b characters
- * o.content = o.content.replace(/a/g, 'b');
- * });
- * }
- * });
- */
- 'onSetContent',
-
- /**
- * Fires after the contents has been extracted from the editor using for example getContent.
- *
- * @event onGetContent
- * @param {tinymce.Editor} sender Editor instance.
- * @example
- * // Adds an observer to the onGetContent event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onGetContent.add(function(ed, o) {
- * // Replace all a characters with b
- * o.content = o.content.replace(/a/g, 'b');
- * });
- * }
- * });
- */
- 'onGetContent',
-
- /**
- * Fires when the editor gets loaded with contents for example when the load method is executed.
- *
- * @event onLoadContent
- * @param {tinymce.Editor} sender Editor instance.
- * @example
- * // Adds an observer to the onLoadContent event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onLoadContent.add(function(ed, o) {
- * // Output the element name
- * console.debug(o.element.nodeName);
- * });
- * }
- * });
- */
- 'onLoadContent',
-
- /**
- * Fires when the editor contents gets saved for example when the save method is executed.
- *
- * @event onSaveContent
- * @param {tinymce.Editor} sender Editor instance.
- * @example
- * // Adds an observer to the onSaveContent event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onSaveContent.add(function(ed, o) {
- * // Output the element name
- * console.debug(o.element.nodeName);
- * });
- * }
- * });
- */
- 'onSaveContent',
-
- /**
- * Fires when the user changes node location using the mouse or keyboard.
- *
- * @event onNodeChange
- * @param {tinymce.Editor} sender Editor instance.
- * @example
- * // Adds an observer to the onNodeChange event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onNodeChange.add(function(ed, cm, e) {
- * // Activates the link button when the caret is placed in a anchor element
- * if (e.nodeName == 'A')
- * cm.setActive('link', true);
- * });
- * }
- * });
- */
- 'onNodeChange',
-
- /**
- * Fires when a new undo level is added to the editor.
- *
- * @event onChange
- * @param {tinymce.Editor} sender Editor instance.
- * @example
- * // Adds an observer to the onChange event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onChange.add(function(ed, l) {
- * console.debug('Editor contents was modified. Contents: ' + l.content);
- * });
- * }
- * });
- */
- 'onChange',
-
- /**
- * Fires before a command gets executed for example "Bold".
- *
- * @event onBeforeExecCommand
- * @param {tinymce.Editor} sender Editor instance.
- * @example
- * // Adds an observer to the onBeforeExecCommand event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onBeforeExecCommand.add(function(ed, cmd, ui, val) {
- * console.debug('Command is to be executed: ' + cmd);
- * });
- * }
- * });
- */
- 'onBeforeExecCommand',
-
- /**
- * Fires after a command is executed for example "Bold".
- *
- * @event onExecCommand
- * @param {tinymce.Editor} sender Editor instance.
- * @example
- * // Adds an observer to the onExecCommand event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onExecCommand.add(function(ed, cmd, ui, val) {
- * console.debug('Command was executed: ' + cmd);
- * });
- * }
- * });
- */
- 'onExecCommand',
-
- /**
- * Fires when the contents is undo:ed.
- *
- * @event onUndo
- * @param {tinymce.Editor} sender Editor instance.
- * @param {Object} level Undo level object.
- * @ example
- * // Adds an observer to the onUndo event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onUndo.add(function(ed, level) {
- * console.debug('Undo was performed: ' + level.content);
- * });
- * }
- * });
- */
- 'onUndo',
-
- /**
- * Fires when the contents is redo:ed.
- *
- * @event onRedo
- * @param {tinymce.Editor} sender Editor instance.
- * @param {Object} level Undo level object.
- * @example
- * // Adds an observer to the onRedo event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onRedo.add(function(ed, level) {
- * console.debug('Redo was performed: ' +level.content);
- * });
- * }
- * });
- */
- 'onRedo',
-
- /**
- * Fires when visual aids is enabled/disabled.
- *
- * @event onVisualAid
- * @param {tinymce.Editor} sender Editor instance.
- * @example
- * // Adds an observer to the onVisualAid event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onVisualAid.add(function(ed, e, s) {
- * console.debug('onVisualAid event: ' + ed.id + ", State: " + s);
- * });
- * }
- * });
- */
- 'onVisualAid',
-
- /**
- * Fires when the progress throbber is shown above the editor.
- *
- * @event onSetProgressState
- * @param {tinymce.Editor} sender Editor instance.
- * @example
- * // Adds an observer to the onSetProgressState event using tinyMCE.init
- * tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onSetProgressState.add(function(ed, b) {
- * if (b)
- * console.debug('SHOW!');
- * else
- * console.debug('HIDE!');
- * });
- * }
- * });
- */
- 'onSetProgressState',
-
- /**
- * Fires after an attribute is set using setAttrib.
- *
- * @event onSetAttrib
- * @param {tinymce.Editor} sender Editor instance.
- * @example
- * // Adds an observer to the onSetAttrib event using tinyMCE.init
- *tinyMCE.init({
- * ...
- * setup : function(ed) {
- * ed.onSetAttrib.add(function(ed, node, attribute, attributeValue) {
- * console.log('onSetAttrib tag');
- * });
- * }
- * });
- */
- 'onSetAttrib'
- ], function(e) {
- t[e] = new Dispatcher(t);
- });
-
- /**
- * Name/value collection with editor settings.
- *
- * @property settings
- * @type Object
- * @example
- * // Get the value of the theme setting
- * tinyMCE.activeEditor.windowManager.alert("You are using the " + tinyMCE.activeEditor.settings.theme + " theme");
- */
- t.settings = s = extend({
- id : id,
- language : 'en',
- docs_language : 'en',
- theme : 'simple',
- skin : 'default',
- delta_width : 0,
- delta_height : 0,
- popup_css : '',
- plugins : '',
- document_base_url : tinymce.documentBaseURL,
- add_form_submit_trigger : 1,
- submit_patch : 1,
- add_unload_trigger : 1,
- convert_urls : 1,
- relative_urls : 1,
- remove_script_host : 1,
- table_inline_editing : 0,
- object_resizing : 1,
- cleanup : 1,
- accessibility_focus : 1,
- custom_shortcuts : 1,
- custom_undo_redo_keyboard_shortcuts : 1,
- custom_undo_redo_restore_selection : 1,
- custom_undo_redo : 1,
- doctype : tinymce.isIE6 ? '' : '', // Use old doctype on IE 6 to avoid horizontal scroll
- visual_table_class : 'mceItemTable',
- visual : 1,
- font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
- font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
- apply_source_formatting : 1,
- directionality : 'ltr',
- forced_root_block : 'p',
- hidden_input : 1,
- padd_empty_editor : 1,
- render_ui : 1,
- init_theme : 1,
- force_p_newlines : 1,
- indentation : '30px',
- keep_styles : 1,
- fix_table_elements : 1,
- inline_styles : 1,
- convert_fonts_to_spans : true,
- indent : 'simple',
- indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
- indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
- validate : true,
- entity_encoding : 'named',
- url_converter : t.convertURL,
- url_converter_scope : t,
- ie7_compat : true
- }, s);
+ self.plugins = {};
/**
* URI object to document configured for the TinyMCE instance.
@@ -874,7 +152,7 @@
* // Get absolute URL from the location of document_base_url
* tinyMCE.activeEditor.documentBaseURI.toAbsolute('somefile.htm');
*/
- t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
+ self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, {
base_uri : tinyMCE.baseURI
});
@@ -890,7 +168,7 @@
* // Get absolute URL from the location of the API
* tinyMCE.activeEditor.baseURI.toAbsolute('somefile.htm');
*/
- t.baseURI = tinymce.baseURI;
+ self.baseURI = tinymce.baseURI;
/**
* Array with CSS files to load into the iframe.
@@ -898,10 +176,26 @@
* @property contentCSS
* @type Array
*/
- t.contentCSS = [];
+ self.contentCSS = [];
+
+ /**
+ * Array of CSS styles to add to head of document when the editor loads.
+ *
+ * @property contentStyles
+ * @type Array
+ */
+ self.contentStyles = [];
+
+ // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
+ self.setupEvents();
+
+ // Internal command handler objects
+ self.execCommands = {};
+ self.queryStateCommands = {};
+ self.queryValueCommands = {};
// Call setup
- t.execCallback('setup', t);
+ self.execCallback('setup', self);
},
/**
@@ -914,7 +208,7 @@
// Page is not loaded yet, wait for it
if (!Event.domLoaded) {
- Event.add(document, 'init', function() {
+ Event.add(window, 'ready', function() {
t.render();
});
return;
@@ -927,8 +221,7 @@
return;
// Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff
- // here since the browser says it has contentEditable support but there is no visible
- // caret We will remove this check ones Apple implements full contentEditable support
+ // here since the browser says it has contentEditable support but there is no visible caret.
if (tinymce.isIDevice && !tinymce.isIOS5)
return;
@@ -936,6 +229,12 @@
if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
+ // Hide target element early to prevent content flashing
+ if (!s.content_editable) {
+ t.orgVisibility = t.getElement().style.visibility;
+ t.getElement().style.visibility = 'hidden';
+ }
+
/**
* Window manager reference, use this to open new windows and dialogs.
*
@@ -1016,7 +315,7 @@
if (s.language && s.language_load !== false)
sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
- if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
+ if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
each(explode(s.plugins), function(p) {
@@ -1026,9 +325,8 @@
var dependencies = PluginManager.dependencies(p);
each(dependencies, function(dep) {
var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};
- var dep = PluginManager.createUrl(defaultSettings, dep);
+ dep = PluginManager.createUrl(defaultSettings, dep);
PluginManager.load(dep.resource, dep);
-
});
} else {
// Skip safari plugin, since it is removed as of 3.3b1
@@ -1058,7 +356,7 @@
* @method init
*/
init : function() {
- var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
+ var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
tinymce.add(t);
@@ -1074,13 +372,18 @@
* tinyMCE.activeEditor.theme.someMethod();
*/
if (s.theme) {
- s.theme = s.theme.replace(/-/, '');
- o = ThemeManager.get(s.theme);
- t.theme = new o();
-
- if (t.theme.init && s.init_theme)
- t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
+ if (typeof s.theme != "function") {
+ s.theme = s.theme.replace(/-/, '');
+ o = ThemeManager.get(s.theme);
+ t.theme = new o();
+
+ if (t.theme.init)
+ t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
+ } else {
+ t.theme = s.theme;
+ }
}
+
function initPlugin(p) {
var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
if (c && tinymce.inArray(initializedPlugins,p) === -1) {
@@ -1123,95 +426,94 @@
*/
t.controlManager = new tinymce.ControlManager(t);
- if (s.custom_undo_redo) {
- t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
- if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
- t.undoManager.beforeChange();
- });
-
- t.onExecCommand.add(function(ed, cmd, ui, val, a) {
- if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
- t.undoManager.add();
- });
- }
-
- t.onExecCommand.add(function(ed, c) {
- // Don't refresh the select lists until caret move
- if (!/^(FontName|FontSize)$/.test(c))
- t.nodeChanged();
- });
-
- // Remove ghost selections on images and tables in Gecko
- if (isGecko) {
- function repaint(a, o) {
- if (!o || !o.initial)
- t.execCommand('mceRepaint');
- };
-
- t.onUndo.add(repaint);
- t.onRedo.add(repaint);
- t.onSetContent.add(repaint);
- }
-
// Enables users to override the control factory
t.onBeforeRenderUI.dispatch(t, t.controlManager);
// Measure box
- if (s.render_ui) {
- w = s.width || e.style.width || e.offsetWidth;
- h = s.height || e.style.height || e.offsetHeight;
+ if (s.render_ui && t.theme) {
t.orgDisplay = e.style.display;
- re = /^[0-9\.]+(|px)$/i;
- if (re.test('' + w))
- w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
+ if (typeof s.theme != "function") {
+ w = s.width || e.style.width || e.offsetWidth;
+ h = s.height || e.style.height || e.offsetHeight;
+ mh = s.min_height || 100;
+ re = /^[0-9\.]+(|px)$/i;
+
+ if (re.test('' + w))
+ w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100);
+
+ if (re.test('' + h))
+ h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh);
+
+ // Render UI
+ o = t.theme.renderUI({
+ targetNode : e,
+ width : w,
+ height : h,
+ deltaWidth : s.delta_width,
+ deltaHeight : s.delta_height
+ });
- if (re.test('' + h))
- h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
+ // Resize editor
+ DOM.setStyles(o.sizeContainer || o.editorContainer, {
+ width : w,
+ height : h
+ });
- // Render UI
- o = t.theme.renderUI({
- targetNode : e,
- width : w,
- height : h,
- deltaWidth : s.delta_width,
- deltaHeight : s.delta_height
- });
+ h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
+ if (h < mh)
+ h = mh;
+ } else {
+ o = s.theme(t, e);
+
+ // Convert element type to id:s
+ if (o.editorContainer.nodeType) {
+ o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent";
+ }
+
+ // Convert element type to id:s
+ if (o.iframeContainer.nodeType) {
+ o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer";
+ }
+
+ // Use specified iframe height or the targets offsetHeight
+ h = o.iframeHeight || e.offsetHeight;
+
+ // Store away the selection when it's changed to it can be restored later with a editor.focus() call
+ if (isIE) {
+ t.onInit.add(function(ed) {
+ ed.dom.bind(ed.getBody(), 'beforedeactivate keydown', function() {
+ ed.lastIERng = ed.selection.getRng();
+ });
+ });
+ }
+ }
t.editorContainer = o.editorContainer;
}
- // #ifdef contentEditable
+ // Load specified content CSS last
+ if (s.content_css) {
+ each(explode(s.content_css), function(u) {
+ t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
+ });
+ }
+
+ // Load specified content CSS last
+ if (s.content_style) {
+ t.contentStyles.push(s.content_style);
+ }
// Content editable mode ends here
if (s.content_editable) {
e = n = o = null; // Fix IE leak
- return t.setupContentEditable();
+ return t.initContentBody();
}
- // #endif
-
// User specified a document.domain value
if (document.domain && location.hostname != document.domain)
tinymce.relaxedDomain = document.domain;
- // Resize editor
- DOM.setStyles(o.sizeContainer || o.editorContainer, {
- width : w,
- height : h
- });
-
- // Load specified content CSS last
- if (s.content_css) {
- tinymce.each(explode(s.content_css), function(u) {
- t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
- });
- }
-
- h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
- if (h < 100)
- h = 100;
-
t.iframeHTML = s.doctype + '
' + o.content + ''; - }); - } + if (settings.directionality) + body.dir = settings.directionality; - if (s.verify_css_classes) { - t.serializer.attribValueFilter = function(n, v) { - var s, cl; + if (settings.nowrap) + body.style.whiteSpace = "nowrap"; - if (n == 'class') { - // Build regexp for classes - if (!t.classesRE) { - cl = t.dom.getClasses(); - - if (cl.length > 0) { - s = ''; - - each (cl, function(o) { - s += (s ? '|' : '') + o['class']; - }); - - t.classesRE = new RegExp('(' + s + ')', 'gi'); - } - } - - return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : ''; - } - - return v; - }; - } - - if (s.cleanup_callback) { - t.onBeforeSetContent.add(function(ed, o) { - o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); - }); - - t.onPreProcess.add(function(ed, o) { - if (o.set) - t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o); - - if (o.get) - t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o); - }); - - t.onPostProcess.add(function(ed, o) { - if (o.set) - o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); - - if (o.get) - o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o); - }); - } - - if (s.save_callback) { - t.onGetContent.add(function(ed, o) { - if (o.save) - o.content = t.execCallback('save_callback', t.id, o.content, t.getBody()); - }); - } - - if (s.handle_event_callback) { - t.onEvent.add(function(ed, e, o) { - if (t.execCallback('handle_event_callback', e, ed, o) === false) - Event.cancel(e); + if (settings.protect) { + self.onBeforeSetContent.add(function(ed, o) { + each(settings.protect, function(pattern) { + o.content = o.content.replace(pattern, function(str) { + return ''; + }); + }); }); } // Add visual aids when new contents is added - t.onSetContent.add(function() { - t.addVisual(t.getBody()); + self.onSetContent.add(function() { + self.addVisual(self.getBody()); }); // Remove empty contents - if (s.padd_empty_editor) { - t.onPostProcess.add(function(ed, o) { + if (settings.padd_empty_editor) { + self.onPostProcess.add(function(ed, o) { o.content = o.content.replace(/^(
]*>( | |\s|\u00a0|)<\/p>[\r\n]*|
[\r\n]*)$/, '');
});
}
- if (isGecko) {
- // Fix gecko link bug, when a link is placed at the end of block elements there is
- // no way to move the caret behind the link. This fix adds a bogus br element after the link
- function fixLinks(ed, o) {
- each(ed.dom.select('a'), function(n) {
- var pn = n.parentNode;
-
- if (ed.dom.isBlock(pn) && pn.lastChild === n)
- ed.dom.add(pn, 'br', {'data-mce-bogus' : 1});
- });
- };
-
- t.onExecCommand.add(function(ed, cmd) {
- if (cmd === 'CreateLink')
- fixLinks(ed);
- });
-
- t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
- }
+ self.load({initial : true, format : 'html'});
+ self.startContent = self.getContent({format : 'raw'});
- t.load({initial : true, format : 'html'});
- t.startContent = t.getContent({format : 'raw'});
- t.undoManager.add();
/**
* Is set to true after the editor instance has been initialized
*
@@ -1751,23 +836,34 @@
* return editor && editor.initialized;
* }
*/
- t.initialized = true;
+ self.initialized = true;
- t.onInit.dispatch(t);
- t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
- t.execCallback('init_instance_callback', t);
- t.focus(true);
- t.nodeChanged({initial : 1});
+ self.onInit.dispatch(self);
+ self.execCallback('setupcontent_callback', self.id, body, doc);
+ self.execCallback('init_instance_callback', self);
+ self.focus(true);
+ self.nodeChanged({initial : true});
+
+ // Add editor specific CSS styles
+ if (self.contentStyles.length > 0) {
+ contentCssText = '';
+
+ each(self.contentStyles, function(style) {
+ contentCssText += style + "\r\n";
+ });
+
+ self.dom.addStyle(contentCssText);
+ }
// Load specified content CSS last
- each(t.contentCSS, function(u) {
- t.dom.loadCSS(u);
+ each(self.contentCSS, function(url) {
+ self.dom.loadCSS(url);
});
// Handle auto focus
- if (s.auto_focus) {
+ if (settings.auto_focus) {
setTimeout(function () {
- var ed = tinymce.get(s.auto_focus);
+ var ed = tinymce.get(settings.auto_focus);
ed.selection.select(ed.getBody(), 1);
ed.selection.collapse(1);
@@ -1776,135 +872,52 @@
}, 100);
}
- e = null;
- },
-
- // #ifdef contentEditable
-
- /**
- * Sets up the contentEditable mode.
- *
- * @method setupContentEditable
- */
- setupContentEditable : function() {
- var t = this, s = t.settings, e = t.getElement();
-
- t.contentDocument = s.content_document || document;
- t.contentWindow = s.content_window || window;
- t.bodyElement = e;
-
- // Prevent leak in IE
- s.content_document = s.content_window = null;
-
- DOM.hide(e);
- e.contentEditable = t.getParam('content_editable_state', true);
- DOM.show(e);
-
- if (!s.gecko_spellcheck)
- t.getDoc().body.spellcheck = 0;
-
- // Setup objects
- t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
- keep_values : true,
- url_converter : t.convertURL,
- url_converter_scope : t,
- hex_colors : s.force_hex_style_colors,
- class_filter : s.class_filter,
- root_element : t.id,
- fix_ie_paragraphs : 1,
- update_styles : 1
- });
-
- t.serializer = new tinymce.dom.Serializer(s, t.dom, schema);
-
- t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
- t.forceBlocks = new tinymce.ForceBlocks(t, {
- forced_root_block : s.forced_root_block
- });
-
- t.editorCommands = new tinymce.EditorCommands(t);
-
- // Pass through
- t.serializer.onPreProcess.add(function(se, o) {
- return t.onPreProcess.dispatch(t, o, se);
- });
-
- t.serializer.onPostProcess.add(function(se, o) {
- return t.onPostProcess.dispatch(t, o, se);
- });
-
- t.onPreInit.dispatch(t);
- t._addEvents();
-
- t.controlManager.onPostRender.dispatch(t, t.controlManager);
- t.onPostRender.dispatch(t);
-
- t.onSetContent.add(function() {
- t.addVisual(t.getBody());
- });
-
- //t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')});
- t.startContent = t.getContent({format : 'raw'});
- t.undoManager.add({initial : true});
- t.initialized = true;
-
- t.onInit.dispatch(t);
- t.focus(true);
- t.nodeChanged({initial : 1});
-
- // Load specified content CSS last
- if (s.content_css) {
- each(explode(s.content_css), function(u) {
- t.dom.loadCSS(t.documentBaseURI.toAbsolute(u));
- });
- }
-
- if (isIE) {
- // Store away selection
- t.dom.bind(t.getElement(), 'beforedeactivate', function() {
- t.lastSelectionBookmark = t.selection.getBookmark(1);
- });
-
- t.onBeforeExecCommand.add(function(ed, cmd, ui, val, o) {
- if (!DOM.getParent(ed.selection.getStart(), function(n) {return n == ed.getBody();}))
- o.terminate = 1;
-
- if (!DOM.getParent(ed.selection.getEnd(), function(n) {return n == ed.getBody();}))
- o.terminate = 1;
- });
- }
-
- e = null; // Cleanup
+ // Clean up references for IE
+ targetElm = doc = body = null;
},
- // #endif
-
/**
* Focuses/activates the editor. This will set this editor as the activeEditor in the tinymce collection
* it will also place DOM focus inside the editor.
*
* @method focus
- * @param {Boolean} sf Skip DOM focus. Just set is as the active editor.
+ * @param {Boolean} skip_focus Skip DOM focus. Just set is as the active editor.
*/
- focus : function(sf) {
- var oed, t = this, selection = t.selection, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
+ focus : function(skip_focus) {
+ var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body;
+
+ if (!skip_focus) {
+ if (self.lastIERng) {
+ selection.setRng(self.lastIERng);
+ }
- if (!sf) {
// Get selected control element
ieRng = selection.getRng();
if (ieRng.item) {
controlElm = ieRng.item(0);
}
- t._refreshContentEditable();
+ self._refreshContentEditable();
- // Is not content editable
- if (!ce)
- t.getWin().focus();
+ // Focus the window iframe
+ if (!contentEditable) {
+ self.getWin().focus();
+ }
// Focus the body as well since it's contentEditable
- if (tinymce.isGecko) {
- t.getBody().focus();
+ if (tinymce.isGecko || contentEditable) {
+ body = self.getBody();
+
+ // Check for setActive since it doesn't scroll to the element
+ if (body.setActive) {
+ body.setActive();
+ } else {
+ body.focus();
+ }
+
+ if (contentEditable) {
+ selection.normalize();
+ }
}
// Restore selected control element
@@ -1915,32 +928,16 @@
ieRng.addElement(controlElm);
ieRng.select();
}
-
- // #ifdef contentEditable
-
- // Content editable mode ends here
- if (ce) {
- if (tinymce.isWebKit)
- t.getWin().focus();
- else {
- if (tinymce.isIE)
- t.getElement().setActive();
- else
- t.getElement().focus();
- }
- }
-
- // #endif
}
- if (tinymce.activeEditor != t) {
+ if (tinymce.activeEditor != self) {
if ((oed = tinymce.activeEditor) != null)
- oed.onDeactivate.dispatch(oed, t);
+ oed.onDeactivate.dispatch(oed, self);
- t.onActivate.dispatch(t, oed);
+ self.onActivate.dispatch(self, oed);
}
- tinymce._setActive(t);
+ tinymce._setActive(self);
},
/**
@@ -1988,7 +985,7 @@
if (!s)
return '';
- return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
+ return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) {
return i18n[c + '.' + b] || '{#' + b + '}';
});
},
@@ -2051,27 +1048,30 @@
* @param {Object} o Optional object to pass along for the node changed event.
*/
nodeChanged : function(o) {
- var t = this, s = t.selection, n = s.getStart() || t.getBody();
+ var self = this, selection = self.selection, node;
// Fix for bug #1896577 it seems that this can not be fired while the editor is loading
- if (t.initialized) {
+ if (self.initialized) {
o = o || {};
- n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
+
+ // Get start node
+ node = selection.getStart() || self.getBody();
+ node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state
// Get parents and add them to object
o.parents = [];
- t.dom.getParent(n, function(node) {
+ self.dom.getParent(node, function(node) {
if (node.nodeName == 'BODY')
return true;
o.parents.push(node);
});
- t.onNodeChange.dispatch(
- t,
- o ? o.controlManager || t.controlManager : t.controlManager,
- n,
- s.isCollapsed(),
+ self.onNodeChange.dispatch(
+ self,
+ o ? o.controlManager || self.controlManager : self.controlManager,
+ node,
+ selection.isCollapsed(),
o
);
}
@@ -2083,8 +1083,8 @@
* powerfull if you need more control use the ControlManagers factory methods instead.
*
* @method addButton
- * @param {String} n Button name to add.
- * @param {Object} s Settings object with title, cmd etc.
+ * @param {String} name Button name to add.
+ * @param {Object} settings Settings object with title, cmd etc.
* @example
* // Adds a custom button to the editor and when a user clicks the button it will open
* // an alert box with the selected contents as plain text.
@@ -2105,11 +1105,11 @@
* }
* });
*/
- addButton : function(n, s) {
- var t = this;
+ addButton : function(name, settings) {
+ var self = this;
- t.buttons = t.buttons || {};
- t.buttons[n] = s;
+ self.buttons = self.buttons || {};
+ self.buttons[name] = settings;
},
/**
@@ -2196,7 +1196,7 @@
addShortcut : function(pa, desc, cmd_func, sc) {
var t = this, c;
- if (!t.settings.custom_shortcuts)
+ if (t.settings.custom_shortcuts === false)
return false;
t.shortcuts = t.shortcuts || {};
@@ -2221,7 +1221,7 @@
var o = {
func : cmd_func,
scope : sc || this,
- desc : desc,
+ desc : t.translate(desc),
alt : false,
ctrl : false,
shift : false
@@ -2395,11 +1395,11 @@
* @method show
*/
show : function() {
- var t = this;
+ var self = this;
- DOM.show(t.getContainer());
- DOM.hide(t.id);
- t.load();
+ DOM.show(self.getContainer());
+ DOM.hide(self.id);
+ self.load();
},
/**
@@ -2408,16 +1408,20 @@
* @method hide
*/
hide : function() {
- var t = this, d = t.getDoc();
+ var self = this, doc = self.getDoc();
// Fixed bug where IE has a blinking cursor left from the editor
- if (isIE && d)
- d.execCommand('SelectAll');
+ if (isIE && doc)
+ doc.execCommand('SelectAll');
// We must save before we hide so Safari doesn't crash
- t.save();
- DOM.hide(t.getContainer());
- DOM.setStyle(t.id, 'display', t.orgDisplay);
+ self.save();
+
+ // defer the call to hide to prevent an IE9 crash #4921
+ setTimeout(function() {
+ DOM.hide(self.getContainer());
+ }, 1);
+ DOM.setStyle(self.id, 'display', self.orgDisplay);
},
/**
@@ -2502,12 +1506,6 @@
o = o || {};
o.save = true;
- // Add undo level will trigger onchange event
- if (!o.no_events) {
- t.undoManager.typing = false;
- t.undoManager.add();
- }
-
o.element = e;
h = o.content = t.getContent(o);
@@ -2602,7 +1600,10 @@
if (!args.no_events)
self.onSetContent.dispatch(self, args);
- self.selection.normalize();
+ // Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise
+ if (!self.settings.content_editable || document.activeElement === self.getBody()) {
+ self.selection.normalize();
+ }
return args.content;
},
@@ -2625,12 +1626,13 @@
* tinyMCE.get('content id').getContent()
*/
getContent : function(args) {
- var self = this, content;
+ var self = this, content, body = self.getBody();
// Setup args object
args = args || {};
args.format = args.format || 'html';
args.get = true;
+ args.getInner = true;
// Do preprocessing
if (!args.no_events)
@@ -2638,11 +1640,18 @@
// Get raw contents or by default the cleaned contents
if (args.format == 'raw')
- content = self.getBody().innerHTML;
+ content = body.innerHTML;
+ else if (args.format == 'text')
+ content = body.innerText || body.textContent;
else
- content = self.serializer.serialize(self.getBody(), args);
+ content = self.serializer.serialize(body, args);
- args.content = tinymce.trim(content);
+ // Trim whitespace in beginning/end of HTML
+ if (args.format != 'text') {
+ args.content = tinymce.trim(content);
+ } else {
+ args.content = content;
+ }
// Do post processing
if (!args.no_events)
@@ -2674,12 +1683,12 @@
* @return {Element} HTML DOM element for the editor container.
*/
getContainer : function() {
- var t = this;
+ var self = this;
- if (!t.container)
- t.container = DOM.get(t.editorContainer || t.id + '_parent');
+ if (!self.container)
+ self.container = DOM.get(self.editorContainer || self.id + '_parent');
- return t.container;
+ return self.container;
},
/**
@@ -2710,16 +1719,16 @@
* @return {Window} Iframe DOM window object.
*/
getWin : function() {
- var t = this, e;
+ var self = this, elm;
- if (!t.contentWindow) {
- e = DOM.get(t.id + "_ifr");
+ if (!self.contentWindow) {
+ elm = DOM.get(self.id + "_ifr");
- if (e)
- t.contentWindow = e.contentWindow;
+ if (elm)
+ self.contentWindow = elm.contentWindow;
}
- return t.contentWindow;
+ return self.contentWindow;
},
/**
@@ -2729,16 +1738,16 @@
* @return {Document} Iframe DOM document object.
*/
getDoc : function() {
- var t = this, w;
+ var self = this, win;
- if (!t.contentDocument) {
- w = t.getWin();
+ if (!self.contentDocument) {
+ win = self.getWin();
- if (w)
- t.contentDocument = w.document;
+ if (win)
+ self.contentDocument = win.document;
}
- return t.contentDocument;
+ return self.contentDocument;
},
/**
@@ -2757,77 +1766,81 @@
* manipulation functions.
*
* @method convertURL
- * @param {string} u URL to convert.
- * @param {string} n Attribute name src, href etc.
- * @param {string/HTMLElement} Tag name or HTML DOM element depending on HTML or DOM insert.
+ * @param {string} url URL to convert.
+ * @param {string} name Attribute name src, href etc.
+ * @param {string/HTMLElement} elm Tag name or HTML DOM element depending on HTML or DOM insert.
* @return {string} Converted URL string.
*/
- convertURL : function(u, n, e) {
- var t = this, s = t.settings;
+ convertURL : function(url, name, elm) {
+ var self = this, settings = self.settings;
// Use callback instead
- if (s.urlconverter_callback)
- return t.execCallback('urlconverter_callback', u, e, true, n);
+ if (settings.urlconverter_callback)
+ return self.execCallback('urlconverter_callback', url, elm, true, name);
// Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
- if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
- return u;
+ if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0)
+ return url;
// Convert to relative
- if (s.relative_urls)
- return t.documentBaseURI.toRelative(u);
+ if (settings.relative_urls)
+ return self.documentBaseURI.toRelative(url);
// Convert to absolute
- u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
+ url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);
- return u;
+ return url;
},
/**
* Adds visual aid for tables, anchors etc so they can be more easily edited inside the editor.
*
* @method addVisual
- * @param {Element} e Optional root element to loop though to find tables etc that needs the visual aid.
+ * @param {Element} elm Optional root element to loop though to find tables etc that needs the visual aid.
*/
- addVisual : function(e) {
- var t = this, s = t.settings;
+ addVisual : function(elm) {
+ var self = this, settings = self.settings, dom = self.dom, cls;
- e = e || t.getBody();
+ elm = elm || self.getBody();
- if (!is(t.hasVisual))
- t.hasVisual = s.visual;
+ if (!is(self.hasVisual))
+ self.hasVisual = settings.visual;
- each(t.dom.select('table,a', e), function(e) {
- var v;
+ each(dom.select('table,a', elm), function(elm) {
+ var value;
- switch (e.nodeName) {
+ switch (elm.nodeName) {
case 'TABLE':
- v = t.dom.getAttrib(e, 'border');
+ cls = settings.visual_table_class || 'mceItemTable';
+ value = dom.getAttrib(elm, 'border');
- if (!v || v == '0') {
- if (t.hasVisual)
- t.dom.addClass(e, s.visual_table_class);
+ if (!value || value == '0') {
+ if (self.hasVisual)
+ dom.addClass(elm, cls);
else
- t.dom.removeClass(e, s.visual_table_class);
+ dom.removeClass(elm, cls);
}
return;
case 'A':
- v = t.dom.getAttrib(e, 'name');
-
- if (v) {
- if (t.hasVisual)
- t.dom.addClass(e, 'mceItemAnchor');
- else
- t.dom.removeClass(e, 'mceItemAnchor');
+ if (!dom.getAttrib(elm, 'href', false)) {
+ value = dom.getAttrib(elm, 'name') || elm.id;
+ cls = 'mceItemAnchor';
+
+ if (value) {
+ if (self.hasVisual)
+ dom.addClass(elm, cls);
+ else
+ dom.removeClass(elm, cls);
+ }
}
return;
}
});
- t.onVisualAid.dispatch(t, e, t.hasVisual);
+ self.onVisualAid.dispatch(self, elm, self.hasVisual);
},
/**
@@ -2836,19 +1849,31 @@
* @method remove
*/
remove : function() {
- var t = this, e = t.getContainer();
+ var self = this, elm = self.getContainer();
+
+ if (!self.removed) {
+ self.removed = 1; // Cancels post remove event execution
+ self.hide();
+
+ // Don't clear the window or document if content editable
+ // is enabled since other instances might still be present
+ if (!self.settings.content_editable) {
+ Event.unbind(self.getWin());
+ Event.unbind(self.getDoc());
+ }
- t.removed = 1; // Cancels post remove event execution
- t.hide();
+ Event.unbind(self.getBody());
+ Event.clear(elm);
- t.execCallback('remove_instance_callback', t);
- t.onRemove.dispatch(t);
+ self.execCallback('remove_instance_callback', self);
+ self.onRemove.dispatch(self);
- // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
- t.onExecCommand.listeners = [];
+ // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
+ self.onExecCommand.listeners = [];
- tinymce.remove(t);
- DOM.remove(e);
+ tinymce.remove(self);
+ DOM.remove(elm);
+ }
},
/**
@@ -2866,6 +1891,13 @@
if (t.destroyed)
return;
+ // We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message
+ if (isGecko) {
+ Event.unbind(t.getDoc());
+ Event.unbind(t.getWin());
+ Event.unbind(t.getBody());
+ }
+
if (!s) {
tinymce.removeUnload(t.destroy);
tinyMCE.onBeforeUnload.remove(t._beforeUnload);
@@ -2878,18 +1910,6 @@
t.controlManager.destroy();
t.selection.destroy();
t.dom.destroy();
-
- // Remove all events
-
- // Don't clear the window or document if content editable
- // is enabled since other instances might still be present
- if (!t.settings.content_editable) {
- Event.clear(t.getWin());
- Event.clear(t.getDoc());
- }
-
- Event.clear(t.getBody());
- Event.clear(t.formElement);
}
if (t.formElement) {
@@ -2907,407 +1927,6 @@
// Internal functions
- _addEvents : function() {
- // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
- var t = this, i, s = t.settings, dom = t.dom, lo = {
- mouseup : 'onMouseUp',
- mousedown : 'onMouseDown',
- click : 'onClick',
- keyup : 'onKeyUp',
- keydown : 'onKeyDown',
- keypress : 'onKeyPress',
- submit : 'onSubmit',
- reset : 'onReset',
- contextmenu : 'onContextMenu',
- dblclick : 'onDblClick',
- paste : 'onPaste' // Doesn't work in all browsers yet
- };
-
- function eventHandler(e, o) {
- var ty = e.type;
-
- // Don't fire events when it's removed
- if (t.removed)
- return;
-
- // Generic event handler
- if (t.onEvent.dispatch(t, e, o) !== false) {
- // Specific event handler
- t[lo[e.fakeType || e.type]].dispatch(t, e, o);
- }
- };
-
- // Add DOM events
- each(lo, function(v, k) {
- switch (k) {
- case 'contextmenu':
- dom.bind(t.getDoc(), k, eventHandler);
- break;
-
- case 'paste':
- dom.bind(t.getBody(), k, function(e) {
- eventHandler(e);
- });
- break;
-
- case 'submit':
- case 'reset':
- dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
- break;
-
- default:
- dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
- }
- });
-
- dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
- t.focus(true);
- });
-
- // #ifdef contentEditable
-
- if (s.content_editable && tinymce.isOpera) {
- // Opera doesn't support focus event for contentEditable elements so we need to fake it
- function doFocus(e) {
- t.focus(true);
- };
-
- dom.bind(t.getBody(), 'click', doFocus);
- dom.bind(t.getBody(), 'keydown', doFocus);
- }
-
- // #endif
-
- // Fixes bug where a specified document_base_uri could result in broken images
- // This will also fix drag drop of images in Gecko
- if (tinymce.isGecko) {
- dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
- var v;
-
- e = e.target;
-
- if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src')))
- e.src = t.documentBaseURI.toAbsolute(v);
- });
- }
-
- // Set various midas options in Gecko
- if (isGecko) {
- function setOpts() {
- var t = this, d = t.getDoc(), s = t.settings;
-
- if (isGecko && !s.readonly) {
- t._refreshContentEditable();
-
- try {
- // Try new Gecko method
- d.execCommand("styleWithCSS", 0, false);
- } catch (ex) {
- // Use old method
- if (!t._isHidden())
- try {d.execCommand("useCSS", 0, true);} catch (ex) {}
- }
-
- if (!s.table_inline_editing)
- try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
-
- if (!s.object_resizing)
- try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
- }
- };
-
- t.onBeforeExecCommand.add(setOpts);
- t.onMouseDown.add(setOpts);
- }
-
- // Add node change handlers
- t.onMouseUp.add(t.nodeChanged);
- //t.onClick.add(t.nodeChanged);
- t.onKeyUp.add(function(ed, e) {
- var c = e.keyCode;
-
- if ((c >= 33 && c <= 36) || (c >= 37 && c <= 40) || c == 13 || c == 45 || c == 46 || c == 8 || (tinymce.isMac && (c == 91 || c == 93)) || e.ctrlKey)
- t.nodeChanged();
- });
-
-
- // Add block quote deletion handler
- t.onKeyDown.add(function(ed, e) {
- if (e.keyCode != VK.BACKSPACE)
- return;
-
- var rng = ed.selection.getRng();
- if (!rng.collapsed)
- return;
-
- var n = rng.startContainer;
- var offset = rng.startOffset;
-
- while (n && n.nodeType && n.nodeType != 1 && n.parentNode)
- n = n.parentNode;
-
- // Is the cursor at the beginning of a blockquote?
- if (n && n.parentNode && n.parentNode.tagName === 'BLOCKQUOTE' && n.parentNode.firstChild == n && offset == 0) {
- // Remove the blockquote
- ed.formatter.toggle('blockquote', null, n.parentNode);
-
- // Move the caret to the beginning of n
- rng.setStart(n, 0);
- rng.setEnd(n, 0);
- ed.selection.setRng(rng);
- ed.selection.collapse(false);
- }
- });
-
-
-
- // Add reset handler
- t.onReset.add(function() {
- t.setContent(t.startContent, {format : 'raw'});
- });
-
- // Add shortcuts
- if (s.custom_shortcuts) {
- if (s.custom_undo_redo_keyboard_shortcuts) {
- t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
- t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
- }
-
- // Add default shortcuts for gecko
- t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
- t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
- t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
-
- // BlockFormat shortcuts keys
- for (i=1; i<=6; i++)
- t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
-
- t.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
- t.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
- t.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
-
- function find(e) {
- var v = null;
-
- if (!e.altKey && !e.ctrlKey && !e.metaKey)
- return v;
-
- each(t.shortcuts, function(o) {
- if (tinymce.isMac && o.ctrl != e.metaKey)
- return;
- else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
- return;
-
- if (o.alt != e.altKey)
- return;
-
- if (o.shift != e.shiftKey)
- return;
-
- if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
- v = o;
- return false;
- }
- });
-
- return v;
- };
-
- t.onKeyUp.add(function(ed, e) {
- var o = find(e);
-
- if (o)
- return Event.cancel(e);
- });
-
- t.onKeyPress.add(function(ed, e) {
- var o = find(e);
-
- if (o)
- return Event.cancel(e);
- });
-
- t.onKeyDown.add(function(ed, e) {
- var o = find(e);
-
- if (o) {
- o.func.call(o.scope);
- return Event.cancel(e);
- }
- });
- }
-
- if (tinymce.isIE) {
- // Fix so resize will only update the width and height attributes not the styles of an image
- // It will also block mceItemNoResize items
- dom.bind(t.getDoc(), 'controlselect', function(e) {
- var re = t.resizeInfo, cb;
-
- e = e.target;
-
- // Don't do this action for non image elements
- if (e.nodeName !== 'IMG')
- return;
-
- if (re)
- dom.unbind(re.node, re.ev, re.cb);
-
- if (!dom.hasClass(e, 'mceItemNoResize')) {
- ev = 'resizeend';
- cb = dom.bind(e, ev, function(e) {
- var v;
-
- e = e.target;
-
- if (v = dom.getStyle(e, 'width')) {
- dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
- dom.setStyle(e, 'width', '');
- }
-
- if (v = dom.getStyle(e, 'height')) {
- dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
- dom.setStyle(e, 'height', '');
- }
- });
- } else {
- ev = 'resizestart';
- cb = dom.bind(e, 'resizestart', Event.cancel, Event);
- }
-
- re = t.resizeInfo = {
- node : e,
- ev : ev,
- cb : cb
- };
- });
- }
-
- if (tinymce.isOpera) {
- t.onClick.add(function(ed, e) {
- Event.prevent(e);
- });
- }
-
- // Add custom undo/redo handlers
- if (s.custom_undo_redo) {
- function addUndo() {
- t.undoManager.typing = false;
- t.undoManager.add();
- };
-
- var focusLostFunc = tinymce.isGecko ? 'blur' : 'focusout';
- dom.bind(t.getDoc(), focusLostFunc, function(e){
- if (!t.removed && t.undoManager.typing)
- addUndo();
- });
-
- // Add undo level when contents is drag/dropped within the editor
- t.dom.bind(t.dom.getRoot(), 'dragend', function(e) {
- addUndo();
- });
-
- t.onKeyUp.add(function(ed, e) {
- var keyCode = e.keyCode;
-
- if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || e.ctrlKey)
- addUndo();
- });
-
- t.onKeyDown.add(function(ed, e) {
- var keyCode = e.keyCode, sel;
-
- if (keyCode == 8) {
- sel = t.getDoc().selection;
-
- // Fix IE control + backspace browser bug
- if (sel && sel.createRange && sel.createRange().item) {
- t.undoManager.beforeChange();
- ed.dom.remove(sel.createRange().item(0));
- addUndo();
-
- return Event.cancel(e);
- }
- }
-
- // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
- if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) {
- // Add position before enter key is pressed, used by IE since it still uses the default browser behavior
- // Todo: Remove this once we normalize enter behavior on IE
- if (tinymce.isIE && keyCode == 13)
- t.undoManager.beforeChange();
-
- if (t.undoManager.typing)
- addUndo();
-
- return;
- }
-
- // If key isn't shift,ctrl,alt,capslock,metakey
- if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) {
- t.undoManager.beforeChange();
- t.undoManager.typing = true;
- t.undoManager.add();
- }
- });
-
- t.onMouseDown.add(function() {
- if (t.undoManager.typing)
- addUndo();
- });
- }
-
- // Bug fix for FireFox keeping styles from end of selection instead of start.
- if (tinymce.isGecko) {
- function getAttributeApplyFunction() {
- var template = t.dom.getAttribs(t.selection.getStart().cloneNode(false));
-
- return function() {
- var target = t.selection.getStart();
-
- if (target !== t.getBody()) {
- t.dom.setAttrib(target, "style", null);
-
- each(template, function(attr) {
- target.setAttributeNode(attr.cloneNode(true));
- });
- }
- };
- }
-
- function isSelectionAcrossElements() {
- var s = t.selection;
-
- return !s.isCollapsed() && s.getStart() != s.getEnd();
- }
-
- t.onKeyPress.add(function(ed, e) {
- var applyAttributes;
-
- if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
- applyAttributes = getAttributeApplyFunction();
- t.getDoc().execCommand('delete', false, null);
- applyAttributes();
-
- return Event.cancel(e);
- }
- });
-
- t.dom.bind(t.getDoc(), 'cut', function(e) {
- var applyAttributes;
-
- if (isSelectionAcrossElements()) {
- applyAttributes = getAttributeApplyFunction();
- t.onKeyUp.addToTop(Event.cancel, Event);
-
- setTimeout(function() {
- applyAttributes();
- t.onKeyUp.remove(Event.cancel, Event);
- }, 0);
- }
- });
- }
- },
-
_refreshContentEditable : function() {
var self = this, body, parent;
@@ -3331,7 +1950,7 @@
// Weird, wheres that cursor selection?
s = this.selection.getSel();
- return (!s || !s.rangeCount || s.rangeCount == 0);
+ return (!s || !s.rangeCount || s.rangeCount === 0);
}
});
-})(tinymce);
+})(tinymce);
\ No newline at end of file
diff --git a/design/standard/javascript/classes/EditorCommands.js b/design/standard/javascript/classes/EditorCommands.js
index ac33e662..a16e0650 100644
--- a/design/standard/javascript/classes/EditorCommands.js
+++ b/design/standard/javascript/classes/EditorCommands.js
@@ -1,16 +1,16 @@
/**
* EditorCommands.js
*
- * Copyright 2009, Moxiecode Systems AB
+ * Copyright, Moxiecode Systems AB
* Released under LGPL License.
*
- * License: http://tinymce.moxiecode.com/license
- * Contributing: http://tinymce.moxiecode.com/contributing
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
*/
(function(tinymce) {
// Added for compression purposes
- var each = tinymce.each, undefined, TRUE = true, FALSE = false;
+ var each = tinymce.each, undef, TRUE = true, FALSE = false;
/**
* This class enables you to add custom editor commands and it contains
@@ -109,10 +109,10 @@
// Private methods
function execNativeCommand(command, ui, value) {
- if (ui === undefined)
+ if (ui === undef)
ui = FALSE;
- if (value === undefined)
+ if (value === undef)
value = null;
return editor.getDoc().execCommand(command, ui, value);
@@ -123,7 +123,7 @@
};
function toggleFormat(name, value) {
- formatter.toggle(name, value ? {value : value} : undefined);
+ formatter.toggle(name, value ? {value : value} : undef);
};
function storeSelection(type) {
@@ -348,7 +348,7 @@
// Insert bookmark node and get the parent
selection.setContent(bookmarkHtml);
- parentNode = editor.selection.getNode();
+ parentNode = selection.getNode();
rootNode = editor.getBody();
// Opera will return the document node when selection is in root
@@ -422,6 +422,10 @@
editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
},
+ mceToggleFormat : function(command, ui, value) {
+ toggleFormat(value);
+ },
+
mceSetContent : function(command, ui, value) {
editor.setContent(value);
},
@@ -435,6 +439,11 @@
intentValue = parseInt(intentValue);
if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
+ // If forced_root_blocks is set to false we don't have a block to indent so lets create a div
+ if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
+ formatter.apply('div');
+ }
+
each(selection.getSelectedBlocks(), function(element) {
if (command == 'outdent') {
value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
@@ -506,10 +515,15 @@
selectAll : function() {
var root = dom.getRoot(), rng = dom.createRng();
- rng.setStart(root, 0);
- rng.setEnd(root, root.childNodes.length);
+ // Old IE does a better job with selectall than new versions
+ if (selection.getRng().setStart) {
+ rng.setStart(root, 0);
+ rng.setEnd(root, root.childNodes.length);
- editor.selection.setRng(rng);
+ selection.setRng(rng);
+ } else {
+ execNativeCommand('SelectAll');
+ }
}
});
@@ -518,9 +532,7 @@
// Override justify commands
'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
var name = 'align' + command.substring(7);
- // Use Formatter.matchNode instead of Formatter.match so that we don't match on parent node. This fixes bug where for both left
- // and right align buttons can be active. This could occur when selected nodes have align right and the parent has align left.
- var nodes = selection.isCollapsed() ? [selection.getNode()] : selection.getSelectedBlocks();
+ var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
var matches = tinymce.map(nodes, function(node) {
return !!formatter.matchNode(node, name);
});
@@ -550,7 +562,10 @@
},
'InsertUnorderedList,InsertOrderedList' : function(command) {
- return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
+ var list = dom.getParent(selection.getNode(), 'ul,ol');
+ return list &&
+ (command === 'insertunorderedlist' && list.tagName === 'UL'
+ || command === 'insertorderedlist' && list.tagName === 'OL');
}
}, 'state');
@@ -571,16 +586,14 @@
}, 'value');
// Add undo manager logic
- if (settings.custom_undo_redo) {
- addCommands({
- Undo : function() {
- editor.undoManager.undo();
- },
-
- Redo : function() {
- editor.undoManager.redo();
- }
- });
- }
+ addCommands({
+ Undo : function() {
+ editor.undoManager.undo();
+ },
+
+ Redo : function() {
+ editor.undoManager.redo();
+ }
+ });
};
})(tinymce);
diff --git a/design/standard/javascript/classes/EditorManager.js b/design/standard/javascript/classes/EditorManager.js
index f70abb18..81de7ae1 100644
--- a/design/standard/javascript/classes/EditorManager.js
+++ b/design/standard/javascript/classes/EditorManager.js
@@ -1,11 +1,11 @@
/**
* EditorManager.js
*
- * Copyright 2009, Moxiecode Systems AB
+ * Copyright, Moxiecode Systems AB
* Released under LGPL License.
*
- * License: http://tinymce.moxiecode.com/license
- * Contributing: http://tinymce.moxiecode.com/contributing
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
*/
(function(tinymce) {
@@ -18,7 +18,7 @@
DOM = tinymce.DOM, Event = tinymce.dom.Event,
ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
explode = tinymce.explode,
- Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
+ Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0;
// Setup some URLs where the editor API is located and where the document is
tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
@@ -118,6 +118,26 @@
init : function(s) {
var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
+ function createId(elm) {
+ var id = elm.id;
+
+ // Use element id, or unique name or generate a unique id
+ if (!id) {
+ id = elm.name;
+
+ if (id && !DOM.get(id)) {
+ id = elm.name;
+ } else {
+ // Generate unique name
+ id = DOM.uniqueId();
+ }
+
+ elm.setAttribute('id', id);
+ }
+
+ return id;
+ };
+
function execCallback(se, n, s) {
var f = se[n];
@@ -133,15 +153,14 @@
return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
};
- s = extend({
- theme : "simple",
- language : "en"
- }, s);
+ function hasClass(n, c) {
+ return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
+ };
t.settings = s;
// Legacy call
- Event.add(document, 'init', function() {
+ Event.bind(window, 'ready', function() {
var l, co;
execCallback(s, 'onpageload');
@@ -176,30 +195,36 @@
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))
+ each(DOM.select('textarea'), function(elm) {
+ if (s.editor_deselector && hasClass(elm, 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);
+ if (!s.editor_selector || hasClass(elm, s.editor_selector)) {
+ ed = new tinymce.Editor(createId(elm), s);
el.push(ed);
ed.render(1);
}
});
break;
+
+ default:
+ if (s.types) {
+ // Process type specific selector
+ each(s.types, function(type) {
+ each(DOM.select(type.selector), function(elm) {
+ var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type));
+ el.push(editor);
+ editor.render(1);
+ });
+ });
+ } else if (s.selector) {
+ // Process global selector
+ each(DOM.select(s.selector), function(elm) {
+ var editor = new tinymce.Editor(createId(elm), s);
+ el.push(editor);
+ editor.render(1);
+ });
+ }
}
// Call onInit when all editors are initialized
@@ -247,9 +272,12 @@
* });
*/
get : function(id) {
- if (id === undefined)
+ if (id === undef)
return this.editors;
+ if (!this.editors.hasOwnProperty(id))
+ return undef;
+
return this.editors[id];
},
@@ -339,6 +367,12 @@
execCommand : function(c, u, v) {
var t = this, ed = t.get(v), w;
+ function clr() {
+ ed.destroy();
+ w.detachEvent('onunload', clr);
+ w = w.tinyMCE = w.tinymce = null; // IE leak
+ };
+
// Manager commands
switch (c) {
case "mceFocus":
@@ -367,12 +401,6 @@
// 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);
}
diff --git a/design/standard/javascript/classes/EnterKey.js b/design/standard/javascript/classes/EnterKey.js
new file mode 100644
index 00000000..2256b70d
--- /dev/null
+++ b/design/standard/javascript/classes/EnterKey.js
@@ -0,0 +1,578 @@
+/**
+ * EnterKey.js
+ *
+ * Copyright, Moxiecode Systems AB
+ * Released under LGPL License.
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
+
+(function(tinymce) {
+ var TreeWalker = tinymce.dom.TreeWalker;
+
+ /**
+ * Contains logic for handling the enter key to split/generate block elements.
+ */
+ tinymce.EnterKey = function(editor) {
+ var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements();
+
+ function handleEnterKey(evt) {
+ var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,
+ newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
+
+ // Returns true if the block can be split into two blocks or not
+ function canSplitBlock(node) {
+ return node &&
+ dom.isBlock(node) &&
+ !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
+ !/^(fixed|absolute)/i.test(node.style.position) &&
+ dom.getContentEditable(node) !== "true";
+ };
+
+ // Renders empty block on IE
+ function renderBlockOnIE(block) {
+ var oldRng;
+
+ if (tinymce.isIE && dom.isBlock(block)) {
+ oldRng = selection.getRng();
+ block.appendChild(dom.create('span', null, '\u00a0'));
+ selection.select(block);
+ block.lastChild.outerHTML = '';
+ selection.setRng(oldRng);
+ }
+ };
+
+ // Remove the first empty inline element of the block so this:
x
becomes this:x
+ function trimInlineElementsOnLeftSideOfBlock(block) { + var node = block, firstChilds = [], i; + + // Find inner most first child ex:*
+ while (node = node.firstChild) { + if (dom.isBlock(node)) { + return; + } + + if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) { + firstChilds.push(node); + } + } + + i = firstChilds.length; + while (i--) { + node = firstChilds[i]; + if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) { + dom.remove(node); + } else { + // Remove see #5381 + if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') { + dom.remove(node); + } + } + } + }; + + // Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image + function moveToCaretPosition(root) { + var walker, node, rng, y, viewPort, lastNode = root, tempElm; + + rng = dom.createRng(); + + if (root.hasChildNodes()) { + walker = new TreeWalker(root, root); + + while (node = walker.current()) { + if (node.nodeType == 3) { + rng.setStart(node, 0); + rng.setEnd(node, 0); + break; + } + + if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { + rng.setStartBefore(node); + rng.setEndBefore(node); + break; + } + + lastNode = node; + node = walker.next(); + } + + if (!node) { + rng.setStart(lastNode, 0); + rng.setEnd(lastNode, 0); + } + } else { + if (root.nodeName == 'BR') { + if (root.nextSibling && dom.isBlock(root.nextSibling)) { + // Trick on older IE versions to render the caret before the BR between two lists + if (!documentMode || documentMode < 9) { + tempElm = dom.create('br'); + root.parentNode.insertBefore(tempElm, root); + } + + rng.setStartBefore(root); + rng.setEndBefore(root); + } else { + rng.setStartAfter(root); + rng.setEndAfter(root); + } + } else { + rng.setStart(root, 0); + rng.setEnd(root, 0); + } + } + + selection.setRng(rng); + + // Remove tempElm created for old IE:s + dom.remove(tempElm); + + viewPort = dom.getViewPort(editor.getWin()); + + // 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 = dom.getPos(root).y; + if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) { + editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks + } + }; + + // Creates a new block element by cloning the current one or creating a new one if the name is specified + // This function will also copy any text formatting from the parent block and add it to the new one + function createNewBlock(name) { + var node = container, block, clonedNode, caretNode; + + block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false); + caretNode = block; + + // Clone any parent styles + if (settings.keep_styles !== false) { + do { + if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) { + // Never clone a caret containers + if (node.id == '_mce_caret') { + continue; + } + + clonedNode = node.cloneNode(false); + dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique + + if (block.hasChildNodes()) { + clonedNode.appendChild(block.firstChild); + block.appendChild(clonedNode); + } else { + caretNode = clonedNode; + block.appendChild(clonedNode); + } + } + } while (node = node.parentNode); + } + + // BR is needed in empty blocks on non IE browsers + if (!tinymce.isIE) { + caretNode.innerHTML = 'text|
text|text2
a b
" should keep space between a and b var trimmedLength = tinymce.trim(node.nodeValue).length; - if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength == 0 && surroundedBySpans(node)) + if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) return; } else if (type == 1) { // If the only child is a bookmark then move it up @@ -1791,12 +1864,7 @@ * @return {function} Function callback handler the same as the one passed in. */ bind : function(target, name, func, scope) { - var t = this; - - if (!t.events) - t.events = new tinymce.dom.EventUtils(); - - return t.events.add(target, name, func, scope || this); + return this.events.add(target, name, func, scope || this); }, /** @@ -1809,12 +1877,39 @@ * @return {bool/Array} Bool state if true if the handler was removed or an array with states if multiple elements where passed in. */ unbind : function(target, name, func) { - var t = this; + return this.events.remove(target, name, func); + }, + + /** + * Fires the specified event name with object on target. + * + * @method fire + * @param {Node/Document/Window} target Target element or object to fire event on. + * @param {String} name Name of the event to fire. + * @param {Object} evt Event object to send. + * @return {Event} Event object. + */ + fire : function(target, name, evt) { + return this.events.fire(target, name, evt); + }, + + // Returns the content editable state of a node + getContentEditable: function(node) { + var contentEditable; + + // Check type + if (node.nodeType != 1) { + return null; + } - if (!t.events) - t.events = new tinymce.dom.EventUtils(); + // Check for fake content editable + contentEditable = node.getAttribute("data-mce-contenteditable"); + if (contentEditable && contentEditable !== "inherit") { + return contentEditable; + } - return t.events.remove(target, name, func); + // Check for real content editable + return node.contentEditable !== "inherit" ? node.contentEditable : null; }, // #ifdef debug diff --git a/design/standard/javascript/classes/dom/Element.js b/design/standard/javascript/classes/dom/Element.js index a1a87862..4b913775 100644 --- a/design/standard/javascript/classes/dom/Element.js +++ b/design/standard/javascript/classes/dom/Element.js @@ -1,11 +1,11 @@ /** * Element.js * - * Copyright 2009, Moxiecode Systems AB + * Copyright, Moxiecode Systems AB * Released under LGPL License. * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing */ (function(tinymce) { @@ -46,20 +46,20 @@ ('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; + 'isHidden,setHTML,get').split(/,/), function(k) { + t[k] = function() { + var a = [id], i; - for (i = 0; i < arguments.length; i++) - a.push(arguments[i]); + for (i = 0; i < arguments.length; i++) + a.push(arguments[i]); - a = dom[k].apply(dom, a); - t.update(k); + a = dom[k].apply(dom, a); + t.update(k); - return a; - }; - }); + return a; + }; + } + ); tinymce.extend(t, { /** diff --git a/design/standard/javascript/classes/dom/EventUtils.js b/design/standard/javascript/classes/dom/EventUtils.js index 8df718de..19fabd44 100644 --- a/design/standard/javascript/classes/dom/EventUtils.js +++ b/design/standard/javascript/classes/dom/EventUtils.js @@ -1,387 +1,616 @@ /** * EventUtils.js * - * Copyright 2009, Moxiecode Systems AB + * Copyright, Moxiecode Systems AB * Released under LGPL License. * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing */ -(function(tinymce) { - // Shorten names - var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event; +// JSLint defined globals +/*global tinymce:false, window:false */ + +tinymce.dom = {}; + +(function(namespace, expando) { + var w3cEventModel = !!document.addEventListener; /** - * 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 + * Binds a native event to a callback on the speified target. */ - tinymce.create('tinymce.dom.EventUtils', { - /** - * Constructs a new EventUtils instance. - * - * @constructor - * @method EventUtils - */ - EventUtils : function() { - this.inits = []; - this.events = []; - }, + function addEvent(target, name, callback, capture) { + if (target.addEventListener) { + target.addEventListener(name, callback, capture || false); + } else if (target.attachEvent) { + target.attachEvent('on' + name, callback); + } + } - /** - * 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. - * @example - * // Adds a click handler to the current document - * tinymce.dom.Event.add(document, 'click', function(e) { - * console.debug(e.target); - * }); - */ - add : function(o, n, f, s) { - var cb, t = this, el = t.events, r; + /** + * Unbinds a native event callback on the specified target. + */ + function removeEvent(target, name, callback, capture) { + if (target.removeEventListener) { + target.removeEventListener(name, callback, capture || false); + } else if (target.detachEvent) { + target.detachEvent('on' + name, callback); + } + } - if (n instanceof Array) { - r = []; + /** + * Normalizes a native event object or just adds the event specific methods on a custom event. + */ + function fix(original_event, data) { + var name, event = data || {}; - each(n, function(n) { - r.push(t.add(o, n, f, s)); - }); + // Dummy function that gets replaced on the delegation state functions + function returnFalse() { + return false; + } + + // Dummy function that gets replaced on the delegation state functions + function returnTrue() { + return true; + } - return r; + // Copy all properties from the original event + for (name in original_event) { + // layerX/layerY is deprecated in Chrome and produces a warning + if (name !== "layerX" && name !== "layerY") { + event[name] = original_event[name]; } + } - // Handle array - if (o && o.hasOwnProperty && o instanceof Array) { - r = []; + // Normalize target IE uses srcElement + if (!event.target) { + event.target = event.srcElement || document; + } - each(o, function(o) { - o = DOM.get(o); - r.push(t.add(o, n, f, s)); - }); + // Add preventDefault method + event.preventDefault = function() { + event.isDefaultPrevented = returnTrue; - return r; + // Execute preventDefault on the original event object + if (original_event) { + if (original_event.preventDefault) { + original_event.preventDefault(); + } else { + original_event.returnValue = false; // IE + } } + }; + + // Add stopPropagation + event.stopPropagation = function() { + event.isPropagationStopped = returnTrue; + + // Execute stopPropagation on the original event object + if (original_event) { + if (original_event.stopPropagation) { + original_event.stopPropagation(); + } else { + original_event.cancelBubble = true; // IE + } + } + }; + + // Add stopImmediatePropagation + event.stopImmediatePropagation = function() { + event.isImmediatePropagationStopped = returnTrue; + event.stopPropagation(); + }; + + // Add event delegation states + if (!event.isDefaultPrevented) { + event.isDefaultPrevented = returnFalse; + event.isPropagationStopped = returnFalse; + event.isImmediatePropagationStopped = returnFalse; + } - o = DOM.get(o); - - if (!o) - return; - - // Setup event callback - cb = function(e) { - // Is all events disabled - if (t.disabled) - return; + return event; + } - e = e || window.event; + /** + * Bind a DOMContentLoaded event across browsers and executes the callback once the page DOM is initialized. + * It will also set/check the domLoaded state of the event_utils instance so ready isn't called multiple times. + */ + function bindOnReady(win, callback, event_utils) { + var doc = win.document, event = {type: 'ready'}; + + // Gets called when the DOM is ready + function readyHandler() { + if (!event_utils.domLoaded) { + event_utils.domLoaded = true; + callback(event); + } + } - // Patch in target, preventDefault and stopPropagation in IE it's W3C valid - if (e && isIE) { - if (!e.target) - e.target = e.srcElement; + // Page already loaded then fire it directly + if (doc.readyState == "complete") { + readyHandler(); + return; + } - // Patch in preventDefault, stopPropagation methods for W3C compatibility - tinymce.extend(e, t._stoppers); + // Use W3C method + if (w3cEventModel) { + addEvent(win, 'DOMContentLoaded', readyHandler); + } else { + // Use IE method + addEvent(doc, "readystatechange", function() { + if (doc.readyState === "complete") { + removeEvent(doc, "readystatechange", arguments.callee); + readyHandler(); } + }); - if (!s) - return f(e); - - return f.call(s, e); - }; + // Wait until we can scroll, when we can the DOM is initialized + if (doc.documentElement.doScroll && win === win.top) { + (function() { + try { + // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author. + // http://javascript.nwbox.com/IEContentLoaded/ + doc.documentElement.doScroll("left"); + } catch (ex) { + setTimeout(arguments.callee, 0); + return; + } - if (n == 'unload') { - tinymce.unloads.unshift({func : cb}); - return cb; + readyHandler(); + })(); } + } - if (n == 'init') { - if (t.domLoaded) - cb(); - else - t.inits.push(cb); + // Fallback if any of the above methods should fail for some odd reason + addEvent(win, 'load', readyHandler); + } - return cb; - } + /** + * This class enables you to bind/unbind native events to elements and normalize it's behavior across browsers. + */ + function EventUtils(proxy) { + var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave; - // Store away listener reference - el.push({ - obj : o, - name : n, - func : f, - cfunc : cb, - scope : s - }); + hasMouseEnterLeave = "onmouseenter" in document.documentElement; + hasFocusIn = "onfocusin" in document.documentElement; + mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'}; + count = 1; - t._add(o, n, cb); + // State if the DOMContentLoaded was executed or not + self.domLoaded = false; + self.events = events; - return f; - }, + /** + * Executes all event handler callbacks for a specific event. + * + * @param {Event} evt Event object. + * @param {String} id Expando id value to look for. + */ + function executeHandlers(evt, id) { + var callbackList, i, l, callback; + + callbackList = events[id][evt.type]; + if (callbackList) { + for (i = 0, l = callbackList.length; i < l; i++) { + callback = callbackList[i]; + + // Check if callback exists might be removed if a unbind is called inside the callback + if (callback && callback.func.call(callback.scope, evt) === false) { + evt.preventDefault(); + } + + // Should we stop propagation to immediate listeners + if (evt.isImmediatePropagationStopped()) { + return; + } + } + } + } /** - * Removes the specified event handler by name and function from a element or collection of elements. + * Binds a callback to an event on the specified target. * - * @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. - * @example - * // Adds a click handler to the current document - * var func = tinymce.dom.Event.add(document, 'click', function(e) { - * console.debug(e.target); - * }); - * - * // Removes the click handler from the document - * tinymce.dom.Event.remove(document, 'click', func); + * @method bind + * @param {Object} target Target node/window or custom object. + * @param {String} names Name of the event to bind. + * @param {function} callback Callback function to execute when the event occurs. + * @param {Object} scope Scope to call the callback function on, defaults to target. + * @return {function} Callback function that got bound. */ - remove : function(o, n, f) { - var t = this, a = t.events, s = false, r; + self.bind = function(target, names, callback, scope) { + var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window; + + // Native event handler function patches the event and executes the callbacks for the expando + function defaultNativeHandler(evt) { + executeHandlers(fix(evt || win.event), id); + } - // Handle array - if (o && o.hasOwnProperty && o instanceof Array) { - r = []; + // Don't bind to text nodes or comments + if (!target || target.nodeType === 3 || target.nodeType === 8) { + return; + } - each(o, function(o) { - o = DOM.get(o); - r.push(t.remove(o, n, f)); - }); + // Create or get events id for the target + if (!target[expando]) { + id = count++; + target[expando] = id; + events[id] = {}; + } else { + id = target[expando]; - return r; + if (!events[id]) { + events[id] = {}; + } } - o = DOM.get(o); + // Setup the specified scope or use the target as a default + scope = scope || target; + + // Split names and bind each event, enables you to bind multiple events with one call + names = names.split(' '); + i = names.length; + while (i--) { + name = names[i]; + nativeHandler = defaultNativeHandler; + fakeName = capture = false; - 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; + // Use ready instead of DOMContentLoaded + if (name === "DOMContentLoaded") { + name = "ready"; } - }); - return s; - }, + // DOM is already ready + if ((self.domLoaded || target.readyState == 'complete') && name === "ready") { + self.domLoaded = true; + callback.call(scope, fix({type: name})); + continue; + } - /** - * Clears all events of a specific object. - * - * @method clear - * @param {Object} o DOM element or object to remove all events from. - * @example - * // Cancels all mousedown events in the active editor - * tinyMCE.activeEditor.onMouseDown.add(function(ed, e) { - * return tinymce.dom.Event.cancel(e); - * }); - */ - clear : function(o) { - var t = this, a = t.events, i, e; + // Handle mouseenter/mouseleaver + if (!hasMouseEnterLeave) { + fakeName = mouseEnterLeave[name]; + + if (fakeName) { + nativeHandler = function(evt) { + var current, related; + + current = evt.currentTarget; + related = evt.relatedTarget; + + // Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element + if (related && current.contains) { + // Use contains for performance + related = current.contains(related); + } else { + while (related && related !== current) { + related = related.parentNode; + } + } + + // Fire fake event + if (!related) { + evt = fix(evt || win.event); + evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter'; + evt.target = current; + executeHandlers(evt, id); + } + }; + } + } - if (o) { - o = DOM.get(o); + // Fake bubbeling of focusin/focusout + if (!hasFocusIn && (name === "focusin" || name === "focusout")) { + capture = true; + fakeName = name === "focusin" ? "focus" : "blur"; + nativeHandler = function(evt) { + evt = fix(evt || win.event); + evt.type = evt.type === 'focus' ? 'focusin' : 'focusout'; + executeHandlers(evt, id); + }; + } - for (i = a.length - 1; i >= 0; i--) { - e = a[i]; + // Setup callback list and bind native event + callbackList = events[id][name]; + if (!callbackList) { + events[id][name] = callbackList = [{func: callback, scope: scope}]; + callbackList.fakeName = fakeName; + callbackList.capture = capture; + + // Add the nativeHandler to the callback list so that we can later unbind it + callbackList.nativeHandler = nativeHandler; + if (!w3cEventModel) { + callbackList.proxyHandler = proxy(id); + } - if (e.obj === o) { - t._remove(e.obj, e.name, e.cfunc); - e.obj = e.cfunc = null; - a.splice(i, 1); + // Check if the target has native events support + if (name === "ready") { + bindOnReady(target, nativeHandler, self); + } else { + addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture); } + } else { + // If it already has an native handler then just push the callback + callbackList.push({func: callback, scope: scope}); } } - }, + + target = callbackList = 0; // Clean memory for IE + + return callback; + }; /** - * Cancels an event for both bubbeling and the default browser behavior. + * Unbinds the specified event by name, name and callback or all events on the target. * - * @method cancel - * @param {Event} e Event object to cancel. - * @return {Boolean} Always false. + * @method unbind + * @param {Object} target Target node/window or custom object. + * @param {String} names Optional event name to unbind. + * @param {function} callback Optional callback function to unbind. + * @return {EventUtils} Event utils instance. */ - cancel : function(e) { - if (!e) - return false; + self.unbind = function(target, names, callback) { + var id, callbackList, i, ci, name, eventMap; + + // Don't bind to text nodes or comments + if (!target || target.nodeType === 3 || target.nodeType === 8) { + return self; + } + + // Unbind event or events if the target has the expando + id = target[expando]; + if (id) { + eventMap = events[id]; + + // Specific callback + if (names) { + names = names.split(' '); + i = names.length; + while (i--) { + name = names[i]; + callbackList = eventMap[name]; + + // Unbind the event if it exists in the map + if (callbackList) { + // Remove specified callback + if (callback) { + ci = callbackList.length; + while (ci--) { + if (callbackList[ci].func === callback) { + callbackList.splice(ci, 1); + } + } + } + + // Remove all callbacks if there isn't a specified callback or there is no callbacks left + if (!callback || callbackList.length === 0) { + delete eventMap[name]; + removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture); + } + } + } + } else { + // All events for a specific element + for (name in eventMap) { + callbackList = eventMap[name]; + removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture); + } - this.stop(e); + eventMap = {}; + } - return this.prevent(e); - }, + // Check if object is empty, if it isn't then we won't remove the expando map + for (name in eventMap) { + return self; + } - /** - * 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; + // Delete event object + delete events[id]; - return false; - }, + // Remove expando from target + try { + // IE will fail here since it can't delete properties from window + delete target[expando]; + } catch (ex) { + // IE will set it to null + target[expando] = null; + } + } + + return self; + }; /** - * Prevent default browser behvaior of an event. + * Fires the specified event on the specified target. * - * @method prevent - * @param {Event} e Event to prevent default browser behvaior of an event. - * @return {Boolean} Always false. + * @method fire + * @param {Object} target Target node/window or custom object. + * @param {String} name Event name to fire. + * @param {Object} args Optional arguments to send to the observers. + * @return {EventUtils} Event utils instance. */ - prevent : function(e) { - if (e.preventDefault) - e.preventDefault(); - else - e.returnValue = false; + self.fire = function(target, name, args) { + var id, event; - return false; - }, + // Don't bind to text nodes or comments + if (!target || target.nodeType === 3 || target.nodeType === 8) { + return self; + } + + // Build event object by patching the args + event = fix(null, args); + event.type = name; + + do { + // Found an expando that means there is listeners to execute + id = target[expando]; + if (id) { + executeHandlers(event, id); + } + + // Walk up the DOM + target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow; + } while (target && !event.isPropagationStopped()); + + return self; + }; /** - * Destroys the instance. + * Removes all bound event listeners for the specified target. This will also remove any bound + * listeners to child nodes within that target. * - * @method destroy + * @method clean + * @param {Object} target Target node/window object. + * @return {EventUtils} Event utils instance. */ - destroy : function() { - var t = this; + self.clean = function(target) { + var i, children, unbind = self.unbind; + + // Don't bind to text nodes or comments + if (!target || target.nodeType === 3 || target.nodeType === 8) { + return self; + } - each(t.events, function(e, i) { - t._remove(e.obj, e.name, e.cfunc); - e.obj = e.cfunc = null; - }); + // Unbind any element on the specificed target + if (target[expando]) { + unbind(target); + } - 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 + // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children + if (!target.getElementsByTagName) { + target = target.document; + } + + // Remove events from each child element + if (target && target.getElementsByTagName) { + unbind(target); + + children = target.getElementsByTagName('*'); + i = children.length; + while (i--) { + target = children[i]; + + if (target[expando]) { + unbind(target); + } } } - }, - _pageInit : function(win) { - var t = this; + return self; + }; - // Keep it from running more than once - if (t.domLoaded) - return; + self.callNativeHandler = function(id, evt) { + if (events) { + events[id][evt.type].nativeHandler(evt); + } + }; - t.domLoaded = true; + /** + * Destroys the event object. Call this on IE to remove memory leaks. + */ + self.destory = function() { + events = {}; + }; - each(t.inits, function(c) { - c(); - }); + // Legacy function calls - t.inits = []; - }, + self.add = function(target, events, func, scope) { + // Old API supported direct ID assignment + if (typeof(target) === "string") { + target = document.getElementById(target); + } - _wait : function(win) { - var t = this, doc = win.document; + // Old API supported multiple targets + if (target && target instanceof Array) { + var i = target.length; + + while (i--) { + self.add(target[i], events, func, scope); + } - // No need since the document is already loaded - if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) { - t.domLoaded = 1; return; } - // When loaded asynchronously, the DOM Content may already be loaded - if (doc.readyState === 'complete') { - t._pageInit(win); - return; + // Old API called ready init + if (events === "init") { + events = "ready"; } - // 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 licensed under MIT by request to the author. - // http://javascript.nwbox.com/IEContentLoaded/ - doc.documentElement.doScroll("left"); - } catch (ex) { - setTimeout(arguments.callee, 0); - return; - } + return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope); + }; - t._pageInit(win); - })(); + self.remove = function(target, events, func, scope) { + if (!target) { + return self; + } + + // Old API supported direct ID assignment + if (typeof(target) === "string") { + target = document.getElementById(target); + } + + // Old API supported multiple targets + if (target instanceof Array) { + var i = target.length; + + while (i--) { + self.remove(target[i], events, func, scope); } - } else if (doc.addEventListener) { - t._add(win, 'DOMContentLoaded', function() { - t._pageInit(win); - }); + + return self; } - t._add(win, 'load', function() { - t._pageInit(win); - }); - }, + return self.unbind(target, events instanceof Array ? events.join(' ') : events, func); + }; + + self.clear = function(target) { + // Old API supported direct ID assignment + if (typeof(target) === "string") { + target = document.getElementById(target); + } - _stoppers : { - preventDefault : function() { - this.returnValue = false; - }, + return self.clean(target); + }; - stopPropagation : function() { - this.cancelBubble = true; + self.cancel = function(e) { + if (e) { + self.prevent(e); + self.stop(e); } - } - }); - /** - * Instance of EventUtils for the current document. - * - * @property Event - * @member tinymce.dom - * @type tinymce.dom.EventUtils - */ - Event = tinymce.dom.Event = new tinymce.dom.EventUtils(); + return false; + }; - // Dispatch DOM content loaded event for IE and Safari - Event._wait(window); + self.prevent = function(e) { + if (!e.preventDefault) { + e = fix(e); + } + + e.preventDefault(); + + return false; + }; - tinymce.addUnload(function() { - Event.destroy(); + self.stop = function(e) { + if (!e.stopPropagation) { + e = fix(e); + } + + e.stopPropagation(); + + return false; + }; + } + + namespace.EventUtils = EventUtils; + + namespace.Event = new EventUtils(function(id) { + return function(evt) { + tinymce.dom.Event.callNativeHandler(id, evt); + }; }); -})(tinymce); + + // Bind ready event when tinymce script is loaded + namespace.Event.bind(window, 'ready', function() {}); + + namespace = 0; +})(tinymce.dom, 'data-mce-expando'); // Namespace and expando diff --git a/design/standard/javascript/classes/dom/Range.js b/design/standard/javascript/classes/dom/Range.js index 54b84722..cd13c79a 100644 --- a/design/standard/javascript/classes/dom/Range.js +++ b/design/standard/javascript/classes/dom/Range.js @@ -1,11 +1,11 @@ /** * Range.js * - * Copyright 2009, Moxiecode Systems AB + * Copyright, Moxiecode Systems AB * Released under LGPL License. * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing */ (function(ns) { @@ -56,9 +56,14 @@ cloneContents : cloneContents, insertNode : insertNode, surroundContents : surroundContents, - cloneRange : cloneRange + cloneRange : cloneRange, + toStringIE : toStringIE }); + function createDocumentFragment() { + return doc.createDocumentFragment(); + }; + function setStart(n, o) { _setEndPoint(TRUE, n, o); }; @@ -391,10 +396,10 @@ }; function _traverseSameContainer(how) { - var frag, s, sub, n, cnt, sibling, xferNode; + var frag, s, sub, n, cnt, sibling, xferNode, start, len; if (how != DELETE) - frag = doc.createDocumentFragment(); + frag = createDocumentFragment(); // If selection is empty, just return the fragment if (t[START_OFFSET] == t[END_OFFSET]) @@ -408,7 +413,15 @@ // 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]); + n = t[START_CONTAINER]; + start = t[START_OFFSET]; + len = t[END_OFFSET] - t[START_OFFSET]; + + if (start === 0 && len >= n.nodeValue.length - 1) { + n.parentNode.removeChild(n); + } else { + n.deleteData(start, len); + } // Nothing is partially selected, so collapse to start point t.collapse(TRUE); @@ -417,7 +430,10 @@ if (how == DELETE) return; - frag.appendChild(doc.createTextNode(sub)); + if (sub.length > 0) { + frag.appendChild(doc.createTextNode(sub)); + } + return frag; } @@ -425,7 +441,7 @@ n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]); cnt = t[END_OFFSET] - t[START_OFFSET]; - while (cnt > 0) { + while (n && cnt > 0) { sibling = n.nextSibling; xferNode = _traverseFullySelected(n, how); @@ -447,7 +463,7 @@ var frag, n, endIdx, cnt, sibling, xferNode; if (how != DELETE) - frag = doc.createDocumentFragment(); + frag = createDocumentFragment(); n = _traverseRightBoundary(endAncestor, how); @@ -494,7 +510,7 @@ var frag, startIdx, n, cnt, sibling, xferNode; if (how != DELETE) - frag = doc.createDocumentFragment(); + frag = createDocumentFragment(); n = _traverseLeftBoundary(startAncestor, how); if (frag) @@ -505,7 +521,7 @@ cnt = t[END_OFFSET] - startIdx; n = startAncestor.nextSibling; - while (cnt > 0) { + while (n && cnt > 0) { sibling = n.nextSibling; xferNode = _traverseFullySelected(n, how); @@ -528,7 +544,7 @@ var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; if (how != DELETE) - frag = doc.createDocumentFragment(); + frag = createDocumentFragment(); n = _traverseLeftBoundary(startAncestor, how); if (frag) @@ -663,7 +679,7 @@ if (how == DELETE) return; - newNode = n.cloneNode(FALSE); + newNode = dom.clone(n, FALSE); newNode.nodeValue = newNodeValue; return newNode; @@ -672,16 +688,27 @@ if (how == DELETE) return; - return n.cloneNode(FALSE); + return dom.clone(n, FALSE); }; function _traverseFullySelected(n, how) { if (how != DELETE) - return how == CLONE ? n.cloneNode(TRUE) : n; + return how == CLONE ? dom.clone(n, TRUE) : n; n.parentNode.removeChild(n); }; + + function toStringIE() { + return dom.create('body', null, cloneContents()).outerText; + } + + return t; }; ns.Range = Range; + + // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype + Range.prototype.toString = function() { + return this.toStringIE(); + }; })(tinymce.dom); diff --git a/design/standard/javascript/classes/dom/RangeUtils.js b/design/standard/javascript/classes/dom/RangeUtils.js index 051c395c..64476efe 100644 --- a/design/standard/javascript/classes/dom/RangeUtils.js +++ b/design/standard/javascript/classes/dom/RangeUtils.js @@ -1,11 +1,11 @@ /** * Range.js * - * Copyright 2009, Moxiecode Systems AB + * Copyright, Moxiecode Systems AB * Released under LGPL License. * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing */ (function(tinymce) { diff --git a/design/standard/javascript/classes/dom/ScriptLoader.js b/design/standard/javascript/classes/dom/ScriptLoader.js index b79bab13..e5ab44f8 100644 --- a/design/standard/javascript/classes/dom/ScriptLoader.js +++ b/design/standard/javascript/classes/dom/ScriptLoader.js @@ -1,11 +1,11 @@ /** * ScriptLoader.js * - * Copyright 2009, Moxiecode Systems AB + * Copyright, Moxiecode Systems AB * Released under LGPL License. * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing */ (function(tinymce) { @@ -42,7 +42,7 @@ scriptLoadedCallbacks = {}, queueLoadedCallbacks = [], loading = 0, - undefined; + undef; /** * Loads a specific script directly without adding it to the load queue. @@ -109,11 +109,10 @@ } // Create new script element - elm = dom.create('script', { - id : id, - type : 'text/javascript', - src : tinymce._addVer(url) - }); + elm = document.createElement('script'); + elm.id = id; + elm.type = 'text/javascript'; + elm.src = tinymce._addVer(url); // Add onload listener for non IE browsers since IE9 // fires onload event before the script is parsed and executed @@ -181,7 +180,7 @@ var item, state = states[url]; // Add url to load queue - if (state == undefined) { + if (state == undef) { queue.push(url); states[url] = QUEUED; } @@ -227,7 +226,7 @@ callback.func.call(callback.scope); }); - scriptLoadedCallbacks[url] = undefined; + scriptLoadedCallbacks[url] = undef; }; queueLoadedCallbacks.push({ diff --git a/design/standard/javascript/classes/dom/Selection.js b/design/standard/javascript/classes/dom/Selection.js index faa91e8b..37f2591b 100644 --- a/design/standard/javascript/classes/dom/Selection.js +++ b/design/standard/javascript/classes/dom/Selection.js @@ -1,11 +1,11 @@ /** * Selection.js * - * Copyright 2009, Moxiecode Systems AB + * Copyright, Moxiecode Systems AB * Released under LGPL License. * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing */ (function(tinymce) { @@ -14,7 +14,7 @@ }; // Shorten names - var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each; + var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker; /** * This class handles text and control selection it's an crossbrowser utility class. @@ -35,12 +35,13 @@ * @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) { + Selection : function(dom, win, serializer, editor) { var t = this; t.dom = dom; t.win = win; t.serializer = serializer; + t.editor = editor; // Add events each([ @@ -49,16 +50,16 @@ * * @event onBeforeSetContent * @param {tinymce.dom.Selection} selection Selection object that fired the event. - * @param {Object} args Contains things like the contents that will be returned. + * @param {Object} args Contains things like the contents that will be returned. */ 'onBeforeSetContent', /** - * This event gets executed before contents is inserted into selection. + * This event gets executed before contents is inserted into selection. * * @event onBeforeGetContent * @param {tinymce.dom.Selection} selection Selection object that fired the event. - * @param {Object} args Contains things like the contents that will be inserted. + * @param {Object} args Contains things like the contents that will be inserted. */ 'onBeforeGetContent', @@ -67,7 +68,7 @@ * * @event onSetContent * @param {tinymce.dom.Selection} selection Selection object that fired the event. - * @param {Object} args Contains things like the contents that will be inserted. + * @param {Object} args Contains things like the contents that will be inserted. */ 'onSetContent', @@ -76,7 +77,7 @@ * * @event onGetContent * @param {tinymce.dom.Selection} selection Selection object that fired the event. - * @param {Object} args Contains things like the contents that will be returned. + * @param {Object} args Contains things like the contents that will be returned. */ 'onGetContent' ], function(e) { @@ -115,7 +116,7 @@ * @example * // Alerts the currently selected contents * alert(tinyMCE.activeEditor.selection.getContent()); - * + * * // Alerts the currently selected contents as plain text * alert(tinyMCE.activeEditor.selection.getContent({format : 'text'})); */ @@ -197,7 +198,7 @@ } else { rng.deleteContents(); - if (doc.body.childNodes.length == 0) { + if (doc.body.childNodes.length === 0) { doc.body.innerHTML = content; } else { // createContextualFragment doesn't exists in IE 9 DOMRanges @@ -261,7 +262,7 @@ * @return {Element} Start element of selection range. */ getStart : function() { - var rng = this.getRng(), startElement, parentElement, checkRng, node; + var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node; if (rng.duplicate || rng.item) { // Control selection, return first item @@ -272,6 +273,9 @@ checkRng = rng.duplicate(); checkRng.collapse(1); startElement = checkRng.parentElement(); + if (startElement.ownerDocument !== self.dom.doc) { + startElement = self.dom.getRoot(); + } // 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 @@ -305,31 +309,34 @@ * @return {Element} End element of selection range. */ getEnd : function() { - var t = this, r = t.getRng(), e, eo; + var self = this, rng = self.getRng(), endElement, endOffset; - if (r.duplicate || r.item) { - if (r.item) - return r.item(0); + if (rng.duplicate || rng.item) { + if (rng.item) + return rng.item(0); - r = r.duplicate(); - r.collapse(0); - e = r.parentElement(); + rng = rng.duplicate(); + rng.collapse(0); + endElement = rng.parentElement(); + if (endElement.ownerDocument !== self.dom.doc) { + endElement = self.dom.getRoot(); + } - if (e && e.nodeName == 'BODY') - return e.lastChild || e; + if (endElement && endElement.nodeName == 'BODY') + return endElement.lastChild || endElement; - return e; + return endElement; } else { - e = r.endContainer; - eo = r.endOffset; + endElement = rng.endContainer; + endOffset = rng.endOffset; - if (e.nodeType == 1 && e.hasChildNodes()) - e = e.childNodes[eo > 0 ? eo - 1 : eo]; + if (endElement.nodeType == 1 && endElement.hasChildNodes()) + endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset]; - if (e && e.nodeType == 3) - return e.parentNode; + if (endElement && endElement.nodeType == 3) + return endElement.parentNode; - return e; + return endElement; } }, @@ -344,9 +351,9 @@ * @example * // Stores a bookmark of the current selection * var bm = tinyMCE.activeEditor.selection.getBookmark(); - * + * * tinyMCE.activeEditor.setContent(tinyMCE.activeEditor.getContent() + 'Some new content'); - * + * * // Restore the selection bookmark * tinyMCE.activeEditor.selection.moveToBookmark(bm); */ @@ -364,46 +371,69 @@ return index; }; - if (type == 2) { - function getLocation() { - var rng = t.getRng(true), root = dom.getRoot(), bookmark = {}; + function normalizeTableCellSelection(rng) { + function moveEndPoint(start) { + var container, offset, childNodes, prefix = start ? 'start' : 'end'; - function getPoint(rng, start) { - var container = rng[start ? 'startContainer' : 'endContainer'], - offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; + container = rng[prefix + 'Container']; + offset = rng[prefix + 'Offset']; - if (container.nodeType == 3) { - if (normalized) { - for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) - offset += node.nodeValue.length; - } + if (container.nodeType == 1 && container.nodeName == "TR") { + childNodes = container.childNodes; + container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)]; + if (container) { + offset = start ? 0 : container.childNodes.length; + rng['set' + (start ? 'Start' : 'End')](container, offset); + } + } + }; - point.push(offset); - } else { - childNodes = container.childNodes; + moveEndPoint(true); + moveEndPoint(); - if (offset >= childNodes.length && childNodes.length) { - after = 1; - offset = Math.max(0, childNodes.length - 1); - } + return rng; + }; + + function getLocation() { + var rng = t.getRng(true), root = dom.getRoot(), bookmark = {}; - point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); + 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; } - for (; container && container != root; container = container.parentNode) - point.push(t.dom.nodeIndex(container, normalized)); + point.push(offset); + } else { + childNodes = container.childNodes; - return point; - }; + if (offset >= childNodes.length && childNodes.length) { + after = 1; + offset = Math.max(0, childNodes.length - 1); + } - bookmark.start = getPoint(rng, true); + point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); + } - if (!t.isCollapsed()) - bookmark.end = getPoint(rng); + for (; container && container != root; container = container.parentNode) + point.push(t.dom.nodeIndex(container, normalized)); - return bookmark; + return point; }; + bookmark.start = getPoint(rng, true); + + if (!t.isCollapsed()) + bookmark.end = getPoint(rng); + + return bookmark; + }; + + if (type == 2) { if (t.tridentSel) return t.tridentSel.getBookmark(type); @@ -436,7 +466,7 @@ // Detect the empty space after block elements in IE and move the end back one character ] becomes]
rng.moveToElementText(rng2.parentElement()); - if (rng.compareEndPoints('StartToEnd', rng2) == 0) + if (rng.compareEndPoints('StartToEnd', rng2) === 0) rng2.move('character', -1); rng2.pasteHTML('' + chr + ''); @@ -459,7 +489,7 @@ return {name : name, index : findIndex(name, element)}; // W3C method - rng2 = rng.cloneRange(); + rng2 = normalizeTableCellSelection(rng.cloneRange()); // Insert end marker if (!collapsed) { @@ -467,6 +497,7 @@ rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr)); } + rng = normalizeTableCellSelection(rng); rng.collapse(true); rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr)); } @@ -485,131 +516,131 @@ * @example * // Stores a bookmark of the current selection * var bm = tinyMCE.activeEditor.selection.getBookmark(); - * + * * tinyMCE.activeEditor.setContent(tinyMCE.activeEditor.getContent() + 'Some new content'); - * + * * // Restore the selection bookmark * tinyMCE.activeEditor.selection.moveToBookmark(bm); */ moveToBookmark : function(bookmark) { var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset; - if (bookmark) { - if (bookmark.start) { - rng = dom.createRng(); - root = dom.getRoot(); + function setEndPoint(start) { + var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; - function setEndPoint(start) { - var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; + if (point) { + offset = point[0]; - if (point) { - offset = point[0]; + // Find container node + for (node = root, i = point.length - 1; i >= 1; i--) { + children = node.childNodes; - // Find container node - for (node = root, i = point.length - 1; i >= 1; i--) { - children = node.childNodes; + if (point[i] > children.length - 1) + return; - if (point[i] > children.length - 1) - return; + node = children[point[i]]; + } - node = children[point[i]]; - } + // Move text offset to best suitable location + if (node.nodeType === 3) + offset = Math.min(point[0], node.nodeValue.length); + + // Move element offset to best suitable location + if (node.nodeType === 1) + offset = Math.min(point[0], node.childNodes.length); - // Move text offset to best suitable location - if (node.nodeType === 3) - offset = Math.min(point[0], node.nodeValue.length); + // Set offset within container node + if (start) + rng.setStart(node, offset); + else + rng.setEnd(node, offset); + } - // Move element offset to best suitable location - if (node.nodeType === 1) - offset = Math.min(point[0], node.childNodes.length); + return true; + }; - // Set offset within container node - if (start) - rng.setStart(node, offset); - else - rng.setEnd(node, offset); - } + function restoreEndPoint(suffix) { + var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; - return true; - }; + if (marker) { + node = marker.parentNode; - if (t.tridentSel) - return t.tridentSel.moveToBookmark(bookmark); + if (suffix == 'start') { + if (!keep) { + idx = dom.nodeIndex(marker); + } else { + node = marker.firstChild; + idx = 1; + } - if (setEndPoint(true) && setEndPoint()) { - t.setRng(rng); + startContainer = endContainer = node; + startOffset = endOffset = idx; + } else { + if (!keep) { + idx = dom.nodeIndex(marker); + } else { + node = marker.firstChild; + idx = 1; + } + + endContainer = node; + endOffset = idx; } - } 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 (!keep) { + prev = marker.previousSibling; + next = marker.nextSibling; - if (suffix == 'start') { - if (!keep) { - idx = dom.nodeIndex(marker); - } else { - node = marker.firstChild; - idx = 1; - } + // 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 unless it's Opera since it some how removes the node + // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact + if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) { + idx = prev.nodeValue.length; + prev.appendData(next.nodeValue); + dom.remove(next); - startContainer = endContainer = node; + if (suffix == 'start') { + startContainer = endContainer = prev; startOffset = endOffset = idx; } else { - if (!keep) { - idx = dom.nodeIndex(marker); - } else { - node = marker.firstChild; - idx = 1; - } - - endContainer = node; + endContainer = prev; 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 unless it's Opera since it some how removes the node - // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact - if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) { - 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 + if (dom.isBlock(node) && !node.innerHTML && !isIE) + node.innerHTML = '|
would become this:|
+ sibling = startContainer.previousSibling; + if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) { + sibling.innerHTML = '\uFEFF'; + } else { + sibling = null; + } + + startContainer.innerHTML = '\uFEFF\uFEFF'; + ieRng.moveToElementText(startContainer.lastChild); + ieRng.select(); + dom.doc.selection.clear(); + startContainer.innerHTML = ''; + + if (sibling) { + sibling.innerHTML = ''; + } + return; + } else { + startOffset = dom.nodeIndex(startContainer); + startContainer = startContainer.parentNode; + } + } + if (startOffset == endOffset - 1) { try { + ctrlElm = startContainer.childNodes[startOffset]; ctrlRng = body.createControlRange(); - ctrlRng.addElement(startContainer.childNodes[startOffset]); + ctrlRng.addElement(ctrlElm); ctrlRng.select(); - return; + + // Check if the range produced is on the correct element and is a control range + // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398 + nativeRng = selection.getRng(); + if (nativeRng.item && ctrlElm === nativeRng.item(0)) { + return; + } } catch (ex) { // Ignore } diff --git a/design/standard/javascript/classes/html/DomParser.js b/design/standard/javascript/classes/html/DomParser.js index b55ba176..ca7740ac 100644 --- a/design/standard/javascript/classes/html/DomParser.js +++ b/design/standard/javascript/classes/html/DomParser.js @@ -1,11 +1,11 @@ /** * DomParser.js * - * Copyright 2010, Moxiecode Systems AB + * Copyright, Moxiecode Systems AB * Released under LGPL License. * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing */ (function(tinymce) { @@ -41,18 +41,41 @@ function fixInvalidChildren(nodes) { var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i, - childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode; + childClone, nonEmptyElements, nonSplitableElements, textBlockElements, sibling, nextNode; nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table'); nonEmptyElements = schema.getNonEmptyElements(); + textBlockElements = schema.getTextBlockElements(); for (ni = 0; ni < nodes.length; ni++) { node = nodes[ni]; - // Already removed - if (!node.parent) + // Already removed or fixed + if (!node.parent || node.fixed) continue; + // If the invalid element is a text block and the text block is within a parent LI element + // Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office + if (textBlockElements[node.name] && node.parent.name == 'li') { + // Move sibling text blocks after LI element + sibling = node.next; + while (sibling) { + if (textBlockElements[sibling.name]) { + sibling.name = 'li'; + sibling.fixed = true; + node.parent.insert(sibling, node.parent); + } else { + break; + } + + sibling = sibling.next; + } + + // Unwrap current text block + node.unwrap(node); + continue; + } + // Get list of all parent nodes until we find a valid parent to stick the child into parents = [node]; for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent) @@ -231,8 +254,8 @@ */ self.parse = function(html, args) { var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate, - blockElements, startWhiteSpaceRegExp, invalidChildren = [], - endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName; + blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement, + endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName; args = args || {}; matchedNodes = {}; @@ -247,6 +270,7 @@ startWhiteSpaceRegExp = /^[ \t\r\n]+/; endWhiteSpaceRegExp = /[ \t\r\n]+$/; allWhiteSpaceRegExp = /[ \t\r\n]+/g; + isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/; function addRootBlocks() { var node = rootNode.firstChild, next, rootBlockNode; @@ -302,9 +326,23 @@ } }; + function cloneAndExcludeBlocks(input) { + var name, output = {}; + + for (name in input) { + if (name !== 'li' && name != 'p') { + output[name] = input[name]; + } + } + + return output; + }; + parser = new tinymce.html.SaxParser({ validate : validate, - fix_self_closing : !validate, // Let the DOM parser handlein