+});\r
+})(tinymce);\r
+\r
+(function(tinymce) {\r
+ var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;\r
+\r
+ tinymce.create('tinymce.AddOnManager', {\r
+ AddOnManager : function() {\r
+ var self = this;\r
+\r
+ self.items = [];\r
+ self.urls = {};\r
+ self.lookup = {};\r
+ self.onAdd = new Dispatcher(self);\r
+ },\r
+\r
+ get : function(n) {\r
+ if (this.lookup[n]) {\r
+ return this.lookup[n].instance;\r
+ } else {\r
+ return undefined;\r
+ }\r
+ },\r
+\r
+ dependencies : function(n) {\r
+ var result;\r
+ if (this.lookup[n]) {\r
+ result = this.lookup[n].dependencies;\r
+ }\r
+ return result || [];\r
+ },\r
+\r
+ requireLangPack : function(n) {\r
+ var s = tinymce.settings;\r
+\r
+ if (s && s.language && s.language_load !== false)\r
+ tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');\r
+ },\r
+\r
+ add : function(id, o, dependencies) {\r
+ this.items.push(o);\r
+ this.lookup[id] = {instance:o, dependencies:dependencies};\r
+ this.onAdd.dispatch(this, id, o);\r
+\r
+ return o;\r
+ },\r
+ createUrl: function(baseUrl, dep) {\r
+ if (typeof dep === "object") {\r
+ return dep\r
+ } else {\r
+ return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};\r
+ }\r
+ },\r
+\r
+ addComponents: function(pluginName, scripts) {\r
+ var pluginUrl = this.urls[pluginName];\r
+ tinymce.each(scripts, function(script){\r
+ tinymce.ScriptLoader.add(pluginUrl+"/"+script); \r
+ });\r
+ },\r
+\r
+ load : function(n, u, cb, s) {\r
+ var t = this, url = u;\r
+\r
+ function loadDependencies() {\r
+ var dependencies = t.dependencies(n);\r
+ tinymce.each(dependencies, function(dep) {\r
+ var newUrl = t.createUrl(u, dep);\r
+ t.load(newUrl.resource, newUrl, undefined, undefined);\r
+ });\r
+ if (cb) {\r
+ if (s) {\r
+ cb.call(s);\r
+ } else {\r
+ cb.call(tinymce.ScriptLoader);\r
+ }\r
+ }\r
+ }\r
+\r
+ if (t.urls[n])\r
+ return;\r
+ if (typeof u === "object")\r
+ url = u.prefix + u.resource + u.suffix;\r
+\r
+ if (url.indexOf('/') != 0 && url.indexOf('://') == -1)\r
+ url = tinymce.baseURL + '/' + url;\r
+\r
+ t.urls[n] = url.substring(0, url.lastIndexOf('/'));\r
+\r
+ if (t.lookup[n]) {\r
+ loadDependencies();\r
+ } else {\r
+ tinymce.ScriptLoader.add(url, loadDependencies, s);\r
+ }\r
+ }\r
+ });\r
+\r
+ // Create plugin and theme managers\r
+ tinymce.PluginManager = new tinymce.AddOnManager();\r
+ tinymce.ThemeManager = new tinymce.AddOnManager();\r
+}(tinymce));\r
+\r
+(function(tinymce) {\r
+ // Shorten names\r
+ var each = tinymce.each, extend = tinymce.extend,\r
+ DOM = tinymce.DOM, Event = tinymce.dom.Event,\r
+ ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,\r
+ explode = tinymce.explode,\r
+ Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;\r
+\r
+ // Setup some URLs where the editor API is located and where the document is\r
+ tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');\r
+ if (!/[\/\\]$/.test(tinymce.documentBaseURL))\r
+ tinymce.documentBaseURL += '/';\r
+\r
+ tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);\r
+\r
+ tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);\r
+\r
+ // Add before unload listener\r
+ // This was required since IE was leaking memory if you added and removed beforeunload listeners\r
+ // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event\r
+ tinymce.onBeforeUnload = new Dispatcher(tinymce);\r
+\r
+ // Must be on window or IE will leak if the editor is placed in frame or iframe\r
+ Event.add(window, 'beforeunload', function(e) {\r
+ tinymce.onBeforeUnload.dispatch(tinymce, e);\r
+ });\r
+\r
+ tinymce.onAddEditor = new Dispatcher(tinymce);\r
+\r
+ tinymce.onRemoveEditor = new Dispatcher(tinymce);\r
+\r
+ tinymce.EditorManager = extend(tinymce, {\r
+ editors : [],\r
+\r
+ i18n : {},\r
+\r
+ activeEditor : null,\r
+\r
+ init : function(s) {\r
+ var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;\r
+\r
+ function execCallback(se, n, s) {\r
+ var f = se[n];\r
+\r
+ if (!f)\r
+ return;\r
+\r
+ if (tinymce.is(f, 'string')) {\r
+ s = f.replace(/\.\w+$/, '');\r
+ s = s ? tinymce.resolve(s) : 0;\r
+ f = tinymce.resolve(f);\r
+ }\r
+\r
+ return f.apply(s || this, Array.prototype.slice.call(arguments, 2));\r
+ };\r
+\r
+ s = extend({\r
+ theme : "simple",\r
+ language : "en"\r
+ }, s);\r
+\r
+ t.settings = s;\r
+\r
+ // Legacy call\r
+ Event.add(document, 'init', function() {\r
+ var l, co;\r
+\r
+ execCallback(s, 'onpageload');\r
+\r
+ switch (s.mode) {\r
+ case "exact":\r
+ l = s.elements || '';\r
+\r
+ if(l.length > 0) {\r
+ each(explode(l), function(v) {\r
+ if (DOM.get(v)) {\r
+ ed = new tinymce.Editor(v, s);\r
+ el.push(ed);\r
+ ed.render(1);\r
+ } else {\r
+ each(document.forms, function(f) {\r
+ each(f.elements, function(e) {\r
+ if (e.name === v) {\r
+ v = 'mce_editor_' + instanceCounter++;\r
+ DOM.setAttrib(e, 'id', v);\r
+\r
+ ed = new tinymce.Editor(v, s);\r
+ el.push(ed);\r
+ ed.render(1);\r
+ }\r
+ });\r
+ });\r
+ }\r
+ });\r
+ }\r
+ break;\r
+\r
+ case "textareas":\r
+ case "specific_textareas":\r
+ function hasClass(n, c) {\r
+ return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);\r
+ };\r
+\r
+ each(DOM.select('textarea'), function(v) {\r
+ if (s.editor_deselector && hasClass(v, s.editor_deselector))\r
+ return;\r
+\r
+ if (!s.editor_selector || hasClass(v, s.editor_selector)) {\r
+ // Can we use the name\r
+ e = DOM.get(v.name);\r
+ if (!v.id && !e)\r
+ v.id = v.name;\r
+\r
+ // Generate unique name if missing or already exists\r
+ if (!v.id || t.get(v.id))\r
+ v.id = DOM.uniqueId();\r
+\r
+ ed = new tinymce.Editor(v.id, s);\r
+ el.push(ed);\r
+ ed.render(1);\r
+ }\r
+ });\r
+ break;\r
+ }\r
+\r
+ // Call onInit when all editors are initialized\r
+ if (s.oninit) {\r
+ l = co = 0;\r
+\r
+ each(el, function(ed) {\r
+ co++;\r
+\r
+ if (!ed.initialized) {\r
+ // Wait for it\r
+ ed.onInit.add(function() {\r
+ l++;\r
+\r
+ // All done\r
+ if (l == co)\r
+ execCallback(s, 'oninit');\r
+ });\r
+ } else\r
+ l++;\r
+\r
+ // All done\r
+ if (l == co)\r
+ execCallback(s, 'oninit'); \r
+ });\r
+ }\r
+ });\r
+ },\r
+\r
+ get : function(id) {\r
+ if (id === undefined)\r
+ return this.editors;\r
+\r
+ return this.editors[id];\r
+ },\r
+\r
+ getInstanceById : function(id) {\r
+ return this.get(id);\r
+ },\r
+\r
+ add : function(editor) {\r
+ var self = this, editors = self.editors;\r
+\r
+ // Add named and index editor instance\r
+ editors[editor.id] = editor;\r
+ editors.push(editor);\r
+\r
+ self._setActive(editor);\r
+ self.onAddEditor.dispatch(self, editor);\r
+\r
+\r
+ return editor;\r
+ },\r
+\r
+ remove : function(editor) {\r
+ var t = this, i, editors = t.editors;\r
+\r
+ // Not in the collection\r
+ if (!editors[editor.id])\r
+ return null;\r
+\r
+ delete editors[editor.id];\r
+\r
+ for (i = 0; i < editors.length; i++) {\r
+ if (editors[i] == editor) {\r
+ editors.splice(i, 1);\r
+ break;\r
+ }\r
+ }\r
+\r
+ // Select another editor since the active one was removed\r
+ if (t.activeEditor == editor)\r
+ t._setActive(editors[0]);\r
+\r
+ editor.destroy();\r
+ t.onRemoveEditor.dispatch(t, editor);\r
+\r
+ return editor;\r
+ },\r
+\r
+ execCommand : function(c, u, v) {\r
+ var t = this, ed = t.get(v), w;\r
+\r
+ // Manager commands\r
+ switch (c) {\r
+ case "mceFocus":\r
+ ed.focus();\r
+ return true;\r
+\r
+ case "mceAddEditor":\r
+ case "mceAddControl":\r
+ if (!t.get(v))\r
+ new tinymce.Editor(v, t.settings).render();\r
+\r
+ return true;\r
+\r
+ case "mceAddFrameControl":\r
+ w = v.window;\r
+\r
+ // Add tinyMCE global instance and tinymce namespace to specified window\r
+ w.tinyMCE = tinyMCE;\r
+ w.tinymce = tinymce;\r
+\r
+ tinymce.DOM.doc = w.document;\r
+ tinymce.DOM.win = w;\r
+\r
+ ed = new tinymce.Editor(v.element_id, v);\r
+ ed.render();\r
+\r
+ // Fix IE memory leaks\r
+ if (tinymce.isIE) {\r
+ function clr() {\r
+ ed.destroy();\r
+ w.detachEvent('onunload', clr);\r
+ w = w.tinyMCE = w.tinymce = null; // IE leak\r
+ };\r
+\r
+ w.attachEvent('onunload', clr);\r
+ }\r
+\r
+ v.page_window = null;\r
+\r
+ return true;\r
+\r
+ case "mceRemoveEditor":\r
+ case "mceRemoveControl":\r
+ if (ed)\r
+ ed.remove();\r
+\r
+ return true;\r
+\r
+ case 'mceToggleEditor':\r
+ if (!ed) {\r
+ t.execCommand('mceAddControl', 0, v);\r
+ return true;\r
+ }\r
+\r
+ if (ed.isHidden())\r
+ ed.show();\r
+ else\r
+ ed.hide();\r
+\r
+ return true;\r
+ }\r
+\r
+ // Run command on active editor\r
+ if (t.activeEditor)\r
+ return t.activeEditor.execCommand(c, u, v);\r
+\r
+ return false;\r
+ },\r
+\r
+ execInstanceCommand : function(id, c, u, v) {\r
+ var ed = this.get(id);\r
+\r
+ if (ed)\r
+ return ed.execCommand(c, u, v);\r
+\r
+ return false;\r
+ },\r
+\r
+ triggerSave : function() {\r
+ each(this.editors, function(e) {\r
+ e.save();\r
+ });\r
+ },\r
+\r
+ addI18n : function(p, o) {\r
+ var lo, i18n = this.i18n;\r
+\r
+ if (!tinymce.is(p, 'string')) {\r
+ each(p, function(o, lc) {\r
+ each(o, function(o, g) {\r
+ each(o, function(o, k) {\r
+ if (g === 'common')\r
+ i18n[lc + '.' + k] = o;\r
+ else\r
+ i18n[lc + '.' + g + '.' + k] = o;\r
+ });\r
+ });\r
+ });\r
+ } else {\r
+ each(o, function(o, k) {\r
+ i18n[p + '.' + k] = o;\r
+ });\r
+ }\r
+ },\r
+\r
+ // Private methods\r
+\r
+ _setActive : function(editor) {\r
+ this.selectedInstance = this.activeEditor = editor;\r
+ }\r
+ });\r
+})(tinymce);\r
+\r
+(function(tinymce) {\r
+ // Shorten these names\r
+ var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,\r
+ Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,\r
+ isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,\r
+ ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,\r
+ inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;\r
+\r
+ tinymce.create('tinymce.Editor', {\r
+ Editor : function(id, s) {\r
+ var t = this;\r
+\r
+ t.id = t.editorId = id;\r
+\r
+ t.execCommands = {};\r
+ t.queryStateCommands = {};\r
+ t.queryValueCommands = {};\r
+\r
+ t.isNotDirty = false;\r
+\r
+ t.plugins = {};\r
+\r
+ // Add events to the editor\r
+ each([\r
+ 'onPreInit',\r
+\r
+ 'onBeforeRenderUI',\r
+\r
+ 'onPostRender',\r
+\r
+ 'onInit',\r
+\r
+ 'onRemove',\r
+\r
+ 'onActivate',\r
+\r
+ 'onDeactivate',\r
+\r
+ 'onClick',\r
+\r
+ 'onEvent',\r
+\r
+ 'onMouseUp',\r
+\r
+ 'onMouseDown',\r
+\r
+ 'onDblClick',\r
+\r
+ 'onKeyDown',\r
+\r
+ 'onKeyUp',\r
+\r
+ 'onKeyPress',\r
+\r
+ 'onContextMenu',\r
+\r
+ 'onSubmit',\r
+\r
+ 'onReset',\r
+\r
+ 'onPaste',\r
+\r
+ 'onPreProcess',\r
+\r
+ 'onPostProcess',\r
+\r
+ 'onBeforeSetContent',\r
+\r
+ 'onBeforeGetContent',\r
+\r
+ 'onSetContent',\r
+\r
+ 'onGetContent',\r
+\r
+ 'onLoadContent',\r
+\r
+ 'onSaveContent',\r
+\r
+ 'onNodeChange',\r
+\r
+ 'onChange',\r
+\r
+ 'onBeforeExecCommand',\r
+\r
+ 'onExecCommand',\r
+\r
+ 'onUndo',\r
+\r
+ 'onRedo',\r
+\r
+ 'onVisualAid',\r
+\r
+ 'onSetProgressState'\r
+ ], function(e) {\r
+ t[e] = new Dispatcher(t);\r
+ });\r
+\r
+ t.settings = s = extend({\r
+ id : id,\r
+ language : 'en',\r
+ docs_language : 'en',\r
+ theme : 'simple',\r
+ skin : 'default',\r
+ delta_width : 0,\r
+ delta_height : 0,\r
+ popup_css : '',\r
+ plugins : '',\r
+ document_base_url : tinymce.documentBaseURL,\r
+ add_form_submit_trigger : 1,\r
+ submit_patch : 1,\r
+ add_unload_trigger : 1,\r
+ convert_urls : 1,\r
+ relative_urls : 1,\r
+ remove_script_host : 1,\r
+ table_inline_editing : 0,\r
+ object_resizing : 1,\r
+ cleanup : 1,\r
+ accessibility_focus : 1,\r
+ custom_shortcuts : 1,\r
+ custom_undo_redo_keyboard_shortcuts : 1,\r
+ custom_undo_redo_restore_selection : 1,\r
+ custom_undo_redo : 1,\r
+ doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll\r
+ visual_table_class : 'mceItemTable',\r
+ visual : 1,\r
+ font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',\r
+ apply_source_formatting : 1,\r
+ directionality : 'ltr',\r
+ forced_root_block : 'p',\r
+ hidden_input : 1,\r
+ padd_empty_editor : 1,\r
+ render_ui : 1,\r
+ init_theme : 1,\r
+ force_p_newlines : 1,\r
+ indentation : '30px',\r
+ keep_styles : 1,\r
+ fix_table_elements : 1,\r
+ inline_styles : 1,\r
+ convert_fonts_to_spans : true,\r
+ indent : 'simple',\r
+ indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',\r
+ indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',\r
+ validate : true,\r
+ entity_encoding : 'named',\r
+ url_converter : t.convertURL,\r
+ url_converter_scope : t,\r
+ ie7_compat : true\r
+ }, s);\r
+\r
+ t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {\r
+ base_uri : tinyMCE.baseURI\r
+ });\r
+\r
+ t.baseURI = tinymce.baseURI;\r
+\r
+ t.contentCSS = [];\r
+\r
+ // Call setup\r
+ t.execCallback('setup', t);\r
+ },\r
+\r
+ render : function(nst) {\r
+ var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;\r
+\r
+ // Page is not loaded yet, wait for it\r
+ if (!Event.domLoaded) {\r
+ Event.add(document, 'init', function() {\r
+ t.render();\r
+ });\r
+ return;\r
+ }\r
+\r
+ tinyMCE.settings = s;\r
+\r
+ // Element not found, then skip initialization\r
+ if (!t.getElement())\r
+ return;\r
+\r
+ // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff \r
+ // here since the browser says it has contentEditable support but there is no visible\r
+ // caret We will remove this check ones Apple implements full contentEditable support\r
+ if (tinymce.isIDevice && !tinymce.isIOS5)\r
+ return;\r
+\r
+ // Add hidden input for non input elements inside form elements\r
+ if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))\r
+ DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);\r
+\r
+ if (tinymce.WindowManager)\r
+ t.windowManager = new tinymce.WindowManager(t);\r
+\r
+ if (s.encoding == 'xml') {\r
+ t.onGetContent.add(function(ed, o) {\r
+ if (o.save)\r
+ o.content = DOM.encode(o.content);\r
+ });\r
+ }\r
+\r
+ if (s.add_form_submit_trigger) {\r
+ t.onSubmit.addToTop(function() {\r
+ if (t.initialized) {\r
+ t.save();\r
+ t.isNotDirty = 1;\r
+ }\r
+ });\r
+ }\r
+\r
+ if (s.add_unload_trigger) {\r
+ t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {\r
+ if (t.initialized && !t.destroyed && !t.isHidden())\r
+ t.save({format : 'raw', no_events : true});\r
+ });\r
+ }\r
+\r
+ tinymce.addUnload(t.destroy, t);\r
+\r
+ if (s.submit_patch) {\r
+ t.onBeforeRenderUI.add(function() {\r
+ var n = t.getElement().form;\r
+\r
+ if (!n)\r
+ return;\r
+\r
+ // Already patched\r
+ if (n._mceOldSubmit)\r
+ return;\r
+\r
+ // Check page uses id="submit" or name="submit" for it's submit button\r
+ if (!n.submit.nodeType && !n.submit.length) {\r
+ t.formElement = n;\r
+ n._mceOldSubmit = n.submit;\r
+ n.submit = function() {\r
+ // Save all instances\r
+ tinymce.triggerSave();\r
+ t.isNotDirty = 1;\r
+\r
+ return t.formElement._mceOldSubmit(t.formElement);\r
+ };\r
+ }\r
+\r
+ n = null;\r
+ });\r
+ }\r
+\r
+ // Load scripts\r
+ function loadScripts() {\r
+ if (s.language && s.language_load !== false)\r
+ sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');\r
+\r
+ if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])\r
+ ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');\r
+\r
+ each(explode(s.plugins), function(p) {\r
+ if (p &&!PluginManager.urls[p]) {\r
+ if (p.charAt(0) == '-') {\r
+ p = p.substr(1, p.length);\r
+ var dependencies = PluginManager.dependencies(p);\r
+ each(dependencies, function(dep) {\r
+ var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};\r
+ var dep = PluginManager.createUrl(defaultSettings, dep);\r
+ PluginManager.load(dep.resource, dep);\r
+ \r
+ });\r
+ } else {\r
+ // Skip safari plugin, since it is removed as of 3.3b1\r
+ if (p == 'safari') {\r
+ return;\r
+ }\r
+ PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});\r
+ }\r
+ }\r
+ });\r
+\r
+ // Init when que is loaded\r
+ sl.loadQueue(function() {\r
+ if (!t.removed)\r
+ t.init();\r
+ });\r
+ };\r
+\r
+ loadScripts();\r
+ },\r
+\r
+ init : function() {\r
+ var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];\r
+\r
+ tinymce.add(t);\r
+\r
+ s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));\r
+\r
+ if (s.theme) {\r
+ s.theme = s.theme.replace(/-/, '');\r
+ o = ThemeManager.get(s.theme);\r
+ t.theme = new o();\r
+\r
+ if (t.theme.init && s.init_theme)\r
+ t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));\r
+ }\r
+ function initPlugin(p) {\r
+ var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;\r
+ if (c && tinymce.inArray(initializedPlugins,p) === -1) {\r
+ each(PluginManager.dependencies(p), function(dep){\r
+ initPlugin(dep);\r
+ });\r
+ po = new c(t, u);\r
+\r
+ t.plugins[p] = po;\r
+\r
+ if (po.init) {\r
+ po.init(t, u);\r
+ initializedPlugins.push(p);\r
+ }\r
+ }\r
+ }\r
+ \r
+ // Create all plugins\r
+ each(explode(s.plugins.replace(/\-/g, '')), initPlugin);\r
+\r
+ // Setup popup CSS path(s)\r
+ if (s.popup_css !== false) {\r
+ if (s.popup_css)\r
+ s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);\r
+ else\r
+ s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");\r
+ }\r
+\r
+ if (s.popup_css_add)\r
+ s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);\r
+\r
+ t.controlManager = new tinymce.ControlManager(t);\r
+\r
+ if (s.custom_undo_redo) {\r
+ t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {\r
+ if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))\r
+ t.undoManager.beforeChange();\r
+ });\r
+\r
+ t.onExecCommand.add(function(ed, cmd, ui, val, a) {\r
+ if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))\r
+ t.undoManager.add();\r
+ });\r
+ }\r
+\r
+ t.onExecCommand.add(function(ed, c) {\r
+ // Don't refresh the select lists until caret move\r
+ if (!/^(FontName|FontSize)$/.test(c))\r
+ t.nodeChanged();\r
+ });\r
+\r
+ // Remove ghost selections on images and tables in Gecko\r
+ if (isGecko) {\r
+ function repaint(a, o) {\r
+ if (!o || !o.initial)\r
+ t.execCommand('mceRepaint');\r
+ };\r
+\r
+ t.onUndo.add(repaint);\r
+ t.onRedo.add(repaint);\r
+ t.onSetContent.add(repaint);\r
+ }\r
+\r
+ // Enables users to override the control factory\r
+ t.onBeforeRenderUI.dispatch(t, t.controlManager);\r
+\r
+ // Measure box\r
+ if (s.render_ui) {\r
+ w = s.width || e.style.width || e.offsetWidth;\r
+ h = s.height || e.style.height || e.offsetHeight;\r
+ t.orgDisplay = e.style.display;\r
+ re = /^[0-9\.]+(|px)$/i;\r
+\r
+ if (re.test('' + w))\r
+ w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);\r
+\r
+ if (re.test('' + h))\r
+ h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);\r
+\r
+ // Render UI\r
+ o = t.theme.renderUI({\r
+ targetNode : e,\r
+ width : w,\r
+ height : h,\r
+ deltaWidth : s.delta_width,\r
+ deltaHeight : s.delta_height\r
+ });\r
+\r
+ t.editorContainer = o.editorContainer;\r
+ }\r
+\r
+\r
+ // User specified a document.domain value\r
+ if (document.domain && location.hostname != document.domain)\r
+ tinymce.relaxedDomain = document.domain;\r
+\r
+ // Resize editor\r
+ DOM.setStyles(o.sizeContainer || o.editorContainer, {\r
+ width : w,\r
+ height : h\r
+ });\r
+\r
+ // Load specified content CSS last\r
+ if (s.content_css) {\r
+ tinymce.each(explode(s.content_css), function(u) {\r
+ t.contentCSS.push(t.documentBaseURI.toAbsolute(u));\r
+ });\r
+ }\r
+\r
+ h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');\r
+ if (h < 100)\r
+ h = 100;\r
+\r
+ t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';\r
+\r
+ // We only need to override paths if we have to\r
+ // IE has a bug where it remove site absolute urls to relative ones if this is specified\r
+ if (s.document_base_url != tinymce.documentBaseURL)\r
+ t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';\r
+\r
+ // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.\r
+ if (s.ie7_compat)\r
+ t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';\r
+ else\r
+ t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';\r
+\r
+ t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';\r
+\r
+ bi = s.body_id || 'tinymce';\r
+ if (bi.indexOf('=') != -1) {\r
+ bi = t.getParam('body_id', '', 'hash');\r
+ bi = bi[t.id] || bi;\r
+ }\r
+\r
+ bc = s.body_class || '';\r
+ if (bc.indexOf('=') != -1) {\r
+ bc = t.getParam('body_class', '', 'hash');\r
+ bc = bc[t.id] || '';\r
+ }\r
+\r
+ t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"><br></body></html>';\r
+\r
+ // Domain relaxing enabled, then set document domain\r
+ if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {\r
+ // We need to write the contents here in IE since multiple writes messes up refresh button and back button\r
+ 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();})()'; \r
+ }\r
+\r
+ // Create iframe\r
+ // TODO: ACC add the appropriate description on this.\r
+ n = DOM.add(o.iframeContainer, 'iframe', { \r
+ id : t.id + "_ifr",\r
+ src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7\r
+ frameBorder : '0',\r
+ allowTransparency : "true",\r
+ title : s.aria_label,\r
+ style : {\r
+ width : '100%',\r
+ height : h,\r
+ display : 'block' // Important for Gecko to render the iframe correctly\r
+ }\r
+ });\r
+\r
+ t.contentAreaContainer = o.iframeContainer;\r
+ DOM.get(o.editorContainer).style.display = t.orgDisplay;\r
+ DOM.get(t.id).style.display = 'none';\r
+ DOM.setAttrib(t.id, 'aria-hidden', true);\r
+\r
+ if (!tinymce.relaxedDomain || !u)\r
+ t.setupIframe();\r
+\r
+ e = n = o = null; // Cleanup\r
+ },\r
+\r
+ setupIframe : function() {\r
+ var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;\r
+\r
+ // Setup iframe body\r
+ if (!isIE || !tinymce.relaxedDomain) {\r
+ // Fix for a focus bug in FF 3.x where the body element\r
+ // wouldn't get proper focus if the user clicked on the HTML element\r
+ if (isGecko && !Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4\r
+ t.onMouseDown.add(function(ed, e) {\r
+ if (e.target.nodeName === "HTML") {\r
+ var body = t.getBody();\r
+\r
+ // Blur the body it's focused but not correctly focused\r
+ body.blur();\r
+\r
+ // Refocus the body after a little while\r
+ setTimeout(function() {\r
+ body.focus();\r
+ }, 0);\r
+ }\r
+ });\r
+ }\r
+\r
+ d.open();\r
+ d.write(t.iframeHTML);\r
+ d.close();\r
+\r
+ if (tinymce.relaxedDomain)\r
+ d.domain = tinymce.relaxedDomain;\r
+ }\r
+\r
+ // It will not steal focus while setting contentEditable\r
+ b = t.getBody();\r
+ b.disabled = true;\r
+\r
+ if (!s.readonly)\r
+ b.contentEditable = true;\r
+\r
+ b.disabled = false;\r
+\r
+ t.schema = new tinymce.html.Schema(s);\r
+\r
+ t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {\r
+ keep_values : true,\r
+ url_converter : t.convertURL,\r
+ url_converter_scope : t,\r
+ hex_colors : s.force_hex_style_colors,\r
+ class_filter : s.class_filter,\r
+ update_styles : 1,\r
+ fix_ie_paragraphs : 1,\r
+ schema : t.schema\r
+ });\r
+\r
+ t.parser = new tinymce.html.DomParser(s, t.schema);\r
+\r
+ // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.\r
+ if (!t.settings.allow_html_in_named_anchor) {\r
+ t.parser.addAttributeFilter('name', function(nodes, name) {\r
+ var i = nodes.length, sibling, prevSibling, parent, node;\r
+ \r
+ while (i--) {\r
+ node = nodes[i];\r
+ if (node.name === 'a' && node.firstChild) {\r
+ parent = node.parent;\r
+ \r
+ // Move children after current node\r
+ sibling = node.lastChild;\r
+ do {\r
+ prevSibling = sibling.prev;\r
+ parent.insert(sibling, node);\r
+ sibling = prevSibling;\r
+ } while (sibling);\r
+ }\r
+ }\r
+ });\r
+ }\r
+\r
+ // Convert src and href into data-mce-src, data-mce-href and data-mce-style\r
+ t.parser.addAttributeFilter('src,href,style', function(nodes, name) {\r
+ var i = nodes.length, node, dom = t.dom, value, internalName;\r
+\r
+ while (i--) {\r
+ node = nodes[i];\r
+ value = node.attr(name);\r
+ internalName = 'data-mce-' + name;\r
+\r
+ // Add internal attribute if we need to we don't on a refresh of the document\r
+ if (!node.attributes.map[internalName]) { \r
+ if (name === "style")\r
+ node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));\r
+ else\r
+ node.attr(internalName, t.convertURL(value, name, node.name));\r
+ }\r
+ }\r
+ });\r
+\r
+ // Keep scripts from executing\r
+ t.parser.addNodeFilter('script', function(nodes, name) {\r
+ var i = nodes.length;\r
+\r
+ while (i--)\r
+ nodes[i].attr('type', 'mce-text/javascript');\r
+ });\r
+\r
+ t.parser.addNodeFilter('#cdata', function(nodes, name) {\r
+ var i = nodes.length, node;\r
+\r
+ while (i--) {\r
+ node = nodes[i];\r
+ node.type = 8;\r
+ node.name = '#comment';\r
+ node.value = '[CDATA[' + node.value + ']]';\r
+ }\r
+ });\r
+\r
+ t.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {\r
+ var i = nodes.length, node, nonEmptyElements = t.schema.getNonEmptyElements();\r
+\r
+ while (i--) {\r
+ node = nodes[i];\r
+\r
+ if (node.isEmpty(nonEmptyElements))\r
+ node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;\r
+ }\r
+ });\r
+\r
+ t.serializer = new tinymce.dom.Serializer(s, t.dom, t.schema);\r
+\r
+ t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);\r
+\r
+ t.formatter = new tinymce.Formatter(this);\r
+\r
+ // Register default formats\r
+ t.formatter.register({\r
+ alignleft : [\r
+ {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},\r
+ {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}\r
+ ],\r
+\r
+ aligncenter : [\r
+ {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},\r
+ {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},\r
+ {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}\r
+ ],\r
+\r
+ alignright : [\r
+ {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},\r
+ {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}\r
+ ],\r
+\r
+ alignfull : [\r
+ {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}\r
+ ],\r
+\r
+ bold : [\r
+ {inline : 'strong', remove : 'all'},\r
+ {inline : 'span', styles : {fontWeight : 'bold'}},\r
+ {inline : 'b', remove : 'all'}\r
+ ],\r
+\r
+ italic : [\r
+ {inline : 'em', remove : 'all'},\r
+ {inline : 'span', styles : {fontStyle : 'italic'}},\r
+ {inline : 'i', remove : 'all'}\r
+ ],\r
+\r
+ underline : [\r
+ {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},\r
+ {inline : 'u', remove : 'all'}\r
+ ],\r
+\r
+ strikethrough : [\r
+ {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},\r
+ {inline : 'strike', remove : 'all'}\r
+ ],\r
+\r
+ forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},\r
+ hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},\r
+ fontname : {inline : 'span', styles : {fontFamily : '%value'}},\r
+ fontsize : {inline : 'span', styles : {fontSize : '%value'}},\r
+ fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},\r
+ blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},\r
+ subscript : {inline : 'sub'},\r
+ superscript : {inline : 'sup'},\r
+\r
+ link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true,\r
+ onmatch : function(node) {\r
+ return true;\r
+ },\r
+\r
+ onformat : function(elm, fmt, vars) {\r
+ each(vars, function(value, key) {\r
+ t.dom.setAttrib(elm, key, value);\r
+ });\r
+ }\r
+ },\r
+\r
+ removeformat : [\r
+ {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},\r
+ {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},\r
+ {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}\r
+ ]\r
+ });\r
+\r
+ // Register default block formats\r
+ each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {\r
+ t.formatter.register(name, {block : name, remove : 'all'});\r
+ });\r
+\r
+ // Register user defined formats\r
+ t.formatter.register(t.settings.formats);\r
+\r
+ t.undoManager = new tinymce.UndoManager(t);\r
+\r
+ // Pass through\r
+ t.undoManager.onAdd.add(function(um, l) {\r
+ if (um.hasUndo())\r
+ return t.onChange.dispatch(t, l, um);\r
+ });\r
+\r
+ t.undoManager.onUndo.add(function(um, l) {\r
+ return t.onUndo.dispatch(t, l, um);\r
+ });\r
+\r
+ t.undoManager.onRedo.add(function(um, l) {\r
+ return t.onRedo.dispatch(t, l, um);\r
+ });\r
+\r
+ t.forceBlocks = new tinymce.ForceBlocks(t, {\r
+ forced_root_block : s.forced_root_block\r
+ });\r
+\r
+ t.editorCommands = new tinymce.EditorCommands(t);\r
+\r
+ // Pass through\r
+ t.serializer.onPreProcess.add(function(se, o) {\r
+ return t.onPreProcess.dispatch(t, o, se);\r
+ });\r
+\r
+ t.serializer.onPostProcess.add(function(se, o) {\r
+ return t.onPostProcess.dispatch(t, o, se);\r
+ });\r
+\r
+ t.onPreInit.dispatch(t);\r
+\r
+ if (!s.gecko_spellcheck)\r
+ t.getBody().spellcheck = 0;\r
+\r
+ if (!s.readonly)\r
+ t._addEvents();\r
+\r
+ t.controlManager.onPostRender.dispatch(t, t.controlManager);\r
+ t.onPostRender.dispatch(t);\r
+\r
+ t.quirks = new tinymce.util.Quirks(this);\r
+\r
+ if (s.directionality)\r
+ t.getBody().dir = s.directionality;\r
+\r
+ if (s.nowrap)\r
+ t.getBody().style.whiteSpace = "nowrap";\r
+\r
+ if (s.handle_node_change_callback) {\r
+ t.onNodeChange.add(function(ed, cm, n) {\r
+ t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());\r
+ });\r
+ }\r
+\r
+ if (s.save_callback) {\r
+ t.onSaveContent.add(function(ed, o) {\r
+ var h = t.execCallback('save_callback', t.id, o.content, t.getBody());\r
+\r
+ if (h)\r
+ o.content = h;\r
+ });\r
+ }\r
+\r
+ if (s.onchange_callback) {\r
+ t.onChange.add(function(ed, l) {\r
+ t.execCallback('onchange_callback', t, l);\r
+ });\r
+ }\r
+\r
+ if (s.protect) {\r
+ t.onBeforeSetContent.add(function(ed, o) {\r
+ if (s.protect) {\r
+ each(s.protect, function(pattern) {\r
+ o.content = o.content.replace(pattern, function(str) {\r
+ return '<!--mce:protected ' + escape(str) + '-->';\r
+ });\r
+ });\r
+ }\r
+ });\r
+ }\r
+\r
+ if (s.convert_newlines_to_brs) {\r
+ t.onBeforeSetContent.add(function(ed, o) {\r
+ if (o.initial)\r
+ o.content = o.content.replace(/\r?\n/g, '<br />');\r
+ });\r
+ }\r
+\r
+ if (s.preformatted) {\r
+ t.onPostProcess.add(function(ed, o) {\r
+ o.content = o.content.replace(/^\s*<pre.*?>/, '');\r
+ o.content = o.content.replace(/<\/pre>\s*$/, '');\r
+\r
+ if (o.set)\r
+ o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';\r
+ });\r
+ }\r
+\r
+ if (s.verify_css_classes) {\r
+ t.serializer.attribValueFilter = function(n, v) {\r
+ var s, cl;\r
+\r
+ if (n == 'class') {\r
+ // Build regexp for classes\r
+ if (!t.classesRE) {\r
+ cl = t.dom.getClasses();\r
+\r
+ if (cl.length > 0) {\r
+ s = '';\r
+\r
+ each (cl, function(o) {\r
+ s += (s ? '|' : '') + o['class'];\r
+ });\r
+\r
+ t.classesRE = new RegExp('(' + s + ')', 'gi');\r
+ }\r
+ }\r
+\r
+ return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';\r
+ }\r
+\r
+ return v;\r
+ };\r
+ }\r
+\r
+ if (s.cleanup_callback) {\r
+ t.onBeforeSetContent.add(function(ed, o) {\r
+ o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);\r
+ });\r
+\r
+ t.onPreProcess.add(function(ed, o) {\r
+ if (o.set)\r
+ t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);\r
+\r
+ if (o.get)\r
+ t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);\r
+ });\r
+\r
+ t.onPostProcess.add(function(ed, o) {\r
+ if (o.set)\r
+ o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);\r
+\r
+ if (o.get) \r
+ o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);\r
+ });\r
+ }\r
+\r
+ if (s.save_callback) {\r
+ t.onGetContent.add(function(ed, o) {\r
+ if (o.save)\r
+ o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());\r
+ });\r
+ }\r
+\r
+ if (s.handle_event_callback) {\r
+ t.onEvent.add(function(ed, e, o) {\r
+ if (t.execCallback('handle_event_callback', e, ed, o) === false)\r
+ Event.cancel(e);\r
+ });\r
+ }\r
+\r
+ // Add visual aids when new contents is added\r
+ t.onSetContent.add(function() {\r
+ t.addVisual(t.getBody());\r
+ });\r
+\r
+ // Remove empty contents\r
+ if (s.padd_empty_editor) {\r
+ t.onPostProcess.add(function(ed, o) {\r
+ o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');\r
+ });\r
+ }\r
+\r
+ if (isGecko) {\r
+ // Fix gecko link bug, when a link is placed at the end of block elements there is\r
+ // no way to move the caret behind the link. This fix adds a bogus br element after the link\r
+ function fixLinks(ed, o) {\r
+ each(ed.dom.select('a'), function(n) {\r
+ var pn = n.parentNode;\r
+\r
+ if (ed.dom.isBlock(pn) && pn.lastChild === n)\r
+ ed.dom.add(pn, 'br', {'data-mce-bogus' : 1});\r
+ });\r
+ };\r
+\r
+ t.onExecCommand.add(function(ed, cmd) {\r
+ if (cmd === 'CreateLink')\r
+ fixLinks(ed);\r
+ });\r
+\r
+ t.onSetContent.add(t.selection.onSetContent.add(fixLinks));\r
+ }\r
+\r
+ t.load({initial : true, format : 'html'});\r
+ t.startContent = t.getContent({format : 'raw'});\r
+ t.undoManager.add();\r
+ t.initialized = true;\r
+\r
+ t.onInit.dispatch(t);\r
+ t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());\r
+ t.execCallback('init_instance_callback', t);\r
+ t.focus(true);\r
+ t.nodeChanged({initial : 1});\r
+\r
+ // Load specified content CSS last\r
+ each(t.contentCSS, function(u) {\r
+ t.dom.loadCSS(u);\r
+ });\r
+\r
+ // Handle auto focus\r
+ if (s.auto_focus) {\r
+ setTimeout(function () {\r
+ var ed = tinymce.get(s.auto_focus);\r
+\r
+ ed.selection.select(ed.getBody(), 1);\r
+ ed.selection.collapse(1);\r
+ ed.getBody().focus();\r
+ ed.getWin().focus();\r
+ }, 100);\r
+ }\r
+\r
+ e = null;\r
+ },\r
+\r
+\r
+ focus : function(sf) {\r
+ var oed, t = this, selection = t.selection, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();\r
+\r
+ if (!sf) {\r
+ // Get selected control element\r
+ ieRng = selection.getRng();\r
+ if (ieRng.item) {\r
+ controlElm = ieRng.item(0);\r
+ }\r
+\r
+ t._refreshContentEditable();\r
+ selection.normalize();\r
+\r
+ // Is not content editable\r
+ if (!ce)\r
+ t.getWin().focus();\r
+\r
+ // Focus the body as well since it's contentEditable\r
+ if (tinymce.isGecko) {\r
+ t.getBody().focus();\r
+ }\r
+\r
+ // Restore selected control element\r
+ // This is needed when for example an image is selected within a\r
+ // layer a call to focus will then remove the control selection\r
+ if (controlElm && controlElm.ownerDocument == doc) {\r
+ ieRng = doc.body.createControlRange();\r
+ ieRng.addElement(controlElm);\r
+ ieRng.select();\r
+ }\r
+\r
+ }\r
+\r
+ if (tinymce.activeEditor != t) {\r
+ if ((oed = tinymce.activeEditor) != null)\r
+ oed.onDeactivate.dispatch(oed, t);\r
+\r
+ t.onActivate.dispatch(t, oed);\r
+ }\r
+\r
+ tinymce._setActive(t);\r
+ },\r
+\r
+ execCallback : function(n) {\r
+ var t = this, f = t.settings[n], s;\r
+\r
+ if (!f)\r
+ return;\r
+\r
+ // Look through lookup\r
+ if (t.callbackLookup && (s = t.callbackLookup[n])) {\r
+ f = s.func;\r
+ s = s.scope;\r
+ }\r
+\r
+ if (is(f, 'string')) {\r
+ s = f.replace(/\.\w+$/, '');\r
+ s = s ? tinymce.resolve(s) : 0;\r
+ f = tinymce.resolve(f);\r
+ t.callbackLookup = t.callbackLookup || {};\r
+ t.callbackLookup[n] = {func : f, scope : s};\r
+ }\r
+\r
+ return f.apply(s || t, Array.prototype.slice.call(arguments, 1));\r
+ },\r
+\r
+ translate : function(s) {\r
+ var c = this.settings.language || 'en', i18n = tinymce.i18n;\r
+\r
+ if (!s)\r
+ return '';\r
+\r
+ return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {\r
+ return i18n[c + '.' + b] || '{#' + b + '}';\r
+ });\r
+ },\r
+\r
+ getLang : function(n, dv) {\r
+ return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');\r
+ },\r
+\r
+ getParam : function(n, dv, ty) {\r
+ var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;\r
+\r
+ if (ty === 'hash') {\r
+ o = {};\r
+\r
+ if (is(v, 'string')) {\r
+ each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {\r
+ v = v.split('=');\r
+\r
+ if (v.length > 1)\r
+ o[tr(v[0])] = tr(v[1]);\r
+ else\r
+ o[tr(v[0])] = tr(v);\r
+ });\r
+ } else\r
+ o = v;\r
+\r
+ return o;\r
+ }\r
+\r
+ return v;\r
+ },\r
+\r
+ nodeChanged : function(o) {\r
+ var t = this, s = t.selection, n = s.getStart() || t.getBody();\r
+\r
+ // Fix for bug #1896577 it seems that this can not be fired while the editor is loading\r
+ if (t.initialized) {\r
+ o = o || {};\r
+ n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state\r
+\r
+ // Get parents and add them to object\r
+ o.parents = [];\r
+ t.dom.getParent(n, function(node) {\r
+ if (node.nodeName == 'BODY')\r
+ return true;\r
+\r
+ o.parents.push(node);\r
+ });\r
+\r
+ t.onNodeChange.dispatch(\r
+ t,\r
+ o ? o.controlManager || t.controlManager : t.controlManager,\r
+ n,\r
+ s.isCollapsed(),\r
+ o\r
+ );\r
+ }\r
+ },\r
+\r
+ addButton : function(n, s) {\r
+ var t = this;\r
+\r
+ t.buttons = t.buttons || {};\r
+ t.buttons[n] = s;\r
+ },\r
+\r
+ addCommand : function(name, callback, scope) {\r
+ this.execCommands[name] = {func : callback, scope : scope || this};\r
+ },\r
+\r
+ addQueryStateHandler : function(name, callback, scope) {\r
+ this.queryStateCommands[name] = {func : callback, scope : scope || this};\r
+ },\r
+\r
+ addQueryValueHandler : function(name, callback, scope) {\r
+ this.queryValueCommands[name] = {func : callback, scope : scope || this};\r
+ },\r
+\r
+ addShortcut : function(pa, desc, cmd_func, sc) {\r
+ var t = this, c;\r
+\r
+ if (!t.settings.custom_shortcuts)\r
+ return false;\r
+\r
+ t.shortcuts = t.shortcuts || {};\r
+\r
+ if (is(cmd_func, 'string')) {\r
+ c = cmd_func;\r
+\r
+ cmd_func = function() {\r
+ t.execCommand(c, false, null);\r
+ };\r
+ }\r
+\r
+ if (is(cmd_func, 'object')) {\r
+ c = cmd_func;\r
+\r
+ cmd_func = function() {\r
+ t.execCommand(c[0], c[1], c[2]);\r
+ };\r
+ }\r
+\r
+ each(explode(pa), function(pa) {\r
+ var o = {\r
+ func : cmd_func,\r
+ scope : sc || this,\r
+ desc : desc,\r
+ alt : false,\r
+ ctrl : false,\r
+ shift : false\r
+ };\r
+\r
+ each(explode(pa, '+'), function(v) {\r
+ switch (v) {\r
+ case 'alt':\r
+ case 'ctrl':\r
+ case 'shift':\r
+ o[v] = true;\r
+ break;\r
+\r
+ default:\r
+ o.charCode = v.charCodeAt(0);\r
+ o.keyCode = v.toUpperCase().charCodeAt(0);\r
+ }\r
+ });\r
+\r
+ t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;\r
+ });\r
+\r
+ return true;\r
+ },\r
+\r
+ execCommand : function(cmd, ui, val, a) {\r
+ var t = this, s = 0, o, st;\r
+\r
+ if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))\r
+ t.focus();\r
+\r
+ o = {};\r
+ t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);\r
+ if (o.terminate)\r
+ return false;\r
+\r
+ // Command callback\r
+ if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {\r
+ t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
+ return true;\r
+ }\r
+\r
+ // Registred commands\r
+ if (o = t.execCommands[cmd]) {\r
+ st = o.func.call(o.scope, ui, val);\r
+\r
+ // Fall through on true\r
+ if (st !== true) {\r
+ t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
+ return st;\r
+ }\r
+ }\r
+\r
+ // Plugin commands\r
+ each(t.plugins, function(p) {\r
+ if (p.execCommand && p.execCommand(cmd, ui, val)) {\r
+ t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
+ s = 1;\r
+ return false;\r
+ }\r
+ });\r
+\r
+ if (s)\r
+ return true;\r
+\r
+ // Theme commands\r
+ if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {\r
+ t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
+ return true;\r
+ }\r
+\r
+ // Editor commands\r
+ if (t.editorCommands.execCommand(cmd, ui, val)) {\r
+ t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
+ return true;\r
+ }\r
+\r
+ // Browser commands\r
+ t.getDoc().execCommand(cmd, ui, val);\r
+ t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
+ },\r
+\r
+ queryCommandState : function(cmd) {\r
+ var t = this, o, s;\r
+\r
+ // Is hidden then return undefined\r
+ if (t._isHidden())\r
+ return;\r
+\r
+ // Registred commands\r
+ if (o = t.queryStateCommands[cmd]) {\r
+ s = o.func.call(o.scope);\r
+\r
+ // Fall though on true\r
+ if (s !== true)\r
+ return s;\r
+ }\r
+\r
+ // Registred commands\r
+ o = t.editorCommands.queryCommandState(cmd);\r
+ if (o !== -1)\r
+ return o;\r
+\r
+ // Browser commands\r
+ try {\r
+ return this.getDoc().queryCommandState(cmd);\r
+ } catch (ex) {\r
+ // Fails sometimes see bug: 1896577\r
+ }\r
+ },\r
+\r
+ queryCommandValue : function(c) {\r
+ var t = this, o, s;\r
+\r
+ // Is hidden then return undefined\r
+ if (t._isHidden())\r
+ return;\r
+\r
+ // Registred commands\r
+ if (o = t.queryValueCommands[c]) {\r
+ s = o.func.call(o.scope);\r
+\r
+ // Fall though on true\r
+ if (s !== true)\r
+ return s;\r
+ }\r
+\r
+ // Registred commands\r
+ o = t.editorCommands.queryCommandValue(c);\r
+ if (is(o))\r
+ return o;\r
+\r
+ // Browser commands\r
+ try {\r
+ return this.getDoc().queryCommandValue(c);\r
+ } catch (ex) {\r
+ // Fails sometimes see bug: 1896577\r
+ }\r
+ },\r
+\r
+ show : function() {\r
+ var t = this;\r
+\r
+ DOM.show(t.getContainer());\r
+ DOM.hide(t.id);\r
+ t.load();\r
+ },\r
+\r
+ hide : function() {\r
+ var t = this, d = t.getDoc();\r
+\r
+ // Fixed bug where IE has a blinking cursor left from the editor\r
+ if (isIE && d)\r
+ d.execCommand('SelectAll');\r
+\r
+ // We must save before we hide so Safari doesn't crash\r
+ t.save();\r
+ DOM.hide(t.getContainer());\r
+ DOM.setStyle(t.id, 'display', t.orgDisplay);\r
+ },\r
+\r
+ isHidden : function() {\r
+ return !DOM.isHidden(this.id);\r
+ },\r
+\r
+ setProgressState : function(b, ti, o) {\r
+ this.onSetProgressState.dispatch(this, b, ti, o);\r
+\r
+ return b;\r
+ },\r
+\r
+ load : function(o) {\r
+ var t = this, e = t.getElement(), h;\r
+\r
+ if (e) {\r
+ o = o || {};\r
+ o.load = true;\r
+\r
+ // Double encode existing entities in the value\r
+ h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);\r
+ o.element = e;\r
+\r
+ if (!o.no_events)\r
+ t.onLoadContent.dispatch(t, o);\r
+\r
+ o.element = e = null;\r
+\r
+ return h;\r
+ }\r
+ },\r
+\r
+ save : function(o) {\r
+ var t = this, e = t.getElement(), h, f;\r
+\r
+ if (!e || !t.initialized)\r
+ return;\r
+\r
+ o = o || {};\r
+ o.save = true;\r
+\r
+ // Add undo level will trigger onchange event\r
+ if (!o.no_events) {\r
+ t.undoManager.typing = false;\r
+ t.undoManager.add();\r
+ }\r
+\r
+ o.element = e;\r
+ h = o.content = t.getContent(o);\r
+\r
+ if (!o.no_events)\r
+ t.onSaveContent.dispatch(t, o);\r
+\r
+ h = o.content;\r
+\r
+ if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {\r
+ e.innerHTML = h;\r
+\r
+ // Update hidden form element\r
+ if (f = DOM.getParent(t.id, 'form')) {\r
+ each(f.elements, function(e) {\r
+ if (e.name == t.id) {\r
+ e.value = h;\r
+ return false;\r
+ }\r
+ });\r
+ }\r
+ } else\r
+ e.value = h;\r
+\r
+ o.element = e = null;\r
+\r
+ return h;\r
+ },\r
+\r
+ setContent : function(content, args) {\r
+ var self = this, rootNode, body = self.getBody(), forcedRootBlockName;\r
+\r
+ // Setup args object\r
+ args = args || {};\r
+ args.format = args.format || 'html';\r
+ args.set = true;\r
+ args.content = content;\r
+\r
+ // Do preprocessing\r
+ if (!args.no_events)\r
+ self.onBeforeSetContent.dispatch(self, args);\r
+\r
+ content = args.content;\r
+\r
+ // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content\r
+ // It will also be impossible to place the caret in the editor unless there is a BR element present\r
+ if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {\r
+ forcedRootBlockName = self.settings.forced_root_block;\r
+ if (forcedRootBlockName)\r
+ content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';\r
+ else\r
+ content = '<br data-mce-bogus="1">';\r
+\r
+ body.innerHTML = content;\r
+ self.selection.select(body, true);\r
+ self.selection.collapse(true);\r
+ return;\r
+ }\r
+\r
+ // Parse and serialize the html\r
+ if (args.format !== 'raw') {\r
+ content = new tinymce.html.Serializer({}, self.schema).serialize(\r
+ self.parser.parse(content)\r
+ );\r
+ }\r
+\r
+ // Set the new cleaned contents to the editor\r
+ args.content = tinymce.trim(content);\r
+ self.dom.setHTML(body, args.content);\r
+\r
+ // Do post processing\r
+ if (!args.no_events)\r
+ self.onSetContent.dispatch(self, args);\r
+\r
+ self.selection.normalize();\r
+\r
+ return args.content;\r
+ },\r
+\r
+ getContent : function(args) {\r
+ var self = this, content;\r
+\r
+ // Setup args object\r
+ args = args || {};\r
+ args.format = args.format || 'html';\r
+ args.get = true;\r
+\r
+ // Do preprocessing\r
+ if (!args.no_events)\r
+ self.onBeforeGetContent.dispatch(self, args);\r
+\r
+ // Get raw contents or by default the cleaned contents\r
+ if (args.format == 'raw')\r
+ content = self.getBody().innerHTML;\r
+ else\r
+ content = self.serializer.serialize(self.getBody(), args);\r
+\r
+ args.content = tinymce.trim(content);\r
+\r
+ // Do post processing\r
+ if (!args.no_events)\r
+ self.onGetContent.dispatch(self, args);\r
+\r
+ return args.content;\r
+ },\r
+\r
+ isDirty : function() {\r
+ var self = this;\r
+\r
+ return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;\r
+ },\r
+\r
+ getContainer : function() {\r
+ var t = this;\r
+\r
+ if (!t.container)\r
+ t.container = DOM.get(t.editorContainer || t.id + '_parent');\r
+\r
+ return t.container;\r
+ },\r
+\r
+ getContentAreaContainer : function() {\r
+ return this.contentAreaContainer;\r
+ },\r
+\r
+ getElement : function() {\r
+ return DOM.get(this.settings.content_element || this.id);\r
+ },\r
+\r
+ getWin : function() {\r
+ var t = this, e;\r
+\r
+ if (!t.contentWindow) {\r
+ e = DOM.get(t.id + "_ifr");\r
+\r
+ if (e)\r
+ t.contentWindow = e.contentWindow;\r
+ }\r
+\r
+ return t.contentWindow;\r
+ },\r
+\r
+ getDoc : function() {\r
+ var t = this, w;\r
+\r
+ if (!t.contentDocument) {\r
+ w = t.getWin();\r
+\r
+ if (w)\r
+ t.contentDocument = w.document;\r
+ }\r
+\r
+ return t.contentDocument;\r
+ },\r
+\r
+ getBody : function() {\r
+ return this.bodyElement || this.getDoc().body;\r
+ },\r
+\r
+ convertURL : function(u, n, e) {\r
+ var t = this, s = t.settings;\r
+\r
+ // Use callback instead\r
+ if (s.urlconverter_callback)\r
+ return t.execCallback('urlconverter_callback', u, e, true, n);\r
+\r
+ // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs\r
+ if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)\r
+ return u;\r
+\r
+ // Convert to relative\r
+ if (s.relative_urls)\r
+ return t.documentBaseURI.toRelative(u);\r
+\r
+ // Convert to absolute\r
+ u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);\r
+\r
+ return u;\r
+ },\r
+\r
+ addVisual : function(e) {\r
+ var t = this, s = t.settings;\r
+\r
+ e = e || t.getBody();\r
+\r
+ if (!is(t.hasVisual))\r
+ t.hasVisual = s.visual;\r
+\r
+ each(t.dom.select('table,a', e), function(e) {\r
+ var v;\r
+\r
+ switch (e.nodeName) {\r
+ case 'TABLE':\r
+ v = t.dom.getAttrib(e, 'border');\r
+\r
+ if (!v || v == '0') {\r
+ if (t.hasVisual)\r
+ t.dom.addClass(e, s.visual_table_class);\r
+ else\r
+ t.dom.removeClass(e, s.visual_table_class);\r
+ }\r
+\r
+ return;\r
+\r
+ case 'A':\r
+ v = t.dom.getAttrib(e, 'name');\r
+\r
+ if (v) {\r
+ if (t.hasVisual)\r
+ t.dom.addClass(e, 'mceItemAnchor');\r
+ else\r
+ t.dom.removeClass(e, 'mceItemAnchor');\r
+ }\r
+\r
+ return;\r
+ }\r
+ });\r
+\r
+ t.onVisualAid.dispatch(t, e, t.hasVisual);\r
+ },\r
+\r
+ remove : function() {\r
+ var t = this, e = t.getContainer();\r
+\r
+ t.removed = 1; // Cancels post remove event execution\r
+ t.hide();\r
+\r
+ t.execCallback('remove_instance_callback', t);\r
+ t.onRemove.dispatch(t);\r
+\r
+ // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command\r
+ t.onExecCommand.listeners = [];\r
+\r
+ tinymce.remove(t);\r
+ DOM.remove(e);\r
+ },\r
+\r
+ destroy : function(s) {\r
+ var t = this;\r
+\r
+ // One time is enough\r
+ if (t.destroyed)\r
+ return;\r
+\r
+ if (!s) {\r
+ tinymce.removeUnload(t.destroy);\r
+ tinyMCE.onBeforeUnload.remove(t._beforeUnload);\r
+\r
+ // Manual destroy\r
+ if (t.theme && t.theme.destroy)\r
+ t.theme.destroy();\r
+\r
+ // Destroy controls, selection and dom\r
+ t.controlManager.destroy();\r
+ t.selection.destroy();\r
+ t.dom.destroy();\r
+\r
+ // Remove all events\r
+\r
+ // Don't clear the window or document if content editable\r
+ // is enabled since other instances might still be present\r
+ if (!t.settings.content_editable) {\r
+ Event.clear(t.getWin());\r
+ Event.clear(t.getDoc());\r
+ }\r
+\r
+ Event.clear(t.getBody());\r
+ Event.clear(t.formElement);\r
+ }\r
+\r
+ if (t.formElement) {\r
+ t.formElement.submit = t.formElement._mceOldSubmit;\r
+ t.formElement._mceOldSubmit = null;\r
+ }\r
+\r
+ t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;\r
+\r
+ if (t.selection)\r
+ t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;\r
+\r
+ t.destroyed = 1;\r
+ },\r
+\r
+ // Internal functions\r
+\r
+ _addEvents : function() {\r
+ // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset\r
+ var t = this, i, s = t.settings, dom = t.dom, lo = {\r
+ mouseup : 'onMouseUp',\r
+ mousedown : 'onMouseDown',\r
+ click : 'onClick',\r
+ keyup : 'onKeyUp',\r
+ keydown : 'onKeyDown',\r
+ keypress : 'onKeyPress',\r
+ submit : 'onSubmit',\r
+ reset : 'onReset',\r
+ contextmenu : 'onContextMenu',\r
+ dblclick : 'onDblClick',\r
+ paste : 'onPaste' // Doesn't work in all browsers yet\r
+ };\r
+\r
+ function eventHandler(e, o) {\r
+ var ty = e.type;\r
+\r
+ // Don't fire events when it's removed\r
+ if (t.removed)\r
+ return;\r
+\r
+ // Generic event handler\r
+ if (t.onEvent.dispatch(t, e, o) !== false) {\r
+ // Specific event handler\r
+ t[lo[e.fakeType || e.type]].dispatch(t, e, o);\r
+ }\r
+ };\r
+\r
+ // Add DOM events\r
+ each(lo, function(v, k) {\r
+ switch (k) {\r
+ case 'contextmenu':\r
+ dom.bind(t.getDoc(), k, eventHandler);\r
+ break;\r
+\r
+ case 'paste':\r
+ dom.bind(t.getBody(), k, function(e) {\r
+ eventHandler(e);\r
+ });\r
+ break;\r
+\r
+ case 'submit':\r
+ case 'reset':\r
+ dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);\r
+ break;\r
+\r
+ default:\r
+ dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);\r
+ }\r
+ });\r
+\r
+ dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {\r
+ t.focus(true);\r
+ });\r
+\r
+\r
+ // Fixes bug where a specified document_base_uri could result in broken images\r
+ // This will also fix drag drop of images in Gecko\r
+ if (tinymce.isGecko) {\r
+ dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {\r
+ var v;\r
+\r
+ e = e.target;\r
+\r
+ if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src')))\r
+ e.src = t.documentBaseURI.toAbsolute(v);\r
+ });\r
+ }\r
+\r
+ // Set various midas options in Gecko\r
+ if (isGecko) {\r
+ function setOpts() {\r
+ var t = this, d = t.getDoc(), s = t.settings;\r
+\r
+ if (isGecko && !s.readonly) {\r
+ t._refreshContentEditable();\r
+\r
+ try {\r
+ // Try new Gecko method\r
+ d.execCommand("styleWithCSS", 0, false);\r
+ } catch (ex) {\r
+ // Use old method\r
+ if (!t._isHidden())\r
+ try {d.execCommand("useCSS", 0, true);} catch (ex) {}\r
+ }\r
+\r
+ if (!s.table_inline_editing)\r
+ try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}\r
+\r
+ if (!s.object_resizing)\r
+ try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}\r
+ }\r
+ };\r
+\r
+ t.onBeforeExecCommand.add(setOpts);\r
+ t.onMouseDown.add(setOpts);\r
+ }\r
+\r
+ t.onClick.add(function(ed, e) {\r
+ e = e.target;\r
+\r
+ // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250\r
+ // WebKit can't even do simple things like selecting an image\r
+ // Needs tobe the setBaseAndExtend or it will fail to select floated images\r
+ if (tinymce.isWebKit && e.nodeName == 'IMG')\r
+ t.selection.getSel().setBaseAndExtent(e, 0, e, 1);\r
+\r
+ if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor'))\r
+ t.selection.select(e);\r
+\r
+ t.nodeChanged();\r
+ });\r
+\r
+ // Add node change handlers\r
+ t.onMouseUp.add(t.nodeChanged);\r
+ //t.onClick.add(t.nodeChanged);\r
+ t.onKeyUp.add(function(ed, e) {\r
+ var c = e.keyCode;\r
+\r
+ 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)\r
+ t.nodeChanged();\r
+ });\r
+\r
+\r
+ // Add block quote deletion handler\r
+ t.onKeyDown.add(function(ed, e) {\r
+ // Was the BACKSPACE key pressed?\r
+ if (e.keyCode != 8)\r
+ return;\r
+\r
+ var n = ed.selection.getRng().startContainer;\r
+ var offset = ed.selection.getRng().startOffset;\r
+\r
+ while (n && n.nodeType && n.nodeType != 1 && n.parentNode)\r
+ n = n.parentNode;\r
+ \r
+ // Is the cursor at the beginning of a blockquote?\r
+ if (n && n.parentNode && n.parentNode.tagName === 'BLOCKQUOTE' && n.parentNode.firstChild == n && offset == 0) {\r
+ // Remove the blockquote\r
+ ed.formatter.toggle('blockquote', null, n.parentNode);\r
+\r
+ // Move the caret to the beginning of n\r
+ var rng = ed.selection.getRng();\r
+ rng.setStart(n, 0);\r
+ rng.setEnd(n, 0);\r
+ ed.selection.setRng(rng);\r
+ ed.selection.collapse(false);\r
+ }\r
+ });\r
+ \r
+\r
+\r
+ // Add reset handler\r
+ t.onReset.add(function() {\r
+ t.setContent(t.startContent, {format : 'raw'});\r
+ });\r
+\r
+ // Add shortcuts\r
+ if (s.custom_shortcuts) {\r
+ if (s.custom_undo_redo_keyboard_shortcuts) {\r
+ t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');\r
+ t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');\r
+ }\r
+\r
+ // Add default shortcuts for gecko\r
+ t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');\r
+ t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');\r
+ t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');\r
+\r
+ // BlockFormat shortcuts keys\r
+ for (i=1; i<=6; i++)\r
+ t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);\r
+\r
+ t.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);\r
+ t.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);\r
+ t.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);\r
+\r
+ function find(e) {\r
+ var v = null;\r
+\r
+ if (!e.altKey && !e.ctrlKey && !e.metaKey)\r
+ return v;\r
+\r
+ each(t.shortcuts, function(o) {\r
+ if (tinymce.isMac && o.ctrl != e.metaKey)\r
+ return;\r
+ else if (!tinymce.isMac && o.ctrl != e.ctrlKey)\r
+ return;\r
+\r
+ if (o.alt != e.altKey)\r
+ return;\r
+\r
+ if (o.shift != e.shiftKey)\r
+ return;\r
+\r
+ if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {\r
+ v = o;\r
+ return false;\r
+ }\r
+ });\r
+\r
+ return v;\r
+ };\r
+\r
+ t.onKeyUp.add(function(ed, e) {\r
+ var o = find(e);\r
+\r
+ if (o)\r
+ return Event.cancel(e);\r
+ });\r
+\r
+ t.onKeyPress.add(function(ed, e) {\r
+ var o = find(e);\r
+\r
+ if (o)\r
+ return Event.cancel(e);\r
+ });\r
+\r
+ t.onKeyDown.add(function(ed, e) {\r
+ var o = find(e);\r
+\r
+ if (o) {\r
+ o.func.call(o.scope);\r
+ return Event.cancel(e);\r
+ }\r
+ });\r
+ }\r
+\r
+ if (tinymce.isIE) {\r
+ // Fix so resize will only update the width and height attributes not the styles of an image\r
+ // It will also block mceItemNoResize items\r
+ dom.bind(t.getDoc(), 'controlselect', function(e) {\r
+ var re = t.resizeInfo, cb;\r
+\r
+ e = e.target;\r
+\r
+ // Don't do this action for non image elements\r
+ if (e.nodeName !== 'IMG')\r
+ return;\r
+\r
+ if (re)\r
+ dom.unbind(re.node, re.ev, re.cb);\r
+\r
+ if (!dom.hasClass(e, 'mceItemNoResize')) {\r
+ ev = 'resizeend';\r
+ cb = dom.bind(e, ev, function(e) {\r
+ var v;\r
+\r
+ e = e.target;\r
+\r
+ if (v = dom.getStyle(e, 'width')) {\r
+ dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));\r
+ dom.setStyle(e, 'width', '');\r
+ }\r
+\r
+ if (v = dom.getStyle(e, 'height')) {\r
+ dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));\r
+ dom.setStyle(e, 'height', '');\r
+ }\r
+ });\r
+ } else {\r
+ ev = 'resizestart';\r
+ cb = dom.bind(e, 'resizestart', Event.cancel, Event);\r
+ }\r
+\r
+ re = t.resizeInfo = {\r
+ node : e,\r
+ ev : ev,\r
+ cb : cb\r
+ };\r
+ });\r
+ }\r
+\r
+ if (tinymce.isOpera) {\r
+ t.onClick.add(function(ed, e) {\r
+ Event.prevent(e);\r
+ });\r
+ }\r
+\r
+ // Add custom undo/redo handlers\r
+ if (s.custom_undo_redo) {\r
+ function addUndo() {\r
+ t.undoManager.typing = false;\r
+ t.undoManager.add();\r
+ };\r
+\r
+ dom.bind(t.getDoc(), 'focusout', function(e) {\r
+ if (!t.removed && t.undoManager.typing)\r
+ addUndo();\r
+ });\r
+\r
+ // Add undo level when contents is drag/dropped within the editor\r
+ t.dom.bind(t.dom.getRoot(), 'dragend', function(e) {\r
+ addUndo();\r
+ });\r
+\r
+ t.onKeyUp.add(function(ed, e) {\r
+ var keyCode = e.keyCode;\r
+\r
+ if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || e.ctrlKey)\r
+ addUndo();\r
+ });\r
+\r
+ t.onKeyDown.add(function(ed, e) {\r
+ var keyCode = e.keyCode, sel;\r
+\r
+ if (keyCode == 8) {\r
+ sel = t.getDoc().selection;\r
+\r
+ // Fix IE control + backspace browser bug\r
+ if (sel && sel.createRange && sel.createRange().item) {\r
+ t.undoManager.beforeChange();\r
+ ed.dom.remove(sel.createRange().item(0));\r
+ addUndo();\r
+\r
+ return Event.cancel(e);\r
+ }\r
+ }\r
+\r
+ // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter\r
+ if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) {\r
+ // Add position before enter key is pressed, used by IE since it still uses the default browser behavior\r
+ // Todo: Remove this once we normalize enter behavior on IE\r
+ if (tinymce.isIE && keyCode == 13)\r
+ t.undoManager.beforeChange();\r
+\r
+ if (t.undoManager.typing)\r
+ addUndo();\r
+\r
+ return;\r
+ }\r
+\r
+ // If key isn't shift,ctrl,alt,capslock,metakey\r
+ if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) {\r
+ t.undoManager.beforeChange();\r
+ t.undoManager.typing = true;\r
+ t.undoManager.add();\r
+ }\r
+ });\r
+\r
+ t.onMouseDown.add(function() {\r
+ if (t.undoManager.typing)\r
+ addUndo();\r
+ });\r
+ }\r
+\r
+ // Fire a nodeChanged when the selection is changed on WebKit this fixes selection issues on iOS5\r
+ // It only fires the nodeChange event every 50ms since it would other wise update the UI when you type and it hogs the CPU\r
+ if (tinymce.isWebKit) {\r
+ dom.bind(t.getDoc(), 'selectionchange', function() {\r
+ if (t.selectionTimer) {\r
+ clearTimeout(t.selectionTimer);\r
+ t.selectionTimer = 0;\r
+ }\r
+\r
+ t.selectionTimer = window.setTimeout(function() {\r
+ t.nodeChanged();\r
+ }, 50);\r
+ });\r
+ }\r
+\r
+ // Bug fix for FireFox keeping styles from end of selection instead of start.\r
+ if (tinymce.isGecko) {\r
+ function getAttributeApplyFunction() {\r
+ var template = t.dom.getAttribs(t.selection.getStart().cloneNode(false));\r
+\r
+ return function() {\r
+ var target = t.selection.getStart();\r
+\r
+ if (target !== t.getBody()) {\r
+ t.dom.removeAllAttribs(target);\r
+\r
+ each(template, function(attr) {\r
+ target.setAttributeNode(attr.cloneNode(true));\r
+ });\r
+ }\r
+ };\r
+ }\r
+\r
+ function isSelectionAcrossElements() {\r
+ var s = t.selection;\r
+\r
+ return !s.isCollapsed() && s.getStart() != s.getEnd();\r
+ }\r
+\r
+ t.onKeyPress.add(function(ed, e) {\r
+ var applyAttributes;\r
+\r
+ if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {\r
+ applyAttributes = getAttributeApplyFunction();\r
+ t.getDoc().execCommand('delete', false, null);\r
+ applyAttributes();\r
+\r
+ return Event.cancel(e);\r
+ }\r
+ });\r
+\r
+ t.dom.bind(t.getDoc(), 'cut', function(e) {\r
+ var applyAttributes;\r
+\r
+ if (isSelectionAcrossElements()) {\r
+ applyAttributes = getAttributeApplyFunction();\r
+ t.onKeyUp.addToTop(Event.cancel, Event);\r
+\r
+ setTimeout(function() {\r
+ applyAttributes();\r
+ t.onKeyUp.remove(Event.cancel, Event);\r
+ }, 0);\r
+ }\r
+ });\r
+ }\r
+ },\r
+\r
+ _refreshContentEditable : function() {\r
+ var self = this, body, parent;\r
+\r
+ // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again\r
+ if (self._isHidden()) {\r
+ body = self.getBody();\r
+ parent = body.parentNode;\r
+\r
+ parent.removeChild(body);\r
+ parent.appendChild(body);\r
+\r
+ body.focus();\r
+ }\r
+ },\r
+\r
+ _isHidden : function() {\r
+ var s;\r
+\r
+ if (!isGecko)\r
+ return 0;\r
+\r
+ // Weird, wheres that cursor selection?\r
+ s = this.selection.getSel();\r
+ return (!s || !s.rangeCount || s.rangeCount == 0);\r
+ }\r
+ });\r
+})(tinymce);\r
+\r
+(function(tinymce) {\r
+ // Added for compression purposes\r
+ var each = tinymce.each, undefined, TRUE = true, FALSE = false;\r
+\r
+ tinymce.EditorCommands = function(editor) {\r
+ var dom = editor.dom,\r
+ selection = editor.selection,\r
+ commands = {state: {}, exec : {}, value : {}},\r
+ settings = editor.settings,\r
+ formatter = editor.formatter,\r
+ bookmark;\r
+\r
+ function execCommand(command, ui, value) {\r
+ var func;\r
+\r
+ command = command.toLowerCase();\r
+ if (func = commands.exec[command]) {\r
+ func(command, ui, value);\r
+ return TRUE;\r
+ }\r
+\r
+ return FALSE;\r
+ };\r
+\r
+ function queryCommandState(command) {\r
+ var func;\r
+\r
+ command = command.toLowerCase();\r
+ if (func = commands.state[command])\r
+ return func(command);\r
+\r
+ return -1;\r
+ };\r
+\r
+ function queryCommandValue(command) {\r
+ var func;\r
+\r
+ command = command.toLowerCase();\r
+ if (func = commands.value[command])\r
+ return func(command);\r
+\r
+ return FALSE;\r
+ };\r
+\r
+ function addCommands(command_list, type) {\r
+ type = type || 'exec';\r
+\r
+ each(command_list, function(callback, command) {\r
+ each(command.toLowerCase().split(','), function(command) {\r
+ commands[type][command] = callback;\r
+ });\r
+ });\r
+ };\r
+\r
+ // Expose public methods\r
+ tinymce.extend(this, {\r
+ execCommand : execCommand,\r
+ queryCommandState : queryCommandState,\r
+ queryCommandValue : queryCommandValue,\r
+ addCommands : addCommands\r
+ });\r
+\r
+ // Private methods\r
+\r
+ function execNativeCommand(command, ui, value) {\r
+ if (ui === undefined)\r
+ ui = FALSE;\r
+\r
+ if (value === undefined)\r
+ value = null;\r
+\r
+ return editor.getDoc().execCommand(command, ui, value);\r
+ };\r
+\r
+ function isFormatMatch(name) {\r
+ return formatter.match(name);\r
+ };\r
+\r
+ function toggleFormat(name, value) {\r
+ formatter.toggle(name, value ? {value : value} : undefined);\r
+ };\r
+\r
+ function storeSelection(type) {\r
+ bookmark = selection.getBookmark(type);\r
+ };\r
+\r
+ function restoreSelection() {\r
+ selection.moveToBookmark(bookmark);\r
+ };\r
+\r
+ // Add execCommand overrides\r
+ addCommands({\r
+ // Ignore these, added for compatibility\r
+ 'mceResetDesignMode,mceBeginUndoLevel' : function() {},\r
+\r
+ // Add undo manager logic\r
+ 'mceEndUndoLevel,mceAddUndoLevel' : function() {\r
+ editor.undoManager.add();\r
+ },\r
+\r
+ 'Cut,Copy,Paste' : function(command) {\r
+ var doc = editor.getDoc(), failed;\r
+\r
+ // Try executing the native command\r
+ try {\r
+ execNativeCommand(command);\r
+ } catch (ex) {\r
+ // Command failed\r
+ failed = TRUE;\r
+ }\r
+\r
+ // Present alert message about clipboard access not being available\r
+ if (failed || !doc.queryCommandSupported(command)) {\r
+ if (tinymce.isGecko) {\r
+ editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {\r
+ if (state)\r
+ open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');\r
+ });\r
+ } else\r
+ editor.windowManager.alert(editor.getLang('clipboard_no_support'));\r
+ }\r
+ },\r
+\r
+ // Override unlink command\r
+ unlink : function(command) {\r
+ if (selection.isCollapsed())\r
+ selection.select(selection.getNode());\r
+\r
+ execNativeCommand(command);\r
+ selection.collapse(FALSE);\r
+ },\r
+\r
+ // Override justify commands to use the text formatter engine\r
+ 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {\r
+ var align = command.substring(7);\r
+\r
+ // Remove all other alignments first\r
+ each('left,center,right,full'.split(','), function(name) {\r
+ if (align != name)\r
+ formatter.remove('align' + name);\r
+ });\r
+\r
+ toggleFormat('align' + align);\r
+ execCommand('mceRepaint');\r
+ },\r
+\r
+ // Override list commands to fix WebKit bug\r
+ 'InsertUnorderedList,InsertOrderedList' : function(command) {\r
+ var listElm, listParent;\r
+\r
+ execNativeCommand(command);\r
+\r
+ // WebKit produces lists within block elements so we need to split them\r
+ // we will replace the native list creation logic to custom logic later on\r
+ // TODO: Remove this when the list creation logic is removed\r
+ listElm = dom.getParent(selection.getNode(), 'ol,ul');\r
+ if (listElm) {\r
+ listParent = listElm.parentNode;\r
+\r
+ // If list is within a text block then split that block\r
+ if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {\r
+ storeSelection();\r
+ dom.split(listParent, listElm);\r
+ restoreSelection();\r
+ }\r
+ }\r
+ },\r
+\r
+ // Override commands to use the text formatter engine\r
+ 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {\r
+ toggleFormat(command);\r
+ },\r
+\r
+ // Override commands to use the text formatter engine\r
+ 'ForeColor,HiliteColor,FontName' : function(command, ui, value) {\r
+ toggleFormat(command, value);\r
+ },\r
+\r
+ FontSize : function(command, ui, value) {\r
+ var fontClasses, fontSizes;\r
+\r
+ // Convert font size 1-7 to styles\r
+ if (value >= 1 && value <= 7) {\r
+ fontSizes = tinymce.explode(settings.font_size_style_values);\r
+ fontClasses = tinymce.explode(settings.font_size_classes);\r
+\r
+ if (fontClasses)\r
+ value = fontClasses[value - 1] || value;\r
+ else\r
+ value = fontSizes[value - 1] || value;\r
+ }\r
+\r
+ toggleFormat(command, value);\r
+ },\r
+\r
+ RemoveFormat : function(command) {\r
+ formatter.remove(command);\r
+ },\r
+\r
+ mceBlockQuote : function(command) {\r
+ toggleFormat('blockquote');\r
+ },\r
+\r
+ FormatBlock : function(command, ui, value) {\r
+ return toggleFormat(value || 'p');\r
+ },\r
+\r
+ mceCleanup : function() {\r
+ var bookmark = selection.getBookmark();\r
+\r
+ editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});\r
+\r
+ selection.moveToBookmark(bookmark);\r
+ },\r
+\r
+ mceRemoveNode : function(command, ui, value) {\r
+ var node = value || selection.getNode();\r
+\r
+ // Make sure that the body node isn't removed\r
+ if (node != editor.getBody()) {\r
+ storeSelection();\r
+ editor.dom.remove(node, TRUE);\r
+ restoreSelection();\r
+ }\r
+ },\r
+\r
+ mceSelectNodeDepth : function(command, ui, value) {\r
+ var counter = 0;\r
+\r
+ dom.getParent(selection.getNode(), function(node) {\r
+ if (node.nodeType == 1 && counter++ == value) {\r
+ selection.select(node);\r
+ return FALSE;\r
+ }\r
+ }, editor.getBody());\r
+ },\r
+\r
+ mceSelectNode : function(command, ui, value) {\r
+ selection.select(value);\r
+ },\r
+\r
+ mceInsertContent : function(command, ui, value) {\r
+ var parser, serializer, parentNode, rootNode, fragment, args,\r
+ marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;\r
+\r
+ // Setup parser and serializer\r
+ parser = editor.parser;\r
+ serializer = new tinymce.html.Serializer({}, editor.schema);\r
+ bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';\r
+\r
+ // Run beforeSetContent handlers on the HTML to be inserted\r
+ args = {content: value, format: 'html'};\r
+ selection.onBeforeSetContent.dispatch(selection, args);\r
+ value = args.content;\r
+\r
+ // Add caret at end of contents if it's missing\r
+ if (value.indexOf('{$caret}') == -1)\r
+ value += '{$caret}';\r
+\r
+ // Replace the caret marker with a span bookmark element\r
+ value = value.replace(/\{\$caret\}/, bookmarkHtml);\r
+\r
+ // Insert node maker where we will insert the new HTML and get it's parent\r
+ if (!selection.isCollapsed())\r
+ editor.getDoc().execCommand('Delete', false, null);\r
+\r
+ parentNode = selection.getNode();\r
+\r
+ // Parse the fragment within the context of the parent node\r
+ args = {context : parentNode.nodeName.toLowerCase()};\r
+ fragment = parser.parse(value, args);\r
+\r
+ // Move the caret to a more suitable location\r
+ node = fragment.lastChild;\r
+ if (node.attr('id') == 'mce_marker') {\r
+ marker = node;\r
+\r
+ for (node = node.prev; node; node = node.walk(true)) {\r
+ if (node.type == 3 || !dom.isBlock(node.name)) {\r
+ node.parent.insert(marker, node, node.name === 'br');\r
+ break;\r
+ }\r
+ }\r
+ }\r
+\r
+ // If parser says valid we can insert the contents into that parent\r
+ if (!args.invalid) {\r
+ value = serializer.serialize(fragment);\r
+\r
+ // Check if parent is empty or only has one BR element then set the innerHTML of that parent\r
+ node = parentNode.firstChild;\r
+ node2 = parentNode.lastChild;\r
+ if (!node || (node === node2 && node.nodeName === 'BR'))\r
+ dom.setHTML(parentNode, value);\r
+ else\r
+ selection.setContent(value);\r
+ } else {\r
+ // If the fragment was invalid within that context then we need\r
+ // to parse and process the parent it's inserted into\r
+\r
+ // Insert bookmark node and get the parent\r
+ selection.setContent(bookmarkHtml);\r
+ parentNode = editor.selection.getNode();\r
+ rootNode = editor.getBody();\r
+\r
+ // Opera will return the document node when selection is in root\r
+ if (parentNode.nodeType == 9)\r
+ parentNode = node = rootNode;\r
+ else\r
+ node = parentNode;\r
+\r
+ // Find the ancestor just before the root element\r
+ while (node !== rootNode) {\r
+ parentNode = node;\r
+ node = node.parentNode;\r
+ }\r
+\r
+ // Get the outer/inner HTML depending on if we are in the root and parser and serialize that\r
+ value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);\r
+ value = serializer.serialize(\r
+ parser.parse(\r
+ // Need to replace by using a function since $ in the contents would otherwise be a problem\r
+ value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {\r
+ return serializer.serialize(fragment);\r
+ })\r
+ )\r
+ );\r
+\r
+ // Set the inner/outer HTML depending on if we are in the root or not\r
+ if (parentNode == rootNode)\r
+ dom.setHTML(rootNode, value);\r
+ else\r
+ dom.setOuterHTML(parentNode, value);\r
+ }\r
+\r
+ marker = dom.get('mce_marker');\r
+\r
+ // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well\r
+ nodeRect = dom.getRect(marker);\r
+ viewPortRect = dom.getViewPort(editor.getWin());\r
+\r
+ // Check if node is out side the viewport if it is then scroll to it\r
+ if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||\r
+ (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {\r
+ viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();\r
+ viewportBodyElement.scrollLeft = nodeRect.x;\r
+ viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;\r
+ }\r
+\r
+ // Move selection before marker and remove it\r
+ rng = dom.createRng();\r
+\r
+ // If previous sibling is a text node set the selection to the end of that node\r
+ node = marker.previousSibling;\r
+ if (node && node.nodeType == 3) {\r
+ rng.setStart(node, node.nodeValue.length);\r
+ } else {\r
+ // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node\r
+ rng.setStartBefore(marker);\r
+ rng.setEndBefore(marker);\r
+ }\r
+\r
+ // Remove the marker node and set the new range\r
+ dom.remove(marker);\r
+ selection.setRng(rng);\r
+\r
+ // Dispatch after event and add any visual elements needed\r
+ selection.onSetContent.dispatch(selection, args);\r
+ editor.addVisual();\r
+ },\r
+\r
+ mceInsertRawHTML : function(command, ui, value) {\r
+ selection.setContent('tiny_mce_marker');\r
+ editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));\r
+ },\r
+\r
+ mceSetContent : function(command, ui, value) {\r
+ editor.setContent(value);\r
+ },\r
+\r
+ 'Indent,Outdent' : function(command) {\r
+ var intentValue, indentUnit, value;\r
+\r
+ // Setup indent level\r
+ intentValue = settings.indentation;\r
+ indentUnit = /[a-z%]+$/i.exec(intentValue);\r
+ intentValue = parseInt(intentValue);\r
+\r
+ if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {\r
+ each(selection.getSelectedBlocks(), function(element) {\r
+ if (command == 'outdent') {\r
+ value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);\r
+ dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');\r
+ } else\r
+ dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);\r
+ });\r
+ } else\r
+ execNativeCommand(command);\r
+ },\r
+\r
+ mceRepaint : function() {\r
+ var bookmark;\r
+\r
+ if (tinymce.isGecko) {\r
+ try {\r
+ storeSelection(TRUE);\r
+\r
+ if (selection.getSel())\r
+ selection.getSel().selectAllChildren(editor.getBody());\r
+\r
+ selection.collapse(TRUE);\r
+ restoreSelection();\r
+ } catch (ex) {\r
+ // Ignore\r
+ }\r
+ }\r
+ },\r
+\r
+ mceToggleFormat : function(command, ui, value) {\r
+ formatter.toggle(value);\r
+ },\r
+\r
+ InsertHorizontalRule : function() {\r
+ editor.execCommand('mceInsertContent', false, '<hr />');\r
+ },\r
+\r
+ mceToggleVisualAid : function() {\r
+ editor.hasVisual = !editor.hasVisual;\r
+ editor.addVisual();\r
+ },\r
+\r
+ mceReplaceContent : function(command, ui, value) {\r
+ editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));\r
+ },\r
+\r
+ mceInsertLink : function(command, ui, value) {\r
+ var anchor;\r
+\r
+ if (typeof(value) == 'string')\r
+ value = {href : value};\r
+\r
+ anchor = dom.getParent(selection.getNode(), 'a');\r
+\r
+ // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.\r
+ value.href = value.href.replace(' ', '%20');\r
+\r
+ // Remove existing links if there could be child links or that the href isn't specified\r
+ if (!anchor || !value.href) {\r
+ formatter.remove('link');\r
+ } \r
+\r
+ // Apply new link to selection\r
+ if (value.href) {\r
+ formatter.apply('link', value, anchor);\r
+ }\r
+ },\r
+\r
+ selectAll : function() {\r
+ var root = dom.getRoot(), rng = dom.createRng();\r
+\r
+ rng.setStart(root, 0);\r
+ rng.setEnd(root, root.childNodes.length);\r
+\r
+ editor.selection.setRng(rng);\r
+ }\r
+ });\r
+\r
+ // Add queryCommandState overrides\r
+ addCommands({\r
+ // Override justify commands\r
+ 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {\r
+ return isFormatMatch('align' + command.substring(7));\r
+ },\r
+\r
+ 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {\r
+ return isFormatMatch(command);\r
+ },\r
+\r
+ mceBlockQuote : function() {\r
+ return isFormatMatch('blockquote');\r
+ },\r
+\r
+ Outdent : function() {\r
+ var node;\r
+\r
+ if (settings.inline_styles) {\r
+ if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)\r
+ return TRUE;\r
+\r
+ if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)\r
+ return TRUE;\r
+ }\r
+\r
+ return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));\r
+ },\r
+\r
+ 'InsertUnorderedList,InsertOrderedList' : function(command) {\r
+ return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');\r
+ }\r
+ }, 'state');\r
+\r
+ // Add queryCommandValue overrides\r
+ addCommands({\r
+ 'FontSize,FontName' : function(command) {\r
+ var value = 0, parent;\r
+\r
+ if (parent = dom.getParent(selection.getNode(), 'span')) {\r
+ if (command == 'fontsize')\r
+ value = parent.style.fontSize;\r
+ else\r
+ value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();\r
+ }\r
+\r
+ return value;\r
+ }\r
+ }, 'value');\r
+\r
+ // Add undo manager logic\r
+ if (settings.custom_undo_redo) {\r
+ addCommands({\r
+ Undo : function() {\r
+ editor.undoManager.undo();\r
+ },\r
+\r
+ Redo : function() {\r
+ editor.undoManager.redo();\r
+ }\r
+ });\r
+ }\r
+ };\r
+})(tinymce);\r
+\r
+(function(tinymce) {\r
+ var Dispatcher = tinymce.util.Dispatcher;\r
+\r
+ tinymce.UndoManager = function(editor) {\r
+ var self, index = 0, data = [], beforeBookmark;\r
+\r
+ function getContent() {\r
+ return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));\r
+ };\r
+\r
+ return self = {\r
+ typing : false,\r
+\r
+ onAdd : new Dispatcher(self),\r
+\r
+ onUndo : new Dispatcher(self),\r
+\r
+ onRedo : new Dispatcher(self),\r
+\r
+ beforeChange : function() {\r
+ beforeBookmark = editor.selection.getBookmark(2, true);\r
+ },\r
+\r
+ add : function(level) {\r
+ var i, settings = editor.settings, lastLevel;\r
+\r
+ level = level || {};\r
+ level.content = getContent();\r
+\r
+ // Add undo level if needed\r
+ lastLevel = data[index];\r
+ if (lastLevel && lastLevel.content == level.content)\r
+ return null;\r
+\r
+ // Set before bookmark on previous level\r
+ if (data[index])\r
+ data[index].beforeBookmark = beforeBookmark;\r
+\r
+ // Time to compress\r
+ if (settings.custom_undo_redo_levels) {\r
+ if (data.length > settings.custom_undo_redo_levels) {\r
+ for (i = 0; i < data.length - 1; i++)\r
+ data[i] = data[i + 1];\r
+\r
+ data.length--;\r
+ index = data.length;\r
+ }\r
+ }\r
+\r
+ // Get a non intrusive normalized bookmark\r
+ level.bookmark = editor.selection.getBookmark(2, true);\r
+\r
+ // Crop array if needed\r
+ if (index < data.length - 1)\r
+ data.length = index + 1;\r
+\r
+ data.push(level);\r
+ index = data.length - 1;\r
+\r
+ self.onAdd.dispatch(self, level);\r
+ editor.isNotDirty = 0;\r
+\r
+ return level;\r
+ },\r
+\r
+ undo : function() {\r
+ var level, i;\r
+\r
+ if (self.typing) {\r
+ self.add();\r
+ self.typing = false;\r
+ }\r
+\r
+ if (index > 0) {\r
+ level = data[--index];\r
+\r
+ editor.setContent(level.content, {format : 'raw'});\r
+ editor.selection.moveToBookmark(level.beforeBookmark);\r
+\r
+ self.onUndo.dispatch(self, level);\r
+ }\r
+\r
+ return level;\r
+ },\r
+\r
+ redo : function() {\r
+ var level;\r
+\r
+ if (index < data.length - 1) {\r
+ level = data[++index];\r
+\r
+ editor.setContent(level.content, {format : 'raw'});\r
+ editor.selection.moveToBookmark(level.bookmark);\r
+\r
+ self.onRedo.dispatch(self, level);\r
+ }\r
+\r
+ return level;\r
+ },\r
+\r
+ clear : function() {\r
+ data = [];\r
+ index = 0;\r
+ self.typing = false;\r
+ },\r
+\r
+ hasUndo : function() {\r
+ return index > 0 || this.typing;\r
+ },\r
+\r
+ hasRedo : function() {\r
+ return index < data.length - 1 && !this.typing;\r
+ }\r
+ };\r
+ };\r
+})(tinymce);\r
+\r
+(function(tinymce) {\r
+ // Shorten names\r
+ var Event = tinymce.dom.Event,\r
+ isIE = tinymce.isIE,\r
+ isGecko = tinymce.isGecko,\r
+ isOpera = tinymce.isOpera,\r
+ each = tinymce.each,\r
+ extend = tinymce.extend,\r
+ TRUE = true,\r
+ FALSE = false;\r
+\r
+ function cloneFormats(node) {\r
+ var clone, temp, inner;\r
+\r
+ do {\r
+ if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {\r
+ if (clone) {\r
+ temp = node.cloneNode(false);\r
+ temp.appendChild(clone);\r
+ clone = temp;\r
+ } else {\r
+ clone = inner = node.cloneNode(false);\r
+ }\r
+\r
+ clone.removeAttribute('id');\r
+ }\r
+ } while (node = node.parentNode);\r
+\r
+ if (clone)\r
+ return {wrapper : clone, inner : inner};\r
+ };\r
+\r
+ // Checks if the selection/caret is at the end of the specified block element\r
+ function isAtEnd(rng, par) {\r
+ var rng2 = par.ownerDocument.createRange();\r
+\r
+ rng2.setStart(rng.endContainer, rng.endOffset);\r
+ rng2.setEndAfter(par);\r
+\r
+ // Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element\r
+ return rng2.cloneContents().textContent.length == 0;\r
+ };\r
+\r
+ function splitList(selection, dom, li) {\r
+ var listBlock, block;\r
+\r
+ if (dom.isEmpty(li)) {\r
+ listBlock = dom.getParent(li, 'ul,ol');\r
+\r
+ if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {\r
+ dom.split(listBlock, li);\r
+ block = dom.create('p', 0, '<br data-mce-bogus="1" />');\r
+ dom.replace(block, li);\r
+ selection.select(block, 1);\r
+ }\r
+\r
+ return FALSE;\r
+ }\r
+\r
+ return TRUE;\r
+ };\r
+\r
+ tinymce.create('tinymce.ForceBlocks', {\r
+ ForceBlocks : function(ed) {\r
+ var t = this, s = ed.settings, elm;\r
+\r
+ t.editor = ed;\r
+ t.dom = ed.dom;\r
+ elm = (s.forced_root_block || 'p').toLowerCase();\r
+ s.element = elm.toUpperCase();\r
+\r
+ ed.onPreInit.add(t.setup, t);\r
+ },\r
+\r
+ setup : function() {\r
+ var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection, blockElements = ed.schema.getBlockElements();\r
+\r
+ // Force root blocks\r
+ if (s.forced_root_block) {\r
+ function addRootBlocks() {\r
+ var node = selection.getStart(), rootNode = ed.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF;\r
+\r
+ if (!node || node.nodeType !== 1)\r
+ return;\r
+\r
+ // Check if node is wrapped in block\r
+ while (node != rootNode) {\r
+ if (blockElements[node.nodeName])\r
+ return;\r
+\r
+ node = node.parentNode;\r
+ }\r
+\r
+ // Get current selection\r
+ rng = selection.getRng();\r
+ if (rng.setStart) {\r
+ startContainer = rng.startContainer;\r
+ startOffset = rng.startOffset;\r
+ endContainer = rng.endContainer;\r
+ endOffset = rng.endOffset;\r
+ } else {\r
+ // Force control range into text range\r
+ if (rng.item) {\r
+ rng = ed.getDoc().body.createTextRange();\r
+ rng.moveToElementText(rng.item(0));\r
+ }\r
+\r
+ tmpRng = rng.duplicate();\r
+ tmpRng.collapse(true);\r
+ startOffset = tmpRng.move('character', offset) * -1;\r
+\r
+ if (!tmpRng.collapsed) {\r
+ tmpRng = rng.duplicate();\r
+ tmpRng.collapse(false);\r
+ endOffset = (tmpRng.move('character', offset) * -1) - startOffset;\r
+ }\r
+ }\r
+\r
+ // Wrap non block elements and text nodes\r
+ for (node = rootNode.firstChild; node; node) {\r
+ if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {\r
+ if (!rootBlockNode) {\r
+ rootBlockNode = dom.create(s.forced_root_block);\r
+ node.parentNode.insertBefore(rootBlockNode, node);\r
+ }\r
+\r
+ tempNode = node;\r
+ node = node.nextSibling;\r
+ rootBlockNode.appendChild(tempNode);\r
+ } else {\r
+ rootBlockNode = null;\r
+ node = node.nextSibling;\r
+ }\r
+ }\r
+\r
+ if (rng.setStart) {\r
+ rng.setStart(startContainer, startOffset);\r
+ rng.setEnd(endContainer, endOffset);\r
+ selection.setRng(rng);\r
+ } else {\r
+ try {\r
+ rng = ed.getDoc().body.createTextRange();\r
+ rng.moveToElementText(rootNode);\r
+ rng.collapse(true);\r
+ rng.moveStart('character', startOffset);\r
+\r
+ if (endOffset > 0)\r
+ rng.moveEnd('character', endOffset);\r
+\r
+ rng.select();\r
+ } catch (ex) {\r
+ // Ignore\r
+ }\r
+ }\r
+\r
+ ed.nodeChanged();\r
+ };\r
+\r
+ ed.onKeyUp.add(addRootBlocks);\r
+ ed.onClick.add(addRootBlocks);\r
+ }\r
+\r
+ if (s.force_br_newlines) {\r
+ // Force IE to produce BRs on enter\r
+ if (isIE) {\r
+ ed.onKeyPress.add(function(ed, e) {\r
+ var n;\r
+\r
+ if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {\r
+ selection.setContent('<br id="__" /> ', {format : 'raw'});\r
+ n = dom.get('__');\r
+ n.removeAttribute('id');\r
+ selection.select(n);\r
+ selection.collapse();\r
+ return Event.cancel(e);\r
+ }\r
+ });\r
+ }\r
+ }\r
+\r
+ if (s.force_p_newlines) {\r
+ if (!isIE) {\r
+ ed.onKeyPress.add(function(ed, e) {\r
+ if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))\r
+ Event.cancel(e);\r
+ });\r
+ } else {\r
+ // Ungly hack to for IE to preserve the formatting when you press\r
+ // enter at the end of a block element with formatted contents\r
+ // This logic overrides the browsers default logic with\r
+ // custom logic that enables us to control the output\r
+ tinymce.addUnload(function() {\r
+ t._previousFormats = 0; // Fix IE leak\r
+ });\r
+\r
+ ed.onKeyPress.add(function(ed, e) {\r
+ t._previousFormats = 0;\r
+\r
+ // Clone the current formats, this will later be applied to the new block contents\r
+ if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)\r
+ t._previousFormats = cloneFormats(ed.selection.getStart());\r
+ });\r
+\r
+ ed.onKeyUp.add(function(ed, e) {\r
+ // Let IE break the element and the wrap the new caret location in the previous formats\r
+ if (e.keyCode == 13 && !e.shiftKey) {\r
+ var parent = ed.selection.getStart(), fmt = t._previousFormats;\r
+\r
+ // Parent is an empty block\r
+ if (!parent.hasChildNodes() && fmt) {\r
+ parent = dom.getParent(parent, dom.isBlock);\r
+\r
+ if (parent && parent.nodeName != 'LI') {\r
+ parent.innerHTML = '';\r
+\r
+ if (t._previousFormats) {\r
+ parent.appendChild(fmt.wrapper);\r
+ fmt.inner.innerHTML = '\uFEFF';\r
+ } else\r
+ parent.innerHTML = '\uFEFF';\r
+\r
+ selection.select(parent, 1);\r
+ selection.collapse(true);\r
+ ed.getDoc().execCommand('Delete', false, null);\r
+ t._previousFormats = 0;\r
+ }\r
+ }\r
+ }\r
+ });\r
+ }\r
+\r
+ if (isGecko) {\r
+ ed.onKeyDown.add(function(ed, e) {\r
+ if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)\r
+ t.backspaceDelete(e, e.keyCode == 8);\r
+ });\r
+ }\r
+ }\r
+\r
+ // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973\r
+ if (tinymce.isWebKit) {\r
+ function insertBr(ed) {\r
+ var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;\r
+\r
+ // Insert BR element\r
+ rng.insertNode(br = dom.create('br'));\r
+\r
+ // Place caret after BR\r
+ rng.setStartAfter(br);\r
+ rng.setEndAfter(br);\r
+ selection.setRng(rng);\r
+\r
+ // Could not place caret after BR then insert an nbsp entity and move the caret\r
+ if (selection.getSel().focusNode == br.previousSibling) {\r
+ selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));\r
+ selection.collapse(TRUE);\r
+ }\r
+\r
+ // Create a temporary DIV after the BR and get the position as it\r
+ // seems like getPos() returns 0 for text nodes and BR elements.\r
+ dom.insertAfter(div, br);\r
+ divYPos = dom.getPos(div).y;\r
+ dom.remove(div);\r
+\r
+ // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117\r
+ if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.\r
+ ed.getWin().scrollTo(0, divYPos);\r
+ };\r
+\r
+ ed.onKeyPress.add(function(ed, e) {\r
+ if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {\r
+ insertBr(ed);\r
+ Event.cancel(e);\r
+ }\r
+ });\r
+ }\r
+\r
+ // IE specific fixes\r
+ if (isIE) {\r
+ // Replaces IE:s auto generated paragraphs with the specified element name\r
+ if (s.element != 'P') {\r
+ ed.onKeyPress.add(function(ed, e) {\r
+ t.lastElm = selection.getNode().nodeName;\r
+ });\r
+\r
+ ed.onKeyUp.add(function(ed, e) {\r
+ var bl, n = selection.getNode(), b = ed.getBody();\r
+\r
+ if (b.childNodes.length === 1 && n.nodeName == 'P') {\r
+ n = dom.rename(n, s.element);\r
+ selection.select(n);\r
+ selection.collapse();\r
+ ed.nodeChanged();\r
+ } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {\r
+ bl = dom.getParent(n, 'p');\r
+\r
+ if (bl) {\r
+ dom.rename(bl, s.element);\r
+ ed.nodeChanged();\r
+ }\r
+ }\r
+ });\r
+ }\r
+ }\r
+ },\r
+\r
+ getParentBlock : function(n) {\r
+ var d = this.dom;\r
+\r
+ return d.getParent(n, d.isBlock);\r
+ },\r
+\r
+ insertPara : function(e) {\r
+ var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body;\r
+ var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car;\r
+\r
+ ed.undoManager.beforeChange();\r
+\r
+ // If root blocks are forced then use Operas default behavior since it's really good\r
+// Removed due to bug: #1853816\r
+// if (se.forced_root_block && isOpera)\r
+// return TRUE;\r
+\r
+ // Setup before range\r
+ rb = d.createRange();\r
+\r
+ // If is before the first block element and in body, then move it into first block element\r
+ rb.setStart(s.anchorNode, s.anchorOffset);\r
+ rb.collapse(TRUE);\r
+\r
+ // Setup after range\r
+ ra = d.createRange();\r
+\r
+ // If is before the first block element and in body, then move it into first block element\r
+ ra.setStart(s.focusNode, s.focusOffset);\r
+ ra.collapse(TRUE);\r
+\r
+ // Setup start/end points\r
+ dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;\r
+ sn = dir ? s.anchorNode : s.focusNode;\r
+ so = dir ? s.anchorOffset : s.focusOffset;\r
+ en = dir ? s.focusNode : s.anchorNode;\r
+ eo = dir ? s.focusOffset : s.anchorOffset;\r
+\r
+ // If selection is in empty table cell\r
+ if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {\r
+ if (sn.firstChild.nodeName == 'BR')\r
+ dom.remove(sn.firstChild); // Remove BR\r
+\r
+ // Create two new block elements\r
+ if (sn.childNodes.length == 0) {\r
+ ed.dom.add(sn, se.element, null, '<br />');\r
+ aft = ed.dom.add(sn, se.element, null, '<br />');\r
+ } else {\r
+ n = sn.innerHTML;\r
+ sn.innerHTML = '';\r
+ ed.dom.add(sn, se.element, null, n);\r
+ aft = ed.dom.add(sn, se.element, null, '<br />');\r
+ }\r
+\r
+ // Move caret into the last one\r
+ r = d.createRange();\r
+ r.selectNodeContents(aft);\r
+ r.collapse(1);\r
+ ed.selection.setRng(r);\r
+\r
+ return FALSE;\r
+ }\r
+\r
+ // If the caret is in an invalid location in FF we need to move it into the first block\r
+ if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {\r
+ sn = en = sn.firstChild;\r
+ so = eo = 0;\r
+ rb = d.createRange();\r
+ rb.setStart(sn, 0);\r
+ ra = d.createRange();\r
+ ra.setStart(en, 0);\r
+ }\r
+\r
+ // If the body is totally empty add a BR element this might happen on webkit\r
+ if (!d.body.hasChildNodes()) {\r
+ d.body.appendChild(dom.create('br'));\r
+ }\r
+\r
+ // Never use body as start or end node\r
+ sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes\r
+ sn = sn.nodeName == "BODY" ? sn.firstChild : sn;\r
+ en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes\r
+ en = en.nodeName == "BODY" ? en.firstChild : en;\r
+\r
+ // Get start and end blocks\r
+ sb = t.getParentBlock(sn);\r
+ eb = t.getParentBlock(en);\r
+ bn = sb ? sb.nodeName : se.element; // Get block name to create\r
+\r
+ // Return inside list use default browser behavior\r
+ if (n = t.dom.getParent(sb, 'li,pre')) {\r
+ if (n.nodeName == 'LI')\r
+ return splitList(ed.selection, t.dom, n);\r
+\r
+ return TRUE;\r
+ }\r
+\r
+ // If caption or absolute layers then always generate new blocks within\r
+ if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {\r
+ bn = se.element;\r
+ sb = null;\r
+ }\r
+\r
+ // If caption or absolute layers then always generate new blocks within\r
+ if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {\r
+ bn = se.element;\r
+ eb = null;\r
+ }\r
+\r
+ // Use P instead\r
+ if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {\r
+ bn = se.element;\r
+ sb = eb = null;\r
+ }\r
+\r
+ // Setup new before and after blocks\r
+ bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);\r
+ aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);\r
+\r
+ // Remove id from after clone\r
+ aft.removeAttribute('id');\r
+\r
+ // Is header and cursor is at the end, then force paragraph under\r
+ if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))\r
+ aft = ed.dom.create(se.element);\r
+\r
+ // Find start chop node\r
+ n = sc = sn;\r
+ do {\r
+ if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))\r
+ break;\r
+\r
+ sc = n;\r
+ } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));\r
+\r
+ // Find end chop node\r
+ n = ec = en;\r
+ do {\r
+ if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))\r
+ break;\r
+\r
+ ec = n;\r
+ } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));\r
+\r
+ // Place first chop part into before block element\r
+ if (sc.nodeName == bn)\r
+ rb.setStart(sc, 0);\r
+ else\r
+ rb.setStartBefore(sc);\r
+\r
+ rb.setEnd(sn, so);\r
+ bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari\r
+\r
+ // Place secnd chop part within new block element\r
+ try {\r
+ ra.setEndAfter(ec);\r
+ } catch(ex) {\r
+ //console.debug(s.focusNode, s.focusOffset);\r
+ }\r
+\r
+ ra.setStart(en, eo);\r
+ aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari\r
+\r
+ // Create range around everything\r
+ r = d.createRange();\r
+ if (!sc.previousSibling && sc.parentNode.nodeName == bn) {\r
+ r.setStartBefore(sc.parentNode);\r
+ } else {\r
+ if (rb.startContainer.nodeName == bn && rb.startOffset == 0)\r
+ r.setStartBefore(rb.startContainer);\r
+ else\r
+ r.setStart(rb.startContainer, rb.startOffset);\r
+ }\r
+\r
+ if (!ec.nextSibling && ec.parentNode.nodeName == bn)\r
+ r.setEndAfter(ec.parentNode);\r
+ else\r
+ r.setEnd(ra.endContainer, ra.endOffset);\r
+\r
+ // Delete and replace it with new block elements\r
+ r.deleteContents();\r
+\r
+ if (isOpera)\r
+ ed.getWin().scrollTo(0, vp.y);\r
+\r
+ // Never wrap blocks in blocks\r
+ if (bef.firstChild && bef.firstChild.nodeName == bn)\r
+ bef.innerHTML = bef.firstChild.innerHTML;\r
+\r
+ if (aft.firstChild && aft.firstChild.nodeName == bn)\r
+ aft.innerHTML = aft.firstChild.innerHTML;\r
+\r
+ function appendStyles(e, en) {\r
+ var nl = [], nn, n, i;\r
+\r
+ e.innerHTML = '';\r
+\r
+ // Make clones of style elements\r
+ if (se.keep_styles) {\r
+ n = en;\r
+ do {\r
+ // We only want style specific elements\r
+ if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {\r
+ nn = n.cloneNode(FALSE);\r
+ dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique\r
+ nl.push(nn);\r
+ }\r
+ } while (n = n.parentNode);\r
+ }\r
+\r
+ // Append style elements to aft\r
+ if (nl.length > 0) {\r
+ for (i = nl.length - 1, nn = e; i >= 0; i--)\r
+ nn = nn.appendChild(nl[i]);\r
+\r
+ // Padd most inner style element\r
+ nl[0].innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there\r
+ return nl[0]; // Move caret to most inner element\r
+ } else\r
+ e.innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there\r
+ };\r
+ \r
+ // Padd empty blocks\r
+ if (dom.isEmpty(bef))\r
+ appendStyles(bef, sn);\r
+\r
+ // Fill empty afterblook with current style\r
+ if (dom.isEmpty(aft))\r
+ car = appendStyles(aft, en);\r
+\r
+ // Opera needs this one backwards for older versions\r
+ if (isOpera && parseFloat(opera.version()) < 9.5) {\r
+ r.insertNode(bef);\r
+ r.insertNode(aft);\r
+ } else {\r
+ r.insertNode(aft);\r
+ r.insertNode(bef);\r
+ }\r
+\r
+ // Normalize\r
+ aft.normalize();\r
+ bef.normalize();\r
+\r
+ // Move cursor and scroll into view\r
+ ed.selection.select(aft, true);\r
+ ed.selection.collapse(true);\r
+\r
+ // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs\r
+ y = ed.dom.getPos(aft).y;\r
+ //ch = aft.clientHeight;\r
+\r
+ // Is element within viewport\r
+ if (y < vp.y || y + 25 > vp.y + vp.h) {\r
+ ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks\r
+\r
+ /*console.debug(\r
+ 'Element: y=' + y + ', h=' + ch + ', ' +\r
+ 'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h)\r
+ );*/\r
+ }\r
+\r
+ ed.undoManager.add();\r
+\r
+ return FALSE;\r
+ },\r
+\r
+ backspaceDelete : function(e, bs) {\r
+ var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn, walker;\r
+\r
+ // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651\r
+ if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {\r
+ walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);\r
+\r
+ // Walk the dom backwards until we find a text node\r
+ for (n = sc.lastChild; n; n = walker.prev()) {\r
+ if (n.nodeType == 3) {\r
+ r.setStart(n, n.nodeValue.length);\r
+ r.collapse(true);\r
+ se.setRng(r);\r
+ return;\r
+ }\r
+ }\r
+ }\r
+\r
+ // The caret sometimes gets stuck in Gecko if you delete empty paragraphs\r
+ // This workaround removes the element by hand and moves the caret to the previous element\r
+ if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {\r
+ if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {\r
+ // Find previous block element\r
+ n = sc;\r
+ while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;\r
+\r
+ if (n) {\r
+ if (sc != b.firstChild) {\r
+ // Find last text node\r
+ w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);\r
+ while (tn = w.nextNode())\r
+ n = tn;\r
+\r
+ // Place caret at the end of last text node\r
+ r = ed.getDoc().createRange();\r
+ r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);\r
+ r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);\r
+ se.setRng(r);\r
+\r
+ // Remove the target container\r
+ ed.dom.remove(sc);\r
+ }\r
+\r
+ return Event.cancel(e);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ });\r
+})(tinymce);\r
+\r
+(function(tinymce) {\r
+ // Shorten names\r
+ var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;\r
+\r
+ tinymce.create('tinymce.ControlManager', {\r
+ ControlManager : function(ed, s) {\r
+ var t = this, i;\r
+\r
+ s = s || {};\r
+ t.editor = ed;\r
+ t.controls = {};\r
+ t.onAdd = new tinymce.util.Dispatcher(t);\r
+ t.onPostRender = new tinymce.util.Dispatcher(t);\r
+ t.prefix = s.prefix || ed.id + '_';\r
+ t._cls = {};\r
+\r
+ t.onPostRender.add(function() {\r
+ each(t.controls, function(c) {\r
+ c.postRender();\r
+ });\r
+ });\r
+ },\r
+\r
+ get : function(id) {\r
+ return this.controls[this.prefix + id] || this.controls[id];\r
+ },\r
+\r
+ setActive : function(id, s) {\r
+ var c = null;\r
+\r
+ if (c = this.get(id))\r
+ c.setActive(s);\r
+\r
+ return c;\r
+ },\r
+\r
+ setDisabled : function(id, s) {\r
+ var c = null;\r
+\r
+ if (c = this.get(id))\r
+ c.setDisabled(s);\r
+\r
+ return c;\r
+ },\r
+\r
+ add : function(c) {\r
+ var t = this;\r
+\r
+ if (c) {\r
+ t.controls[c.id] = c;\r
+ t.onAdd.dispatch(c, t);\r
+ }\r
+\r
+ return c;\r
+ },\r
+\r
+ createControl : function(n) {\r
+ var c, t = this, ed = t.editor;\r
+\r
+ each(ed.plugins, function(p) {\r
+ if (p.createControl) {\r
+ c = p.createControl(n, t);\r
+\r
+ if (c)\r
+ return false;\r
+ }\r
+ });\r
+\r
+ switch (n) {\r
+ case "|":\r
+ case "separator":\r
+ return t.createSeparator();\r
+ }\r
+\r
+ if (!c && ed.buttons && (c = ed.buttons[n]))\r
+ return t.createButton(n, c);\r
+\r
+ return t.add(c);\r
+ },\r
+\r
+ createDropMenu : function(id, s, cc) {\r
+ var t = this, ed = t.editor, c, bm, v, cls;\r
+\r
+ s = extend({\r
+ 'class' : 'mceDropDown',\r
+ constrain : ed.settings.constrain_menus\r
+ }, s);\r
+\r
+ s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';\r
+ if (v = ed.getParam('skin_variant'))\r
+ s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);\r
+\r
+ id = t.prefix + id;\r
+ cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;\r
+ c = t.controls[id] = new cls(id, s);\r
+ c.onAddItem.add(function(c, o) {\r
+ var s = o.settings;\r
+\r
+ s.title = ed.getLang(s.title, s.title);\r
+\r
+ if (!s.onclick) {\r
+ s.onclick = function(v) {\r
+ if (s.cmd)\r
+ ed.execCommand(s.cmd, s.ui || false, s.value);\r
+ };\r
+ }\r
+ });\r
+\r
+ ed.onRemove.add(function() {\r
+ c.destroy();\r
+ });\r
+\r
+ // Fix for bug #1897785, #1898007\r
+ if (tinymce.isIE) {\r
+ c.onShowMenu.add(function() {\r
+ // IE 8 needs focus in order to store away a range with the current collapsed caret location\r
+ ed.focus();\r
+\r
+ bm = ed.selection.getBookmark(1);\r
+ });\r
+\r
+ c.onHideMenu.add(function() {\r
+ if (bm) {\r
+ ed.selection.moveToBookmark(bm);\r
+ bm = 0;\r
+ }\r
+ });\r
+ }\r
+\r
+ return t.add(c);\r
+ },\r
+\r
+ createListBox : function(id, s, cc) {\r
+ var t = this, ed = t.editor, cmd, c, cls;\r
+\r
+ if (t.get(id))\r
+ return null;\r
+\r
+ s.title = ed.translate(s.title);\r
+ s.scope = s.scope || ed;\r
+\r
+ if (!s.onselect) {\r
+ s.onselect = function(v) {\r
+ ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
+ };\r
+ }\r
+\r
+ s = extend({\r
+ title : s.title,\r
+ 'class' : 'mce_' + id,\r
+ scope : s.scope,\r
+ control_manager : t\r
+ }, s);\r
+\r
+ id = t.prefix + id;\r
+\r
+ if (ed.settings.use_native_selects)\r
+ c = new tinymce.ui.NativeListBox(id, s);\r
+ else {\r
+ cls = cc || t._cls.listbox || tinymce.ui.ListBox;\r
+ c = new cls(id, s, ed);\r
+ }\r
+\r
+ t.controls[id] = c;\r
+\r
+ // Fix focus problem in Safari\r
+ if (tinymce.isWebKit) {\r
+ c.onPostRender.add(function(c, n) {\r
+ // Store bookmark on mousedown\r
+ Event.add(n, 'mousedown', function() {\r
+ ed.bookmark = ed.selection.getBookmark(1);\r
+ });\r
+\r
+ // Restore on focus, since it might be lost\r
+ Event.add(n, 'focus', function() {\r
+ ed.selection.moveToBookmark(ed.bookmark);\r
+ ed.bookmark = null;\r
+ });\r
+ });\r
+ }\r
+\r
+ if (c.hideMenu)\r
+ ed.onMouseDown.add(c.hideMenu, c);\r
+\r
+ return t.add(c);\r
+ },\r
+\r
+ createButton : function(id, s, cc) {\r
+ var t = this, ed = t.editor, o, c, cls;\r
+\r
+ if (t.get(id))\r
+ return null;\r
+\r
+ s.title = ed.translate(s.title);\r
+ s.label = ed.translate(s.label);\r
+ s.scope = s.scope || ed;\r
+\r
+ if (!s.onclick && !s.menu_button) {\r
+ s.onclick = function() {\r
+ ed.execCommand(s.cmd, s.ui || false, s.value);\r
+ };\r
+ }\r
+\r
+ s = extend({\r
+ title : s.title,\r
+ 'class' : 'mce_' + id,\r
+ unavailable_prefix : ed.getLang('unavailable', ''),\r
+ scope : s.scope,\r
+ control_manager : t\r
+ }, s);\r
+\r
+ id = t.prefix + id;\r
+\r
+ if (s.menu_button) {\r
+ cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;\r
+ c = new cls(id, s, ed);\r
+ ed.onMouseDown.add(c.hideMenu, c);\r
+ } else {\r
+ cls = t._cls.button || tinymce.ui.Button;\r
+ c = new cls(id, s, ed);\r
+ }\r
+\r
+ return t.add(c);\r
+ },\r
+\r
+ createMenuButton : function(id, s, cc) {\r
+ s = s || {};\r
+ s.menu_button = 1;\r
+\r
+ return this.createButton(id, s, cc);\r
+ },\r
+\r
+ createSplitButton : function(id, s, cc) {\r
+ var t = this, ed = t.editor, cmd, c, cls;\r
+\r
+ if (t.get(id))\r
+ return null;\r
+\r
+ s.title = ed.translate(s.title);\r
+ s.scope = s.scope || ed;\r
+\r
+ if (!s.onclick) {\r
+ s.onclick = function(v) {\r
+ ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
+ };\r
+ }\r
+\r
+ if (!s.onselect) {\r
+ s.onselect = function(v) {\r
+ ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
+ };\r
+ }\r
+\r
+ s = extend({\r
+ title : s.title,\r
+ 'class' : 'mce_' + id,\r
+ scope : s.scope,\r
+ control_manager : t\r
+ }, s);\r
+\r
+ id = t.prefix + id;\r
+ cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;\r
+ c = t.add(new cls(id, s, ed));\r
+ ed.onMouseDown.add(c.hideMenu, c);\r
+\r
+ return c;\r
+ },\r
+\r
+ createColorSplitButton : function(id, s, cc) {\r
+ var t = this, ed = t.editor, cmd, c, cls, bm;\r
+\r
+ if (t.get(id))\r
+ return null;\r
+\r
+ s.title = ed.translate(s.title);\r
+ s.scope = s.scope || ed;\r
+\r
+ if (!s.onclick) {\r
+ s.onclick = function(v) {\r
+ if (tinymce.isIE)\r
+ bm = ed.selection.getBookmark(1);\r
+\r
+ ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
+ };\r
+ }\r
+\r
+ if (!s.onselect) {\r
+ s.onselect = function(v) {\r
+ ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
+ };\r
+ }\r
+\r
+ s = extend({\r
+ title : s.title,\r
+ 'class' : 'mce_' + id,\r
+ 'menu_class' : ed.getParam('skin') + 'Skin',\r
+ scope : s.scope,\r
+ more_colors_title : ed.getLang('more_colors')\r
+ }, s);\r
+\r
+ id = t.prefix + id;\r
+ cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;\r
+ c = new cls(id, s, ed);\r
+ ed.onMouseDown.add(c.hideMenu, c);\r
+\r
+ // Remove the menu element when the editor is removed\r
+ ed.onRemove.add(function() {\r
+ c.destroy();\r
+ });\r
+\r
+ // Fix for bug #1897785, #1898007\r
+ if (tinymce.isIE) {\r
+ c.onShowMenu.add(function() {\r
+ // IE 8 needs focus in order to store away a range with the current collapsed caret location\r
+ ed.focus();\r
+ bm = ed.selection.getBookmark(1);\r
+ });\r
+\r
+ c.onHideMenu.add(function() {\r
+ if (bm) {\r
+ ed.selection.moveToBookmark(bm);\r
+ bm = 0;\r
+ }\r
+ });\r
+ }\r
+\r
+ return t.add(c);\r
+ },\r
+\r
+ createToolbar : function(id, s, cc) {\r
+ var c, t = this, cls;\r
+\r
+ id = t.prefix + id;\r
+ cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;\r
+ c = new cls(id, s, t.editor);\r
+\r
+ if (t.get(id))\r
+ return null;\r
+\r
+ return t.add(c);\r
+ },\r
+ \r
+ createToolbarGroup : function(id, s, cc) {\r
+ var c, t = this, cls;\r
+ id = t.prefix + id;\r
+ cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;\r
+ c = new cls(id, s, t.editor);\r
+ \r
+ if (t.get(id))\r
+ return null;\r
+ \r
+ return t.add(c);\r
+ },\r
+\r
+ createSeparator : function(cc) {\r
+ var cls = cc || this._cls.separator || tinymce.ui.Separator;\r
+\r
+ return new cls();\r
+ },\r
+\r
+ setControlType : function(n, c) {\r
+ return this._cls[n.toLowerCase()] = c;\r
+ },\r
+ \r
+ destroy : function() {\r
+ each(this.controls, function(c) {\r
+ c.destroy();\r
+ });\r
+\r
+ this.controls = null;\r
+ }\r
+ });\r
+})(tinymce);\r
+\r
+(function(tinymce) {\r
+ var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;\r
+\r
+ tinymce.create('tinymce.WindowManager', {\r
+ WindowManager : function(ed) {\r
+ var t = this;\r
+\r
+ t.editor = ed;\r
+ t.onOpen = new Dispatcher(t);\r
+ t.onClose = new Dispatcher(t);\r
+ t.params = {};\r
+ t.features = {};\r
+ },\r
+\r
+ open : function(s, p) {\r
+ var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;\r
+\r
+ // Default some options\r
+ s = s || {};\r
+ p = p || {};\r
+ sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window\r
+ sh = isOpera ? vp.h : screen.height;\r
+ s.name = s.name || 'mc_' + new Date().getTime();\r
+ s.width = parseInt(s.width || 320);\r
+ s.height = parseInt(s.height || 240);\r
+ s.resizable = true;\r
+ s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);\r
+ s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);\r
+ p.inline = false;\r
+ p.mce_width = s.width;\r
+ p.mce_height = s.height;\r
+ p.mce_auto_focus = s.auto_focus;\r
+\r
+ if (mo) {\r
+ if (isIE) {\r
+ s.center = true;\r
+ s.help = false;\r
+ s.dialogWidth = s.width + 'px';\r
+ s.dialogHeight = s.height + 'px';\r
+ s.scroll = s.scrollbars || false;\r
+ }\r
+ }\r
+\r
+ // Build features string\r
+ each(s, function(v, k) {\r
+ if (tinymce.is(v, 'boolean'))\r
+ v = v ? 'yes' : 'no';\r
+\r
+ if (!/^(name|url)$/.test(k)) {\r
+ if (isIE && mo)\r
+ f += (f ? ';' : '') + k + ':' + v;\r
+ else\r
+ f += (f ? ',' : '') + k + '=' + v;\r
+ }\r
+ });\r
+\r
+ t.features = s;\r
+ t.params = p;\r
+ t.onOpen.dispatch(t, s, p);\r
+\r
+ u = s.url || s.file;\r
+ u = tinymce._addVer(u);\r
+\r
+ try {\r
+ if (isIE && mo) {\r
+ w = 1;\r
+ window.showModalDialog(u, window, f);\r
+ } else\r
+ w = window.open(u, s.name, f);\r
+ } catch (ex) {\r
+ // Ignore\r
+ }\r
+\r
+ if (!w)\r
+ alert(t.editor.getLang('popup_blocked'));\r
+ },\r
+\r
+ close : function(w) {\r
+ w.close();\r
+ this.onClose.dispatch(this);\r
+ },\r
+\r
+ createInstance : function(cl, a, b, c, d, e) {\r
+ var f = tinymce.resolve(cl);\r
+\r
+ return new f(a, b, c, d, e);\r
+ },\r
+\r
+ confirm : function(t, cb, s, w) {\r
+ w = w || window;\r
+\r
+ cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));\r
+ },\r
+\r
+ alert : function(tx, cb, s, w) {\r
+ var t = this;\r
+\r
+ w = w || window;\r
+ w.alert(t._decode(t.editor.getLang(tx, tx)));\r
+\r
+ if (cb)\r
+ cb.call(s || t);\r
+ },\r
+\r
+ resizeBy : function(dw, dh, win) {\r
+ win.resizeBy(dw, dh);\r
+ },\r
+\r
+ // Internal functions\r
+\r
+ _decode : function(s) {\r
+ return tinymce.DOM.decode(s).replace(/\\n/g, '\n');\r
+ }\r
+ });\r
+}(tinymce));\r
+(function(tinymce) {\r
+ tinymce.Formatter = function(ed) {\r
+ var formats = {},\r
+ each = tinymce.each,\r
+ dom = ed.dom,\r
+ selection = ed.selection,\r
+ TreeWalker = tinymce.dom.TreeWalker,\r
+ rangeUtils = new tinymce.dom.RangeUtils(dom),\r
+ isValid = ed.schema.isValidChild,\r
+ isBlock = dom.isBlock,\r
+ forcedRootBlock = ed.settings.forced_root_block,\r
+ nodeIndex = dom.nodeIndex,\r
+ INVISIBLE_CHAR = '\uFEFF',\r
+ MCE_ATTR_RE = /^(src|href|style)$/,\r
+ FALSE = false,\r
+ TRUE = true,\r
+ undefined,\r
+ pendingFormats = {apply : [], remove : []};\r
+\r
+ function isArray(obj) {\r
+ return obj instanceof Array;\r
+ };\r
+\r
+ function getParents(node, selector) {\r
+ return dom.getParents(node, selector, dom.getRoot());\r
+ };\r
+\r
+ function isCaretNode(node) {\r
+ return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline');\r
+ };\r
+\r
+ // Public functions\r
+\r
+ function get(name) {\r
+ return name ? formats[name] : formats;\r
+ };\r
+\r
+ function register(name, format) {\r
+ if (name) {\r
+ if (typeof(name) !== 'string') {\r
+ each(name, function(format, name) {\r
+ register(name, format);\r
+ });\r
+ } else {\r
+ // Force format into array and add it to internal collection\r
+ format = format.length ? format : [format];\r
+\r
+ each(format, function(format) {\r
+ // Set deep to false by default on selector formats this to avoid removing\r
+ // alignment on images inside paragraphs when alignment is changed on paragraphs\r
+ if (format.deep === undefined)\r
+ format.deep = !format.selector;\r
+\r
+ // Default to true\r
+ if (format.split === undefined)\r
+ format.split = !format.selector || format.inline;\r
+\r
+ // Default to true\r
+ if (format.remove === undefined && format.selector && !format.inline)\r
+ format.remove = 'none';\r
+\r
+ // Mark format as a mixed format inline + block level\r
+ if (format.selector && format.inline) {\r
+ format.mixed = true;\r
+ format.block_expand = true;\r
+ }\r
+\r
+ // Split classes if needed\r
+ if (typeof(format.classes) === 'string')\r
+ format.classes = format.classes.split(/\s+/);\r
+ });\r
+\r
+ formats[name] = format;\r
+ }\r
+ }\r
+ };\r
+\r
+ var getTextDecoration = function(node) {\r
+ var decoration;\r
+\r
+ ed.dom.getParent(node, function(n) {\r
+ decoration = ed.dom.getStyle(n, 'text-decoration');\r
+ return decoration && decoration !== 'none';\r
+ });\r
+\r
+ return decoration;\r
+ };\r
+\r
+ var processUnderlineAndColor = function(node) {\r
+ var textDecoration;\r
+ if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {\r
+ textDecoration = getTextDecoration(node.parentNode);\r
+ if (ed.dom.getStyle(node, 'color') && textDecoration) {\r
+ ed.dom.setStyle(node, 'text-decoration', textDecoration);\r
+ } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {\r
+ ed.dom.setStyle(node, 'text-decoration', null);\r
+ }\r
+ }\r
+ };\r
+\r
+ function apply(name, vars, node) {\r
+ var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();\r
+\r
+ function moveStart(rng) {\r
+ var container = rng.startContainer,\r
+ offset = rng.startOffset,\r
+ walker, node;\r
+\r
+ // Move startContainer/startOffset in to a suitable node\r
+ if (container.nodeType == 1 || container.nodeValue === "") {\r
+ container = container.nodeType == 1 ? container.childNodes[offset] : container;\r
+\r
+ // Might fail if the offset is behind the last element in it's container\r
+ if (container) {\r
+ walker = new TreeWalker(container, container.parentNode);\r
+ for (node = walker.current(); node; node = walker.next()) {\r
+ if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {\r
+ rng.setStart(node, 0);\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ return rng;\r
+ };\r
+\r
+ function setElementFormat(elm, fmt) {\r
+ fmt = fmt || format;\r
+\r
+ if (elm) {\r
+ if (fmt.onformat) {\r
+ fmt.onformat(elm, fmt, vars, node);\r
+ }\r
+\r
+ each(fmt.styles, function(value, name) {\r
+ dom.setStyle(elm, name, replaceVars(value, vars));\r
+ });\r
+\r
+ each(fmt.attributes, function(value, name) {\r
+ dom.setAttrib(elm, name, replaceVars(value, vars));\r
+ });\r
+\r
+ each(fmt.classes, function(value) {\r
+ value = replaceVars(value, vars);\r
+\r
+ if (!dom.hasClass(elm, value))\r
+ dom.addClass(elm, value);\r
+ });\r
+ }\r
+ };\r
+ function adjustSelectionToVisibleSelection() {\r
+ function findSelectionEnd(start, end) {\r
+ var walker = new TreeWalker(end);\r
+ for (node = walker.current(); node; node = walker.prev()) {\r
+ if (node.childNodes.length > 1 || node == start) {\r
+ return node;\r
+ }\r
+ }\r
+ };\r
+\r
+ // Adjust selection so that a end container with a end offset of zero is not included in the selection\r
+ // as this isn't visible to the user.\r
+ var rng = ed.selection.getRng();\r
+ var start = rng.startContainer;\r
+ var end = rng.endContainer;\r
+\r
+ if (start != end && rng.endOffset == 0) {\r
+ var newEnd = findSelectionEnd(start, end);\r
+ var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;\r
+\r
+ rng.setEnd(newEnd, endOffset);\r
+ }\r
+\r
+ return rng;\r
+ }\r
+ \r
+ function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){\r
+ var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;\r
+ \r
+ // find the index of the first child list.\r
+ each(node.childNodes, function(n, index) {\r
+ if (n.nodeName === "UL" || n.nodeName === "OL") {\r
+ listIndex = index;\r
+ list = n;\r
+ return false;\r
+ }\r
+ });\r
+ \r
+ // get the index of the bookmarks\r
+ each(node.childNodes, function(n, index) {\r
+ if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {\r
+ if (n.id == bookmark.id + "_start") {\r
+ startIndex = index;\r
+ } else if (n.id == bookmark.id + "_end") {\r
+ endIndex = index;\r
+ }\r
+ }\r
+ });\r
+ \r
+ // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally\r
+ if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {\r
+ each(tinymce.grep(node.childNodes), process);\r
+ return 0;\r
+ } else {\r
+ currentWrapElm = wrapElm.cloneNode(FALSE);\r
+ \r
+ // create a list of the nodes on the same side of the list as the selection\r
+ each(tinymce.grep(node.childNodes), function(n, index) {\r
+ if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {\r
+ nodes.push(n); \r
+ n.parentNode.removeChild(n);\r
+ }\r
+ });\r
+ \r
+ // insert the wrapping element either before or after the list.\r
+ if (startIndex < listIndex) {\r
+ node.insertBefore(currentWrapElm, list);\r
+ } else if (startIndex > listIndex) {\r
+ node.insertBefore(currentWrapElm, list.nextSibling);\r
+ }\r
+ \r
+ // add the new nodes to the list.\r
+ newWrappers.push(currentWrapElm);\r
+\r
+ each(nodes, function(node) {\r
+ currentWrapElm.appendChild(node);\r
+ });\r
+\r
+ return currentWrapElm;\r
+ }\r
+ };\r
+ \r
+ function applyRngStyle(rng, bookmark) {\r
+ var newWrappers = [], wrapName, wrapElm;\r
+\r
+ // Setup wrapper element\r
+ wrapName = format.inline || format.block;\r
+ wrapElm = dom.create(wrapName);\r
+ setElementFormat(wrapElm);\r
+\r
+ rangeUtils.walk(rng, function(nodes) {\r
+ var currentWrapElm;\r
+\r
+ function process(node) {\r
+ var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;\r
+\r
+ // Stop wrapping on br elements\r
+ if (isEq(nodeName, 'br')) {\r
+ currentWrapElm = 0;\r
+\r
+ // Remove any br elements when we wrap things\r
+ if (format.block)\r
+ dom.remove(node);\r
+\r
+ return;\r
+ }\r
+\r
+ // If node is wrapper type\r
+ if (format.wrapper && matchNode(node, name, vars)) {\r
+ currentWrapElm = 0;\r
+ return;\r
+ }\r
+\r
+ // Can we rename the block\r
+ if (format.block && !format.wrapper && isTextBlock(nodeName)) {\r
+ node = dom.rename(node, wrapName);\r
+ setElementFormat(node);\r
+ newWrappers.push(node);\r
+ currentWrapElm = 0;\r
+ return;\r
+ }\r
+\r
+ // Handle selector patterns\r
+ if (format.selector) {\r
+ // Look for matching formats\r
+ each(formatList, function(format) {\r
+ // Check collapsed state if it exists\r
+ if ('collapsed' in format && format.collapsed !== isCollapsed) {\r
+ return;\r
+ }\r
+\r
+ if (dom.is(node, format.selector) && !isCaretNode(node)) {\r
+ setElementFormat(node, format);\r
+ found = true;\r
+ }\r
+ });\r
+\r
+ // Continue processing if a selector match wasn't found and a inline element is defined\r
+ if (!format.inline || found) {\r
+ currentWrapElm = 0;\r
+ return;\r
+ }\r
+ }\r
+\r
+ // Is it valid to wrap this item\r
+ if (isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&\r
+ !(node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279)) {\r
+ // Start wrapping\r
+ if (!currentWrapElm) {\r
+ // Wrap the node\r
+ currentWrapElm = wrapElm.cloneNode(FALSE);\r
+ node.parentNode.insertBefore(currentWrapElm, node);\r
+ newWrappers.push(currentWrapElm);\r
+ }\r
+\r
+ currentWrapElm.appendChild(node);\r
+ } else if (nodeName == 'li' && bookmark) {\r
+ // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element.\r
+ currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);\r
+ } else {\r
+ // Start a new wrapper for possible children\r
+ currentWrapElm = 0;\r
+\r
+ each(tinymce.grep(node.childNodes), process);\r
+\r
+ // End the last wrapper\r
+ currentWrapElm = 0;\r
+ }\r
+ };\r
+\r
+ // Process siblings from range\r
+ each(nodes, process);\r
+ });\r
+\r
+ // Wrap links inside as well, for example color inside a link when the wrapper is around the link\r
+ if (format.wrap_links === false) {\r
+ each(newWrappers, function(node) {\r
+ function process(node) {\r
+ var i, currentWrapElm, children;\r
+\r
+ if (node.nodeName === 'A') {\r
+ currentWrapElm = wrapElm.cloneNode(FALSE);\r
+ newWrappers.push(currentWrapElm);\r
+\r
+ children = tinymce.grep(node.childNodes);\r
+ for (i = 0; i < children.length; i++)\r
+ currentWrapElm.appendChild(children[i]);\r
+\r
+ node.appendChild(currentWrapElm);\r
+ }\r
+\r
+ each(tinymce.grep(node.childNodes), process);\r
+ };\r
+\r
+ process(node);\r
+ });\r
+ }\r
+\r
+ // Cleanup\r
+ each(newWrappers, function(node) {\r
+ var childCount;\r
+\r
+ function getChildCount(node) {\r
+ var count = 0;\r
+\r
+ each(node.childNodes, function(node) {\r
+ if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))\r
+ count++;\r
+ });\r
+\r
+ return count;\r
+ };\r
+\r
+ function mergeStyles(node) {\r
+ var child, clone;\r
+\r
+ each(node.childNodes, function(node) {\r
+ if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {\r
+ child = node;\r
+ return FALSE; // break loop\r
+ }\r
+ });\r
+\r
+ // If child was found and of the same type as the current node\r
+ if (child && matchName(child, format)) {\r
+ clone = child.cloneNode(FALSE);\r
+ setElementFormat(clone);\r
+\r
+ dom.replace(clone, node, TRUE);\r
+ dom.remove(child, 1);\r
+ }\r
+\r
+ return clone || node;\r
+ };\r
+\r
+ childCount = getChildCount(node);\r
+\r
+ // Remove empty nodes but only if there is multiple wrappers and they are not block\r
+ // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at\r
+ if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {\r
+ dom.remove(node, 1);\r
+ return;\r
+ }\r
+\r
+ if (format.inline || format.wrapper) {\r
+ // Merges the current node with it's children of similar type to reduce the number of elements\r
+ if (!format.exact && childCount === 1)\r
+ node = mergeStyles(node);\r
+\r
+ // Remove/merge children\r
+ each(formatList, function(format) {\r
+ // Merge all children of similar type will move styles from child to parent\r
+ // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>\r
+ // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>\r
+ each(dom.select(format.inline, node), function(child) {\r
+ var parent;\r
+\r
+ // When wrap_links is set to false we don't want\r
+ // to remove the format on children within links\r
+ if (format.wrap_links === false) {\r
+ parent = child.parentNode;\r
+\r
+ do {\r
+ if (parent.nodeName === 'A')\r
+ return;\r
+ } while (parent = parent.parentNode);\r
+ }\r
+\r
+ removeFormat(format, vars, child, format.exact ? child : null);\r
+ });\r
+ });\r
+\r
+ // Remove child if direct parent is of same type\r
+ if (matchNode(node.parentNode, name, vars)) {\r
+ dom.remove(node, 1);\r
+ node = 0;\r
+ return TRUE;\r
+ }\r
+\r
+ // Look for parent with similar style format\r
+ if (format.merge_with_parents) {\r
+ dom.getParent(node.parentNode, function(parent) {\r
+ if (matchNode(parent, name, vars)) {\r
+ dom.remove(node, 1);\r
+ node = 0;\r
+ return TRUE;\r
+ }\r
+ });\r
+ }\r
+\r
+ // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>\r
+ if (node && format.merge_siblings !== false) {\r
+ node = mergeSiblings(getNonWhiteSpaceSibling(node), node);\r
+ node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));\r
+ }\r
+ }\r
+ });\r
+ };\r
+\r
+ if (format) {\r
+ if (node) {\r
+ rng = dom.createRng();\r
+\r
+ rng.setStartBefore(node);\r
+ rng.setEndAfter(node);\r
+\r
+ applyRngStyle(expandRng(rng, formatList));\r
+ } else {\r
+ if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {\r
+ // Obtain selection node before selection is unselected by applyRngStyle()\r
+ var curSelNode = ed.selection.getNode();\r
+\r
+ // Apply formatting to selection\r
+ ed.selection.setRng(adjustSelectionToVisibleSelection());\r
+ bookmark = selection.getBookmark();\r
+ applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);\r
+\r
+ // Colored nodes should be underlined so that the color of the underline matches the text color.\r
+ if (format.styles && (format.styles.color || format.styles.textDecoration)) {\r
+ tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');\r
+ processUnderlineAndColor(curSelNode);\r
+ }\r
+\r
+ selection.moveToBookmark(bookmark);\r
+ selection.setRng(moveStart(selection.getRng(TRUE)));\r
+ ed.nodeChanged();\r
+ } else\r
+ performCaretAction('apply', name, vars);\r
+ }\r
+ }\r
+ };\r
+\r
+ function remove(name, vars, node) {\r
+ var formatList = get(name), format = formatList[0], bookmark, i, rng;\r
+ function moveStart(rng) {\r
+ var container = rng.startContainer,\r
+ offset = rng.startOffset,\r
+ walker, node, nodes, tmpNode;\r
+\r
+ // Convert text node into index if possible\r
+ if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {\r
+ container = container.parentNode;\r
+ offset = nodeIndex(container) + 1;\r
+ }\r
+\r
+ // Move startContainer/startOffset in to a suitable node\r
+ if (container.nodeType == 1) {\r
+ nodes = container.childNodes;\r
+ container = nodes[Math.min(offset, nodes.length - 1)];\r
+ walker = new TreeWalker(container);\r
+\r
+ // If offset is at end of the parent node walk to the next one\r
+ if (offset > nodes.length - 1)\r
+ walker.next();\r
+\r
+ for (node = walker.current(); node; node = walker.next()) {\r
+ if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {\r
+ // IE has a "neat" feature where it moves the start node into the closest element\r
+ // we can avoid this by inserting an element before it and then remove it after we set the selection\r
+ tmpNode = dom.create('a', null, INVISIBLE_CHAR);\r
+ node.parentNode.insertBefore(tmpNode, node);\r
+\r
+ // Set selection and remove tmpNode\r
+ rng.setStart(node, 0);\r
+ selection.setRng(rng);\r
+ dom.remove(tmpNode);\r
+\r
+ return;\r
+ }\r
+ }\r
+ }\r
+ };\r
+\r
+ // Merges the styles for each node\r
+ function process(node) {\r
+ var children, i, l;\r
+\r
+ // Grab the children first since the nodelist might be changed\r
+ children = tinymce.grep(node.childNodes);\r
+\r
+ // Process current node\r
+ for (i = 0, l = formatList.length; i < l; i++) {\r
+ if (removeFormat(formatList[i], vars, node, node))\r
+ break;\r
+ }\r
+\r
+ // Process the children\r
+ if (format.deep) {\r
+ for (i = 0, l = children.length; i < l; i++)\r
+ process(children[i]);\r
+ }\r
+ };\r
+\r
+ function findFormatRoot(container) {\r
+ var formatRoot;\r
+\r
+ // Find format root\r
+ each(getParents(container.parentNode).reverse(), function(parent) {\r
+ var format;\r
+\r
+ // Find format root element\r
+ if (!formatRoot && parent.id != '_start' && parent.id != '_end') {\r
+ // Is the node matching the format we are looking for\r
+ format = matchNode(parent, name, vars);\r
+ if (format && format.split !== false)\r
+ formatRoot = parent;\r
+ }\r
+ });\r
+\r
+ return formatRoot;\r
+ };\r
+\r
+ function wrapAndSplit(format_root, container, target, split) {\r
+ var parent, clone, lastClone, firstClone, i, formatRootParent;\r
+\r
+ // Format root found then clone formats and split it\r
+ if (format_root) {\r
+ formatRootParent = format_root.parentNode;\r
+\r
+ for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {\r
+ clone = parent.cloneNode(FALSE);\r
+\r
+ for (i = 0; i < formatList.length; i++) {\r
+ if (removeFormat(formatList[i], vars, clone, clone)) {\r
+ clone = 0;\r
+ break;\r
+ }\r
+ }\r
+\r
+ // Build wrapper node\r
+ if (clone) {\r
+ if (lastClone)\r
+ clone.appendChild(lastClone);\r
+\r
+ if (!firstClone)\r
+ firstClone = clone;\r
+\r
+ lastClone = clone;\r
+ }\r
+ }\r
+\r
+ // Never split block elements if the format is mixed\r
+ if (split && (!format.mixed || !isBlock(format_root)))\r
+ container = dom.split(format_root, container);\r
+\r
+ // Wrap container in cloned formats\r
+ if (lastClone) {\r
+ target.parentNode.insertBefore(lastClone, target);\r
+ firstClone.appendChild(target);\r
+ }\r
+ }\r
+\r
+ return container;\r
+ };\r
+\r
+ function splitToFormatRoot(container) {\r
+ return wrapAndSplit(findFormatRoot(container), container, container, true);\r
+ };\r
+\r
+ function unwrap(start) {\r
+ var node = dom.get(start ? '_start' : '_end'),\r
+ out = node[start ? 'firstChild' : 'lastChild'];\r
+\r
+ // If the end is placed within the start the result will be removed\r
+ // So this checks if the out node is a bookmark node if it is it\r
+ // checks for another more suitable node\r
+ if (isBookmarkNode(out))\r
+ out = out[start ? 'firstChild' : 'lastChild'];\r
+\r
+ dom.remove(node, true);\r
+\r
+ return out;\r
+ };\r
+\r
+ function removeRngStyle(rng) {\r
+ var startContainer, endContainer;\r
+\r
+ rng = expandRng(rng, formatList, TRUE);\r
+\r
+ if (format.split) {\r
+ startContainer = getContainer(rng, TRUE);\r
+ endContainer = getContainer(rng);\r
+\r
+ if (startContainer != endContainer) {\r
+ // Wrap start/end nodes in span element since these might be cloned/moved\r
+ startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});\r
+ endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});\r