JR Utily

naive integration of tinymce as inputTextarea

Showing 472 changed files with 5038 additions and 0 deletions
/**
* AddOnManager.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* 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;
}
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;
},
remove: function(name) {
delete this.urls[name];
delete this.lookup[name];
},
createUrl: function(baseUrl, dep) {
if (typeof dep === "object") {
return dep;
}
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'
* }]
* });
* }
* });
* });
*/
/**
* Compat.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* TinyMCE core class.
*
* @static
* @class tinymce
* @borrow-members tinymce.EditorManager
* @borrow-members tinymce.util.Tools
*/
define("tinymce/Compat", [
"tinymce/dom/DOMUtils",
"tinymce/dom/EventUtils",
"tinymce/dom/ScriptLoader",
"tinymce/AddOnManager",
"tinymce/util/Tools",
"tinymce/Env"
], function(DOMUtils, EventUtils, ScriptLoader, AddOnManager, Tools, Env) {
var tinymce = window.tinymce;
/**
* @property {tinymce.dom.DOMUtils} DOM Global DOM instance.
* @property {tinymce.dom.ScriptLoader} ScriptLoader Global ScriptLoader instance.
* @property {tinymce.AddOnManager} PluginManager Global PluginManager instance.
* @property {tinymce.AddOnManager} ThemeManager Global ThemeManager instance.
*/
tinymce.DOM = DOMUtils.DOM;
tinymce.ScriptLoader = ScriptLoader.ScriptLoader;
tinymce.PluginManager = AddOnManager.PluginManager;
tinymce.ThemeManager = AddOnManager.ThemeManager;
tinymce.dom = tinymce.dom || {};
tinymce.dom.Event = EventUtils.Event;
Tools.each(Tools, function(func, key) {
tinymce[key] = func;
});
Tools.each('isOpera isWebKit isIE isGecko isMac'.split(' '), function(name) {
tinymce[name] = Env[name.substr(2).toLowerCase()];
});
return {};
});
// Describe the different namespaces
/**
* Root level namespace this contains classes directly related to the TinyMCE editor.
*
* @namespace tinymce
*/
/**
* Contains classes for handling the browsers DOM.
*
* @namespace tinymce.dom
*/
/**
* Contains html parser and serializer logic.
*
* @namespace tinymce.html
*/
/**
* Contains the different UI types such as buttons, listboxes etc.
*
* @namespace tinymce.ui
*/
/**
* Contains various utility classes such as json parser, cookies etc.
*
* @namespace tinymce.util
*/
/**
* Contains modules to handle data binding.
*
* @namespace tinymce.data
*/
/**
* DragDropOverrides.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* This module contains logic overriding the drag/drop logic of the editor.
*
* @private
* @class tinymce.DragDropOverrides
*/
define("tinymce/DragDropOverrides", [
"tinymce/dom/NodeType",
"tinymce/util/Arr",
"tinymce/util/Fun",
"tinymce/util/Delay",
"tinymce/dom/DOMUtils",
"tinymce/dom/MousePosition"
], function(
NodeType, Arr, Fun, Delay, DOMUtils, MousePosition
) {
var isContentEditableFalse = NodeType.isContentEditableFalse,
isContentEditableTrue = NodeType.isContentEditableTrue;
var isDraggable = function (elm) {
return isContentEditableFalse(elm);
};
var isValidDropTarget = function (editor, targetElement, dragElement) {
if (targetElement === dragElement || editor.dom.isChildOf(targetElement, dragElement)) {
return false;
}
if (isContentEditableFalse(targetElement)) {
return false;
}
return true;
};
var cloneElement = function (elm) {
var cloneElm = elm.cloneNode(true);
cloneElm.removeAttribute('data-mce-selected');
return cloneElm;
};
var createGhost = function (editor, elm, width, height) {
var clonedElm = elm.cloneNode(true);
editor.dom.setStyles(clonedElm, {width: width, height: height});
editor.dom.setAttrib(clonedElm, 'data-mce-selected', null);
var ghostElm = editor.dom.create('div', {
'class': 'mce-drag-container',
'data-mce-bogus': 'all',
unselectable: 'on',
contenteditable: 'false'
});
editor.dom.setStyles(ghostElm, {
position: 'absolute',
opacity: 0.5,
overflow: 'hidden',
border: 0,
padding: 0,
margin: 0,
width: width,
height: height
});
editor.dom.setStyles(clonedElm, {
margin: 0,
boxSizing: 'border-box'
});
ghostElm.appendChild(clonedElm);
return ghostElm;
};
var appendGhostToBody = function (ghostElm, bodyElm) {
if (ghostElm.parentNode !== bodyElm) {
bodyElm.appendChild(ghostElm);
}
};
var moveGhost = function (ghostElm, position, width, height, maxX, maxY) {
var overflowX = 0, overflowY = 0;
ghostElm.style.left = position.pageX + 'px';
ghostElm.style.top = position.pageY + 'px';
if (position.pageX + width > maxX) {
overflowX = (position.pageX + width) - maxX;
}
if (position.pageY + height > maxY) {
overflowY = (position.pageY + height) - maxY;
}
ghostElm.style.width = (width - overflowX) + 'px';
ghostElm.style.height = (height - overflowY) + 'px';
};
var removeElement = function (elm) {
if (elm && elm.parentNode) {
elm.parentNode.removeChild(elm);
}
};
var isLeftMouseButtonPressed = function (e) {
return e.button === 0;
};
var hasDraggableElement = function (state) {
return state.element;
};
var applyRelPos = function (state, position) {
return {
pageX: position.pageX - state.relX,
pageY: position.pageY + 5
};
};
var start = function (state, editor) {
return function (e) {
if (isLeftMouseButtonPressed(e)) {
var ceElm = Arr.find(editor.dom.getParents(e.target), Fun.or(isContentEditableFalse, isContentEditableTrue));
if (isDraggable(ceElm)) {
var elmPos = editor.dom.getPos(ceElm);
var bodyElm = editor.getBody();
var docElm = editor.getDoc().documentElement;
state.element = ceElm;
state.screenX = e.screenX;
state.screenY = e.screenY;
state.maxX = (editor.inline ? bodyElm.scrollWidth : docElm.offsetWidth) - 2;
state.maxY = (editor.inline ? bodyElm.scrollHeight : docElm.offsetHeight) - 2;
state.relX = e.pageX - elmPos.x;
state.relY = e.pageY - elmPos.y;
state.width = ceElm.offsetWidth;
state.height = ceElm.offsetHeight;
state.ghost = createGhost(editor, ceElm, state.width, state.height);
}
}
};
};
var move = function (state, editor) {
// Reduces laggy drag behavior on Gecko
var throttledPlaceCaretAt = Delay.throttle(function (clientX, clientY) {
editor._selectionOverrides.hideFakeCaret();
editor.selection.placeCaretAt(clientX, clientY);
}, 0);
return function (e) {
var movement = Math.max(Math.abs(e.screenX - state.screenX), Math.abs(e.screenY - state.screenY));
if (hasDraggableElement(state) && !state.dragging && movement > 10) {
var args = editor.fire('dragstart', {target: state.element});
if (args.isDefaultPrevented()) {
return;
}
state.dragging = true;
editor.focus();
}
if (state.dragging) {
var targetPos = applyRelPos(state, MousePosition.calc(editor, e));
appendGhostToBody(state.ghost, editor.getBody());
moveGhost(state.ghost, targetPos, state.width, state.height, state.maxX, state.maxY);
throttledPlaceCaretAt(e.clientX, e.clientY);
}
};
};
var drop = function (state, editor) {
return function (e) {
if (state.dragging) {
if (isValidDropTarget(editor, editor.selection.getNode(), state.element)) {
var targetClone = cloneElement(state.element);
var args = editor.fire('drop', {
targetClone: targetClone,
clientX: e.clientX,
clientY: e.clientY
});
if (!args.isDefaultPrevented()) {
targetClone = args.targetClone;
editor.undoManager.transact(function() {
removeElement(state.element);
editor.insertContent(editor.dom.getOuterHTML(targetClone));
editor._selectionOverrides.hideFakeCaret();
});
}
}
}
removeDragState(state);
};
};
var stop = function (state, editor) {
return function () {
removeDragState(state);
if (state.dragging) {
editor.fire('dragend');
}
};
};
var removeDragState = function (state) {
state.dragging = false;
state.element = null;
removeElement(state.ghost);
};
var bindFakeDragEvents = function (editor) {
var state = {}, pageDom, dragStartHandler, dragHandler, dropHandler, dragEndHandler, rootDocument;
pageDom = DOMUtils.DOM;
rootDocument = document;
dragStartHandler = start(state, editor);
dragHandler = move(state, editor);
dropHandler = drop(state, editor);
dragEndHandler = stop(state, editor);
editor.on('mousedown', dragStartHandler);
editor.on('mousemove', dragHandler);
editor.on('mouseup', dropHandler);
pageDom.bind(rootDocument, 'mousemove', dragHandler);
pageDom.bind(rootDocument, 'mouseup', dragEndHandler);
editor.on('remove', function () {
pageDom.unbind(rootDocument, 'mousemove', dragHandler);
pageDom.unbind(rootDocument, 'mouseup', dragEndHandler);
});
};
var blockIeDrop = function (editor) {
editor.on('drop', function(e) {
// FF doesn't pass out clientX/clientY for drop since this is for IE we just use null instead
var realTarget = typeof e.clientX !== 'undefined' ? editor.getDoc().elementFromPoint(e.clientX, e.clientY) : null;
if (isContentEditableFalse(realTarget) || isContentEditableFalse(editor.dom.getContentEditableParent(realTarget))) {
e.preventDefault();
}
});
};
var init = function (editor) {
bindFakeDragEvents(editor);
blockIeDrop(editor);
};
return {
init: init
};
});
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
/**
* EditorObservable.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* This mixin contains the event logic for the tinymce.Editor class.
*
* @mixin tinymce.EditorObservable
* @extends tinymce.util.Observable
*/
define("tinymce/EditorObservable", [
"tinymce/util/Observable",
"tinymce/dom/DOMUtils",
"tinymce/util/Tools"
], function(Observable, DOMUtils, Tools) {
var DOM = DOMUtils.DOM, customEventRootDelegates;
/**
* Returns the event target so for the specified event. Some events fire
* only on document, some fire on documentElement etc. This also handles the
* custom event root setting where it returns that element instead of the body.
*
* @private
* @param {tinymce.Editor} editor Editor instance to get event target from.
* @param {String} eventName Name of the event for example "click".
* @return {Element/Document} HTML Element or document target to bind on.
*/
function getEventTarget(editor, eventName) {
if (eventName == 'selectionchange') {
return editor.getDoc();
}
// Need to bind mousedown/mouseup etc to document not body in iframe mode
// Since the user might click on the HTML element not the BODY
if (!editor.inline && /^mouse|touch|click|contextmenu|drop|dragover|dragend/.test(eventName)) {
return editor.getDoc().documentElement;
}
// Bind to event root instead of body if it's defined
if (editor.settings.event_root) {
if (!editor.eventRoot) {
editor.eventRoot = DOM.select(editor.settings.event_root)[0];
}
return editor.eventRoot;
}
return editor.getBody();
}
/**
* Binds a event delegate for the specified name this delegate will fire
* the event to the editor dispatcher.
*
* @private
* @param {tinymce.Editor} editor Editor instance to get event target from.
* @param {String} eventName Name of the event for example "click".
*/
function bindEventDelegate(editor, eventName) {
var eventRootElm = getEventTarget(editor, eventName), delegate;
function isListening(editor) {
return !editor.hidden && !editor.readonly;
}
if (!editor.delegates) {
editor.delegates = {};
}
if (editor.delegates[eventName]) {
return;
}
if (editor.settings.event_root) {
if (!customEventRootDelegates) {
customEventRootDelegates = {};
editor.editorManager.on('removeEditor', function() {
var name;
if (!editor.editorManager.activeEditor) {
if (customEventRootDelegates) {
for (name in customEventRootDelegates) {
editor.dom.unbind(getEventTarget(editor, name));
}
customEventRootDelegates = null;
}
}
});
}
if (customEventRootDelegates[eventName]) {
return;
}
delegate = function(e) {
var target = e.target, editors = editor.editorManager.editors, i = editors.length;
while (i--) {
var body = editors[i].getBody();
if (body === target || DOM.isChildOf(target, body)) {
if (isListening(editors[i])) {
editors[i].fire(eventName, e);
}
}
}
};
customEventRootDelegates[eventName] = delegate;
DOM.bind(eventRootElm, eventName, delegate);
} else {
delegate = function(e) {
if (isListening(editor)) {
editor.fire(eventName, e);
}
};
DOM.bind(eventRootElm, eventName, delegate);
editor.delegates[eventName] = delegate;
}
}
var EditorObservable = {
/**
* Bind any pending event delegates. This gets executed after the target body/document is created.
*
* @private
*/
bindPendingEventDelegates: function() {
var self = this;
Tools.each(self._pendingNativeEvents, function(name) {
bindEventDelegate(self, name);
});
},
/**
* Toggles a native event on/off this is called by the EventDispatcher when
* the first native event handler is added and when the last native event handler is removed.
*
* @private
*/
toggleNativeEvent: function(name, state) {
var self = this;
// Never bind focus/blur since the FocusManager fakes those
if (name == "focus" || name == "blur") {
return;
}
if (state) {
if (self.initialized) {
bindEventDelegate(self, name);
} else {
if (!self._pendingNativeEvents) {
self._pendingNativeEvents = [name];
} else {
self._pendingNativeEvents.push(name);
}
}
} else if (self.initialized) {
self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]);
delete self.delegates[name];
}
},
/**
* Unbinds all native event handlers that means delegates, custom events bound using the Events API etc.
*
* @private
*/
unbindAllNativeEvents: function() {
var self = this, name;
if (self.delegates) {
for (name in self.delegates) {
self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]);
}
delete self.delegates;
}
if (!self.inline) {
self.getBody().onload = null;
self.dom.unbind(self.getWin());
self.dom.unbind(self.getDoc());
}
self.dom.unbind(self.getBody());
self.dom.unbind(self.getContainer());
}
};
EditorObservable = Tools.extend({}, Observable, EditorObservable);
return EditorObservable;
});
/**
* EditorUpload.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* Handles image uploads, updates undo stack and patches over various internal functions.
*
* @private
* @class tinymce.EditorUpload
*/
define("tinymce/EditorUpload", [
"tinymce/util/Arr",
"tinymce/file/Uploader",
"tinymce/file/ImageScanner",
"tinymce/file/BlobCache",
"tinymce/file/UploadStatus"
], function(Arr, Uploader, ImageScanner, BlobCache, UploadStatus) {
return function(editor) {
var blobCache = new BlobCache(), uploader, imageScanner, settings = editor.settings;
var uploadStatus = new UploadStatus();
function aliveGuard(callback) {
return function(result) {
if (editor.selection) {
return callback(result);
}
return [];
};
}
// Replaces strings without regexps to avoid FF regexp to big issue
function replaceString(content, search, replace) {
var index = 0;
do {
index = content.indexOf(search, index);
if (index !== -1) {
content = content.substring(0, index) + replace + content.substr(index + search.length);
index += replace.length - search.length + 1;
}
} while (index !== -1);
return content;
}
function replaceImageUrl(content, targetUrl, replacementUrl) {
content = replaceString(content, 'src="' + targetUrl + '"', 'src="' + replacementUrl + '"');
content = replaceString(content, 'data-mce-src="' + targetUrl + '"', 'data-mce-src="' + replacementUrl + '"');
return content;
}
function replaceUrlInUndoStack(targetUrl, replacementUrl) {
Arr.each(editor.undoManager.data, function(level) {
level.content = replaceImageUrl(level.content, targetUrl, replacementUrl);
});
}
function openNotification() {
return editor.notificationManager.open({
text: editor.translate('Image uploading...'),
type: 'info',
timeout: -1,
progressBar: true
});
}
function replaceImageUri(image, resultUri) {
blobCache.removeByUri(image.src);
replaceUrlInUndoStack(image.src, resultUri);
editor.$(image).attr({
src: resultUri,
'data-mce-src': editor.convertURL(resultUri, 'src')
});
}
function uploadImages(callback) {
if (!uploader) {
uploader = new Uploader(uploadStatus, {
url: settings.images_upload_url,
basePath: settings.images_upload_base_path,
credentials: settings.images_upload_credentials,
handler: settings.images_upload_handler
});
}
return scanForImages().then(aliveGuard(function(imageInfos) {
var blobInfos;
blobInfos = Arr.map(imageInfos, function(imageInfo) {
return imageInfo.blobInfo;
});
return uploader.upload(blobInfos, openNotification).then(aliveGuard(function(result) {
result = Arr.map(result, function(uploadInfo, index) {
var image = imageInfos[index].image;
if (uploadInfo.status && editor.settings.images_replace_blob_uris !== false) {
replaceImageUri(image, uploadInfo.url);
}
return {
element: image,
status: uploadInfo.status
};
});
if (callback) {
callback(result);
}
return result;
}));
}));
}
function uploadImagesAuto(callback) {
if (settings.automatic_uploads !== false) {
return uploadImages(callback);
}
}
function isValidDataUriImage(imgElm) {
return settings.images_dataimg_filter ? settings.images_dataimg_filter(imgElm) : true;
}
function scanForImages() {
if (!imageScanner) {
imageScanner = new ImageScanner(uploadStatus, blobCache);
}
return imageScanner.findAll(editor.getBody(), isValidDataUriImage).then(aliveGuard(function(result) {
Arr.each(result, function(resultItem) {
replaceUrlInUndoStack(resultItem.image.src, resultItem.blobInfo.blobUri());
resultItem.image.src = resultItem.blobInfo.blobUri();
resultItem.image.removeAttribute('data-mce-src');
});
return result;
}));
}
function destroy() {
blobCache.destroy();
uploadStatus.destroy();
imageScanner = uploader = null;
}
function replaceBlobUris(content) {
return content.replace(/src="(blob:[^"]+)"/g, function(match, blobUri) {
var resultUri = uploadStatus.getResultUri(blobUri);
if (resultUri) {
return 'src="' + resultUri + '"';
}
var blobInfo = blobCache.getByUri(blobUri);
if (!blobInfo) {
blobInfo = Arr.reduce(editor.editorManager.editors, function(result, editor) {
return result || editor.editorUpload.blobCache.getByUri(blobUri);
}, null);
}
if (blobInfo) {
return 'src="data:' + blobInfo.blob().type + ';base64,' + blobInfo.base64() + '"';
}
return match;
});
}
editor.on('setContent', function() {
if (editor.settings.automatic_uploads !== false) {
uploadImagesAuto();
} else {
scanForImages();
}
});
editor.on('RawSaveContent', function(e) {
e.content = replaceBlobUris(e.content);
});
editor.on('getContent', function(e) {
if (e.source_view || e.format == 'raw') {
return;
}
e.content = replaceBlobUris(e.content);
});
editor.on('PostRender', function() {
editor.parser.addNodeFilter('img', function(images) {
Arr.each(images, function(img) {
var src = img.attr('src');
if (blobCache.getByUri(src)) {
return;
}
var resultUri = uploadStatus.getResultUri(src);
if (resultUri) {
img.attr('src', resultUri);
}
});
});
});
return {
blobCache: blobCache,
uploadImages: uploadImages,
uploadImagesAuto: uploadImagesAuto,
scanForImages: scanForImages,
destroy: destroy
};
};
});
\ No newline at end of file
This diff is collapsed. Click to expand it.
/**
* Env.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* This class contains various environment constants like browser versions etc.
* Normally you don't want to sniff specific browser versions but sometimes you have
* to when it's impossible to feature detect. So use this with care.
*
* @class tinymce.Env
* @static
*/
define("tinymce/Env", [], function() {
var nav = navigator, userAgent = nav.userAgent;
var opera, webkit, ie, ie11, ie12, gecko, mac, iDevice, android, fileApi, phone, tablet, windowsPhone;
function matchMediaQuery(query) {
return "matchMedia" in window ? matchMedia(query).matches : false;
}
opera = window.opera && window.opera.buildNumber;
android = /Android/.test(userAgent);
webkit = /WebKit/.test(userAgent);
ie = !webkit && !opera && (/MSIE/gi).test(userAgent) && (/Explorer/gi).test(nav.appName);
ie = ie && /MSIE (\w+)\./.exec(userAgent)[1];
ie11 = userAgent.indexOf('Trident/') != -1 && (userAgent.indexOf('rv:') != -1 || nav.appName.indexOf('Netscape') != -1) ? 11 : false;
ie12 = (userAgent.indexOf('Edge/') != -1 && !ie && !ie11) ? 12 : false;
ie = ie || ie11 || ie12;
gecko = !webkit && !ie11 && /Gecko/.test(userAgent);
mac = userAgent.indexOf('Mac') != -1;
iDevice = /(iPad|iPhone)/.test(userAgent);
fileApi = "FormData" in window && "FileReader" in window && "URL" in window && !!URL.createObjectURL;
phone = matchMediaQuery("only screen and (max-device-width: 480px)") && (android || iDevice);
tablet = matchMediaQuery("only screen and (min-width: 800px)") && (android || iDevice);
windowsPhone = userAgent.indexOf('Windows Phone') != -1;
if (ie12) {
webkit = false;
}
// Is a iPad/iPhone and not on iOS5 sniff the WebKit version since older iOS WebKit versions
// says it has contentEditable support but there is no visible caret.
var contentEditable = !iDevice || fileApi || userAgent.match(/AppleWebKit\/(\d*)/)[1] >= 534;
return {
/**
* Constant that is true if the browser is Opera.
*
* @property opera
* @type Boolean
* @final
*/
opera: opera,
/**
* Constant that is true if the browser is WebKit (Safari/Chrome).
*
* @property webKit
* @type Boolean
* @final
*/
webkit: webkit,
/**
* Constant that is more than zero if the browser is IE.
*
* @property ie
* @type Boolean
* @final
*/
ie: ie,
/**
* Constant that is true if the browser is Gecko.
*
* @property gecko
* @type Boolean
* @final
*/
gecko: gecko,
/**
* Constant that is true if the os is Mac OS.
*
* @property mac
* @type Boolean
* @final
*/
mac: mac,
/**
* Constant that is true if the os is iOS.
*
* @property iOS
* @type Boolean
* @final
*/
iOS: iDevice,
/**
* Constant that is true if the os is android.
*
* @property android
* @type Boolean
* @final
*/
android: android,
/**
* Constant that is true if the browser supports editing.
*
* @property contentEditable
* @type Boolean
* @final
*/
contentEditable: contentEditable,
/**
* Transparent image data url.
*
* @property transparentSrc
* @type Boolean
* @final
*/
transparentSrc: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
/**
* Returns true/false if the browser can or can't place the caret after a inline block like an image.
*
* @property noCaretAfter
* @type Boolean
* @final
*/
caretAfter: ie != 8,
/**
* Constant that is true if the browser supports native DOM Ranges. IE 9+.
*
* @property range
* @type Boolean
*/
range: window.getSelection && "Range" in window,
/**
* Returns the IE document mode for non IE browsers this will fake IE 10.
*
* @property documentMode
* @type Number
*/
documentMode: ie && !ie12 ? (document.documentMode || 7) : 10,
/**
* Constant that is true if the browser has a modern file api.
*
* @property fileApi
* @type Boolean
*/
fileApi: fileApi,
/**
* Constant that is true if the browser supports contentEditable=false regions.
*
* @property ceFalse
* @type Boolean
*/
ceFalse: (ie === false || ie > 8),
/**
* Constant if CSP mode is possible or not. Meaning we can't use script urls for the iframe.
*/
canHaveCSP: (ie === false || ie > 11),
desktop: !phone && !tablet,
windowsPhone: windowsPhone
};
});
/**
* FocusManager.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* 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/util/Delay",
"tinymce/Env"
], function(DOMUtils, Delay, 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, lastRng;
if (editor.selection.lastFocusBookmark) {
lastRng = bookmarkToRng(editor, editor.selection.lastFocusBookmark);
editor.selection.lastFocusBookmark = null;
editor.selection.setRng(lastRng);
}
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() {
Delay.setEditorTimeout(editor, function() {
var focusedEditor = editorManager.focusedEditor;
// Still the same editor 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;
}
}
});
});
// 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, target;
target = e.target;
if (activeEditor && 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 && target != activeEditor.getBody()) {
activeEditor.selection.lastFocusBookmark = createBookmark(activeEditor.dom, activeEditor.lastRng);
}
// Fire a blur event if the element isn't a UI element
if (target != document.body && !isUIElement(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, dom = activeEditor.dom;
if (activeEditor.inline && dom && !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;
});
/**
* ForceBlocks.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* Makes sure that everything gets wrapped in paragraphs.
*
* @private
* @class tinymce.ForceBlocks
*/
define("tinymce/ForceBlocks", [], function() {
return function(editor) {
var settings = editor.settings, dom = editor.dom, selection = editor.selection;
var schema = editor.schema, blockElements = schema.getBlockElements();
function addRootBlocks() {
var node = selection.getStart(), rootNode = editor.getBody(), rng;
var startContainer, startOffset, endContainer, endOffset, rootBlockNode;
var tempNode, offset = -0xFFFFFF, wrapped, restoreSelection;
var tmpRng, rootNodeName, forcedRootBlock;
forcedRootBlock = settings.forced_root_block;
if (!node || node.nodeType !== 1 || !forcedRootBlock) {
return;
}
// Check if node is wrapped in block
while (node && node != rootNode) {
if (blockElements[node.nodeName]) {
return;
}
node = node.parentNode;
}
// Get current selection
rng = selection.getRng();
if (rng.setStart) {
startContainer = rng.startContainer;
startOffset = rng.startOffset;
endContainer = rng.endContainer;
endOffset = rng.endOffset;
try {
restoreSelection = editor.getDoc().activeElement === rootNode;
} catch (ex) {
// IE throws unspecified error here sometimes
}
} else {
// Force control range into text range
if (rng.item) {
node = rng.item(0);
rng = editor.getDoc().body.createTextRange();
rng.moveToElementText(node);
}
restoreSelection = rng.parentElement().ownerDocument === editor.getDoc();
tmpRng = rng.duplicate();
tmpRng.collapse(true);
startOffset = tmpRng.move('character', offset) * -1;
if (!tmpRng.collapsed) {
tmpRng = rng.duplicate();
tmpRng.collapse(false);
endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
}
}
// Wrap non block elements and text nodes
node = rootNode.firstChild;
rootNodeName = rootNode.nodeName.toLowerCase();
while (node) {
// TODO: Break this up, too complex
if (((node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName]))) &&
schema.isValidChild(rootNodeName, forcedRootBlock.toLowerCase())) {
// Remove empty text nodes
if (node.nodeType === 3 && node.nodeValue.length === 0) {
tempNode = node;
node = node.nextSibling;
dom.remove(tempNode);
continue;
}
if (!rootBlockNode) {
rootBlockNode = dom.create(forcedRootBlock, editor.settings.forced_root_block_attrs);
node.parentNode.insertBefore(rootBlockNode, node);
wrapped = true;
}
tempNode = node;
node = node.nextSibling;
rootBlockNode.appendChild(tempNode);
} else {
rootBlockNode = null;
node = node.nextSibling;
}
}
if (wrapped && restoreSelection) {
if (rng.setStart) {
rng.setStart(startContainer, startOffset);
rng.setEnd(endContainer, endOffset);
selection.setRng(rng);
} else {
// Only select if the previous selection was inside the document to prevent auto focus in quirks mode
try {
rng = editor.getDoc().body.createTextRange();
rng.moveToElementText(rootNode);
rng.collapse(true);
rng.moveStart('character', startOffset);
if (endOffset > 0) {
rng.moveEnd('character', endOffset);
}
rng.select();
} catch (ex) {
// Ignore
}
}
editor.nodeChanged();
}
}
// Force root blocks
if (settings.forced_root_block) {
editor.on('NodeChange', addRootBlocks);
}
};
});
\ No newline at end of file
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
/**
* InsertList.js
*
* Released under LGPL License.
* Copyright (c) 1999-2016 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* Handles inserts of lists into the editor instance.
*
* @class tinymce.InsertList
* @private
*/
define("tinymce/InsertList", [
"tinymce/util/Tools",
"tinymce/caret/CaretWalker",
"tinymce/caret/CaretPosition"
], function(Tools, CaretWalker, CaretPosition) {
var isListFragment = function(fragment) {
var firstChild = fragment.firstChild;
var lastChild = fragment.lastChild;
// Skip meta since it's likely <meta><ul>..</ul>
if (firstChild && firstChild.name === 'meta') {
firstChild = firstChild.next;
}
// Skip mce_marker since it's likely <ul>..</ul><span id="mce_marker"></span>
if (lastChild && lastChild.attr('id') === 'mce_marker') {
lastChild = lastChild.prev;
}
if (!firstChild || firstChild !== lastChild) {
return false;
}
return firstChild.name === 'ul' || firstChild.name === 'ol';
};
var cleanupDomFragment = function (domFragment) {
var firstChild = domFragment.firstChild;
var lastChild = domFragment.lastChild;
// TODO: remove the meta tag from paste logic
if (firstChild && firstChild.nodeName === 'META') {
firstChild.parentNode.removeChild(firstChild);
}
if (lastChild && lastChild.id === 'mce_marker') {
lastChild.parentNode.removeChild(lastChild);
}
return domFragment;
};
var toDomFragment = function(dom, serializer, fragment) {
var html = serializer.serialize(fragment);
var domFragment = dom.createFragment(html);
return cleanupDomFragment(domFragment);
};
var listItems = function(elm) {
return Tools.grep(elm.childNodes, function(child) {
return child.nodeName === 'LI';
});
};
var isEmpty = function (elm) {
return !elm.firstChild;
};
var trimListItems = function(elms) {
return elms.length > 0 && isEmpty(elms[elms.length - 1]) ? elms.slice(0, -1) : elms;
};
var getParentLi = function(dom, node) {
var parentBlock = dom.getParent(node, dom.isBlock);
return parentBlock && parentBlock.nodeName === 'LI' ? parentBlock : null;
};
var isParentBlockLi = function(dom, node) {
return !!getParentLi(dom, node);
};
var getSplit = function(parentNode, rng) {
var beforeRng = rng.cloneRange();
var afterRng = rng.cloneRange();
beforeRng.setStartBefore(parentNode);
afterRng.setEndAfter(parentNode);
return [
beforeRng.cloneContents(),
afterRng.cloneContents()
];
};
var findFirstIn = function(node, rootNode) {
var caretPos = CaretPosition.before(node);
var caretWalker = new CaretWalker(rootNode);
var newCaretPos = caretWalker.next(caretPos);
return newCaretPos ? newCaretPos.toRange() : null;
};
var findLastOf = function(node, rootNode) {
var caretPos = CaretPosition.after(node);
var caretWalker = new CaretWalker(rootNode);
var newCaretPos = caretWalker.prev(caretPos);
return newCaretPos ? newCaretPos.toRange() : null;
};
var insertMiddle = function(target, elms, rootNode, rng) {
var parts = getSplit(target, rng);
var parentElm = target.parentNode;
parentElm.insertBefore(parts[0], target);
Tools.each(elms, function(li) {
parentElm.insertBefore(li, target);
});
parentElm.insertBefore(parts[1], target);
parentElm.removeChild(target);
return findLastOf(elms[elms.length - 1], rootNode);
};
var insertBefore = function(target, elms, rootNode) {
var parentElm = target.parentNode;
Tools.each(elms, function(elm) {
parentElm.insertBefore(elm, target);
});
return findFirstIn(target, rootNode);
};
var insertAfter = function(target, elms, rootNode, dom) {
dom.insertAfter(elms.reverse(), target);
return findLastOf(elms[0], rootNode);
};
var insertAtCaret = function(serializer, dom, rng, fragment) {
var domFragment = toDomFragment(dom, serializer, fragment);
var liTarget = getParentLi(dom, rng.startContainer);
var liElms = trimListItems(listItems(domFragment.firstChild));
var BEGINNING = 1, END = 2;
var rootNode = dom.getRoot();
var isAt = function(location) {
var caretPos = CaretPosition.fromRangeStart(rng);
var caretWalker = new CaretWalker(dom.getRoot());
var newPos = location === BEGINNING ? caretWalker.prev(caretPos) : caretWalker.next(caretPos);
return newPos ? getParentLi(dom, newPos.getNode()) !== liTarget : true;
};
if (isAt(BEGINNING)) {
return insertBefore(liTarget, liElms, rootNode);
} else if (isAt(END)) {
return insertAfter(liTarget, liElms, rootNode, dom);
}
return insertMiddle(liTarget, liElms, rootNode, rng);
};
return {
isListFragment: isListFragment,
insertAtCaret: insertAtCaret,
isParentBlockLi: isParentBlockLi,
trimListItems: trimListItems,
listItems: listItems
};
});
\ No newline at end of file
/**
* LegacyInput.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* Converts legacy input to modern HTML.
*
* @class tinymce.LegacyInput
* @private
*/
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);
}
});
});
});
\ No newline at end of file
/**
* Mode.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* Mode switcher logic.
*
* @private
* @class tinymce.Mode
*/
define("tinymce/Mode", [], function() {
function setEditorCommandState(editor, cmd, state) {
try {
editor.getDoc().execCommand(cmd, false, state);
} catch (ex) {
// Ignore
}
}
function clickBlocker(editor) {
var target, handler;
target = editor.getBody();
handler = function(e) {
if (editor.dom.getParents(e.target, 'a').length > 0) {
e.preventDefault();
}
};
editor.dom.bind(target, 'click', handler);
return {
unbind: function() {
editor.dom.unbind(target, 'click', handler);
}
};
}
function toggleReadOnly(editor, state) {
if (editor._clickBlocker) {
editor._clickBlocker.unbind();
editor._clickBlocker = null;
}
if (state) {
editor._clickBlocker = clickBlocker(editor);
editor.selection.controlSelection.hideResizeRect();
editor.readonly = true;
editor.getBody().contentEditable = false;
} else {
editor.readonly = false;
editor.getBody().contentEditable = true;
setEditorCommandState(editor, "StyleWithCSS", false);
setEditorCommandState(editor, "enableInlineTableEditing", false);
setEditorCommandState(editor, "enableObjectResizing", false);
editor.focus();
editor.nodeChanged();
}
}
function setMode(editor, mode) {
var currentMode = editor.readonly ? 'readonly' : 'design';
if (mode == currentMode) {
return;
}
if (editor.initialized) {
toggleReadOnly(editor, mode == 'readonly');
} else {
editor.on('init', function() {
toggleReadOnly(editor, mode == 'readonly');
});
}
// Event is NOT preventable
editor.fire('SwitchMode', {mode: mode});
}
return {
setMode: setMode
};
});
\ No newline at end of file
/**
* NodeChange.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* This class handles the nodechange event dispatching both manual and through selection change events.
*
* @class tinymce.NodeChange
* @private
*/
define("tinymce/NodeChange", [
"tinymce/dom/RangeUtils",
"tinymce/Env",
"tinymce/util/Delay"
], function(RangeUtils, Env, Delay) {
return function(editor) {
var lastRng, lastPath = [];
/**
* Returns true/false if the current element path has been changed or not.
*
* @private
* @return {Boolean} True if the element path is the same false if it's not.
*/
function isSameElementPath(startElm) {
var i, currentPath;
currentPath = editor.$(startElm).parentsUntil(editor.getBody()).add(startElm);
if (currentPath.length === lastPath.length) {
for (i = currentPath.length; i >= 0; i--) {
if (currentPath[i] !== lastPath[i]) {
break;
}
}
if (i === -1) {
lastPath = currentPath;
return true;
}
}
lastPath = currentPath;
return false;
}
// Gecko doesn't support the "selectionchange" event
if (!('onselectionchange' in editor.getDoc())) {
editor.on('NodeChange Click MouseUp KeyUp Focus', function(e) {
var nativeRng, fakeRng;
// Since DOM Ranges mutate on modification
// of the DOM we need to clone it's contents
nativeRng = editor.selection.getRng();
fakeRng = {
startContainer: nativeRng.startContainer,
startOffset: nativeRng.startOffset,
endContainer: nativeRng.endContainer,
endOffset: nativeRng.endOffset
};
// Always treat nodechange as a selectionchange since applying
// formatting to the current range wouldn't update the range but it's parent
if (e.type == 'nodechange' || !RangeUtils.compareRanges(fakeRng, lastRng)) {
editor.fire('SelectionChange');
}
lastRng = fakeRng;
});
}
// IE has a bug where it fires a selectionchange on right click that has a range at the start of the body
// When the contextmenu event fires the selection is located at the right location
editor.on('contextmenu', function() {
editor.fire('SelectionChange');
});
// Selection change is delayed ~200ms on IE when you click inside the current range
editor.on('SelectionChange', function() {
var startElm = editor.selection.getStart(true);
// IE 8 will fire a selectionchange event with an incorrect selection
// when focusing out of table cells. Click inside cell -> toolbar = Invalid SelectionChange event
if (!Env.range && editor.selection.isCollapsed()) {
return;
}
if (!isSameElementPath(startElm) && editor.dom.isChildOf(startElm, editor.getBody())) {
editor.nodeChanged({selectionChange: true});
}
});
// Fire an extra nodeChange on mouseup for compatibility reasons
editor.on('MouseUp', function(e) {
if (!e.isDefaultPrevented()) {
// Delay nodeChanged call for WebKit edge case issue where the range
// isn't updated until after you click outside a selected image
if (editor.selection.getNode().nodeName == 'IMG') {
Delay.setEditorTimeout(editor, function() {
editor.nodeChanged();
});
} else {
editor.nodeChanged();
}
}
});
/**
* Dispatches out a onNodeChange event to all observers. This method should be called when you
* need to update the UI states or element path etc.
*
* @method nodeChanged
* @param {Object} args Optional args to pass to NodeChange event handlers.
*/
this.nodeChanged = function(args) {
var selection = editor.selection, node, parents, root;
// Fix for bug #1896577 it seems that this can not be fired while the editor is loading
if (editor.initialized && selection && !editor.settings.disable_nodechange && !editor.readonly) {
// Get start node
root = editor.getBody();
node = selection.getStart() || root;
// Make sure the node is within the editor root or is the editor root
if (node.ownerDocument != editor.getDoc() || !editor.dom.isChildOf(node, root)) {
node = root;
}
// Edge case for <p>|<img></p>
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);
}
};
};
});
/**
* NotificationManager.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* This class handles the creation of TinyMCE's notifications.
*
* @class tinymce.notificationManager
* @example
* // Opens a new notification of type "error" with text "An error occurred."
* tinymce.activeEditor.notificationManager.open({
* text: 'An error occurred.',
* type: 'error'
* });
*/
define("tinymce/NotificationManager", [
"tinymce/ui/Notification",
"tinymce/util/Delay"
], function(Notification, Delay) {
return function(editor) {
var self = this, notifications = [];
function getLastNotification() {
if (notifications.length) {
return notifications[notifications.length - 1];
}
}
self.notifications = notifications;
function resizeWindowEvent() {
Delay.requestAnimationFrame(function() {
prePositionNotifications();
positionNotifications();
});
}
// Since the viewport will change based on the present notifications, we need to move them all to the
// top left of the viewport to give an accurate size measurement so we can position them later.
function prePositionNotifications() {
for (var i = 0; i < notifications.length; i++) {
notifications[i].moveTo(0, 0);
}
}
function positionNotifications() {
if (notifications.length > 0) {
var firstItem = notifications.slice(0, 1)[0];
var container = editor.inline ? editor.getElement() : editor.getContentAreaContainer();
firstItem.moveRel(container, 'tc-tc');
if (notifications.length > 1) {
for (var i = 1; i < notifications.length; i++) {
notifications[i].moveRel(notifications[i - 1].getEl(), 'bc-tc');
}
}
}
}
editor.on('remove', function() {
var i = notifications.length;
while (i--) {
notifications[i].close();
}
});
editor.on('ResizeEditor', positionNotifications);
editor.on('ResizeWindow', resizeWindowEvent);
/**
* Opens a new notification.
*
* @method open
* @param {Object} args Optional name/value settings collection contains things like timeout/color/message etc.
*/
self.open = function(args) {
var notif;
editor.editorManager.setActive(editor);
notif = new Notification(args);
notifications.push(notif);
//If we have a timeout value
if (args.timeout > 0) {
notif.timer = setTimeout(function() {
notif.close();
}, args.timeout);
}
notif.on('close', function() {
var i = notifications.length;
if (notif.timer) {
editor.getWin().clearTimeout(notif.timer);
}
while (i--) {
if (notifications[i] === notif) {
notifications.splice(i, 1);
}
}
positionNotifications();
});
notif.renderTo();
positionNotifications();
return notif;
};
/**
* Closes the top most notification.
*
* @method close
*/
self.close = function() {
if (getLastNotification()) {
getLastNotification().close();
}
};
/**
* Returns the currently opened notification objects.
*
* @method getNotifications
* @return {Array} Array of the currently opened notifications.
*/
self.getNotifications = function() {
return notifications;
};
editor.on('SkinLoaded', function() {
var serviceMessage = editor.settings.service_message;
if (serviceMessage) {
editor.notificationManager.open({
text: serviceMessage,
type: 'warning',
timeout: 0,
icon: ''
});
}
});
//self.positionNotifications = positionNotifications;
};
});
/**
* Register.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* This registers tinymce in common module loaders.
*
* @private
* @class tinymce.Register
*/
define("tinymce/Register", [
], function() {
/*eslint consistent-this: 0 */
var context = this || window;
var tinymce = function() {
return context.tinymce;
};
if (typeof context.define === "function") {
// Bolt
if (!context.define.amd) {
context.define("ephox/tinymce", [], tinymce);
}
}
return {};
});
This diff is collapsed. Click to expand it.
/**
* Shortcuts.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* Contains all logic for handling of keyboard shortcuts.
*
* @class tinymce.Shortcuts
* @example
* editor.shortcuts.add('ctrl+a', function() {});
* editor.shortcuts.add('meta+a', function() {}); // "meta" maps to Command on Mac and Ctrl on PC
* editor.shortcuts.add('ctrl+alt+a', function() {});
* editor.shortcuts.add('access+a', function() {}); // "access" maps to ctrl+alt on Mac and shift+alt on PC
*/
define("tinymce/Shortcuts", [
"tinymce/util/Tools",
"tinymce/Env"
], function(Tools, Env) {
var each = Tools.each, explode = Tools.explode;
var keyCodeLookup = {
"f9": 120,
"f10": 121,
"f11": 122
};
var modifierNames = Tools.makeMap('alt,ctrl,shift,meta,access');
return function(editor) {
var self = this, shortcuts = {}, pendingPatterns = [];
function parseShortcut(pattern) {
var id, key, shortcut = {};
// Parse modifiers and keys ctrl+alt+b for example
each(explode(pattern, '+'), function(value) {
if (value in modifierNames) {
shortcut[value] = true;
} else {
// Allow numeric keycodes like ctrl+219 for ctrl+[
if (/^[0-9]{2,}$/.test(value)) {
shortcut.keyCode = parseInt(value, 10);
} else {
shortcut.charCode = value.charCodeAt(0);
shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0);
}
}
});
// Generate unique id for modifier combination and set default state for unused modifiers
id = [shortcut.keyCode];
for (key in modifierNames) {
if (shortcut[key]) {
id.push(key);
} else {
shortcut[key] = false;
}
}
shortcut.id = id.join(',');
// Handle special access modifier differently depending on Mac/Win
if (shortcut.access) {
shortcut.alt = true;
if (Env.mac) {
shortcut.ctrl = true;
} else {
shortcut.shift = true;
}
}
// Handle special meta modifier differently depending on Mac/Win
if (shortcut.meta) {
if (Env.mac) {
shortcut.meta = true;
} else {
shortcut.ctrl = true;
shortcut.meta = false;
}
}
return shortcut;
}
function createShortcut(pattern, desc, cmdFunc, scope) {
var shortcuts;
shortcuts = Tools.map(explode(pattern, '>'), parseShortcut);
shortcuts[shortcuts.length - 1] = Tools.extend(shortcuts[shortcuts.length - 1], {
func: cmdFunc,
scope: scope || editor
});
return Tools.extend(shortcuts[0], {
desc: editor.translate(desc),
subpatterns: shortcuts.slice(1)
});
}
function hasModifier(e) {
return e.altKey || e.ctrlKey || e.metaKey;
}
function isFunctionKey(e) {
return e.keyCode >= 112 && e.keyCode <= 123;
}
function matchShortcut(e, shortcut) {
if (!shortcut) {
return false;
}
if (shortcut.ctrl != e.ctrlKey || shortcut.meta != e.metaKey) {
return false;
}
if (shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) {
return false;
}
if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
e.preventDefault();
return true;
}
return false;
}
function executeShortcutAction(shortcut) {
return shortcut.func ? shortcut.func.call(shortcut.scope) : null;
}
editor.on('keyup keypress keydown', function(e) {
if ((hasModifier(e) || isFunctionKey(e)) && !e.isDefaultPrevented()) {
each(shortcuts, function(shortcut) {
if (matchShortcut(e, shortcut)) {
pendingPatterns = shortcut.subpatterns.slice(0);
if (e.type == "keydown") {
executeShortcutAction(shortcut);
}
return true;
}
});
if (matchShortcut(e, pendingPatterns[0])) {
if (pendingPatterns.length === 1) {
if (e.type == "keydown") {
executeShortcutAction(pendingPatterns[0]);
}
}
pendingPatterns.shift();
}
}
});
/**
* 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} scope Optional scope to execute the function in.
* @return {Boolean} true/false state if the shortcut was added or not.
*/
self.add = function(pattern, desc, cmdFunc, scope) {
var cmd;
cmd = cmdFunc;
if (typeof cmdFunc === 'string') {
cmdFunc = function() {
editor.execCommand(cmd, false, null);
};
} else if (Tools.isArray(cmd)) {
cmdFunc = function() {
editor.execCommand(cmd[0], cmd[1], cmd[2]);
};
}
each(explode(Tools.trim(pattern.toLowerCase())), function(pattern) {
var shortcut = createShortcut(pattern, desc, cmdFunc, scope);
shortcuts[shortcut.id] = shortcut;
});
return true;
};
/**
* Remove a keyboard shortcut by pattern.
*
* @method remove
* @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
* @return {Boolean} true/false state if the shortcut was removed or not.
*/
self.remove = function(pattern) {
var shortcut = createShortcut(pattern);
if (shortcuts[shortcut.id]) {
delete shortcuts[shortcut.id];
return true;
}
return false;
};
};
});
/**
* UndoManager.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* This class handles the undo/redo history levels for the editor. Since the built-in undo/redo has major drawbacks a custom one was needed.
*
* @class tinymce.UndoManager
*/
define("tinymce/UndoManager", [
"tinymce/util/VK",
"tinymce/Env"
], function(VK, Env) {
return function(editor) {
var self = this, index = 0, data = [], beforeBookmark, isFirstTypedCharacter, locks = 0;
function getContent() {
return editor.serializer.getTrimmedContent();
}
function setDirty(state) {
editor.setDirty(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 Cut', function() {
self.beforeChange();
});
editor.on('SaveContent ObjectResized blur', addNonTypingUndoLevel);
editor.on('DragEnd', addNonTypingUndoLevel);
editor.on('KeyUp', function(e) {
var keyCode = e.keyCode;
// If key is prevented then don't add undo level
// This would happen on keyboard shortcuts for example
if (e.isDefaultPrevented()) {
return;
}
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.isDirty()) {
editor.fire('change', {level: data[0], lastLevel: null});
}
}
editor.fire('TypingUndo');
isFirstTypedCharacter = false;
editor.nodeChanged();
}
});
editor.on('KeyDown', function(e) {
var keyCode = e.keyCode;
// If key is prevented then don't add undo level
// This would happen on keyboard shortcuts for example
if (e.isDefaultPrevented()) {
return;
}
// Is character position 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 = {
// Explode 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];
editor.setContent(level.content, {format: 'raw'});
editor.selection.moveToBookmark(level.beforeBookmark);
setDirty(true);
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;
self.data = data;
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 mutator function as an undo transaction. 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 translation that adds undo levels will
* be ignored. So a translation can include calls to execCommand or editor.insertContent.
*
* @method transact
* @param {function} callback Function that gets executed and has dom manipulation logic in it.
* @return {Object} Undo level that got added or null it a level wasn't needed.
*/
transact: function(callback) {
self.beforeChange();
try {
locks++;
callback();
} finally {
locks--;
}
return self.add();
},
/**
* Adds an extra "hidden" undo level by first applying the first mutation and store that to the undo stack
* then roll back that change and do the second mutation on top of the stack. This will produce an extra
* undo level that the user doesn't see until they undo.
*
* @method extra
* @param {function} callback1 Function that does mutation but gets stored as a "hidden" extra undo level.
* @param {function} callback2 Function that does mutation but gets displayed to the user.
*/
extra: function (callback1, callback2) {
var lastLevel, bookmark;
if (self.transact(callback1)) {
bookmark = data[index].bookmark;
lastLevel = data[index - 1];
editor.setContent(lastLevel.content, {format: 'raw'});
editor.selection.moveToBookmark(lastLevel.beforeBookmark);
if (self.transact(callback2)) {
data[index - 1].beforeBookmark = bookmark;
}
}
}
};
return self;
};
});
/**
* WindowManager.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* This class handles the creation of native windows and dialogs. This class can be extended to provide for example inline dialogs.
*
* @class tinymce.WindowManager
* @example
* // 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
* });
*
* // Displays an alert box using the active editors window manager instance
* tinymce.activeEditor.windowManager.alert('Hello world!');
*
* // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm
* tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) {
* if (s)
* tinymce.activeEditor.windowManager.alert("Ok");
* else
* tinymce.activeEditor.windowManager.alert("Cancel");
* });
*/
define("tinymce/WindowManager", [
"tinymce/ui/Window",
"tinymce/ui/MessageBox"
], function(Window, MessageBox) {
return function(editor) {
var self = this, windows = [];
function getTopMostWindow() {
if (windows.length) {
return windows[windows.length - 1];
}
}
function fireOpenEvent(win) {
editor.fire('OpenWindow', {
win: win
});
}
function fireCloseEvent(win) {
editor.fire('CloseWindow', {
win: win
});
}
self.windows = windows;
editor.on('remove', function() {
var i = windows.length;
while (i--) {
windows[i].close();
}
});
/**
* Opens a new window.
*
* @method open
* @param {Object} args Optional name/value settings collection contains things like width/height/url etc.
* @param {Object} params Options like title, file, width, height etc.
* @option {String} title Window title.
* @option {String} file URL of the file to open in the window.
* @option {Number} width Width in pixels.
* @option {Number} height Height in pixels.
* @option {Boolean} autoScroll Specifies whether the popup window can have scrollbars if required (i.e. content
* larger than the popup size specified).
*/
self.open = function(args, params) {
var win;
editor.editorManager.setActive(editor);
args.title = args.title || ' ';
// Handle URL
args.url = args.url || args.file; // Legacy
if (args.url) {
args.width = parseInt(args.width || 320, 10);
args.height = parseInt(args.height || 240, 10);
}
// Handle body
if (args.body) {
args.items = {
defaults: args.defaults,
type: args.bodyType || 'form',
items: args.body,
data: args.data,
callbacks: args.commands
};
}
if (!args.url && !args.buttons) {
args.buttons = [
{text: 'Ok', subtype: 'primary', onclick: function() {
win.find('form')[0].submit();
}},
{text: 'Cancel', onclick: function() {
win.close();
}}
];
}
win = new Window(args);
windows.push(win);
win.on('close', function() {
var i = windows.length;
while (i--) {
if (windows[i] === win) {
windows.splice(i, 1);
}
}
if (!windows.length) {
editor.focus();
}
fireCloseEvent(win);
});
// Handle data
if (args.data) {
win.on('postRender', function() {
this.find('*').each(function(ctrl) {
var name = ctrl.name();
if (name in args.data) {
ctrl.value(args.data[name]);
}
});
});
}
// store args and parameters
win.features = args || {};
win.params = params || {};
// Takes a snapshot in the FocusManager of the selection before focus is lost to dialog
if (windows.length === 1) {
editor.nodeChanged();
}
win = win.renderTo().reflow();
fireOpenEvent(win);
return win;
};
/**
* Creates a alert dialog. Please don't use the blocking behavior of this
* native version use the callback method instead then it can be extended.
*
* @method alert
* @param {String} message Text to display in the new alert dialog.
* @param {function} callback Callback function to be executed after the user has selected ok.
* @param {Object} scope Optional scope to execute the callback in.
* @example
* // Displays an alert box using the active editors window manager instance
* tinymce.activeEditor.windowManager.alert('Hello world!');
*/
self.alert = function(message, callback, scope) {
var win;
win = MessageBox.alert(message, function() {
if (callback) {
callback.call(scope || this);
} else {
editor.focus();
}
});
win.on('close', function() {
fireCloseEvent(win);
});
fireOpenEvent(win);
};
/**
* Creates a confirm dialog. Please don't use the blocking behavior of this
* native version use the callback method instead then it can be extended.
*
* @method confirm
* @param {String} message Text to display in the new confirm dialog.
* @param {function} callback Callback function to be executed after the user has selected ok or cancel.
* @param {Object} scope Optional scope to execute the callback in.
* @example
* // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm
* tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) {
* if (s)
* tinymce.activeEditor.windowManager.alert("Ok");
* else
* tinymce.activeEditor.windowManager.alert("Cancel");
* });
*/
self.confirm = function(message, callback, scope) {
var win;
win = MessageBox.confirm(message, function(state) {
callback.call(scope || this, state);
});
win.on('close', function() {
fireCloseEvent(win);
});
fireOpenEvent(win);
};
/**
* Closes the top most window.
*
* @method close
*/
self.close = function() {
if (getTopMostWindow()) {
getTopMostWindow().close();
}
};
/**
* Returns the params of the last window open call. This can be used in iframe based
* dialog to get params passed from the tinymce plugin.
*
* @example
* var dialogArguments = top.tinymce.activeEditor.windowManager.getParams();
*
* @method getParams
* @return {Object} Name/value object with parameters passed from windowManager.open call.
*/
self.getParams = function() {
return getTopMostWindow() ? getTopMostWindow().params : null;
};
/**
* Sets the params of the last opened window.
*
* @method setParams
* @param {Object} params Params object to set for the last opened window.
*/
self.setParams = function(params) {
if (getTopMostWindow()) {
getTopMostWindow().params = params;
}
};
/**
* Returns the currently opened window objects.
*
* @method getWindows
* @return {Array} Array of the currently opened windows.
*/
self.getWindows = function() {
return windows;
};
};
});
/**
* CaretBookmark.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* This module creates or resolves xpath like string representation of a CaretPositions.
*
* The format is a / separated list of chunks with:
* <element|text()>[index|after|before]
*
* For example:
* p[0]/b[0]/text()[0],1 = <p><b>a|c</b></p>
* p[0]/img[0],before = <p>|<img></p>
* p[0]/img[0],after = <p><img>|</p>
*
* @private
* @static
* @class tinymce.caret.CaretBookmark
* @example
* var bookmark = CaretBookmark.create(rootElm, CaretPosition.before(rootElm.firstChild));
* var caretPosition = CaretBookmark.resolve(bookmark);
*/
define('tinymce/caret/CaretBookmark', [
'tinymce/dom/NodeType',
'tinymce/dom/DOMUtils',
'tinymce/util/Fun',
'tinymce/util/Arr',
'tinymce/caret/CaretPosition'
], function(NodeType, DomUtils, Fun, Arr, CaretPosition) {
var isText = NodeType.isText,
isBogus = NodeType.isBogus,
nodeIndex = DomUtils.nodeIndex;
function normalizedParent(node) {
var parentNode = node.parentNode;
if (isBogus(parentNode)) {
return normalizedParent(parentNode);
}
return parentNode;
}
function getChildNodes(node) {
if (!node) {
return [];
}
return Arr.reduce(node.childNodes, function(result, node) {
if (isBogus(node) && node.nodeName != 'BR') {
result = result.concat(getChildNodes(node));
} else {
result.push(node);
}
return result;
}, []);
}
function normalizedTextOffset(textNode, offset) {
while ((textNode = textNode.previousSibling)) {
if (!isText(textNode)) {
break;
}
offset += textNode.data.length;
}
return offset;
}
function equal(targetValue) {
return function(value) {
return targetValue === value;
};
}
function normalizedNodeIndex(node) {
var nodes, index, numTextFragments;
nodes = getChildNodes(normalizedParent(node));
index = Arr.findIndex(nodes, equal(node), node);
nodes = nodes.slice(0, index + 1);
numTextFragments = Arr.reduce(nodes, function(result, node, i) {
if (isText(node) && isText(nodes[i - 1])) {
result++;
}
return result;
}, 0);
nodes = Arr.filter(nodes, NodeType.matchNodeNames(node.nodeName));
index = Arr.findIndex(nodes, equal(node), node);
return index - numTextFragments;
}
function createPathItem(node) {
var name;
if (isText(node)) {
name = 'text()';
} else {
name = node.nodeName.toLowerCase();
}
return name + '[' + normalizedNodeIndex(node) + ']';
}
function parentsUntil(rootNode, node, predicate) {
var parents = [];
for (node = node.parentNode; node != rootNode; node = node.parentNode) {
if (predicate && predicate(node)) {
break;
}
parents.push(node);
}
return parents;
}
function create(rootNode, caretPosition) {
var container, offset, path = [],
outputOffset, childNodes, parents;
container = caretPosition.container();
offset = caretPosition.offset();
if (isText(container)) {
outputOffset = normalizedTextOffset(container, offset);
} else {
childNodes = container.childNodes;
if (offset >= childNodes.length) {
outputOffset = 'after';
offset = childNodes.length - 1;
} else {
outputOffset = 'before';
}
container = childNodes[offset];
}
path.push(createPathItem(container));
parents = parentsUntil(rootNode, container);
parents = Arr.filter(parents, Fun.negate(NodeType.isBogus));
path = path.concat(Arr.map(parents, function(node) {
return createPathItem(node);
}));
return path.reverse().join('/') + ',' + outputOffset;
}
function resolvePathItem(node, name, index) {
var nodes = getChildNodes(node);
nodes = Arr.filter(nodes, function(node, index) {
return !isText(node) || !isText(nodes[index - 1]);
});
nodes = Arr.filter(nodes, NodeType.matchNodeNames(name));
return nodes[index];
}
function findTextPosition(container, offset) {
var node = container, targetOffset = 0, dataLen;
while (isText(node)) {
dataLen = node.data.length;
if (offset >= targetOffset && offset <= targetOffset + dataLen) {
container = node;
offset = offset - targetOffset;
break;
}
if (!isText(node.nextSibling)) {
container = node;
offset = dataLen;
break;
}
targetOffset += dataLen;
node = node.nextSibling;
}
if (offset > container.data.length) {
offset = container.data.length;
}
return new CaretPosition(container, offset);
}
function resolve(rootNode, path) {
var parts, container, offset;
if (!path) {
return null;
}
parts = path.split(',');
path = parts[0].split('/');
offset = parts.length > 1 ? parts[1] : 'before';
container = Arr.reduce(path, function(result, value) {
value = /([\w\-\(\)]+)\[([0-9]+)\]/.exec(value);
if (!value) {
return null;
}
if (value[1] === 'text()') {
value[1] = '#text';
}
return resolvePathItem(result, value[1], parseInt(value[2], 10));
}, rootNode);
if (!container) {
return null;
}
if (!isText(container)) {
if (offset === 'after') {
offset = nodeIndex(container) + 1;
} else {
offset = nodeIndex(container);
}
return new CaretPosition(container.parentNode, offset);
}
return findTextPosition(container, parseInt(offset, 10));
}
return {
/**
* Create a xpath bookmark location for the specified caret position.
*
* @method create
* @param {Node} rootNode Root node to create bookmark within.
* @param {tinymce.caret.CaretPosition} caretPosition Caret position within the root node.
* @return {String} String xpath like location of caret position.
*/
create: create,
/**
* Resolves a xpath like bookmark location to the a caret position.
*
* @method resolve
* @param {Node} rootNode Root node to resolve xpath bookmark within.
* @param {String} bookmark Bookmark string to resolve.
* @return {tinymce.caret.CaretPosition} Caret position resolved from xpath like bookmark.
*/
resolve: resolve
};
});
\ No newline at end of file
/**
* CaretCandidate.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* This module contains logic for handling caret candidates. A caret candidate is
* for example text nodes, images, input elements, cE=false elements etc.
*
* @private
* @class tinymce.caret.CaretCandidate
*/
define("tinymce/caret/CaretCandidate", [
"tinymce/dom/NodeType",
"tinymce/util/Arr",
"tinymce/caret/CaretContainer"
], function(NodeType, Arr, CaretContainer) {
var isContentEditableTrue = NodeType.isContentEditableTrue,
isContentEditableFalse = NodeType.isContentEditableFalse,
isBr = NodeType.isBr,
isText = NodeType.isText,
isInvalidTextElement = NodeType.matchNodeNames('script style textarea'),
isAtomicInline = NodeType.matchNodeNames('img input textarea hr iframe video audio object'),
isTable = NodeType.matchNodeNames('table'),
isCaretContainer = CaretContainer.isCaretContainer;
function isCaretCandidate(node) {
if (isCaretContainer(node)) {
return false;
}
if (isText(node)) {
if (isInvalidTextElement(node.parentNode)) {
return false;
}
return true;
}
return isAtomicInline(node) || isBr(node) || isTable(node) || isContentEditableFalse(node);
}
function isInEditable(node, rootNode) {
for (node = node.parentNode; node && node != rootNode; node = node.parentNode) {
if (isContentEditableFalse(node)) {
return false;
}
if (isContentEditableTrue(node)) {
return true;
}
}
return true;
}
function isAtomicContentEditableFalse(node) {
if (!isContentEditableFalse(node)) {
return false;
}
return Arr.reduce(node.getElementsByTagName('*'), function(result, elm) {
return result || isContentEditableTrue(elm);
}, false) !== true;
}
function isAtomic(node) {
return isAtomicInline(node) || isAtomicContentEditableFalse(node);
}
function isEditableCaretCandidate(node, rootNode) {
return isCaretCandidate(node) && isInEditable(node, rootNode);
}
return {
isCaretCandidate: isCaretCandidate,
isInEditable: isInEditable,
isAtomic: isAtomic,
isEditableCaretCandidate: isEditableCaretCandidate
};
});
\ No newline at end of file
/**
* CaretContainer.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* This module handles caret containers. A caret container is a node that
* holds the caret for positional purposes.
*
* @private
* @class tinymce.caret.CaretContainer
*/
define("tinymce/caret/CaretContainer", [
"tinymce/dom/NodeType",
"tinymce/text/Zwsp"
], function(NodeType, Zwsp) {
var isElement = NodeType.isElement,
isText = NodeType.isText;
function isCaretContainerBlock(node) {
if (isText(node)) {
node = node.parentNode;
}
return isElement(node) && node.hasAttribute('data-mce-caret');
}
function isCaretContainerInline(node) {
return isText(node) && Zwsp.isZwsp(node.data);
}
function isCaretContainer(node) {
return isCaretContainerBlock(node) || isCaretContainerInline(node);
}
function removeNode(node) {
var parentNode = node.parentNode;
if (parentNode) {
parentNode.removeChild(node);
}
}
function getNodeValue(node) {
try {
return node.nodeValue;
} catch (ex) {
// IE sometimes produces "Invalid argument" on nodes
return "";
}
}
function setNodeValue(node, text) {
if (text.length === 0) {
removeNode(node);
} else {
node.nodeValue = text;
}
}
function insertInline(node, before) {
var doc, sibling, textNode, parentNode;
doc = node.ownerDocument;
textNode = doc.createTextNode(Zwsp.ZWSP);
parentNode = node.parentNode;
if (!before) {
sibling = node.nextSibling;
if (isText(sibling)) {
if (isCaretContainer(sibling)) {
return sibling;
}
if (startsWithCaretContainer(sibling)) {
sibling.splitText(1);
return sibling;
}
}
if (node.nextSibling) {
parentNode.insertBefore(textNode, node.nextSibling);
} else {
parentNode.appendChild(textNode);
}
} else {
sibling = node.previousSibling;
if (isText(sibling)) {
if (isCaretContainer(sibling)) {
return sibling;
}
if (endsWithCaretContainer(sibling)) {
return sibling.splitText(sibling.data.length - 1);
}
}
parentNode.insertBefore(textNode, node);
}
return textNode;
}
function insertBlock(blockName, node, before) {
var doc, blockNode, parentNode;
doc = node.ownerDocument;
blockNode = doc.createElement(blockName);
blockNode.setAttribute('data-mce-caret', before ? 'before' : 'after');
blockNode.setAttribute('data-mce-bogus', 'all');
blockNode.appendChild(doc.createTextNode('\u00a0'));
parentNode = node.parentNode;
if (!before) {
if (node.nextSibling) {
parentNode.insertBefore(blockNode, node.nextSibling);
} else {
parentNode.appendChild(blockNode);
}
} else {
parentNode.insertBefore(blockNode, node);
}
return blockNode;
}
function remove(caretContainerNode) {
if (isElement(caretContainerNode) && isCaretContainer(caretContainerNode)) {
if (caretContainerNode.innerHTML != '&nbsp;') {
caretContainerNode.removeAttribute('data-mce-caret');
} else {
removeNode(caretContainerNode);
}
}
if (isText(caretContainerNode)) {
var text = Zwsp.trim(getNodeValue(caretContainerNode));
setNodeValue(caretContainerNode, text);
}
}
function startsWithCaretContainer(node) {
return isText(node) && node.data[0] == Zwsp.ZWSP;
}
function endsWithCaretContainer(node) {
return isText(node) && node.data[node.data.length - 1] == Zwsp.ZWSP;
}
return {
isCaretContainer: isCaretContainer,
isCaretContainerBlock: isCaretContainerBlock,
isCaretContainerInline: isCaretContainerInline,
insertInline: insertInline,
insertBlock: insertBlock,
remove: remove,
startsWithCaretContainer: startsWithCaretContainer,
endsWithCaretContainer: endsWithCaretContainer
};
});
\ No newline at end of file
This diff is collapsed. Click to expand it.
/**
* CaretUtils.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* Utility functions shared by the caret logic.
*
* @private
* @class tinymce.caret.CaretUtils
*/
define("tinymce/caret/CaretUtils", [
"tinymce/util/Fun",
"tinymce/dom/TreeWalker",
"tinymce/dom/NodeType",
"tinymce/caret/CaretPosition",
"tinymce/caret/CaretContainer",
"tinymce/caret/CaretCandidate"
], function(Fun, TreeWalker, NodeType, CaretPosition, CaretContainer, CaretCandidate) {
var isContentEditableTrue = NodeType.isContentEditableTrue,
isContentEditableFalse = NodeType.isContentEditableFalse,
isBlockLike = NodeType.matchStyleValues('display', 'block table table-cell table-caption'),
isCaretContainer = CaretContainer.isCaretContainer,
curry = Fun.curry,
isElement = NodeType.isElement,
isCaretCandidate = CaretCandidate.isCaretCandidate;
function isForwards(direction) {
return direction > 0;
}
function isBackwards(direction) {
return direction < 0;
}
function findNode(node, direction, predicateFn, rootNode, shallow) {
var walker = new TreeWalker(node, rootNode);
if (isBackwards(direction)) {
if (isContentEditableFalse(node)) {
node = walker.prev(true);
if (predicateFn(node)) {
return node;
}
}
while ((node = walker.prev(shallow))) {
if (predicateFn(node)) {
return node;
}
}
}
if (isForwards(direction)) {
if (isContentEditableFalse(node)) {
node = walker.next(true);
if (predicateFn(node)) {
return node;
}
}
while ((node = walker.next(shallow))) {
if (predicateFn(node)) {
return node;
}
}
}
return null;
}
function getEditingHost(node, rootNode) {
for (node = node.parentNode; node && node != rootNode; node = node.parentNode) {
if (isContentEditableTrue(node)) {
return node;
}
}
return rootNode;
}
function getParentBlock(node, rootNode) {
while (node && node != rootNode) {
if (isBlockLike(node)) {
return node;
}
node = node.parentNode;
}
return null;
}
function isInSameBlock(caretPosition1, caretPosition2, rootNode) {
return getParentBlock(caretPosition1.container(), rootNode) == getParentBlock(caretPosition2.container(), rootNode);
}
function isInSameEditingHost(caretPosition1, caretPosition2, rootNode) {
return getEditingHost(caretPosition1.container(), rootNode) == getEditingHost(caretPosition2.container(), rootNode);
}
function getChildNodeAtRelativeOffset(relativeOffset, caretPosition) {
var container, offset;
if (!caretPosition) {
return null;
}
container = caretPosition.container();
offset = caretPosition.offset();
if (!isElement(container)) {
return null;
}
return container.childNodes[offset + relativeOffset];
}
function beforeAfter(before, node) {
var range = node.ownerDocument.createRange();
if (before) {
range.setStartBefore(node);
range.setEndBefore(node);
} else {
range.setStartAfter(node);
range.setEndAfter(node);
}
return range;
}
function isNodesInSameBlock(rootNode, node1, node2) {
return getParentBlock(node1, rootNode) == getParentBlock(node2, rootNode);
}
function lean(left, rootNode, node) {
var sibling, siblingName;
if (left) {
siblingName = 'previousSibling';
} else {
siblingName = 'nextSibling';
}
while (node && node != rootNode) {
sibling = node[siblingName];
if (isCaretContainer(sibling)) {
sibling = sibling[siblingName];
}
if (isContentEditableFalse(sibling)) {
if (isNodesInSameBlock(rootNode, sibling, node)) {
return sibling;
}
break;
}
if (isCaretCandidate(sibling)) {
break;
}
node = node.parentNode;
}
return null;
}
var before = curry(beforeAfter, true);
var after = curry(beforeAfter, false);
function normalizeRange(direction, rootNode, range) {
var node, container, offset, location;
var leanLeft = curry(lean, true, rootNode);
var leanRight = curry(lean, false, rootNode);
container = range.startContainer;
offset = range.startOffset;
if (CaretContainer.isCaretContainerBlock(container)) {
if (!isElement(container)) {
container = container.parentNode;
}
location = container.getAttribute('data-mce-caret');
if (location == 'before') {
node = container.nextSibling;
if (isContentEditableFalse(node)) {
return before(node);
}
}
if (location == 'after') {
node = container.previousSibling;
if (isContentEditableFalse(node)) {
return after(node);
}
}
}
if (!range.collapsed) {
return range;
}
if (NodeType.isText(container)) {
if (isCaretContainer(container)) {
if (direction === 1) {
node = leanRight(container);
if (node) {
return before(node);
}
node = leanLeft(container);
if (node) {
return after(node);
}
}
if (direction === -1) {
node = leanLeft(container);
if (node) {
return after(node);
}
node = leanRight(container);
if (node) {
return before(node);
}
}
return range;
}
if (CaretContainer.endsWithCaretContainer(container) && offset >= container.data.length - 1) {
if (direction === 1) {
node = leanRight(container);
if (node) {
return before(node);
}
}
return range;
}
if (CaretContainer.startsWithCaretContainer(container) && offset <= 1) {
if (direction === -1) {
node = leanLeft(container);
if (node) {
return after(node);
}
}
return range;
}
if (offset === container.data.length) {
node = leanRight(container);
if (node) {
return before(node);
}
return range;
}
if (offset === 0) {
node = leanLeft(container);
if (node) {
return after(node);
}
return range;
}
}
return range;
}
function isNextToContentEditableFalse(relativeOffset, caretPosition) {
return isContentEditableFalse(getChildNodeAtRelativeOffset(relativeOffset, caretPosition));
}
return {
isForwards: isForwards,
isBackwards: isBackwards,
findNode: findNode,
getEditingHost: getEditingHost,
getParentBlock: getParentBlock,
isInSameBlock: isInSameBlock,
isInSameEditingHost: isInSameEditingHost,
isBeforeContentEditableFalse: curry(isNextToContentEditableFalse, 0),
isAfterContentEditableFalse: curry(isNextToContentEditableFalse, -1),
normalizeRange: normalizeRange
};
});
/**
* CaretWalker.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* This module contains logic for moving around a virtual caret in logical order within a DOM element.
*
* It ignores the most obvious invalid caret locations such as within a script element or within a
* contentEditable=false element but it will return locations that isn't possible to render visually.
*
* @private
* @class tinymce.caret.CaretWalker
* @example
* var caretWalker = new CaretWalker(rootElm);
*
* var prevLogicalCaretPosition = caretWalker.prev(CaretPosition.fromRangeStart(range));
* var nextLogicalCaretPosition = caretWalker.next(CaretPosition.fromRangeEnd(range));
*/
define("tinymce/caret/CaretWalker", [
"tinymce/dom/NodeType",
"tinymce/caret/CaretCandidate",
"tinymce/caret/CaretPosition",
"tinymce/caret/CaretUtils",
"tinymce/util/Arr",
"tinymce/util/Fun"
], function(NodeType, CaretCandidate, CaretPosition, CaretUtils, Arr, Fun) {
var isContentEditableFalse = NodeType.isContentEditableFalse,
isText = NodeType.isText,
isElement = NodeType.isElement,
isBr = NodeType.isBr,
isForwards = CaretUtils.isForwards,
isBackwards = CaretUtils.isBackwards,
isCaretCandidate = CaretCandidate.isCaretCandidate,
isAtomic = CaretCandidate.isAtomic,
isEditableCaretCandidate = CaretCandidate.isEditableCaretCandidate;
function getParents(node, rootNode) {
var parents = [];
while (node && node != rootNode) {
parents.push(node);
node = node.parentNode;
}
return parents;
}
function nodeAtIndex(container, offset) {
if (container.hasChildNodes() && offset < container.childNodes.length) {
return container.childNodes[offset];
}
return null;
}
function getCaretCandidatePosition(direction, node) {
if (isForwards(direction)) {
if (isCaretCandidate(node.previousSibling) && !isText(node.previousSibling)) {
return CaretPosition.before(node);
}
if (isText(node)) {
return CaretPosition(node, 0);
}
}
if (isBackwards(direction)) {
if (isCaretCandidate(node.nextSibling) && !isText(node.nextSibling)) {
return CaretPosition.after(node);
}
if (isText(node)) {
return CaretPosition(node, node.data.length);
}
}
if (isBackwards(direction)) {
if (isBr(node)) {
return CaretPosition.before(node);
}
return CaretPosition.after(node);
}
return CaretPosition.before(node);
}
// Jumps over BR elements <p>|<br></p><p>a</p> -> <p><br></p><p>|a</p>
function isBrBeforeBlock(node, rootNode) {
var next;
if (!NodeType.isBr(node)) {
return false;
}
next = findCaretPosition(1, CaretPosition.after(node), rootNode);
if (!next) {
return false;
}
return !CaretUtils.isInSameBlock(CaretPosition.before(node), CaretPosition.before(next), rootNode);
}
function findCaretPosition(direction, startCaretPosition, rootNode) {
var container, offset, node, nextNode, innerNode,
rootContentEditableFalseElm, caretPosition;
if (!isElement(rootNode) || !startCaretPosition) {
return null;
}
caretPosition = startCaretPosition;
container = caretPosition.container();
offset = caretPosition.offset();
if (isText(container)) {
if (isBackwards(direction) && offset > 0) {
return CaretPosition(container, --offset);
}
if (isForwards(direction) && offset < container.length) {
return CaretPosition(container, ++offset);
}
node = container;
} else {
if (isBackwards(direction) && offset > 0) {
nextNode = nodeAtIndex(container, offset - 1);
if (isCaretCandidate(nextNode)) {
if (!isAtomic(nextNode)) {
innerNode = CaretUtils.findNode(nextNode, direction, isEditableCaretCandidate, nextNode);
if (innerNode) {
if (isText(innerNode)) {
return CaretPosition(innerNode, innerNode.data.length);
}
return CaretPosition.after(innerNode);
}
}
if (isText(nextNode)) {
return CaretPosition(nextNode, nextNode.data.length);
}
return CaretPosition.before(nextNode);
}
}
if (isForwards(direction) && offset < container.childNodes.length) {
nextNode = nodeAtIndex(container, offset);
if (isCaretCandidate(nextNode)) {
if (isBrBeforeBlock(nextNode, rootNode)) {
return findCaretPosition(direction, CaretPosition.after(nextNode), rootNode);
}
if (!isAtomic(nextNode)) {
innerNode = CaretUtils.findNode(nextNode, direction, isEditableCaretCandidate, nextNode);
if (innerNode) {
if (isText(innerNode)) {
return CaretPosition(innerNode, 0);
}
return CaretPosition.before(innerNode);
}
}
if (isText(nextNode)) {
return CaretPosition(nextNode, 0);
}
return CaretPosition.after(nextNode);
}
}
node = caretPosition.getNode();
}
if ((isForwards(direction) && caretPosition.isAtEnd()) || (isBackwards(direction) && caretPosition.isAtStart())) {
node = CaretUtils.findNode(node, direction, Fun.constant(true), rootNode, true);
if (isEditableCaretCandidate(node)) {
return getCaretCandidatePosition(direction, node);
}
}
nextNode = CaretUtils.findNode(node, direction, isEditableCaretCandidate, rootNode);
rootContentEditableFalseElm = Arr.last(Arr.filter(getParents(container, rootNode), isContentEditableFalse));
if (rootContentEditableFalseElm && (!nextNode || !rootContentEditableFalseElm.contains(nextNode))) {
if (isForwards(direction)) {
caretPosition = CaretPosition.after(rootContentEditableFalseElm);
} else {
caretPosition = CaretPosition.before(rootContentEditableFalseElm);
}
return caretPosition;
}
if (nextNode) {
return getCaretCandidatePosition(direction, nextNode);
}
return null;
}
return function(rootNode) {
return {
/**
* Returns the next logical caret position from the specificed input
* caretPoisiton or null if there isn't any more positions left for example
* at the end specified root element.
*
* @method next
* @param {tinymce.caret.CaretPosition} caretPosition Caret position to start from.
* @return {tinymce.caret.CaretPosition} CaretPosition or null if no position was found.
*/
next: function(caretPosition) {
return findCaretPosition(1, caretPosition, rootNode);
},
/**
* Returns the previous logical caret position from the specificed input
* caretPoisiton or null if there isn't any more positions left for example
* at the end specified root element.
*
* @method prev
* @param {tinymce.caret.CaretPosition} caretPosition Caret position to start from.
* @return {tinymce.caret.CaretPosition} CaretPosition or null if no position was found.
*/
prev: function(caretPosition) {
return findCaretPosition(-1, caretPosition, rootNode);
}
};
};
});
\ No newline at end of file
/**
* FakeCaret.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* This module contains logic for rendering a fake visual caret.
*
* @private
* @class tinymce.caret.FakeCaret
*/
define("tinymce/caret/FakeCaret", [
"tinymce/caret/CaretContainer",
"tinymce/caret/CaretPosition",
"tinymce/dom/NodeType",
"tinymce/dom/RangeUtils",
"tinymce/dom/DomQuery",
"tinymce/geom/ClientRect",
"tinymce/util/Delay"
], function(CaretContainer, CaretPosition, NodeType, RangeUtils, $, ClientRect, Delay) {
var isContentEditableFalse = NodeType.isContentEditableFalse;
return function(rootNode, isBlock) {
var cursorInterval, $lastVisualCaret, caretContainerNode;
function getAbsoluteClientRect(node, before) {
var clientRect = ClientRect.collapse(node.getBoundingClientRect(), before),
docElm, scrollX, scrollY, margin, rootRect;
if (rootNode.tagName == 'BODY') {
docElm = rootNode.ownerDocument.documentElement;
scrollX = rootNode.scrollLeft || docElm.scrollLeft;
scrollY = rootNode.scrollTop || docElm.scrollTop;
} else {
rootRect = rootNode.getBoundingClientRect();
scrollX = rootNode.scrollLeft - rootRect.left;
scrollY = rootNode.scrollTop - rootRect.top;
}
clientRect.left += scrollX;
clientRect.right += scrollX;
clientRect.top += scrollY;
clientRect.bottom += scrollY;
clientRect.width = 1;
margin = node.offsetWidth - node.clientWidth;
if (margin > 0) {
if (before) {
margin *= -1;
}
clientRect.left += margin;
clientRect.right += margin;
}
return clientRect;
}
function trimInlineCaretContainers() {
var contentEditableFalseNodes, node, sibling, i, data;
contentEditableFalseNodes = $('*[contentEditable=false]', rootNode);
for (i = 0; i < contentEditableFalseNodes.length; i++) {
node = contentEditableFalseNodes[i];
sibling = node.previousSibling;
if (CaretContainer.endsWithCaretContainer(sibling)) {
data = sibling.data;
if (data.length == 1) {
sibling.parentNode.removeChild(sibling);
} else {
sibling.deleteData(data.length - 1, 1);
}
}
sibling = node.nextSibling;
if (CaretContainer.startsWithCaretContainer(sibling)) {
data = sibling.data;
if (data.length == 1) {
sibling.parentNode.removeChild(sibling);
} else {
sibling.deleteData(0, 1);
}
}
}
return null;
}
function show(before, node) {
var clientRect, rng, container;
hide();
if (isBlock(node)) {
caretContainerNode = CaretContainer.insertBlock('p', node, before);
clientRect = getAbsoluteClientRect(node, before);
$(caretContainerNode).css('top', clientRect.top);
$lastVisualCaret = $('<div class="mce-visual-caret" data-mce-bogus="all"></div>').css(clientRect).appendTo(rootNode);
if (before) {
$lastVisualCaret.addClass('mce-visual-caret-before');
}
startBlink();
rng = node.ownerDocument.createRange();
container = caretContainerNode.firstChild;
rng.setStart(container, 0);
rng.setEnd(container, 1);
} else {
caretContainerNode = CaretContainer.insertInline(node, before);
rng = node.ownerDocument.createRange();
if (isContentEditableFalse(caretContainerNode.nextSibling)) {
rng.setStart(caretContainerNode, 0);
rng.setEnd(caretContainerNode, 0);
} else {
rng.setStart(caretContainerNode, 1);
rng.setEnd(caretContainerNode, 1);
}
return rng;
}
return rng;
}
function hide() {
trimInlineCaretContainers();
if (caretContainerNode) {
CaretContainer.remove(caretContainerNode);
caretContainerNode = null;
}
if ($lastVisualCaret) {
$lastVisualCaret.remove();
$lastVisualCaret = null;
}
clearInterval(cursorInterval);
}
function startBlink() {
cursorInterval = Delay.setInterval(function() {
$('div.mce-visual-caret', rootNode).toggleClass('mce-visual-caret-hidden');
}, 500);
}
function destroy() {
Delay.clearInterval(cursorInterval);
}
function getCss() {
return (
'.mce-visual-caret {' +
'position: absolute;' +
'background-color: black;' +
'background-color: currentcolor;' +
'}' +
'.mce-visual-caret-hidden {' +
'display: none;' +
'}' +
'*[data-mce-caret] {' +
'position: absolute;' +
'left: -1000px;' +
'right: auto;' +
'top: 0;' +
'margin: 0;' +
'padding: 0;' +
'}'
);
}
return {
show: show,
hide: hide,
getCss: getCss,
destroy: destroy
};
};
});
\ No newline at end of file
/**
* LineUtils.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* Utility functions for working with lines.
*
* @private
* @class tinymce.caret.LineUtils
*/
define("tinymce/caret/LineUtils", [
"tinymce/util/Fun",
"tinymce/util/Arr",
"tinymce/dom/NodeType",
"tinymce/dom/Dimensions",
"tinymce/geom/ClientRect",
"tinymce/caret/CaretUtils",
"tinymce/caret/CaretCandidate"
], function(Fun, Arr, NodeType, Dimensions, ClientRect, CaretUtils, CaretCandidate) {
var isContentEditableFalse = NodeType.isContentEditableFalse,
findNode = CaretUtils.findNode,
curry = Fun.curry;
function distanceToRectLeft(clientRect, clientX) {
return Math.abs(clientRect.left - clientX);
}
function distanceToRectRight(clientRect, clientX) {
return Math.abs(clientRect.right - clientX);
}
function findClosestClientRect(clientRects, clientX) {
function isInside(clientX, clientRect) {
return clientX >= clientRect.left && clientX <= clientRect.right;
}
return Arr.reduce(clientRects, function(oldClientRect, clientRect) {
var oldDistance, newDistance;
oldDistance = Math.min(distanceToRectLeft(oldClientRect, clientX), distanceToRectRight(oldClientRect, clientX));
newDistance = Math.min(distanceToRectLeft(clientRect, clientX), distanceToRectRight(clientRect, clientX));
if (isInside(clientX, clientRect)) {
return clientRect;
}
if (isInside(clientX, oldClientRect)) {
return oldClientRect;
}
// cE=false has higher priority
if (newDistance == oldDistance && isContentEditableFalse(clientRect.node)) {
return clientRect;
}
if (newDistance < oldDistance) {
return clientRect;
}
return oldClientRect;
});
}
function walkUntil(direction, rootNode, predicateFn, node) {
while ((node = findNode(node, direction, CaretCandidate.isEditableCaretCandidate, rootNode))) {
if (predicateFn(node)) {
return;
}
}
}
function findLineNodeRects(rootNode, targetNodeRect) {
var clientRects = [];
function collect(checkPosFn, node) {
var lineRects;
lineRects = Arr.filter(Dimensions.getClientRects(node), function(clientRect) {
return !checkPosFn(clientRect, targetNodeRect);
});
clientRects = clientRects.concat(lineRects);
return lineRects.length === 0;
}
clientRects.push(targetNodeRect);
walkUntil(-1, rootNode, curry(collect, ClientRect.isAbove), targetNodeRect.node);
walkUntil(1, rootNode, curry(collect, ClientRect.isBelow), targetNodeRect.node);
return clientRects;
}
function getContentEditableFalseChildren(rootNode) {
return Arr.filter(Arr.toArray(rootNode.getElementsByTagName('*')), isContentEditableFalse);
}
function caretInfo(clientRect, clientX) {
return {
node: clientRect.node,
before: distanceToRectLeft(clientRect, clientX) < distanceToRectRight(clientRect, clientX)
};
}
function closestCaret(rootNode, clientX, clientY) {
var contentEditableFalseNodeRects, closestNodeRect;
contentEditableFalseNodeRects = Dimensions.getClientRects(getContentEditableFalseChildren(rootNode));
contentEditableFalseNodeRects = Arr.filter(contentEditableFalseNodeRects, function(clientRect) {
return clientY >= clientRect.top && clientY <= clientRect.bottom;
});
closestNodeRect = findClosestClientRect(contentEditableFalseNodeRects, clientX);
if (closestNodeRect) {
closestNodeRect = findClosestClientRect(findLineNodeRects(rootNode, closestNodeRect), clientX);
if (closestNodeRect && isContentEditableFalse(closestNodeRect.node)) {
return caretInfo(closestNodeRect, clientX);
}
}
return null;
}
return {
findClosestClientRect: findClosestClientRect,
findLineNodeRects: findLineNodeRects,
closestCaret: closestCaret
};
});
\ No newline at end of file
/**
* LineWalker.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* This module lets you walk the document line by line
* returing nodes and client rects for each line.
*
* @private
* @class tinymce.caret.LineWalker
*/
define("tinymce/caret/LineWalker", [
"tinymce/util/Fun",
"tinymce/util/Arr",
"tinymce/dom/Dimensions",
"tinymce/caret/CaretCandidate",
"tinymce/caret/CaretUtils",
"tinymce/caret/CaretWalker",
"tinymce/caret/CaretPosition",
"tinymce/geom/ClientRect"
], function(Fun, Arr, Dimensions, CaretCandidate, CaretUtils, CaretWalker, CaretPosition, ClientRect) {
var curry = Fun.curry;
function findUntil(direction, rootNode, predicateFn, node) {
while ((node = CaretUtils.findNode(node, direction, CaretCandidate.isEditableCaretCandidate, rootNode))) {
if (predicateFn(node)) {
return;
}
}
}
function walkUntil(direction, isAboveFn, isBeflowFn, rootNode, predicateFn, caretPosition) {
var line = 0, node, result = [], targetClientRect;
function add(node) {
var i, clientRect, clientRects;
clientRects = Dimensions.getClientRects(node);
if (direction == -1) {
clientRects = clientRects.reverse();
}
for (i = 0; i < clientRects.length; i++) {
clientRect = clientRects[i];
if (isBeflowFn(clientRect, targetClientRect)) {
continue;
}
if (result.length > 0 && isAboveFn(clientRect, Arr.last(result))) {
line++;
}
clientRect.line = line;
if (predicateFn(clientRect)) {
return true;
}
result.push(clientRect);
}
}
targetClientRect = Arr.last(caretPosition.getClientRects());
if (!targetClientRect) {
return result;
}
node = caretPosition.getNode();
add(node);
findUntil(direction, rootNode, add, node);
return result;
}
function aboveLineNumber(lineNumber, clientRect) {
return clientRect.line > lineNumber;
}
function isLine(lineNumber, clientRect) {
return clientRect.line === lineNumber;
}
var upUntil = curry(walkUntil, -1, ClientRect.isAbove, ClientRect.isBelow);
var downUntil = curry(walkUntil, 1, ClientRect.isBelow, ClientRect.isAbove);
function positionsUntil(direction, rootNode, predicateFn, node) {
var caretWalker = new CaretWalker(rootNode), walkFn, isBelowFn, isAboveFn,
caretPosition, result = [], line = 0, clientRect, targetClientRect;
function getClientRect(caretPosition) {
if (direction == 1) {
return Arr.last(caretPosition.getClientRects());
}
return Arr.last(caretPosition.getClientRects());
}
if (direction == 1) {
walkFn = caretWalker.next;
isBelowFn = ClientRect.isBelow;
isAboveFn = ClientRect.isAbove;
caretPosition = CaretPosition.after(node);
} else {
walkFn = caretWalker.prev;
isBelowFn = ClientRect.isAbove;
isAboveFn = ClientRect.isBelow;
caretPosition = CaretPosition.before(node);
}
targetClientRect = getClientRect(caretPosition);
do {
if (!caretPosition.isVisible()) {
continue;
}
clientRect = getClientRect(caretPosition);
if (isAboveFn(clientRect, targetClientRect)) {
continue;
}
if (result.length > 0 && isBelowFn(clientRect, Arr.last(result))) {
line++;
}
clientRect = ClientRect.clone(clientRect);
clientRect.position = caretPosition;
clientRect.line = line;
if (predicateFn(clientRect)) {
return result;
}
result.push(clientRect);
} while ((caretPosition = walkFn(caretPosition)));
return result;
}
return {
upUntil: upUntil,
downUntil: downUntil,
/**
* Find client rects with line and caret position until the predicate returns true.
*
* @method positionsUntil
* @param {Number} direction Direction forward/backward 1/-1.
* @param {DOMNode} rootNode Root node to walk within.
* @param {function} predicateFn Gets the client rect as it's input.
* @param {DOMNode} node Node to start walking from.
* @return {Array} Array of client rects with line and position properties.
*/
positionsUntil: positionsUntil,
isAboveLine: curry(aboveLineNumber),
isLine: curry(isLine)
};
});
\ No newline at end of file
/**
* Binding.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* This class gets dynamically extended to provide a binding between two models. This makes it possible to
* sync the state of two properties in two models by a layer of abstraction.
*
* @private
* @class tinymce.data.Binding
*/
define("tinymce/data/Binding", [], function() {
/**
* Constructs a new bidning.
*
* @constructor
* @method Binding
* @param {Object} settings Settings to the binding.
*/
function Binding(settings) {
this.create = settings.create;
}
/**
* Creates a binding for a property on a model.
*
* @method create
* @param {tinymce.data.ObservableObject} model Model to create binding to.
* @param {String} name Name of property to bind.
* @return {tinymce.data.Binding} Binding instance.
*/
Binding.create = function(model, name) {
return new Binding({
create: function(otherModel, otherName) {
var bindings;
function fromSelfToOther(e) {
otherModel.set(otherName, e.value);
}
function fromOtherToSelf(e) {
model.set(name, e.value);
}
otherModel.on('change:' + otherName, fromOtherToSelf);
model.on('change:' + name, fromSelfToOther);
// Keep track of the bindings
bindings = otherModel._bindings;
if (!bindings) {
bindings = otherModel._bindings = [];
otherModel.on('destroy', function() {
var i = bindings.length;
while (i--) {
bindings[i]();
}
});
}
bindings.push(function() {
model.off('change:' + name, fromSelfToOther);
});
return model.get(name);
}
});
};
return Binding;
});
\ No newline at end of file
/**
* ObservableArray.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* This class is an array that emmits events when mutation occurs.
*
* @private
* @class tinymce.data.ObservableArray
*/
define("tinymce/data/ObservableArray", [
"tinymce/util/Observable",
"tinymce/util/Class"
], function(Observable, Class) {
var push = Array.prototype.push, slice = Array.prototype.slice, splice = Array.prototype.splice;
var ObservableArray = Class.extend({
Mixins: [Observable],
/**
* Number of items in array.
*
* @field length
* @type Number
*/
length: 0,
/**
* Constructs a new observable object instance.
*
* @constructor
* @param {Object} data Optional initial data for the object.
*/
init: function(data) {
if (data) {
this.push.apply(this, data);
}
},
/**
* Adds items to the end of array.
*
* @method push
* @param {Object} item... Item or items to add to the end of array.
* @return {Number} Number of items that got added.
*/
push: function() {
var args, index = this.length;
args = Array.prototype.slice.call(arguments);
push.apply(this, args);
this.fire('add', {
items: args,
index: index
});
return args.length;
},
/**
* Pops the last item off the array.
*
* @method pop
* @return {Object} Item that got popped out.
*/
pop: function() {
return this.splice(this.length - 1, 1)[0];
},
/**
* Slices out a portion of the array as a new array.
*
* @method slice
* @param {Number} begin Beginning of slice.
* @param {Number} end End of slice.
* @return {Array} Native array instance with items.
*/
slice: function(begin, end) {
return slice.call(this, begin, end);
},
/**
* Removes/replaces/inserts items in the array.
*
* @method splice
* @param {Number} index Index to splice at.
* @param {Number} howMany Optional number of items to splice away.
* @param {Object} item ... Item or items to insert at the specified index.
*/
splice: function(index) {
var added, removed, args = slice.call(arguments);
if (args.length === 1) {
args[1] = this.length;
}
removed = splice.apply(this, args);
added = args.slice(2);
if (removed.length > 0) {
this.fire('remove', {items: removed, index: index});
}
if (added.length > 0) {
this.fire('add', {items: added, index: index});
}
return removed;
},
/**
* Removes and returns the first item of the array.
*
* @method shift
* @return {Object} First item of the array.
*/
shift: function() {
return this.splice(0, 1)[0];
},
/**
* Appends an item to the top of array.
*
* @method unshift
* @param {Object} item... Item or items to prepend to array.
* @return {Number} Number of items that got added.
*/
unshift: function() {
var args = slice.call(arguments);
this.splice.apply(this, [0, 0].concat(args));
return args.length;
},
/**
* Executes the callback for each item in the array.
*
* @method forEach
* @param {function} callback Callback to execute for each item in array.
* @param {Object} scope Optional scope for this when executing the callback.
*/
forEach: function(callback, scope) {
var i;
scope = scope || this;
for (i = 0; i < this.length; i++) {
callback.call(scope, this[i], i, this);
}
},
/**
* Returns the index of the specified item or -1 if it wasn't found.
*
* @method indexOf
* @return {Number} Index of item or null if it wasn't found.
*/
indexOf: function(item) {
for (var i = 0; i < this.length; i++) {
if (this[i] === item) {
return i;
}
}
return -1;
},
/**
* Filters the observable array into a new observable array
* based on the true/false return value of the specified callback.
*
* @method filter
* @param {function} callback Callback function to execute for each item and filter by.
* @param {Object} thisArg Optional scope for this when executing the callback.
* @return {tinymce.data.ObservableArray} Filtered observable array instance.
*/
filter: function(callback, thisArg) {
var self = this, out = new ObservableArray();
this.forEach(function(item, index) {
if (callback.call(thisArg || self, item, index, self)) {
out.push(item);
}
});
return out;
}
});
return ObservableArray;
});
\ No newline at end of file
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff could not be displayed because it is too large.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff is collapsed. Click to expand it.