PHP Classes

File: WITheme/WICMS/admin/js/plugins/textEditor/tinymce_4.2.6_dev/tinymce/js/tinymce/classes/Editor.js

Recommend this page to a friend!
  Classes of Jules Warner   WICMS   WITheme/WICMS/admin/js/plugins/textEditor/tinymce_4.2.6_dev/tinymce/js/tinymce/classes/Editor.js   Download  
File: WITheme/WICMS/admin/js/plugins/textEditor/tinymce_4.2.6_dev/tinymce/js/tinymce/classes/Editor.js
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: WICMS
Database driven content management system with PDO
Author: By
Last change:
Date: 7 years ago
Size: 62,254 bytes
 

Contents

Class file image Download
/** * Editor.js * * Released under LGPL License. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /*jshint scripturl:true */ /** * Include the base event class documentation. * * @include ../../../tools/docs/tinymce.Event.js */ /** * This class contains the core logic for a TinyMCE editor. * * @class tinymce.Editor * @mixes tinymce.util.Observable * @example * // Add a class to all paragraphs in the editor. * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass'); * * // Gets the current editors selection as text * tinymce.activeEditor.selection.getContent({format: 'text'}); * * // Creates a new editor instance * var ed = new tinymce.Editor('textareaid', { * some_setting: 1 * }, tinymce.EditorManager); * * // Select each item the user clicks on * ed.on('click', function(e) { * ed.selection.select(e.target); * }); * * ed.render(); */ define("tinymce/Editor", [ "tinymce/dom/DOMUtils", "tinymce/dom/DomQuery", "tinymce/AddOnManager", "tinymce/NodeChange", "tinymce/html/Node", "tinymce/dom/Serializer", "tinymce/html/Serializer", "tinymce/dom/Selection", "tinymce/Formatter", "tinymce/UndoManager", "tinymce/EnterKey", "tinymce/ForceBlocks", "tinymce/EditorCommands", "tinymce/util/URI", "tinymce/dom/ScriptLoader", "tinymce/dom/EventUtils", "tinymce/WindowManager", "tinymce/html/Schema", "tinymce/html/DomParser", "tinymce/util/Quirks", "tinymce/Env", "tinymce/util/Tools", "tinymce/EditorObservable", "tinymce/Shortcuts", "tinymce/EditorUpload" ], function( DOMUtils, DomQuery, AddOnManager, NodeChange, Node, DomSerializer, Serializer, Selection, Formatter, UndoManager, EnterKey, ForceBlocks, EditorCommands, URI, ScriptLoader, EventUtils, WindowManager, Schema, DomParser, Quirks, Env, Tools, EditorObservable, Shortcuts, EditorUpload ) { // Shorten these names var DOM = DOMUtils.DOM, ThemeManager = AddOnManager.ThemeManager, PluginManager = AddOnManager.PluginManager; var extend = Tools.extend, each = Tools.each, explode = Tools.explode; var inArray = Tools.inArray, trim = Tools.trim, resolve = Tools.resolve; var Event = EventUtils.Event; var isGecko = Env.gecko, ie = Env.ie; /** * Include documentation for all the events. * * @include ../../../tools/docs/tinymce.Editor.js */ /** * Constructs a editor instance by id. * * @constructor * @method Editor * @param {String} id Unique id for the editor. * @param {Object} settings Settings for the editor. * @param {tinymce.EditorManager} editorManager EditorManager instance. */ function Editor(id, settings, editorManager) { var self = this, documentBaseUrl, baseUri; documentBaseUrl = self.documentBaseUrl = editorManager.documentBaseURL; baseUri = editorManager.baseURI; /** * Name/value collection with editor settings. * * @property settings * @type Object * @example * // Get the value of the theme setting * tinymce.activeEditor.windowManager.alert("You are using the " + tinymce.activeEditor.settings.theme + " theme"); */ self.settings = settings = extend({ id: id, theme: 'modern', delta_width: 0, delta_height: 0, popup_css: '', plugins: '', document_base_url: documentBaseUrl, add_form_submit_trigger: true, submit_patch: true, add_unload_trigger: true, convert_urls: true, relative_urls: true, remove_script_host: true, object_resizing: true, doctype: '<!DOCTYPE html>', visual: true, font_size_style_values: 'xx-small,x-small,small,medium,large,x-large,xx-large', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size font_size_legacy_values: 'xx-small,small,medium,large,x-large,xx-large,300%', forced_root_block: 'p', hidden_input: true, padd_empty_editor: true, render_ui: true, indentation: '30px', inline_styles: true, convert_fonts_to_spans: true, indent: 'simple', indent_before: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,' + 'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', indent_after: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,' + 'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', validate: true, entity_encoding: 'named', url_converter: self.convertURL, url_converter_scope: self, ie7_compat: true }, settings); AddOnManager.language = settings.language || 'en'; AddOnManager.languageLoad = settings.language_load; AddOnManager.baseURL = editorManager.baseURL; /** * Editor instance id, normally the same as the div/textarea that was replaced. * * @property id * @type String */ self.id = settings.id = id; /** * State to force the editor to return false on a isDirty call. * * @property isNotDirty * @type Boolean * @example * function ajaxSave() { * var ed = tinymce.get('elm1'); * * // Save contents using some XHR call * alert(ed.getContent()); * * ed.isNotDirty = true; // Force not dirty state * } */ self.isNotDirty = true; /** * Name/Value object containting plugin instances. * * @property plugins * @type Object * @example * // Execute a method inside a plugin directly * tinymce.activeEditor.plugins.someplugin.someMethod(); */ self.plugins = {}; /** * URI object to document configured for the TinyMCE instance. * * @property documentBaseURI * @type tinymce.util.URI * @example * // Get relative URL from the location of document_base_url * tinymce.activeEditor.documentBaseURI.toRelative('/somedir/somefile.htm'); * * // Get absolute URL from the location of document_base_url * tinymce.activeEditor.documentBaseURI.toAbsolute('somefile.htm'); */ self.documentBaseURI = new URI(settings.document_base_url || documentBaseUrl, { base_uri: baseUri }); /** * URI object to current document that holds the TinyMCE editor instance. * * @property baseURI * @type tinymce.util.URI * @example * // Get relative URL from the location of the API * tinymce.activeEditor.baseURI.toRelative('/somedir/somefile.htm'); * * // Get absolute URL from the location of the API * tinymce.activeEditor.baseURI.toAbsolute('somefile.htm'); */ self.baseURI = baseUri; /** * Array with CSS files to load into the iframe. * * @property contentCSS * @type Array */ self.contentCSS = []; /** * Array of CSS styles to add to head of document when the editor loads. * * @property contentStyles * @type Array */ self.contentStyles = []; // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic self.shortcuts = new Shortcuts(self); self.loadedCSS = {}; self.editorCommands = new EditorCommands(self); if (settings.target) { self.targetElm = settings.target; } self.suffix = editorManager.suffix; self.editorManager = editorManager; self.inline = settings.inline; if (settings.cache_suffix) { Env.cacheSuffix = settings.cache_suffix.replace(/^[\?\&]+/, ''); } if (settings.override_viewport === false) { Env.overrideViewPort = false; } // Call setup editorManager.fire('SetupEditor', self); self.execCallback('setup', self); /** * Dom query instance with default scope to the editor document and default element is the body of the editor. * * @property $ * @type tinymce.dom.DomQuery * @example * tinymce.activeEditor.$('p').css('color', 'red'); * tinymce.activeEditor.$().append('<p>new</p>'); */ self.$ = DomQuery.overrideDefaults(function() { return { context: self.inline ? self.getBody() : self.getDoc(), element: self.getBody() }; }); } Editor.prototype = { /** * Renderes the editor/adds it to the page. * * @method render */ render: function() { var self = this, settings = self.settings, id = self.id, suffix = self.suffix; function readyHandler() { DOM.unbind(window, 'ready', readyHandler); self.render(); } // Page is not loaded yet, wait for it if (!Event.domLoaded) { DOM.bind(window, 'ready', readyHandler); return; } // Element not found, then skip initialization if (!self.getElement()) { return; } // No editable support old iOS versions etc if (!Env.contentEditable) { return; } // Hide target element early to prevent content flashing if (!settings.inline) { self.orgVisibility = self.getElement().style.visibility; self.getElement().style.visibility = 'hidden'; } else { self.inline = true; } var form = self.getElement().form || DOM.getParent(id, 'form'); if (form) { self.formElement = form; // Add hidden input for non input elements inside form elements if (settings.hidden_input && !/TEXTAREA|INPUT/i.test(self.getElement().nodeName)) { DOM.insertAfter(DOM.create('input', {type: 'hidden', name: id}), id); self.hasHiddenInput = true; } // Pass submit/reset from form to editor instance self.formEventDelegate = function(e) { self.fire(e.type, e); }; DOM.bind(form, 'submit reset', self.formEventDelegate); // Reset contents in editor when the form is reset self.on('reset', function() { self.setContent(self.startContent, {format: 'raw'}); }); // Check page uses id="submit" or name="submit" for it's submit button if (settings.submit_patch && !form.submit.nodeType && !form.submit.length && !form._mceOldSubmit) { form._mceOldSubmit = form.submit; form.submit = function() { self.editorManager.triggerSave(); self.isNotDirty = true; return form._mceOldSubmit(form); }; } } /** * Window manager reference, use this to open new windows and dialogs. * * @property windowManager * @type tinymce.WindowManager * @example * // Shows an alert message * tinymce.activeEditor.windowManager.alert('Hello world!'); * * // Opens a new dialog with the file.htm file and the size 320x240 * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog. * tinymce.activeEditor.windowManager.open({ * url: 'file.htm', * width: 320, * height: 240 * }, { * custom_param: 1 * }); */ self.windowManager = new WindowManager(self); if (settings.encoding == 'xml') { self.on('GetContent', function(e) { if (e.save) { e.content = DOM.encode(e.content); } }); } if (settings.add_form_submit_trigger) { self.on('submit', function() { if (self.initialized) { self.save(); } }); } if (settings.add_unload_trigger) { self._beforeUnload = function() { if (self.initialized && !self.destroyed && !self.isHidden()) { self.save({format: 'raw', no_events: true, set_dirty: false}); } }; self.editorManager.on('BeforeUnload', self._beforeUnload); } // Load scripts function loadScripts() { var scriptLoader = ScriptLoader.ScriptLoader; if (settings.language && settings.language != 'en' && !settings.language_url) { settings.language_url = self.editorManager.baseURL + '/langs/' + settings.language + '.js'; } if (settings.language_url) { scriptLoader.add(settings.language_url); } if (settings.theme && typeof settings.theme != "function" && settings.theme.charAt(0) != '-' && !ThemeManager.urls[settings.theme]) { var themeUrl = settings.theme_url; if (themeUrl) { themeUrl = self.documentBaseURI.toAbsolute(themeUrl); } else { themeUrl = 'themes/' + settings.theme + '/theme' + suffix + '.js'; } ThemeManager.load(settings.theme, themeUrl); } if (Tools.isArray(settings.plugins)) { settings.plugins = settings.plugins.join(' '); } each(settings.external_plugins, function(url, name) { PluginManager.load(name, url); settings.plugins += ' ' + name; }); each(settings.plugins.split(/[ ,]/), function(plugin) { plugin = trim(plugin); if (plugin && !PluginManager.urls[plugin]) { if (plugin.charAt(0) == '-') { plugin = plugin.substr(1, plugin.length); var dependencies = PluginManager.dependencies(plugin); each(dependencies, function(dep) { var defaultSettings = { prefix: 'plugins/', resource: dep, suffix: '/plugin' + suffix + '.js' }; dep = PluginManager.createUrl(defaultSettings, dep); PluginManager.load(dep.resource, dep); }); } else { PluginManager.load(plugin, { prefix: 'plugins/', resource: plugin, suffix: '/plugin' + suffix + '.js' }); } } }); scriptLoader.loadQueue(function() { if (!self.removed) { self.init(); } }); } loadScripts(); }, /** * Initializes the editor this will be called automatically when * all plugins/themes and language packs are loaded by the rendered method. * This method will setup the iframe and create the theme and plugin instances. * * @method init */ init: function() { var self = this, settings = self.settings, elm = self.getElement(); var w, h, minHeight, n, o, Theme, url, bodyId, bodyClass, re, i, initializedPlugins = []; this.editorManager.i18n.setCode(settings.language); self.rtl = settings.rtl_ui || this.editorManager.i18n.rtl; self.editorManager.add(self); settings.aria_label = settings.aria_label || DOM.getAttrib(elm, 'aria-label', self.getLang('aria.rich_text_area')); /** * Reference to the theme instance that was used to generate the UI. * * @property theme * @type tinymce.Theme * @example * // Executes a method on the theme directly * tinymce.activeEditor.theme.someMethod(); */ if (settings.theme) { if (typeof settings.theme != "function") { settings.theme = settings.theme.replace(/-/, ''); Theme = ThemeManager.get(settings.theme); self.theme = new Theme(self, ThemeManager.urls[settings.theme]); if (self.theme.init) { self.theme.init(self, ThemeManager.urls[settings.theme] || self.documentBaseUrl.replace(/\/$/, ''), self.$); } } else { self.theme = settings.theme; } } function initPlugin(plugin) { var Plugin = PluginManager.get(plugin), pluginUrl, pluginInstance; pluginUrl = PluginManager.urls[plugin] || self.documentBaseUrl.replace(/\/$/, ''); plugin = trim(plugin); if (Plugin && inArray(initializedPlugins, plugin) === -1) { each(PluginManager.dependencies(plugin), function(dep) { initPlugin(dep); }); pluginInstance = new Plugin(self, pluginUrl, self.$); self.plugins[plugin] = pluginInstance; if (pluginInstance.init) { pluginInstance.init(self, pluginUrl); initializedPlugins.push(plugin); } } } // Create all plugins each(settings.plugins.replace(/\-/g, '').split(/[ ,]/), initPlugin); // Measure box if (settings.render_ui && self.theme) { self.orgDisplay = elm.style.display; if (typeof settings.theme != "function") { w = settings.width || elm.style.width || elm.offsetWidth; h = settings.height || elm.style.height || elm.offsetHeight; minHeight = settings.min_height || 100; re = /^[0-9\.]+(|px)$/i; if (re.test('' + w)) { w = Math.max(parseInt(w, 10), 100); } if (re.test('' + h)) { h = Math.max(parseInt(h, 10), minHeight); } // Render UI o = self.theme.renderUI({ targetNode: elm, width: w, height: h, deltaWidth: settings.delta_width, deltaHeight: settings.delta_height }); // Resize editor if (!settings.content_editable) { h = (o.iframeHeight || h) + (typeof h == 'number' ? (o.deltaHeight || 0) : ''); if (h < minHeight) { h = minHeight; } } } else { o = settings.theme(self, elm); // Convert element type to id:s if (o.editorContainer.nodeType) { o.editorContainer = o.editorContainer.id = o.editorContainer.id || self.id + "_parent"; } // Convert element type to id:s if (o.iframeContainer.nodeType) { o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || self.id + "_iframecontainer"; } // Use specified iframe height or the targets offsetHeight h = o.iframeHeight || elm.offsetHeight; } self.editorContainer = o.editorContainer; } // Load specified content CSS last if (settings.content_css) { each(explode(settings.content_css), function(u) { self.contentCSS.push(self.documentBaseURI.toAbsolute(u)); }); } // Load specified content CSS last if (settings.content_style) { self.contentStyles.push(settings.content_style); } // Content editable mode ends here if (settings.content_editable) { elm = n = o = null; // Fix IE leak return self.initContentBody(); } self.iframeHTML = settings.doctype + '<html><head>'; // We only need to override paths if we have to // IE has a bug where it remove site absolute urls to relative ones if this is specified if (settings.document_base_url != self.documentBaseUrl) { self.iframeHTML += '<base href="' + self.documentBaseURI.getURI() + '" />'; } // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode. if (!Env.caretAfter && settings.ie7_compat) { self.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />'; } self.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'; // Load the CSS by injecting them into the HTML this will reduce "flicker" for (i = 0; i < self.contentCSS.length; i++) { var cssUrl = self.contentCSS[i]; self.iframeHTML += ( '<link type="text/css" ' + 'rel="stylesheet" ' + 'href="' + Tools._addCacheSuffix(cssUrl) + '" />' ); self.loadedCSS[cssUrl] = true; } bodyId = settings.body_id || 'tinymce'; if (bodyId.indexOf('=') != -1) { bodyId = self.getParam('body_id', '', 'hash'); bodyId = bodyId[self.id] || bodyId; } bodyClass = settings.body_class || ''; if (bodyClass.indexOf('=') != -1) { bodyClass = self.getParam('body_class', '', 'hash'); bodyClass = bodyClass[self.id] || ''; } if (settings.content_security_policy) { self.iframeHTML += '<meta http-equiv="Content-Security-Policy" content="' + settings.content_security_policy + '" />'; } self.iframeHTML += '</head><body id="' + bodyId + '" class="mce-content-body ' + bodyClass + '" data-id="' + self.id + '"><br></body></html>'; /*eslint no-script-url:0 */ var domainRelaxUrl = 'javascript:(function(){' + 'document.open();document.domain="' + document.domain + '";' + 'var ed = window.parent.tinymce.get("' + self.id + '");document.write(ed.iframeHTML);' + 'document.close();ed.initContentBody(true);})()'; // Domain relaxing is required since the user has messed around with document.domain if (document.domain != location.hostname) { // Edge seems to be able to handle domain relaxing if (Env.ie && Env.ie < 12) { url = domainRelaxUrl; } } // Create iframe // TODO: ACC add the appropriate description on this. var ifr = DOM.create('iframe', { id: self.id + "_ifr", //src: url || 'javascript:""', // Workaround for HTTPS warning in IE6/7 frameBorder: '0', allowTransparency: "true", title: self.editorManager.translate( "Rich Text Area. Press ALT-F9 for menu. " + "Press ALT-F10 for toolbar. Press ALT-0 for help" ), style: { width: '100%', height: h, display: 'block' // Important for Gecko to render the iframe correctly } }); ifr.onload = function() { ifr.onload = null; self.fire("load"); }; DOM.setAttrib(ifr, "src", url || 'javascript:""'); self.contentAreaContainer = o.iframeContainer; self.iframeElement = ifr; n = DOM.add(o.iframeContainer, ifr); // Try accessing the document this will fail on IE when document.domain is set to the same as location.hostname // Then we have to force domain relaxing using the domainRelaxUrl approach very ugly!! if (ie) { try { self.getDoc(); } catch (e) { n.src = url = domainRelaxUrl; } } if (o.editorContainer) { DOM.get(o.editorContainer).style.display = self.orgDisplay; self.hidden = DOM.isHidden(o.editorContainer); } self.getElement().style.display = 'none'; DOM.setAttrib(self.id, 'aria-hidden', true); if (!url) { self.initContentBody(); } elm = n = o = null; // Cleanup }, /** * This method get called by the init method once the iframe is loaded. * It will fill the iframe with contents, sets up DOM and selection objects for the iframe. * * @method initContentBody * @private */ initContentBody: function(skipWrite) { var self = this, settings = self.settings, targetElm = self.getElement(), doc = self.getDoc(), body, contentCssText; // Restore visibility on target element if (!settings.inline) { self.getElement().style.visibility = self.orgVisibility; } // Setup iframe body if (!skipWrite && !settings.content_editable) { doc.open(); doc.write(self.iframeHTML); doc.close(); } if (settings.content_editable) { self.on('remove', function() { var bodyEl = this.getBody(); DOM.removeClass(bodyEl, 'mce-content-body'); DOM.removeClass(bodyEl, 'mce-edit-focus'); DOM.setAttrib(bodyEl, 'contentEditable', null); }); DOM.addClass(targetElm, 'mce-content-body'); self.contentDocument = doc = settings.content_document || document; self.contentWindow = settings.content_window || window; self.bodyElement = targetElm; // Prevent leak in IE settings.content_document = settings.content_window = null; // TODO: Fix this settings.root_name = targetElm.nodeName.toLowerCase(); } // It will not steal focus while setting contentEditable body = self.getBody(); body.disabled = true; if (!settings.readonly) { if (self.inline && DOM.getStyle(body, 'position', true) == 'static') { body.style.position = 'relative'; } body.contentEditable = self.getParam('content_editable_state', true); } body.disabled = false; self.editorUpload = new EditorUpload(self); /** * Schema instance, enables you to validate elements and its children. * * @property schema * @type tinymce.html.Schema */ self.schema = new Schema(settings); /** * DOM instance for the editor. * * @property dom * @type tinymce.dom.DOMUtils * @example * // Adds a class to all paragraphs within the editor * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass'); */ self.dom = new DOMUtils(doc, { keep_values: true, url_converter: self.convertURL, url_converter_scope: self, hex_colors: settings.force_hex_style_colors, class_filter: settings.class_filter, update_styles: true, root_element: self.inline ? self.getBody() : null, collect: settings.content_editable, schema: self.schema, onSetAttrib: function(e) { self.fire('SetAttrib', e); } }); /** * HTML parser will be used when contents is inserted into the editor. * * @property parser * @type tinymce.html.DomParser */ self.parser = new DomParser(settings, self.schema); // Convert src and href into data-mce-src, data-mce-href and data-mce-style self.parser.addAttributeFilter('src,href,style,tabindex', function(nodes, name) { var i = nodes.length, node, dom = self.dom, value, internalName; while (i--) { node = nodes[i]; value = node.attr(name); internalName = 'data-mce-' + name; // Add internal attribute if we need to we don't on a refresh of the document if (!node.attributes.map[internalName]) { // Don't duplicate these since they won't get modified by any browser if (value.indexOf('data:') === 0 || value.indexOf('blob:') === 0) { continue; } if (name === "style") { value = dom.serializeStyle(dom.parseStyle(value), node.name); if (!value.length) { value = null; } node.attr(internalName, value); node.attr(name, value); } else if (name === "tabindex") { node.attr(internalName, value); node.attr(name, null); } else { node.attr(internalName, self.convertURL(value, name, node.name)); } } } }); // Keep scripts from executing self.parser.addNodeFilter('script', function(nodes) { var i = nodes.length, node; while (i--) { node = nodes[i]; node.attr('type', 'mce-' + (node.attr('type') || 'no/type')); } }); self.parser.addNodeFilter('#cdata', function(nodes) { var i = nodes.length, node; while (i--) { node = nodes[i]; node.type = 8; node.name = '#comment'; node.value = '[CDATA[' + node.value + ']]'; } }); self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes) { var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements(); while (i--) { node = nodes[i]; if (node.isEmpty(nonEmptyElements)) { node.append(new Node('br', 1)).shortEnded = true; } } }); /** * DOM serializer for the editor. Will be used when contents is extracted from the editor. * * @property serializer * @type tinymce.dom.Serializer * @example * // Serializes the first paragraph in the editor into a string * tinymce.activeEditor.serializer.serialize(tinymce.activeEditor.dom.select('p')[0]); */ self.serializer = new DomSerializer(settings, self); /** * Selection instance for the editor. * * @property selection * @type tinymce.dom.Selection * @example * // Sets some contents to the current selection in the editor * tinymce.activeEditor.selection.setContent('Some contents'); * * // Gets the current selection * alert(tinymce.activeEditor.selection.getContent()); * * // Selects the first paragraph found * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]); */ self.selection = new Selection(self.dom, self.getWin(), self.serializer, self); /** * Formatter instance. * * @property formatter * @type tinymce.Formatter */ self.formatter = new Formatter(self); /** * Undo manager instance, responsible for handling undo levels. * * @property undoManager * @type tinymce.UndoManager * @example * // Undoes the last modification to the editor * tinymce.activeEditor.undoManager.undo(); */ self.undoManager = new UndoManager(self); self.forceBlocks = new ForceBlocks(self); self.enterKey = new EnterKey(self); self._nodeChangeDispatcher = new NodeChange(self); self.fire('PreInit'); if (!settings.browser_spellcheck && !settings.gecko_spellcheck) { doc.body.spellcheck = false; // Gecko DOM.setAttrib(body, "spellcheck", "false"); } self.fire('PostRender'); self.quirks = new Quirks(self); if (settings.directionality) { body.dir = settings.directionality; } if (settings.nowrap) { body.style.whiteSpace = "nowrap"; } if (settings.protect) { self.on('BeforeSetContent', function(e) { each(settings.protect, function(pattern) { e.content = e.content.replace(pattern, function(str) { return '<!--mce:protected ' + escape(str) + '-->'; }); }); }); } self.on('SetContent', function() { self.addVisual(self.getBody()); }); // Remove empty contents if (settings.padd_empty_editor) { self.on('PostProcess', function(e) { e.content = e.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, ''); }); } self.load({initial: true, format: 'html'}); self.startContent = self.getContent({format: 'raw'}); /** * Is set to true after the editor instance has been initialized * * @property initialized * @type Boolean * @example * function isEditorInitialized(editor) { * return editor && editor.initialized; * } */ self.initialized = true; self.bindPendingEventDelegates(); self.fire('init'); self.focus(true); self.nodeChanged({initial: true}); self.execCallback('init_instance_callback', self); // Add editor specific CSS styles if (self.contentStyles.length > 0) { contentCssText = ''; each(self.contentStyles, function(style) { contentCssText += style + "\r\n"; }); self.dom.addStyle(contentCssText); } // Load specified content CSS last each(self.contentCSS, function(cssUrl) { if (!self.loadedCSS[cssUrl]) { self.dom.loadCSS(cssUrl); self.loadedCSS[cssUrl] = true; } }); // Handle auto focus if (settings.auto_focus) { setTimeout(function() { var editor; if (settings.auto_focus === true) { editor = self; } else { editor = self.editorManager.get(settings.auto_focus); } if (!editor.destroyed) { editor.focus(); } }, 100); } // Clean up references for IE targetElm = doc = body = null; }, /** * Focuses/activates the editor. This will set this editor as the activeEditor in the tinymce collection * it will also place DOM focus inside the editor. * * @method focus * @param {Boolean} skipFocus Skip DOM focus. Just set is as the active editor. */ focus: function(skipFocus) { var self = this, selection = self.selection, contentEditable = self.settings.content_editable, rng; var controlElm, doc = self.getDoc(), body; if (!skipFocus) { // Get selected control element rng = selection.getRng(); if (rng.item) { controlElm = rng.item(0); } self._refreshContentEditable(); // Focus the window iframe if (!contentEditable) { // WebKit needs this call to fire focusin event properly see #5948 // But Opera pre Blink engine will produce an empty selection so skip Opera if (!Env.opera) { self.getBody().focus(); } self.getWin().focus(); } // Focus the body as well since it's contentEditable if (isGecko || contentEditable) { body = self.getBody(); // Check for setActive since it doesn't scroll to the element if (body.setActive) { // IE 11 sometimes throws "Invalid function" then fallback to focus try { body.setActive(); } catch (ex) { body.focus(); } } else { body.focus(); } if (contentEditable) { selection.normalize(); } } // Restore selected control element // This is needed when for example an image is selected within a // layer a call to focus will then remove the control selection if (controlElm && controlElm.ownerDocument == doc) { rng = doc.body.createControlRange(); rng.addElement(controlElm); rng.select(); } } self.editorManager.setActive(self); }, /** * Executes a legacy callback. This method is useful to call old 2.x option callbacks. * There new event model is a better way to add callback so this method might be removed in the future. * * @method execCallback * @param {String} name Name of the callback to execute. * @return {Object} Return value passed from callback function. */ execCallback: function(name) { var self = this, callback = self.settings[name], scope; if (!callback) { return; } // Look through lookup if (self.callbackLookup && (scope = self.callbackLookup[name])) { callback = scope.func; scope = scope.scope; } if (typeof callback === 'string') { scope = callback.replace(/\.\w+$/, ''); scope = scope ? resolve(scope) : 0; callback = resolve(callback); self.callbackLookup = self.callbackLookup || {}; self.callbackLookup[name] = {func: callback, scope: scope}; } return callback.apply(scope || self, Array.prototype.slice.call(arguments, 1)); }, /** * Translates the specified string by replacing variables with language pack items it will also check if there is * a key mathcin the input. * * @method translate * @param {String} text String to translate by the language pack data. * @return {String} Translated string. */ translate: function(text) { var lang = this.settings.language || 'en', i18n = this.editorManager.i18n; if (!text) { return ''; } return i18n.data[lang + '.' + text] || text.replace(/\{\#([^\}]+)\}/g, function(a, b) { return i18n.data[lang + '.' + b] || '{#' + b + '}'; }); }, /** * Returns a language pack item by name/key. * * @method getLang * @param {String} name Name/key to get from the language pack. * @param {String} defaultVal Optional default value to retrive. */ getLang: function(name, defaultVal) { return ( this.editorManager.i18n.data[(this.settings.language || 'en') + '.' + name] || (defaultVal !== undefined ? defaultVal : '{#' + name + '}') ); }, /** * Returns a configuration parameter by name. * * @method getParam * @param {String} name Configruation parameter to retrive. * @param {String} defaultVal Optional default value to return. * @param {String} type Optional type parameter. * @return {String} Configuration parameter value or default value. * @example * // Returns a specific config value from the currently active editor * var someval = tinymce.activeEditor.getParam('myvalue'); * * // Returns a specific config value from a specific editor instance by id * var someval2 = tinymce.get('my_editor').getParam('myvalue'); */ getParam: function(name, defaultVal, type) { var value = name in this.settings ? this.settings[name] : defaultVal, output; if (type === 'hash') { output = {}; if (typeof value === 'string') { each(value.indexOf('=') > 0 ? value.split(/[;,](?![^=;,]*(?:[;,]|$))/) : value.split(','), function(value) { value = value.split('='); if (value.length > 1) { output[trim(value[0])] = trim(value[1]); } else { output[trim(value[0])] = trim(value); } }); } else { output = value; } return output; } return value; }, /** * Dispatches out a onNodeChange event to all observers. This method should be called when you * need to update the UI states or element path etc. * * @method nodeChanged * @param {Object} args Optional args to pass to NodeChange event handlers. */ nodeChanged: function(args) { this._nodeChangeDispatcher.nodeChanged(args); }, /** * Adds a button that later gets created by the theme in the editors toolbars. * * @method addButton * @param {String} name Button name to add. * @param {Object} settings Settings object with title, cmd etc. * @example * // Adds a custom button to the editor that inserts contents when clicked * tinymce.init({ * ... * * toolbar: 'example' * * setup: function(ed) { * ed.addButton('example', { * title: 'My title', * image: '../js/tinymce/plugins/example/img/example.gif', * onclick: function() { * ed.insertContent('Hello world!!'); * } * }); * } * }); */ addButton: function(name, settings) { var self = this; if (settings.cmd) { settings.onclick = function() { self.execCommand(settings.cmd); }; } if (!settings.text && !settings.icon) { settings.icon = name; } self.buttons = self.buttons || {}; settings.tooltip = settings.tooltip || settings.title; self.buttons[name] = settings; }, /** * Adds a menu item to be used in the menus of the theme. There might be multiple instances * of this menu item for example it might be used in the main menus of the theme but also in * the context menu so make sure that it's self contained and supports multiple instances. * * @method addMenuItem * @param {String} name Menu item name to add. * @param {Object} settings Settings object with title, cmd etc. * @example * // Adds a custom menu item to the editor that inserts contents when clicked * // The context option allows you to add the menu item to an existing default menu * tinymce.init({ * ... * * setup: function(ed) { * ed.addMenuItem('example', { * text: 'My menu item', * context: 'tools', * onclick: function() { * ed.insertContent('Hello world!!'); * } * }); * } * }); */ addMenuItem: function(name, settings) { var self = this; if (settings.cmd) { settings.onclick = function() { self.execCommand(settings.cmd); }; } self.menuItems = self.menuItems || {}; self.menuItems[name] = settings; }, /** * Adds a contextual toolbar to be rendered when the selector matches. * * @method addContextToolbar * @param {function/string} predicate Predicate that needs to return true if provided strings get converted into CSS predicates. * @param {String/Array} items String or array with items to add to the context toolbar. */ addContextToolbar: function(predicate, items) { var self = this, selector; self.contextToolbars = self.contextToolbars || []; // Convert selector to predicate if (typeof predicate == "string") { selector = predicate; predicate = function(elm) { return self.dom.is(elm, selector); }; } self.contextToolbars.push({ predicate: predicate, items: items }); }, /** * Adds a custom command to the editor, you can also override existing commands with this method. * The command that you add can be executed with execCommand. * * @method addCommand * @param {String} name Command name to add/override. * @param {addCommandCallback} callback Function to execute when the command occurs. * @param {Object} scope Optional scope to execute the function in. * @example * // Adds a custom command that later can be executed using execCommand * tinymce.init({ * ... * * setup: function(ed) { * // Register example command * ed.addCommand('mycommand', function(ui, v) { * ed.windowManager.alert('Hello world!! Selection: ' + ed.selection.getContent({format: 'text'})); * }); * } * }); */ addCommand: function(name, callback, scope) { /** * Callback function that gets called when a command is executed. * * @callback addCommandCallback * @param {Boolean} ui Display UI state true/false. * @param {Object} value Optional value for command. * @return {Boolean} True/false state if the command was handled or not. */ this.editorCommands.addCommand(name, callback, scope); }, /** * Adds a custom query state command to the editor, you can also override existing commands with this method. * The command that you add can be executed with queryCommandState function. * * @method addQueryStateHandler * @param {String} name Command name to add/override. * @param {addQueryStateHandlerCallback} callback Function to execute when the command state retrieval occurs. * @param {Object} scope Optional scope to execute the function in. */ addQueryStateHandler: function(name, callback, scope) { /** * Callback function that gets called when a queryCommandState is executed. * * @callback addQueryStateHandlerCallback * @return {Boolean} True/false state if the command is enabled or not like is it bold. */ this.editorCommands.addQueryStateHandler(name, callback, scope); }, /** * Adds a custom query value command to the editor, you can also override existing commands with this method. * The command that you add can be executed with queryCommandValue function. * * @method addQueryValueHandler * @param {String} name Command name to add/override. * @param {addQueryValueHandlerCallback} callback Function to execute when the command value retrieval occurs. * @param {Object} scope Optional scope to execute the function in. */ addQueryValueHandler: function(name, callback, scope) { /** * Callback function that gets called when a queryCommandValue is executed. * * @callback addQueryValueHandlerCallback * @return {Object} Value of the command or undefined. */ this.editorCommands.addQueryValueHandler(name, callback, scope); }, /** * Adds a keyboard shortcut for some command or function. * * @method addShortcut * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o. * @param {String} desc Text description for the command. * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed. * @param {Object} sc Optional scope to execute the function in. * @return {Boolean} true/false state if the shortcut was added or not. */ addShortcut: function(pattern, desc, cmdFunc, scope) { this.shortcuts.add(pattern, desc, cmdFunc, scope); }, /** * Executes a command on the current instance. These commands can be TinyMCE internal commands prefixed with "mce" or * they can be build in browser commands such as "Bold". A compleate list of browser commands is available on MSDN or Mozilla.org. * This function will dispatch the execCommand function on each plugin, theme or the execcommand_callback option if none of these * return true it will handle the command as a internal browser command. * * @method execCommand * @param {String} cmd Command name to execute, for example mceLink or Bold. * @param {Boolean} ui True/false state if a UI (dialog) should be presented or not. * @param {mixed} value Optional command value, this can be anything. * @param {Object} args Optional arguments object. */ execCommand: function(cmd, ui, value, args) { return this.editorCommands.execCommand(cmd, ui, value, args); }, /** * Returns a command specific state, for example if bold is enabled or not. * * @method queryCommandState * @param {string} cmd Command to query state from. * @return {Boolean} Command specific state, for example if bold is enabled or not. */ queryCommandState: function(cmd) { return this.editorCommands.queryCommandState(cmd); }, /** * Returns a command specific value, for example the current font size. * * @method queryCommandValue * @param {string} cmd Command to query value from. * @return {Object} Command specific value, for example the current font size. */ queryCommandValue: function(cmd) { return this.editorCommands.queryCommandValue(cmd); }, /** * Returns true/false if the command is supported or not. * * @method queryCommandSupported * @param {String} cmd Command that we check support for. * @return {Boolean} true/false if the command is supported or not. */ queryCommandSupported: function(cmd) { return this.editorCommands.queryCommandSupported(cmd); }, /** * Shows the editor and hides any textarea/div that the editor is supposed to replace. * * @method show */ show: function() { var self = this; if (self.hidden) { self.hidden = false; if (self.inline) { self.getBody().contentEditable = true; } else { DOM.show(self.getContainer()); DOM.hide(self.id); } self.load(); self.fire('show'); } }, /** * Hides the editor and shows any textarea/div that the editor is supposed to replace. * * @method hide */ hide: function() { var self = this, doc = self.getDoc(); if (!self.hidden) { // Fixed bug where IE has a blinking cursor left from the editor if (ie && doc && !self.inline) { doc.execCommand('SelectAll'); } // We must save before we hide so Safari doesn't crash self.save(); if (self.inline) { self.getBody().contentEditable = false; // Make sure the editor gets blurred if (self == self.editorManager.focusedEditor) { self.editorManager.focusedEditor = null; } } else { DOM.hide(self.getContainer()); DOM.setStyle(self.id, 'display', self.orgDisplay); } self.hidden = true; self.fire('hide'); } }, /** * Returns true/false if the editor is hidden or not. * * @method isHidden * @return {Boolean} True/false if the editor is hidden or not. */ isHidden: function() { return !!this.hidden; }, /** * Sets the progress state, this will display a throbber/progess for the editor. * This is ideal for asynchronous operations like an AJAX save call. * * @method setProgressState * @param {Boolean} state Boolean state if the progress should be shown or hidden. * @param {Number} time Optional time to wait before the progress gets shown. * @return {Boolean} Same as the input state. * @example * // Show progress for the active editor * tinymce.activeEditor.setProgressState(true); * * // Hide progress for the active editor * tinymce.activeEditor.setProgressState(false); * * // Show progress after 3 seconds * tinymce.activeEditor.setProgressState(true, 3000); */ setProgressState: function(state, time) { this.fire('ProgressState', {state: state, time: time}); }, /** * Loads contents from the textarea or div element that got converted into an editor instance. * This method will move the contents from that textarea or div into the editor by using setContent * so all events etc that method has will get dispatched as well. * * @method load * @param {Object} args Optional content object, this gets passed around through the whole load process. * @return {String} HTML string that got set into the editor. */ load: function(args) { var self = this, elm = self.getElement(), html; if (elm) { args = args || {}; args.load = true; html = self.setContent(elm.value !== undefined ? elm.value : elm.innerHTML, args); args.element = elm; if (!args.no_events) { self.fire('LoadContent', args); } args.element = elm = null; return html; } }, /** * Saves the contents from a editor out to the textarea or div element that got converted into an editor instance. * This method will move the HTML contents from the editor into that textarea or div by getContent * so all events etc that method has will get dispatched as well. * * @method save * @param {Object} args Optional content object, this gets passed around through the whole save process. * @return {String} HTML string that got set into the textarea/div. */ save: function(args) { var self = this, elm = self.getElement(), html, form; if (!elm || !self.initialized) { return; } args = args || {}; args.save = true; args.element = elm; html = args.content = self.getContent(args); if (!args.no_events) { self.fire('SaveContent', args); } // Always run this internal event if (args.format == 'raw') { self.fire('RawSaveContent', args); } html = args.content; if (!/TEXTAREA|INPUT/i.test(elm.nodeName)) { // Update DIV element when not in inline mode if (!self.inline) { elm.innerHTML = html; } // Update hidden form element if ((form = DOM.getParent(self.id, 'form'))) { each(form.elements, function(elm) { if (elm.name == self.id) { elm.value = html; return false; } }); } } else { elm.value = html; } args.element = elm = null; if (args.set_dirty !== false) { self.isNotDirty = true; } return html; }, /** * Sets the specified content to the editor instance, this will cleanup the content before it gets set using * the different cleanup rules options. * * @method setContent * @param {String} content Content to set to editor, normally HTML contents but can be other formats as well. * @param {Object} args Optional content object, this gets passed around through the whole set process. * @return {String} HTML string that got set into the editor. * @example * // Sets the HTML contents of the activeEditor editor * tinymce.activeEditor.setContent('<span>some</span> html'); * * // Sets the raw contents of the activeEditor editor * tinymce.activeEditor.setContent('<span>some</span> html', {format: 'raw'}); * * // Sets the content of a specific editor (my_editor in this example) * tinymce.get('my_editor').setContent(data); * * // Sets the bbcode contents of the activeEditor editor if the bbcode plugin was added * tinymce.activeEditor.setContent('[b]some[/b] html', {format: 'bbcode'}); */ setContent: function(content, args) { var self = this, body = self.getBody(), forcedRootBlockName; // Setup args object args = args || {}; args.format = args.format || 'html'; args.set = true; args.content = content; // Do preprocessing if (!args.no_events) { self.fire('BeforeSetContent', args); } content = args.content; // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content // It will also be impossible to place the caret in the editor unless there is a BR element present if (content.length === 0 || /^\s+$/.test(content)) { forcedRootBlockName = self.settings.forced_root_block; // Check if forcedRootBlock is configured and that the block is a valid child of the body if (forcedRootBlockName && self.schema.isValidChild(body.nodeName.toLowerCase(), forcedRootBlockName.toLowerCase())) { // Padd with bogus BR elements on modern browsers and IE 7 and 8 since they don't render empty P tags properly content = ie && ie < 11 ? '' : '<br data-mce-bogus="1">'; content = self.dom.createHTML(forcedRootBlockName, self.settings.forced_root_block_attrs, content); } else if (!ie) { // We need to add a BR when forced_root_block is disabled on non IE browsers to place the caret content = '<br data-mce-bogus="1">'; } self.dom.setHTML(body, content); self.fire('SetContent', args); } else { // Parse and serialize the html if (args.format !== 'raw') { content = new Serializer({}, self.schema).serialize( self.parser.parse(content, {isRootContent: true}) ); } // Set the new cleaned contents to the editor args.content = trim(content); self.dom.setHTML(body, args.content); // Do post processing if (!args.no_events) { self.fire('SetContent', args); } // Don't normalize selection if the focused element isn't the body in // content editable mode since it will steal focus otherwise /*if (!self.settings.content_editable || document.activeElement === self.getBody()) { self.selection.normalize(); }*/ } return args.content; }, /** * Gets the content from the editor instance, this will cleanup the content before it gets returned using * the different cleanup rules options. * * @method getContent * @param {Object} args Optional content object, this gets passed around through the whole get process. * @return {String} Cleaned content string, normally HTML contents. * @example * // Get the HTML contents of the currently active editor * console.debug(tinymce.activeEditor.getContent()); * * // Get the raw contents of the currently active editor * tinymce.activeEditor.getContent({format: 'raw'}); * * // Get content of a specific editor: * tinymce.get('content id').getContent() */ getContent: function(args) { var self = this, content, body = self.getBody(); // Setup args object args = args || {}; args.format = args.format || 'html'; args.get = true; args.getInner = true; // Do preprocessing if (!args.no_events) { self.fire('BeforeGetContent', args); } // Get raw contents or by default the cleaned contents if (args.format == 'raw') { content = body.innerHTML; } else if (args.format == 'text') { content = body.innerText || body.textContent; } else { content = self.serializer.serialize(body, args); } // Trim whitespace in beginning/end of HTML if (args.format != 'text') { args.content = trim(content); } else { args.content = content; } // Do post processing if (!args.no_events) { self.fire('GetContent', args); } return args.content; }, /** * Inserts content at caret position. * * @method insertContent * @param {String} content Content to insert. * @param {Object} args Optional args to pass to insert call. */ insertContent: function(content, args) { if (args) { content = extend({content: content}, args); } this.execCommand('mceInsertContent', false, content); }, /** * Returns true/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents. * * @method isDirty * @return {Boolean} True/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents. * @example * if (tinymce.activeEditor.isDirty()) * alert("You must save your contents."); */ isDirty: function() { return !this.isNotDirty; }, /** * Returns the editors container element. The container element wrappes in * all the elements added to the page for the editor. Such as UI, iframe etc. * * @method getContainer * @return {Element} HTML DOM element for the editor container. */ getContainer: function() { var self = this; if (!self.container) { self.container = DOM.get(self.editorContainer || self.id + '_parent'); } return self.container; }, /** * Returns the editors content area container element. The this element is the one who * holds the iframe or the editable element. * * @method getContentAreaContainer * @return {Element} HTML DOM element for the editor area container. */ getContentAreaContainer: function() { return this.contentAreaContainer; }, /** * Returns the target element/textarea that got replaced with a TinyMCE editor instance. * * @method getElement * @return {Element} HTML DOM element for the replaced element. */ getElement: function() { if (!this.targetElm) { this.targetElm = DOM.get(this.id); } return this.targetElm; }, /** * Returns the iframes window object. * * @method getWin * @return {Window} Iframe DOM window object. */ getWin: function() { var self = this, elm; if (!self.contentWindow) { elm = self.iframeElement; if (elm) { self.contentWindow = elm.contentWindow; } } return self.contentWindow; }, /** * Returns the iframes document object. * * @method getDoc * @return {Document} Iframe DOM document object. */ getDoc: function() { var self = this, win; if (!self.contentDocument) { win = self.getWin(); if (win) { self.contentDocument = win.document; } } return self.contentDocument; }, /** * Returns the root element of the editable area. * For a non-inline iframe-based editor, returns the iframe's body element. * * @method getBody * @return {Element} The root element of the editable area. */ getBody: function() { return this.bodyElement || this.getDoc().body; }, /** * URL converter function this gets executed each time a user adds an img, a or * any other element that has a URL in it. This will be called both by the DOM and HTML * manipulation functions. * * @method convertURL * @param {string} url URL to convert. * @param {string} name Attribute name src, href etc. * @param {string/HTMLElement} elm Tag name or HTML DOM element depending on HTML or DOM insert. * @return {string} Converted URL string. */ convertURL: function(url, name, elm) { var self = this, settings = self.settings; // Use callback instead if (settings.urlconverter_callback) { return self.execCallback('urlconverter_callback', url, elm, true, name); } // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0 || url.length === 0) { return url; } // Convert to relative if (settings.relative_urls) { return self.documentBaseURI.toRelative(url); } // Convert to absolute url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host); return url; }, /** * Adds visual aid for tables, anchors etc so they can be more easily edited inside the editor. * * @method addVisual * @param {Element} elm Optional root element to loop though to find tables etc that needs the visual aid. */ addVisual: function(elm) { var self = this, settings = self.settings, dom = self.dom, cls; elm = elm || self.getBody(); if (self.hasVisual === undefined) { self.hasVisual = settings.visual; } each(dom.select('table,a', elm), function(elm) { var value; switch (elm.nodeName) { case 'TABLE': cls = settings.visual_table_class || 'mce-item-table'; value = dom.getAttrib(elm, 'border'); if ((!value || value == '0') && self.hasVisual) { dom.addClass(elm, cls); } else { dom.removeClass(elm, cls); } return; case 'A': if (!dom.getAttrib(elm, 'href', false)) { value = dom.getAttrib(elm, 'name') || elm.id; cls = settings.visual_anchor_class || 'mce-item-anchor'; if (value && self.hasVisual) { dom.addClass(elm, cls); } else { dom.removeClass(elm, cls); } } return; } }); self.fire('VisualAid', {element: elm, hasVisual: self.hasVisual}); }, /** * Removes the editor from the dom and tinymce collection. * * @method remove */ remove: function() { var self = this; if (!self.removed) { self.save(); self.removed = 1; self.unbindAllNativeEvents(); // Remove any hidden input if (self.hasHiddenInput) { DOM.remove(self.getElement().nextSibling); } if (!self.inline) { // IE 9 has a bug where the selection stops working if you place the // caret inside the editor then remove the iframe if (ie && ie < 10) { self.getDoc().execCommand('SelectAll', false, null); } DOM.setStyle(self.id, 'display', self.orgDisplay); self.getBody().onload = null; // Prevent #6816 } self.fire('remove'); self.editorManager.remove(self); DOM.remove(self.getContainer()); self.editorUpload.destroy(); self.destroy(); } }, /** * Destroys the editor instance by removing all events, element references or other resources * that could leak memory. This method will be called automatically when the page is unloaded * but you can also call it directly if you know what you are doing. * * @method destroy * @param {Boolean} automatic Optional state if the destroy is an automatic destroy or user called one. */ destroy: function(automatic) { var self = this, form; // One time is enough if (self.destroyed) { return; } // If user manually calls destroy and not remove // Users seems to have logic that calls destroy instead of remove if (!automatic && !self.removed) { self.remove(); return; } if (!automatic) { self.editorManager.off('beforeunload', self._beforeUnload); // Manual destroy if (self.theme && self.theme.destroy) { self.theme.destroy(); } // Destroy controls, selection and dom self.selection.destroy(); self.dom.destroy(); } form = self.formElement; if (form) { if (form._mceOldSubmit) { form.submit = form._mceOldSubmit; form._mceOldSubmit = null; } DOM.unbind(form, 'submit reset', self.formEventDelegate); } self.contentAreaContainer = self.formElement = self.container = self.editorContainer = null; self.bodyElement = self.contentDocument = self.contentWindow = null; self.iframeElement = self.targetElm = null; if (self.selection) { self.selection = self.selection.win = self.selection.dom = self.selection.dom.doc = null; } self.destroyed = 1; }, /** * Uploads all data uri/blob uri images in the editor contents to server. * * @method uploadImages * @param {function} callback Optional callback with images and status for each image. * @return {tinymce.util.Promise} Promise instance. */ uploadImages: function(callback) { return this.editorUpload.uploadImages(callback); }, // Internal functions _scanForImages: function() { return this.editorUpload.scanForImages(); }, _refreshContentEditable: function() { var self = this, body, parent; // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again if (self._isHidden()) { body = self.getBody(); parent = body.parentNode; parent.removeChild(body); parent.appendChild(body); body.focus(); } }, _isHidden: function() { var sel; if (!isGecko) { return 0; } // Weird, wheres that cursor selection? sel = this.selection.getSel(); return (!sel || !sel.rangeCount || sel.rangeCount === 0); } }; extend(Editor.prototype, EditorObservable); return Editor; });