abcabc123
would produceabc
abc123
. - * - * @method split - * @param {Element} parentElm Parent element to split. - * @param {Element} splitElm Element to split at. - * @param {Element} replacementElm Optional replacement element to replace the split element with. - * @return {Element} Returns the split element or the replacement element if that is specified. - */ - split: function(parentElm, splitElm, replacementElm) { - var self = this, r = self.createRng(), bef, aft, pa; - - // W3C valid browsers tend to leave empty nodes to the left/right side of the contents - this makes sense - // but we don't want that in our code since it serves no purpose for the end user - // For example splitting this html at the bold element: - //text 1CHOPtext 2
- // would produce: - //text 1
CHOPtext 2
- // this function will then trim off empty edges and produce: - //text 1
CHOPtext 2
- function trimNode(node) { - var i, children = node.childNodes, type = node.nodeType; - - function surroundedBySpans(node) { - var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN'; - var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN'; - return previousIsSpan && nextIsSpan; - } - - if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') { - return; - } - - for (i = children.length - 1; i >= 0; i--) { - trimNode(children[i]); - } - - if (type != 9) { - // Keep non whitespace text nodes - if (type == 3 && node.nodeValue.length > 0) { - // If parent element isn't a block or there isn't any useful contents for example "" - // Also keep text nodes with only spaces if surrounded by spans. - // eg. "
a b
" should keep space between a and b - var trimmedLength = trim(node.nodeValue).length; - if (!self.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) { - return; - } - } else if (type == 1) { - // If the only child is a bookmark then move it up - children = node.childNodes; - - // TODO fix this complex if - if (children.length == 1 && children[0] && children[0].nodeType == 1 && - children[0].getAttribute('data-mce-type') == 'bookmark') { - node.parentNode.insertBefore(children[0], node); - } - - // Keep non empty elements or img, hr etc - if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) { - return; - } - } - - self.remove(node); - } - - return node; - } - - if (parentElm && splitElm) { - // Get before chunk - r.setStart(parentElm.parentNode, self.nodeIndex(parentElm)); - r.setEnd(splitElm.parentNode, self.nodeIndex(splitElm)); - bef = r.extractContents(); - - // Get after chunk - r = self.createRng(); - r.setStart(splitElm.parentNode, self.nodeIndex(splitElm) + 1); - r.setEnd(parentElm.parentNode, self.nodeIndex(parentElm) + 1); - aft = r.extractContents(); - - // Insert before chunk - pa = parentElm.parentNode; - pa.insertBefore(trimNode(bef), parentElm); - - // Insert middle chunk - if (replacementElm) { - pa.replaceChild(replacementElm, splitElm); - } else { - pa.insertBefore(splitElm, parentElm); - } - - // Insert after chunk - pa.insertBefore(trimNode(aft), parentElm); - self.remove(parentElm); - - return replacementElm || splitElm; - } - }, - - /** - * Adds an event handler to the specified object. - * - * @method bind - * @param {Element/Document/Window/Array} target Target element to bind events to. - * handler to or an array of elements/ids/documents. - * @param {String} name Name of event handler to add, for example: click. - * @param {function} func Function to execute when the event occurs. - * @param {Object} scope Optional scope to execute the function in. - * @return {function} Function callback handler the same as the one passed in. - */ - bind: function(target, name, func, scope) { - var self = this; - - if (Tools.isArray(target)) { - var i = target.length; - - while (i--) { - target[i] = self.bind(target[i], name, func, scope); - } - - return target; - } - - // Collect all window/document events bound by editor instance - if (self.settings.collect && (target === self.doc || target === self.win)) { - self.boundEvents.push([target, name, func, scope]); - } - - return self.events.bind(target, name, func, scope || self); - }, - - /** - * Removes the specified event handler by name and function from an element or collection of elements. - * - * @method unbind - * @param {Element/Document/Window/Array} target Target element to unbind events on. - * @param {String} name Event handler name, for example: "click" - * @param {function} func Function to remove. - * @return {bool/Array} Bool state of true if the handler was removed, or an array of states if multiple input elements - * were passed in. - */ - unbind: function(target, name, func) { - var self = this, i; - - if (Tools.isArray(target)) { - i = target.length; - - while (i--) { - target[i] = self.unbind(target[i], name, func); - } - - return target; - } - - // Remove any bound events matching the input - if (self.boundEvents && (target === self.doc || target === self.win)) { - i = self.boundEvents.length; - - while (i--) { - var item = self.boundEvents[i]; - - if (target == item[0] && (!name || name == item[1]) && (!func || func == item[2])) { - this.events.unbind(item[0], item[1], item[2]); - } - } - } - - return this.events.unbind(target, name, func); - }, - - /** - * Fires the specified event name with object on target. - * - * @method fire - * @param {Node/Document/Window} target Target element or object to fire event on. - * @param {String} name Name of the event to fire. - * @param {Object} evt Event object to send. - * @return {Event} Event object. - */ - fire: function(target, name, evt) { - return this.events.fire(target, name, evt); - }, - - // Returns the content editable state of a node - getContentEditable: function(node) { - var contentEditable; - - // Check type - if (!node || node.nodeType != 1) { - return null; - } - - // Check for fake content editable - contentEditable = node.getAttribute("data-mce-contenteditable"); - if (contentEditable && contentEditable !== "inherit") { - return contentEditable; - } - - // Check for real content editable - return node.contentEditable !== "inherit" ? node.contentEditable : null; - }, - - getContentEditableParent: function(node) { - var root = this.getRoot(), state = null; - - for (; node && node !== root; node = node.parentNode) { - state = this.getContentEditable(node); - - if (state !== null) { - break; - } - } - - return state; - }, - - /** - * Destroys all internal references to the DOM to solve IE leak issues. - * - * @method destroy - */ - destroy: function() { - var self = this; - - // Unbind all events bound to window/document by editor instance - if (self.boundEvents) { - var i = self.boundEvents.length; - - while (i--) { - var item = self.boundEvents[i]; - this.events.unbind(item[0], item[1], item[2]); - } - - self.boundEvents = null; - } - - // Restore sizzle document to window.document - // Since the current document might be removed producing "Permission denied" on IE see #6325 - if (Sizzle.setDocument) { - Sizzle.setDocument(); - } - - self.win = self.doc = self.root = self.events = self.frag = null; - }, - - isChildOf: function(node, parent) { - while (node) { - if (parent === node) { - return true; - } - - node = node.parentNode; - } - - return false; - }, - - // #ifdef debug - - dumpRng: function(r) { - return ( - 'startContainer: ' + r.startContainer.nodeName + - ', startOffset: ' + r.startOffset + - ', endContainer: ' + r.endContainer.nodeName + - ', endOffset: ' + r.endOffset - ); - }, - - // #endif - - _findSib: function(node, selector, name) { - var self = this, func = selector; - - if (node) { - // If expression make a function of it using is - if (typeof func == 'string') { - func = function(node) { - return self.is(node, selector); - }; - } - - // Loop all siblings - for (node = node[name]; node; node = node[name]) { - if (func(node)) { - return node; - } - } - } - - return null; - } - }; - - /** - * Instance of DOMUtils for the current document. - * - * @static - * @property DOM - * @type tinymce.dom.DOMUtils - * @example - * // Example of how to add a class to some element by id - * tinymce.DOM.addClass('someid', 'someclass'); - */ - DOMUtils.DOM = new DOMUtils(document); - - return DOMUtils; -}); - -// Included from: js/tinymce/classes/dom/ScriptLoader.js - -/** - * ScriptLoader.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/*globals console*/ - -/** - * This class handles asynchronous/synchronous loading of JavaScript files it will execute callbacks - * when various items gets loaded. This class is useful to load external JavaScript files. - * - * @class tinymce.dom.ScriptLoader - * @example - * // Load a script from a specific URL using the global script loader - * tinymce.ScriptLoader.load('somescript.js'); - * - * // Load a script using a unique instance of the script loader - * var scriptLoader = new tinymce.dom.ScriptLoader(); - * - * scriptLoader.load('somescript.js'); - * - * // Load multiple scripts - * var scriptLoader = new tinymce.dom.ScriptLoader(); - * - * scriptLoader.add('somescript1.js'); - * scriptLoader.add('somescript2.js'); - * scriptLoader.add('somescript3.js'); - * - * scriptLoader.loadQueue(function() { - * alert('All scripts are now loaded.'); - * }); - */ -define("tinymce/dom/ScriptLoader", [ - "tinymce/dom/DOMUtils", - "tinymce/util/Tools" -], function(DOMUtils, Tools) { - var DOM = DOMUtils.DOM; - var each = Tools.each, grep = Tools.grep; - - function ScriptLoader() { - var QUEUED = 0, - LOADING = 1, - LOADED = 2, - states = {}, - queue = [], - scriptLoadedCallbacks = {}, - queueLoadedCallbacks = [], - loading = 0, - undef; - - /** - * Loads a specific script directly without adding it to the load queue. - * - * @method load - * @param {String} url Absolute URL to script to add. - * @param {function} callback Optional callback function to execute ones this script gets loaded. - * @param {Object} scope Optional scope to execute callback in. - */ - function loadScript(url, callback) { - var dom = DOM, elm, id; - - // Execute callback when script is loaded - function done() { - dom.remove(id); - - if (elm) { - elm.onreadystatechange = elm.onload = elm = null; - } - - callback(); - } - - function error() { - /*eslint no-console:0 */ - - // Report the error so it's easier for people to spot loading errors - if (typeof console !== "undefined" && console.log) { - console.log("Failed to load: " + url); - } - - // We can't mark it as done if there is a load error since - // A) We don't want to produce 404 errors on the server and - // B) the onerror event won't fire on all browsers. - // done(); - } - - id = dom.uniqueId(); - - // Create new script element - elm = document.createElement('script'); - elm.id = id; - elm.type = 'text/javascript'; - elm.src = Tools._addCacheSuffix(url); - - // Seems that onreadystatechange works better on IE 10 onload seems to fire incorrectly - if ("onreadystatechange" in elm) { - elm.onreadystatechange = function() { - if (/loaded|complete/.test(elm.readyState)) { - done(); - } - }; - } else { - elm.onload = done; - } - - // Add onerror event will get fired on some browsers but not all of them - elm.onerror = error; - - // Add script to document - (document.getElementsByTagName('head')[0] || document.body).appendChild(elm); - } - - /** - * Returns true/false if a script has been loaded or not. - * - * @method isDone - * @param {String} url URL to check for. - * @return {Boolean} true/false if the URL is loaded. - */ - this.isDone = function(url) { - return states[url] == LOADED; - }; - - /** - * Marks a specific script to be loaded. This can be useful if a script got loaded outside - * the script loader or to skip it from loading some script. - * - * @method markDone - * @param {string} u Absolute URL to the script to mark as loaded. - */ - this.markDone = function(url) { - states[url] = LOADED; - }; - - /** - * Adds a specific script to the load queue of the script loader. - * - * @method add - * @param {String} url Absolute URL to script to add. - * @param {function} callback Optional callback function to execute ones this script gets loaded. - * @param {Object} scope Optional scope to execute callback in. - */ - this.add = this.load = function(url, callback, scope) { - var state = states[url]; - - // Add url to load queue - if (state == undef) { - queue.push(url); - states[url] = QUEUED; - } - - if (callback) { - // Store away callback for later execution - if (!scriptLoadedCallbacks[url]) { - scriptLoadedCallbacks[url] = []; - } - - scriptLoadedCallbacks[url].push({ - func: callback, - scope: scope || this - }); - } - }; - - /** - * Starts the loading of the queue. - * - * @method loadQueue - * @param {function} callback Optional callback to execute when all queued items are loaded. - * @param {Object} scope Optional scope to execute the callback in. - */ - this.loadQueue = function(callback, scope) { - this.loadScripts(queue, callback, scope); - }; - - /** - * Loads the specified queue of files and executes the callback ones they are loaded. - * This method is generally not used outside this class but it might be useful in some scenarios. - * - * @method loadScripts - * @param {Array} scripts Array of queue items to load. - * @param {function} callback Optional callback to execute ones all items are loaded. - * @param {Object} scope Optional scope to execute callback in. - */ - this.loadScripts = function(scripts, callback, scope) { - var loadScripts; - - function execScriptLoadedCallbacks(url) { - // Execute URL callback functions - each(scriptLoadedCallbacks[url], function(callback) { - callback.func.call(callback.scope); - }); - - scriptLoadedCallbacks[url] = undef; - } - - queueLoadedCallbacks.push({ - func: callback, - scope: scope || this - }); - - loadScripts = function() { - var loadingScripts = grep(scripts); - - // Current scripts has been handled - scripts.length = 0; - - // Load scripts that needs to be loaded - each(loadingScripts, function(url) { - // Script is already loaded then execute script callbacks directly - if (states[url] == LOADED) { - execScriptLoadedCallbacks(url); - return; - } - - // Is script not loading then start loading it - if (states[url] != LOADING) { - states[url] = LOADING; - loading++; - - loadScript(url, function() { - states[url] = LOADED; - loading--; - - execScriptLoadedCallbacks(url); - - // Load more scripts if they where added by the recently loaded script - loadScripts(); - }); - } - }); - - // No scripts are currently loading then execute all pending queue loaded callbacks - if (!loading) { - each(queueLoadedCallbacks, function(callback) { - callback.func.call(callback.scope); - }); - - queueLoadedCallbacks.length = 0; - } - }; - - loadScripts(); - }; - } - - ScriptLoader.ScriptLoader = new ScriptLoader(); - - return ScriptLoader; -}); - -// Included from: js/tinymce/classes/AddOnManager.js - -/** - * AddOnManager.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * This class handles the loading of themes/plugins or other add-ons and their language packs. - * - * @class tinymce.AddOnManager - */ -define("tinymce/AddOnManager", [ - "tinymce/dom/ScriptLoader", - "tinymce/util/Tools" -], function(ScriptLoader, Tools) { - var each = Tools.each; - - function AddOnManager() { - var self = this; - - self.items = []; - self.urls = {}; - self.lookup = {}; - } - - AddOnManager.prototype = { - /** - * Returns the specified add on by the short name. - * - * @method get - * @param {String} name Add-on to look for. - * @return {tinymce.Theme/tinymce.Plugin} Theme or plugin add-on instance or undefined. - */ - get: function(name) { - if (this.lookup[name]) { - return this.lookup[name].instance; - } else { - return undefined; - } - }, - - dependencies: function(name) { - var result; - - if (this.lookup[name]) { - result = this.lookup[name].dependencies; - } - - return result || []; - }, - - /** - * Loads a language pack for the specified add-on. - * - * @method requireLangPack - * @param {String} name Short name of the add-on. - * @param {String} languages Optional comma or space separated list of languages to check if it matches the name. - */ - requireLangPack: function(name, languages) { - var language = AddOnManager.language; - - if (language && AddOnManager.languageLoad !== false) { - if (languages) { - languages = ',' + languages + ','; - - // Load short form sv.js or long form sv_SE.js - if (languages.indexOf(',' + language.substr(0, 2) + ',') != -1) { - language = language.substr(0, 2); - } else if (languages.indexOf(',' + language + ',') == -1) { - return; - } - } - - ScriptLoader.ScriptLoader.add(this.urls[name] + '/langs/' + language + '.js'); - } - }, - - /** - * Adds a instance of the add-on by it's short name. - * - * @method add - * @param {String} id Short name/id for the add-on. - * @param {tinymce.Theme/tinymce.Plugin} addOn Theme or plugin to add. - * @return {tinymce.Theme/tinymce.Plugin} The same theme or plugin instance that got passed in. - * @example - * // Create a simple plugin - * tinymce.create('tinymce.plugins.TestPlugin', { - * TestPlugin: function(ed, url) { - * ed.on('click', function(e) { - * ed.windowManager.alert('Hello World!'); - * }); - * } - * }); - * - * // Register plugin using the add method - * tinymce.PluginManager.add('test', tinymce.plugins.TestPlugin); - * - * // Initialize TinyMCE - * tinymce.init({ - * ... - * plugins: '-test' // Init the plugin but don't try to load it - * }); - */ - add: function(id, addOn, dependencies) { - this.items.push(addOn); - this.lookup[id] = {instance: addOn, dependencies: dependencies}; - - return addOn; - }, - - createUrl: function(baseUrl, dep) { - if (typeof dep === "object") { - return dep; - } else { - return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix}; - } - }, - - /** - * Add a set of components that will make up the add-on. Using the url of the add-on name as the base url. - * This should be used in development mode. A new compressor/javascript munger process will ensure that the - * components are put together into the plugin.js file and compressed correctly. - * - * @method addComponents - * @param {String} pluginName name of the plugin to load scripts from (will be used to get the base url for the plugins). - * @param {Array} scripts Array containing the names of the scripts to load. - */ - addComponents: function(pluginName, scripts) { - var pluginUrl = this.urls[pluginName]; - - each(scripts, function(script) { - ScriptLoader.ScriptLoader.add(pluginUrl + "/" + script); - }); - }, - - /** - * Loads an add-on from a specific url. - * - * @method load - * @param {String} name Short name of the add-on that gets loaded. - * @param {String} addOnUrl URL to the add-on that will get loaded. - * @param {function} callback Optional callback to execute ones the add-on is loaded. - * @param {Object} scope Optional scope to execute the callback in. - * @example - * // Loads a plugin from an external URL - * tinymce.PluginManager.load('myplugin', '/some/dir/someplugin/plugin.js'); - * - * // Initialize TinyMCE - * tinymce.init({ - * ... - * plugins: '-myplugin' // Don't try to load it again - * }); - */ - load: function(name, addOnUrl, callback, scope) { - var self = this, url = addOnUrl; - - function loadDependencies() { - var dependencies = self.dependencies(name); - - each(dependencies, function(dep) { - var newUrl = self.createUrl(addOnUrl, dep); - - self.load(newUrl.resource, newUrl, undefined, undefined); - }); - - if (callback) { - if (scope) { - callback.call(scope); - } else { - callback.call(ScriptLoader); - } - } - } - - if (self.urls[name]) { - return; - } - - if (typeof addOnUrl === "object") { - url = addOnUrl.prefix + addOnUrl.resource + addOnUrl.suffix; - } - - if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) { - url = AddOnManager.baseURL + '/' + url; - } - - self.urls[name] = url.substring(0, url.lastIndexOf('/')); - - if (self.lookup[name]) { - loadDependencies(); - } else { - ScriptLoader.ScriptLoader.add(url, loadDependencies, scope); - } - } - }; - - AddOnManager.PluginManager = new AddOnManager(); - AddOnManager.ThemeManager = new AddOnManager(); - - return AddOnManager; -}); - -/** - * TinyMCE theme class. - * - * @class tinymce.Theme - */ - -/** - * This method is responsible for rendering/generating the overall user interface with toolbars, buttons, iframe containers etc. - * - * @method renderUI - * @param {Object} obj Object parameter containing the targetNode DOM node that will be replaced visually with an editor instance. - * @return {Object} an object with items like iframeContainer, editorContainer, sizeContainer, deltaWidth, deltaHeight. - */ - -/** - * Plugin base class, this is a pseudo class that describes how a plugin is to be created for TinyMCE. The methods below are all optional. - * - * @class tinymce.Plugin - * @example - * tinymce.PluginManager.add('example', function(editor, url) { - * // Add a button that opens a window - * editor.addButton('example', { - * text: 'My button', - * icon: false, - * onclick: function() { - * // Open window - * editor.windowManager.open({ - * title: 'Example plugin', - * body: [ - * {type: 'textbox', name: 'title', label: 'Title'} - * ], - * onsubmit: function(e) { - * // Insert content when the window form is submitted - * editor.insertContent('Title: ' + e.data.title); - * } - * }); - * } - * }); - * - * // Adds a menu item to the tools menu - * editor.addMenuItem('example', { - * text: 'Example plugin', - * context: 'tools', - * onclick: function() { - * // Open window with a specific url - * editor.windowManager.open({ - * title: 'TinyMCE site', - * url: 'http://www.tinymce.com', - * width: 800, - * height: 600, - * buttons: [{ - * text: 'Close', - * onclick: 'close' - * }] - * }); - * } - * }); - * }); - */ - -// Included from: js/tinymce/classes/dom/RangeUtils.js - -/** - * RangeUtils.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * This class contains a few utility methods for ranges. - * - * @class tinymce.dom.RangeUtils - */ -define("tinymce/dom/RangeUtils", [ - "tinymce/util/Tools", - "tinymce/dom/TreeWalker" -], function(Tools, TreeWalker) { - var each = Tools.each; - - function getEndChild(container, index) { - var childNodes = container.childNodes; - - index--; - - if (index > childNodes.length - 1) { - index = childNodes.length - 1; - } else if (index < 0) { - index = 0; - } - - return childNodes[index] || container; - } - - function RangeUtils(dom) { - /** - * Walks the specified range like object and executes the callback for each sibling collection it finds. - * - * @method walk - * @param {Object} rng Range like object. - * @param {function} callback Callback function to execute for each sibling collection. - */ - this.walk = function(rng, callback) { - var startContainer = rng.startContainer, - startOffset = rng.startOffset, - endContainer = rng.endContainer, - endOffset = rng.endOffset, - ancestor, startPoint, - endPoint, node, parent, siblings, nodes; - - // Handle table cell selection the table plugin enables - // you to fake select table cells and perform formatting actions on them - nodes = dom.select('td.mce-item-selected,th.mce-item-selected'); - if (nodes.length > 0) { - each(nodes, function(node) { - callback([node]); - }); - - return; - } - - /** - * Excludes start/end text node if they are out side the range - * - * @private - * @param {Array} nodes Nodes to exclude items from. - * @return {Array} Array with nodes excluding the start/end container if needed. - */ - function exclude(nodes) { - var node; - - // First node is excluded - node = nodes[0]; - if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) { - nodes.splice(0, 1); - } - - // Last node is excluded - node = nodes[nodes.length - 1]; - if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) { - nodes.splice(nodes.length - 1, 1); - } - - return nodes; - } - - /** - * Collects siblings - * - * @private - * @param {Node} node Node to collect siblings from. - * @param {String} name Name of the sibling to check for. - * @return {Array} Array of collected siblings. - */ - function collectSiblings(node, name, end_node) { - var siblings = []; - - for (; node && node != end_node; node = node[name]) { - siblings.push(node); - } - - return siblings; - } - - /** - * Find an end point this is the node just before the common ancestor root. - * - * @private - * @param {Node} node Node to start at. - * @param {Node} root Root/ancestor element to stop just before. - * @return {Node} Node just before the root element. - */ - function findEndPoint(node, root) { - do { - if (node.parentNode == root) { - return node; - } - - node = node.parentNode; - } while (node); - } - - function walkBoundary(start_node, end_node, next) { - var siblingName = next ? 'nextSibling' : 'previousSibling'; - - for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) { - parent = node.parentNode; - siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName); - - if (siblings.length) { - if (!next) { - siblings.reverse(); - } - - callback(exclude(siblings)); - } - } - } - - // If index based start position then resolve it - if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { - startContainer = startContainer.childNodes[startOffset]; - } - - // If index based end position then resolve it - if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { - endContainer = getEndChild(endContainer, endOffset); - } - - // Same container - if (startContainer == endContainer) { - return callback(exclude([startContainer])); - } - - // Find common ancestor and end points - ancestor = dom.findCommonAncestor(startContainer, endContainer); - - // Process left side - for (node = startContainer; node; node = node.parentNode) { - if (node === endContainer) { - return walkBoundary(startContainer, ancestor, true); - } - - if (node === ancestor) { - break; - } - } - - // Process right side - for (node = endContainer; node; node = node.parentNode) { - if (node === startContainer) { - return walkBoundary(endContainer, ancestor); - } - - if (node === ancestor) { - break; - } - } - - // Find start/end point - startPoint = findEndPoint(startContainer, ancestor) || startContainer; - endPoint = findEndPoint(endContainer, ancestor) || endContainer; - - // Walk left leaf - walkBoundary(startContainer, startPoint, true); - - // Walk the middle from start to end point - siblings = collectSiblings( - startPoint == startContainer ? startPoint : startPoint.nextSibling, - 'nextSibling', - endPoint == endContainer ? endPoint.nextSibling : endPoint - ); - - if (siblings.length) { - callback(exclude(siblings)); - } - - // Walk right leaf - walkBoundary(endContainer, endPoint); - }; - - /** - * Splits the specified range at it's start/end points. - * - * @private - * @param {Range/RangeObject} rng Range to split. - * @return {Object} Range position object. - */ - this.split = function(rng) { - var startContainer = rng.startContainer, - startOffset = rng.startOffset, - endContainer = rng.endContainer, - endOffset = rng.endOffset; - - function splitText(node, offset) { - return node.splitText(offset); - } - - // Handle single text node - if (startContainer == endContainer && startContainer.nodeType == 3) { - if (startOffset > 0 && startOffset < startContainer.nodeValue.length) { - endContainer = splitText(startContainer, startOffset); - startContainer = endContainer.previousSibling; - - if (endOffset > startOffset) { - endOffset = endOffset - startOffset; - startContainer = endContainer = splitText(endContainer, endOffset).previousSibling; - endOffset = endContainer.nodeValue.length; - startOffset = 0; - } else { - endOffset = 0; - } - } - } else { - // Split startContainer text node if needed - if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) { - startContainer = splitText(startContainer, startOffset); - startOffset = 0; - } - - // Split endContainer text node if needed - if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) { - endContainer = splitText(endContainer, endOffset).previousSibling; - endOffset = endContainer.nodeValue.length; - } - } - - return { - startContainer: startContainer, - startOffset: startOffset, - endContainer: endContainer, - endOffset: endOffset - }; - }; - - /** - * Normalizes the specified range by finding the closest best suitable caret location. - * - * @private - * @param {Range} rng Range to normalize. - * @return {Boolean} True/false if the specified range was normalized or not. - */ - this.normalize = function(rng) { - var normalized, collapsed; - - function normalizeEndPoint(start) { - var container, offset, walker, body = dom.getRoot(), node, nonEmptyElementsMap; - var directionLeft, isAfterNode; - - function hasBrBeforeAfter(node, left) { - var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body); - - while ((node = walker[left ? 'prev' : 'next']())) { - if (node.nodeName === "BR") { - return true; - } - } - } - - function isPrevNode(node, name) { - return node.previousSibling && node.previousSibling.nodeName == name; - } - - // Walks the dom left/right to find a suitable text node to move the endpoint into - // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG - function findTextNodeRelative(left, startNode) { - var walker, lastInlineElement, parentBlockContainer; - - startNode = startNode || container; - parentBlockContainer = dom.getParent(startNode.parentNode, dom.isBlock) || body; - - // Lean left before the BR element if it's the only BR within a block element. Gecko bug: #6680 - // This:
|
|
x|
|
- if (node.nodeName == 'IMG' && selection.isCollapsed()) { - node = node.parentNode; - } - - // Get parents and add them to object - parents = []; - editor.dom.getParent(node, function(node) { - if (node === root) { - return true; - } - - parents.push(node); - }); - - args = args || {}; - args.element = node; - args.parents = parents; - - editor.fire('NodeChange', args); - } - }; - }; -}); - -// Included from: js/tinymce/classes/html/Node.js - -/** - * Node.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * This class is a minimalistic implementation of a DOM like node used by the DomParser class. - * - * @example - * var node = new tinymce.html.Node('strong', 1); - * someRoot.append(node); - * - * @class tinymce.html.Node - * @version 3.4 - */ -define("tinymce/html/Node", [], function() { - var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = { - '#text': 3, - '#comment': 8, - '#cdata': 4, - '#pi': 7, - '#doctype': 10, - '#document-fragment': 11 - }; - - // Walks the tree left/right - function walk(node, root_node, prev) { - var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next'; - - // Walk into nodes if it has a start - if (node[startName]) { - return node[startName]; - } - - // Return the sibling if it has one - if (node !== root_node) { - sibling = node[siblingName]; - - if (sibling) { - return sibling; - } - - // Walk up the parents to look for siblings - for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) { - sibling = parent[siblingName]; - - if (sibling) { - return sibling; - } - } - } - } - - /** - * Constructs a new Node instance. - * - * @constructor - * @method Node - * @param {String} name Name of the node type. - * @param {Number} type Numeric type representing the node. - */ - function Node(name, type) { - this.name = name; - this.type = type; - - if (type === 1) { - this.attributes = []; - this.attributes.map = {}; - } - } - - Node.prototype = { - /** - * Replaces the current node with the specified one. - * - * @example - * someNode.replace(someNewNode); - * - * @method replace - * @param {tinymce.html.Node} node Node to replace the current node with. - * @return {tinymce.html.Node} The old node that got replaced. - */ - replace: function(node) { - var self = this; - - if (node.parent) { - node.remove(); - } - - self.insert(node, self); - self.remove(); - - return self; - }, - - /** - * Gets/sets or removes an attribute by name. - * - * @example - * someNode.attr("name", "value"); // Sets an attribute - * console.log(someNode.attr("name")); // Gets an attribute - * someNode.attr("name", null); // Removes an attribute - * - * @method attr - * @param {String} name Attribute name to set or get. - * @param {String} value Optional value to set. - * @return {String/tinymce.html.Node} String or undefined on a get operation or the current node on a set operation. - */ - attr: function(name, value) { - var self = this, attrs, i, undef; - - if (typeof name !== "string") { - for (i in name) { - self.attr(i, name[i]); - } - - return self; - } - - if ((attrs = self.attributes)) { - if (value !== undef) { - // Remove attribute - if (value === null) { - if (name in attrs.map) { - delete attrs.map[name]; - - i = attrs.length; - while (i--) { - if (attrs[i].name === name) { - attrs = attrs.splice(i, 1); - return self; - } - } - } - - return self; - } - - // Set attribute - if (name in attrs.map) { - // Set attribute - i = attrs.length; - while (i--) { - if (attrs[i].name === name) { - attrs[i].value = value; - break; - } - } - } else { - attrs.push({name: name, value: value}); - } - - attrs.map[name] = value; - - return self; - } else { - return attrs.map[name]; - } - } - }, - - /** - * Does a shallow clones the node into a new node. It will also exclude id attributes since - * there should only be one id per document. - * - * @example - * var clonedNode = node.clone(); - * - * @method clone - * @return {tinymce.html.Node} New copy of the original node. - */ - clone: function() { - var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs; - - // Clone element attributes - if ((selfAttrs = self.attributes)) { - cloneAttrs = []; - cloneAttrs.map = {}; - - for (i = 0, l = selfAttrs.length; i < l; i++) { - selfAttr = selfAttrs[i]; - - // Clone everything except id - if (selfAttr.name !== 'id') { - cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value}; - cloneAttrs.map[selfAttr.name] = selfAttr.value; - } - } - - clone.attributes = cloneAttrs; - } - - clone.value = self.value; - clone.shortEnded = self.shortEnded; - - return clone; - }, - - /** - * Wraps the node in in another node. - * - * @example - * node.wrap(wrapperNode); - * - * @method wrap - */ - wrap: function(wrapper) { - var self = this; - - self.parent.insert(wrapper, self); - wrapper.append(self); - - return self; - }, - - /** - * Unwraps the node in other words it removes the node but keeps the children. - * - * @example - * node.unwrap(); - * - * @method unwrap - */ - unwrap: function() { - var self = this, node, next; - - for (node = self.firstChild; node;) { - next = node.next; - self.insert(node, self, true); - node = next; - } - - self.remove(); - }, - - /** - * Removes the node from it's parent. - * - * @example - * node.remove(); - * - * @method remove - * @return {tinymce.html.Node} Current node that got removed. - */ - remove: function() { - var self = this, parent = self.parent, next = self.next, prev = self.prev; - - if (parent) { - if (parent.firstChild === self) { - parent.firstChild = next; - - if (next) { - next.prev = null; - } - } else { - prev.next = next; - } - - if (parent.lastChild === self) { - parent.lastChild = prev; - - if (prev) { - prev.next = null; - } - } else { - next.prev = prev; - } - - self.parent = self.next = self.prev = null; - } - - return self; - }, - - /** - * Appends a new node as a child of the current node. - * - * @example - * node.append(someNode); - * - * @method append - * @param {tinymce.html.Node} node Node to append as a child of the current one. - * @return {tinymce.html.Node} The node that got appended. - */ - append: function(node) { - var self = this, last; - - if (node.parent) { - node.remove(); - } - - last = self.lastChild; - if (last) { - last.next = node; - node.prev = last; - self.lastChild = node; - } else { - self.lastChild = self.firstChild = node; - } - - node.parent = self; - - return node; - }, - - /** - * Inserts a node at a specific position as a child of the current node. - * - * @example - * parentNode.insert(newChildNode, oldChildNode); - * - * @method insert - * @param {tinymce.html.Node} node Node to insert as a child of the current node. - * @param {tinymce.html.Node} ref_node Reference node to set node before/after. - * @param {Boolean} before Optional state to insert the node before the reference node. - * @return {tinymce.html.Node} The node that got inserted. - */ - insert: function(node, ref_node, before) { - var parent; - - if (node.parent) { - node.remove(); - } - - parent = ref_node.parent || this; - - if (before) { - if (ref_node === parent.firstChild) { - parent.firstChild = node; - } else { - ref_node.prev.next = node; - } - - node.prev = ref_node.prev; - node.next = ref_node; - ref_node.prev = node; - } else { - if (ref_node === parent.lastChild) { - parent.lastChild = node; - } else { - ref_node.next.prev = node; - } - - node.next = ref_node.next; - node.prev = ref_node; - ref_node.next = node; - } - - node.parent = parent; - - return node; - }, - - /** - * Get all children by name. - * - * @method getAll - * @param {String} name Name of the child nodes to collect. - * @return {Array} Array with child nodes matchin the specified name. - */ - getAll: function(name) { - var self = this, node, collection = []; - - for (node = self.firstChild; node; node = walk(node, self)) { - if (node.name === name) { - collection.push(node); - } - } - - return collection; - }, - - /** - * Removes all children of the current node. - * - * @method empty - * @return {tinymce.html.Node} The current node that got cleared. - */ - empty: function() { - var self = this, nodes, i, node; - - // Remove all children - if (self.firstChild) { - nodes = []; - - // Collect the children - for (node = self.firstChild; node; node = walk(node, self)) { - nodes.push(node); - } - - // Remove the children - i = nodes.length; - while (i--) { - node = nodes[i]; - node.parent = node.firstChild = node.lastChild = node.next = node.prev = null; - } - } - - self.firstChild = self.lastChild = null; - - return self; - }, - - /** - * Returns true/false if the node is to be considered empty or not. - * - * @example - * node.isEmpty({img: true}); - * @method isEmpty - * @param {Object} elements Name/value object with elements that are automatically treated as non empty elements. - * @return {Boolean} true/false if the node is empty or not. - */ - isEmpty: function(elements) { - var self = this, node = self.firstChild, i, name; - - if (node) { - do { - if (node.type === 1) { - // Ignore bogus elements - if (node.attributes.map['data-mce-bogus']) { - continue; - } - - // Keep empty elements like - if (elements[node.name]) { - return false; - } - - // Keep bookmark nodes and name attribute like - i = node.attributes.length; - while (i--) { - name = node.attributes[i].name; - if (name === "name" || name.indexOf('data-mce-bookmark') === 0) { - return false; - } - } - } - - // Keep comments - if (node.type === 8) { - return false; - } - - // Keep non whitespace text nodes - if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) { - return false; - } - } while ((node = walk(node, self))); - } - - return true; - }, - - /** - * Walks to the next or previous node and returns that node or null if it wasn't found. - * - * @method walk - * @param {Boolean} prev Optional previous node state defaults to false. - * @return {tinymce.html.Node} Node that is next to or previous of the current node. - */ - walk: function(prev) { - return walk(this, null, prev); - } - }; - - /** - * Creates a node of a specific type. - * - * @static - * @method create - * @param {String} name Name of the node type to create for example "b" or "#text". - * @param {Object} attrs Name/value collection of attributes that will be applied to elements. - */ - Node.create = function(name, attrs) { - var node, attrName; - - // Create node - node = new Node(name, typeLookup[name] || 1); - - // Add attributes if needed - if (attrs) { - for (attrName in attrs) { - node.attr(attrName, attrs[attrName]); - } - } - - return node; - }; - - return Node; -}); - -// Included from: js/tinymce/classes/html/Schema.js - -/** - * Schema.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * Schema validator class. - * - * @class tinymce.html.Schema - * @example - * if (tinymce.activeEditor.schema.isValidChild('p', 'span')) - * alert('span is valid child of p.'); - * - * if (tinymce.activeEditor.schema.getElementRule('p')) - * alert('P is a valid element.'); - * - * @class tinymce.html.Schema - * @version 3.4 - */ -define("tinymce/html/Schema", [ - "tinymce/util/Tools" -], function(Tools) { - var mapCache = {}, dummyObj = {}; - var makeMap = Tools.makeMap, each = Tools.each, extend = Tools.extend, explode = Tools.explode, inArray = Tools.inArray; - - function split(items, delim) { - return items ? items.split(delim || ' ') : []; - } - - /** - * Builds a schema lookup table - * - * @private - * @param {String} type html4, html5 or html5-strict schema type. - * @return {Object} Schema lookup table. - */ - function compileSchema(type) { - var schema = {}, globalAttributes, blockContent; - var phrasingContent, flowContent, html4BlockContent, html4PhrasingContent; - - function add(name, attributes, children) { - var ni, i, attributesOrder, args = arguments; - - function arrayToMap(array, obj) { - var map = {}, i, l; - - for (i = 0, l = array.length; i < l; i++) { - map[array[i]] = obj || {}; - } - - return map; - } - - children = children || []; - attributes = attributes || ""; - - if (typeof children === "string") { - children = split(children); - } - - // Split string children - for (i = 3; i < args.length; i++) { - if (typeof args[i] === "string") { - args[i] = split(args[i]); - } - - children.push.apply(children, args[i]); - } - - name = split(name); - ni = name.length; - while (ni--) { - attributesOrder = [].concat(globalAttributes, split(attributes)); - schema[name[ni]] = { - attributes: arrayToMap(attributesOrder), - attributesOrder: attributesOrder, - children: arrayToMap(children, dummyObj) - }; - } - } - - function addAttrs(name, attributes) { - var ni, schemaItem, i, l; - - name = split(name); - ni = name.length; - attributes = split(attributes); - while (ni--) { - schemaItem = schema[name[ni]]; - for (i = 0, l = attributes.length; i < l; i++) { - schemaItem.attributes[attributes[i]] = {}; - schemaItem.attributesOrder.push(attributes[i]); - } - } - } - - // Use cached schema - if (mapCache[type]) { - return mapCache[type]; - } - - // Attributes present on all elements - globalAttributes = split("id accesskey class dir lang style tabindex title"); - - // Event attributes can be opt-in/opt-out - /*eventAttributes = split("onabort onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextmenu oncuechange " + - "ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended " + - "onerror onfocus oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart " + - "onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onpause onplay onplaying onprogress onratechange " + - "onreset onscroll onseeked onseeking onseeking onselect onshow onstalled onsubmit onsuspend ontimeupdate onvolumechange " + - "onwaiting" - );*/ - - // Block content elements - blockContent = split( - "address blockquote div dl fieldset form h1 h2 h3 h4 h5 h6 hr menu ol p pre table ul" - ); - - // Phrasing content elements from the HTML5 spec (inline) - phrasingContent = split( - "a abbr b bdo br button cite code del dfn em embed i iframe img input ins kbd " + - "label map noscript object q s samp script select small span strong sub sup " + - "textarea u var #text #comment" - ); - - // Add HTML5 items to globalAttributes, blockContent, phrasingContent - if (type != "html4") { - globalAttributes.push.apply(globalAttributes, split("contenteditable contextmenu draggable dropzone " + - "hidden spellcheck translate")); - blockContent.push.apply(blockContent, split("article aside details dialog figure header footer hgroup section nav")); - phrasingContent.push.apply(phrasingContent, split("audio canvas command datalist mark meter output progress time wbr " + - "video ruby bdi keygen")); - } - - // Add HTML4 elements unless it's html5-strict - if (type != "html5-strict") { - globalAttributes.push("xml:lang"); - - html4PhrasingContent = split("acronym applet basefont big font strike tt"); - phrasingContent.push.apply(phrasingContent, html4PhrasingContent); - - each(html4PhrasingContent, function(name) { - add(name, "", phrasingContent); - }); - - html4BlockContent = split("center dir isindex noframes"); - blockContent.push.apply(blockContent, html4BlockContent); - - // Flow content elements from the HTML5 spec (block+inline) - flowContent = [].concat(blockContent, phrasingContent); - - each(html4BlockContent, function(name) { - add(name, "", flowContent); - }); - } - - // Flow content elements from the HTML5 spec (block+inline) - flowContent = flowContent || [].concat(blockContent, phrasingContent); - - // HTML4 base schema TODO: Move HTML5 specific attributes to HTML5 specific if statement - // Schema itemsa
b
c will becomea
b
c
- * - * @example - * var parser = new tinymce.html.DomParser({validate: true}, schema); - * var rootNode = parser.parse('x
->x
- function trim(rootBlockNode) { - if (rootBlockNode) { - node = rootBlockNode.firstChild; - if (node && node.type == 3) { - node.value = node.value.replace(startWhiteSpaceRegExp, ''); - } - - node = rootBlockNode.lastChild; - if (node && node.type == 3) { - node.value = node.value.replace(endWhiteSpaceRegExp, ''); - } - } - } - - // Check if rootBlock is valid within rootNode for example if P is valid in H1 if H1 is the contentEditabe root - if (!schema.isValidChild(rootNode.name, rootBlockName.toLowerCase())) { - return; - } - - while (node) { - next = node.next; - - if (node.type == 3 || (node.type == 1 && node.name !== 'p' && - !blockElements[node.name] && !node.attr('data-mce-type'))) { - if (!rootBlockNode) { - // Create a new root block element - rootBlockNode = createNode(rootBlockName, 1); - rootBlockNode.attr(settings.forced_root_block_attrs); - rootNode.insert(rootBlockNode, node); - rootBlockNode.append(node); - } else { - rootBlockNode.append(node); - } - } else { - trim(rootBlockNode); - rootBlockNode = null; - } - - node = next; - } - - trim(rootBlockNode); - } - - function createNode(name, type) { - var node = new Node(name, type), list; - - if (name in nodeFilters) { - list = matchedNodes[name]; - - if (list) { - list.push(node); - } else { - matchedNodes[name] = [node]; - } - } - - return node; - } - - function removeWhitespaceBefore(node) { - var textNode, textVal, sibling; - - for (textNode = node.prev; textNode && textNode.type === 3;) { - textVal = textNode.value.replace(endWhiteSpaceRegExp, ''); - - if (textVal.length > 0) { - textNode.value = textVal; - textNode = textNode.prev; - } else { - sibling = textNode.prev; - textNode.remove(); - textNode = sibling; - } - } - } - - function cloneAndExcludeBlocks(input) { - var name, output = {}; - - for (name in input) { - if (name !== 'li' && name != 'p') { - output[name] = input[name]; - } - } - - return output; - } - - parser = new SaxParser({ - validate: validate, - allow_script_urls: settings.allow_script_urls, - allow_conditional_comments: settings.allow_conditional_comments, - - // Exclude P and LI from DOM parsing since it's treated better by the DOM parser - self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()), - - cdata: function(text) { - node.append(createNode('#cdata', 4)).value = text; - }, - - text: function(text, raw) { - var textNode; - - // Trim all redundant whitespace on non white space elements - if (!isInWhiteSpacePreservedElement) { - text = text.replace(allWhiteSpaceRegExp, ' '); - - if (node.lastChild && blockElements[node.lastChild.name]) { - text = text.replace(startWhiteSpaceRegExp, ''); - } - } - - // Do we need to create the node - if (text.length !== 0) { - textNode = createNode('#text', 3); - textNode.raw = !!raw; - node.append(textNode).value = text; - } - }, - - comment: function(text) { - node.append(createNode('#comment', 8)).value = text; - }, - - pi: function(name, text) { - node.append(createNode(name, 7)).value = text; - removeWhitespaceBefore(node); - }, - - doctype: function(text) { - var newNode; - - newNode = node.append(createNode('#doctype', 10)); - newNode.value = text; - removeWhitespaceBefore(node); - }, - - start: function(name, attrs, empty) { - var newNode, attrFiltersLen, elementRule, attrName, parent; - - elementRule = validate ? schema.getElementRule(name) : {}; - if (elementRule) { - newNode = createNode(elementRule.outputName || name, 1); - newNode.attributes = attrs; - newNode.shortEnded = empty; - - node.append(newNode); - - // Check if node is valid child of the parent node is the child is - // unknown we don't collect it since it's probably a custom element - parent = children[node.name]; - if (parent && children[newNode.name] && !parent[newNode.name]) { - invalidChildren.push(newNode); - } - - attrFiltersLen = attributeFilters.length; - while (attrFiltersLen--) { - attrName = attributeFilters[attrFiltersLen].name; - - if (attrName in attrs.map) { - list = matchedAttributes[attrName]; - - if (list) { - list.push(newNode); - } else { - matchedAttributes[attrName] = [newNode]; - } - } - } - - // Trim whitespace before block - if (blockElements[name]) { - removeWhitespaceBefore(newNode); - } - - // Change current node if the element wasn't empty i.e nota
- lastParent = node; - while (parent && parent.firstChild === lastParent && parent.lastChild === lastParent) { - lastParent = parent; - - if (blockElements[parent.name]) { - break; - } - - parent = parent.parent; - } - - if (lastParent === parent) { - textNode = new Node('#text', 3); - textNode.value = '\u00a0'; - node.replace(textNode); - } - } - } - }); - } - - // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included. - if (!settings.allow_html_in_named_anchor) { - self.addAttributeFilter('id,name', function(nodes) { - var i = nodes.length, sibling, prevSibling, parent, node; - - while (i--) { - node = nodes[i]; - if (node.name === 'a' && node.firstChild && !node.attr('href')) { - parent = node.parent; - - // Move children after current node - sibling = node.lastChild; - do { - prevSibling = sibling.prev; - parent.insert(sibling, node); - sibling = prevSibling; - } while (sibling); - } - } - }); - } - - if (settings.validate && schema.getValidClasses()) { - self.addAttributeFilter('class', function(nodes) { - var i = nodes.length, node, classList, ci, className, classValue; - var validClasses = schema.getValidClasses(), validClassesMap, valid; - - while (i--) { - node = nodes[i]; - classList = node.attr('class').split(' '); - classValue = ''; - - for (ci = 0; ci < classList.length; ci++) { - className = classList[ci]; - valid = false; - - validClassesMap = validClasses['*']; - if (validClassesMap && validClassesMap[className]) { - valid = true; - } - - validClassesMap = validClasses[node.name]; - if (!valid && validClassesMap && validClassesMap[className]) { - valid = true; - } - - if (valid) { - if (classValue) { - classValue += ' '; - } - - classValue += className; - } - } - - if (!classValue.length) { - classValue = null; - } - - node.attr('class', classValue); - } - }); - } - }; -}); - -// Included from: js/tinymce/classes/html/Writer.js - -/** - * Writer.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * This class is used to write HTML tags out it can be used with the Serializer or the SaxParser. - * - * @class tinymce.html.Writer - * @example - * var writer = new tinymce.html.Writer({indent: true}); - * var parser = new tinymce.html.SaxParser(writer).parse('
.
- *
- * @method start
- * @param {String} name Name of the element.
- * @param {Array} attrs Optional attribute array or undefined if it hasn't any.
- * @param {Boolean} empty Optional empty state if the tag should end like
.
- */
- start: function(name, attrs, empty) {
- var i, l, attr, value;
-
- if (indent && indentBefore[name] && html.length > 0) {
- value = html[html.length - 1];
-
- if (value.length > 0 && value !== '\n') {
- html.push('\n');
- }
- }
-
- html.push('<', name);
-
- if (attrs) {
- for (i = 0, l = attrs.length; i < l; i++) {
- attr = attrs[i];
- html.push(' ', attr.name, '="', encode(attr.value, true), '"');
- }
- }
-
- if (!empty || htmlOutput) {
- html[html.length] = '>';
- } else {
- html[html.length] = ' />';
- }
-
- if (empty && indent && indentAfter[name] && html.length > 0) {
- value = html[html.length - 1];
-
- if (value.length > 0 && value !== '\n') {
- html.push('\n');
- }
- }
- },
-
- /**
- * Writes the a end element such as
text
')); - * @class tinymce.html.Serializer - * @version 3.4 - */ -define("tinymce/html/Serializer", [ - "tinymce/html/Writer", - "tinymce/html/Schema" -], function(Writer, Schema) { - /** - * Constructs a new Serializer instance. - * - * @constructor - * @method Serializer - * @param {Object} settings Name/value settings object. - * @param {tinymce.html.Schema} schema Schema instance to use. - */ - return function(settings, schema) { - var self = this, writer = new Writer(settings); - - settings = settings || {}; - settings.validate = "validate" in settings ? settings.validate : true; - - self.schema = schema = schema || new Schema(); - self.writer = writer; - - /** - * Serializes the specified node into a string. - * - * @example - * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('text
')); - * @method serialize - * @param {tinymce.html.Node} node Node instance to serialize. - * @return {String} String with HTML based on DOM tree. - */ - self.serialize = function(node) { - var handlers, validate; - - validate = settings.validate; - - handlers = { - // #text - 3: function(node) { - writer.text(node.value, node.raw); - }, - - // #comment - 8: function(node) { - writer.comment(node.value); - }, - - // Processing instruction - 7: function(node) { - writer.pi(node.name, node.value); - }, - - // Doctype - 10: function(node) { - writer.doctype(node.value); - }, - - // CDATA - 4: function(node) { - writer.cdata(node.value); - }, - - // Document fragment - 11: function(node) { - if ((node = node.firstChild)) { - do { - walk(node); - } while ((node = node.next)); - } - } - }; - - writer.reset(); - - function walk(node) { - var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule; - - if (!handler) { - name = node.name; - isEmpty = node.shortEnded; - attrs = node.attributes; - - // Sort attributes - if (validate && attrs && attrs.length > 1) { - sortedAttrs = []; - sortedAttrs.map = {}; - - elementRule = schema.getElementRule(node.name); - for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) { - attrName = elementRule.attributesOrder[i]; - - if (attrName in attrs.map) { - attrValue = attrs.map[attrName]; - sortedAttrs.map[attrName] = attrValue; - sortedAttrs.push({name: attrName, value: attrValue}); - } - } - - for (i = 0, l = attrs.length; i < l; i++) { - attrName = attrs[i].name; - - if (!(attrName in sortedAttrs.map)) { - attrValue = attrs.map[attrName]; - sortedAttrs.map[attrName] = attrValue; - sortedAttrs.push({name: attrName, value: attrValue}); - } - } - - attrs = sortedAttrs; - } - - writer.start(node.name, attrs, isEmpty); - - if (!isEmpty) { - if ((node = node.firstChild)) { - do { - walk(node); - } while ((node = node.next)); - } - - writer.end(name); - } - } else { - handler(node); - } - } - - // Serialize element and treat all non elements as fragments - if (node.type == 1 && !settings.inner) { - walk(node); - } else { - handlers[11](node); - } - - return writer.getContent(); - }; - }; -}); - -// Included from: js/tinymce/classes/dom/Serializer.js - -/** - * Serializer.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * This class is used to serialize DOM trees into a string. Consult the TinyMCE Wiki API for - * more details and examples on how to use this class. - * - * @class tinymce.dom.Serializer - */ -define("tinymce/dom/Serializer", [ - "tinymce/dom/DOMUtils", - "tinymce/html/DomParser", - "tinymce/html/Entities", - "tinymce/html/Serializer", - "tinymce/html/Node", - "tinymce/html/Schema", - "tinymce/Env", - "tinymce/util/Tools" -], function(DOMUtils, DomParser, Entities, Serializer, Node, Schema, Env, Tools) { - var each = Tools.each, trim = Tools.trim; - var DOM = DOMUtils.DOM; - - /** - * Constructs a new DOM serializer class. - * - * @constructor - * @method Serializer - * @param {Object} settings Serializer settings object. - * @param {tinymce.Editor} editor Optional editor to bind events to and get schema/dom from. - */ - return function(settings, editor) { - var dom, schema, htmlParser; - - if (editor) { - dom = editor.dom; - schema = editor.schema; - } - - // Default DOM and Schema if they are undefined - dom = dom || DOM; - schema = schema || new Schema(settings); - settings.entity_encoding = settings.entity_encoding || 'named'; - settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true; - - htmlParser = new DomParser(settings, schema); - - // Convert tabindex back to elements when serializing contents - htmlParser.addAttributeFilter('data-mce-tabindex', function(nodes, name) { - var i = nodes.length, node; - - while (i--) { - node = nodes[i]; - node.attr('tabindex', node.attributes.map['data-mce-tabindex']); - node.attr(name, null); - } - }); - - // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed - htmlParser.addAttributeFilter('src,href,style', function(nodes, name) { - var i = nodes.length, node, value, internalName = 'data-mce-' + name; - var urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef; - - while (i--) { - node = nodes[i]; - - value = node.attributes.map[internalName]; - if (value !== undef) { - // Set external name to internal value and remove internal - node.attr(name, value.length > 0 ? value : null); - node.attr(internalName, null); - } else { - // No internal attribute found then convert the value we have in the DOM - value = node.attributes.map[name]; - - if (name === "style") { - value = dom.serializeStyle(dom.parseStyle(value), node.name); - } else if (urlConverter) { - value = urlConverter.call(urlConverterScope, value, name, node.name); - } - - node.attr(name, value.length > 0 ? value : null); - } - } - }); - - // Remove internal classes mceItem<..> or mceSelected - htmlParser.addAttributeFilter('class', function(nodes) { - var i = nodes.length, node, value; - - while (i--) { - node = nodes[i]; - value = node.attr('class'); - - if (value) { - value = node.attr('class').replace(/(?:^|\s)mce-item-\w+(?!\S)/g, ''); - node.attr('class', value.length > 0 ? value : null); - } - } - }); - - // Remove bookmark elements - htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) { - var i = nodes.length, node; - - while (i--) { - node = nodes[i]; - - if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) { - node.remove(); - } - } - }); - - htmlParser.addNodeFilter('noscript', function(nodes) { - var i = nodes.length, node; - - while (i--) { - node = nodes[i].firstChild; - - if (node) { - node.value = Entities.decode(node.value); - } - } - }); - - // Force script into CDATA sections and remove the mce- prefix also add comments around styles - htmlParser.addNodeFilter('script,style', function(nodes, name) { - var i = nodes.length, node, value, type; - - function trim(value) { - /*jshint maxlen:255 */ - /*eslint max-len:0 */ - return value.replace(/()/g, '\n') - .replace(/^[\r\n]*|[\r\n]*$/g, '') - .replace(/^\s*(()?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, ''); - } - - while (i--) { - node = nodes[i]; - value = node.firstChild ? node.firstChild.value : ''; - - if (name === "script") { - // Remove mce- prefix from script elements and remove default type since the user specified - // a script element without type attribute - type = node.attr('type'); - if (type) { - node.attr('type', type == 'mce-no/type' ? null : type.replace(/^mce\-/, '')); - } - - if (value.length > 0) { - node.firstChild.value = '// '; - } - } else { - if (value.length > 0) { - node.firstChild.value = ''; - } - } - } - }); - - // Convert comments to cdata and handle protected comments - htmlParser.addNodeFilter('#comment', function(nodes) { - var i = nodes.length, node; - - while (i--) { - node = nodes[i]; - - if (node.value.indexOf('[CDATA[') === 0) { - node.name = '#cdata'; - node.type = 4; - node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, ''); - } else if (node.value.indexOf('mce:protected ') === 0) { - node.name = "#text"; - node.type = 3; - node.raw = true; - node.value = unescape(node.value).substr(14); - } - } - }); - - htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) { - var i = nodes.length, node; - - while (i--) { - node = nodes[i]; - if (node.type === 7) { - node.remove(); - } else if (node.type === 1) { - if (name === "input" && !("type" in node.attributes.map)) { - node.attr('type', 'text'); - } - } - } - }); - - // Fix list elements, TODO: Replace this later - if (settings.fix_list_elements) { - htmlParser.addNodeFilter('ul,ol', function(nodes) { - var i = nodes.length, node, parentNode; - - while (i--) { - node = nodes[i]; - parentNode = node.parent; - - if (parentNode.name === 'ul' || parentNode.name === 'ol') { - if (node.prev && node.prev.name === 'li') { - node.prev.append(node); - } - } - } - }); - } - - // Remove internal data attributes - htmlParser.addAttributeFilter( - 'data-mce-src,data-mce-href,data-mce-style,' + - 'data-mce-selected,data-mce-expando,' + - 'data-mce-type,data-mce-resize', - - function(nodes, name) { - var i = nodes.length; - - while (i--) { - nodes[i].attr(name, null); - } - } - ); - - // Return public methods - return { - /** - * Schema instance that was used to when the Serializer was constructed. - * - * @field {tinymce.html.Schema} schema - */ - schema: schema, - - /** - * Adds a node filter function to the parser used by the serializer, the parser will collect the specified nodes by name - * and then execute the callback ones it has finished parsing the document. - * - * @example - * parser.addNodeFilter('p,h1', function(nodes, name) { - * for (var i = 0; i < nodes.length; i++) { - * console.log(nodes[i].name); - * } - * }); - * @method addNodeFilter - * @method {String} name Comma separated list of nodes to collect. - * @param {function} callback Callback function to execute once it has collected nodes. - */ - addNodeFilter: htmlParser.addNodeFilter, - - /** - * Adds a attribute filter function to the parser used by the serializer, the parser will - * collect nodes that has the specified attributes - * and then execute the callback ones it has finished parsing the document. - * - * @example - * parser.addAttributeFilter('src,href', function(nodes, name) { - * for (var i = 0; i < nodes.length; i++) { - * console.log(nodes[i].name); - * } - * }); - * @method addAttributeFilter - * @method {String} name Comma separated list of nodes to collect. - * @param {function} callback Callback function to execute once it has collected nodes. - */ - addAttributeFilter: htmlParser.addAttributeFilter, - - /** - * Serializes the specified browser DOM node into a HTML string. - * - * @method serialize - * @param {DOMNode} node DOM node to serialize. - * @param {Object} args Arguments option that gets passed to event handlers. - */ - serialize: function(node, args) { - var self = this, impl, doc, oldDoc, htmlSerializer, content; - - // Explorer won't clone contents of script and style and the - // selected index of select elements are cleared on a clone operation. - if (Env.ie && dom.select('script,style,select,map').length > 0) { - content = node.innerHTML; - node = node.cloneNode(false); - dom.setHTML(node, content); - } else { - node = node.cloneNode(true); - } - - // Nodes needs to be attached to something in WebKit/Opera - // This fix will make DOM ranges and make Sizzle happy! - impl = node.ownerDocument.implementation; - if (impl.createHTMLDocument) { - // Create an empty HTML document - doc = impl.createHTMLDocument(""); - - // Add the element or it's children if it's a body element to the new document - each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) { - doc.body.appendChild(doc.importNode(node, true)); - }); - - // Grab first child or body element for serialization - if (node.nodeName != 'BODY') { - node = doc.body.firstChild; - } else { - node = doc.body; - } - - // set the new document in DOMUtils so createElement etc works - oldDoc = dom.doc; - dom.doc = doc; - } - - args = args || {}; - args.format = args.format || 'html'; - - // Don't wrap content if we want selected html - if (args.selection) { - args.forced_root_block = ''; - } - - // Pre process - if (!args.no_events) { - args.node = node; - self.onPreProcess(args); - } - - // Setup serializer - htmlSerializer = new Serializer(settings, schema); - - // Parse and serialize HTML - args.content = htmlSerializer.serialize( - htmlParser.parse(trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args) - ); - - // Replace all BOM characters for now until we can find a better solution - if (!args.cleanup) { - args.content = args.content.replace(/\uFEFF/g, ''); - } - - // Post process - if (!args.no_events) { - self.onPostProcess(args); - } - - // Restore the old document if it was changed - if (oldDoc) { - dom.doc = oldDoc; - } - - args.node = null; - - return args.content; - }, - - /** - * Adds valid elements rules to the serializers schema instance this enables you to specify things - * like what elements should be outputted and what attributes specific elements might have. - * Consult the Wiki for more details on this format. - * - * @method addRules - * @param {String} rules Valid elements rules string to add to schema. - */ - addRules: function(rules) { - schema.addValidElements(rules); - }, - - /** - * Sets the valid elements rules to the serializers schema instance this enables you to specify things - * like what elements should be outputted and what attributes specific elements might have. - * Consult the Wiki for more details on this format. - * - * @method setRules - * @param {String} rules Valid elements rules string. - */ - setRules: function(rules) { - schema.setValidElements(rules); - }, - - onPreProcess: function(args) { - if (editor) { - editor.fire('PreProcess', args); - } - }, - - onPostProcess: function(args) { - if (editor) { - editor.fire('PostProcess', args); - } - } - }; - }; -}); - -// Included from: js/tinymce/classes/dom/TridentSelection.js - -/** - * TridentSelection.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * Selection class for old explorer versions. This one fakes the - * native selection object available on modern browsers. - * - * @class tinymce.dom.TridentSelection - */ -define("tinymce/dom/TridentSelection", [], function() { - function Selection(selection) { - var self = this, dom = selection.dom, FALSE = false; - - function getPosition(rng, start) { - var checkRng, startIndex = 0, endIndex, inside, - children, child, offset, index, position = -1, parent; - - // Setup test range, collapse it and get the parent - checkRng = rng.duplicate(); - checkRng.collapse(start); - parent = checkRng.parentElement(); - - // Check if the selection is within the right document - if (parent.ownerDocument !== selection.dom.doc) { - return; - } - - // IE will report non editable elements as it's parent so look for an editable one - while (parent.contentEditable === "false") { - parent = parent.parentNode; - } - - // If parent doesn't have any children then return that we are inside the element - if (!parent.hasChildNodes()) { - return {node: parent, inside: 1}; - } - - // Setup node list and endIndex - children = parent.children; - endIndex = children.length - 1; - - // Perform a binary search for the position - while (startIndex <= endIndex) { - index = Math.floor((startIndex + endIndex) / 2); - - // Move selection to node and compare the ranges - child = children[index]; - checkRng.moveToElementText(child); - position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng); - - // Before/after or an exact match - if (position > 0) { - endIndex = index - 1; - } else if (position < 0) { - startIndex = index + 1; - } else { - return {node: child}; - } - } - - // Check if child position is before or we didn't find a position - if (position < 0) { - // No element child was found use the parent element and the offset inside that - if (!child) { - checkRng.moveToElementText(parent); - checkRng.collapse(true); - child = parent; - inside = true; - } else { - checkRng.collapse(false); - } - - // Walk character by character in text node until we hit the selected range endpoint, - // hit the end of document or parent isn't the right one - // We need to walk char by char since rng.text or rng.htmlText will trim line endings - offset = 0; - while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { - if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) { - break; - } - - offset++; - } - } else { - // Child position is after the selection endpoint - checkRng.collapse(true); - - // Walk character by character in text node until we hit the selected range endpoint, hit - // the end of document or parent isn't the right one - offset = 0; - while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { - if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) { - break; - } - - offset++; - } - } - - return {node: child, position: position, offset: offset, inside: inside}; - } - - // Returns a W3C DOM compatible range object by using the IE Range API - function getRange() { - var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark; - - // If selection is outside the current document just return an empty range - element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); - if (element.ownerDocument != dom.doc) { - return domRange; - } - - collapsed = selection.isCollapsed(); - - // Handle control selection - if (ieRange.item) { - domRange.setStart(element.parentNode, dom.nodeIndex(element)); - domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); - - return domRange; - } - - function findEndPoint(start) { - var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue; - - container = endPoint.node; - offset = endPoint.offset; - - if (endPoint.inside && !container.hasChildNodes()) { - domRange[start ? 'setStart' : 'setEnd'](container, 0); - return; - } - - if (offset === undef) { - domRange[start ? 'setStartBefore' : 'setEndAfter'](container); - return; - } - - if (endPoint.position < 0) { - sibling = endPoint.inside ? container.firstChild : container.nextSibling; - - if (!sibling) { - domRange[start ? 'setStartAfter' : 'setEndAfter'](container); - return; - } - - if (!offset) { - if (sibling.nodeType == 3) { - domRange[start ? 'setStart' : 'setEnd'](sibling, 0); - } else { - domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling); - } - - return; - } - - // Find the text node and offset - while (sibling) { - if (sibling.nodeType == 3) { - nodeValue = sibling.nodeValue; - textNodeOffset += nodeValue.length; - - // We are at or passed the position we where looking for - if (textNodeOffset >= offset) { - container = sibling; - textNodeOffset -= offset; - textNodeOffset = nodeValue.length - textNodeOffset; - break; - } - } - - sibling = sibling.nextSibling; - } - } else { - // Find the text node and offset - sibling = container.previousSibling; - - if (!sibling) { - return domRange[start ? 'setStartBefore' : 'setEndBefore'](container); - } - - // If there isn't any text to loop then use the first position - if (!offset) { - if (container.nodeType == 3) { - domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length); - } else { - domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling); - } - - return; - } - - while (sibling) { - if (sibling.nodeType == 3) { - textNodeOffset += sibling.nodeValue.length; - - // We are at or passed the position we where looking for - if (textNodeOffset >= offset) { - container = sibling; - textNodeOffset -= offset; - break; - } - } - - sibling = sibling.previousSibling; - } - } - - domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset); - } - - try { - // Find start point - findEndPoint(true); - - // Find end point if needed - if (!collapsed) { - findEndPoint(); - } - } catch (ex) { - // IE has a nasty bug where text nodes might throw "invalid argument" when you - // access the nodeValue or other properties of text nodes. This seems to happend when - // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it. - if (ex.number == -2147024809) { - // Get the current selection - bookmark = self.getBookmark(2); - - // Get start element - tmpRange = ieRange.duplicate(); - tmpRange.collapse(true); - element = tmpRange.parentElement(); - - // Get end element - if (!collapsed) { - tmpRange = ieRange.duplicate(); - tmpRange.collapse(false); - element2 = tmpRange.parentElement(); - element2.innerHTML = element2.innerHTML; - } - - // Remove the broken elements - element.innerHTML = element.innerHTML; - - // Restore the selection - self.moveToBookmark(bookmark); - - // Since the range has moved we need to re-get it - ieRange = selection.getRng(); - - // Find start point - findEndPoint(true); - - // Find end point if needed - if (!collapsed) { - findEndPoint(); - } - } else { - throw ex; // Throw other errors - } - } - - return domRange; - } - - this.getBookmark = function(type) { - var rng = selection.getRng(), bookmark = {}; - - function getIndexes(node) { - var parent, root, children, i, indexes = []; - - parent = node.parentNode; - root = dom.getRoot().parentNode; - - while (parent != root && parent.nodeType !== 9) { - children = parent.children; - - i = children.length; - while (i--) { - if (node === children[i]) { - indexes.push(i); - break; - } - } - - node = parent; - parent = parent.parentNode; - } - - return indexes; - } - - function getBookmarkEndPoint(start) { - var position; - - position = getPosition(rng, start); - if (position) { - return { - position: position.position, - offset: position.offset, - indexes: getIndexes(position.node), - inside: position.inside - }; - } - } - - // Non ubstructive bookmark - if (type === 2) { - // Handle text selection - if (!rng.item) { - bookmark.start = getBookmarkEndPoint(true); - - if (!selection.isCollapsed()) { - bookmark.end = getBookmarkEndPoint(); - } - } else { - bookmark.start = {ctrl: true, indexes: getIndexes(rng.item(0))}; - } - } - - return bookmark; - }; - - this.moveToBookmark = function(bookmark) { - var rng, body = dom.doc.body; - - function resolveIndexes(indexes) { - var node, i, idx, children; - - node = dom.getRoot(); - for (i = indexes.length - 1; i >= 0; i--) { - children = node.children; - idx = indexes[i]; - - if (idx <= children.length - 1) { - node = children[idx]; - } - } - - return node; - } - - function setBookmarkEndPoint(start) { - var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef, offset; - - if (endPoint) { - moveLeft = endPoint.position > 0; - - moveRng = body.createTextRange(); - moveRng.moveToElementText(resolveIndexes(endPoint.indexes)); - - offset = endPoint.offset; - if (offset !== undef) { - moveRng.collapse(endPoint.inside || moveLeft); - moveRng.moveStart('character', moveLeft ? -offset : offset); - } else { - moveRng.collapse(start); - } - - rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng); - - if (start) { - rng.collapse(true); - } - } - } - - if (bookmark.start) { - if (bookmark.start.ctrl) { - rng = body.createControlRange(); - rng.addElement(resolveIndexes(bookmark.start.indexes)); - rng.select(); - } else { - rng = body.createTextRange(); - setBookmarkEndPoint(true); - setBookmarkEndPoint(); - rng.select(); - } - } - }; - - this.addRange = function(rng) { - var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, - doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm; - - function setEndPoint(start) { - var container, offset, marker, tmpRng, nodes; - - marker = dom.create('a'); - container = start ? startContainer : endContainer; - offset = start ? startOffset : endOffset; - tmpRng = ieRng.duplicate(); - - if (container == doc || container == doc.documentElement) { - container = body; - offset = 0; - } - - if (container.nodeType == 3) { - container.parentNode.insertBefore(marker, container); - tmpRng.moveToElementText(marker); - tmpRng.moveStart('character', offset); - dom.remove(marker); - ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); - } else { - nodes = container.childNodes; - - if (nodes.length) { - if (offset >= nodes.length) { - dom.insertAfter(marker, nodes[nodes.length - 1]); - } else { - container.insertBefore(marker, nodes[offset]); - } - - tmpRng.moveToElementText(marker); - } else if (container.canHaveHTML) { - // Empty node selection for example|
would become this:|
- sibling = startContainer.previousSibling; - if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) { - sibling.innerHTML = ''; - } else { - sibling = null; - } - - startContainer.innerHTML = ''; - ieRng.moveToElementText(startContainer.lastChild); - ieRng.select(); - dom.doc.selection.clear(); - startContainer.innerHTML = ''; - - if (sibling) { - sibling.innerHTML = ''; - } - return; - } else { - startOffset = dom.nodeIndex(startContainer); - startContainer = startContainer.parentNode; - } - } - - if (startOffset == endOffset - 1) { - try { - ctrlElm = startContainer.childNodes[startOffset]; - ctrlRng = body.createControlRange(); - ctrlRng.addElement(ctrlElm); - ctrlRng.select(); - - // Check if the range produced is on the correct element and is a control range - // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398 - nativeRng = selection.getRng(); - if (nativeRng.item && ctrlElm === nativeRng.item(0)) { - return; - } - } catch (ex) { - // Ignore - } - } - } - - // Set start/end point of selection - setEndPoint(true); - setEndPoint(); - - // Select the new range and scroll it into view - ieRng.select(); - }; - - // Expose range method - this.getRangeAt = getRange; - } - - return Selection; -}); - -// Included from: js/tinymce/classes/util/VK.js - -/** - * VK.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * This file exposes a set of the common KeyCodes for use. Please grow it as needed. - */ -define("tinymce/util/VK", [ - "tinymce/Env" -], function(Env) { - return { - BACKSPACE: 8, - DELETE: 46, - DOWN: 40, - ENTER: 13, - LEFT: 37, - RIGHT: 39, - SPACEBAR: 32, - TAB: 9, - UP: 38, - - modifierPressed: function(e) { - return e.shiftKey || e.ctrlKey || e.altKey || this.metaKeyPressed(e); - }, - - metaKeyPressed: function(e) { - // Check if ctrl or meta key is pressed. Edge case for AltGr on Windows where it produces ctrlKey+altKey states - return (Env.mac ? e.metaKey : e.ctrlKey && !e.altKey); - } - }; -}); - -// Included from: js/tinymce/classes/dom/ControlSelection.js - -/** - * ControlSelection.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * This class handles control selection of elements. Controls are elements - * that can be resized and needs to be selected as a whole. It adds custom resize handles - * to all browser engines that support properly disabling the built in resize logic. - * - * @class tinymce.dom.ControlSelection - */ -define("tinymce/dom/ControlSelection", [ - "tinymce/util/VK", - "tinymce/util/Tools", - "tinymce/Env" -], function(VK, Tools, Env) { - return function(selection, editor) { - var dom = editor.dom, each = Tools.each; - var selectedElm, selectedElmGhost, resizeHelper, resizeHandles, selectedHandle, lastMouseDownEvent; - var startX, startY, selectedElmX, selectedElmY, startW, startH, ratio, resizeStarted; - var width, height, editableDoc = editor.getDoc(), rootDocument = document, isIE = Env.ie && Env.ie < 11; - var abs = Math.abs, round = Math.round, rootElement = editor.getBody(), startScrollWidth, startScrollHeight; - - // Details about each resize handle how to scale etc - resizeHandles = { - // Name: x multiplier, y multiplier, delta size x, delta size y - n: [0.5, 0, 0, -1], - e: [1, 0.5, 1, 0], - s: [0.5, 1, 0, 1], - w: [0, 0.5, -1, 0], - nw: [0, 0, -1, -1], - ne: [1, 0, 1, -1], - se: [1, 1, 1, 1], - sw: [0, 1, -1, 1] - }; - - // Add CSS for resize handles, cloned element and selected - var rootClass = '.mce-content-body'; - editor.contentStyles.push( - rootClass + ' div.mce-resizehandle {' + - 'position: absolute;' + - 'border: 1px solid black;' + - 'background: #FFF;' + - 'width: 5px;' + - 'height: 5px;' + - 'z-index: 10000' + - '}' + - rootClass + ' .mce-resizehandle:hover {' + - 'background: #000' + - '}' + - rootClass + ' img[data-mce-selected], hr[data-mce-selected] {' + - 'outline: 1px solid black;' + - 'resize: none' + // Have been talks about implementing this in browsers - '}' + - rootClass + ' .mce-clonedresizable {' + - 'position: absolute;' + - (Env.gecko ? '' : 'outline: 1px dashed black;') + // Gecko produces trails while resizing - 'opacity: .5;' + - 'filter: alpha(opacity=50);' + - 'z-index: 10000' + - '}' + - rootClass + ' .mce-resize-helper {' + - 'background: #555;' + - 'background: rgba(0,0,0,0.75);' + - 'border-radius: 3px;' + - 'border: 1px;' + - 'color: white;' + - 'display: none;' + - 'font-family: sans-serif;' + - 'font-size: 12px;' + - 'white-space: nowrap;' + - 'line-height: 14px;' + - 'margin: 5px 10px;' + - 'padding: 5px;' + - 'position: absolute;' + - 'z-index: 10001' + - '}' - ); - - function isResizable(elm) { - var selector = editor.settings.object_resizing; - - if (selector === false || Env.iOS) { - return false; - } - - if (typeof selector != 'string') { - selector = 'table,img,div'; - } - - if (elm.getAttribute('data-mce-resize') === 'false') { - return false; - } - - return editor.dom.is(elm, selector); - } - - function resizeGhostElement(e) { - var deltaX, deltaY, proportional; - var resizeHelperX, resizeHelperY; - - // Calc new width/height - deltaX = e.screenX - startX; - deltaY = e.screenY - startY; - - // Calc new size - width = deltaX * selectedHandle[2] + startW; - height = deltaY * selectedHandle[3] + startH; - - // Never scale down lower than 5 pixels - width = width < 5 ? 5 : width; - height = height < 5 ? 5 : height; - - if (selectedElm.nodeName == "IMG" && editor.settings.resize_img_proportional !== false) { - proportional = !VK.modifierPressed(e); - } else { - proportional = VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0); - } - - // Constrain proportions - if (proportional) { - if (abs(deltaX) > abs(deltaY)) { - height = round(width * ratio); - width = round(height / ratio); - } else { - width = round(height / ratio); - height = round(width * ratio); - } - } - - // Update ghost size - dom.setStyles(selectedElmGhost, { - width: width, - height: height - }); - - // Update resize helper position - resizeHelperX = selectedHandle.startPos.x + deltaX; - resizeHelperY = selectedHandle.startPos.y + deltaY; - resizeHelperX = resizeHelperX > 0 ? resizeHelperX : 0; - resizeHelperY = resizeHelperY > 0 ? resizeHelperY : 0; - - dom.setStyles(resizeHelper, { - left: resizeHelperX, - top: resizeHelperY, - display: 'block' - }); - - resizeHelper.innerHTML = width + ' × ' + height; - - // Update ghost X position if needed - if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) { - dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width)); - } - - // Update ghost Y position if needed - if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) { - dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height)); - } - - // Calculate how must overflow we got - deltaX = rootElement.scrollWidth - startScrollWidth; - deltaY = rootElement.scrollHeight - startScrollHeight; - - // Re-position the resize helper based on the overflow - if (deltaX + deltaY !== 0) { - dom.setStyles(resizeHelper, { - left: resizeHelperX - deltaX, - top: resizeHelperY - deltaY - }); - } - - if (!resizeStarted) { - editor.fire('ObjectResizeStart', {target: selectedElm, width: startW, height: startH}); - resizeStarted = true; - } - } - - function endGhostResize() { - resizeStarted = false; - - function setSizeProp(name, value) { - if (value) { - // Resize by using style or attribute - if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) { - dom.setStyle(selectedElm, name, value); - } else { - dom.setAttrib(selectedElm, name, value); - } - } - } - - // Set width/height properties - setSizeProp('width', width); - setSizeProp('height', height); - - dom.unbind(editableDoc, 'mousemove', resizeGhostElement); - dom.unbind(editableDoc, 'mouseup', endGhostResize); - - if (rootDocument != editableDoc) { - dom.unbind(rootDocument, 'mousemove', resizeGhostElement); - dom.unbind(rootDocument, 'mouseup', endGhostResize); - } - - // Remove ghost/helper and update resize handle positions - dom.remove(selectedElmGhost); - dom.remove(resizeHelper); - - if (!isIE || selectedElm.nodeName == "TABLE") { - showResizeRect(selectedElm); - } - - editor.fire('ObjectResized', {target: selectedElm, width: width, height: height}); - dom.setAttrib(selectedElm, 'style', dom.getAttrib(selectedElm, 'style')); - editor.nodeChanged(); - } - - function showResizeRect(targetElm, mouseDownHandleName, mouseDownEvent) { - var position, targetWidth, targetHeight, e, rect; - - unbindResizeHandleEvents(); - - // Get position and size of target - position = dom.getPos(targetElm, rootElement); - selectedElmX = position.x; - selectedElmY = position.y; - rect = targetElm.getBoundingClientRect(); // Fix for Gecko offsetHeight for table with caption - targetWidth = rect.width || (rect.right - rect.left); - targetHeight = rect.height || (rect.bottom - rect.top); - - // Reset width/height if user selects a new image/table - if (selectedElm != targetElm) { - detachResizeStartListener(); - selectedElm = targetElm; - width = height = 0; - } - - // Makes it possible to disable resizing - e = editor.fire('ObjectSelected', {target: targetElm}); - - if (isResizable(targetElm) && !e.isDefaultPrevented()) { - each(resizeHandles, function(handle, name) { - var handleElm, handlerContainerElm; - - function startDrag(e) { - startX = e.screenX; - startY = e.screenY; - startW = selectedElm.clientWidth; - startH = selectedElm.clientHeight; - ratio = startH / startW; - selectedHandle = handle; - - handle.startPos = { - x: targetWidth * handle[0] + selectedElmX, - y: targetHeight * handle[1] + selectedElmY - }; - - startScrollWidth = rootElement.scrollWidth; - startScrollHeight = rootElement.scrollHeight; - - selectedElmGhost = selectedElm.cloneNode(true); - dom.addClass(selectedElmGhost, 'mce-clonedresizable'); - dom.setAttrib(selectedElmGhost, 'data-mce-bogus', 'all'); - selectedElmGhost.contentEditable = false; // Hides IE move layer cursor - selectedElmGhost.unSelectabe = true; - dom.setStyles(selectedElmGhost, { - left: selectedElmX, - top: selectedElmY, - margin: 0 - }); - - selectedElmGhost.removeAttribute('data-mce-selected'); - rootElement.appendChild(selectedElmGhost); - - dom.bind(editableDoc, 'mousemove', resizeGhostElement); - dom.bind(editableDoc, 'mouseup', endGhostResize); - - if (rootDocument != editableDoc) { - dom.bind(rootDocument, 'mousemove', resizeGhostElement); - dom.bind(rootDocument, 'mouseup', endGhostResize); - } - - resizeHelper = dom.add(rootElement, 'div', { - 'class': 'mce-resize-helper', - 'data-mce-bogus': 'all' - }, startW + ' × ' + startH); - } - - if (mouseDownHandleName) { - // Drag started by IE native resizestart - if (name == mouseDownHandleName) { - startDrag(mouseDownEvent); - } - - return; - } - - // Get existing or render resize handle - handleElm = dom.get('mceResizeHandle' + name); - if (!handleElm) { - handlerContainerElm = rootElement; - - handleElm = dom.add(handlerContainerElm, 'div', { - id: 'mceResizeHandle' + name, - 'data-mce-bogus': 'all', - 'class': 'mce-resizehandle', - unselectable: true, - style: 'cursor:' + name + '-resize; margin:0; padding:0' - }); - - // Hides IE move layer cursor - // If we set it on Chrome we get this wounderful bug: #6725 - if (Env.ie) { - handleElm.contentEditable = false; - } - } else { - dom.show(handleElm); - } - - if (!handle.elm) { - dom.bind(handleElm, 'mousedown', function(e) { - e.stopImmediatePropagation(); - e.preventDefault(); - startDrag(e); - }); - - handle.elm = handleElm; - } - - // Position element - dom.setStyles(handleElm, { - left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2), - top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2) - }); - }); - } else { - hideResizeRect(); - } - - selectedElm.setAttribute('data-mce-selected', '1'); - } - - function hideResizeRect() { - var name, handleElm; - - unbindResizeHandleEvents(); - - if (selectedElm) { - selectedElm.removeAttribute('data-mce-selected'); - } - - for (name in resizeHandles) { - handleElm = dom.get('mceResizeHandle' + name); - if (handleElm) { - dom.unbind(handleElm); - dom.remove(handleElm); - } - } - } - - function updateResizeRect(e) { - var startElm, controlElm; - - function isChildOrEqual(node, parent) { - if (node) { - do { - if (node === parent) { - return true; - } - } while ((node = node.parentNode)); - } - } - - // Ignore all events while resizing - if (resizeStarted) { - return; - } - - // Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v - each(dom.select('img[data-mce-selected],hr[data-mce-selected]'), function(img) { - img.removeAttribute('data-mce-selected'); - }); - - controlElm = e.type == 'mousedown' ? e.target : selection.getNode(); - controlElm = dom.$(controlElm).closest(isIE ? 'table' : 'table,img,hr')[0]; - - if (isChildOrEqual(controlElm, rootElement)) { - disableGeckoResize(); - startElm = selection.getStart(true); - - if (isChildOrEqual(startElm, controlElm) && isChildOrEqual(selection.getEnd(true), controlElm)) { - if (!isIE || (controlElm != startElm && startElm.nodeName !== 'IMG')) { - showResizeRect(controlElm); - return; - } - } - } - - hideResizeRect(); - } - - function attachEvent(elm, name, func) { - if (elm && elm.attachEvent) { - elm.attachEvent('on' + name, func); - } - } - - function detachEvent(elm, name, func) { - if (elm && elm.detachEvent) { - elm.detachEvent('on' + name, func); - } - } - - function resizeNativeStart(e) { - var target = e.srcElement, pos, name, corner, cornerX, cornerY, relativeX, relativeY; - - pos = target.getBoundingClientRect(); - relativeX = lastMouseDownEvent.clientX - pos.left; - relativeY = lastMouseDownEvent.clientY - pos.top; - - // Figure out what corner we are draging on - for (name in resizeHandles) { - corner = resizeHandles[name]; - - cornerX = target.offsetWidth * corner[0]; - cornerY = target.offsetHeight * corner[1]; - - if (abs(cornerX - relativeX) < 8 && abs(cornerY - relativeY) < 8) { - selectedHandle = corner; - break; - } - } - - // Remove native selection and let the magic begin - resizeStarted = true; - editor.fire('ObjectResizeStart', { - target: selectedElm, - width: selectedElm.clientWidth, - height: selectedElm.clientHeight - }); - editor.getDoc().selection.empty(); - showResizeRect(target, name, lastMouseDownEvent); - } - - function nativeControlSelect(e) { - var target = e.srcElement; - - if (target != selectedElm) { - editor.fire('ObjectSelected', {target: target}); - detachResizeStartListener(); - - if (target.id.indexOf('mceResizeHandle') === 0) { - e.returnValue = false; - return; - } - - if (target.nodeName == 'IMG' || target.nodeName == 'TABLE') { - hideResizeRect(); - selectedElm = target; - attachEvent(target, 'resizestart', resizeNativeStart); - } - } - } - - function detachResizeStartListener() { - detachEvent(selectedElm, 'resizestart', resizeNativeStart); - } - - function unbindResizeHandleEvents() { - for (var name in resizeHandles) { - var handle = resizeHandles[name]; - - if (handle.elm) { - dom.unbind(handle.elm); - delete handle.elm; - } - } - } - - function disableGeckoResize() { - try { - // Disable object resizing on Gecko - editor.getDoc().execCommand('enableObjectResizing', false, false); - } catch (ex) { - // Ignore - } - } - - function controlSelect(elm) { - var ctrlRng; - - if (!isIE) { - return; - } - - ctrlRng = editableDoc.body.createControlRange(); - - try { - ctrlRng.addElement(elm); - ctrlRng.select(); - return true; - } catch (ex) { - // Ignore since the element can't be control selected for example a P tag - } - } - - editor.on('init', function() { - if (isIE) { - // Hide the resize rect on resize and reselect the image - editor.on('ObjectResized', function(e) { - if (e.target.nodeName != 'TABLE') { - hideResizeRect(); - controlSelect(e.target); - } - }); - - attachEvent(rootElement, 'controlselect', nativeControlSelect); - - editor.on('mousedown', function(e) { - lastMouseDownEvent = e; - }); - } else { - disableGeckoResize(); - - if (Env.ie >= 11) { - // TODO: Drag/drop doesn't work - editor.on('mouseup', function(e) { - var nodeName = e.target.nodeName; - - if (!resizeStarted && /^(TABLE|IMG|HR)$/.test(nodeName)) { - editor.selection.select(e.target, nodeName == 'TABLE'); - editor.nodeChanged(); - } - }); - - editor.dom.bind(rootElement, 'mscontrolselect', function(e) { - if (/^(TABLE|IMG|HR)$/.test(e.target.nodeName)) { - e.preventDefault(); - - // This moves the selection from being a control selection to a text like selection like in WebKit #6753 - // TODO: Fix this the day IE works like other browsers without this nasty native ugly control selections. - if (e.target.tagName == 'IMG') { - window.setTimeout(function() { - editor.selection.select(e.target); - }, 0); - } - } - }); - } - } - - editor.on('nodechange ResizeEditor', updateResizeRect); - - // Update resize rect while typing in a table - editor.on('keydown keyup', function(e) { - if (selectedElm && selectedElm.nodeName == "TABLE") { - updateResizeRect(e); - } - }); - - editor.on('hide', hideResizeRect); - - // Hide rect on focusout since it would float on top of windows otherwise - //editor.on('focusout', hideResizeRect); - }); - - editor.on('remove', unbindResizeHandleEvents); - - function destroy() { - selectedElm = selectedElmGhost = null; - - if (isIE) { - detachResizeStartListener(); - detachEvent(rootElement, 'controlselect', nativeControlSelect); - } - } - - return { - isResizable: isResizable, - showResizeRect: showResizeRect, - hideResizeRect: hideResizeRect, - updateResizeRect: updateResizeRect, - controlSelect: controlSelect, - destroy: destroy - }; - }; -}); - -// Included from: js/tinymce/classes/dom/BookmarkManager.js - -/** - * BookmarkManager.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * This class handles selection bookmarks. - * - * @class tinymce.dom.BookmarkManager - */ -define("tinymce/dom/BookmarkManager", [ - "tinymce/Env", - "tinymce/util/Tools" -], function(Env, Tools) { - /** - * Constructs a new BookmarkManager instance for a specific selection instance. - * - * @constructor - * @method BookmarkManager - * @param {tinymce.dom.Selection} selection Selection instance to handle bookmarks for. - */ - function BookmarkManager(selection) { - var dom = selection.dom; - - /** - * Returns a bookmark location for the current selection. This bookmark object - * can then be used to restore the selection after some content modification to the document. - * - * @method getBookmark - * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex. - * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization. - * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection. - * @example - * // Stores a bookmark of the current selection - * var bm = tinymce.activeEditor.selection.getBookmark(); - * - * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content'); - * - * // Restore the selection bookmark - * tinymce.activeEditor.selection.moveToBookmark(bm); - */ - this.getBookmark = function(type, normalized) { - var rng, rng2, id, collapsed, name, element, chr = '', styles; - - function findIndex(name, element) { - var index = 0; - - Tools.each(dom.select(name), function(node, i) { - if (node == element) { - index = i; - } - }); - - return index; - } - - function normalizeTableCellSelection(rng) { - function moveEndPoint(start) { - var container, offset, childNodes, prefix = start ? 'start' : 'end'; - - container = rng[prefix + 'Container']; - offset = rng[prefix + 'Offset']; - - if (container.nodeType == 1 && container.nodeName == "TR") { - childNodes = container.childNodes; - container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)]; - if (container) { - offset = start ? 0 : container.childNodes.length; - rng['set' + (start ? 'Start' : 'End')](container, offset); - } - } - } - - moveEndPoint(true); - moveEndPoint(); - - return rng; - } - - function getLocation() { - var rng = selection.getRng(true), root = dom.getRoot(), bookmark = {}; - - function getPoint(rng, start) { - var container = rng[start ? 'startContainer' : 'endContainer'], - offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; - - if (container.nodeType == 3) { - if (normalized) { - for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) { - offset += node.nodeValue.length; - } - } - - point.push(offset); - } else { - childNodes = container.childNodes; - - if (offset >= childNodes.length && childNodes.length) { - after = 1; - offset = Math.max(0, childNodes.length - 1); - } - - point.push(dom.nodeIndex(childNodes[offset], normalized) + after); - } - - for (; container && container != root; container = container.parentNode) { - point.push(dom.nodeIndex(container, normalized)); - } - - return point; - } - - bookmark.start = getPoint(rng, true); - - if (!selection.isCollapsed()) { - bookmark.end = getPoint(rng); - } - - return bookmark; - } - - if (type == 2) { - element = selection.getNode(); - name = element ? element.nodeName : null; - - if (name == 'IMG') { - return {name: name, index: findIndex(name, element)}; - } - - if (selection.tridentSel) { - return selection.tridentSel.getBookmark(type); - } - - return getLocation(); - } - - // Handle simple range - if (type) { - return {rng: selection.getRng()}; - } - - rng = selection.getRng(); - id = dom.uniqueId(); - collapsed = selection.isCollapsed(); - styles = 'overflow:hidden;line-height:0px'; - - // Explorer method - if (rng.duplicate || rng.item) { - // Text selection - if (!rng.item) { - rng2 = rng.duplicate(); - - try { - // Insert start marker - rng.collapse(); - rng.pasteHTML('' + chr + ''); - - // Insert end marker - if (!collapsed) { - rng2.collapse(false); - - // Detect the empty space after block elements in IE and move the - // end back one character ] becomes]
- rng.moveToElementText(rng2.parentElement()); - if (rng.compareEndPoints('StartToEnd', rng2) === 0) { - rng2.move('character', -1); - } - - rng2.pasteHTML('' + chr + ''); - } - } catch (ex) { - // IE might throw unspecified error so lets ignore it - return null; - } - } else { - // Control selection - element = rng.item(0); - name = element.nodeName; - - return {name: name, index: findIndex(name, element)}; - } - } else { - element = selection.getNode(); - name = element.nodeName; - if (name == 'IMG') { - return {name: name, index: findIndex(name, element)}; - } - - // W3C method - rng2 = normalizeTableCellSelection(rng.cloneRange()); - - // Insert end marker - if (!collapsed) { - rng2.collapse(false); - rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr)); - } - - rng = normalizeTableCellSelection(rng); - rng.collapse(true); - rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr)); - } - - selection.moveToBookmark({id: id, keep: 1}); - - return {id: id}; - }; - - /** - * Restores the selection to the specified bookmark. - * - * @method moveToBookmark - * @param {Object} bookmark Bookmark to restore selection from. - * @return {Boolean} true/false if it was successful or not. - * @example - * // Stores a bookmark of the current selection - * var bm = tinymce.activeEditor.selection.getBookmark(); - * - * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content'); - * - * // Restore the selection bookmark - * tinymce.activeEditor.selection.moveToBookmark(bm); - */ - this.moveToBookmark = function(bookmark) { - var rng, root, startContainer, endContainer, startOffset, endOffset; - - function setEndPoint(start) { - var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; - - if (point) { - offset = point[0]; - - // Find container node - for (node = root, i = point.length - 1; i >= 1; i--) { - children = node.childNodes; - - if (point[i] > children.length - 1) { - return; - } - - node = children[point[i]]; - } - - // Move text offset to best suitable location - if (node.nodeType === 3) { - offset = Math.min(point[0], node.nodeValue.length); - } - - // Move element offset to best suitable location - if (node.nodeType === 1) { - offset = Math.min(point[0], node.childNodes.length); - } - - // Set offset within container node - if (start) { - rng.setStart(node, offset); - } else { - rng.setEnd(node, offset); - } - } - - return true; - } - - function restoreEndPoint(suffix) { - var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; - - if (marker) { - node = marker.parentNode; - - if (suffix == 'start') { - if (!keep) { - idx = dom.nodeIndex(marker); - } else { - node = marker.firstChild; - idx = 1; - } - - startContainer = endContainer = node; - startOffset = endOffset = idx; - } else { - if (!keep) { - idx = dom.nodeIndex(marker); - } else { - node = marker.firstChild; - idx = 1; - } - - endContainer = node; - endOffset = idx; - } - - if (!keep) { - prev = marker.previousSibling; - next = marker.nextSibling; - - // Remove all marker text nodes - Tools.each(Tools.grep(marker.childNodes), function(node) { - if (node.nodeType == 3) { - node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); - } - }); - - // Remove marker but keep children if for example contents where inserted into the marker - // Also remove duplicated instances of the marker for example by a - // split operation or by WebKit auto split on paste feature - while ((marker = dom.get(bookmark.id + '_' + suffix))) { - dom.remove(marker, 1); - } - - // If siblings are text nodes then merge them unless it's Opera since it some how removes the node - // and we are sniffing since adding a lot of detection code for a browser with 3% of the market - // isn't worth the effort. Sorry, Opera but it's just a fact - if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !Env.opera) { - idx = prev.nodeValue.length; - prev.appendData(next.nodeValue); - dom.remove(next); - - if (suffix == 'start') { - startContainer = endContainer = prev; - startOffset = endOffset = idx; - } else { - endContainer = prev; - endOffset = idx; - } - } - } - } - } - - function addBogus(node) { - // Adds a bogus BR element for empty block elements - if (dom.isBlock(node) && !node.innerHTML && !Env.ie) { - node.innerHTML = '*texttext*
! - // This will reduce the number of wrapper elements that needs to be created - // Move start point up the tree - if (format[0].inline || format[0].block_expand) { - if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) { - startContainer = findParentContainer(true); - } - - if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) { - endContainer = findParentContainer(); - } - } - - // Expand start/end container to matching selector - if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { - // Find new startContainer/endContainer if there is better one - startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); - endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); - } - - // Expand start/end container to matching block element or text node - if (format[0].block || format[0].selector) { - // Find new startContainer/endContainer if there is better one - startContainer = findBlockEndPoint(startContainer, 'previousSibling'); - endContainer = findBlockEndPoint(endContainer, 'nextSibling'); - - // Non block element then try to expand up the leaf - if (format[0].block) { - if (!isBlock(startContainer)) { - startContainer = findParentContainer(true); - } - - if (!isBlock(endContainer)) { - endContainer = findParentContainer(); - } - } - } - - // Setup index for startContainer - if (startContainer.nodeType == 1) { - startOffset = nodeIndex(startContainer); - startContainer = startContainer.parentNode; - } - - // Setup index for endContainer - if (endContainer.nodeType == 1) { - endOffset = nodeIndex(endContainer) + 1; - endContainer = endContainer.parentNode; - } - - // Return new range like object - return { - startContainer: startContainer, - startOffset: startOffset, - endContainer: endContainer, - endOffset: endOffset - }; - } - - function isColorFormatAndAnchor(node, format) { - return format.links && node.tagName == 'A'; - } - - /** - * Removes the specified format for the specified node. It will also remove the node if it doesn't have - * any attributes if the format specifies it to do so. - * - * @private - * @param {Object} format Format object with items to remove from node. - * @param {Object} vars Name/value object with variables to apply to format. - * @param {Node} node Node to remove the format styles on. - * @param {Node} compare_node Optional compare node, if specified the styles will be compared to that node. - * @return {Boolean} True/false if the node was removed or not. - */ - function removeFormat(format, vars, node, compare_node) { - var i, attrs, stylesModified; - - // Check if node matches format - if (!matchName(node, format) && !isColorFormatAndAnchor(node, format)) { - return FALSE; - } - - // Should we compare with format attribs and styles - if (format.remove != 'all') { - // Remove styles - each(format.styles, function(value, name) { - value = normalizeStyleValue(replaceVars(value, vars), name); - - // Indexed array - if (typeof name === 'number') { - name = value; - compare_node = 0; - } - - if (format.remove_similar || (!compare_node || isEq(getStyle(compare_node, name), value))) { - dom.setStyle(node, name, ''); - } - - stylesModified = 1; - }); - - // Remove style attribute if it's empty - if (stylesModified && dom.getAttrib(node, 'style') === '') { - node.removeAttribute('style'); - node.removeAttribute('data-mce-style'); - } - - // Remove attributes - each(format.attributes, function(value, name) { - var valueOut; - - value = replaceVars(value, vars); - - // Indexed array - if (typeof name === 'number') { - name = value; - compare_node = 0; - } - - if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) { - // Keep internal classes - if (name == 'class') { - value = dom.getAttrib(node, name); - if (value) { - // Build new class value where everything is removed except the internal prefixed classes - valueOut = ''; - each(value.split(/\s+/), function(cls) { - if (/mce\-\w+/.test(cls)) { - valueOut += (valueOut ? ' ' : '') + cls; - } - }); - - // We got some internal classes left - if (valueOut) { - dom.setAttrib(node, name, valueOut); - return; - } - } - } - - // IE6 has a bug where the attribute doesn't get removed correctly - if (name == "class") { - node.removeAttribute('className'); - } - - // Remove mce prefixed attributes - if (MCE_ATTR_RE.test(name)) { - node.removeAttribute('data-mce-' + name); - } - - node.removeAttribute(name); - } - }); - - // Remove classes - each(format.classes, function(value) { - value = replaceVars(value, vars); - - if (!compare_node || dom.hasClass(compare_node, value)) { - dom.removeClass(node, value); - } - }); - - // Check for non internal attributes - attrs = dom.getAttribs(node); - for (i = 0; i < attrs.length; i++) { - if (attrs[i].nodeName.indexOf('_') !== 0) { - return FALSE; - } - } - } - - // Remove the inline child if it's empty for example or - if (format.remove != 'none') { - removeNode(node, format); - return TRUE; - } - } - - /** - * Removes the node and wrap it's children in paragraphs before doing so or - * appends BR elements to the beginning/end of the block element if forcedRootBlocks is disabled. - * - * If the div in the node below gets removed: - * text|
- formatNode.parentNode.replaceChild(caretContainer, formatNode); - } else { - // Insert caret container after the formated node - dom.insertAfter(caretContainer, formatNode); - } - - // Move selection to text node - selection.setCursorLocation(node, 1); - - // If the formatNode is empty, we can remove it safely. - if (dom.isEmpty(formatNode)) { - dom.remove(formatNode); - } - } - } - - // Checks if the parent caret container node isn't empty if that is the case it - // will remove the bogus state on all children that isn't empty - function unmarkBogusCaretParents() { - var caretContainer; - - caretContainer = getParentCaretContainer(selection.getStart()); - if (caretContainer && !dom.isEmpty(caretContainer)) { - walk(caretContainer, function(node) { - if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) { - dom.setAttrib(node, 'data-mce-bogus', null); - } - }, 'childNodes'); - } - } - - // Only bind the caret events once - if (!ed._hasCaretEvents) { - // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements - markCaretContainersBogus = function() { - var nodes = [], i; - - if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) { - // Mark children - i = nodes.length; - while (i--) { - dom.setAttrib(nodes[i], 'data-mce-bogus', '1'); - } - } - }; - - disableCaretContainer = function(e) { - var keyCode = e.keyCode; - - removeCaretContainer(); - - // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys - // Backspace key needs to check if the range is collapsed due to bug #6780 - if ((keyCode == 8 && selection.isCollapsed()) || keyCode == 37 || keyCode == 39) { - removeCaretContainer(getParentCaretContainer(selection.getStart())); - } - - unmarkBogusCaretParents(); - }; - - // Remove bogus state if they got filled by contents using editor.selection.setContent - ed.on('SetContent', function(e) { - if (e.selection) { - unmarkBogusCaretParents(); - } - }); - ed._hasCaretEvents = true; - } - - // Do apply or remove caret format - if (type == "apply") { - applyCaretFormat(); - } else { - removeCaretFormat(); - } - } - - /** - * Moves the start to the first suitable text node. - */ - function moveStart(rng) { - var container = rng.startContainer, - offset = rng.startOffset, isAtEndOfText, - walker, node, nodes, tmpNode; - - // Convert text node into index if possible - if (container.nodeType == 3 && offset >= container.nodeValue.length) { - // Get the parent container location and walk from there - offset = nodeIndex(container); - container = container.parentNode; - isAtEndOfText = true; - } - - // Move startContainer/startOffset in to a suitable node - if (container.nodeType == 1) { - nodes = container.childNodes; - container = nodes[Math.min(offset, nodes.length - 1)]; - walker = new TreeWalker(container, dom.getParent(container, dom.isBlock)); - - // If offset is at end of the parent node walk to the next one - if (offset > nodes.length - 1 || isAtEndOfText) { - walker.next(); - } - - for (node = walker.current(); node; node = walker.next()) { - if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { - // IE has a "neat" feature where it moves the start node into the closest element - // we can avoid this by inserting an element before it and then remove it after we set the selection - tmpNode = dom.create('a', {'data-mce-bogus': 'all'}, INVISIBLE_CHAR); - node.parentNode.insertBefore(tmpNode, node); - - // Set selection and remove tmpNode - rng.setStart(node, 0); - selection.setRng(rng); - dom.remove(tmpNode); - - return; - } - } - } - } - }; -}); - -// Included from: js/tinymce/classes/UndoManager.js - -/** - * UndoManager.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * This class handles the undo/redo history levels for the editor. Since the build in undo/redo has major drawbacks a custom one was needed. - * - * @class tinymce.UndoManager - */ -define("tinymce/UndoManager", [ - "tinymce/util/VK", - "tinymce/Env", - "tinymce/util/Tools", - "tinymce/html/SaxParser" -], function(VK, Env, Tools, SaxParser) { - var trim = Tools.trim, trimContentRegExp; - - trimContentRegExp = new RegExp([ - ']+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\\/span>', // Trim bogus spans like caret containers - '\\s?data-mce-selected="[^"]+"' // Trim temporaty data-mce prefixed attributes like data-mce-selected - ].join('|'), 'gi'); - - return function(editor) { - var self = this, index = 0, data = [], beforeBookmark, isFirstTypedCharacter, locks = 0; - - /** - * Returns a trimmed version of the editor contents to be used for the undo level. This - * will remove any data-mce-bogus="all" marked elements since these are used for UI it will also - * remove the data-mce-selected attributes used for selection of objects and caret containers. - * It will keep all data-mce-bogus="1" elements since these can be used to place the caret etc and will - * be removed by the serialization logic when you save. - * - * @private - * @return {String} HTML contents of the editor excluding some internal bogus elements. - */ - function getContent() { - var content = editor.getContent({format: 'raw', no_events: 1}); - var bogusAllRegExp = /<(\w+) [^>]*data-mce-bogus="all"[^>]*>/g; - var endTagIndex, index, matchLength, matches, shortEndedElements, schema = editor.schema; - - content = content.replace(trimContentRegExp, ''); - shortEndedElements = schema.getShortEndedElements(); - - // Remove all bogus elements marked with "all" - while ((matches = bogusAllRegExp.exec(content))) { - index = bogusAllRegExp.lastIndex; - matchLength = matches[0].length; - - if (shortEndedElements[matches[1]]) { - endTagIndex = index; - } else { - endTagIndex = SaxParser.findEndTag(schema, content, index); - } - - content = content.substring(0, index - matchLength) + content.substring(endTagIndex); - bogusAllRegExp.lastIndex = index - matchLength; - } - - return trim(content); - } - - function setDirty(state) { - editor.isNotDirty = !state; - } - - function addNonTypingUndoLevel(e) { - self.typing = false; - self.add({}, e); - } - - // Add initial undo level when the editor is initialized - editor.on('init', function() { - self.add(); - }); - - // Get position before an execCommand is processed - editor.on('BeforeExecCommand', function(e) { - var cmd = e.command; - - if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') { - self.beforeChange(); - } - }); - - // Add undo level after an execCommand call was made - editor.on('ExecCommand', function(e) { - var cmd = e.command; - - if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') { - addNonTypingUndoLevel(e); - } - }); - - editor.on('ObjectResizeStart', function() { - self.beforeChange(); - }); - - editor.on('SaveContent ObjectResized blur', addNonTypingUndoLevel); - editor.on('DragEnd', addNonTypingUndoLevel); - - editor.on('KeyUp', function(e) { - var keyCode = e.keyCode; - - if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) { - addNonTypingUndoLevel(); - editor.nodeChanged(); - } - - if (keyCode == 46 || keyCode == 8 || (Env.mac && (keyCode == 91 || keyCode == 93))) { - editor.nodeChanged(); - } - - // Fire a TypingUndo event on the first character entered - if (isFirstTypedCharacter && self.typing) { - // Make it dirty if the content was changed after typing the first character - if (!editor.isDirty()) { - setDirty(data[0] && getContent() != data[0].content); - - // Fire initial change event - if (!editor.isNotDirty) { - editor.fire('change', {level: data[0], lastLevel: null}); - } - } - - editor.fire('TypingUndo'); - isFirstTypedCharacter = false; - editor.nodeChanged(); - } - }); - - editor.on('KeyDown', function(e) { - var keyCode = e.keyCode; - - // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter - if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) { - if (self.typing) { - addNonTypingUndoLevel(e); - } - - return; - } - - // If key isn't Ctrl+Alt/AltGr - var modKey = (e.ctrlKey && !e.altKey) || e.metaKey; - if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing && !modKey) { - self.beforeChange(); - self.typing = true; - self.add({}, e); - isFirstTypedCharacter = true; - } - }); - - editor.on('MouseDown', function(e) { - if (self.typing) { - addNonTypingUndoLevel(e); - } - }); - - // Add keyboard shortcuts for undo/redo keys - editor.addShortcut('meta+z', '', 'Undo'); - editor.addShortcut('meta+y,meta+shift+z', '', 'Redo'); - - editor.on('AddUndo Undo Redo ClearUndos', function(e) { - if (!e.isDefaultPrevented()) { - editor.nodeChanged(); - } - }); - - /*eslint consistent-this:0 */ - self = { - // Explose for debugging reasons - data: data, - - /** - * State if the user is currently typing or not. This will add a typing operation into one undo - * level instead of one new level for each keystroke. - * - * @field {Boolean} typing - */ - typing: false, - - /** - * Stores away a bookmark to be used when performing an undo action so that the selection is before - * the change has been made. - * - * @method beforeChange - */ - beforeChange: function() { - if (!locks) { - beforeBookmark = editor.selection.getBookmark(2, true); - } - }, - - /** - * Adds a new undo level/snapshot to the undo list. - * - * @method add - * @param {Object} level Optional undo level object to add. - * @param {DOMEvent} Event Optional event responsible for the creation of the undo level. - * @return {Object} Undo level that got added or null it a level wasn't needed. - */ - add: function(level, event) { - var i, settings = editor.settings, lastLevel; - - level = level || {}; - level.content = getContent(); - - if (locks || editor.removed) { - return null; - } - - lastLevel = data[index]; - if (editor.fire('BeforeAddUndo', {level: level, lastLevel: lastLevel, originalEvent: event}).isDefaultPrevented()) { - return null; - } - - // Add undo level if needed - if (lastLevel && lastLevel.content == level.content) { - return null; - } - - // Set before bookmark on previous level - if (data[index]) { - data[index].beforeBookmark = beforeBookmark; - } - - // Time to compress - if (settings.custom_undo_redo_levels) { - if (data.length > settings.custom_undo_redo_levels) { - for (i = 0; i < data.length - 1; i++) { - data[i] = data[i + 1]; - } - - data.length--; - index = data.length; - } - } - - // Get a non intrusive normalized bookmark - level.bookmark = editor.selection.getBookmark(2, true); - - // Crop array if needed - if (index < data.length - 1) { - data.length = index + 1; - } - - data.push(level); - index = data.length - 1; - - var args = {level: level, lastLevel: lastLevel, originalEvent: event}; - - editor.fire('AddUndo', args); - - if (index > 0) { - setDirty(true); - editor.fire('change', args); - } - - return level; - }, - - /** - * Undoes the last action. - * - * @method undo - * @return {Object} Undo level or null if no undo was performed. - */ - undo: function() { - var level; - - if (self.typing) { - self.add(); - self.typing = false; - } - - if (index > 0) { - level = data[--index]; - - // Undo to first index then set dirty state to false - if (index === 0) { - setDirty(false); - } - - editor.setContent(level.content, {format: 'raw'}); - editor.selection.moveToBookmark(level.beforeBookmark); - - editor.fire('undo', {level: level}); - } - - return level; - }, - - /** - * Redoes the last action. - * - * @method redo - * @return {Object} Redo level or null if no redo was performed. - */ - redo: function() { - var level; - - if (index < data.length - 1) { - level = data[++index]; - - editor.setContent(level.content, {format: 'raw'}); - editor.selection.moveToBookmark(level.bookmark); - setDirty(true); - - editor.fire('redo', {level: level}); - } - - return level; - }, - - /** - * Removes all undo levels. - * - * @method clear - */ - clear: function() { - data = []; - index = 0; - self.typing = false; - editor.fire('ClearUndos'); - }, - - /** - * Returns true/false if the undo manager has any undo levels. - * - * @method hasUndo - * @return {Boolean} true/false if the undo manager has any undo levels. - */ - hasUndo: function() { - // Has undo levels or typing and content isn't the same as the initial level - return index > 0 || (self.typing && data[0] && getContent() != data[0].content); - }, - - /** - * Returns true/false if the undo manager has any redo levels. - * - * @method hasRedo - * @return {Boolean} true/false if the undo manager has any redo levels. - */ - hasRedo: function() { - return index < data.length - 1 && !this.typing; - }, - - /** - * Executes the specified function in an undo transation. The selection - * before the modification will be stored to the undo stack and if the DOM changes - * it will add a new undo level. Any methods within the transation that adds undo levels will - * be ignored. So a transation can include calls to execCommand or editor.insertContent. - * - * @method transact - * @param {function} callback Function to execute dom manipulation logic in. - */ - transact: function(callback) { - self.beforeChange(); - - try { - locks++; - callback(); - } finally { - locks--; - } - - self.add(); - } - }; - - return self; - }; -}); - -// Included from: js/tinymce/classes/EnterKey.js - -/** - * EnterKey.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * Contains logic for handling the enter key to split/generate block elements. - */ -define("tinymce/EnterKey", [ - "tinymce/dom/TreeWalker", - "tinymce/dom/RangeUtils", - "tinymce/Env" -], function(TreeWalker, RangeUtils, Env) { - var isIE = Env.ie && Env.ie < 11; - - return function(editor) { - var dom = editor.dom, selection = editor.selection, settings = editor.settings; - var undoManager = editor.undoManager, schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements(), - moveCaretBeforeOnEnterElementsMap = schema.getMoveCaretBeforeOnEnterElements(); - - function handleEnterKey(evt) { - var rng, tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey, - newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer; - - // Returns true if the block can be split into two blocks or not - function canSplitBlock(node) { - return node && - dom.isBlock(node) && - !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) && - !/^(fixed|absolute)/i.test(node.style.position) && - dom.getContentEditable(node) !== "true"; - } - - // Renders empty block on IE - function renderBlockOnIE(block) { - var oldRng; - - if (dom.isBlock(block)) { - oldRng = selection.getRng(); - block.appendChild(dom.create('span', null, '\u00a0')); - selection.select(block); - block.lastChild.outerHTML = ''; - selection.setRng(oldRng); - } - } - - // Remove the first empty inline element of the block so this:x
becomes this:x
- function trimInlineElementsOnLeftSideOfBlock(block) { - var node = block, firstChilds = [], i; - - if (!node) { - return; - } - - // Find inner most first child ex:*
- while ((node = node.firstChild)) { - if (dom.isBlock(node)) { - return; - } - - if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) { - firstChilds.push(node); - } - } - - i = firstChilds.length; - while (i--) { - node = firstChilds[i]; - if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) { - dom.remove(node); - } else { - // Remove see #5381 - if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') { - dom.remove(node); - } - } - } - } - - // Moves the caret to a suitable position within the root for example in the first non - // pure whitespace text node or before an image - function moveToCaretPosition(root) { - var walker, node, rng, lastNode = root, tempElm; - function firstNonWhiteSpaceNodeSibling(node) { - while (node) { - if (node.nodeType == 1 || (node.nodeType == 3 && node.data && /[\r\n\s]/.test(node.data))) { - return node; - } - - node = node.nextSibling; - } - } - - if (!root) { - return; - } - - // Old IE versions doesn't properly render blocks with br elements in them - // For exampletext|
text|text2
|
- rng = selection.getRng(); - var caretElement = rng.startContainer || (rng.parentElement ? rng.parentElement() : null); - var body = editor.getBody(); - if (caretElement === body && selection.isCollapsed()) { - if (dom.isBlock(body.firstChild) && dom.isEmpty(body.firstChild)) { - rng = dom.createRng(); - rng.setStart(body.firstChild, 0); - rng.setEnd(body.firstChild, 0); - selection.setRng(rng); - } - } - - // Insert node maker where we will insert the new HTML and get it's parent - if (!selection.isCollapsed()) { - editor.getDoc().execCommand('Delete', false, null); - trimNbspAfterDeleteAndPaddValue(); - } - - parentNode = selection.getNode(); - - // Parse the fragment within the context of the parent node - var parserArgs = {context: parentNode.nodeName.toLowerCase()}; - fragment = parser.parse(value, parserArgs); - - markInlineFormatElements(fragment); - - // Move the caret to a more suitable location - node = fragment.lastChild; - if (node.attr('id') == 'mce_marker') { - marker = node; - - for (node = node.prev; node; node = node.walk(true)) { - if (node.type == 3 || !dom.isBlock(node.name)) { - if (editor.schema.isValidChild(node.parent.name, 'span')) { - node.parent.insert(marker, node, node.name === 'br'); - } - break; - } - } - } - - // If parser says valid we can insert the contents into that parent - if (!parserArgs.invalid) { - value = serializer.serialize(fragment); - - // Check if parent is empty or only has one BR element then set the innerHTML of that parent - node = parentNode.firstChild; - node2 = parentNode.lastChild; - if (!node || (node === node2 && node.nodeName === 'BR')) { - dom.setHTML(parentNode, value); - } else { - selection.setContent(value); - } - } else { - // If the fragment was invalid within that context then we need - // to parse and process the parent it's inserted into - - // Insert bookmark node and get the parent - selection.setContent(bookmarkHtml); - parentNode = selection.getNode(); - rootNode = editor.getBody(); - - // Opera will return the document node when selection is in root - if (parentNode.nodeType == 9) { - parentNode = node = rootNode; - } else { - node = parentNode; - } - - // Find the ancestor just before the root element - while (node !== rootNode) { - parentNode = node; - node = node.parentNode; - } - - // Get the outer/inner HTML depending on if we are in the root and parser and serialize that - value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode); - value = serializer.serialize( - parser.parse( - // Need to replace by using a function since $ in the contents would otherwise be a problem - value.replace(//i, function() { - return serializer.serialize(fragment); - }) - ) - ); - - // Set the inner/outer HTML depending on if we are in the root or not - if (parentNode == rootNode) { - dom.setHTML(rootNode, value); - } else { - dom.setOuterHTML(parentNode, value); - } - } - - reduceInlineTextElements(); - - marker = dom.get('mce_marker'); - selection.scrollIntoView(marker); - - // Move selection before marker and remove it - rng = dom.createRng(); - - // If previous sibling is a text node set the selection to the end of that node - node = marker.previousSibling; - if (node && node.nodeType == 3) { - rng.setStart(node, node.nodeValue.length); - - // TODO: Why can't we normalize on IE - if (!isIE) { - node2 = marker.nextSibling; - if (node2 && node2.nodeType == 3) { - node.appendData(node2.data); - node2.parentNode.removeChild(node2); - } - } - } else { - // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node - rng.setStartBefore(marker); - rng.setEndBefore(marker); - } - - // Remove the marker node and set the new range - dom.remove(marker); - selection.setRng(rng); - - // Dispatch after event and add any visual elements needed - editor.fire('SetContent', args); - editor.addVisual(); - }, - - mceInsertRawHTML: function(command, ui, value) { - selection.setContent('tiny_mce_marker'); - editor.setContent( - editor.getContent().replace(/tiny_mce_marker/g, function() { - return value; - }) - ); - }, - - mceToggleFormat: function(command, ui, value) { - toggleFormat(value); - }, - - mceSetContent: function(command, ui, value) { - editor.setContent(value); - }, - - 'Indent,Outdent': function(command) { - var intentValue, indentUnit, value; - - // Setup indent level - intentValue = settings.indentation; - indentUnit = /[a-z%]+$/i.exec(intentValue); - intentValue = parseInt(intentValue, 10); - - if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { - // If forced_root_blocks is set to false we don't have a block to indent so lets create a div - if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) { - formatter.apply('div'); - } - - each(selection.getSelectedBlocks(), function(element) { - if (element.nodeName != "LI") { - var indentStyleName = editor.getParam('indent_use_margin', false) ? 'margin' : 'padding'; - - indentStyleName += dom.getStyle(element, 'direction', true) == 'rtl' ? 'Right' : 'Left'; - - if (command == 'outdent') { - value = Math.max(0, parseInt(element.style[indentStyleName] || 0, 10) - intentValue); - dom.setStyle(element, indentStyleName, value ? value + indentUnit : ''); - } else { - value = (parseInt(element.style[indentStyleName] || 0, 10) + intentValue) + indentUnit; - dom.setStyle(element, indentStyleName, value); - } - } - }); - } else { - execNativeCommand(command); - } - }, - - mceRepaint: function() { - if (isGecko) { - try { - storeSelection(TRUE); - - if (selection.getSel()) { - selection.getSel().selectAllChildren(editor.getBody()); - } - - selection.collapse(TRUE); - restoreSelection(); - } catch (ex) { - // Ignore - } - } - }, - - InsertHorizontalRule: function() { - editor.execCommand('mceInsertContent', false, '|
- rng = selection.getRng(); - if (!rng.item) { - rng.moveToElementText(root); - rng.select(); - } - } - }, - - "delete": function() { - execNativeCommand("Delete"); - - // Check if body is empty after the delete call if so then set the contents - // to an empty string and move the caret to any block produced by that operation - // this fixes the issue with root blocks not being properly produced after a delete call on IE - var body = editor.getBody(); - - if (dom.isEmpty(body)) { - editor.setContent(''); - - if (body.firstChild && dom.isBlock(body.firstChild)) { - editor.selection.setCursorLocation(body.firstChild, 0); - } else { - editor.selection.setCursorLocation(body, 0); - } - } - }, - - mceNewDocument: function() { - editor.setContent(''); - }, - - InsertLineBreak: function(command, ui, value) { - // We load the current event in from EnterKey.js when appropriate to heed - // certain event-specific variations such as ctrl-enter in a list - var evt = value; - var brElm, extraBr, marker; - var rng = selection.getRng(true); - new RangeUtils(dom).normalize(rng); - - var offset = rng.startOffset; - var container = rng.startContainer; - - // Resolve node index - if (container.nodeType == 1 && container.hasChildNodes()) { - var isAfterLastNodeInContainer = offset > container.childNodes.length - 1; - - container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container; - if (isAfterLastNodeInContainer && container.nodeType == 3) { - offset = container.nodeValue.length; - } else { - offset = 0; - } - } - - var parentBlock = dom.getParent(container, dom.isBlock); - var parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 - var containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null; - var containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 - - // Enter inside block contained within a LI then split or insert before/after LI - var isControlKey = evt && evt.ctrlKey; - if (containerBlockName == 'LI' && !isControlKey) { - parentBlock = containerBlock; - parentBlockName = containerBlockName; - } - - // Walks the parent block to the right and look for BR elements - function hasRightSideContent() { - var walker = new TreeWalker(container, parentBlock), node; - var nonEmptyElementsMap = editor.schema.getNonEmptyElements(); - - while ((node = walker.next())) { - if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) { - return true; - } - } - } - - if (container && container.nodeType == 3 && offset >= container.nodeValue.length) { - // Insert extra BR element at the end block elements - if (!isOldIE && !hasRightSideContent()) { - brElm = dom.create('br'); - rng.insertNode(brElm); - rng.setStartAfter(brElm); - rng.setEndAfter(brElm); - extraBr = true; - } - } - - brElm = dom.create('br'); - rng.insertNode(brElm); - - // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it - var documentMode = dom.doc.documentMode; - if (isOldIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) { - brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm); - } - - // Insert temp marker and scroll to that - marker = dom.create('span', {}, ' '); - brElm.parentNode.insertBefore(marker, brElm); - selection.scrollIntoView(marker); - dom.remove(marker); - - if (!extraBr) { - rng.setStartAfter(brElm); - rng.setEndAfter(brElm); - } else { - rng.setStartBefore(brElm); - rng.setEndBefore(brElm); - } - - selection.setRng(rng); - editor.undoManager.add(); - - return TRUE; - } - }); - - // Add queryCommandState overrides - addCommands({ - // Override justify commands - 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) { - var name = 'align' + command.substring(7); - var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks(); - var matches = map(nodes, function(node) { - return !!formatter.matchNode(node, name); - }); - return inArray(matches, TRUE) !== -1; - }, - - 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) { - return isFormatMatch(command); - }, - - mceBlockQuote: function() { - return isFormatMatch('blockquote'); - }, - - Outdent: function() { - var node; - - if (settings.inline_styles) { - if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) { - return TRUE; - } - - if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) { - return TRUE; - } - } - - return ( - queryCommandState('InsertUnorderedList') || - queryCommandState('InsertOrderedList') || - (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')) - ); - }, - - 'InsertUnorderedList,InsertOrderedList': function(command) { - var list = dom.getParent(selection.getNode(), 'ul,ol'); - - return list && - ( - command === 'insertunorderedlist' && list.tagName === 'UL' || - command === 'insertorderedlist' && list.tagName === 'OL' - ); - } - }, 'state'); - - // Add queryCommandValue overrides - addCommands({ - 'FontSize,FontName': function(command) { - var value = 0, parent; - - if ((parent = dom.getParent(selection.getNode(), 'span'))) { - if (command == 'fontsize') { - value = parent.style.fontSize; - } else { - value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); - } - } - - return value; - } - }, 'value'); - - // Add undo manager logic - addCommands({ - Undo: function() { - editor.undoManager.undo(); - }, - - Redo: function() { - editor.undoManager.redo(); - } - }); - }; -}); - -// Included from: js/tinymce/classes/util/URI.js - -/** - * URI.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * This class handles parsing, modification and serialization of URI/URL strings. - * @class tinymce.util.URI - */ -define("tinymce/util/URI", [ - "tinymce/util/Tools" -], function(Tools) { - var each = Tools.each, trim = Tools.trim; - var queryParts = "source protocol authority userInfo user password host port relative path directory file query anchor".split(' '); - var DEFAULT_PORTS = { - 'ftp': 21, - 'http': 80, - 'https': 443, - 'mailto': 25 - }; - - /** - * Constructs a new URI instance. - * - * @constructor - * @method URI - * @param {String} url URI string to parse. - * @param {Object} settings Optional settings object. - */ - function URI(url, settings) { - var self = this, baseUri, base_url; - - url = trim(url); - settings = self.settings = settings || {}; - baseUri = settings.base_uri; - - // Strange app protocol that isn't http/https or local anchor - // For example: mailto,skype,tel etc. - if (/^([\w\-]+):([^\/]{2})/i.test(url) || /^\s*#/.test(url)) { - self.source = url; - return; - } - - var isProtocolRelative = url.indexOf('//') === 0; - - // Absolute path with no host, fake host and protocol - if (url.indexOf('/') === 0 && !isProtocolRelative) { - url = (baseUri ? baseUri.protocol || 'http' : 'http') + '://mce_host' + url; - } - - // Relative path http:// or protocol relative //path - if (!/^[\w\-]*:?\/\//.test(url)) { - base_url = settings.base_uri ? settings.base_uri.path : new URI(location.href).directory; - if (settings.base_uri.protocol === "") { - url = '//mce_host' + self.toAbsPath(base_url, url); - } else { - url = /([^#?]*)([#?]?.*)/.exec(url); - url = ((baseUri && baseUri.protocol) || 'http') + '://mce_host' + self.toAbsPath(base_url, url[1]) + url[2]; - } - } - - // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri) - url = url.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something - - /*jshint maxlen: 255 */ - /*eslint max-len: 0 */ - url = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(url); - - each(queryParts, function(v, i) { - var part = url[i]; - - // Zope 3 workaround, they use @@something - if (part) { - part = part.replace(/\(mce_at\)/g, '@@'); - } - - self[v] = part; - }); - - if (baseUri) { - if (!self.protocol) { - self.protocol = baseUri.protocol; - } - - if (!self.userInfo) { - self.userInfo = baseUri.userInfo; - } - - if (!self.port && self.host === 'mce_host') { - self.port = baseUri.port; - } - - if (!self.host || self.host === 'mce_host') { - self.host = baseUri.host; - } - - self.source = ''; - } - - if (isProtocolRelative) { - self.protocol = ''; - } - - //t.path = t.path || '/'; - } - - URI.prototype = { - /** - * Sets the internal path part of the URI. - * - * @method setPath - * @param {string} path Path string to set. - */ - setPath: function(path) { - var self = this; - - path = /^(.*?)\/?(\w+)?$/.exec(path); - - // Update path parts - self.path = path[0]; - self.directory = path[1]; - self.file = path[2]; - - // Rebuild source - self.source = ''; - self.getURI(); - }, - - /** - * Converts the specified URI into a relative URI based on the current URI instance location. - * - * @method toRelative - * @param {String} uri URI to convert into a relative path/URI. - * @return {String} Relative URI from the point specified in the current URI instance. - * @example - * // Converts an absolute URL to an relative URL url will be somedir/somefile.htm - * var url = new tinymce.util.URI('http://www.site.com/dir/').toRelative('http://www.site.com/dir/somedir/somefile.htm'); - */ - toRelative: function(uri) { - var self = this, output; - - if (uri === "./") { - return uri; - } - - uri = new URI(uri, {base_uri: self}); - - // Not on same domain/port or protocol - if ((uri.host != 'mce_host' && self.host != uri.host && uri.host) || self.port != uri.port || - (self.protocol != uri.protocol && uri.protocol !== "")) { - return uri.getURI(); - } - - var tu = self.getURI(), uu = uri.getURI(); - - // Allow usage of the base_uri when relative_urls = true - if (tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) { - return tu; - } - - output = self.toRelPath(self.path, uri.path); - - // Add query - if (uri.query) { - output += '?' + uri.query; - } - - // Add anchor - if (uri.anchor) { - output += '#' + uri.anchor; - } - - return output; - }, - - /** - * Converts the specified URI into a absolute URI based on the current URI instance location. - * - * @method toAbsolute - * @param {String} uri URI to convert into a relative path/URI. - * @param {Boolean} noHost No host and protocol prefix. - * @return {String} Absolute URI from the point specified in the current URI instance. - * @example - * // Converts an relative URL to an absolute URL url will be http://www.site.com/dir/somedir/somefile.htm - * var url = new tinymce.util.URI('http://www.site.com/dir/').toAbsolute('somedir/somefile.htm'); - */ - toAbsolute: function(uri, noHost) { - uri = new URI(uri, {base_uri: this}); - - return uri.getURI(noHost && this.isSameOrigin(uri)); - }, - - /** - * Determine whether the given URI has the same origin as this URI. Based on RFC-6454. - * Supports default ports for protocols listed in DEFAULT_PORTS. Unsupported protocols will fail safe: they - * won't match, if the port specifications differ. - * - * @method isSameOrigin - * @param {tinymce.util.URI} uri Uri instance to compare. - * @returns {Boolean} True if the origins are the same. - */ - isSameOrigin: function(uri) { - if (this.host == uri.host && this.protocol == uri.protocol) { - if (this.port == uri.port) { - return true; - } - - var defaultPort = DEFAULT_PORTS[this.protocol]; - if (defaultPort && ((this.port || defaultPort) == (uri.port || defaultPort))) { - return true; - } - } - - return false; - }, - - /** - * Converts a absolute path into a relative path. - * - * @method toRelPath - * @param {String} base Base point to convert the path from. - * @param {String} path Absolute path to convert into a relative path. - */ - toRelPath: function(base, path) { - var items, breakPoint = 0, out = '', i, l; - - // Split the paths - base = base.substring(0, base.lastIndexOf('/')); - base = base.split('/'); - items = path.split('/'); - - if (base.length >= items.length) { - for (i = 0, l = base.length; i < l; i++) { - if (i >= items.length || base[i] != items[i]) { - breakPoint = i + 1; - break; - } - } - } - - if (base.length < items.length) { - for (i = 0, l = items.length; i < l; i++) { - if (i >= base.length || base[i] != items[i]) { - breakPoint = i + 1; - break; - } - } - } - - if (breakPoint === 1) { - return path; - } - - for (i = 0, l = base.length - (breakPoint - 1); i < l; i++) { - out += "../"; - } - - for (i = breakPoint - 1, l = items.length; i < l; i++) { - if (i != breakPoint - 1) { - out += "/" + items[i]; - } else { - out += items[i]; - } - } - - return out; - }, - - /** - * Converts a relative path into a absolute path. - * - * @method toAbsPath - * @param {String} base Base point to convert the path from. - * @param {String} path Relative path to convert into an absolute path. - */ - toAbsPath: function(base, path) { - var i, nb = 0, o = [], tr, outPath; - - // Split paths - tr = /\/$/.test(path) ? '/' : ''; - base = base.split('/'); - path = path.split('/'); - - // Remove empty chunks - each(base, function(k) { - if (k) { - o.push(k); - } - }); - - base = o; - - // Merge relURLParts chunks - for (i = path.length - 1, o = []; i >= 0; i--) { - // Ignore empty or . - if (path[i].length === 0 || path[i] === ".") { - continue; - } - - // Is parent - if (path[i] === '..') { - nb++; - continue; - } - - // Move up - if (nb > 0) { - nb--; - continue; - } - - o.push(path[i]); - } - - i = base.length - nb; - - // If /a/b/c or / - if (i <= 0) { - outPath = o.reverse().join('/'); - } else { - outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/'); - } - - // Add front / if it's needed - if (outPath.indexOf('/') !== 0) { - outPath = '/' + outPath; - } - - // Add traling / if it's needed - if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) { - outPath += tr; - } - - return outPath; - }, - - /** - * Returns the full URI of the internal structure. - * - * @method getURI - * @param {Boolean} noProtoHost Optional no host and protocol part. Defaults to false. - */ - getURI: function(noProtoHost) { - var s, self = this; - - // Rebuild source - if (!self.source || noProtoHost) { - s = ''; - - if (!noProtoHost) { - if (self.protocol) { - s += self.protocol + '://'; - } else { - s += '//'; - } - - if (self.userInfo) { - s += self.userInfo + '@'; - } - - if (self.host) { - s += self.host; - } - - if (self.port) { - s += ':' + self.port; - } - } - - if (self.path) { - s += self.path; - } - - if (self.query) { - s += '?' + self.query; - } - - if (self.anchor) { - s += '#' + self.anchor; - } - - self.source = s; - } - - return self.source; - } - }; - - return URI; -}); - -// Included from: js/tinymce/classes/util/Class.js - -/** - * Class.js - * - * Copyright 2003-2012, Moxiecode Systems AB, All rights reserved. - */ - -/** - * This utilitiy class is used for easier inheritage. - * - * Features: - * * Exposed super functions: this._super(); - * * Mixins - * * Dummy functions - * * Property functions: var value = object.value(); and object.value(newValue); - * * Static functions - * * Defaults settings - */ -define("tinymce/util/Class", [ - "tinymce/util/Tools" -], function(Tools) { - var each = Tools.each, extend = Tools.extend; - - var extendClass, initializing; - - function Class() { - } - - // Provides classical inheritance, based on code made by John Resig - Class.extend = extendClass = function(prop) { - var self = this, _super = self.prototype, prototype, name, member; - - // The dummy class constructor - function Class() { - var i, mixins, mixin, self = this; - - // All construction is actually done in the init method - if (!initializing) { - // Run class constuctor - if (self.init) { - self.init.apply(self, arguments); - } - - // Run mixin constructors - mixins = self.Mixins; - if (mixins) { - i = mixins.length; - while (i--) { - mixin = mixins[i]; - if (mixin.init) { - mixin.init.apply(self, arguments); - } - } - } - } - } - - // Dummy function, needs to be extended in order to provide functionality - function dummy() { - return this; - } - - // Creates a overloaded method for the class - // this enables you to use this._super(); to call the super function - function createMethod(name, fn) { - return function() { - var self = this, tmp = self._super, ret; - - self._super = _super[name]; - ret = fn.apply(self, arguments); - self._super = tmp; - - return ret; - }; - } - - // Instantiate a base class (but only create the instance, - // don't run the init constructor) - initializing = true; - - /*eslint new-cap:0 */ - prototype = new self(); - initializing = false; - - // Add mixins - if (prop.Mixins) { - each(prop.Mixins, function(mixin) { - mixin = mixin; - - for (var name in mixin) { - if (name !== "init") { - prop[name] = mixin[name]; - } - } - }); - - if (_super.Mixins) { - prop.Mixins = _super.Mixins.concat(prop.Mixins); - } - } - - // Generate dummy methods - if (prop.Methods) { - each(prop.Methods.split(','), function(name) { - prop[name] = dummy; - }); - } - - // Generate property methods - if (prop.Properties) { - each(prop.Properties.split(','), function(name) { - var fieldName = '_' + name; - - prop[name] = function(value) { - var self = this, undef; - - // Set value - if (value !== undef) { - self[fieldName] = value; - - return self; - } - - // Get value - return self[fieldName]; - }; - }); - } - - // Static functions - if (prop.Statics) { - each(prop.Statics, function(func, name) { - Class[name] = func; - }); - } - - // Default settings - if (prop.Defaults && _super.Defaults) { - prop.Defaults = extend({}, _super.Defaults, prop.Defaults); - } - - // Copy the properties over onto the new prototype - for (name in prop) { - member = prop[name]; - - if (typeof member == "function" && _super[name]) { - prototype[name] = createMethod(name, member); - } else { - prototype[name] = member; - } - } - - // Populate our constructed prototype object - Class.prototype = prototype; - - // Enforce the constructor to be what we expect - Class.constructor = Class; - - // And make this class extendible - Class.extend = extendClass; - - return Class; - }; - - return Class; -}); - -// Included from: js/tinymce/classes/util/EventDispatcher.js - -/** - * EventDispatcher.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * This class lets you add/remove and fire events by name on the specified scope. This makes - * it easy to add event listener logic to any class. - * - * @class tinymce.util.EventDispatcher - * @example - * var eventDispatcher = new EventDispatcher(); - * - * eventDispatcher.on('click', function() {console.log('data');}); - * eventDispatcher.fire('click', {data: 123}); - */ -define("tinymce/util/EventDispatcher", [ - "tinymce/util/Tools" -], function(Tools) { - var nativeEvents = Tools.makeMap( - "focus blur focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange " + - "mouseout mouseenter mouseleave wheel keydown keypress keyup input contextmenu dragstart dragend dragover " + - "draggesture dragdrop drop drag submit " + - "compositionstart compositionend compositionupdate touchstart touchend", - ' ' - ); - - function Dispatcher(settings) { - var self = this, scope, bindings = {}, toggleEvent; - - function returnFalse() { - return false; - } - - function returnTrue() { - return true; - } - - settings = settings || {}; - scope = settings.scope || self; - toggleEvent = settings.toggleEvent || returnFalse; - - /** - * Fires the specified event by name. - * - * @method fire - * @param {String} name Name of the event to fire. - * @param {Object?} args Event arguments. - * @return {Object} Event args instance passed in. - * @example - * instance.fire('event', {...}); - */ - function fire(name, args) { - var handlers, i, l, callback; - - name = name.toLowerCase(); - args = args || {}; - args.type = name; - - // Setup target is there isn't one - if (!args.target) { - args.target = scope; - } - - // Add event delegation methods if they are missing - if (!args.preventDefault) { - // Add preventDefault method - args.preventDefault = function() { - args.isDefaultPrevented = returnTrue; - }; - - // Add stopPropagation - args.stopPropagation = function() { - args.isPropagationStopped = returnTrue; - }; - - // Add stopImmediatePropagation - args.stopImmediatePropagation = function() { - args.isImmediatePropagationStopped = returnTrue; - }; - - // Add event delegation states - args.isDefaultPrevented = returnFalse; - args.isPropagationStopped = returnFalse; - args.isImmediatePropagationStopped = returnFalse; - } - - if (settings.beforeFire) { - settings.beforeFire(args); - } - - handlers = bindings[name]; - if (handlers) { - for (i = 0, l = handlers.length; i < l; i++) { - callback = handlers[i]; - - // Unbind handlers marked with "once" - if (callback.once) { - off(name, callback.func); - } - - // Stop immediate propagation if needed - if (args.isImmediatePropagationStopped()) { - args.stopPropagation(); - return args; - } - - // If callback returns false then prevent default and stop all propagation - if (callback.func.call(scope, args) === false) { - args.preventDefault(); - return args; - } - } - } - - return args; - } - - /** - * Binds an event listener to a specific event by name. - * - * @method on - * @param {String} name Event name or space separated list of events to bind. - * @param {callback} callback Callback to be executed when the event occurs. - * @param {Boolean} first Optional flag if the event should be prepended. Use this with care. - * @return {Object} Current class instance. - * @example - * instance.on('event', function(e) { - * // Callback logic - * }); - */ - function on(name, callback, prepend, extra) { - var handlers, names, i; - - if (callback === false) { - callback = returnFalse; - } - - if (callback) { - callback = { - func: callback - }; - - if (extra) { - Tools.extend(callback, extra); - } - - names = name.toLowerCase().split(' '); - i = names.length; - while (i--) { - name = names[i]; - handlers = bindings[name]; - if (!handlers) { - handlers = bindings[name] = []; - toggleEvent(name, true); - } - - if (prepend) { - handlers.unshift(callback); - } else { - handlers.push(callback); - } - } - } - - return self; - } - - /** - * Unbinds an event listener to a specific event by name. - * - * @method off - * @param {String?} name Name of the event to unbind. - * @param {callback?} callback Callback to unbind. - * @return {Object} Current class instance. - * @example - * // Unbind specific callback - * instance.off('event', handler); - * - * // Unbind all listeners by name - * instance.off('event'); - * - * // Unbind all events - * instance.off(); - */ - function off(name, callback) { - var i, handlers, bindingName, names, hi; - - if (name) { - names = name.toLowerCase().split(' '); - i = names.length; - while (i--) { - name = names[i]; - handlers = bindings[name]; - - // Unbind all handlers - if (!name) { - for (bindingName in bindings) { - toggleEvent(bindingName, false); - delete bindings[bindingName]; - } - - return self; - } - - if (handlers) { - // Unbind all by name - if (!callback) { - handlers.length = 0; - } else { - // Unbind specific ones - hi = handlers.length; - while (hi--) { - if (handlers[hi].func === callback) { - handlers = handlers.slice(0, hi).concat(handlers.slice(hi + 1)); - bindings[name] = handlers; - } - } - } - - if (!handlers.length) { - toggleEvent(name, false); - delete bindings[name]; - } - } - } - } else { - for (name in bindings) { - toggleEvent(name, false); - } - - bindings = {}; - } - - return self; - } - - /** - * Binds an event listener to a specific event by name - * and automatically unbind the event once the callback fires. - * - * @method once - * @param {String} name Event name or space separated list of events to bind. - * @param {callback} callback Callback to be executed when the event occurs. - * @param {Boolean} first Optional flag if the event should be prepended. Use this with care. - * @return {Object} Current class instance. - * @example - * instance.once('event', function(e) { - * // Callback logic - * }); - */ - function once(name, callback, prepend) { - return on(name, callback, prepend, {once: true}); - } - - /** - * Returns true/false if the dispatcher has a event of the specified name. - * - * @method has - * @param {String} name Name of the event to check for. - * @return {Boolean} true/false if the event exists or not. - */ - function has(name) { - name = name.toLowerCase(); - return !(!bindings[name] || bindings[name].length === 0); - } - - // Expose - self.fire = fire; - self.on = on; - self.off = off; - self.once = once; - self.has = has; - } - - /** - * Returns true/false if the specified event name is a native browser event or not. - * - * @method isNative - * @param {String} name Name to check if it's native. - * @return {Boolean} true/false if the event is native or not. - * @static - */ - Dispatcher.isNative = function(name) { - return !!nativeEvents[name.toLowerCase()]; - }; - - return Dispatcher; -}); - -// Included from: js/tinymce/classes/ui/Selector.js - -/** - * Selector.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/*eslint no-nested-ternary:0 */ - -/** - * Selector engine, enables you to select controls by using CSS like expressions. - * We currently only support basic CSS expressions to reduce the size of the core - * and the ones we support should be enough for most cases. - * - * @example - * Supported expressions: - * element - * element#name - * element.class - * element[attr] - * element[attr*=value] - * element[attr~=value] - * element[attr!=value] - * element[attr^=value] - * element[attr$=value] - * element: bug on IE 8 #6178
- DOMUtils.DOM.setHTML(elm, html);
- }
- };
-});
-
-// Included from: js/tinymce/classes/ui/Control.js
-
-/**
- * Control.js
- *
- * Copyright, Moxiecode Systems AB
- * Released under LGPL License.
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
-
-/*eslint consistent-this:0 */
-
-/**
- * This is the base class for all controls and containers. All UI control instances inherit
- * from this one as it has the base logic needed by all of them.
- *
- * @class tinymce.ui.Control
- */
-define("tinymce/ui/Control", [
- "tinymce/util/Class",
- "tinymce/util/Tools",
- "tinymce/util/EventDispatcher",
- "tinymce/ui/Collection",
- "tinymce/ui/DomUtils"
-], function(Class, Tools, EventDispatcher, Collection, DomUtils) {
- "use strict";
-
- var hasMouseWheelEventSupport = "onmousewheel" in document;
- var hasWheelEventSupport = false;
- var classPrefix = "mce-";
-
- function getEventDispatcher(obj) {
- if (!obj._eventDispatcher) {
- obj._eventDispatcher = new EventDispatcher({
- scope: obj,
- toggleEvent: function(name, state) {
- if (state && EventDispatcher.isNative(name)) {
- if (!obj._nativeEvents) {
- obj._nativeEvents = {};
- }
-
- obj._nativeEvents[name] = true;
-
- if (obj._rendered) {
- obj.bindPendingEvents();
- }
- }
- }
- });
- }
-
- return obj._eventDispatcher;
- }
-
- var Control = Class.extend({
- Statics: {
- classPrefix: classPrefix
- },
-
- isRtl: function() {
- return Control.rtl;
- },
-
- /**
- * Class/id prefix to use for all controls.
- *
- * @final
- * @field {String} classPrefix
- */
- classPrefix: classPrefix,
-
- /**
- * Constructs a new control instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- * @setting {String} style Style CSS properties to add.
- * @setting {String} border Border box values example: 1 1 1 1
- * @setting {String} padding Padding box values example: 1 1 1 1
- * @setting {String} margin Margin box values example: 1 1 1 1
- * @setting {Number} minWidth Minimal width for the control.
- * @setting {Number} minHeight Minimal height for the control.
- * @setting {String} classes Space separated list of classes to add.
- * @setting {String} role WAI-ARIA role to use for control.
- * @setting {Boolean} hidden Is the control hidden by default.
- * @setting {Boolean} disabled Is the control disabled by default.
- * @setting {String} name Name of the control instance.
- */
- init: function(settings) {
- var self = this, classes, i;
-
- self.settings = settings = Tools.extend({}, self.Defaults, settings);
-
- // Initial states
- self._id = settings.id || DomUtils.id();
- self._text = self._name = '';
- self._width = self._height = 0;
- self._aria = {role: settings.role};
- this._elmCache = {};
-
- // Setup classes
- classes = settings.classes;
- if (classes) {
- classes = classes.split(' ');
- classes.map = {};
- i = classes.length;
- while (i--) {
- classes.map[classes[i]] = true;
- }
- }
-
- self._classes = classes || [];
- self.visible(true);
-
- // Set some properties
- Tools.each('title text width height name classes visible disabled active value'.split(' '), function(name) {
- var value = settings[name], undef;
-
- if (value !== undef) {
- self[name](value);
- } else if (self['_' + name] === undef) {
- self['_' + name] = false;
- }
- });
-
- self.on('click', function() {
- if (self.disabled()) {
- return false;
- }
- });
-
- // TODO: Is this needed duplicate code see above?
- if (settings.classes) {
- Tools.each(settings.classes.split(' '), function(cls) {
- self.addClass(cls);
- });
- }
-
- /**
- * Name/value object with settings for the current control.
- *
- * @field {Object} settings
- */
- self.settings = settings;
-
- self._borderBox = self.parseBox(settings.border);
- self._paddingBox = self.parseBox(settings.padding);
- self._marginBox = self.parseBox(settings.margin);
-
- if (settings.hidden) {
- self.hide();
- }
- },
-
- // Will generate getter/setter methods for these properties
- Properties: 'parent,title,text,width,height,disabled,active,name,value',
-
- // Will generate empty dummy functions for these
- Methods: 'renderHtml',
-
- /**
- * Returns the root element to render controls into.
- *
- * @method getContainerElm
- * @return {Element} HTML DOM element to render into.
- */
- getContainerElm: function() {
- return document.body;
- },
-
- /**
- * Returns a control instance for the current DOM element.
- *
- * @method getParentCtrl
- * @param {Element} elm HTML dom element to get parent control from.
- * @return {tinymce.ui.Control} Control instance or undefined.
- */
- getParentCtrl: function(elm) {
- var ctrl, lookup = this.getRoot().controlIdLookup;
-
- while (elm && lookup) {
- ctrl = lookup[elm.id];
- if (ctrl) {
- break;
- }
-
- elm = elm.parentNode;
- }
-
- return ctrl;
- },
-
- /**
- * Parses the specified box value. A box value contains 1-4 properties in clockwise order.
- *
- * @method parseBox
- * @param {String/Number} value Box value "0 1 2 3" or "0" etc.
- * @return {Object} Object with top/right/bottom/left properties.
- * @private
- */
- parseBox: function(value) {
- var len, radix = 10;
-
- if (!value) {
- return;
- }
-
- if (typeof value === "number") {
- value = value || 0;
-
- return {
- top: value,
- left: value,
- bottom: value,
- right: value
- };
- }
-
- value = value.split(' ');
- len = value.length;
-
- if (len === 1) {
- value[1] = value[2] = value[3] = value[0];
- } else if (len === 2) {
- value[2] = value[0];
- value[3] = value[1];
- } else if (len === 3) {
- value[3] = value[1];
- }
-
- return {
- top: parseInt(value[0], radix) || 0,
- right: parseInt(value[1], radix) || 0,
- bottom: parseInt(value[2], radix) || 0,
- left: parseInt(value[3], radix) || 0
- };
- },
-
- borderBox: function() {
- return this._borderBox;
- },
-
- paddingBox: function() {
- return this._paddingBox;
- },
-
- marginBox: function() {
- return this._marginBox;
- },
-
- measureBox: function(elm, prefix) {
- function getStyle(name) {
- var defaultView = document.defaultView;
-
- if (defaultView) {
- // Remove camelcase
- name = name.replace(/[A-Z]/g, function(a) {
- return '-' + a;
- });
-
- return defaultView.getComputedStyle(elm, null).getPropertyValue(name);
- }
-
- return elm.currentStyle[name];
- }
-
- function getSide(name) {
- var val = parseFloat(getStyle(name), 10);
-
- return isNaN(val) ? 0 : val;
- }
-
- return {
- top: getSide(prefix + "TopWidth"),
- right: getSide(prefix + "RightWidth"),
- bottom: getSide(prefix + "BottomWidth"),
- left: getSide(prefix + "LeftWidth")
- };
- },
-
- /**
- * Initializes the current controls layout rect.
- * This will be executed by the layout managers to determine the
- * default minWidth/minHeight etc.
- *
- * @method initLayoutRect
- * @return {Object} Layout rect instance.
- */
- initLayoutRect: function() {
- var self = this, settings = self.settings, borderBox, layoutRect;
- var elm = self.getEl(), width, height, minWidth, minHeight, autoResize;
- var startMinWidth, startMinHeight, initialSize;
-
- // Measure the current element
- borderBox = self._borderBox = self._borderBox || self.measureBox(elm, 'border');
- self._paddingBox = self._paddingBox || self.measureBox(elm, 'padding');
- self._marginBox = self._marginBox || self.measureBox(elm, 'margin');
- initialSize = DomUtils.getSize(elm);
-
- // Setup minWidth/minHeight and width/height
- startMinWidth = settings.minWidth;
- startMinHeight = settings.minHeight;
- minWidth = startMinWidth || initialSize.width;
- minHeight = startMinHeight || initialSize.height;
- width = settings.width;
- height = settings.height;
- autoResize = settings.autoResize;
- autoResize = typeof autoResize != "undefined" ? autoResize : !width && !height;
-
- width = width || minWidth;
- height = height || minHeight;
-
- var deltaW = borderBox.left + borderBox.right;
- var deltaH = borderBox.top + borderBox.bottom;
-
- var maxW = settings.maxWidth || 0xFFFF;
- var maxH = settings.maxHeight || 0xFFFF;
-
- // Setup initial layout rect
- self._layoutRect = layoutRect = {
- x: settings.x || 0,
- y: settings.y || 0,
- w: width,
- h: height,
- deltaW: deltaW,
- deltaH: deltaH,
- contentW: width - deltaW,
- contentH: height - deltaH,
- innerW: width - deltaW,
- innerH: height - deltaH,
- startMinWidth: startMinWidth || 0,
- startMinHeight: startMinHeight || 0,
- minW: Math.min(minWidth, maxW),
- minH: Math.min(minHeight, maxH),
- maxW: maxW,
- maxH: maxH,
- autoResize: autoResize,
- scrollW: 0
- };
-
- self._lastLayoutRect = {};
-
- return layoutRect;
- },
-
- /**
- * Getter/setter for the current layout rect.
- *
- * @method layoutRect
- * @param {Object} [newRect] Optional new layout rect.
- * @return {tinymce.ui.Control/Object} Current control or rect object.
- */
- layoutRect: function(newRect) {
- var self = this, curRect = self._layoutRect, lastLayoutRect, size, deltaWidth, deltaHeight, undef, repaintControls;
-
- // Initialize default layout rect
- if (!curRect) {
- curRect = self.initLayoutRect();
- }
-
- // Set new rect values
- if (newRect) {
- // Calc deltas between inner and outer sizes
- deltaWidth = curRect.deltaW;
- deltaHeight = curRect.deltaH;
-
- // Set x position
- if (newRect.x !== undef) {
- curRect.x = newRect.x;
- }
-
- // Set y position
- if (newRect.y !== undef) {
- curRect.y = newRect.y;
- }
-
- // Set minW
- if (newRect.minW !== undef) {
- curRect.minW = newRect.minW;
- }
-
- // Set minH
- if (newRect.minH !== undef) {
- curRect.minH = newRect.minH;
- }
-
- // Set new width and calculate inner width
- size = newRect.w;
- if (size !== undef) {
- size = size < curRect.minW ? curRect.minW : size;
- size = size > curRect.maxW ? curRect.maxW : size;
- curRect.w = size;
- curRect.innerW = size - deltaWidth;
- }
-
- // Set new height and calculate inner height
- size = newRect.h;
- if (size !== undef) {
- size = size < curRect.minH ? curRect.minH : size;
- size = size > curRect.maxH ? curRect.maxH : size;
- curRect.h = size;
- curRect.innerH = size - deltaHeight;
- }
-
- // Set new inner width and calculate width
- size = newRect.innerW;
- if (size !== undef) {
- size = size < curRect.minW - deltaWidth ? curRect.minW - deltaWidth : size;
- size = size > curRect.maxW - deltaWidth ? curRect.maxW - deltaWidth : size;
- curRect.innerW = size;
- curRect.w = size + deltaWidth;
- }
-
- // Set new height and calculate inner height
- size = newRect.innerH;
- if (size !== undef) {
- size = size < curRect.minH - deltaHeight ? curRect.minH - deltaHeight : size;
- size = size > curRect.maxH - deltaHeight ? curRect.maxH - deltaHeight : size;
- curRect.innerH = size;
- curRect.h = size + deltaHeight;
- }
-
- // Set new contentW
- if (newRect.contentW !== undef) {
- curRect.contentW = newRect.contentW;
- }
-
- // Set new contentH
- if (newRect.contentH !== undef) {
- curRect.contentH = newRect.contentH;
- }
-
- // Compare last layout rect with the current one to see if we need to repaint or not
- lastLayoutRect = self._lastLayoutRect;
- if (lastLayoutRect.x !== curRect.x || lastLayoutRect.y !== curRect.y ||
- lastLayoutRect.w !== curRect.w || lastLayoutRect.h !== curRect.h) {
- repaintControls = Control.repaintControls;
-
- if (repaintControls) {
- if (repaintControls.map && !repaintControls.map[self._id]) {
- repaintControls.push(self);
- repaintControls.map[self._id] = true;
- }
- }
-
- lastLayoutRect.x = curRect.x;
- lastLayoutRect.y = curRect.y;
- lastLayoutRect.w = curRect.w;
- lastLayoutRect.h = curRect.h;
- }
-
- return self;
- }
-
- return curRect;
- },
-
- /**
- * Repaints the control after a layout operation.
- *
- * @method repaint
- */
- repaint: function() {
- var self = this, style, bodyStyle, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect, round;
-
- // Use Math.round on all values on IE < 9
- round = !document.createRange ? Math.round : function(value) {
- return value;
- };
-
- style = self.getEl().style;
- rect = self._layoutRect;
- lastRepaintRect = self._lastRepaintRect || {};
-
- borderBox = self._borderBox;
- borderW = borderBox.left + borderBox.right;
- borderH = borderBox.top + borderBox.bottom;
-
- if (rect.x !== lastRepaintRect.x) {
- style.left = round(rect.x) + 'px';
- lastRepaintRect.x = rect.x;
- }
-
- if (rect.y !== lastRepaintRect.y) {
- style.top = round(rect.y) + 'px';
- lastRepaintRect.y = rect.y;
- }
-
- if (rect.w !== lastRepaintRect.w) {
- style.width = round(rect.w - borderW) + 'px';
- lastRepaintRect.w = rect.w;
- }
-
- if (rect.h !== lastRepaintRect.h) {
- style.height = round(rect.h - borderH) + 'px';
- lastRepaintRect.h = rect.h;
- }
-
- // Update body if needed
- if (self._hasBody && rect.innerW !== lastRepaintRect.innerW) {
- bodyStyle = self.getEl('body').style;
- bodyStyle.width = round(rect.innerW) + 'px';
- lastRepaintRect.innerW = rect.innerW;
- }
-
- if (self._hasBody && rect.innerH !== lastRepaintRect.innerH) {
- bodyStyle = bodyStyle || self.getEl('body').style;
- bodyStyle.height = round(rect.innerH) + 'px';
- lastRepaintRect.innerH = rect.innerH;
- }
-
- self._lastRepaintRect = lastRepaintRect;
- self.fire('repaint', {}, false);
- },
-
- /**
- * Binds a callback to the specified event. This event can both be
- * native browser events like "click" or custom ones like PostRender.
- *
- * The callback function will be passed a DOM event like object that enables yout do stop propagation.
- *
- * @method on
- * @param {String} name Name of the event to bind. For example "click".
- * @param {String/function} callback Callback function to execute ones the event occurs.
- * @return {tinymce.ui.Control} Current control object.
- */
- on: function(name, callback) {
- var self = this;
-
- function resolveCallbackName(name) {
- var callback, scope;
-
- if (typeof name != 'string') {
- return name;
- }
-
- return function(e) {
- if (!callback) {
- self.parentsAndSelf().each(function(ctrl) {
- var callbacks = ctrl.settings.callbacks;
-
- if (callbacks && (callback = callbacks[name])) {
- scope = ctrl;
- return false;
- }
- });
- }
-
- return callback.call(scope, e);
- };
- }
-
- getEventDispatcher(self).on(name, resolveCallbackName(callback));
-
- return self;
- },
-
- /**
- * Unbinds the specified event and optionally a specific callback. If you omit the name
- * parameter all event handlers will be removed. If you omit the callback all event handles
- * by the specified name will be removed.
- *
- * @method off
- * @param {String} [name] Name for the event to unbind.
- * @param {function} [callback] Callback function to unbind.
- * @return {mxex.ui.Control} Current control object.
- */
- off: function(name, callback) {
- getEventDispatcher(this).off(name, callback);
- return this;
- },
-
- /**
- * Fires the specified event by name and arguments on the control. This will execute all
- * bound event handlers.
- *
- * @method fire
- * @param {String} name Name of the event to fire.
- * @param {Object} [args] Arguments to pass to the event.
- * @param {Boolean} [bubble] Value to control bubbeling. Defaults to true.
- * @return {Object} Current arguments object.
- */
- fire: function(name, args, bubble) {
- var self = this;
-
- args = args || {};
-
- if (!args.control) {
- args.control = self;
- }
-
- args = getEventDispatcher(self).fire(name, args);
-
- // Bubble event up to parents
- if (bubble !== false && self.parent) {
- var parent = self.parent();
- while (parent && !args.isPropagationStopped()) {
- parent.fire(name, args, false);
- parent = parent.parent();
- }
- }
-
- return args;
- },
-
- /**
- * Returns true/false if the specified event has any listeners.
- *
- * @method hasEventListeners
- * @param {String} name Name of the event to check for.
- * @return {Boolean} True/false state if the event has listeners.
- */
- hasEventListeners: function(name) {
- return getEventDispatcher(this).has(name);
- },
-
- /**
- * Returns a control collection with all parent controls.
- *
- * @method parents
- * @param {String} selector Optional selector expression to find parents.
- * @return {tinymce.ui.Collection} Collection with all parent controls.
- */
- parents: function(selector) {
- var self = this, ctrl, parents = new Collection();
-
- // Add each parent to collection
- for (ctrl = self.parent(); ctrl; ctrl = ctrl.parent()) {
- parents.add(ctrl);
- }
-
- // Filter away everything that doesn't match the selector
- if (selector) {
- parents = parents.filter(selector);
- }
-
- return parents;
- },
-
- /**
- * Returns the current control and it's parents.
- *
- * @method parentsAndSelf
- * @param {String} selector Optional selector expression to find parents.
- * @return {tinymce.ui.Collection} Collection with all parent controls.
- */
- parentsAndSelf: function(selector) {
- return new Collection(this).add(this.parents(selector));
- },
-
- /**
- * Returns the control next to the current control.
- *
- * @method next
- * @return {tinymce.ui.Control} Next control instance.
- */
- next: function() {
- var parentControls = this.parent().items();
-
- return parentControls[parentControls.indexOf(this) + 1];
- },
-
- /**
- * Returns the control previous to the current control.
- *
- * @method prev
- * @return {tinymce.ui.Control} Previous control instance.
- */
- prev: function() {
- var parentControls = this.parent().items();
-
- return parentControls[parentControls.indexOf(this) - 1];
- },
-
- /**
- * Find the common ancestor for two control instances.
- *
- * @method findCommonAncestor
- * @param {tinymce.ui.Control} ctrl1 First control.
- * @param {tinymce.ui.Control} ctrl2 Second control.
- * @return {tinymce.ui.Control} Ancestor control instance.
- */
- findCommonAncestor: function(ctrl1, ctrl2) {
- var parentCtrl;
-
- while (ctrl1) {
- parentCtrl = ctrl2;
-
- while (parentCtrl && ctrl1 != parentCtrl) {
- parentCtrl = parentCtrl.parent();
- }
-
- if (ctrl1 == parentCtrl) {
- break;
- }
-
- ctrl1 = ctrl1.parent();
- }
-
- return ctrl1;
- },
-
- /**
- * Returns true/false if the specific control has the specific class.
- *
- * @method hasClass
- * @param {String} cls Class to check for.
- * @param {String} [group] Sub element group name.
- * @return {Boolean} True/false if the control has the specified class.
- */
- hasClass: function(cls, group) {
- var classes = this._classes[group || 'control'];
-
- cls = this.classPrefix + cls;
-
- return classes && !!classes.map[cls];
- },
-
- /**
- * Adds the specified class to the control
- *
- * @method addClass
- * @param {String} cls Class to check for.
- * @param {String} [group] Sub element group name.
- * @return {tinymce.ui.Control} Current control object.
- */
- addClass: function(cls, group) {
- var self = this, classes, elm;
-
- cls = this.classPrefix + cls;
- classes = self._classes[group || 'control'];
-
- if (!classes) {
- classes = [];
- classes.map = {};
- self._classes[group || 'control'] = classes;
- }
-
- if (!classes.map[cls]) {
- classes.map[cls] = cls;
- classes.push(cls);
-
- if (self._rendered) {
- elm = self.getEl(group);
-
- if (elm) {
- elm.className = classes.join(' ');
- }
- }
- }
-
- return self;
- },
-
- /**
- * Removes the specified class from the control.
- *
- * @method removeClass
- * @param {String} cls Class to remove.
- * @param {String} [group] Sub element group name.
- * @return {tinymce.ui.Control} Current control object.
- */
- removeClass: function(cls, group) {
- var self = this, classes, i, elm;
-
- cls = this.classPrefix + cls;
- classes = self._classes[group || 'control'];
- if (classes && classes.map[cls]) {
- delete classes.map[cls];
-
- i = classes.length;
- while (i--) {
- if (classes[i] === cls) {
- classes.splice(i, 1);
- }
- }
- }
-
- if (self._rendered) {
- elm = self.getEl(group);
-
- if (elm) {
- elm.className = classes.join(' ');
- }
- }
-
- return self;
- },
-
- /**
- * Toggles the specified class on the control.
- *
- * @method toggleClass
- * @param {String} cls Class to remove.
- * @param {Boolean} state True/false state to add/remove class.
- * @param {String} [group] Sub element group name.
- * @return {tinymce.ui.Control} Current control object.
- */
- toggleClass: function(cls, state, group) {
- var self = this;
-
- if (state) {
- self.addClass(cls, group);
- } else {
- self.removeClass(cls, group);
- }
-
- return self;
- },
-
- /**
- * Returns the class string for the specified group name.
- *
- * @method classes
- * @param {String} [group] Group to get clases by.
- * @return {String} Classes for the specified group.
- */
- classes: function(group) {
- var classes = this._classes[group || 'control'];
-
- return classes ? classes.join(' ') : '';
- },
-
- /**
- * Sets the inner HTML of the control element.
- *
- * @method innerHtml
- * @param {String} html Html string to set as inner html.
- * @return {tinymce.ui.Control} Current control object.
- */
- innerHtml: function(html) {
- DomUtils.innerHtml(this.getEl(), html);
- return this;
- },
-
- /**
- * Returns the control DOM element or sub element.
- *
- * @method getEl
- * @param {String} [suffix] Suffix to get element by.
- * @return {Element} HTML DOM element for the current control or it's children.
- */
- getEl: function(suffix) {
- var id = suffix ? this._id + '-' + suffix : this._id;
-
- if (!this._elmCache[id]) {
- this._elmCache[id] = DomUtils.get(id);
- }
-
- return this._elmCache[id];
- },
-
- /**
- * Sets/gets the visible for the control.
- *
- * @method visible
- * @param {Boolean} state Value to set to control.
- * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
- */
- visible: function(state) {
- var self = this, parentCtrl;
-
- if (typeof state !== "undefined") {
- if (self._visible !== state) {
- if (self._rendered) {
- self.getEl().style.display = state ? '' : 'none';
- }
-
- self._visible = state;
-
- // Parent container needs to reflow
- parentCtrl = self.parent();
- if (parentCtrl) {
- parentCtrl._lastRect = null;
- }
-
- self.fire(state ? 'show' : 'hide');
- }
-
- return self;
- }
-
- return self._visible;
- },
-
- /**
- * Sets the visible state to true.
- *
- * @method show
- * @return {tinymce.ui.Control} Current control instance.
- */
- show: function() {
- return this.visible(true);
- },
-
- /**
- * Sets the visible state to false.
- *
- * @method hide
- * @return {tinymce.ui.Control} Current control instance.
- */
- hide: function() {
- return this.visible(false);
- },
-
- /**
- * Focuses the current control.
- *
- * @method focus
- * @return {tinymce.ui.Control} Current control instance.
- */
- focus: function() {
- try {
- this.getEl().focus();
- } catch (ex) {
- // Ignore IE error
- }
-
- return this;
- },
-
- /**
- * Blurs the current control.
- *
- * @method blur
- * @return {tinymce.ui.Control} Current control instance.
- */
- blur: function() {
- this.getEl().blur();
-
- return this;
- },
-
- /**
- * Sets the specified aria property.
- *
- * @method aria
- * @param {String} name Name of the aria property to set.
- * @param {String} value Value of the aria property.
- * @return {tinymce.ui.Control} Current control instance.
- */
- aria: function(name, value) {
- var self = this, elm = self.getEl(self.ariaTarget);
-
- if (typeof value === "undefined") {
- return self._aria[name];
- } else {
- self._aria[name] = value;
- }
-
- if (self._rendered) {
- elm.setAttribute(name == 'role' ? name : 'aria-' + name, value);
- }
-
- return self;
- },
-
- /**
- * Encodes the specified string with HTML entities. It will also
- * translate the string to different languages.
- *
- * @method encode
- * @param {String/Object/Array} text Text to entity encode.
- * @param {Boolean} [translate=true] False if the contents shouldn't be translated.
- * @return {String} Encoded and possible traslated string.
- */
- encode: function(text, translate) {
- if (translate !== false) {
- text = this.translate(text);
- }
-
- return (text || '').replace(/[&<>"]/g, function(match) {
- return '' + match.charCodeAt(0) + ';';
- });
- },
-
- /**
- * Returns the translated string.
- *
- * @method translate
- * @param {String} text Text to translate.
- * @return {String} Translated string or the same as the input.
- */
- translate: function(text) {
- return Control.translate ? Control.translate(text) : text;
- },
-
- /**
- * Adds items before the current control.
- *
- * @method before
- * @param {Array/tinymce.ui.Collection} items Array of items to prepend before this control.
- * @return {tinymce.ui.Control} Current control instance.
- */
- before: function(items) {
- var self = this, parent = self.parent();
-
- if (parent) {
- parent.insert(items, parent.items().indexOf(self), true);
- }
-
- return self;
- },
-
- /**
- * Adds items after the current control.
- *
- * @method after
- * @param {Array/tinymce.ui.Collection} items Array of items to append after this control.
- * @return {tinymce.ui.Control} Current control instance.
- */
- after: function(items) {
- var self = this, parent = self.parent();
-
- if (parent) {
- parent.insert(items, parent.items().indexOf(self));
- }
-
- return self;
- },
-
- /**
- * Removes the current control from DOM and from UI collections.
- *
- * @method remove
- * @return {tinymce.ui.Control} Current control instance.
- */
- remove: function() {
- var self = this, elm = self.getEl(), parent = self.parent(), newItems, i;
-
- if (self.items) {
- var controls = self.items().toArray();
- i = controls.length;
- while (i--) {
- controls[i].remove();
- }
- }
-
- if (parent && parent.items) {
- newItems = [];
-
- parent.items().each(function(item) {
- if (item !== self) {
- newItems.push(item);
- }
- });
-
- parent.items().set(newItems);
- parent._lastRect = null;
- }
-
- if (self._eventsRoot && self._eventsRoot == self) {
- DomUtils.off(elm);
- }
-
- var lookup = self.getRoot().controlIdLookup;
- if (lookup) {
- delete lookup[self._id];
- }
-
- if (elm && elm.parentNode) {
- elm.parentNode.removeChild(elm);
- }
-
- self._rendered = false;
-
- return self;
- },
-
- /**
- * Renders the control before the specified element.
- *
- * @method renderBefore
- * @param {Element} elm Element to render before.
- * @return {tinymce.ui.Control} Current control instance.
- */
- renderBefore: function(elm) {
- var self = this;
-
- elm.parentNode.insertBefore(DomUtils.createFragment(self.renderHtml()), elm);
- self.postRender();
-
- return self;
- },
-
- /**
- * Renders the control to the specified element.
- *
- * @method renderBefore
- * @param {Element} elm Element to render to.
- * @return {tinymce.ui.Control} Current control instance.
- */
- renderTo: function(elm) {
- var self = this;
-
- elm = elm || self.getContainerElm();
- elm.appendChild(DomUtils.createFragment(self.renderHtml()));
- self.postRender();
-
- return self;
- },
-
- /**
- * Post render method. Called after the control has been rendered to the target.
- *
- * @method postRender
- * @return {tinymce.ui.Control} Current control instance.
- */
- postRender: function() {
- var self = this, settings = self.settings, elm, box, parent, name, parentEventsRoot;
-
- // Bind on |ba
ab
|
- * - * Or: - *|
- if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) { - isCollapsed = editor.selection.isCollapsed(); - body = editor.getBody(); - - // Selection is collapsed but the editor isn't empty - if (isCollapsed && !dom.isEmpty(body)) { - return; - } - - // Selection isn't collapsed but not all the contents is selected - if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) { - return; - } - - // Manually empty the editor - e.preventDefault(); - editor.setContent(''); - - if (body.firstChild && dom.isBlock(body.firstChild)) { - editor.selection.setCursorLocation(body.firstChild, 0); - } else { - editor.selection.setCursorLocation(body, 0); - } - - editor.nodeChanged(); - } - }); - } - - /** - * WebKit doesn't select all the nodes in the body when you press Ctrl+A. - * IE selects more than the contents [a
] instead of[a]
see bug #6438 - * This selects the whole body so that backspace/delete logic will delete everything - */ - function selectAll() { - editor.shortcuts.add('meta+a', null, 'SelectAll'); - } - - /** - * WebKit has a weird issue where it some times fails to properly convert keypresses to input method keystrokes. - * The IME on Mac doesn't initialize when it doesn't fire a proper focus event. - * - * This seems to happen when the user manages to click the documentElement element then the window doesn't get proper focus until - * you enter a character into the editor. - * - * It also happens when the first focus in made to the body. - * - * See: https://bugs.webkit.org/show_bug.cgi?id=83566 - */ - function inputMethodFocus() { - if (!editor.settings.content_editable) { - // Case 1 IME doesn't initialize if you focus the document - dom.bind(editor.getDoc(), 'focusin', function() { - selection.setRng(selection.getRng()); - }); - - // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event - // Needs to be both down/up due to weird rendering bug on Chrome Windows - dom.bind(editor.getDoc(), 'mousedown mouseup', function(e) { - if (e.target == editor.getDoc().documentElement) { - editor.getBody().focus(); - - if (e.type == 'mousedown') { - // Edge case for mousedown, drag select and mousedown again within selection on Chrome Windows to render caret - selection.placeCaretAt(e.clientX, e.clientY); - } else { - selection.setRng(selection.getRng()); - } - } - }); - } - } - - /** - * Backspacing in FireFox/IE from a paragraph into a horizontal rule results in a floating text node because the - * browser just deletes the paragraph - the browser fails to merge the text node with a horizontal rule so it is - * left there. TinyMCE sees a floating text node and wraps it in a paragraph on the key up event (ForceBlocks.js - * addRootBlocks), meaning the action does nothing. With this code, FireFox/IE matche the behaviour of other - * browsers. - * - * It also fixes a bug on Firefox where it's impossible to delete HR elements. - */ - function removeHrOnBackspace() { - editor.on('keydown', function(e) { - if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) { - // Check if there is any HR elements this is faster since getRng on IE 7 & 8 is slow - if (!editor.getBody().getElementsByTagName('hr').length) { - return; - } - - if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { - var node = selection.getNode(); - var previousSibling = node.previousSibling; - - if (node.nodeName == 'HR') { - dom.remove(node); - e.preventDefault(); - return; - } - - if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") { - dom.remove(previousSibling); - e.preventDefault(); - } - } - } - }); - } - - /** - * Firefox 3.x has an issue where the body element won't get proper focus if you click out - * side it's rectangle. - */ - function focusBody() { - // Fix for a focus bug in FF 3.x where the body element - // wouldn't get proper focus if the user clicked on the HTML element - if (!window.Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4 - editor.on('mousedown', function(e) { - if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") { - var body = editor.getBody(); - - // Blur the body it's focused but not correctly focused - body.blur(); - - // Refocus the body after a little while - setTimeout(function() { - body.focus(); - }, 0); - } - }); - } - } - - /** - * WebKit has a bug where it isn't possible to select image, hr or anchor elements - * by clicking on them so we need to fake that. - */ - function selectControlElements() { - editor.on('click', function(e) { - var target = e.target; - - // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250 - // WebKit can't even do simple things like selecting an image - // Needs to be the setBaseAndExtend or it will fail to select floated images - if (/^(IMG|HR)$/.test(target.nodeName)) { - e.preventDefault(); - selection.getSel().setBaseAndExtent(target, 0, target, 1); - editor.nodeChanged(); - } - - if (target.nodeName == 'A' && dom.hasClass(target, 'mce-item-anchor')) { - e.preventDefault(); - selection.select(target); - } - }); - } - - /** - * Fixes a Gecko bug where the style attribute gets added to the wrong element when deleting between two block elements. - * - * Fixes do backspace/delete on this: - *bla[ck
r]ed
- * - * Would become: - *bla|ed
- * - * Instead of: - *bla|ed
- */ - function removeStylesWhenDeletingAcrossBlockElements() { - function getAttributeApplyFunction() { - var template = dom.getAttribs(selection.getStart().cloneNode(false)); - - return function() { - var target = selection.getStart(); - - if (target !== editor.getBody()) { - dom.setAttrib(target, "style", null); - - each(template, function(attr) { - target.setAttributeNode(attr.cloneNode(true)); - }); - } - }; - } - - function isSelectionAcrossElements() { - return !selection.isCollapsed() && - dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock); - } - - editor.on('keypress', function(e) { - var applyAttributes; - - if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { - applyAttributes = getAttributeApplyFunction(); - editor.getDoc().execCommand('delete', false, null); - applyAttributes(); - e.preventDefault(); - return false; - } - }); - - dom.bind(editor.getDoc(), 'cut', function(e) { - var applyAttributes; - - if (!isDefaultPrevented(e) && isSelectionAcrossElements()) { - applyAttributes = getAttributeApplyFunction(); - - setTimeout(function() { - applyAttributes(); - }, 0); - } - }); - } - - /** - * Screen readers on IE needs to have the role application set on the body. - */ - function ensureBodyHasRoleApplication() { - document.body.setAttribute("role", "application"); - } - - /** - * Backspacing into a table behaves differently depending upon browser type. - * Therefore, disable Backspace when cursor immediately follows a table. - */ - function disableBackspaceIntoATable() { - editor.on('keydown', function(e) { - if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) { - if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { - var previousSibling = selection.getNode().previousSibling; - if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") { - e.preventDefault(); - return false; - } - } - } - }); - } - - /** - * Old IE versions can't properly render BR elements in PRE tags white in contentEditable mode. So this - * logic adds a \n before the BR so that it will get rendered. - */ - function addNewLinesBeforeBrInPre() { - // IE8+ rendering mode does the right thing with BR in PRE - if (getDocumentMode() > 7) { - return; - } - - // Enable display: none in area and add a specific class that hides all BR elements in PRE to - // avoid the caret from getting stuck at the BR elements while pressing the right arrow key - setEditorCommandState('RespectVisibilityInDesign', true); - editor.contentStyles.push('.mceHideBrInPre pre br {display: none}'); - dom.addClass(editor.getBody(), 'mceHideBrInPre'); - - // Adds a \n before all BR elements in PRE to get them visual - parser.addNodeFilter('pre', function(nodes) { - var i = nodes.length, brNodes, j, brElm, sibling; - - while (i--) { - brNodes = nodes[i].getAll('br'); - j = brNodes.length; - while (j--) { - brElm = brNodes[j]; - - // Add \n before BR in PRE elements on older IE:s so the new lines get rendered - sibling = brElm.prev; - if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') { - sibling.value += '\n'; - } else { - brElm.parent.insert(new Node('#text', 3), brElm, true).value = '\n'; - } - } - } - }); - - // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible - serializer.addNodeFilter('pre', function(nodes) { - var i = nodes.length, brNodes, j, brElm, sibling; - - while (i--) { - brNodes = nodes[i].getAll('br'); - j = brNodes.length; - while (j--) { - brElm = brNodes[j]; - sibling = brElm.prev; - if (sibling && sibling.type == 3) { - sibling.value = sibling.value.replace(/\r?\n$/, ''); - } - } - } - }); - } - - /** - * Moves style width/height to attribute width/height when the user resizes an image on IE. - */ - function removePreSerializedStylesWhenSelectingControls() { - dom.bind(editor.getBody(), 'mouseup', function() { - var value, node = selection.getNode(); - - // Moved styles to attributes on IMG eements - if (node.nodeName == 'IMG') { - // Convert style width to width attribute - if ((value = dom.getStyle(node, 'width'))) { - dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, '')); - dom.setStyle(node, 'width', ''); - } - - // Convert style height to height attribute - if ((value = dom.getStyle(node, 'height'))) { - dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, '')); - dom.setStyle(node, 'height', ''); - } - } - }); - } - - /** - * Removes a blockquote when backspace is pressed at the beginning of it. - * - * For example: - *- * - * Becomes: - *|x
|x
- */ - function removeBlockQuoteOnBackSpace() { - // Add block quote deletion handler - editor.on('keydown', function(e) { - var rng, container, offset, root, parent; - - if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) { - return; - } - - rng = selection.getRng(); - container = rng.startContainer; - offset = rng.startOffset; - root = dom.getRoot(); - parent = container; - - if (!rng.collapsed || offset !== 0) { - return; - } - - while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) { - parent = parent.parentNode; - } - - // Is the cursor at the beginning of a blockquote? - if (parent.tagName === 'BLOCKQUOTE') { - // Remove the blockquote - editor.formatter.toggle('blockquote', null, parent); - - // Move the caret to the beginning of container - rng = dom.createRng(); - rng.setStart(container, 0); - rng.setEnd(container, 0); - selection.setRng(rng); - } - }); - } - - /** - * Sets various Gecko editing options on mouse down and before a execCommand to disable inline table editing that is broken etc. - */ - function setGeckoEditingOptions() { - function setOpts() { - editor._refreshContentEditable(); - - setEditorCommandState("StyleWithCSS", false); - setEditorCommandState("enableInlineTableEditing", false); - - if (!settings.object_resizing) { - setEditorCommandState("enableObjectResizing", false); - } - } - - if (!settings.readonly) { - editor.on('BeforeExecCommand MouseDown', setOpts); - } - } - - /** - * Fixes a gecko link bug, when a link is placed at the end of block elements there is - * no way to move the caret behind the link. This fix adds a bogus br element after the link. - * - * For example this: - * - * - * Becomes this: - * - */ - function addBrAfterLastLinks() { - function fixLinks() { - each(dom.select('a'), function(node) { - var parentNode = node.parentNode, root = dom.getRoot(); - - if (parentNode.lastChild === node) { - while (parentNode && !dom.isBlock(parentNode)) { - if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) { - return; - } - - parentNode = parentNode.parentNode; - } - - dom.add(parentNode, 'br', {'data-mce-bogus': 1}); - } - }); - } - - editor.on('SetContent ExecCommand', function(e) { - if (e.type == "setcontent" || e.command === 'mceInsertLink') { - fixLinks(); - } - }); - } - - /** - * WebKit will produce DIV elements here and there by default. But since TinyMCE uses paragraphs by - * default we want to change that behavior. - */ - function setDefaultBlockType() { - if (settings.forced_root_block) { - editor.on('init', function() { - setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block); - }); - } - } - - /** - * Removes ghost selections from images/tables on Gecko. - */ - function removeGhostSelection() { - editor.on('Undo Redo SetContent', function(e) { - if (!e.initial) { - editor.execCommand('mceRepaint'); - } - }); - } - - /** - * Deletes the selected image on IE instead of navigating to previous page. - */ - function deleteControlItemOnBackSpace() { - editor.on('keydown', function(e) { - var rng; - - if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) { - rng = editor.getDoc().selection.createRange(); - if (rng && rng.item) { - e.preventDefault(); - editor.undoManager.beforeChange(); - dom.remove(rng.item(0)); - editor.undoManager.add(); - } - } - }); - } - - /** - * IE10 doesn't properly render block elements with the right height until you add contents to them. - * This fixes that by adding a padding-right to all empty text block elements. - * See: https://connect.microsoft.com/IE/feedback/details/743881 - */ - function renderEmptyBlocksFix() { - var emptyBlocksCSS; - - // IE10+ - if (getDocumentMode() >= 10) { - emptyBlocksCSS = ''; - each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) { - emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty'; - }); - - editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}'); - } - } - - /** - * Old IE versions can't retain contents within noscript elements so this logic will store the contents - * as a attribute and the insert that value as it's raw text when the DOM is serialized. - */ - function keepNoScriptContents() { - if (getDocumentMode() < 9) { - parser.addNodeFilter('noscript', function(nodes) { - var i = nodes.length, node, textNode; - - while (i--) { - node = nodes[i]; - textNode = node.firstChild; - - if (textNode) { - node.attr('data-mce-innertext', textNode.value); - } - } - }); - - serializer.addNodeFilter('noscript', function(nodes) { - var i = nodes.length, node, textNode, value; - - while (i--) { - node = nodes[i]; - textNode = nodes[i].firstChild; - - if (textNode) { - textNode.value = Entities.decode(textNode.value); - } else { - // Old IE can't retain noscript value so an attribute is used to store it - value = node.attributes.map['data-mce-innertext']; - if (value) { - node.attr('data-mce-innertext', null); - textNode = new Node('#text', 3); - textNode.value = value; - textNode.raw = true; - node.append(textNode); - } - } - } - }); - } - } - - /** - * IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode. - */ - function fixCaretSelectionOfDocumentElementOnIe() { - var doc = dom.doc, body = doc.body, started, startRng, htmlElm; - - // Return range from point or null if it failed - function rngFromPoint(x, y) { - var rng = body.createTextRange(); - - try { - rng.moveToPoint(x, y); - } catch (ex) { - // IE sometimes throws and exception, so lets just ignore it - rng = null; - } - - return rng; - } - - // Fires while the selection is changing - function selectionChange(e) { - var pointRng; - - // Check if the button is down or not - if (e.button) { - // Create range from mouse position - pointRng = rngFromPoint(e.x, e.y); - - if (pointRng) { - // Check if pointRange is before/after selection then change the endPoint - if (pointRng.compareEndPoints('StartToStart', startRng) > 0) { - pointRng.setEndPoint('StartToStart', startRng); - } else { - pointRng.setEndPoint('EndToEnd', startRng); - } - - pointRng.select(); - } - } else { - endSelection(); - } - } - - // Removes listeners - function endSelection() { - var rng = doc.selection.createRange(); - - // If the range is collapsed then use the last start range - if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) { - startRng.select(); - } - - dom.unbind(doc, 'mouseup', endSelection); - dom.unbind(doc, 'mousemove', selectionChange); - startRng = started = 0; - } - - // Make HTML element unselectable since we are going to handle selection by hand - doc.documentElement.unselectable = true; - - // Detect when user selects outside BODY - dom.bind(doc, 'mousedown contextmenu', function(e) { - if (e.target.nodeName === 'HTML') { - if (started) { - endSelection(); - } - - // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML - htmlElm = doc.documentElement; - if (htmlElm.scrollHeight > htmlElm.clientHeight) { - return; - } - - started = 1; - // Setup start position - startRng = rngFromPoint(e.x, e.y); - if (startRng) { - // Listen for selection change events - dom.bind(doc, 'mouseup', endSelection); - dom.bind(doc, 'mousemove', selectionChange); - - dom.getRoot().focus(); - startRng.select(); - } - } - }); - } - - /** - * Fixes selection issues where the caret can be placed between two inline elements like a|b - * this fix will lean the caret right into the closest inline element. - */ - function normalizeSelection() { - // Normalize selection for example a|a becomes a|a except for Ctrl+A since it selects everything - editor.on('keyup focusin mouseup', function(e) { - if (e.keyCode != 65 || !VK.metaKeyPressed(e)) { - selection.normalize(); - } - }, true); - } - - /** - * Forces Gecko to render a broken image icon if it fails to load an image. - */ - function showBrokenImageIcon() { - editor.contentStyles.push( - 'img:-moz-broken {' + - '-moz-force-broken-image-icon:1;' + - 'min-width:24px;' + - 'min-height:24px' + - '}' - ); - } - - /** - * iOS has a bug where it's impossible to type if the document has a touchstart event - * bound and the user touches the document while having the on screen keyboard visible. - * - * The touch event moves the focus to the parent document while having the caret inside the iframe - * this fix moves the focus back into the iframe document. - */ - function restoreFocusOnKeyDown() { - if (!editor.inline) { - editor.on('keydown', function() { - if (document.activeElement == document.body) { - editor.getWin().focus(); - } - }); - } - } - - /** - * IE 11 has an annoying issue where you can't move focus into the editor - * by clicking on the white area HTML element. We used to be able to to fix this with - * the fixCaretSelectionOfDocumentElementOnIe fix. But since M$ removed the selection - * object it's not possible anymore. So we need to hack in a ungly CSS to force the - * body to be at least 150px. If the user clicks the HTML element out side this 150px region - * we simply move the focus into the first paragraph. Not ideal since you loose the - * positioning of the caret but goot enough for most cases. - */ - function bodyHeight() { - if (!editor.inline) { - editor.contentStyles.push('body {min-height: 150px}'); - editor.on('click', function(e) { - if (e.target.nodeName == 'HTML') { - var rng; - - // Need to store away non collapsed ranges since the focus call will mess that up see #7382 - rng = editor.selection.getRng(); - editor.getBody().focus(); - editor.selection.setRng(rng); - editor.selection.normalize(); - editor.nodeChanged(); - } - }); - } - } - - /** - * Firefox on Mac OS will move the browser back to the previous page if you press CMD+Left arrow. - * You might then loose all your work so we need to block that behavior and replace it with our own. - */ - function blockCmdArrowNavigation() { - if (Env.mac) { - editor.on('keydown', function(e) { - if (VK.metaKeyPressed(e) && (e.keyCode == 37 || e.keyCode == 39)) { - e.preventDefault(); - editor.selection.getSel().modify('move', e.keyCode == 37 ? 'backward' : 'forward', 'lineboundary'); - } - }); - } - } - - /** - * Disables the autolinking in IE 9+ this is then re-enabled by the autolink plugin. - */ - function disableAutoUrlDetect() { - setEditorCommandState("AutoUrlDetect", false); - } - - /** - * IE 11 has a fantastic bug where it will produce two trailing BR elements to iframe bodies when - * the iframe is hidden by display: none on a parent container. The DOM is actually out of sync - * with innerHTML in this case. It's like IE adds shadow DOM BR elements that appears on innerHTML - * but not as the lastChild of the body. However is we add a BR element to the body then remove it - * it doesn't seem to add these BR elements makes sence right?! - * - * Example of what happens: text becomes textnew
'); - */ - 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 + ''; - - // 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 += ']*>( | |\s|\u00a0|)<\/p>[\r\n]*|
[\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('some html');
- *
- * // Sets the raw contents of the activeEditor editor
- * tinymce.activeEditor.setContent('some 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 ? '' : '
';
- 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 = '
';
- }
-
- 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;
-});
-
-// Included from: js/tinymce/classes/util/I18n.js
-
-/**
- * I18n.js
- *
- * Copyright, Moxiecode Systems AB
- * Released under LGPL License.
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
-
-/**
- * I18n class that handles translation of TinyMCE UI.
- * Uses po style with csharp style parameters.
- *
- * @class tinymce.util.I18n
- */
-define("tinymce/util/I18n", [], function() {
- "use strict";
-
- var data = {}, code = "en";
-
- return {
- /**
- * Sets the current language code.
- *
- * @method setCode
- * @param {String} newCode Current language code.
- */
- setCode: function(newCode) {
- if (newCode) {
- code = newCode;
- this.rtl = this.data[newCode] ? this.data[newCode]._dir === 'rtl' : false;
- }
- },
-
- /**
- * Returns the current language code.
- *
- * @return {String} Current language code.
- */
- getCode: function() {
- return code;
- },
-
- /**
- * Property gets set to true if a RTL language pack was loaded.
- *
- * @property rtl
- * @type Boolean
- */
- rtl: false,
-
- /**
- * Adds translations for a specific language code.
- *
- * @method add
- * @param {String} code Language code like sv_SE.
- * @param {Array} items Name/value array with English en_US to sv_SE.
- */
- add: function(code, items) {
- var langData = data[code];
-
- if (!langData) {
- data[code] = langData = {};
- }
-
- for (var name in items) {
- langData[name] = items[name];
- }
-
- this.setCode(code);
- },
-
- /**
- * Translates the specified text.
- *
- * It has a few formats:
- * I18n.translate("Text");
- * I18n.translate(["Text {0}/{1}", 0, 1]);
- * I18n.translate({raw: "Raw string"});
- *
- * @method translate
- * @param {String/Object/Array} text Text to translate.
- * @return {String} String that got translated.
- */
- translate: function(text) {
- var langData;
-
- langData = data[code];
- if (!langData) {
- langData = {};
- }
-
- if (typeof text == "undefined") {
- return text;
- }
-
- if (typeof text != "string" && text.raw) {
- return text.raw;
- }
-
- if (text.push) {
- var values = text.slice(1);
-
- text = (langData[text[0]] || text[0]).replace(/\{([0-9]+)\}/g, function(match1, match2) {
- return values[match2];
- });
- }
-
- return (langData[text] || text).replace(/{context:\w+}$/, '');
- },
-
- data: data
- };
-});
-
-// Included from: js/tinymce/classes/FocusManager.js
-
-/**
- * FocusManager.js
- *
- * Copyright, Moxiecode Systems AB
- * Released under LGPL License.
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
-
-/**
- * This class manages the focus/blur state of the editor. This class is needed since some
- * browsers fire false focus/blur states when the selection is moved to a UI dialog or similar.
- *
- * This class will fire two events focus and blur on the editor instances that got affected.
- * It will also handle the restore of selection when the focus is lost and returned.
- *
- * @class tinymce.FocusManager
- */
-define("tinymce/FocusManager", [
- "tinymce/dom/DOMUtils",
- "tinymce/Env"
-], function(DOMUtils, Env) {
- var selectionChangeHandler, documentFocusInHandler, documentMouseUpHandler, DOM = DOMUtils.DOM;
-
- /**
- * Constructs a new focus manager instance.
- *
- * @constructor FocusManager
- * @param {tinymce.EditorManager} editorManager Editor manager instance to handle focus for.
- */
- function FocusManager(editorManager) {
- function getActiveElement() {
- try {
- return document.activeElement;
- } catch (ex) {
- // IE sometimes fails to get the activeElement when resizing table
- // TODO: Investigate this
- return document.body;
- }
- }
-
- // We can't store a real range on IE 11 since it gets mutated so we need to use a bookmark object
- // TODO: Move this to a separate range utils class since it's it's logic is present in Selection as well.
- function createBookmark(dom, rng) {
- if (rng && rng.startContainer) {
- // Verify that the range is within the root of the editor
- if (!dom.isChildOf(rng.startContainer, dom.getRoot()) || !dom.isChildOf(rng.endContainer, dom.getRoot())) {
- return;
- }
-
- return {
- startContainer: rng.startContainer,
- startOffset: rng.startOffset,
- endContainer: rng.endContainer,
- endOffset: rng.endOffset
- };
- }
-
- return rng;
- }
-
- function bookmarkToRng(editor, bookmark) {
- var rng;
-
- if (bookmark.startContainer) {
- rng = editor.getDoc().createRange();
- rng.setStart(bookmark.startContainer, bookmark.startOffset);
- rng.setEnd(bookmark.endContainer, bookmark.endOffset);
- } else {
- rng = bookmark;
- }
-
- return rng;
- }
-
- function isUIElement(elm) {
- return !!DOM.getParent(elm, FocusManager.isEditorUIElement);
- }
-
- function registerEvents(e) {
- var editor = e.editor;
-
- editor.on('init', function() {
- // Gecko/WebKit has ghost selections in iframes and IE only has one selection per browser tab
- if (editor.inline || Env.ie) {
- // Use the onbeforedeactivate event when available since it works better see #7023
- if ("onbeforedeactivate" in document && Env.ie < 9) {
- editor.dom.bind(editor.getBody(), 'beforedeactivate', function(e) {
- if (e.target != editor.getBody()) {
- return;
- }
-
- try {
- editor.lastRng = editor.selection.getRng();
- } catch (ex) {
- // IE throws "Unexcpected call to method or property access" some times so lets ignore it
- }
- });
- } else {
- // On other browsers take snapshot on nodechange in inline mode since they have Ghost selections for iframes
- editor.on('nodechange mouseup keyup', function(e) {
- var node = getActiveElement();
-
- // Only act on manual nodechanges
- if (e.type == 'nodechange' && e.selectionChange) {
- return;
- }
-
- // IE 11 reports active element as iframe not body of iframe
- if (node && node.id == editor.id + '_ifr') {
- node = editor.getBody();
- }
-
- if (editor.dom.isChildOf(node, editor.getBody())) {
- editor.lastRng = editor.selection.getRng();
- }
- });
- }
-
- // Handles the issue with WebKit not retaining selection within inline document
- // If the user releases the mouse out side the body since a mouse up event wont occur on the body
- if (Env.webkit && !selectionChangeHandler) {
- selectionChangeHandler = function() {
- var activeEditor = editorManager.activeEditor;
-
- if (activeEditor && activeEditor.selection) {
- var rng = activeEditor.selection.getRng();
-
- // Store when it's non collapsed
- if (rng && !rng.collapsed) {
- editor.lastRng = rng;
- }
- }
- };
-
- DOM.bind(document, 'selectionchange', selectionChangeHandler);
- }
- }
- });
-
- editor.on('setcontent', function() {
- editor.lastRng = null;
- });
-
- // Remove last selection bookmark on mousedown see #6305
- editor.on('mousedown', function() {
- editor.selection.lastFocusBookmark = null;
- });
-
- editor.on('focusin', function() {
- var focusedEditor = editorManager.focusedEditor;
-
- if (editor.selection.lastFocusBookmark) {
- editor.selection.setRng(bookmarkToRng(editor, editor.selection.lastFocusBookmark));
- editor.selection.lastFocusBookmark = null;
- }
-
- if (focusedEditor != editor) {
- if (focusedEditor) {
- focusedEditor.fire('blur', {focusedEditor: editor});
- }
-
- editorManager.setActive(editor);
- editorManager.focusedEditor = editor;
- editor.fire('focus', {blurredEditor: focusedEditor});
- editor.focus(true);
- }
-
- editor.lastRng = null;
- });
-
- editor.on('focusout', function() {
- window.setTimeout(function() {
- var focusedEditor = editorManager.focusedEditor;
-
- // Still the same editor the the blur was outside any editor UI
- if (!isUIElement(getActiveElement()) && focusedEditor == editor) {
- editor.fire('blur', {focusedEditor: null});
- editorManager.focusedEditor = null;
-
- // Make sure selection is valid could be invalid if the editor is blured and removed before the timeout occurs
- if (editor.selection) {
- editor.selection.lastFocusBookmark = null;
- }
- }
- }, 0);
- });
-
- // Check if focus is moved to an element outside the active editor by checking if the target node
- // isn't within the body of the activeEditor nor a UI element such as a dialog child control
- if (!documentFocusInHandler) {
- documentFocusInHandler = function(e) {
- var activeEditor = editorManager.activeEditor;
-
- if (activeEditor && e.target.ownerDocument == document) {
- // Check to make sure we have a valid selection don't update the bookmark if it's
- // a focusin to the body of the editor see #7025
- if (activeEditor.selection && e.target != activeEditor.getBody()) {
- activeEditor.selection.lastFocusBookmark = createBookmark(activeEditor.dom, activeEditor.lastRng);
- }
-
- // Fire a blur event if the element isn't a UI element
- if (e.target != document.body && !isUIElement(e.target) && editorManager.focusedEditor == activeEditor) {
- activeEditor.fire('blur', {focusedEditor: null});
- editorManager.focusedEditor = null;
- }
- }
- };
-
- DOM.bind(document, 'focusin', documentFocusInHandler);
- }
-
- // Handle edge case when user starts the selection inside the editor and releases
- // the mouse outside the editor producing a new selection. This weird workaround is needed since
- // Gecko doesn't have the "selectionchange" event we need to do this. Fixes: #6843
- if (editor.inline && !documentMouseUpHandler) {
- documentMouseUpHandler = function(e) {
- var activeEditor = editorManager.activeEditor;
-
- if (activeEditor.inline && !activeEditor.dom.isChildOf(e.target, activeEditor.getBody())) {
- var rng = activeEditor.selection.getRng();
-
- if (!rng.collapsed) {
- activeEditor.lastRng = rng;
- }
- }
- };
-
- DOM.bind(document, 'mouseup', documentMouseUpHandler);
- }
- }
-
- function unregisterDocumentEvents(e) {
- if (editorManager.focusedEditor == e.editor) {
- editorManager.focusedEditor = null;
- }
-
- if (!editorManager.activeEditor) {
- DOM.unbind(document, 'selectionchange', selectionChangeHandler);
- DOM.unbind(document, 'focusin', documentFocusInHandler);
- DOM.unbind(document, 'mouseup', documentMouseUpHandler);
- selectionChangeHandler = documentFocusInHandler = documentMouseUpHandler = null;
- }
- }
-
- editorManager.on('AddEditor', registerEvents);
- editorManager.on('RemoveEditor', unregisterDocumentEvents);
- }
-
- /**
- * Returns true if the specified element is part of the UI for example an button or text input.
- *
- * @method isEditorUIElement
- * @param {Element} elm Element to check if it's part of the UI or not.
- * @return {Boolean} True/false state if the element is part of the UI or not.
- */
- FocusManager.isEditorUIElement = function(elm) {
- // Needs to be converted to string since svg can have focus: #6776
- return elm.className.toString().indexOf('mce-') !== -1;
- };
-
- return FocusManager;
-});
-
-// Included from: js/tinymce/classes/EditorManager.js
-
-/**
- * EditorManager.js
- *
- * Copyright, Moxiecode Systems AB
- * Released under LGPL License.
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
-
-/**
- * This class used as a factory for manager for tinymce.Editor instances.
- *
- * @example
- * tinymce.EditorManager.init({});
- *
- * @class tinymce.EditorManager
- * @mixes tinymce.util.Observable
- * @static
- */
-define("tinymce/EditorManager", [
- "tinymce/Editor",
- "tinymce/dom/DomQuery",
- "tinymce/dom/DOMUtils",
- "tinymce/util/URI",
- "tinymce/Env",
- "tinymce/util/Tools",
- "tinymce/util/Observable",
- "tinymce/util/I18n",
- "tinymce/FocusManager"
-], function(Editor, DomQuery, DOMUtils, URI, Env, Tools, Observable, I18n, FocusManager) {
- var DOM = DOMUtils.DOM;
- var explode = Tools.explode, each = Tools.each, extend = Tools.extend;
- var instanceCounter = 0, beforeUnloadDelegate, EditorManager;
-
- function removeEditorFromList(editor) {
- var editors = EditorManager.editors, removedFromList;
-
- delete editors[editor.id];
-
- for (var i = 0; i < editors.length; i++) {
- if (editors[i] == editor) {
- editors.splice(i, 1);
- removedFromList = true;
- break;
- }
- }
-
- // Select another editor since the active one was removed
- if (EditorManager.activeEditor == editor) {
- EditorManager.activeEditor = editors[0];
- }
-
- // Clear focusedEditor if necessary, so that we don't try to blur the destroyed editor
- if (EditorManager.focusedEditor == editor) {
- EditorManager.focusedEditor = null;
- }
-
- return removedFromList;
- }
-
- function purgeDestroyedEditor(editor) {
- // User has manually destroyed the editor lets clean up the mess
- if (editor && !(editor.getContainer() || editor.getBody()).parentNode) {
- removeEditorFromList(editor);
- editor.unbindAllNativeEvents();
- editor.destroy(true);
- editor = null;
- }
-
- return editor;
- }
-
- EditorManager = {
- /**
- * Dom query instance.
- *
- * @property $
- * @type tinymce.dom.DomQuery
- */
- $: DomQuery,
-
- /**
- * Major version of TinyMCE build.
- *
- * @property majorVersion
- * @type String
- */
- majorVersion: '4',
-
- /**
- * Minor version of TinyMCE build.
- *
- * @property minorVersion
- * @type String
- */
- minorVersion: '1.10',
-
- /**
- * Release date of TinyMCE build.
- *
- * @property releaseDate
- * @type String
- */
- releaseDate: '2015-05-05',
-
- /**
- * Collection of editor instances.
- *
- * @property editors
- * @type Object
- * @example
- * for (edId in tinymce.editors)
- * tinymce.editors[edId].save();
- */
- editors: [],
-
- /**
- * Collection of language pack data.
- *
- * @property i18n
- * @type Object
- */
- i18n: I18n,
-
- /**
- * Currently active editor instance.
- *
- * @property activeEditor
- * @type tinymce.Editor
- * @example
- * tinyMCE.activeEditor.selection.getContent();
- * tinymce.EditorManager.activeEditor.selection.getContent();
- */
- activeEditor: null,
-
- setup: function() {
- var self = this, baseURL, documentBaseURL, suffix = "", preInit, src;
-
- // Get base URL for the current document
- documentBaseURL = document.location.href;
-
- // Check if the URL is a document based format like: http://site/dir/file and file:///
- // leave other formats like applewebdata://... intact
- if (/^[^:]+:\/\/\/?[^\/]+\//.test(documentBaseURL)) {
- documentBaseURL = documentBaseURL.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
-
- if (!/[\/\\]$/.test(documentBaseURL)) {
- documentBaseURL += '/';
- }
- }
-
- // If tinymce is defined and has a base use that or use the old tinyMCEPreInit
- preInit = window.tinymce || window.tinyMCEPreInit;
- if (preInit) {
- baseURL = preInit.base || preInit.baseURL;
- suffix = preInit.suffix;
- } else {
- // Get base where the tinymce script is located
- var scripts = document.getElementsByTagName('script');
- for (var i = 0; i < scripts.length; i++) {
- src = scripts[i].src;
-
- // Script types supported:
- // tinymce.js tinymce.min.js tinymce.dev.js
- // tinymce.jquery.js tinymce.jquery.min.js tinymce.jquery.dev.js
- // tinymce.full.js tinymce.full.min.js tinymce.full.dev.js
- if (/tinymce(\.full|\.jquery|)(\.min|\.dev|)\.js/.test(src)) {
- if (src.indexOf('.min') != -1) {
- suffix = '.min';
- }
-
- baseURL = src.substring(0, src.lastIndexOf('/'));
- break;
- }
- }
-
- // We didn't find any baseURL by looking at the script elements
- // Try to use the document.currentScript as a fallback
- if (!baseURL && document.currentScript) {
- src = document.currentScript.src;
-
- if (src.indexOf('.min') != -1) {
- suffix = '.min';
- }
-
- baseURL = src.substring(0, src.lastIndexOf('/'));
- }
- }
-
- /**
- * Base URL where the root directory if TinyMCE is located.
- *
- * @property baseURL
- * @type String
- */
- self.baseURL = new URI(documentBaseURL).toAbsolute(baseURL);
-
- /**
- * Document base URL where the current document is located.
- *
- * @property documentBaseURL
- * @type String
- */
- self.documentBaseURL = documentBaseURL;
-
- /**
- * Absolute baseURI for the installation path of TinyMCE.
- *
- * @property baseURI
- * @type tinymce.util.URI
- */
- self.baseURI = new URI(self.baseURL);
-
- /**
- * Current suffix to add to each plugin/theme that gets loaded for example ".min".
- *
- * @property suffix
- * @type String
- */
- self.suffix = suffix;
-
- self.focusManager = new FocusManager(self);
- },
-
- /**
- * Initializes a set of editors. This method will create editors based on various settings.
- *
- * @method init
- * @param {Object} settings Settings object to be passed to each editor instance.
- * @example
- * // Initializes a editor using the longer method
- * tinymce.EditorManager.init({
- * some_settings : 'some value'
- * });
- *
- * // Initializes a editor instance using the shorter version
- * tinyMCE.init({
- * some_settings : 'some value'
- * });
- */
- init: function(settings) {
- var self = this, editors = [];
-
- function createId(elm) {
- var id = elm.id;
-
- // Use element id, or unique name or generate a unique id
- if (!id) {
- id = elm.name;
-
- if (id && !DOM.get(id)) {
- id = elm.name;
- } else {
- // Generate unique name
- id = DOM.uniqueId();
- }
-
- elm.setAttribute('id', id);
- }
-
- return id;
- }
-
- function createEditor(id, settings, targetElm) {
- if (!purgeDestroyedEditor(self.get(id))) {
- var editor = new Editor(id, settings, self);
-
- editor.targetElm = editor.targetElm || targetElm;
- editors.push(editor);
- editor.render();
- }
- }
-
- function execCallback(name) {
- var callback = settings[name];
-
- if (!callback) {
- return;
- }
-
- return callback.apply(self, Array.prototype.slice.call(arguments, 2));
- }
-
- function hasClass(elm, className) {
- return className.constructor === RegExp ? className.test(elm.className) : DOM.hasClass(elm, className);
- }
-
- function readyHandler() {
- var l, co;
-
- DOM.unbind(window, 'ready', readyHandler);
-
- execCallback('onpageload');
-
- if (settings.types) {
- // Process type specific selector
- each(settings.types, function(type) {
- each(DOM.select(type.selector), function(elm) {
- createEditor(createId(elm), extend({}, settings, type), elm);
- });
- });
-
- return;
- } else if (settings.selector) {
- // Process global selector
- each(DOM.select(settings.selector), function(elm) {
- createEditor(createId(elm), settings, elm);
- });
-
- return;
- } else if (settings.target) {
- createEditor(createId(settings.target), settings);
- }
-
- // Fallback to old setting
- switch (settings.mode) {
- case "exact":
- l = settings.elements || '';
-
- if (l.length > 0) {
- each(explode(l), function(id) {
- var elm;
-
- if ((elm = DOM.get(id))) {
- createEditor(id, settings, elm);
- } else {
- each(document.forms, function(f) {
- each(f.elements, function(e) {
- if (e.name === id) {
- id = 'mce_editor_' + instanceCounter++;
- DOM.setAttrib(e, 'id', id);
- createEditor(id, settings, e);
- }
- });
- });
- }
- });
- }
- break;
-
- case "textareas":
- case "specific_textareas":
- each(DOM.select('textarea'), function(elm) {
- if (settings.editor_deselector && hasClass(elm, settings.editor_deselector)) {
- return;
- }
-
- if (!settings.editor_selector || hasClass(elm, settings.editor_selector)) {
- createEditor(createId(elm), settings, elm);
- }
- });
- break;
- }
-
- // Call onInit when all editors are initialized
- if (settings.oninit) {
- l = co = 0;
-
- each(editors, function(ed) {
- co++;
-
- if (!ed.initialized) {
- // Wait for it
- ed.on('init', function() {
- l++;
-
- // All done
- if (l == co) {
- execCallback('oninit');
- }
- });
- } else {
- l++;
- }
-
- // All done
- if (l == co) {
- execCallback('oninit');
- }
- });
- }
- }
-
- self.settings = settings;
-
- DOM.bind(window, 'ready', readyHandler);
- },
-
- /**
- * Returns a editor instance by id.
- *
- * @method get
- * @param {String/Number} id Editor instance id or index to return.
- * @return {tinymce.Editor} Editor instance to return.
- * @example
- * // Adds an onclick event to an editor by id (shorter version)
- * tinymce.get('mytextbox').on('click', function(e) {
- * ed.windowManager.alert('Hello world!');
- * });
- *
- * // Adds an onclick event to an editor by id (longer version)
- * tinymce.EditorManager.get('mytextbox').on('click', function(e) {
- * ed.windowManager.alert('Hello world!');
- * });
- */
- get: function(id) {
- if (!arguments.length) {
- return this.editors;
- }
-
- return id in this.editors ? this.editors[id] : null;
- },
-
- /**
- * Adds an editor instance to the editor collection. This will also set it as the active editor.
- *
- * @method add
- * @param {tinymce.Editor} editor Editor instance to add to the collection.
- * @return {tinymce.Editor} The same instance that got passed in.
- */
- add: function(editor) {
- var self = this, editors = self.editors;
-
- // Add named and index editor instance
- editors[editor.id] = editor;
- editors.push(editor);
-
- // Doesn't call setActive method since we don't want
- // to fire a bunch of activate/deactivate calls while initializing
- self.activeEditor = editor;
-
- /**
- * Fires when an editor is added to the EditorManager collection.
- *
- * @event AddEditor
- * @param {Object} e Event arguments.
- */
- self.fire('AddEditor', {editor: editor});
-
- if (!beforeUnloadDelegate) {
- beforeUnloadDelegate = function() {
- self.fire('BeforeUnload');
- };
-
- DOM.bind(window, 'beforeunload', beforeUnloadDelegate);
- }
-
- return editor;
- },
-
- /**
- * Creates an editor instance and adds it to the EditorManager collection.
- *
- * @method createEditor
- * @param {String} id Instance id to use for editor.
- * @param {Object} settings Editor instance settings.
- * @return {tinymce.Editor} Editor instance that got created.
- */
- createEditor: function(id, settings) {
- return this.add(new Editor(id, settings, this));
- },
-
- /**
- * Removes a editor or editors form page.
- *
- * @example
- * // Remove all editors bound to divs
- * tinymce.remove('div');
- *
- * // Remove all editors bound to textareas
- * tinymce.remove('textarea');
- *
- * // Remove all editors
- * tinymce.remove();
- *
- * // Remove specific instance by id
- * tinymce.remove('#id');
- *
- * @method remove
- * @param {tinymce.Editor/String/Object} [selector] CSS selector or editor instance to remove.
- * @return {tinymce.Editor} The editor that got passed in will be return if it was found otherwise null.
- */
- remove: function(selector) {
- var self = this, i, editors = self.editors, editor;
-
- // Remove all editors
- if (!selector) {
- for (i = editors.length - 1; i >= 0; i--) {
- self.remove(editors[i]);
- }
-
- return;
- }
-
- // Remove editors by selector
- if (typeof selector == "string") {
- selector = selector.selector || selector;
-
- each(DOM.select(selector), function(elm) {
- editor = editors[elm.id];
-
- if (editor) {
- self.remove(editor);
- }
- });
-
- return;
- }
-
- // Remove specific editor
- editor = selector;
-
- // Not in the collection
- if (!editors[editor.id]) {
- return null;
- }
-
- /**
- * Fires when an editor is removed from EditorManager collection.
- *
- * @event RemoveEditor
- * @param {Object} e Event arguments.
- */
- if (removeEditorFromList(editor)) {
- self.fire('RemoveEditor', {editor: editor});
- }
-
- if (!editors.length) {
- DOM.unbind(window, 'beforeunload', beforeUnloadDelegate);
- }
-
- editor.remove();
-
- return editor;
- },
-
- /**
- * Executes a specific command on the currently active editor.
- *
- * @method execCommand
- * @param {String} c Command to perform for example Bold.
- * @param {Boolean} u Optional boolean state if a UI should be presented for the command or not.
- * @param {String} v Optional value parameter like for example an URL to a link.
- * @return {Boolean} true/false if the command was executed or not.
- */
- execCommand: function(cmd, ui, value) {
- var self = this, editor = self.get(value);
-
- // Manager commands
- switch (cmd) {
- case "mceAddEditor":
- if (!self.get(value)) {
- new Editor(value, self.settings, self).render();
- }
-
- return true;
-
- case "mceRemoveEditor":
- if (editor) {
- editor.remove();
- }
-
- return true;
-
- case 'mceToggleEditor':
- if (!editor) {
- self.execCommand('mceAddEditor', 0, value);
- return true;
- }
-
- if (editor.isHidden()) {
- editor.show();
- } else {
- editor.hide();
- }
-
- return true;
- }
-
- // Run command on active editor
- if (self.activeEditor) {
- return self.activeEditor.execCommand(cmd, ui, value);
- }
-
- return false;
- },
-
- /**
- * Calls the save method on all editor instances in the collection. This can be useful when a form is to be submitted.
- *
- * @method triggerSave
- * @example
- * // Saves all contents
- * tinyMCE.triggerSave();
- */
- triggerSave: function() {
- each(this.editors, function(editor) {
- editor.save();
- });
- },
-
- /**
- * Adds a language pack, this gets called by the loaded language files like en.js.
- *
- * @method addI18n
- * @param {String} code Optional language code.
- * @param {Object} items Name/value object with translations.
- */
- addI18n: function(code, items) {
- I18n.add(code, items);
- },
-
- /**
- * Translates the specified string using the language pack items.
- *
- * @method translate
- * @param {String/Array/Object} text String to translate
- * @return {String} Translated string.
- */
- translate: function(text) {
- return I18n.translate(text);
- },
-
- /**
- * Sets the active editor instance and fires the deactivate/activate events.
- *
- * @method setActive
- * @param {tinymce.Editor} editor Editor instance to set as the active instance.
- */
- setActive: function(editor) {
- var activeEditor = this.activeEditor;
-
- if (this.activeEditor != editor) {
- if (activeEditor) {
- activeEditor.fire('deactivate', {relatedTarget: editor});
- }
-
- editor.fire('activate', {relatedTarget: activeEditor});
- }
-
- this.activeEditor = editor;
- }
- };
-
- extend(EditorManager, Observable);
-
- EditorManager.setup();
-
- // Export EditorManager as tinymce/tinymce in global namespace
- window.tinymce = window.tinyMCE = EditorManager;
-
- return EditorManager;
-});
-
-// Included from: js/tinymce/classes/LegacyInput.js
-
-/**
- * LegacyInput.js
- *
- * Copyright, Moxiecode Systems AB
- * Released under LGPL License.
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
-
-define("tinymce/LegacyInput", [
- "tinymce/EditorManager",
- "tinymce/util/Tools"
-], function(EditorManager, Tools) {
- var each = Tools.each, explode = Tools.explode;
-
- EditorManager.on('AddEditor', function(e) {
- var editor = e.editor;
-
- editor.on('preInit', function() {
- var filters, fontSizes, dom, settings = editor.settings;
-
- function replaceWithSpan(node, styles) {
- each(styles, function(value, name) {
- if (value) {
- dom.setStyle(node, name, value);
- }
- });
-
- dom.rename(node, 'span');
- }
-
- function convert(e) {
- dom = editor.dom;
-
- if (settings.convert_fonts_to_spans) {
- each(dom.select('font,u,strike', e.node), function(node) {
- filters[node.nodeName.toLowerCase()](dom, node);
- });
- }
- }
-
- if (settings.inline_styles) {
- fontSizes = explode(settings.font_size_legacy_values);
-
- filters = {
- font: function(dom, node) {
- replaceWithSpan(node, {
- backgroundColor: node.style.backgroundColor,
- color: node.color,
- fontFamily: node.face,
- fontSize: fontSizes[parseInt(node.size, 10) - 1]
- });
- },
-
- u: function(dom, node) {
- // HTML5 allows U element
- if (editor.settings.schema === "html4") {
- replaceWithSpan(node, {
- textDecoration: 'underline'
- });
- }
- },
-
- strike: function(dom, node) {
- replaceWithSpan(node, {
- textDecoration: 'line-through'
- });
- }
- };
-
- editor.on('PreProcess SetContent', convert);
- }
- });
- });
-});
-
-// Included from: js/tinymce/classes/util/XHR.js
-
-/**
- * XHR.js
- *
- * Copyright, Moxiecode Systems AB
- * Released under LGPL License.
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
-
-/**
- * This class enables you to send XMLHTTPRequests cross browser.
- * @class tinymce.util.XHR
- * @mixes tinymce.util.Observable
- * @static
- * @example
- * // Sends a low level Ajax request
- * tinymce.util.XHR.send({
- * url: 'someurl',
- * success: function(text) {
- * console.debug(text);
- * }
- * });
- *
- * // Add custom header to XHR request
- * tinymce.util.XHR.on('beforeSend', function(e) {
- * e.xhr.setRequestHeader('X-Requested-With', 'Something');
- * });
- */
-define("tinymce/util/XHR", [
- "tinymce/util/Observable",
- "tinymce/util/Tools"
-], function(Observable, Tools) {
- var XHR = {
- /**
- * Sends a XMLHTTPRequest.
- * Consult the Wiki for details on what settings this method takes.
- *
- * @method send
- * @param {Object} settings Object will target URL, callbacks and other info needed to make the request.
- */
- send: function(settings) {
- var xhr, count = 0;
-
- function ready() {
- if (!settings.async || xhr.readyState == 4 || count++ > 10000) {
- if (settings.success && count < 10000 && xhr.status == 200) {
- settings.success.call(settings.success_scope, '' + xhr.responseText, xhr, settings);
- } else if (settings.error) {
- settings.error.call(settings.error_scope, count > 10000 ? 'TIMED_OUT' : 'GENERAL', xhr, settings);
- }
-
- xhr = null;
- } else {
- setTimeout(ready, 10);
- }
- }
-
- // Default settings
- settings.scope = settings.scope || this;
- settings.success_scope = settings.success_scope || settings.scope;
- settings.error_scope = settings.error_scope || settings.scope;
- settings.async = settings.async === false ? false : true;
- settings.data = settings.data || '';
-
- xhr = new XMLHttpRequest();
-
- if (xhr) {
- if (xhr.overrideMimeType) {
- xhr.overrideMimeType(settings.content_type);
- }
-
- xhr.open(settings.type || (settings.data ? 'POST' : 'GET'), settings.url, settings.async);
-
- if (settings.crossDomain) {
- xhr.withCredentials = true;
- }
-
- if (settings.content_type) {
- xhr.setRequestHeader('Content-Type', settings.content_type);
- }
-
- xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
-
- xhr = XHR.fire('beforeSend', {xhr: xhr, settings: settings}).xhr;
- xhr.send(settings.data);
-
- // Syncronous request
- if (!settings.async) {
- return ready();
- }
-
- // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
- setTimeout(ready, 10);
- }
- }
- };
-
- Tools.extend(XHR, Observable);
-
- return XHR;
-});
-
-// Included from: js/tinymce/classes/util/JSON.js
-
-/**
- * JSON.js
- *
- * Copyright, Moxiecode Systems AB
- * Released under LGPL License.
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
-
-/**
- * JSON parser and serializer class.
- *
- * @class tinymce.util.JSON
- * @static
- * @example
- * // JSON parse a string into an object
- * var obj = tinymce.util.JSON.parse(somestring);
- *
- * // JSON serialize a object into an string
- * var str = tinymce.util.JSON.serialize(obj);
- */
-define("tinymce/util/JSON", [], function() {
- function serialize(o, quote) {
- var i, v, t, name;
-
- quote = quote || '"';
-
- if (o === null) {
- return 'null';
- }
-
- t = typeof o;
-
- if (t == 'string') {
- v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
-
- return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
- // Make sure single quotes never get encoded inside double quotes for JSON compatibility
- if (quote === '"' && a === "'") {
- return a;
- }
-
- i = v.indexOf(b);
-
- if (i + 1) {
- return '\\' + v.charAt(i + 1);
- }
-
- a = b.charCodeAt().toString(16);
-
- return '\\u' + '0000'.substring(a.length) + a;
- }) + quote;
- }
-
- if (t == 'object') {
- if (o.hasOwnProperty && Object.prototype.toString.call(o) === '[object Array]') {
- for (i = 0, v = '['; i < o.length; i++) {
- v += (i > 0 ? ',' : '') + serialize(o[i], quote);
- }
-
- return v + ']';
- }
-
- v = '{';
-
- for (name in o) {
- if (o.hasOwnProperty(name)) {
- v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name +
- quote + ':' + serialize(o[name], quote) : '';
- }
- }
-
- return v + '}';
- }
-
- return '' + o;
- }
-
- return {
- /**
- * Serializes the specified object as a JSON string.
- *
- * @method serialize
- * @param {Object} obj Object to serialize as a JSON string.
- * @param {String} quote Optional quote string defaults to ".
- * @return {string} JSON string serialized from input.
- */
- serialize: serialize,
-
- /**
- * Unserializes/parses the specified JSON string into a object.
- *
- * @method parse
- * @param {string} s JSON String to parse into a JavaScript object.
- * @return {Object} Object from input JSON string or undefined if it failed.
- */
- parse: function(text) {
- try {
- // Trick uglify JS
- return window[String.fromCharCode(101) + 'val']('(' + text + ')');
- } catch (ex) {
- // Ignore
- }
- }
-
- /**#@-*/
- };
-});
-
-// Included from: js/tinymce/classes/util/JSONRequest.js
-
-/**
- * JSONRequest.js
- *
- * Copyright, Moxiecode Systems AB
- * Released under LGPL License.
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
-
-/**
- * This class enables you to use JSON-RPC to call backend methods.
- *
- * @class tinymce.util.JSONRequest
- * @example
- * var json = new tinymce.util.JSONRequest({
- * url: 'somebackend.php'
- * });
- *
- * // Send RPC call 1
- * json.send({
- * method: 'someMethod1',
- * params: ['a', 'b'],
- * success: function(result) {
- * console.dir(result);
- * }
- * });
- *
- * // Send RPC call 2
- * json.send({
- * method: 'someMethod2',
- * params: ['a', 'b'],
- * success: function(result) {
- * console.dir(result);
- * }
- * });
- */
-define("tinymce/util/JSONRequest", [
- "tinymce/util/JSON",
- "tinymce/util/XHR",
- "tinymce/util/Tools"
-], function(JSON, XHR, Tools) {
- var extend = Tools.extend;
-
- function JSONRequest(settings) {
- this.settings = extend({}, settings);
- this.count = 0;
- }
-
- /**
- * Simple helper function to send a JSON-RPC request without the need to initialize an object.
- * Consult the Wiki API documentation for more details on what you can pass to this function.
- *
- * @method sendRPC
- * @static
- * @param {Object} o Call object where there are three field id, method and params this object should also contain callbacks etc.
- */
- JSONRequest.sendRPC = function(o) {
- return new JSONRequest().send(o);
- };
-
- JSONRequest.prototype = {
- /**
- * Sends a JSON-RPC call. Consult the Wiki API documentation for more details on what you can pass to this function.
- *
- * @method send
- * @param {Object} args Call object where there are three field id, method and params this object should also contain callbacks etc.
- */
- send: function(args) {
- var ecb = args.error, scb = args.success;
-
- args = extend(this.settings, args);
-
- args.success = function(c, x) {
- c = JSON.parse(c);
-
- if (typeof c == 'undefined') {
- c = {
- error: 'JSON Parse error.'
- };
- }
-
- if (c.error) {
- ecb.call(args.error_scope || args.scope, c.error, x);
- } else {
- scb.call(args.success_scope || args.scope, c.result);
- }
- };
-
- args.error = function(ty, x) {
- if (ecb) {
- ecb.call(args.error_scope || args.scope, ty, x);
- }
- };
-
- args.data = JSON.serialize({
- id: args.id || 'c' + (this.count++),
- method: args.method,
- params: args.params
- });
-
- // JSON content type for Ruby on rails. Bug: #1883287
- args.content_type = 'application/json';
-
- XHR.send(args);
- }
- };
-
- return JSONRequest;
-});
-
-// Included from: js/tinymce/classes/util/JSONP.js
-
-/**
- * JSONP.js
- *
- * Copyright, Moxiecode Systems AB
- * Released under LGPL License.
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
-
-define("tinymce/util/JSONP", [
- "tinymce/dom/DOMUtils"
-], function(DOMUtils) {
- return {
- callbacks: {},
- count: 0,
-
- send: function(settings) {
- var self = this, dom = DOMUtils.DOM, count = settings.count !== undefined ? settings.count : self.count;
- var id = 'tinymce_jsonp_' + count;
-
- self.callbacks[count] = function(json) {
- dom.remove(id);
- delete self.callbacks[count];
-
- settings.callback(json);
- };
-
- dom.add(dom.doc.body, 'script', {
- id: id,
- src: settings.url,
- type: 'text/javascript'
- });
-
- self.count++;
- }
- };
-});
-
-// Included from: js/tinymce/classes/util/LocalStorage.js
-
-/**
- * LocalStorage.js
- *
- * Copyright, Moxiecode Systems AB
- * Released under LGPL License.
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
-
-/**
- * This class will simulate LocalStorage on IE 7 and return the native version on modern browsers.
- * Storage is done using userData on IE 7 and a special serialization format. The format is designed
- * to be as small as possible by making sure that the keys and values doesn't need to be encoded. This
- * makes it possible to store for example HTML data.
- *
- * Storage format for userData:
- *