4 * Copyright 2009, Moxiecode Systems AB
5 * Released under LGPL License.
7 * License: http://tinymce.moxiecode.com/license
8 * Contributing: http://tinymce.moxiecode.com/contributing
12 // Shorten these names
13 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
14 Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
15 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
16 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
17 inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
20 * This class contains the core logic for a TinyMCE editor.
22 * @class tinymce.Editor
24 * // Add a class to all paragraphs in the editor.
25 * tinyMCE.activeEditor.dom.addClass(tinyMCE.activeEditor.dom.select('p'), 'someclass');
27 * // Gets the current editors selection as text
28 * tinyMCE.activeEditor.selection.getContent({format : 'text'});
30 * // Creates a new editor instance
31 * var ed = new tinymce.Editor('textareaid', {
35 * // Select each item the user clicks on
36 * ed.onClick.add(function(ed, e) {
37 * ed.selection.select(e.target);
42 tinymce.create('tinymce.Editor', {
44 * Constructs a editor instance by id.
48 * @param {String} id Unique id for the editor.
49 * @param {Object} s Optional settings string for the editor.
52 Editor : function(id, s) {
56 * Editor instance id, normally the same as the div/textarea that was replaced.
61 t.id = t.editorId = id;
64 t.queryStateCommands = {};
65 t.queryValueCommands = {};
68 * State to force the editor to return false on a isDirty call.
70 * @property isNotDirty
73 * function ajaxSave() {
74 * var ed = tinyMCE.get('elm1');
76 * // Save contents using some XHR call
77 * alert(ed.getContent());
79 * ed.isNotDirty = 1; // Force not dirty state
85 * Name/Value object containting plugin instances.
90 * // Execute a method inside a plugin directly
91 * tinyMCE.activeEditor.plugins.someplugin.someMethod();
95 // Add events to the editor
98 * Fires before the initialization of the editor.
101 * @param {tinymce.Editor} sender Editor instance.
104 * // Adds an observer to the onPreInit event using tinyMCE.init
107 * setup : function(ed) {
108 * ed.onPreInit.add(function(ed) {
109 * console.debug('PreInit: ' + ed.id);
117 * Fires before the initialization of the editor.
119 * @event onBeforeRenderUI
120 * @param {tinymce.Editor} sender Editor instance.
122 * // Adds an observer to the onBeforeRenderUI event using tinyMCE.init
125 * setup : function(ed) {
126 * ed.onBeforeRenderUI.add(function(ed, cm) {
127 * console.debug('Before render: ' + ed.id);
135 * Fires after the rendering has completed.
137 * @event onPostRender
138 * @param {tinymce.Editor} sender Editor instance.
140 * // Adds an observer to the onPostRender event using tinyMCE.init
143 * setup : function(ed) {
144 * ed.onPostRender.add(function(ed, cm) {
145 * console.debug('After render: ' + ed.id);
153 * Fires after the initialization of the editor is done.
156 * @param {tinymce.Editor} sender Editor instance.
159 * // Adds an observer to the onInit event using tinyMCE.init
162 * setup : function(ed) {
163 * ed.onInit.add(function(ed) {
164 * console.debug('Editor is done: ' + ed.id);
172 * Fires when the editor instance is removed from page.
175 * @param {tinymce.Editor} sender Editor instance.
177 * // Adds an observer to the onRemove event using tinyMCE.init
180 * setup : function(ed) {
181 * ed.onRemove.add(function(ed) {
182 * console.debug('Editor was removed: ' + ed.id);
190 * Fires when the editor is activated.
193 * @param {tinymce.Editor} sender Editor instance.
195 * // Adds an observer to the onActivate event using tinyMCE.init
198 * setup : function(ed) {
199 * ed.onActivate.add(function(ed) {
200 * console.debug('Editor was activated: ' + ed.id);
208 * Fires when the editor is deactivated.
210 * @event onDeactivate
211 * @param {tinymce.Editor} sender Editor instance.
213 * // Adds an observer to the onDeactivate event using tinyMCE.init
216 * setup : function(ed) {
217 * ed.onDeactivate.add(function(ed) {
218 * console.debug('Editor was deactivated: ' + ed.id);
226 * Fires when something in the body of the editor is clicked.
229 * @param {tinymce.Editor} sender Editor instance.
230 * @param {Event} evt W3C DOM Event instance.
232 * // Adds an observer to the onClick event using tinyMCE.init
235 * setup : function(ed) {
236 * ed.onClick.add(function(ed, e) {
237 * console.debug('Editor was clicked: ' + e.target.nodeName);
245 * Fires when a registered event is intercepted.
248 * @param {tinymce.Editor} sender Editor instance.
249 * @param {Event} evt W3C DOM Event instance.
251 * // Adds an observer to the onEvent event using tinyMCE.init
254 * setup : function(ed) {
255 * ed.onEvent.add(function(ed, e) {
256 * console.debug('Editor event occured: ' + e.target.nodeName);
264 * Fires when a mouseup event is intercepted inside the editor.
267 * @param {tinymce.Editor} sender Editor instance.
268 * @param {Event} evt W3C DOM Event instance.
270 * // Adds an observer to the onMouseUp event using tinyMCE.init
273 * setup : function(ed) {
274 * ed.onMouseUp.add(function(ed, e) {
275 * console.debug('Mouse up event: ' + e.target.nodeName);
283 * Fires when a mousedown event is intercepted inside the editor.
286 * @param {tinymce.Editor} sender Editor instance.
287 * @param {Event} evt W3C DOM Event instance.
289 * // Adds an observer to the onMouseDown event using tinyMCE.init
292 * setup : function(ed) {
293 * ed.onMouseDown.add(function(ed, e) {
294 * console.debug('Mouse down event: ' + e.target.nodeName);
302 * Fires when a dblclick event is intercepted inside the editor.
305 * @param {tinymce.Editor} sender Editor instance.
306 * @param {Event} evt W3C DOM Event instance.
308 * // Adds an observer to the onDblClick event using tinyMCE.init
311 * setup : function(ed) {
312 * ed.onDblClick.add(function(ed, e) {
313 * console.debug('Double click event: ' + e.target.nodeName);
321 * Fires when a keydown event is intercepted inside the editor.
324 * @param {tinymce.Editor} sender Editor instance.
325 * @param {Event} evt W3C DOM Event instance.
327 * // Adds an observer to the onKeyDown event using tinyMCE.init
330 * setup : function(ed) {
331 * ed.onKeyDown.add(function(ed, e) {
332 * console.debug('Key down event: ' + e.keyCode);
340 * Fires when a keydown event is intercepted inside the editor.
343 * @param {tinymce.Editor} sender Editor instance.
344 * @param {Event} evt W3C DOM Event instance.
346 * // Adds an observer to the onKeyUp event using tinyMCE.init
349 * setup : function(ed) {
350 * ed.onKeyUp.add(function(ed, e) {
351 * console.debug('Key up event: ' + e.keyCode);
359 * Fires when a keypress event is intercepted inside the editor.
362 * @param {tinymce.Editor} sender Editor instance.
363 * @param {Event} evt W3C DOM Event instance.
365 * // Adds an observer to the onKeyPress event using tinyMCE.init
368 * setup : function(ed) {
369 * ed.onKeyPress.add(function(ed, e) {
370 * console.debug('Key press event: ' + e.keyCode);
378 * Fires when a contextmenu event is intercepted inside the editor.
380 * @event onContextMenu
381 * @param {tinymce.Editor} sender Editor instance.
382 * @param {Event} evt W3C DOM Event instance.
384 * // Adds an observer to the onContextMenu event using tinyMCE.init
387 * setup : function(ed) {
388 * ed.onContextMenu.add(function(ed, e) {
389 * console.debug('Context menu event:' + e.target);
397 * Fires when a form submit event is intercepted.
400 * @param {tinymce.Editor} sender Editor instance.
401 * @param {Event} evt W3C DOM Event instance.
403 * // Adds an observer to the onSubmit event using tinyMCE.init
406 * setup : function(ed) {
407 * ed.onSubmit.add(function(ed, e) {
408 * console.debug('Form submit:' + e.target);
416 * Fires when a form reset event is intercepted.
419 * @param {tinymce.Editor} sender Editor instance.
420 * @param {Event} evt W3C DOM Event instance.
422 * // Adds an observer to the onReset event using tinyMCE.init
425 * setup : function(ed) {
426 * ed.onReset.add(function(ed, e) {
427 * console.debug('Form reset:' + e.target);
435 * Fires when a paste event is intercepted inside the editor.
438 * @param {tinymce.Editor} sender Editor instance.
439 * @param {Event} evt W3C DOM Event instance.
441 * // Adds an observer to the onPaste event using tinyMCE.init
444 * setup : function(ed) {
445 * ed.onPaste.add(function(ed, e) {
446 * console.debug('Pasted plain text');
454 * Fires when the Serializer does a preProcess on the contents.
456 * @event onPreProcess
457 * @param {tinymce.Editor} sender Editor instance.
458 * @param {Object} obj PreProcess object.
459 * @option {Node} node DOM node for the item being serialized.
460 * @option {String} format The specified output format normally "html".
461 * @option {Boolean} get Is true if the process is on a getContent operation.
462 * @option {Boolean} set Is true if the process is on a setContent operation.
463 * @option {Boolean} cleanup Is true if the process is on a cleanup operation.
465 * // Adds an observer to the onPreProcess event using tinyMCE.init
468 * setup : function(ed) {
469 * ed.onPreProcess.add(function(ed, o) {
470 * // Add a class to each paragraph in the editor
471 * ed.dom.addClass(ed.dom.select('p', o.node), 'myclass');
479 * Fires when the Serializer does a postProcess on the contents.
481 * @event onPostProcess
482 * @param {tinymce.Editor} sender Editor instance.
483 * @param {Object} obj PreProcess object.
485 * // Adds an observer to the onPostProcess event using tinyMCE.init
488 * setup : function(ed) {
489 * ed.onPostProcess.add(function(ed, o) {
490 * // Remove all paragraphs and replace with BR
491 * o.content = o.content.replace(/<p[^>]+>|<p>/g, '');
492 * o.content = o.content.replace(/<\/p>/g, '<br />');
500 * Fires before new contents is added to the editor. Using for example setContent.
502 * @event onBeforeSetContent
503 * @param {tinymce.Editor} sender Editor instance.
505 * // Adds an observer to the onBeforeSetContent event using tinyMCE.init
508 * setup : function(ed) {
509 * ed.onBeforeSetContent.add(function(ed, o) {
510 * // Replaces all a characters with b characters
511 * o.content = o.content.replace(/a/g, 'b');
516 'onBeforeSetContent',
519 * Fires before contents is extracted from the editor using for example getContent.
521 * @event onBeforeGetContent
522 * @param {tinymce.Editor} sender Editor instance.
523 * @param {Event} evt W3C DOM Event instance.
525 * // Adds an observer to the onBeforeGetContent event using tinyMCE.init
528 * setup : function(ed) {
529 * ed.onBeforeGetContent.add(function(ed, o) {
530 * console.debug('Before get content.');
535 'onBeforeGetContent',
538 * Fires after the contents has been added to the editor using for example onSetContent.
540 * @event onSetContent
541 * @param {tinymce.Editor} sender Editor instance.
543 * // Adds an observer to the onSetContent event using tinyMCE.init
546 * setup : function(ed) {
547 * ed.onSetContent.add(function(ed, o) {
548 * // Replaces all a characters with b characters
549 * o.content = o.content.replace(/a/g, 'b');
557 * Fires after the contents has been extracted from the editor using for example getContent.
559 * @event onGetContent
560 * @param {tinymce.Editor} sender Editor instance.
562 * // Adds an observer to the onGetContent event using tinyMCE.init
565 * setup : function(ed) {
566 * ed.onGetContent.add(function(ed, o) {
567 * // Replace all a characters with b
568 * o.content = o.content.replace(/a/g, 'b');
576 * Fires when the editor gets loaded with contents for example when the load method is executed.
578 * @event onLoadContent
579 * @param {tinymce.Editor} sender Editor instance.
581 * // Adds an observer to the onLoadContent event using tinyMCE.init
584 * setup : function(ed) {
585 * ed.onLoadContent.add(function(ed, o) {
586 * // Output the element name
587 * console.debug(o.element.nodeName);
595 * Fires when the editor contents gets saved for example when the save method is executed.
597 * @event onSaveContent
598 * @param {tinymce.Editor} sender Editor instance.
600 * // Adds an observer to the onSaveContent event using tinyMCE.init
603 * setup : function(ed) {
604 * ed.onSaveContent.add(function(ed, o) {
605 * // Output the element name
606 * console.debug(o.element.nodeName);
614 * Fires when the user changes node location using the mouse or keyboard.
616 * @event onNodeChange
617 * @param {tinymce.Editor} sender Editor instance.
619 * // Adds an observer to the onNodeChange event using tinyMCE.init
622 * setup : function(ed) {
623 * ed.onNodeChange.add(function(ed, cm, e) {
624 * // Activates the link button when the caret is placed in a anchor element
625 * if (e.nodeName == 'A')
626 * cm.setActive('link', true);
634 * Fires when a new undo level is added to the editor.
637 * @param {tinymce.Editor} sender Editor instance.
639 * // Adds an observer to the onChange event using tinyMCE.init
642 * setup : function(ed) {
643 * ed.onChange.add(function(ed, l) {
644 * console.debug('Editor contents was modified. Contents: ' + l.content);
652 * Fires before a command gets executed for example "Bold".
654 * @event onBeforeExecCommand
655 * @param {tinymce.Editor} sender Editor instance.
657 * // Adds an observer to the onBeforeExecCommand event using tinyMCE.init
660 * setup : function(ed) {
661 * ed.onBeforeExecCommand.add(function(ed, cmd, ui, val) {
662 * console.debug('Command is to be executed: ' + cmd);
667 'onBeforeExecCommand',
670 * Fires after a command is executed for example "Bold".
672 * @event onExecCommand
673 * @param {tinymce.Editor} sender Editor instance.
675 * // Adds an observer to the onExecCommand event using tinyMCE.init
678 * setup : function(ed) {
679 * ed.onExecCommand.add(function(ed, cmd, ui, val) {
680 * console.debug('Command was executed: ' + cmd);
688 * Fires when the contents is undo:ed.
691 * @param {tinymce.Editor} sender Editor instance.
692 * @param {Object} level Undo level object.
694 * // Adds an observer to the onUndo event using tinyMCE.init
697 * setup : function(ed) {
698 * ed.onUndo.add(function(ed, level) {
699 * console.debug('Undo was performed: ' + level.content);
707 * Fires when the contents is redo:ed.
710 * @param {tinymce.Editor} sender Editor instance.
711 * @param {Object} level Undo level object.
713 * // Adds an observer to the onRedo event using tinyMCE.init
716 * setup : function(ed) {
717 * ed.onRedo.add(function(ed, level) {
718 * console.debug('Redo was performed: ' +level.content);
726 * Fires when visual aids is enabled/disabled.
729 * @param {tinymce.Editor} sender Editor instance.
731 * // Adds an observer to the onVisualAid event using tinyMCE.init
734 * setup : function(ed) {
735 * ed.onVisualAid.add(function(ed, e, s) {
736 * console.debug('onVisualAid event: ' + ed.id + ", State: " + s);
744 * Fires when the progress throbber is shown above the editor.
746 * @event onSetProgressState
747 * @param {tinymce.Editor} sender Editor instance.
749 * // Adds an observer to the onSetProgressState event using tinyMCE.init
752 * setup : function(ed) {
753 * ed.onSetProgressState.add(function(ed, b) {
755 * console.debug('SHOW!');
757 * console.debug('HIDE!');
764 t[e] = new Dispatcher(t);
768 * Name/value collection with editor settings.
773 * // Get the value of the theme setting
774 * tinyMCE.activeEditor.windowManager.alert("You are using the " + tinyMCE.activeEditor.settings.theme + " theme");
776 t.settings = s = extend({
779 docs_language : 'en',
786 document_base_url : tinymce.documentBaseURL,
787 add_form_submit_trigger : 1,
789 add_unload_trigger : 1,
792 remove_script_host : 1,
793 table_inline_editing : 0,
796 accessibility_focus : 1,
797 custom_shortcuts : 1,
798 custom_undo_redo_keyboard_shortcuts : 1,
799 custom_undo_redo_restore_selection : 1,
800 custom_undo_redo : 1,
801 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
802 visual_table_class : 'mceItemTable',
804 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
805 apply_source_formatting : 1,
806 directionality : 'ltr',
807 forced_root_block : 'p',
809 padd_empty_editor : 1,
812 force_p_newlines : 1,
813 indentation : '30px',
815 fix_table_elements : 1,
817 convert_fonts_to_spans : true,
819 indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
820 indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
822 entity_encoding : 'named',
823 url_converter : t.convertURL,
824 url_converter_scope : t,
829 * URI object to document configured for the TinyMCE instance.
831 * @property documentBaseURI
832 * @type tinymce.util.URI
834 * // Get relative URL from the location of document_base_url
835 * tinyMCE.activeEditor.documentBaseURI.toRelative('/somedir/somefile.htm');
837 * // Get absolute URL from the location of document_base_url
838 * tinyMCE.activeEditor.documentBaseURI.toAbsolute('somefile.htm');
840 t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
841 base_uri : tinyMCE.baseURI
845 * URI object to current document that holds the TinyMCE editor instance.
848 * @type tinymce.util.URI
850 * // Get relative URL from the location of the API
851 * tinyMCE.activeEditor.baseURI.toRelative('/somedir/somefile.htm');
853 * // Get absolute URL from the location of the API
854 * tinyMCE.activeEditor.baseURI.toAbsolute('somefile.htm');
856 t.baseURI = tinymce.baseURI;
859 * Array with CSS files to load into the iframe.
861 * @property contentCSS
867 t.execCallback('setup', t);
871 * Renderes the editor/adds it to the page.
875 render : function(nst) {
876 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
878 // Page is not loaded yet, wait for it
879 if (!Event.domLoaded) {
880 Event.add(document, 'init', function() {
886 tinyMCE.settings = s;
888 // Element not found, then skip initialization
892 // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff
893 // here since the browser says it has contentEditable support but there is no visible
894 // caret We will remove this check ones Apple implements full contentEditable support
895 if (tinymce.isIDevice && !tinymce.isIOS5)
898 // Add hidden input for non input elements inside form elements
899 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
900 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
903 * Window manager reference, use this to open new windows and dialogs.
905 * @property windowManager
906 * @type tinymce.WindowManager
908 * // Shows an alert message
909 * tinyMCE.activeEditor.windowManager.alert('Hello world!');
911 * // Opens a new dialog with the file.htm file and the size 320x240
912 * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog.
913 * tinyMCE.activeEditor.windowManager.open({
921 if (tinymce.WindowManager)
922 t.windowManager = new tinymce.WindowManager(t);
924 if (s.encoding == 'xml') {
925 t.onGetContent.add(function(ed, o) {
927 o.content = DOM.encode(o.content);
931 if (s.add_form_submit_trigger) {
932 t.onSubmit.addToTop(function() {
940 if (s.add_unload_trigger) {
941 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
942 if (t.initialized && !t.destroyed && !t.isHidden())
943 t.save({format : 'raw', no_events : true});
947 tinymce.addUnload(t.destroy, t);
949 if (s.submit_patch) {
950 t.onBeforeRenderUI.add(function() {
951 var n = t.getElement().form;
960 // Check page uses id="submit" or name="submit" for it's submit button
961 if (!n.submit.nodeType && !n.submit.length) {
963 n._mceOldSubmit = n.submit;
964 n.submit = function() {
965 // Save all instances
966 tinymce.triggerSave();
969 return t.formElement._mceOldSubmit(t.formElement);
978 function loadScripts() {
979 if (s.language && s.language_load !== false)
980 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
982 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
983 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
985 each(explode(s.plugins), function(p) {
986 if (p &&!PluginManager.urls[p]) {
987 if (p.charAt(0) == '-') {
988 p = p.substr(1, p.length);
989 var dependencies = PluginManager.dependencies(p);
990 each(dependencies, function(dep) {
991 var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};
992 var dep = PluginManager.createUrl(defaultSettings, dep);
993 PluginManager.load(dep.resource, dep);
997 // Skip safari plugin, since it is removed as of 3.3b1
1001 PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});
1006 // Init when que is loaded
1007 sl.loadQueue(function() {
1017 * Initializes the editor this will be called automatically when
1018 * all plugins/themes and language packs are loaded by the rendered method.
1019 * This method will setup the iframe and create the theme and plugin instances.
1024 var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
1028 s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
1031 * Reference to the theme instance that was used to generate the UI.
1034 * @type tinymce.Theme
1036 * // Executes a method on the theme directly
1037 * tinyMCE.activeEditor.theme.someMethod();
1040 s.theme = s.theme.replace(/-/, '');
1041 o = ThemeManager.get(s.theme);
1044 if (t.theme.init && s.init_theme)
1045 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
1047 function initPlugin(p) {
1048 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
1049 if (c && tinymce.inArray(initializedPlugins,p) === -1) {
1050 each(PluginManager.dependencies(p), function(dep){
1059 initializedPlugins.push(p);
1064 // Create all plugins
1065 each(explode(s.plugins.replace(/\-/g, '')), initPlugin);
1067 // Setup popup CSS path(s)
1068 if (s.popup_css !== false) {
1070 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
1072 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
1075 if (s.popup_css_add)
1076 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
1079 * Control manager instance for the editor. Will enables you to create new UI elements and change their states etc.
1081 * @property controlManager
1082 * @type tinymce.ControlManager
1084 * // Disables the bold button
1085 * tinyMCE.activeEditor.controlManager.setDisabled('bold', true);
1087 t.controlManager = new tinymce.ControlManager(t);
1089 if (s.custom_undo_redo) {
1090 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
1091 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
1092 t.undoManager.beforeChange();
1095 t.onExecCommand.add(function(ed, cmd, ui, val, a) {
1096 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
1097 t.undoManager.add();
1101 t.onExecCommand.add(function(ed, c) {
1102 // Don't refresh the select lists until caret move
1103 if (!/^(FontName|FontSize)$/.test(c))
1107 // Remove ghost selections on images and tables in Gecko
1109 function repaint(a, o) {
1110 if (!o || !o.initial)
1111 t.execCommand('mceRepaint');
1114 t.onUndo.add(repaint);
1115 t.onRedo.add(repaint);
1116 t.onSetContent.add(repaint);
1119 // Enables users to override the control factory
1120 t.onBeforeRenderUI.dispatch(t, t.controlManager);
1124 w = s.width || e.style.width || e.offsetWidth;
1125 h = s.height || e.style.height || e.offsetHeight;
1126 t.orgDisplay = e.style.display;
1127 re = /^[0-9\.]+(|px)$/i;
1129 if (re.test('' + w))
1130 w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
1132 if (re.test('' + h))
1133 h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
1136 o = t.theme.renderUI({
1140 deltaWidth : s.delta_width,
1141 deltaHeight : s.delta_height
1144 t.editorContainer = o.editorContainer;
1147 // #ifdef contentEditable
1149 // Content editable mode ends here
1150 if (s.content_editable) {
1151 e = n = o = null; // Fix IE leak
1152 return t.setupContentEditable();
1157 // User specified a document.domain value
1158 if (document.domain && location.hostname != document.domain)
1159 tinymce.relaxedDomain = document.domain;
1162 DOM.setStyles(o.sizeContainer || o.editorContainer, {
1167 // Load specified content CSS last
1168 if (s.content_css) {
1169 tinymce.each(explode(s.content_css), function(u) {
1170 t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
1174 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
1178 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
1180 // We only need to override paths if we have to
1181 // IE has a bug where it remove site absolute urls to relative ones if this is specified
1182 if (s.document_base_url != tinymce.documentBaseURL)
1183 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
1185 // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
1187 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
1189 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
1191 t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
1193 // Firefox 2 doesn't load stylesheets correctly this way
1194 if (!isGecko || !/Firefox\/2/.test(navigator.userAgent)) {
1195 for (i = 0; i < t.contentCSS.length; i++)
1196 t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
1201 bi = s.body_id || 'tinymce';
1202 if (bi.indexOf('=') != -1) {
1203 bi = t.getParam('body_id', '', 'hash');
1204 bi = bi[t.id] || bi;
1207 bc = s.body_class || '';
1208 if (bc.indexOf('=') != -1) {
1209 bc = t.getParam('body_class', '', 'hash');
1210 bc = bc[t.id] || '';
1213 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
1215 // Domain relaxing enabled, then set document domain
1216 if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
1217 // We need to write the contents here in IE since multiple writes messes up refresh button and back button
1218 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()';
1222 // TODO: ACC add the appropriate description on this.
1223 n = DOM.add(o.iframeContainer, 'iframe', {
1225 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
1227 allowTransparency : "true",
1228 title : s.aria_label,
1235 t.contentAreaContainer = o.iframeContainer;
1236 DOM.get(o.editorContainer).style.display = t.orgDisplay;
1237 DOM.get(t.id).style.display = 'none';
1238 DOM.setAttrib(t.id, 'aria-hidden', true);
1240 if (!tinymce.relaxedDomain || !u)
1243 e = n = o = null; // Cleanup
1247 * This method get called by the init method ones the iframe is loaded.
1248 * It will fill the iframe with contents, setups DOM and selection objects for the iframe.
1249 * This method should not be called directly.
1251 * @method setupIframe
1253 setupIframe : function(filled) {
1254 var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
1256 // Setup iframe body
1257 if ((!isIE || !tinymce.relaxedDomain) && !filled) {
1258 // We need to wait for the load event on Gecko
1259 if (isGecko && !s.readonly) {
1260 t.getWin().addEventListener("DOMContentLoaded", function() {
1261 window.setTimeout(function() {
1262 var b = t.getBody(), undef;
1264 // Editable element needs to have some contents or backspace/delete won't work properly for some odd reason on FF 3.6 or older
1265 b.innerHTML = '<br>';
1267 // Check if Gecko supports contentEditable mode FF2 doesn't
1268 if (b.contentEditable !== undef) {
1269 // Setting the contentEditable off/on seems to force caret mode in the editor and enabled auto focus
1270 b.contentEditable = false;
1271 b.contentEditable = true;
1273 // Caret doesn't get rendered when you mousedown on the HTML element on FF 3.x
1274 t.onMouseDown.add(function(ed, e) {
1275 if (e.target.nodeName === "HTML") {
1276 // Setting the contentEditable off/on seems to force caret mode in the editor and enabled auto focus
1277 b.contentEditable = false;
1278 b.contentEditable = true;
1280 d.designMode = 'on'; // Render the caret
1282 // Remove design mode again after a while so it has some time to execute
1283 window.setTimeout(function() {
1284 d.designMode = 'off';
1285 t.getBody().focus();
1290 d.designMode = 'on';
1292 // Call setup frame once the contentEditable/designMode has been initialized
1293 // since the caret won't be rendered some times otherwise.
1294 t.setupIframe(true);
1300 d.write(t.iframeHTML);
1303 if (tinymce.relaxedDomain)
1304 d.domain = tinymce.relaxedDomain;
1306 // Wait for iframe onload event on Gecko
1307 if (isGecko && !s.readonly)
1311 // It will not steal focus while setting contentEditable
1315 if (!isGecko && !s.readonly)
1316 b.contentEditable = true;
1321 * Schema instance, enables you to validate elements and it's children.
1324 * @type tinymce.html.Schema
1326 t.schema = new tinymce.html.Schema(s);
1329 * DOM instance for the editor.
1332 * @type tinymce.dom.DOMUtils
1334 * // Adds a class to all paragraphs within the editor
1335 * tinyMCE.activeEditor.dom.addClass(tinyMCE.activeEditor.dom.select('p'), 'someclass');
1337 t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
1339 url_converter : t.convertURL,
1340 url_converter_scope : t,
1341 hex_colors : s.force_hex_style_colors,
1342 class_filter : s.class_filter,
1344 fix_ie_paragraphs : 1,
1349 * HTML parser will be used when contents is inserted into the editor.
1352 * @type tinymce.html.DomParser
1354 t.parser = new tinymce.html.DomParser(s, t.schema);
1356 // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
1357 if (!t.settings.allow_html_in_named_anchor) {
1358 t.parser.addAttributeFilter('name', function(nodes, name) {
1359 var i = nodes.length, sibling, prevSibling, parent, node;
1363 if (node.name === 'a' && node.firstChild) {
1364 parent = node.parent;
1366 // Move children after current node
1367 sibling = node.lastChild;
1369 prevSibling = sibling.prev;
1370 parent.insert(sibling, node);
1371 sibling = prevSibling;
1378 // Convert src and href into data-mce-src, data-mce-href and data-mce-style
1379 t.parser.addAttributeFilter('src,href,style', function(nodes, name) {
1380 var i = nodes.length, node, dom = t.dom, value, internalName;
1384 value = node.attr(name);
1385 internalName = 'data-mce-' + name;
1387 // Add internal attribute if we need to we don't on a refresh of the document
1388 if (!node.attributes.map[internalName]) {
1389 if (name === "style")
1390 node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
1392 node.attr(internalName, t.convertURL(value, name, node.name));
1397 // Keep scripts from executing
1398 t.parser.addNodeFilter('script', function(nodes, name) {
1399 var i = nodes.length;
1402 nodes[i].attr('type', 'mce-text/javascript');
1405 t.parser.addNodeFilter('#cdata', function(nodes, name) {
1406 var i = nodes.length, node;
1411 node.name = '#comment';
1412 node.value = '[CDATA[' + node.value + ']]';
1416 t.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
1417 var i = nodes.length, node, nonEmptyElements = t.schema.getNonEmptyElements();
1422 if (node.isEmpty(nonEmptyElements))
1423 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
1428 * DOM serializer for the editor. Will be used when contents is extracted from the editor.
1430 * @property serializer
1431 * @type tinymce.dom.Serializer
1433 * // Serializes the first paragraph in the editor into a string
1434 * tinyMCE.activeEditor.serializer.serialize(tinyMCE.activeEditor.dom.select('p')[0]);
1436 t.serializer = new tinymce.dom.Serializer(s, t.dom, t.schema);
1439 * Selection instance for the editor.
1441 * @property selection
1442 * @type tinymce.dom.Selection
1444 * // Sets some contents to the current selection in the editor
1445 * tinyMCE.activeEditor.selection.setContent('Some contents');
1447 * // Gets the current selection
1448 * alert(tinyMCE.activeEditor.selection.getContent());
1450 * // Selects the first paragraph found
1451 * tinyMCE.activeEditor.selection.select(tinyMCE.activeEditor.dom.select('p')[0]);
1453 t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
1456 * Formatter instance.
1458 * @property formatter
1459 * @type tinymce.Formatter
1461 t.formatter = new tinymce.Formatter(this);
1463 // Register default formats
1464 t.formatter.register({
1466 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
1467 {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
1471 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
1472 {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
1473 {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
1477 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
1478 {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
1482 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
1486 {inline : 'strong', remove : 'all'},
1487 {inline : 'span', styles : {fontWeight : 'bold'}},
1488 {inline : 'b', remove : 'all'}
1492 {inline : 'em', remove : 'all'},
1493 {inline : 'span', styles : {fontStyle : 'italic'}},
1494 {inline : 'i', remove : 'all'}
1498 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
1499 {inline : 'u', remove : 'all'}
1503 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
1504 {inline : 'strike', remove : 'all'}
1507 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
1508 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
1509 fontname : {inline : 'span', styles : {fontFamily : '%value'}},
1510 fontsize : {inline : 'span', styles : {fontSize : '%value'}},
1511 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
1512 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
1513 subscript : {inline : 'sub'},
1514 superscript : {inline : 'sup'},
1517 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
1518 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
1519 {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
1523 // Register default block formats
1524 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
1525 t.formatter.register(name, {block : name, remove : 'all'});
1528 // Register user defined formats
1529 t.formatter.register(t.settings.formats);
1532 * Undo manager instance, responsible for handling undo levels.
1534 * @property undoManager
1535 * @type tinymce.UndoManager
1537 * // Undoes the last modification to the editor
1538 * tinyMCE.activeEditor.undoManager.undo();
1540 t.undoManager = new tinymce.UndoManager(t);
1543 t.undoManager.onAdd.add(function(um, l) {
1545 return t.onChange.dispatch(t, l, um);
1548 t.undoManager.onUndo.add(function(um, l) {
1549 return t.onUndo.dispatch(t, l, um);
1552 t.undoManager.onRedo.add(function(um, l) {
1553 return t.onRedo.dispatch(t, l, um);
1556 t.forceBlocks = new tinymce.ForceBlocks(t, {
1557 forced_root_block : s.forced_root_block
1560 t.editorCommands = new tinymce.EditorCommands(t);
1563 t.serializer.onPreProcess.add(function(se, o) {
1564 return t.onPreProcess.dispatch(t, o, se);
1567 t.serializer.onPostProcess.add(function(se, o) {
1568 return t.onPostProcess.dispatch(t, o, se);
1571 t.onPreInit.dispatch(t);
1573 if (!s.gecko_spellcheck)
1574 t.getBody().spellcheck = 0;
1579 t.controlManager.onPostRender.dispatch(t, t.controlManager);
1580 t.onPostRender.dispatch(t);
1582 t.quirks = new tinymce.util.Quirks(this);
1584 if (s.directionality)
1585 t.getBody().dir = s.directionality;
1588 t.getBody().style.whiteSpace = "nowrap";
1590 if (s.handle_node_change_callback) {
1591 t.onNodeChange.add(function(ed, cm, n) {
1592 t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
1596 if (s.save_callback) {
1597 t.onSaveContent.add(function(ed, o) {
1598 var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
1605 if (s.onchange_callback) {
1606 t.onChange.add(function(ed, l) {
1607 t.execCallback('onchange_callback', t, l);
1612 t.onBeforeSetContent.add(function(ed, o) {
1614 each(s.protect, function(pattern) {
1615 o.content = o.content.replace(pattern, function(str) {
1616 return '<!--mce:protected ' + escape(str) + '-->';
1623 if (s.convert_newlines_to_brs) {
1624 t.onBeforeSetContent.add(function(ed, o) {
1626 o.content = o.content.replace(/\r?\n/g, '<br />');
1630 if (s.preformatted) {
1631 t.onPostProcess.add(function(ed, o) {
1632 o.content = o.content.replace(/^\s*<pre.*?>/, '');
1633 o.content = o.content.replace(/<\/pre>\s*$/, '');
1636 o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
1640 if (s.verify_css_classes) {
1641 t.serializer.attribValueFilter = function(n, v) {
1645 // Build regexp for classes
1647 cl = t.dom.getClasses();
1649 if (cl.length > 0) {
1652 each (cl, function(o) {
1653 s += (s ? '|' : '') + o['class'];
1656 t.classesRE = new RegExp('(' + s + ')', 'gi');
1660 return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
1667 if (s.cleanup_callback) {
1668 t.onBeforeSetContent.add(function(ed, o) {
1669 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
1672 t.onPreProcess.add(function(ed, o) {
1674 t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
1677 t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
1680 t.onPostProcess.add(function(ed, o) {
1682 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
1685 o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
1689 if (s.save_callback) {
1690 t.onGetContent.add(function(ed, o) {
1692 o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
1696 if (s.handle_event_callback) {
1697 t.onEvent.add(function(ed, e, o) {
1698 if (t.execCallback('handle_event_callback', e, ed, o) === false)
1703 // Add visual aids when new contents is added
1704 t.onSetContent.add(function() {
1705 t.addVisual(t.getBody());
1708 // Remove empty contents
1709 if (s.padd_empty_editor) {
1710 t.onPostProcess.add(function(ed, o) {
1711 o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
1716 // Fix gecko link bug, when a link is placed at the end of block elements there is
1717 // no way to move the caret behind the link. This fix adds a bogus br element after the link
1718 function fixLinks(ed, o) {
1719 each(ed.dom.select('a'), function(n) {
1720 var pn = n.parentNode;
1722 if (ed.dom.isBlock(pn) && pn.lastChild === n)
1723 ed.dom.add(pn, 'br', {'data-mce-bogus' : 1});
1727 t.onExecCommand.add(function(ed, cmd) {
1728 if (cmd === 'CreateLink')
1732 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
1735 t.load({initial : true, format : 'html'});
1736 t.startContent = t.getContent({format : 'raw'});
1737 t.undoManager.add();
1738 t.initialized = true;
1740 t.onInit.dispatch(t);
1741 t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
1742 t.execCallback('init_instance_callback', t);
1744 t.nodeChanged({initial : 1});
1746 // Load specified content CSS last
1747 each(t.contentCSS, function(u) {
1751 // Handle auto focus
1753 setTimeout(function () {
1754 var ed = tinymce.get(s.auto_focus);
1756 ed.selection.select(ed.getBody(), 1);
1757 ed.selection.collapse(1);
1758 ed.getBody().focus();
1759 ed.getWin().focus();
1766 // #ifdef contentEditable
1769 * Sets up the contentEditable mode.
1771 * @method setupContentEditable
1773 setupContentEditable : function() {
1774 var t = this, s = t.settings, e = t.getElement();
1776 t.contentDocument = s.content_document || document;
1777 t.contentWindow = s.content_window || window;
1780 // Prevent leak in IE
1781 s.content_document = s.content_window = null;
1784 e.contentEditable = t.getParam('content_editable_state', true);
1787 if (!s.gecko_spellcheck)
1788 t.getDoc().body.spellcheck = 0;
1791 t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
1793 url_converter : t.convertURL,
1794 url_converter_scope : t,
1795 hex_colors : s.force_hex_style_colors,
1796 class_filter : s.class_filter,
1797 root_element : t.id,
1798 fix_ie_paragraphs : 1,
1802 t.serializer = new tinymce.dom.Serializer(s, t.dom, schema);
1804 t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
1805 t.forceBlocks = new tinymce.ForceBlocks(t, {
1806 forced_root_block : s.forced_root_block
1809 t.editorCommands = new tinymce.EditorCommands(t);
1812 t.serializer.onPreProcess.add(function(se, o) {
1813 return t.onPreProcess.dispatch(t, o, se);
1816 t.serializer.onPostProcess.add(function(se, o) {
1817 return t.onPostProcess.dispatch(t, o, se);
1820 t.onPreInit.dispatch(t);
1823 t.controlManager.onPostRender.dispatch(t, t.controlManager);
1824 t.onPostRender.dispatch(t);
1826 t.onSetContent.add(function() {
1827 t.addVisual(t.getBody());
1830 //t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')});
1831 t.startContent = t.getContent({format : 'raw'});
1832 t.undoManager.add({initial : true});
1833 t.initialized = true;
1835 t.onInit.dispatch(t);
1837 t.nodeChanged({initial : 1});
1839 // Load specified content CSS last
1840 if (s.content_css) {
1841 each(explode(s.content_css), function(u) {
1842 t.dom.loadCSS(t.documentBaseURI.toAbsolute(u));
1847 // Store away selection
1848 t.dom.bind(t.getElement(), 'beforedeactivate', function() {
1849 t.lastSelectionBookmark = t.selection.getBookmark(1);
1852 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, o) {
1853 if (!DOM.getParent(ed.selection.getStart(), function(n) {return n == ed.getBody();}))
1856 if (!DOM.getParent(ed.selection.getEnd(), function(n) {return n == ed.getBody();}))
1861 e = null; // Cleanup
1867 * Focuses/activates the editor. This will set this editor as the activeEditor in the tinymce collection
1868 * it will also place DOM focus inside the editor.
1871 * @param {Boolean} sf Skip DOM focus. Just set is as the active editor.
1873 focus : function(sf) {
1874 var oed, t = this, selection = t.selection, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
1877 // Get selected control element
1878 ieRng = selection.getRng();
1880 controlElm = ieRng.item(0);
1883 selection.normalize();
1885 // Is not content editable
1889 // Focus the body as well since it's contentEditable
1890 if (tinymce.isGecko) {
1891 t.getBody().focus();
1894 // Restore selected control element
1895 // This is needed when for example an image is selected within a
1896 // layer a call to focus will then remove the control selection
1897 if (controlElm && controlElm.ownerDocument == doc) {
1898 ieRng = doc.body.createControlRange();
1899 ieRng.addElement(controlElm);
1903 // #ifdef contentEditable
1905 // Content editable mode ends here
1907 if (tinymce.isWebKit)
1911 t.getElement().setActive();
1913 t.getElement().focus();
1920 if (tinymce.activeEditor != t) {
1921 if ((oed = tinymce.activeEditor) != null)
1922 oed.onDeactivate.dispatch(oed, t);
1924 t.onActivate.dispatch(t, oed);
1927 tinymce._setActive(t);
1931 * Executes a legacy callback. This method is useful to call old 2.x option callbacks.
1932 * There new event model is a better way to add callback so this method might be removed in the future.
1934 * @method execCallback
1935 * @param {String} n Name of the callback to execute.
1936 * @return {Object} Return value passed from callback function.
1938 execCallback : function(n) {
1939 var t = this, f = t.settings[n], s;
1944 // Look through lookup
1945 if (t.callbackLookup && (s = t.callbackLookup[n])) {
1950 if (is(f, 'string')) {
1951 s = f.replace(/\.\w+$/, '');
1952 s = s ? tinymce.resolve(s) : 0;
1953 f = tinymce.resolve(f);
1954 t.callbackLookup = t.callbackLookup || {};
1955 t.callbackLookup[n] = {func : f, scope : s};
1958 return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
1962 * Translates the specified string by replacing variables with language pack items it will also check if there is
1963 * a key mathcin the input.
1966 * @param {String} s String to translate by the language pack data.
1967 * @return {String} Translated string.
1969 translate : function(s) {
1970 var c = this.settings.language || 'en', i18n = tinymce.i18n;
1975 return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
1976 return i18n[c + '.' + b] || '{#' + b + '}';
1981 * Returns a language pack item by name/key.
1984 * @param {String} n Name/key to get from the language pack.
1985 * @param {String} dv Optional default value to retrive.
1987 getLang : function(n, dv) {
1988 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
1992 * Returns a configuration parameter by name.
1995 * @param {String} n Configruation parameter to retrive.
1996 * @param {String} dv Optional default value to return.
1997 * @param {String} ty Optional type parameter.
1998 * @return {String} Configuration parameter value or default value.
2000 * // Returns a specific config value from the currently active editor
2001 * var someval = tinyMCE.activeEditor.getParam('myvalue');
2003 * // Returns a specific config value from a specific editor instance by id
2004 * var someval2 = tinyMCE.get('my_editor').getParam('myvalue');
2006 getParam : function(n, dv, ty) {
2007 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
2009 if (ty === 'hash') {
2012 if (is(v, 'string')) {
2013 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
2017 o[tr(v[0])] = tr(v[1]);
2019 o[tr(v[0])] = tr(v);
2031 * Distpaches out a onNodeChange event to all observers. This method should be called when you
2032 * need to update the UI states or element path etc.
2034 * @method nodeChanged
2035 * @param {Object} o Optional object to pass along for the node changed event.
2037 nodeChanged : function(o) {
2038 var t = this, s = t.selection, n = s.getStart() || t.getBody();
2040 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
2041 if (t.initialized) {
2043 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
2045 // Get parents and add them to object
2047 t.dom.getParent(n, function(node) {
2048 if (node.nodeName == 'BODY')
2051 o.parents.push(node);
2054 t.onNodeChange.dispatch(
2056 o ? o.controlManager || t.controlManager : t.controlManager,
2065 * Adds a button that later gets created by the ControlManager. This is a shorter and easier method
2066 * of adding buttons without the need to deal with the ControlManager directly. But it's also less
2067 * powerfull if you need more control use the ControlManagers factory methods instead.
2070 * @param {String} n Button name to add.
2071 * @param {Object} s Settings object with title, cmd etc.
2073 * // Adds a custom button to the editor and when a user clicks the button it will open
2074 * // an alert box with the selected contents as plain text.
2078 * theme_advanced_buttons1 : 'example,..'
2080 * setup : function(ed) {
2081 * // Register example button
2082 * ed.addButton('example', {
2083 * title : 'example.desc',
2084 * image : '../jscripts/tiny_mce/plugins/example/img/example.gif',
2085 * onclick : function() {
2086 * ed.windowManager.alert('Hello world!! Selection: ' + ed.selection.getContent({format : 'text'}));
2092 addButton : function(n, s) {
2095 t.buttons = t.buttons || {};
2100 * Adds a custom command to the editor, you can also override existing commands with this method.
2101 * The command that you add can be executed with execCommand.
2103 * @method addCommand
2104 * @param {String} name Command name to add/override.
2105 * @param {addCommandCallback} callback Function to execute when the command occurs.
2106 * @param {Object} scope Optional scope to execute the function in.
2108 * // Adds a custom command that later can be executed using execCommand
2112 * setup : function(ed) {
2113 * // Register example command
2114 * ed.addCommand('mycommand', function(ui, v) {
2115 * ed.windowManager.alert('Hello world!! Selection: ' + ed.selection.getContent({format : 'text'}));
2120 addCommand : function(name, callback, scope) {
2122 * Callback function that gets called when a command is executed.
2124 * @callback addCommandCallback
2125 * @param {Boolean} ui Display UI state true/false.
2126 * @param {Object} value Optional value for command.
2127 * @return {Boolean} True/false state if the command was handled or not.
2129 this.execCommands[name] = {func : callback, scope : scope || this};
2133 * Adds a custom query state command to the editor, you can also override existing commands with this method.
2134 * The command that you add can be executed with queryCommandState function.
2136 * @method addQueryStateHandler
2137 * @param {String} name Command name to add/override.
2138 * @param {addQueryStateHandlerCallback} callback Function to execute when the command state retrival occurs.
2139 * @param {Object} scope Optional scope to execute the function in.
2141 addQueryStateHandler : function(name, callback, scope) {
2143 * Callback function that gets called when a queryCommandState is executed.
2145 * @callback addQueryStateHandlerCallback
2146 * @return {Boolean} True/false state if the command is enabled or not like is it bold.
2148 this.queryStateCommands[name] = {func : callback, scope : scope || this};
2152 * Adds a custom query value command to the editor, you can also override existing commands with this method.
2153 * The command that you add can be executed with queryCommandValue function.
2155 * @method addQueryValueHandler
2156 * @param {String} name Command name to add/override.
2157 * @param {addQueryValueHandlerCallback} callback Function to execute when the command value retrival occurs.
2158 * @param {Object} scope Optional scope to execute the function in.
2160 addQueryValueHandler : function(name, callback, scope) {
2162 * Callback function that gets called when a queryCommandValue is executed.
2164 * @callback addQueryValueHandlerCallback
2165 * @return {Object} Value of the command or undefined.
2167 this.queryValueCommands[name] = {func : callback, scope : scope || this};
2171 * Adds a keyboard shortcut for some command or function.
2173 * @method addShortcut
2174 * @param {String} pa Shortcut pattern. Like for example: ctrl+alt+o.
2175 * @param {String} desc Text description for the command.
2176 * @param {String/Function} cmd_func Command name string or function to execute when the key is pressed.
2177 * @param {Object} sc Optional scope to execute the function in.
2178 * @return {Boolean} true/false state if the shortcut was added or not.
2180 addShortcut : function(pa, desc, cmd_func, sc) {
2183 if (!t.settings.custom_shortcuts)
2186 t.shortcuts = t.shortcuts || {};
2188 if (is(cmd_func, 'string')) {
2191 cmd_func = function() {
2192 t.execCommand(c, false, null);
2196 if (is(cmd_func, 'object')) {
2199 cmd_func = function() {
2200 t.execCommand(c[0], c[1], c[2]);
2204 each(explode(pa), function(pa) {
2214 each(explode(pa, '+'), function(v) {
2223 o.charCode = v.charCodeAt(0);
2224 o.keyCode = v.toUpperCase().charCodeAt(0);
2228 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
2235 * Executes a command on the current instance. These commands can be TinyMCE internal commands prefixed with "mce" or
2236 * they can be build in browser commands such as "Bold". A compleate list of browser commands is available on MSDN or Mozilla.org.
2237 * This function will dispatch the execCommand function on each plugin, theme or the execcommand_callback option if none of these
2238 * return true it will handle the command as a internal browser command.
2240 * @method execCommand
2241 * @param {String} cmd Command name to execute, for example mceLink or Bold.
2242 * @param {Boolean} ui True/false state if a UI (dialog) should be presented or not.
2243 * @param {mixed} val Optional command value, this can be anything.
2244 * @param {Object} a Optional arguments object.
2245 * @return {Boolean} True/false if the command was executed or not.
2247 execCommand : function(cmd, ui, val, a) {
2248 var t = this, s = 0, o, st;
2250 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
2254 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
2259 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
2260 t.onExecCommand.dispatch(t, cmd, ui, val, a);
2264 // Registred commands
2265 if (o = t.execCommands[cmd]) {
2266 st = o.func.call(o.scope, ui, val);
2268 // Fall through on true
2270 t.onExecCommand.dispatch(t, cmd, ui, val, a);
2276 each(t.plugins, function(p) {
2277 if (p.execCommand && p.execCommand(cmd, ui, val)) {
2278 t.onExecCommand.dispatch(t, cmd, ui, val, a);
2288 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
2289 t.onExecCommand.dispatch(t, cmd, ui, val, a);
2294 if (t.editorCommands.execCommand(cmd, ui, val)) {
2295 t.onExecCommand.dispatch(t, cmd, ui, val, a);
2300 t.getDoc().execCommand(cmd, ui, val);
2301 t.onExecCommand.dispatch(t, cmd, ui, val, a);
2305 * Returns a command specific state, for example if bold is enabled or not.
2307 * @method queryCommandState
2308 * @param {string} cmd Command to query state from.
2309 * @return {Boolean} Command specific state, for example if bold is enabled or not.
2311 queryCommandState : function(cmd) {
2314 // Is hidden then return undefined
2318 // Registred commands
2319 if (o = t.queryStateCommands[cmd]) {
2320 s = o.func.call(o.scope);
2322 // Fall though on true
2327 // Registred commands
2328 o = t.editorCommands.queryCommandState(cmd);
2334 return this.getDoc().queryCommandState(cmd);
2336 // Fails sometimes see bug: 1896577
2341 * Returns a command specific value, for example the current font size.
2343 * @method queryCommandValue
2344 * @param {string} c Command to query value from.
2345 * @return {Object} Command specific value, for example the current font size.
2347 queryCommandValue : function(c) {
2350 // Is hidden then return undefined
2354 // Registred commands
2355 if (o = t.queryValueCommands[c]) {
2356 s = o.func.call(o.scope);
2358 // Fall though on true
2363 // Registred commands
2364 o = t.editorCommands.queryCommandValue(c);
2370 return this.getDoc().queryCommandValue(c);
2372 // Fails sometimes see bug: 1896577
2377 * Shows the editor and hides any textarea/div that the editor is supposed to replace.
2384 DOM.show(t.getContainer());
2390 * Hides the editor and shows any textarea/div that the editor is supposed to replace.
2395 var t = this, d = t.getDoc();
2397 // Fixed bug where IE has a blinking cursor left from the editor
2399 d.execCommand('SelectAll');
2401 // We must save before we hide so Safari doesn't crash
2403 DOM.hide(t.getContainer());
2404 DOM.setStyle(t.id, 'display', t.orgDisplay);
2408 * Returns true/false if the editor is hidden or not.
2411 * @return {Boolean} True/false if the editor is hidden or not.
2413 isHidden : function() {
2414 return !DOM.isHidden(this.id);
2418 * Sets the progress state, this will display a throbber/progess for the editor.
2419 * This is ideal for asycronous operations like an AJAX save call.
2421 * @method setProgressState
2422 * @param {Boolean} b Boolean state if the progress should be shown or hidden.
2423 * @param {Number} ti Optional time to wait before the progress gets shown.
2424 * @param {Object} o Optional object to pass to the progress observers.
2425 * @return {Boolean} Same as the input state.
2427 * // Show progress for the active editor
2428 * tinyMCE.activeEditor.setProgressState(true);
2430 * // Hide progress for the active editor
2431 * tinyMCE.activeEditor.setProgressState(false);
2433 * // Show progress after 3 seconds
2434 * tinyMCE.activeEditor.setProgressState(true, 3000);
2436 setProgressState : function(b, ti, o) {
2437 this.onSetProgressState.dispatch(this, b, ti, o);
2443 * Loads contents from the textarea or div element that got converted into an editor instance.
2444 * This method will move the contents from that textarea or div into the editor by using setContent
2445 * so all events etc that method has will get dispatched as well.
2448 * @param {Object} o Optional content object, this gets passed around through the whole load process.
2449 * @return {String} HTML string that got set into the editor.
2451 load : function(o) {
2452 var t = this, e = t.getElement(), h;
2458 // Double encode existing entities in the value
2459 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
2463 t.onLoadContent.dispatch(t, o);
2465 o.element = e = null;
2472 * Saves the contents from a editor out to the textarea or div element that got converted into an editor instance.
2473 * This method will move the HTML contents from the editor into that textarea or div by getContent
2474 * so all events etc that method has will get dispatched as well.
2477 * @param {Object} o Optional content object, this gets passed around through the whole save process.
2478 * @return {String} HTML string that got set into the textarea/div.
2480 save : function(o) {
2481 var t = this, e = t.getElement(), h, f;
2483 if (!e || !t.initialized)
2489 // Add undo level will trigger onchange event
2491 t.undoManager.typing = false;
2492 t.undoManager.add();
2496 h = o.content = t.getContent(o);
2499 t.onSaveContent.dispatch(t, o);
2503 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
2506 // Update hidden form element
2507 if (f = DOM.getParent(t.id, 'form')) {
2508 each(f.elements, function(e) {
2509 if (e.name == t.id) {
2518 o.element = e = null;
2524 * Sets the specified content to the editor instance, this will cleanup the content before it gets set using
2525 * the different cleanup rules options.
2527 * @method setContent
2528 * @param {String} content Content to set to editor, normally HTML contents but can be other formats as well.
2529 * @param {Object} args Optional content object, this gets passed around through the whole set process.
2530 * @return {String} HTML string that got set into the editor.
2532 * // Sets the HTML contents of the activeEditor editor
2533 * tinyMCE.activeEditor.setContent('<span>some</span> html');
2535 * // Sets the raw contents of the activeEditor editor
2536 * tinyMCE.activeEditor.setContent('<span>some</span> html', {format : 'raw'});
2538 * // Sets the content of a specific editor (my_editor in this example)
2539 * tinyMCE.get('my_editor').setContent(data);
2541 * // Sets the bbcode contents of the activeEditor editor if the bbcode plugin was added
2542 * tinyMCE.activeEditor.setContent('[b]some[/b] html', {format : 'bbcode'});
2544 setContent : function(content, args) {
2545 var self = this, rootNode, body = self.getBody(), forcedRootBlockName;
2547 // Setup args object
2549 args.format = args.format || 'html';
2551 args.content = content;
2554 if (!args.no_events)
2555 self.onBeforeSetContent.dispatch(self, args);
2557 content = args.content;
2559 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
2560 // It will also be impossible to place the caret in the editor unless there is a BR element present
2561 if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
2562 forcedRootBlockName = self.settings.forced_root_block;
2563 if (forcedRootBlockName)
2564 content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';
2566 content = '<br data-mce-bogus="1">';
2568 body.innerHTML = content;
2569 self.selection.select(body, true);
2570 self.selection.collapse(true);
2574 // Parse and serialize the html
2575 if (args.format !== 'raw') {
2576 content = new tinymce.html.Serializer({}, self.schema).serialize(
2577 self.parser.parse(content)
2581 // Set the new cleaned contents to the editor
2582 args.content = tinymce.trim(content);
2583 self.dom.setHTML(body, args.content);
2585 // Do post processing
2586 if (!args.no_events)
2587 self.onSetContent.dispatch(self, args);
2589 self.selection.normalize();
2591 return args.content;
2595 * Gets the content from the editor instance, this will cleanup the content before it gets returned using
2596 * the different cleanup rules options.
2598 * @method getContent
2599 * @param {Object} args Optional content object, this gets passed around through the whole get process.
2600 * @return {String} Cleaned content string, normally HTML contents.
2602 * // Get the HTML contents of the currently active editor
2603 * console.debug(tinyMCE.activeEditor.getContent());
2605 * // Get the raw contents of the currently active editor
2606 * tinyMCE.activeEditor.getContent({format : 'raw'});
2608 * // Get content of a specific editor:
2609 * tinyMCE.get('content id').getContent()
2611 getContent : function(args) {
2612 var self = this, content;
2614 // Setup args object
2616 args.format = args.format || 'html';
2620 if (!args.no_events)
2621 self.onBeforeGetContent.dispatch(self, args);
2623 // Get raw contents or by default the cleaned contents
2624 if (args.format == 'raw')
2625 content = self.getBody().innerHTML;
2627 content = self.serializer.serialize(self.getBody(), args);
2629 args.content = tinymce.trim(content);
2631 // Do post processing
2632 if (!args.no_events)
2633 self.onGetContent.dispatch(self, args);
2635 return args.content;
2639 * Returns true/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
2642 * @return {Boolean} True/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
2644 * if (tinyMCE.activeEditor.isDirty())
2645 * alert("You must save your contents.");
2647 isDirty : function() {
2650 return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
2654 * Returns the editors container element. The container element wrappes in
2655 * all the elements added to the page for the editor. Such as UI, iframe etc.
2657 * @method getContainer
2658 * @return {Element} HTML DOM element for the editor container.
2660 getContainer : function() {
2664 t.container = DOM.get(t.editorContainer || t.id + '_parent');
2670 * Returns the editors content area container element. The this element is the one who
2671 * holds the iframe or the editable element.
2673 * @method getContentAreaContainer
2674 * @return {Element} HTML DOM element for the editor area container.
2676 getContentAreaContainer : function() {
2677 return this.contentAreaContainer;
2681 * Returns the target element/textarea that got replaced with a TinyMCE editor instance.
2683 * @method getElement
2684 * @return {Element} HTML DOM element for the replaced element.
2686 getElement : function() {
2687 return DOM.get(this.settings.content_element || this.id);
2691 * Returns the iframes window object.
2694 * @return {Window} Iframe DOM window object.
2696 getWin : function() {
2699 if (!t.contentWindow) {
2700 e = DOM.get(t.id + "_ifr");
2703 t.contentWindow = e.contentWindow;
2706 return t.contentWindow;
2710 * Returns the iframes document object.
2713 * @return {Document} Iframe DOM document object.
2715 getDoc : function() {
2718 if (!t.contentDocument) {
2722 t.contentDocument = w.document;
2725 return t.contentDocument;
2729 * Returns the iframes body element.
2732 * @return {Element} Iframe body element.
2734 getBody : function() {
2735 return this.bodyElement || this.getDoc().body;
2739 * URL converter function this gets executed each time a user adds an img, a or
2740 * any other element that has a URL in it. This will be called both by the DOM and HTML
2741 * manipulation functions.
2743 * @method convertURL
2744 * @param {string} u URL to convert.
2745 * @param {string} n Attribute name src, href etc.
2746 * @param {string/HTMLElement} Tag name or HTML DOM element depending on HTML or DOM insert.
2747 * @return {string} Converted URL string.
2749 convertURL : function(u, n, e) {
2750 var t = this, s = t.settings;
2752 // Use callback instead
2753 if (s.urlconverter_callback)
2754 return t.execCallback('urlconverter_callback', u, e, true, n);
2756 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
2757 if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
2760 // Convert to relative
2761 if (s.relative_urls)
2762 return t.documentBaseURI.toRelative(u);
2764 // Convert to absolute
2765 u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
2771 * Adds visual aid for tables, anchors etc so they can be more easily edited inside the editor.
2774 * @param {Element} e Optional root element to loop though to find tables etc that needs the visual aid.
2776 addVisual : function(e) {
2777 var t = this, s = t.settings;
2779 e = e || t.getBody();
2781 if (!is(t.hasVisual))
2782 t.hasVisual = s.visual;
2784 each(t.dom.select('table,a', e), function(e) {
2787 switch (e.nodeName) {
2789 v = t.dom.getAttrib(e, 'border');
2791 if (!v || v == '0') {
2793 t.dom.addClass(e, s.visual_table_class);
2795 t.dom.removeClass(e, s.visual_table_class);
2801 v = t.dom.getAttrib(e, 'name');
2805 t.dom.addClass(e, 'mceItemAnchor');
2807 t.dom.removeClass(e, 'mceItemAnchor');
2814 t.onVisualAid.dispatch(t, e, t.hasVisual);
2818 * Removes the editor from the dom and tinymce collection.
2822 remove : function() {
2823 var t = this, e = t.getContainer();
2825 t.removed = 1; // Cancels post remove event execution
2828 t.execCallback('remove_instance_callback', t);
2829 t.onRemove.dispatch(t);
2831 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
2832 t.onExecCommand.listeners = [];
2839 * Destroys the editor instance by removing all events, element references or other resources
2840 * that could leak memory. This method will be called automatically when the page is unloaded
2841 * but you can also call it directly if you know what you are doing.
2844 * @param {Boolean} s Optional state if the destroy is an automatic destroy or user called one.
2846 destroy : function(s) {
2849 // One time is enough
2854 tinymce.removeUnload(t.destroy);
2855 tinyMCE.onBeforeUnload.remove(t._beforeUnload);
2858 if (t.theme && t.theme.destroy)
2861 // Destroy controls, selection and dom
2862 t.controlManager.destroy();
2863 t.selection.destroy();
2866 // Remove all events
2868 // Don't clear the window or document if content editable
2869 // is enabled since other instances might still be present
2870 if (!t.settings.content_editable) {
2871 Event.clear(t.getWin());
2872 Event.clear(t.getDoc());
2875 Event.clear(t.getBody());
2876 Event.clear(t.formElement);
2879 if (t.formElement) {
2880 t.formElement.submit = t.formElement._mceOldSubmit;
2881 t.formElement._mceOldSubmit = null;
2884 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
2887 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
2892 // Internal functions
2894 _addEvents : function() {
2895 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
2896 var t = this, i, s = t.settings, dom = t.dom, lo = {
2897 mouseup : 'onMouseUp',
2898 mousedown : 'onMouseDown',
2901 keydown : 'onKeyDown',
2902 keypress : 'onKeyPress',
2903 submit : 'onSubmit',
2905 contextmenu : 'onContextMenu',
2906 dblclick : 'onDblClick',
2907 paste : 'onPaste' // Doesn't work in all browsers yet
2910 function eventHandler(e, o) {
2913 // Don't fire events when it's removed
2917 // Generic event handler
2918 if (t.onEvent.dispatch(t, e, o) !== false) {
2919 // Specific event handler
2920 t[lo[e.fakeType || e.type]].dispatch(t, e, o);
2925 each(lo, function(v, k) {
2928 dom.bind(t.getDoc(), k, eventHandler);
2932 dom.bind(t.getBody(), k, function(e) {
2939 dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
2943 dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
2947 dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
2951 // #ifdef contentEditable
2953 if (s.content_editable && tinymce.isOpera) {
2954 // Opera doesn't support focus event for contentEditable elements so we need to fake it
2955 function doFocus(e) {
2959 dom.bind(t.getBody(), 'click', doFocus);
2960 dom.bind(t.getBody(), 'keydown', doFocus);
2965 // Fixes bug where a specified document_base_uri could result in broken images
2966 // This will also fix drag drop of images in Gecko
2967 if (tinymce.isGecko) {
2968 dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
2973 if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src')))
2974 e.src = t.documentBaseURI.toAbsolute(v);
2978 // Set various midas options in Gecko
2980 function setOpts() {
2981 var t = this, d = t.getDoc(), s = t.settings;
2983 if (isGecko && !s.readonly) {
2984 if (t._isHidden()) {
2986 if (!s.content_editable) {
2987 d.body.contentEditable = false;
2988 d.body.contentEditable = true;
2991 // Fails if it's hidden
2996 // Try new Gecko method
2997 d.execCommand("styleWithCSS", 0, false);
3001 try {d.execCommand("useCSS", 0, true);} catch (ex) {}
3004 if (!s.table_inline_editing)
3005 try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
3007 if (!s.object_resizing)
3008 try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
3012 t.onBeforeExecCommand.add(setOpts);
3013 t.onMouseDown.add(setOpts);
3016 t.onClick.add(function(ed, e) {
3019 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
3020 // WebKit can't even do simple things like selecting an image
3021 // Needs tobe the setBaseAndExtend or it will fail to select floated images
3022 if (tinymce.isWebKit && e.nodeName == 'IMG')
3023 t.selection.getSel().setBaseAndExtent(e, 0, e, 1);
3025 if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor'))
3026 t.selection.select(e);
3031 // Add node change handlers
3032 t.onMouseUp.add(t.nodeChanged);
3033 //t.onClick.add(t.nodeChanged);
3034 t.onKeyUp.add(function(ed, e) {
3037 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)
3042 // Add block quote deletion handler
3043 t.onKeyDown.add(function(ed, e) {
3044 // Was the BACKSPACE key pressed?
3048 var n = ed.selection.getRng().startContainer;
3049 var offset = ed.selection.getRng().startOffset;
3051 while (n && n.nodeType && n.nodeType != 1 && n.parentNode)
3054 // Is the cursor at the beginning of a blockquote?
3055 if (n && n.parentNode && n.parentNode.tagName === 'BLOCKQUOTE' && n.parentNode.firstChild == n && offset == 0) {
3056 // Remove the blockquote
3057 ed.formatter.toggle('blockquote', null, n.parentNode);
3059 // Move the caret to the beginning of n
3060 var rng = ed.selection.getRng();
3063 ed.selection.setRng(rng);
3064 ed.selection.collapse(false);
3070 // Add reset handler
3071 t.onReset.add(function() {
3072 t.setContent(t.startContent, {format : 'raw'});
3076 if (s.custom_shortcuts) {
3077 if (s.custom_undo_redo_keyboard_shortcuts) {
3078 t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
3079 t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
3082 // Add default shortcuts for gecko
3083 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
3084 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
3085 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
3087 // BlockFormat shortcuts keys
3088 for (i=1; i<=6; i++)
3089 t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
3091 t.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
3092 t.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
3093 t.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
3098 if (!e.altKey && !e.ctrlKey && !e.metaKey)
3101 each(t.shortcuts, function(o) {
3102 if (tinymce.isMac && o.ctrl != e.metaKey)
3104 else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
3107 if (o.alt != e.altKey)
3110 if (o.shift != e.shiftKey)
3113 if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
3122 t.onKeyUp.add(function(ed, e) {
3126 return Event.cancel(e);
3129 t.onKeyPress.add(function(ed, e) {
3133 return Event.cancel(e);
3136 t.onKeyDown.add(function(ed, e) {
3140 o.func.call(o.scope);
3141 return Event.cancel(e);
3147 // Fix so resize will only update the width and height attributes not the styles of an image
3148 // It will also block mceItemNoResize items
3149 dom.bind(t.getDoc(), 'controlselect', function(e) {
3150 var re = t.resizeInfo, cb;
3154 // Don't do this action for non image elements
3155 if (e.nodeName !== 'IMG')
3159 dom.unbind(re.node, re.ev, re.cb);
3161 if (!dom.hasClass(e, 'mceItemNoResize')) {
3163 cb = dom.bind(e, ev, function(e) {
3168 if (v = dom.getStyle(e, 'width')) {
3169 dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
3170 dom.setStyle(e, 'width', '');
3173 if (v = dom.getStyle(e, 'height')) {
3174 dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
3175 dom.setStyle(e, 'height', '');
3180 cb = dom.bind(e, 'resizestart', Event.cancel, Event);
3183 re = t.resizeInfo = {
3191 if (tinymce.isOpera) {
3192 t.onClick.add(function(ed, e) {
3197 // Add custom undo/redo handlers
3198 if (s.custom_undo_redo) {
3199 function addUndo() {
3200 t.undoManager.typing = false;
3201 t.undoManager.add();
3204 dom.bind(t.getDoc(), 'focusout', function(e) {
3205 if (!t.removed && t.undoManager.typing)
3209 // Add undo level when contents is drag/dropped within the editor
3210 t.dom.bind(t.dom.getRoot(), 'dragend', function(e) {
3214 t.onKeyUp.add(function(ed, e) {
3215 var keyCode = e.keyCode;
3217 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || e.ctrlKey)
3221 t.onKeyDown.add(function(ed, e) {
3222 var keyCode = e.keyCode, sel;
3225 sel = t.getDoc().selection;
3227 // Fix IE control + backspace browser bug
3228 if (sel && sel.createRange && sel.createRange().item) {
3229 t.undoManager.beforeChange();
3230 ed.dom.remove(sel.createRange().item(0));
3233 return Event.cancel(e);
3237 // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
3238 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) {
3239 // Add position before enter key is pressed, used by IE since it still uses the default browser behavior
3240 // Todo: Remove this once we normalize enter behavior on IE
3241 if (tinymce.isIE && keyCode == 13)
3242 t.undoManager.beforeChange();
3244 if (t.undoManager.typing)
3250 // If key isn't shift,ctrl,alt,capslock,metakey
3251 if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) {
3252 t.undoManager.beforeChange();
3253 t.undoManager.typing = true;
3254 t.undoManager.add();
3258 t.onMouseDown.add(function() {
3259 if (t.undoManager.typing)
3264 // Fire a nodeChanged when the selection is changed on WebKit this fixes selection issues on iOS5
3265 // It only fires the nodeChange event every 50ms since it would other wise update the UI when you type and it hogs the CPU
3266 if (tinymce.isWebKit) {
3267 dom.bind(t.getDoc(), 'selectionchange', function() {
3268 if (t.selectionTimer) {
3269 window.clearTimeout(t.selectionTimer);
3270 t.selectionTimer = 0;
3273 t.selectionTimer = window.setTimeout(function() {
3279 // Bug fix for FireFox keeping styles from end of selection instead of start.
3280 if (tinymce.isGecko) {
3281 function getAttributeApplyFunction() {
3282 var template = t.dom.getAttribs(t.selection.getStart().cloneNode(false));
3285 var target = t.selection.getStart();
3287 if (target !== t.getBody()) {
3288 t.dom.removeAllAttribs(target);
3290 each(template, function(attr) {
3291 target.setAttributeNode(attr.cloneNode(true));
3297 function isSelectionAcrossElements() {
3298 var s = t.selection;
3300 return !s.isCollapsed() && s.getStart() != s.getEnd();
3303 t.onKeyPress.add(function(ed, e) {
3304 var applyAttributes;
3306 if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
3307 applyAttributes = getAttributeApplyFunction();
3308 t.getDoc().execCommand('delete', false, null);
3311 return Event.cancel(e);
3315 t.dom.bind(t.getDoc(), 'cut', function(e) {
3316 var applyAttributes;
3318 if (isSelectionAcrossElements()) {
3319 applyAttributes = getAttributeApplyFunction();
3320 t.onKeyUp.addToTop(Event.cancel, Event);
3322 setTimeout(function() {
3324 t.onKeyUp.remove(Event.cancel, Event);
3331 _isHidden : function() {
3337 // Weird, wheres that cursor selection?
3338 s = this.selection.getSel();
3339 return (!s || !s.rangeCount || s.rangeCount == 0);