--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/tinymce/dev/classes/Editor.js Wed Jun 17 10:00:10 2015 +0200
@@ -0,0 +1,2070 @@
+/**
+ * Editor.js
+ *
+ * Copyright, Moxiecode Systems AB
+ * Released under LGPL License.
+ *
+ * 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"
+], 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
+) {
+ // 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.
+ * @author Moxiecode
+ */
+ 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(/^[\?\&]+/, '');
+ }
+
+ // 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 = 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) {
+ 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 ones the iframe is loaded.
+ * It will fill the iframe with contents, setups 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;
+
+ /**
+ * Schema instance, enables you to validate elements and it's 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]) {
+ 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[^>]*>( | |\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;
+ },
+
+ /**
+ * Distpaches 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 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 retrival 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 retrival 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 asycronous 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);
+ }
+
+ 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.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;
+ },
+
+ // Internal functions
+
+ _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;
+});