JR Utily

naive integration of tinymce as inputTextarea

Showing 472 changed files with 5038 additions and 0 deletions
1 +/**
2 + * AddOnManager.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * This class handles the loading of themes/plugins or other add-ons and their language packs.
13 + *
14 + * @class tinymce.AddOnManager
15 + */
16 +define("tinymce/AddOnManager", [
17 + "tinymce/dom/ScriptLoader",
18 + "tinymce/util/Tools"
19 +], function(ScriptLoader, Tools) {
20 + var each = Tools.each;
21 +
22 + function AddOnManager() {
23 + var self = this;
24 +
25 + self.items = [];
26 + self.urls = {};
27 + self.lookup = {};
28 + }
29 +
30 + AddOnManager.prototype = {
31 + /**
32 + * Returns the specified add on by the short name.
33 + *
34 + * @method get
35 + * @param {String} name Add-on to look for.
36 + * @return {tinymce.Theme/tinymce.Plugin} Theme or plugin add-on instance or undefined.
37 + */
38 + get: function(name) {
39 + if (this.lookup[name]) {
40 + return this.lookup[name].instance;
41 + }
42 +
43 + return undefined;
44 + },
45 +
46 + dependencies: function(name) {
47 + var result;
48 +
49 + if (this.lookup[name]) {
50 + result = this.lookup[name].dependencies;
51 + }
52 +
53 + return result || [];
54 + },
55 +
56 + /**
57 + * Loads a language pack for the specified add-on.
58 + *
59 + * @method requireLangPack
60 + * @param {String} name Short name of the add-on.
61 + * @param {String} languages Optional comma or space separated list of languages to check if it matches the name.
62 + */
63 + requireLangPack: function(name, languages) {
64 + var language = AddOnManager.language;
65 +
66 + if (language && AddOnManager.languageLoad !== false) {
67 + if (languages) {
68 + languages = ',' + languages + ',';
69 +
70 + // Load short form sv.js or long form sv_SE.js
71 + if (languages.indexOf(',' + language.substr(0, 2) + ',') != -1) {
72 + language = language.substr(0, 2);
73 + } else if (languages.indexOf(',' + language + ',') == -1) {
74 + return;
75 + }
76 + }
77 +
78 + ScriptLoader.ScriptLoader.add(this.urls[name] + '/langs/' + language + '.js');
79 + }
80 + },
81 +
82 + /**
83 + * Adds a instance of the add-on by it's short name.
84 + *
85 + * @method add
86 + * @param {String} id Short name/id for the add-on.
87 + * @param {tinymce.Theme/tinymce.Plugin} addOn Theme or plugin to add.
88 + * @return {tinymce.Theme/tinymce.Plugin} The same theme or plugin instance that got passed in.
89 + * @example
90 + * // Create a simple plugin
91 + * tinymce.create('tinymce.plugins.TestPlugin', {
92 + * TestPlugin: function(ed, url) {
93 + * ed.on('click', function(e) {
94 + * ed.windowManager.alert('Hello World!');
95 + * });
96 + * }
97 + * });
98 + *
99 + * // Register plugin using the add method
100 + * tinymce.PluginManager.add('test', tinymce.plugins.TestPlugin);
101 + *
102 + * // Initialize TinyMCE
103 + * tinymce.init({
104 + * ...
105 + * plugins: '-test' // Init the plugin but don't try to load it
106 + * });
107 + */
108 + add: function(id, addOn, dependencies) {
109 + this.items.push(addOn);
110 + this.lookup[id] = {instance: addOn, dependencies: dependencies};
111 +
112 + return addOn;
113 + },
114 +
115 + remove: function(name) {
116 + delete this.urls[name];
117 + delete this.lookup[name];
118 + },
119 +
120 + createUrl: function(baseUrl, dep) {
121 + if (typeof dep === "object") {
122 + return dep;
123 + }
124 +
125 + return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
126 + },
127 +
128 + /**
129 + * Add a set of components that will make up the add-on. Using the url of the add-on name as the base url.
130 + * This should be used in development mode. A new compressor/javascript munger process will ensure that the
131 + * components are put together into the plugin.js file and compressed correctly.
132 + *
133 + * @method addComponents
134 + * @param {String} pluginName name of the plugin to load scripts from (will be used to get the base url for the plugins).
135 + * @param {Array} scripts Array containing the names of the scripts to load.
136 + */
137 + addComponents: function(pluginName, scripts) {
138 + var pluginUrl = this.urls[pluginName];
139 +
140 + each(scripts, function(script) {
141 + ScriptLoader.ScriptLoader.add(pluginUrl + "/" + script);
142 + });
143 + },
144 +
145 + /**
146 + * Loads an add-on from a specific url.
147 + *
148 + * @method load
149 + * @param {String} name Short name of the add-on that gets loaded.
150 + * @param {String} addOnUrl URL to the add-on that will get loaded.
151 + * @param {function} callback Optional callback to execute ones the add-on is loaded.
152 + * @param {Object} scope Optional scope to execute the callback in.
153 + * @example
154 + * // Loads a plugin from an external URL
155 + * tinymce.PluginManager.load('myplugin', '/some/dir/someplugin/plugin.js');
156 + *
157 + * // Initialize TinyMCE
158 + * tinymce.init({
159 + * ...
160 + * plugins: '-myplugin' // Don't try to load it again
161 + * });
162 + */
163 + load: function(name, addOnUrl, callback, scope) {
164 + var self = this, url = addOnUrl;
165 +
166 + function loadDependencies() {
167 + var dependencies = self.dependencies(name);
168 +
169 + each(dependencies, function(dep) {
170 + var newUrl = self.createUrl(addOnUrl, dep);
171 +
172 + self.load(newUrl.resource, newUrl, undefined, undefined);
173 + });
174 +
175 + if (callback) {
176 + if (scope) {
177 + callback.call(scope);
178 + } else {
179 + callback.call(ScriptLoader);
180 + }
181 + }
182 + }
183 +
184 + if (self.urls[name]) {
185 + return;
186 + }
187 +
188 + if (typeof addOnUrl === "object") {
189 + url = addOnUrl.prefix + addOnUrl.resource + addOnUrl.suffix;
190 + }
191 +
192 + if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) {
193 + url = AddOnManager.baseURL + '/' + url;
194 + }
195 +
196 + self.urls[name] = url.substring(0, url.lastIndexOf('/'));
197 +
198 + if (self.lookup[name]) {
199 + loadDependencies();
200 + } else {
201 + ScriptLoader.ScriptLoader.add(url, loadDependencies, scope);
202 + }
203 + }
204 + };
205 +
206 + AddOnManager.PluginManager = new AddOnManager();
207 + AddOnManager.ThemeManager = new AddOnManager();
208 +
209 + return AddOnManager;
210 +});
211 +
212 +/**
213 + * TinyMCE theme class.
214 + *
215 + * @class tinymce.Theme
216 + */
217 +
218 +/**
219 + * This method is responsible for rendering/generating the overall user interface with toolbars, buttons, iframe containers etc.
220 + *
221 + * @method renderUI
222 + * @param {Object} obj Object parameter containing the targetNode DOM node that will be replaced visually with an editor instance.
223 + * @return {Object} an object with items like iframeContainer, editorContainer, sizeContainer, deltaWidth, deltaHeight.
224 + */
225 +
226 +/**
227 + * 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.
228 + *
229 + * @class tinymce.Plugin
230 + * @example
231 + * tinymce.PluginManager.add('example', function(editor, url) {
232 + * // Add a button that opens a window
233 + * editor.addButton('example', {
234 + * text: 'My button',
235 + * icon: false,
236 + * onclick: function() {
237 + * // Open window
238 + * editor.windowManager.open({
239 + * title: 'Example plugin',
240 + * body: [
241 + * {type: 'textbox', name: 'title', label: 'Title'}
242 + * ],
243 + * onsubmit: function(e) {
244 + * // Insert content when the window form is submitted
245 + * editor.insertContent('Title: ' + e.data.title);
246 + * }
247 + * });
248 + * }
249 + * });
250 + *
251 + * // Adds a menu item to the tools menu
252 + * editor.addMenuItem('example', {
253 + * text: 'Example plugin',
254 + * context: 'tools',
255 + * onclick: function() {
256 + * // Open window with a specific url
257 + * editor.windowManager.open({
258 + * title: 'TinyMCE site',
259 + * url: 'http://www.tinymce.com',
260 + * width: 800,
261 + * height: 600,
262 + * buttons: [{
263 + * text: 'Close',
264 + * onclick: 'close'
265 + * }]
266 + * });
267 + * }
268 + * });
269 + * });
270 + */
1 +/**
2 + * Compat.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * TinyMCE core class.
13 + *
14 + * @static
15 + * @class tinymce
16 + * @borrow-members tinymce.EditorManager
17 + * @borrow-members tinymce.util.Tools
18 + */
19 +define("tinymce/Compat", [
20 + "tinymce/dom/DOMUtils",
21 + "tinymce/dom/EventUtils",
22 + "tinymce/dom/ScriptLoader",
23 + "tinymce/AddOnManager",
24 + "tinymce/util/Tools",
25 + "tinymce/Env"
26 +], function(DOMUtils, EventUtils, ScriptLoader, AddOnManager, Tools, Env) {
27 + var tinymce = window.tinymce;
28 +
29 + /**
30 + * @property {tinymce.dom.DOMUtils} DOM Global DOM instance.
31 + * @property {tinymce.dom.ScriptLoader} ScriptLoader Global ScriptLoader instance.
32 + * @property {tinymce.AddOnManager} PluginManager Global PluginManager instance.
33 + * @property {tinymce.AddOnManager} ThemeManager Global ThemeManager instance.
34 + */
35 + tinymce.DOM = DOMUtils.DOM;
36 + tinymce.ScriptLoader = ScriptLoader.ScriptLoader;
37 + tinymce.PluginManager = AddOnManager.PluginManager;
38 + tinymce.ThemeManager = AddOnManager.ThemeManager;
39 +
40 + tinymce.dom = tinymce.dom || {};
41 + tinymce.dom.Event = EventUtils.Event;
42 +
43 + Tools.each(Tools, function(func, key) {
44 + tinymce[key] = func;
45 + });
46 +
47 + Tools.each('isOpera isWebKit isIE isGecko isMac'.split(' '), function(name) {
48 + tinymce[name] = Env[name.substr(2).toLowerCase()];
49 + });
50 +
51 + return {};
52 +});
53 +
54 +// Describe the different namespaces
55 +
56 +/**
57 + * Root level namespace this contains classes directly related to the TinyMCE editor.
58 + *
59 + * @namespace tinymce
60 + */
61 +
62 +/**
63 + * Contains classes for handling the browsers DOM.
64 + *
65 + * @namespace tinymce.dom
66 + */
67 +
68 +/**
69 + * Contains html parser and serializer logic.
70 + *
71 + * @namespace tinymce.html
72 + */
73 +
74 +/**
75 + * Contains the different UI types such as buttons, listboxes etc.
76 + *
77 + * @namespace tinymce.ui
78 + */
79 +
80 +/**
81 + * Contains various utility classes such as json parser, cookies etc.
82 + *
83 + * @namespace tinymce.util
84 + */
85 +
86 +/**
87 + * Contains modules to handle data binding.
88 + *
89 + * @namespace tinymce.data
90 + */
1 +/**
2 + * DragDropOverrides.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * This module contains logic overriding the drag/drop logic of the editor.
13 + *
14 + * @private
15 + * @class tinymce.DragDropOverrides
16 + */
17 +define("tinymce/DragDropOverrides", [
18 + "tinymce/dom/NodeType",
19 + "tinymce/util/Arr",
20 + "tinymce/util/Fun",
21 + "tinymce/util/Delay",
22 + "tinymce/dom/DOMUtils",
23 + "tinymce/dom/MousePosition"
24 +], function(
25 + NodeType, Arr, Fun, Delay, DOMUtils, MousePosition
26 +) {
27 + var isContentEditableFalse = NodeType.isContentEditableFalse,
28 + isContentEditableTrue = NodeType.isContentEditableTrue;
29 +
30 + var isDraggable = function (elm) {
31 + return isContentEditableFalse(elm);
32 + };
33 +
34 + var isValidDropTarget = function (editor, targetElement, dragElement) {
35 + if (targetElement === dragElement || editor.dom.isChildOf(targetElement, dragElement)) {
36 + return false;
37 + }
38 +
39 + if (isContentEditableFalse(targetElement)) {
40 + return false;
41 + }
42 +
43 + return true;
44 + };
45 +
46 + var cloneElement = function (elm) {
47 + var cloneElm = elm.cloneNode(true);
48 + cloneElm.removeAttribute('data-mce-selected');
49 + return cloneElm;
50 + };
51 +
52 + var createGhost = function (editor, elm, width, height) {
53 + var clonedElm = elm.cloneNode(true);
54 +
55 + editor.dom.setStyles(clonedElm, {width: width, height: height});
56 + editor.dom.setAttrib(clonedElm, 'data-mce-selected', null);
57 +
58 + var ghostElm = editor.dom.create('div', {
59 + 'class': 'mce-drag-container',
60 + 'data-mce-bogus': 'all',
61 + unselectable: 'on',
62 + contenteditable: 'false'
63 + });
64 +
65 + editor.dom.setStyles(ghostElm, {
66 + position: 'absolute',
67 + opacity: 0.5,
68 + overflow: 'hidden',
69 + border: 0,
70 + padding: 0,
71 + margin: 0,
72 + width: width,
73 + height: height
74 + });
75 +
76 + editor.dom.setStyles(clonedElm, {
77 + margin: 0,
78 + boxSizing: 'border-box'
79 + });
80 +
81 + ghostElm.appendChild(clonedElm);
82 +
83 + return ghostElm;
84 + };
85 +
86 + var appendGhostToBody = function (ghostElm, bodyElm) {
87 + if (ghostElm.parentNode !== bodyElm) {
88 + bodyElm.appendChild(ghostElm);
89 + }
90 + };
91 +
92 + var moveGhost = function (ghostElm, position, width, height, maxX, maxY) {
93 + var overflowX = 0, overflowY = 0;
94 +
95 + ghostElm.style.left = position.pageX + 'px';
96 + ghostElm.style.top = position.pageY + 'px';
97 +
98 + if (position.pageX + width > maxX) {
99 + overflowX = (position.pageX + width) - maxX;
100 + }
101 +
102 + if (position.pageY + height > maxY) {
103 + overflowY = (position.pageY + height) - maxY;
104 + }
105 +
106 + ghostElm.style.width = (width - overflowX) + 'px';
107 + ghostElm.style.height = (height - overflowY) + 'px';
108 + };
109 +
110 + var removeElement = function (elm) {
111 + if (elm && elm.parentNode) {
112 + elm.parentNode.removeChild(elm);
113 + }
114 + };
115 +
116 + var isLeftMouseButtonPressed = function (e) {
117 + return e.button === 0;
118 + };
119 +
120 + var hasDraggableElement = function (state) {
121 + return state.element;
122 + };
123 +
124 + var applyRelPos = function (state, position) {
125 + return {
126 + pageX: position.pageX - state.relX,
127 + pageY: position.pageY + 5
128 + };
129 + };
130 +
131 + var start = function (state, editor) {
132 + return function (e) {
133 + if (isLeftMouseButtonPressed(e)) {
134 + var ceElm = Arr.find(editor.dom.getParents(e.target), Fun.or(isContentEditableFalse, isContentEditableTrue));
135 +
136 + if (isDraggable(ceElm)) {
137 + var elmPos = editor.dom.getPos(ceElm);
138 + var bodyElm = editor.getBody();
139 + var docElm = editor.getDoc().documentElement;
140 +
141 + state.element = ceElm;
142 + state.screenX = e.screenX;
143 + state.screenY = e.screenY;
144 + state.maxX = (editor.inline ? bodyElm.scrollWidth : docElm.offsetWidth) - 2;
145 + state.maxY = (editor.inline ? bodyElm.scrollHeight : docElm.offsetHeight) - 2;
146 + state.relX = e.pageX - elmPos.x;
147 + state.relY = e.pageY - elmPos.y;
148 + state.width = ceElm.offsetWidth;
149 + state.height = ceElm.offsetHeight;
150 + state.ghost = createGhost(editor, ceElm, state.width, state.height);
151 + }
152 + }
153 + };
154 + };
155 +
156 + var move = function (state, editor) {
157 + // Reduces laggy drag behavior on Gecko
158 + var throttledPlaceCaretAt = Delay.throttle(function (clientX, clientY) {
159 + editor._selectionOverrides.hideFakeCaret();
160 + editor.selection.placeCaretAt(clientX, clientY);
161 + }, 0);
162 +
163 + return function (e) {
164 + var movement = Math.max(Math.abs(e.screenX - state.screenX), Math.abs(e.screenY - state.screenY));
165 +
166 + if (hasDraggableElement(state) && !state.dragging && movement > 10) {
167 + var args = editor.fire('dragstart', {target: state.element});
168 + if (args.isDefaultPrevented()) {
169 + return;
170 + }
171 +
172 + state.dragging = true;
173 + editor.focus();
174 + }
175 +
176 + if (state.dragging) {
177 + var targetPos = applyRelPos(state, MousePosition.calc(editor, e));
178 +
179 + appendGhostToBody(state.ghost, editor.getBody());
180 + moveGhost(state.ghost, targetPos, state.width, state.height, state.maxX, state.maxY);
181 +
182 + throttledPlaceCaretAt(e.clientX, e.clientY);
183 + }
184 + };
185 + };
186 +
187 + var drop = function (state, editor) {
188 + return function (e) {
189 + if (state.dragging) {
190 + if (isValidDropTarget(editor, editor.selection.getNode(), state.element)) {
191 + var targetClone = cloneElement(state.element);
192 +
193 + var args = editor.fire('drop', {
194 + targetClone: targetClone,
195 + clientX: e.clientX,
196 + clientY: e.clientY
197 + });
198 +
199 + if (!args.isDefaultPrevented()) {
200 + targetClone = args.targetClone;
201 +
202 + editor.undoManager.transact(function() {
203 + removeElement(state.element);
204 + editor.insertContent(editor.dom.getOuterHTML(targetClone));
205 + editor._selectionOverrides.hideFakeCaret();
206 + });
207 + }
208 + }
209 + }
210 +
211 + removeDragState(state);
212 + };
213 + };
214 +
215 + var stop = function (state, editor) {
216 + return function () {
217 + removeDragState(state);
218 + if (state.dragging) {
219 + editor.fire('dragend');
220 + }
221 + };
222 + };
223 +
224 + var removeDragState = function (state) {
225 + state.dragging = false;
226 + state.element = null;
227 + removeElement(state.ghost);
228 + };
229 +
230 + var bindFakeDragEvents = function (editor) {
231 + var state = {}, pageDom, dragStartHandler, dragHandler, dropHandler, dragEndHandler, rootDocument;
232 +
233 + pageDom = DOMUtils.DOM;
234 + rootDocument = document;
235 + dragStartHandler = start(state, editor);
236 + dragHandler = move(state, editor);
237 + dropHandler = drop(state, editor);
238 + dragEndHandler = stop(state, editor);
239 +
240 + editor.on('mousedown', dragStartHandler);
241 + editor.on('mousemove', dragHandler);
242 + editor.on('mouseup', dropHandler);
243 +
244 + pageDom.bind(rootDocument, 'mousemove', dragHandler);
245 + pageDom.bind(rootDocument, 'mouseup', dragEndHandler);
246 +
247 + editor.on('remove', function () {
248 + pageDom.unbind(rootDocument, 'mousemove', dragHandler);
249 + pageDom.unbind(rootDocument, 'mouseup', dragEndHandler);
250 + });
251 + };
252 +
253 + var blockIeDrop = function (editor) {
254 + editor.on('drop', function(e) {
255 + // FF doesn't pass out clientX/clientY for drop since this is for IE we just use null instead
256 + var realTarget = typeof e.clientX !== 'undefined' ? editor.getDoc().elementFromPoint(e.clientX, e.clientY) : null;
257 +
258 + if (isContentEditableFalse(realTarget) || isContentEditableFalse(editor.dom.getContentEditableParent(realTarget))) {
259 + e.preventDefault();
260 + }
261 + });
262 + };
263 +
264 + var init = function (editor) {
265 + bindFakeDragEvents(editor);
266 + blockIeDrop(editor);
267 + };
268 +
269 + return {
270 + init: init
271 + };
272 +});
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
1 +/**
2 + * EditorObservable.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * This mixin contains the event logic for the tinymce.Editor class.
13 + *
14 + * @mixin tinymce.EditorObservable
15 + * @extends tinymce.util.Observable
16 + */
17 +define("tinymce/EditorObservable", [
18 + "tinymce/util/Observable",
19 + "tinymce/dom/DOMUtils",
20 + "tinymce/util/Tools"
21 +], function(Observable, DOMUtils, Tools) {
22 + var DOM = DOMUtils.DOM, customEventRootDelegates;
23 +
24 + /**
25 + * Returns the event target so for the specified event. Some events fire
26 + * only on document, some fire on documentElement etc. This also handles the
27 + * custom event root setting where it returns that element instead of the body.
28 + *
29 + * @private
30 + * @param {tinymce.Editor} editor Editor instance to get event target from.
31 + * @param {String} eventName Name of the event for example "click".
32 + * @return {Element/Document} HTML Element or document target to bind on.
33 + */
34 + function getEventTarget(editor, eventName) {
35 + if (eventName == 'selectionchange') {
36 + return editor.getDoc();
37 + }
38 +
39 + // Need to bind mousedown/mouseup etc to document not body in iframe mode
40 + // Since the user might click on the HTML element not the BODY
41 + if (!editor.inline && /^mouse|touch|click|contextmenu|drop|dragover|dragend/.test(eventName)) {
42 + return editor.getDoc().documentElement;
43 + }
44 +
45 + // Bind to event root instead of body if it's defined
46 + if (editor.settings.event_root) {
47 + if (!editor.eventRoot) {
48 + editor.eventRoot = DOM.select(editor.settings.event_root)[0];
49 + }
50 +
51 + return editor.eventRoot;
52 + }
53 +
54 + return editor.getBody();
55 + }
56 +
57 + /**
58 + * Binds a event delegate for the specified name this delegate will fire
59 + * the event to the editor dispatcher.
60 + *
61 + * @private
62 + * @param {tinymce.Editor} editor Editor instance to get event target from.
63 + * @param {String} eventName Name of the event for example "click".
64 + */
65 + function bindEventDelegate(editor, eventName) {
66 + var eventRootElm = getEventTarget(editor, eventName), delegate;
67 +
68 + function isListening(editor) {
69 + return !editor.hidden && !editor.readonly;
70 + }
71 +
72 + if (!editor.delegates) {
73 + editor.delegates = {};
74 + }
75 +
76 + if (editor.delegates[eventName]) {
77 + return;
78 + }
79 +
80 + if (editor.settings.event_root) {
81 + if (!customEventRootDelegates) {
82 + customEventRootDelegates = {};
83 + editor.editorManager.on('removeEditor', function() {
84 + var name;
85 +
86 + if (!editor.editorManager.activeEditor) {
87 + if (customEventRootDelegates) {
88 + for (name in customEventRootDelegates) {
89 + editor.dom.unbind(getEventTarget(editor, name));
90 + }
91 +
92 + customEventRootDelegates = null;
93 + }
94 + }
95 + });
96 + }
97 +
98 + if (customEventRootDelegates[eventName]) {
99 + return;
100 + }
101 +
102 + delegate = function(e) {
103 + var target = e.target, editors = editor.editorManager.editors, i = editors.length;
104 +
105 + while (i--) {
106 + var body = editors[i].getBody();
107 +
108 + if (body === target || DOM.isChildOf(target, body)) {
109 + if (isListening(editors[i])) {
110 + editors[i].fire(eventName, e);
111 + }
112 + }
113 + }
114 + };
115 +
116 + customEventRootDelegates[eventName] = delegate;
117 + DOM.bind(eventRootElm, eventName, delegate);
118 + } else {
119 + delegate = function(e) {
120 + if (isListening(editor)) {
121 + editor.fire(eventName, e);
122 + }
123 + };
124 +
125 + DOM.bind(eventRootElm, eventName, delegate);
126 + editor.delegates[eventName] = delegate;
127 + }
128 + }
129 +
130 + var EditorObservable = {
131 + /**
132 + * Bind any pending event delegates. This gets executed after the target body/document is created.
133 + *
134 + * @private
135 + */
136 + bindPendingEventDelegates: function() {
137 + var self = this;
138 +
139 + Tools.each(self._pendingNativeEvents, function(name) {
140 + bindEventDelegate(self, name);
141 + });
142 + },
143 +
144 + /**
145 + * Toggles a native event on/off this is called by the EventDispatcher when
146 + * the first native event handler is added and when the last native event handler is removed.
147 + *
148 + * @private
149 + */
150 + toggleNativeEvent: function(name, state) {
151 + var self = this;
152 +
153 + // Never bind focus/blur since the FocusManager fakes those
154 + if (name == "focus" || name == "blur") {
155 + return;
156 + }
157 +
158 + if (state) {
159 + if (self.initialized) {
160 + bindEventDelegate(self, name);
161 + } else {
162 + if (!self._pendingNativeEvents) {
163 + self._pendingNativeEvents = [name];
164 + } else {
165 + self._pendingNativeEvents.push(name);
166 + }
167 + }
168 + } else if (self.initialized) {
169 + self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]);
170 + delete self.delegates[name];
171 + }
172 + },
173 +
174 + /**
175 + * Unbinds all native event handlers that means delegates, custom events bound using the Events API etc.
176 + *
177 + * @private
178 + */
179 + unbindAllNativeEvents: function() {
180 + var self = this, name;
181 +
182 + if (self.delegates) {
183 + for (name in self.delegates) {
184 + self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]);
185 + }
186 +
187 + delete self.delegates;
188 + }
189 +
190 + if (!self.inline) {
191 + self.getBody().onload = null;
192 + self.dom.unbind(self.getWin());
193 + self.dom.unbind(self.getDoc());
194 + }
195 +
196 + self.dom.unbind(self.getBody());
197 + self.dom.unbind(self.getContainer());
198 + }
199 + };
200 +
201 + EditorObservable = Tools.extend({}, Observable, EditorObservable);
202 +
203 + return EditorObservable;
204 +});
1 +/**
2 + * EditorUpload.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * Handles image uploads, updates undo stack and patches over various internal functions.
13 + *
14 + * @private
15 + * @class tinymce.EditorUpload
16 + */
17 +define("tinymce/EditorUpload", [
18 + "tinymce/util/Arr",
19 + "tinymce/file/Uploader",
20 + "tinymce/file/ImageScanner",
21 + "tinymce/file/BlobCache",
22 + "tinymce/file/UploadStatus"
23 +], function(Arr, Uploader, ImageScanner, BlobCache, UploadStatus) {
24 + return function(editor) {
25 + var blobCache = new BlobCache(), uploader, imageScanner, settings = editor.settings;
26 + var uploadStatus = new UploadStatus();
27 +
28 + function aliveGuard(callback) {
29 + return function(result) {
30 + if (editor.selection) {
31 + return callback(result);
32 + }
33 +
34 + return [];
35 + };
36 + }
37 +
38 + // Replaces strings without regexps to avoid FF regexp to big issue
39 + function replaceString(content, search, replace) {
40 + var index = 0;
41 +
42 + do {
43 + index = content.indexOf(search, index);
44 +
45 + if (index !== -1) {
46 + content = content.substring(0, index) + replace + content.substr(index + search.length);
47 + index += replace.length - search.length + 1;
48 + }
49 + } while (index !== -1);
50 +
51 + return content;
52 + }
53 +
54 + function replaceImageUrl(content, targetUrl, replacementUrl) {
55 + content = replaceString(content, 'src="' + targetUrl + '"', 'src="' + replacementUrl + '"');
56 + content = replaceString(content, 'data-mce-src="' + targetUrl + '"', 'data-mce-src="' + replacementUrl + '"');
57 +
58 + return content;
59 + }
60 +
61 + function replaceUrlInUndoStack(targetUrl, replacementUrl) {
62 + Arr.each(editor.undoManager.data, function(level) {
63 + level.content = replaceImageUrl(level.content, targetUrl, replacementUrl);
64 + });
65 + }
66 +
67 + function openNotification() {
68 + return editor.notificationManager.open({
69 + text: editor.translate('Image uploading...'),
70 + type: 'info',
71 + timeout: -1,
72 + progressBar: true
73 + });
74 + }
75 +
76 + function replaceImageUri(image, resultUri) {
77 + blobCache.removeByUri(image.src);
78 + replaceUrlInUndoStack(image.src, resultUri);
79 +
80 + editor.$(image).attr({
81 + src: resultUri,
82 + 'data-mce-src': editor.convertURL(resultUri, 'src')
83 + });
84 + }
85 +
86 + function uploadImages(callback) {
87 + if (!uploader) {
88 + uploader = new Uploader(uploadStatus, {
89 + url: settings.images_upload_url,
90 + basePath: settings.images_upload_base_path,
91 + credentials: settings.images_upload_credentials,
92 + handler: settings.images_upload_handler
93 + });
94 + }
95 +
96 + return scanForImages().then(aliveGuard(function(imageInfos) {
97 + var blobInfos;
98 +
99 + blobInfos = Arr.map(imageInfos, function(imageInfo) {
100 + return imageInfo.blobInfo;
101 + });
102 +
103 + return uploader.upload(blobInfos, openNotification).then(aliveGuard(function(result) {
104 + result = Arr.map(result, function(uploadInfo, index) {
105 + var image = imageInfos[index].image;
106 +
107 + if (uploadInfo.status && editor.settings.images_replace_blob_uris !== false) {
108 + replaceImageUri(image, uploadInfo.url);
109 + }
110 +
111 + return {
112 + element: image,
113 + status: uploadInfo.status
114 + };
115 + });
116 +
117 + if (callback) {
118 + callback(result);
119 + }
120 +
121 + return result;
122 + }));
123 + }));
124 + }
125 +
126 + function uploadImagesAuto(callback) {
127 + if (settings.automatic_uploads !== false) {
128 + return uploadImages(callback);
129 + }
130 + }
131 +
132 + function isValidDataUriImage(imgElm) {
133 + return settings.images_dataimg_filter ? settings.images_dataimg_filter(imgElm) : true;
134 + }
135 +
136 + function scanForImages() {
137 + if (!imageScanner) {
138 + imageScanner = new ImageScanner(uploadStatus, blobCache);
139 + }
140 +
141 + return imageScanner.findAll(editor.getBody(), isValidDataUriImage).then(aliveGuard(function(result) {
142 + Arr.each(result, function(resultItem) {
143 + replaceUrlInUndoStack(resultItem.image.src, resultItem.blobInfo.blobUri());
144 + resultItem.image.src = resultItem.blobInfo.blobUri();
145 + resultItem.image.removeAttribute('data-mce-src');
146 + });
147 +
148 + return result;
149 + }));
150 + }
151 +
152 + function destroy() {
153 + blobCache.destroy();
154 + uploadStatus.destroy();
155 + imageScanner = uploader = null;
156 + }
157 +
158 + function replaceBlobUris(content) {
159 + return content.replace(/src="(blob:[^"]+)"/g, function(match, blobUri) {
160 + var resultUri = uploadStatus.getResultUri(blobUri);
161 +
162 + if (resultUri) {
163 + return 'src="' + resultUri + '"';
164 + }
165 +
166 + var blobInfo = blobCache.getByUri(blobUri);
167 +
168 + if (!blobInfo) {
169 + blobInfo = Arr.reduce(editor.editorManager.editors, function(result, editor) {
170 + return result || editor.editorUpload.blobCache.getByUri(blobUri);
171 + }, null);
172 + }
173 +
174 + if (blobInfo) {
175 + return 'src="data:' + blobInfo.blob().type + ';base64,' + blobInfo.base64() + '"';
176 + }
177 +
178 + return match;
179 + });
180 + }
181 +
182 + editor.on('setContent', function() {
183 + if (editor.settings.automatic_uploads !== false) {
184 + uploadImagesAuto();
185 + } else {
186 + scanForImages();
187 + }
188 + });
189 +
190 + editor.on('RawSaveContent', function(e) {
191 + e.content = replaceBlobUris(e.content);
192 + });
193 +
194 + editor.on('getContent', function(e) {
195 + if (e.source_view || e.format == 'raw') {
196 + return;
197 + }
198 +
199 + e.content = replaceBlobUris(e.content);
200 + });
201 +
202 + editor.on('PostRender', function() {
203 + editor.parser.addNodeFilter('img', function(images) {
204 + Arr.each(images, function(img) {
205 + var src = img.attr('src');
206 +
207 + if (blobCache.getByUri(src)) {
208 + return;
209 + }
210 +
211 + var resultUri = uploadStatus.getResultUri(src);
212 + if (resultUri) {
213 + img.attr('src', resultUri);
214 + }
215 + });
216 + });
217 + });
218 +
219 + return {
220 + blobCache: blobCache,
221 + uploadImages: uploadImages,
222 + uploadImagesAuto: uploadImagesAuto,
223 + scanForImages: scanForImages,
224 + destroy: destroy
225 + };
226 + };
227 +});
...\ No newline at end of file ...\ No newline at end of file
This diff is collapsed. Click to expand it.
1 +/**
2 + * Env.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * This class contains various environment constants like browser versions etc.
13 + * Normally you don't want to sniff specific browser versions but sometimes you have
14 + * to when it's impossible to feature detect. So use this with care.
15 + *
16 + * @class tinymce.Env
17 + * @static
18 + */
19 +define("tinymce/Env", [], function() {
20 + var nav = navigator, userAgent = nav.userAgent;
21 + var opera, webkit, ie, ie11, ie12, gecko, mac, iDevice, android, fileApi, phone, tablet, windowsPhone;
22 +
23 + function matchMediaQuery(query) {
24 + return "matchMedia" in window ? matchMedia(query).matches : false;
25 + }
26 +
27 + opera = window.opera && window.opera.buildNumber;
28 + android = /Android/.test(userAgent);
29 + webkit = /WebKit/.test(userAgent);
30 + ie = !webkit && !opera && (/MSIE/gi).test(userAgent) && (/Explorer/gi).test(nav.appName);
31 + ie = ie && /MSIE (\w+)\./.exec(userAgent)[1];
32 + ie11 = userAgent.indexOf('Trident/') != -1 && (userAgent.indexOf('rv:') != -1 || nav.appName.indexOf('Netscape') != -1) ? 11 : false;
33 + ie12 = (userAgent.indexOf('Edge/') != -1 && !ie && !ie11) ? 12 : false;
34 + ie = ie || ie11 || ie12;
35 + gecko = !webkit && !ie11 && /Gecko/.test(userAgent);
36 + mac = userAgent.indexOf('Mac') != -1;
37 + iDevice = /(iPad|iPhone)/.test(userAgent);
38 + fileApi = "FormData" in window && "FileReader" in window && "URL" in window && !!URL.createObjectURL;
39 + phone = matchMediaQuery("only screen and (max-device-width: 480px)") && (android || iDevice);
40 + tablet = matchMediaQuery("only screen and (min-width: 800px)") && (android || iDevice);
41 + windowsPhone = userAgent.indexOf('Windows Phone') != -1;
42 +
43 + if (ie12) {
44 + webkit = false;
45 + }
46 +
47 + // Is a iPad/iPhone and not on iOS5 sniff the WebKit version since older iOS WebKit versions
48 + // says it has contentEditable support but there is no visible caret.
49 + var contentEditable = !iDevice || fileApi || userAgent.match(/AppleWebKit\/(\d*)/)[1] >= 534;
50 +
51 + return {
52 + /**
53 + * Constant that is true if the browser is Opera.
54 + *
55 + * @property opera
56 + * @type Boolean
57 + * @final
58 + */
59 + opera: opera,
60 +
61 + /**
62 + * Constant that is true if the browser is WebKit (Safari/Chrome).
63 + *
64 + * @property webKit
65 + * @type Boolean
66 + * @final
67 + */
68 + webkit: webkit,
69 +
70 + /**
71 + * Constant that is more than zero if the browser is IE.
72 + *
73 + * @property ie
74 + * @type Boolean
75 + * @final
76 + */
77 + ie: ie,
78 +
79 + /**
80 + * Constant that is true if the browser is Gecko.
81 + *
82 + * @property gecko
83 + * @type Boolean
84 + * @final
85 + */
86 + gecko: gecko,
87 +
88 + /**
89 + * Constant that is true if the os is Mac OS.
90 + *
91 + * @property mac
92 + * @type Boolean
93 + * @final
94 + */
95 + mac: mac,
96 +
97 + /**
98 + * Constant that is true if the os is iOS.
99 + *
100 + * @property iOS
101 + * @type Boolean
102 + * @final
103 + */
104 + iOS: iDevice,
105 +
106 + /**
107 + * Constant that is true if the os is android.
108 + *
109 + * @property android
110 + * @type Boolean
111 + * @final
112 + */
113 + android: android,
114 +
115 + /**
116 + * Constant that is true if the browser supports editing.
117 + *
118 + * @property contentEditable
119 + * @type Boolean
120 + * @final
121 + */
122 + contentEditable: contentEditable,
123 +
124 + /**
125 + * Transparent image data url.
126 + *
127 + * @property transparentSrc
128 + * @type Boolean
129 + * @final
130 + */
131 + transparentSrc: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
132 +
133 + /**
134 + * Returns true/false if the browser can or can't place the caret after a inline block like an image.
135 + *
136 + * @property noCaretAfter
137 + * @type Boolean
138 + * @final
139 + */
140 + caretAfter: ie != 8,
141 +
142 + /**
143 + * Constant that is true if the browser supports native DOM Ranges. IE 9+.
144 + *
145 + * @property range
146 + * @type Boolean
147 + */
148 + range: window.getSelection && "Range" in window,
149 +
150 + /**
151 + * Returns the IE document mode for non IE browsers this will fake IE 10.
152 + *
153 + * @property documentMode
154 + * @type Number
155 + */
156 + documentMode: ie && !ie12 ? (document.documentMode || 7) : 10,
157 +
158 + /**
159 + * Constant that is true if the browser has a modern file api.
160 + *
161 + * @property fileApi
162 + * @type Boolean
163 + */
164 + fileApi: fileApi,
165 +
166 + /**
167 + * Constant that is true if the browser supports contentEditable=false regions.
168 + *
169 + * @property ceFalse
170 + * @type Boolean
171 + */
172 + ceFalse: (ie === false || ie > 8),
173 +
174 + /**
175 + * Constant if CSP mode is possible or not. Meaning we can't use script urls for the iframe.
176 + */
177 + canHaveCSP: (ie === false || ie > 11),
178 +
179 + desktop: !phone && !tablet,
180 + windowsPhone: windowsPhone
181 + };
182 +});
1 +/**
2 + * FocusManager.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * This class manages the focus/blur state of the editor. This class is needed since some
13 + * browsers fire false focus/blur states when the selection is moved to a UI dialog or similar.
14 + *
15 + * This class will fire two events focus and blur on the editor instances that got affected.
16 + * It will also handle the restore of selection when the focus is lost and returned.
17 + *
18 + * @class tinymce.FocusManager
19 + */
20 +define("tinymce/FocusManager", [
21 + "tinymce/dom/DOMUtils",
22 + "tinymce/util/Delay",
23 + "tinymce/Env"
24 +], function(DOMUtils, Delay, Env) {
25 + var selectionChangeHandler, documentFocusInHandler, documentMouseUpHandler, DOM = DOMUtils.DOM;
26 +
27 + /**
28 + * Constructs a new focus manager instance.
29 + *
30 + * @constructor FocusManager
31 + * @param {tinymce.EditorManager} editorManager Editor manager instance to handle focus for.
32 + */
33 + function FocusManager(editorManager) {
34 + function getActiveElement() {
35 + try {
36 + return document.activeElement;
37 + } catch (ex) {
38 + // IE sometimes fails to get the activeElement when resizing table
39 + // TODO: Investigate this
40 + return document.body;
41 + }
42 + }
43 +
44 + // We can't store a real range on IE 11 since it gets mutated so we need to use a bookmark object
45 + // TODO: Move this to a separate range utils class since it's it's logic is present in Selection as well.
46 + function createBookmark(dom, rng) {
47 + if (rng && rng.startContainer) {
48 + // Verify that the range is within the root of the editor
49 + if (!dom.isChildOf(rng.startContainer, dom.getRoot()) || !dom.isChildOf(rng.endContainer, dom.getRoot())) {
50 + return;
51 + }
52 +
53 + return {
54 + startContainer: rng.startContainer,
55 + startOffset: rng.startOffset,
56 + endContainer: rng.endContainer,
57 + endOffset: rng.endOffset
58 + };
59 + }
60 +
61 + return rng;
62 + }
63 +
64 + function bookmarkToRng(editor, bookmark) {
65 + var rng;
66 +
67 + if (bookmark.startContainer) {
68 + rng = editor.getDoc().createRange();
69 + rng.setStart(bookmark.startContainer, bookmark.startOffset);
70 + rng.setEnd(bookmark.endContainer, bookmark.endOffset);
71 + } else {
72 + rng = bookmark;
73 + }
74 +
75 + return rng;
76 + }
77 +
78 + function isUIElement(elm) {
79 + return !!DOM.getParent(elm, FocusManager.isEditorUIElement);
80 + }
81 +
82 + function registerEvents(e) {
83 + var editor = e.editor;
84 +
85 + editor.on('init', function() {
86 + // Gecko/WebKit has ghost selections in iframes and IE only has one selection per browser tab
87 + if (editor.inline || Env.ie) {
88 + // Use the onbeforedeactivate event when available since it works better see #7023
89 + if ("onbeforedeactivate" in document && Env.ie < 9) {
90 + editor.dom.bind(editor.getBody(), 'beforedeactivate', function(e) {
91 + if (e.target != editor.getBody()) {
92 + return;
93 + }
94 +
95 + try {
96 + editor.lastRng = editor.selection.getRng();
97 + } catch (ex) {
98 + // IE throws "Unexcpected call to method or property access" some times so lets ignore it
99 + }
100 + });
101 + } else {
102 + // On other browsers take snapshot on nodechange in inline mode since they have Ghost selections for iframes
103 + editor.on('nodechange mouseup keyup', function(e) {
104 + var node = getActiveElement();
105 +
106 + // Only act on manual nodechanges
107 + if (e.type == 'nodechange' && e.selectionChange) {
108 + return;
109 + }
110 +
111 + // IE 11 reports active element as iframe not body of iframe
112 + if (node && node.id == editor.id + '_ifr') {
113 + node = editor.getBody();
114 + }
115 +
116 + if (editor.dom.isChildOf(node, editor.getBody())) {
117 + editor.lastRng = editor.selection.getRng();
118 + }
119 + });
120 + }
121 +
122 + // Handles the issue with WebKit not retaining selection within inline document
123 + // If the user releases the mouse out side the body since a mouse up event wont occur on the body
124 + if (Env.webkit && !selectionChangeHandler) {
125 + selectionChangeHandler = function() {
126 + var activeEditor = editorManager.activeEditor;
127 +
128 + if (activeEditor && activeEditor.selection) {
129 + var rng = activeEditor.selection.getRng();
130 +
131 + // Store when it's non collapsed
132 + if (rng && !rng.collapsed) {
133 + editor.lastRng = rng;
134 + }
135 + }
136 + };
137 +
138 + DOM.bind(document, 'selectionchange', selectionChangeHandler);
139 + }
140 + }
141 + });
142 +
143 + editor.on('setcontent', function() {
144 + editor.lastRng = null;
145 + });
146 +
147 + // Remove last selection bookmark on mousedown see #6305
148 + editor.on('mousedown', function() {
149 + editor.selection.lastFocusBookmark = null;
150 + });
151 +
152 + editor.on('focusin', function() {
153 + var focusedEditor = editorManager.focusedEditor, lastRng;
154 +
155 + if (editor.selection.lastFocusBookmark) {
156 + lastRng = bookmarkToRng(editor, editor.selection.lastFocusBookmark);
157 + editor.selection.lastFocusBookmark = null;
158 + editor.selection.setRng(lastRng);
159 + }
160 +
161 + if (focusedEditor != editor) {
162 + if (focusedEditor) {
163 + focusedEditor.fire('blur', {focusedEditor: editor});
164 + }
165 +
166 + editorManager.setActive(editor);
167 + editorManager.focusedEditor = editor;
168 + editor.fire('focus', {blurredEditor: focusedEditor});
169 + editor.focus(true);
170 + }
171 +
172 + editor.lastRng = null;
173 + });
174 +
175 + editor.on('focusout', function() {
176 + Delay.setEditorTimeout(editor, function() {
177 + var focusedEditor = editorManager.focusedEditor;
178 +
179 + // Still the same editor the blur was outside any editor UI
180 + if (!isUIElement(getActiveElement()) && focusedEditor == editor) {
181 + editor.fire('blur', {focusedEditor: null});
182 + editorManager.focusedEditor = null;
183 +
184 + // Make sure selection is valid could be invalid if the editor is blured and removed before the timeout occurs
185 + if (editor.selection) {
186 + editor.selection.lastFocusBookmark = null;
187 + }
188 + }
189 + });
190 + });
191 +
192 + // Check if focus is moved to an element outside the active editor by checking if the target node
193 + // isn't within the body of the activeEditor nor a UI element such as a dialog child control
194 + if (!documentFocusInHandler) {
195 + documentFocusInHandler = function(e) {
196 + var activeEditor = editorManager.activeEditor, target;
197 +
198 + target = e.target;
199 +
200 + if (activeEditor && target.ownerDocument == document) {
201 + // Check to make sure we have a valid selection don't update the bookmark if it's
202 + // a focusin to the body of the editor see #7025
203 + if (activeEditor.selection && target != activeEditor.getBody()) {
204 + activeEditor.selection.lastFocusBookmark = createBookmark(activeEditor.dom, activeEditor.lastRng);
205 + }
206 +
207 + // Fire a blur event if the element isn't a UI element
208 + if (target != document.body && !isUIElement(target) && editorManager.focusedEditor == activeEditor) {
209 + activeEditor.fire('blur', {focusedEditor: null});
210 + editorManager.focusedEditor = null;
211 + }
212 + }
213 + };
214 +
215 + DOM.bind(document, 'focusin', documentFocusInHandler);
216 + }
217 +
218 + // Handle edge case when user starts the selection inside the editor and releases
219 + // the mouse outside the editor producing a new selection. This weird workaround is needed since
220 + // Gecko doesn't have the "selectionchange" event we need to do this. Fixes: #6843
221 + if (editor.inline && !documentMouseUpHandler) {
222 + documentMouseUpHandler = function(e) {
223 + var activeEditor = editorManager.activeEditor, dom = activeEditor.dom;
224 +
225 + if (activeEditor.inline && dom && !dom.isChildOf(e.target, activeEditor.getBody())) {
226 + var rng = activeEditor.selection.getRng();
227 +
228 + if (!rng.collapsed) {
229 + activeEditor.lastRng = rng;
230 + }
231 + }
232 + };
233 +
234 + DOM.bind(document, 'mouseup', documentMouseUpHandler);
235 + }
236 + }
237 +
238 + function unregisterDocumentEvents(e) {
239 + if (editorManager.focusedEditor == e.editor) {
240 + editorManager.focusedEditor = null;
241 + }
242 +
243 + if (!editorManager.activeEditor) {
244 + DOM.unbind(document, 'selectionchange', selectionChangeHandler);
245 + DOM.unbind(document, 'focusin', documentFocusInHandler);
246 + DOM.unbind(document, 'mouseup', documentMouseUpHandler);
247 + selectionChangeHandler = documentFocusInHandler = documentMouseUpHandler = null;
248 + }
249 + }
250 +
251 + editorManager.on('AddEditor', registerEvents);
252 + editorManager.on('RemoveEditor', unregisterDocumentEvents);
253 + }
254 +
255 + /**
256 + * Returns true if the specified element is part of the UI for example an button or text input.
257 + *
258 + * @method isEditorUIElement
259 + * @param {Element} elm Element to check if it's part of the UI or not.
260 + * @return {Boolean} True/false state if the element is part of the UI or not.
261 + */
262 + FocusManager.isEditorUIElement = function(elm) {
263 + // Needs to be converted to string since svg can have focus: #6776
264 + return elm.className.toString().indexOf('mce-') !== -1;
265 + };
266 +
267 + return FocusManager;
268 +});
1 +/**
2 + * ForceBlocks.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * Makes sure that everything gets wrapped in paragraphs.
13 + *
14 + * @private
15 + * @class tinymce.ForceBlocks
16 + */
17 +define("tinymce/ForceBlocks", [], function() {
18 + return function(editor) {
19 + var settings = editor.settings, dom = editor.dom, selection = editor.selection;
20 + var schema = editor.schema, blockElements = schema.getBlockElements();
21 +
22 + function addRootBlocks() {
23 + var node = selection.getStart(), rootNode = editor.getBody(), rng;
24 + var startContainer, startOffset, endContainer, endOffset, rootBlockNode;
25 + var tempNode, offset = -0xFFFFFF, wrapped, restoreSelection;
26 + var tmpRng, rootNodeName, forcedRootBlock;
27 +
28 + forcedRootBlock = settings.forced_root_block;
29 +
30 + if (!node || node.nodeType !== 1 || !forcedRootBlock) {
31 + return;
32 + }
33 +
34 + // Check if node is wrapped in block
35 + while (node && node != rootNode) {
36 + if (blockElements[node.nodeName]) {
37 + return;
38 + }
39 +
40 + node = node.parentNode;
41 + }
42 +
43 + // Get current selection
44 + rng = selection.getRng();
45 + if (rng.setStart) {
46 + startContainer = rng.startContainer;
47 + startOffset = rng.startOffset;
48 + endContainer = rng.endContainer;
49 + endOffset = rng.endOffset;
50 +
51 + try {
52 + restoreSelection = editor.getDoc().activeElement === rootNode;
53 + } catch (ex) {
54 + // IE throws unspecified error here sometimes
55 + }
56 + } else {
57 + // Force control range into text range
58 + if (rng.item) {
59 + node = rng.item(0);
60 + rng = editor.getDoc().body.createTextRange();
61 + rng.moveToElementText(node);
62 + }
63 +
64 + restoreSelection = rng.parentElement().ownerDocument === editor.getDoc();
65 + tmpRng = rng.duplicate();
66 + tmpRng.collapse(true);
67 + startOffset = tmpRng.move('character', offset) * -1;
68 +
69 + if (!tmpRng.collapsed) {
70 + tmpRng = rng.duplicate();
71 + tmpRng.collapse(false);
72 + endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
73 + }
74 + }
75 +
76 + // Wrap non block elements and text nodes
77 + node = rootNode.firstChild;
78 + rootNodeName = rootNode.nodeName.toLowerCase();
79 + while (node) {
80 + // TODO: Break this up, too complex
81 + if (((node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName]))) &&
82 + schema.isValidChild(rootNodeName, forcedRootBlock.toLowerCase())) {
83 + // Remove empty text nodes
84 + if (node.nodeType === 3 && node.nodeValue.length === 0) {
85 + tempNode = node;
86 + node = node.nextSibling;
87 + dom.remove(tempNode);
88 + continue;
89 + }
90 +
91 + if (!rootBlockNode) {
92 + rootBlockNode = dom.create(forcedRootBlock, editor.settings.forced_root_block_attrs);
93 + node.parentNode.insertBefore(rootBlockNode, node);
94 + wrapped = true;
95 + }
96 +
97 + tempNode = node;
98 + node = node.nextSibling;
99 + rootBlockNode.appendChild(tempNode);
100 + } else {
101 + rootBlockNode = null;
102 + node = node.nextSibling;
103 + }
104 + }
105 +
106 + if (wrapped && restoreSelection) {
107 + if (rng.setStart) {
108 + rng.setStart(startContainer, startOffset);
109 + rng.setEnd(endContainer, endOffset);
110 + selection.setRng(rng);
111 + } else {
112 + // Only select if the previous selection was inside the document to prevent auto focus in quirks mode
113 + try {
114 + rng = editor.getDoc().body.createTextRange();
115 + rng.moveToElementText(rootNode);
116 + rng.collapse(true);
117 + rng.moveStart('character', startOffset);
118 +
119 + if (endOffset > 0) {
120 + rng.moveEnd('character', endOffset);
121 + }
122 +
123 + rng.select();
124 + } catch (ex) {
125 + // Ignore
126 + }
127 + }
128 +
129 + editor.nodeChanged();
130 + }
131 + }
132 +
133 + // Force root blocks
134 + if (settings.forced_root_block) {
135 + editor.on('NodeChange', addRootBlocks);
136 + }
137 + };
138 +});
...\ No newline at end of file ...\ No newline at end of file
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
1 +/**
2 + * InsertList.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * Handles inserts of lists into the editor instance.
13 + *
14 + * @class tinymce.InsertList
15 + * @private
16 + */
17 +define("tinymce/InsertList", [
18 + "tinymce/util/Tools",
19 + "tinymce/caret/CaretWalker",
20 + "tinymce/caret/CaretPosition"
21 +], function(Tools, CaretWalker, CaretPosition) {
22 + var isListFragment = function(fragment) {
23 + var firstChild = fragment.firstChild;
24 + var lastChild = fragment.lastChild;
25 +
26 + // Skip meta since it's likely <meta><ul>..</ul>
27 + if (firstChild && firstChild.name === 'meta') {
28 + firstChild = firstChild.next;
29 + }
30 +
31 + // Skip mce_marker since it's likely <ul>..</ul><span id="mce_marker"></span>
32 + if (lastChild && lastChild.attr('id') === 'mce_marker') {
33 + lastChild = lastChild.prev;
34 + }
35 +
36 + if (!firstChild || firstChild !== lastChild) {
37 + return false;
38 + }
39 +
40 + return firstChild.name === 'ul' || firstChild.name === 'ol';
41 + };
42 +
43 + var cleanupDomFragment = function (domFragment) {
44 + var firstChild = domFragment.firstChild;
45 + var lastChild = domFragment.lastChild;
46 +
47 + // TODO: remove the meta tag from paste logic
48 + if (firstChild && firstChild.nodeName === 'META') {
49 + firstChild.parentNode.removeChild(firstChild);
50 + }
51 +
52 + if (lastChild && lastChild.id === 'mce_marker') {
53 + lastChild.parentNode.removeChild(lastChild);
54 + }
55 +
56 + return domFragment;
57 + };
58 +
59 + var toDomFragment = function(dom, serializer, fragment) {
60 + var html = serializer.serialize(fragment);
61 + var domFragment = dom.createFragment(html);
62 +
63 + return cleanupDomFragment(domFragment);
64 + };
65 +
66 + var listItems = function(elm) {
67 + return Tools.grep(elm.childNodes, function(child) {
68 + return child.nodeName === 'LI';
69 + });
70 + };
71 +
72 + var isEmpty = function (elm) {
73 + return !elm.firstChild;
74 + };
75 +
76 + var trimListItems = function(elms) {
77 + return elms.length > 0 && isEmpty(elms[elms.length - 1]) ? elms.slice(0, -1) : elms;
78 + };
79 +
80 + var getParentLi = function(dom, node) {
81 + var parentBlock = dom.getParent(node, dom.isBlock);
82 + return parentBlock && parentBlock.nodeName === 'LI' ? parentBlock : null;
83 + };
84 +
85 + var isParentBlockLi = function(dom, node) {
86 + return !!getParentLi(dom, node);
87 + };
88 +
89 + var getSplit = function(parentNode, rng) {
90 + var beforeRng = rng.cloneRange();
91 + var afterRng = rng.cloneRange();
92 +
93 + beforeRng.setStartBefore(parentNode);
94 + afterRng.setEndAfter(parentNode);
95 +
96 + return [
97 + beforeRng.cloneContents(),
98 + afterRng.cloneContents()
99 + ];
100 + };
101 +
102 + var findFirstIn = function(node, rootNode) {
103 + var caretPos = CaretPosition.before(node);
104 + var caretWalker = new CaretWalker(rootNode);
105 + var newCaretPos = caretWalker.next(caretPos);
106 +
107 + return newCaretPos ? newCaretPos.toRange() : null;
108 + };
109 +
110 + var findLastOf = function(node, rootNode) {
111 + var caretPos = CaretPosition.after(node);
112 + var caretWalker = new CaretWalker(rootNode);
113 + var newCaretPos = caretWalker.prev(caretPos);
114 +
115 + return newCaretPos ? newCaretPos.toRange() : null;
116 + };
117 +
118 + var insertMiddle = function(target, elms, rootNode, rng) {
119 + var parts = getSplit(target, rng);
120 + var parentElm = target.parentNode;
121 +
122 + parentElm.insertBefore(parts[0], target);
123 + Tools.each(elms, function(li) {
124 + parentElm.insertBefore(li, target);
125 + });
126 + parentElm.insertBefore(parts[1], target);
127 + parentElm.removeChild(target);
128 +
129 + return findLastOf(elms[elms.length - 1], rootNode);
130 + };
131 +
132 + var insertBefore = function(target, elms, rootNode) {
133 + var parentElm = target.parentNode;
134 +
135 + Tools.each(elms, function(elm) {
136 + parentElm.insertBefore(elm, target);
137 + });
138 +
139 + return findFirstIn(target, rootNode);
140 + };
141 +
142 + var insertAfter = function(target, elms, rootNode, dom) {
143 + dom.insertAfter(elms.reverse(), target);
144 + return findLastOf(elms[0], rootNode);
145 + };
146 +
147 + var insertAtCaret = function(serializer, dom, rng, fragment) {
148 + var domFragment = toDomFragment(dom, serializer, fragment);
149 + var liTarget = getParentLi(dom, rng.startContainer);
150 + var liElms = trimListItems(listItems(domFragment.firstChild));
151 + var BEGINNING = 1, END = 2;
152 + var rootNode = dom.getRoot();
153 +
154 + var isAt = function(location) {
155 + var caretPos = CaretPosition.fromRangeStart(rng);
156 + var caretWalker = new CaretWalker(dom.getRoot());
157 + var newPos = location === BEGINNING ? caretWalker.prev(caretPos) : caretWalker.next(caretPos);
158 +
159 + return newPos ? getParentLi(dom, newPos.getNode()) !== liTarget : true;
160 + };
161 +
162 + if (isAt(BEGINNING)) {
163 + return insertBefore(liTarget, liElms, rootNode);
164 + } else if (isAt(END)) {
165 + return insertAfter(liTarget, liElms, rootNode, dom);
166 + }
167 +
168 + return insertMiddle(liTarget, liElms, rootNode, rng);
169 + };
170 +
171 + return {
172 + isListFragment: isListFragment,
173 + insertAtCaret: insertAtCaret,
174 + isParentBlockLi: isParentBlockLi,
175 + trimListItems: trimListItems,
176 + listItems: listItems
177 + };
178 +});
...\ No newline at end of file ...\ No newline at end of file
1 +/**
2 + * LegacyInput.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * Converts legacy input to modern HTML.
13 + *
14 + * @class tinymce.LegacyInput
15 + * @private
16 + */
17 +define("tinymce/LegacyInput", [
18 + "tinymce/EditorManager",
19 + "tinymce/util/Tools"
20 +], function(EditorManager, Tools) {
21 + var each = Tools.each, explode = Tools.explode;
22 +
23 + EditorManager.on('AddEditor', function(e) {
24 + var editor = e.editor;
25 +
26 + editor.on('preInit', function() {
27 + var filters, fontSizes, dom, settings = editor.settings;
28 +
29 + function replaceWithSpan(node, styles) {
30 + each(styles, function(value, name) {
31 + if (value) {
32 + dom.setStyle(node, name, value);
33 + }
34 + });
35 +
36 + dom.rename(node, 'span');
37 + }
38 +
39 + function convert(e) {
40 + dom = editor.dom;
41 +
42 + if (settings.convert_fonts_to_spans) {
43 + each(dom.select('font,u,strike', e.node), function(node) {
44 + filters[node.nodeName.toLowerCase()](dom, node);
45 + });
46 + }
47 + }
48 +
49 + if (settings.inline_styles) {
50 + fontSizes = explode(settings.font_size_legacy_values);
51 +
52 + filters = {
53 + font: function(dom, node) {
54 + replaceWithSpan(node, {
55 + backgroundColor: node.style.backgroundColor,
56 + color: node.color,
57 + fontFamily: node.face,
58 + fontSize: fontSizes[parseInt(node.size, 10) - 1]
59 + });
60 + },
61 +
62 + u: function(dom, node) {
63 + // HTML5 allows U element
64 + if (editor.settings.schema === "html4") {
65 + replaceWithSpan(node, {
66 + textDecoration: 'underline'
67 + });
68 + }
69 + },
70 +
71 + strike: function(dom, node) {
72 + replaceWithSpan(node, {
73 + textDecoration: 'line-through'
74 + });
75 + }
76 + };
77 +
78 + editor.on('PreProcess SetContent', convert);
79 + }
80 + });
81 + });
82 +});
...\ No newline at end of file ...\ No newline at end of file
1 +/**
2 + * Mode.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * Mode switcher logic.
13 + *
14 + * @private
15 + * @class tinymce.Mode
16 + */
17 +define("tinymce/Mode", [], function() {
18 + function setEditorCommandState(editor, cmd, state) {
19 + try {
20 + editor.getDoc().execCommand(cmd, false, state);
21 + } catch (ex) {
22 + // Ignore
23 + }
24 + }
25 +
26 + function clickBlocker(editor) {
27 + var target, handler;
28 +
29 + target = editor.getBody();
30 +
31 + handler = function(e) {
32 + if (editor.dom.getParents(e.target, 'a').length > 0) {
33 + e.preventDefault();
34 + }
35 + };
36 +
37 + editor.dom.bind(target, 'click', handler);
38 +
39 + return {
40 + unbind: function() {
41 + editor.dom.unbind(target, 'click', handler);
42 + }
43 + };
44 + }
45 +
46 + function toggleReadOnly(editor, state) {
47 + if (editor._clickBlocker) {
48 + editor._clickBlocker.unbind();
49 + editor._clickBlocker = null;
50 + }
51 +
52 + if (state) {
53 + editor._clickBlocker = clickBlocker(editor);
54 + editor.selection.controlSelection.hideResizeRect();
55 + editor.readonly = true;
56 + editor.getBody().contentEditable = false;
57 + } else {
58 + editor.readonly = false;
59 + editor.getBody().contentEditable = true;
60 + setEditorCommandState(editor, "StyleWithCSS", false);
61 + setEditorCommandState(editor, "enableInlineTableEditing", false);
62 + setEditorCommandState(editor, "enableObjectResizing", false);
63 + editor.focus();
64 + editor.nodeChanged();
65 + }
66 + }
67 +
68 + function setMode(editor, mode) {
69 + var currentMode = editor.readonly ? 'readonly' : 'design';
70 +
71 + if (mode == currentMode) {
72 + return;
73 + }
74 +
75 + if (editor.initialized) {
76 + toggleReadOnly(editor, mode == 'readonly');
77 + } else {
78 + editor.on('init', function() {
79 + toggleReadOnly(editor, mode == 'readonly');
80 + });
81 + }
82 +
83 + // Event is NOT preventable
84 + editor.fire('SwitchMode', {mode: mode});
85 + }
86 +
87 + return {
88 + setMode: setMode
89 + };
90 +});
...\ No newline at end of file ...\ No newline at end of file
1 +/**
2 + * NodeChange.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * This class handles the nodechange event dispatching both manual and through selection change events.
13 + *
14 + * @class tinymce.NodeChange
15 + * @private
16 + */
17 +define("tinymce/NodeChange", [
18 + "tinymce/dom/RangeUtils",
19 + "tinymce/Env",
20 + "tinymce/util/Delay"
21 +], function(RangeUtils, Env, Delay) {
22 + return function(editor) {
23 + var lastRng, lastPath = [];
24 +
25 + /**
26 + * Returns true/false if the current element path has been changed or not.
27 + *
28 + * @private
29 + * @return {Boolean} True if the element path is the same false if it's not.
30 + */
31 + function isSameElementPath(startElm) {
32 + var i, currentPath;
33 +
34 + currentPath = editor.$(startElm).parentsUntil(editor.getBody()).add(startElm);
35 + if (currentPath.length === lastPath.length) {
36 + for (i = currentPath.length; i >= 0; i--) {
37 + if (currentPath[i] !== lastPath[i]) {
38 + break;
39 + }
40 + }
41 +
42 + if (i === -1) {
43 + lastPath = currentPath;
44 + return true;
45 + }
46 + }
47 +
48 + lastPath = currentPath;
49 +
50 + return false;
51 + }
52 +
53 + // Gecko doesn't support the "selectionchange" event
54 + if (!('onselectionchange' in editor.getDoc())) {
55 + editor.on('NodeChange Click MouseUp KeyUp Focus', function(e) {
56 + var nativeRng, fakeRng;
57 +
58 + // Since DOM Ranges mutate on modification
59 + // of the DOM we need to clone it's contents
60 + nativeRng = editor.selection.getRng();
61 + fakeRng = {
62 + startContainer: nativeRng.startContainer,
63 + startOffset: nativeRng.startOffset,
64 + endContainer: nativeRng.endContainer,
65 + endOffset: nativeRng.endOffset
66 + };
67 +
68 + // Always treat nodechange as a selectionchange since applying
69 + // formatting to the current range wouldn't update the range but it's parent
70 + if (e.type == 'nodechange' || !RangeUtils.compareRanges(fakeRng, lastRng)) {
71 + editor.fire('SelectionChange');
72 + }
73 +
74 + lastRng = fakeRng;
75 + });
76 + }
77 +
78 + // IE has a bug where it fires a selectionchange on right click that has a range at the start of the body
79 + // When the contextmenu event fires the selection is located at the right location
80 + editor.on('contextmenu', function() {
81 + editor.fire('SelectionChange');
82 + });
83 +
84 + // Selection change is delayed ~200ms on IE when you click inside the current range
85 + editor.on('SelectionChange', function() {
86 + var startElm = editor.selection.getStart(true);
87 +
88 + // IE 8 will fire a selectionchange event with an incorrect selection
89 + // when focusing out of table cells. Click inside cell -> toolbar = Invalid SelectionChange event
90 + if (!Env.range && editor.selection.isCollapsed()) {
91 + return;
92 + }
93 +
94 + if (!isSameElementPath(startElm) && editor.dom.isChildOf(startElm, editor.getBody())) {
95 + editor.nodeChanged({selectionChange: true});
96 + }
97 + });
98 +
99 + // Fire an extra nodeChange on mouseup for compatibility reasons
100 + editor.on('MouseUp', function(e) {
101 + if (!e.isDefaultPrevented()) {
102 + // Delay nodeChanged call for WebKit edge case issue where the range
103 + // isn't updated until after you click outside a selected image
104 + if (editor.selection.getNode().nodeName == 'IMG') {
105 + Delay.setEditorTimeout(editor, function() {
106 + editor.nodeChanged();
107 + });
108 + } else {
109 + editor.nodeChanged();
110 + }
111 + }
112 + });
113 +
114 + /**
115 + * Dispatches out a onNodeChange event to all observers. This method should be called when you
116 + * need to update the UI states or element path etc.
117 + *
118 + * @method nodeChanged
119 + * @param {Object} args Optional args to pass to NodeChange event handlers.
120 + */
121 + this.nodeChanged = function(args) {
122 + var selection = editor.selection, node, parents, root;
123 +
124 + // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
125 + if (editor.initialized && selection && !editor.settings.disable_nodechange && !editor.readonly) {
126 + // Get start node
127 + root = editor.getBody();
128 + node = selection.getStart() || root;
129 +
130 + // Make sure the node is within the editor root or is the editor root
131 + if (node.ownerDocument != editor.getDoc() || !editor.dom.isChildOf(node, root)) {
132 + node = root;
133 + }
134 +
135 + // Edge case for <p>|<img></p>
136 + if (node.nodeName == 'IMG' && selection.isCollapsed()) {
137 + node = node.parentNode;
138 + }
139 +
140 + // Get parents and add them to object
141 + parents = [];
142 + editor.dom.getParent(node, function(node) {
143 + if (node === root) {
144 + return true;
145 + }
146 +
147 + parents.push(node);
148 + });
149 +
150 + args = args || {};
151 + args.element = node;
152 + args.parents = parents;
153 +
154 + editor.fire('NodeChange', args);
155 + }
156 + };
157 + };
158 +});
1 +/**
2 + * NotificationManager.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * This class handles the creation of TinyMCE's notifications.
13 + *
14 + * @class tinymce.notificationManager
15 + * @example
16 + * // Opens a new notification of type "error" with text "An error occurred."
17 + * tinymce.activeEditor.notificationManager.open({
18 + * text: 'An error occurred.',
19 + * type: 'error'
20 + * });
21 + */
22 +define("tinymce/NotificationManager", [
23 + "tinymce/ui/Notification",
24 + "tinymce/util/Delay"
25 +], function(Notification, Delay) {
26 + return function(editor) {
27 + var self = this, notifications = [];
28 +
29 + function getLastNotification() {
30 + if (notifications.length) {
31 + return notifications[notifications.length - 1];
32 + }
33 + }
34 +
35 + self.notifications = notifications;
36 +
37 + function resizeWindowEvent() {
38 + Delay.requestAnimationFrame(function() {
39 + prePositionNotifications();
40 + positionNotifications();
41 + });
42 + }
43 +
44 + // Since the viewport will change based on the present notifications, we need to move them all to the
45 + // top left of the viewport to give an accurate size measurement so we can position them later.
46 + function prePositionNotifications() {
47 + for (var i = 0; i < notifications.length; i++) {
48 + notifications[i].moveTo(0, 0);
49 + }
50 + }
51 +
52 + function positionNotifications() {
53 + if (notifications.length > 0) {
54 + var firstItem = notifications.slice(0, 1)[0];
55 + var container = editor.inline ? editor.getElement() : editor.getContentAreaContainer();
56 + firstItem.moveRel(container, 'tc-tc');
57 + if (notifications.length > 1) {
58 + for (var i = 1; i < notifications.length; i++) {
59 + notifications[i].moveRel(notifications[i - 1].getEl(), 'bc-tc');
60 + }
61 + }
62 + }
63 + }
64 +
65 + editor.on('remove', function() {
66 + var i = notifications.length;
67 +
68 + while (i--) {
69 + notifications[i].close();
70 + }
71 + });
72 +
73 + editor.on('ResizeEditor', positionNotifications);
74 + editor.on('ResizeWindow', resizeWindowEvent);
75 +
76 + /**
77 + * Opens a new notification.
78 + *
79 + * @method open
80 + * @param {Object} args Optional name/value settings collection contains things like timeout/color/message etc.
81 + */
82 + self.open = function(args) {
83 + var notif;
84 +
85 + editor.editorManager.setActive(editor);
86 +
87 + notif = new Notification(args);
88 + notifications.push(notif);
89 +
90 + //If we have a timeout value
91 + if (args.timeout > 0) {
92 + notif.timer = setTimeout(function() {
93 + notif.close();
94 + }, args.timeout);
95 + }
96 +
97 + notif.on('close', function() {
98 + var i = notifications.length;
99 +
100 + if (notif.timer) {
101 + editor.getWin().clearTimeout(notif.timer);
102 + }
103 +
104 + while (i--) {
105 + if (notifications[i] === notif) {
106 + notifications.splice(i, 1);
107 + }
108 + }
109 +
110 + positionNotifications();
111 + });
112 +
113 + notif.renderTo();
114 +
115 + positionNotifications();
116 +
117 + return notif;
118 + };
119 +
120 + /**
121 + * Closes the top most notification.
122 + *
123 + * @method close
124 + */
125 + self.close = function() {
126 + if (getLastNotification()) {
127 + getLastNotification().close();
128 + }
129 + };
130 +
131 + /**
132 + * Returns the currently opened notification objects.
133 + *
134 + * @method getNotifications
135 + * @return {Array} Array of the currently opened notifications.
136 + */
137 + self.getNotifications = function() {
138 + return notifications;
139 + };
140 +
141 + editor.on('SkinLoaded', function() {
142 + var serviceMessage = editor.settings.service_message;
143 +
144 + if (serviceMessage) {
145 + editor.notificationManager.open({
146 + text: serviceMessage,
147 + type: 'warning',
148 + timeout: 0,
149 + icon: ''
150 + });
151 + }
152 + });
153 +
154 + //self.positionNotifications = positionNotifications;
155 + };
156 +});
1 +/**
2 + * Register.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * This registers tinymce in common module loaders.
13 + *
14 + * @private
15 + * @class tinymce.Register
16 + */
17 +define("tinymce/Register", [
18 +], function() {
19 + /*eslint consistent-this: 0 */
20 + var context = this || window;
21 +
22 + var tinymce = function() {
23 + return context.tinymce;
24 + };
25 +
26 + if (typeof context.define === "function") {
27 + // Bolt
28 + if (!context.define.amd) {
29 + context.define("ephox/tinymce", [], tinymce);
30 + }
31 + }
32 +
33 + return {};
34 +});
This diff is collapsed. Click to expand it.
1 +/**
2 + * Shortcuts.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * Contains all logic for handling of keyboard shortcuts.
13 + *
14 + * @class tinymce.Shortcuts
15 + * @example
16 + * editor.shortcuts.add('ctrl+a', function() {});
17 + * editor.shortcuts.add('meta+a', function() {}); // "meta" maps to Command on Mac and Ctrl on PC
18 + * editor.shortcuts.add('ctrl+alt+a', function() {});
19 + * editor.shortcuts.add('access+a', function() {}); // "access" maps to ctrl+alt on Mac and shift+alt on PC
20 + */
21 +define("tinymce/Shortcuts", [
22 + "tinymce/util/Tools",
23 + "tinymce/Env"
24 +], function(Tools, Env) {
25 + var each = Tools.each, explode = Tools.explode;
26 +
27 + var keyCodeLookup = {
28 + "f9": 120,
29 + "f10": 121,
30 + "f11": 122
31 + };
32 +
33 + var modifierNames = Tools.makeMap('alt,ctrl,shift,meta,access');
34 +
35 + return function(editor) {
36 + var self = this, shortcuts = {}, pendingPatterns = [];
37 +
38 + function parseShortcut(pattern) {
39 + var id, key, shortcut = {};
40 +
41 + // Parse modifiers and keys ctrl+alt+b for example
42 + each(explode(pattern, '+'), function(value) {
43 + if (value in modifierNames) {
44 + shortcut[value] = true;
45 + } else {
46 + // Allow numeric keycodes like ctrl+219 for ctrl+[
47 + if (/^[0-9]{2,}$/.test(value)) {
48 + shortcut.keyCode = parseInt(value, 10);
49 + } else {
50 + shortcut.charCode = value.charCodeAt(0);
51 + shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0);
52 + }
53 + }
54 + });
55 +
56 + // Generate unique id for modifier combination and set default state for unused modifiers
57 + id = [shortcut.keyCode];
58 + for (key in modifierNames) {
59 + if (shortcut[key]) {
60 + id.push(key);
61 + } else {
62 + shortcut[key] = false;
63 + }
64 + }
65 + shortcut.id = id.join(',');
66 +
67 + // Handle special access modifier differently depending on Mac/Win
68 + if (shortcut.access) {
69 + shortcut.alt = true;
70 +
71 + if (Env.mac) {
72 + shortcut.ctrl = true;
73 + } else {
74 + shortcut.shift = true;
75 + }
76 + }
77 +
78 + // Handle special meta modifier differently depending on Mac/Win
79 + if (shortcut.meta) {
80 + if (Env.mac) {
81 + shortcut.meta = true;
82 + } else {
83 + shortcut.ctrl = true;
84 + shortcut.meta = false;
85 + }
86 + }
87 +
88 + return shortcut;
89 + }
90 +
91 + function createShortcut(pattern, desc, cmdFunc, scope) {
92 + var shortcuts;
93 +
94 + shortcuts = Tools.map(explode(pattern, '>'), parseShortcut);
95 + shortcuts[shortcuts.length - 1] = Tools.extend(shortcuts[shortcuts.length - 1], {
96 + func: cmdFunc,
97 + scope: scope || editor
98 + });
99 +
100 + return Tools.extend(shortcuts[0], {
101 + desc: editor.translate(desc),
102 + subpatterns: shortcuts.slice(1)
103 + });
104 + }
105 +
106 + function hasModifier(e) {
107 + return e.altKey || e.ctrlKey || e.metaKey;
108 + }
109 +
110 + function isFunctionKey(e) {
111 + return e.keyCode >= 112 && e.keyCode <= 123;
112 + }
113 +
114 + function matchShortcut(e, shortcut) {
115 + if (!shortcut) {
116 + return false;
117 + }
118 +
119 + if (shortcut.ctrl != e.ctrlKey || shortcut.meta != e.metaKey) {
120 + return false;
121 + }
122 +
123 + if (shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) {
124 + return false;
125 + }
126 +
127 + if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
128 + e.preventDefault();
129 + return true;
130 + }
131 +
132 + return false;
133 + }
134 +
135 + function executeShortcutAction(shortcut) {
136 + return shortcut.func ? shortcut.func.call(shortcut.scope) : null;
137 + }
138 +
139 + editor.on('keyup keypress keydown', function(e) {
140 + if ((hasModifier(e) || isFunctionKey(e)) && !e.isDefaultPrevented()) {
141 + each(shortcuts, function(shortcut) {
142 + if (matchShortcut(e, shortcut)) {
143 + pendingPatterns = shortcut.subpatterns.slice(0);
144 +
145 + if (e.type == "keydown") {
146 + executeShortcutAction(shortcut);
147 + }
148 +
149 + return true;
150 + }
151 + });
152 +
153 + if (matchShortcut(e, pendingPatterns[0])) {
154 + if (pendingPatterns.length === 1) {
155 + if (e.type == "keydown") {
156 + executeShortcutAction(pendingPatterns[0]);
157 + }
158 + }
159 +
160 + pendingPatterns.shift();
161 + }
162 + }
163 + });
164 +
165 + /**
166 + * Adds a keyboard shortcut for some command or function.
167 + *
168 + * @method addShortcut
169 + * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
170 + * @param {String} desc Text description for the command.
171 + * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
172 + * @param {Object} scope Optional scope to execute the function in.
173 + * @return {Boolean} true/false state if the shortcut was added or not.
174 + */
175 + self.add = function(pattern, desc, cmdFunc, scope) {
176 + var cmd;
177 +
178 + cmd = cmdFunc;
179 +
180 + if (typeof cmdFunc === 'string') {
181 + cmdFunc = function() {
182 + editor.execCommand(cmd, false, null);
183 + };
184 + } else if (Tools.isArray(cmd)) {
185 + cmdFunc = function() {
186 + editor.execCommand(cmd[0], cmd[1], cmd[2]);
187 + };
188 + }
189 +
190 + each(explode(Tools.trim(pattern.toLowerCase())), function(pattern) {
191 + var shortcut = createShortcut(pattern, desc, cmdFunc, scope);
192 + shortcuts[shortcut.id] = shortcut;
193 + });
194 +
195 + return true;
196 + };
197 +
198 + /**
199 + * Remove a keyboard shortcut by pattern.
200 + *
201 + * @method remove
202 + * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
203 + * @return {Boolean} true/false state if the shortcut was removed or not.
204 + */
205 + self.remove = function(pattern) {
206 + var shortcut = createShortcut(pattern);
207 +
208 + if (shortcuts[shortcut.id]) {
209 + delete shortcuts[shortcut.id];
210 + return true;
211 + }
212 +
213 + return false;
214 + };
215 + };
216 +});
1 +/**
2 + * UndoManager.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * 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.
13 + *
14 + * @class tinymce.UndoManager
15 + */
16 +define("tinymce/UndoManager", [
17 + "tinymce/util/VK",
18 + "tinymce/Env"
19 +], function(VK, Env) {
20 + return function(editor) {
21 + var self = this, index = 0, data = [], beforeBookmark, isFirstTypedCharacter, locks = 0;
22 +
23 + function getContent() {
24 + return editor.serializer.getTrimmedContent();
25 + }
26 +
27 + function setDirty(state) {
28 + editor.setDirty(state);
29 + }
30 +
31 + function addNonTypingUndoLevel(e) {
32 + self.typing = false;
33 + self.add({}, e);
34 + }
35 +
36 + // Add initial undo level when the editor is initialized
37 + editor.on('init', function() {
38 + self.add();
39 + });
40 +
41 + // Get position before an execCommand is processed
42 + editor.on('BeforeExecCommand', function(e) {
43 + var cmd = e.command;
44 +
45 + if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') {
46 + self.beforeChange();
47 + }
48 + });
49 +
50 + // Add undo level after an execCommand call was made
51 + editor.on('ExecCommand', function(e) {
52 + var cmd = e.command;
53 +
54 + if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') {
55 + addNonTypingUndoLevel(e);
56 + }
57 + });
58 +
59 + editor.on('ObjectResizeStart Cut', function() {
60 + self.beforeChange();
61 + });
62 +
63 + editor.on('SaveContent ObjectResized blur', addNonTypingUndoLevel);
64 + editor.on('DragEnd', addNonTypingUndoLevel);
65 +
66 + editor.on('KeyUp', function(e) {
67 + var keyCode = e.keyCode;
68 +
69 + // If key is prevented then don't add undo level
70 + // This would happen on keyboard shortcuts for example
71 + if (e.isDefaultPrevented()) {
72 + return;
73 + }
74 +
75 + if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
76 + addNonTypingUndoLevel();
77 + editor.nodeChanged();
78 + }
79 +
80 + if (keyCode == 46 || keyCode == 8 || (Env.mac && (keyCode == 91 || keyCode == 93))) {
81 + editor.nodeChanged();
82 + }
83 +
84 + // Fire a TypingUndo event on the first character entered
85 + if (isFirstTypedCharacter && self.typing) {
86 + // Make it dirty if the content was changed after typing the first character
87 + if (!editor.isDirty()) {
88 + setDirty(data[0] && getContent() != data[0].content);
89 +
90 + // Fire initial change event
91 + if (editor.isDirty()) {
92 + editor.fire('change', {level: data[0], lastLevel: null});
93 + }
94 + }
95 +
96 + editor.fire('TypingUndo');
97 + isFirstTypedCharacter = false;
98 + editor.nodeChanged();
99 + }
100 + });
101 +
102 + editor.on('KeyDown', function(e) {
103 + var keyCode = e.keyCode;
104 +
105 + // If key is prevented then don't add undo level
106 + // This would happen on keyboard shortcuts for example
107 + if (e.isDefaultPrevented()) {
108 + return;
109 + }
110 +
111 + // Is character position keys left,right,up,down,home,end,pgdown,pgup,enter
112 + if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
113 + if (self.typing) {
114 + addNonTypingUndoLevel(e);
115 + }
116 +
117 + return;
118 + }
119 +
120 + // If key isn't Ctrl+Alt/AltGr
121 + var modKey = (e.ctrlKey && !e.altKey) || e.metaKey;
122 + if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing && !modKey) {
123 + self.beforeChange();
124 + self.typing = true;
125 + self.add({}, e);
126 + isFirstTypedCharacter = true;
127 + }
128 + });
129 +
130 + editor.on('MouseDown', function(e) {
131 + if (self.typing) {
132 + addNonTypingUndoLevel(e);
133 + }
134 + });
135 +
136 + // Add keyboard shortcuts for undo/redo keys
137 + editor.addShortcut('meta+z', '', 'Undo');
138 + editor.addShortcut('meta+y,meta+shift+z', '', 'Redo');
139 +
140 + editor.on('AddUndo Undo Redo ClearUndos', function(e) {
141 + if (!e.isDefaultPrevented()) {
142 + editor.nodeChanged();
143 + }
144 + });
145 +
146 + /*eslint consistent-this:0 */
147 + self = {
148 + // Explode for debugging reasons
149 + data: data,
150 +
151 + /**
152 + * State if the user is currently typing or not. This will add a typing operation into one undo
153 + * level instead of one new level for each keystroke.
154 + *
155 + * @field {Boolean} typing
156 + */
157 + typing: false,
158 +
159 + /**
160 + * Stores away a bookmark to be used when performing an undo action so that the selection is before
161 + * the change has been made.
162 + *
163 + * @method beforeChange
164 + */
165 + beforeChange: function() {
166 + if (!locks) {
167 + beforeBookmark = editor.selection.getBookmark(2, true);
168 + }
169 + },
170 +
171 + /**
172 + * Adds a new undo level/snapshot to the undo list.
173 + *
174 + * @method add
175 + * @param {Object} level Optional undo level object to add.
176 + * @param {DOMEvent} event Optional event responsible for the creation of the undo level.
177 + * @return {Object} Undo level that got added or null it a level wasn't needed.
178 + */
179 + add: function(level, event) {
180 + var i, settings = editor.settings, lastLevel;
181 +
182 + level = level || {};
183 + level.content = getContent();
184 +
185 + if (locks || editor.removed) {
186 + return null;
187 + }
188 +
189 + lastLevel = data[index];
190 + if (editor.fire('BeforeAddUndo', {level: level, lastLevel: lastLevel, originalEvent: event}).isDefaultPrevented()) {
191 + return null;
192 + }
193 +
194 + // Add undo level if needed
195 + if (lastLevel && lastLevel.content == level.content) {
196 + return null;
197 + }
198 +
199 + // Set before bookmark on previous level
200 + if (data[index]) {
201 + data[index].beforeBookmark = beforeBookmark;
202 + }
203 +
204 + // Time to compress
205 + if (settings.custom_undo_redo_levels) {
206 + if (data.length > settings.custom_undo_redo_levels) {
207 + for (i = 0; i < data.length - 1; i++) {
208 + data[i] = data[i + 1];
209 + }
210 +
211 + data.length--;
212 + index = data.length;
213 + }
214 + }
215 +
216 + // Get a non intrusive normalized bookmark
217 + level.bookmark = editor.selection.getBookmark(2, true);
218 +
219 + // Crop array if needed
220 + if (index < data.length - 1) {
221 + data.length = index + 1;
222 + }
223 +
224 + data.push(level);
225 + index = data.length - 1;
226 +
227 + var args = {level: level, lastLevel: lastLevel, originalEvent: event};
228 +
229 + editor.fire('AddUndo', args);
230 +
231 + if (index > 0) {
232 + setDirty(true);
233 + editor.fire('change', args);
234 + }
235 +
236 + return level;
237 + },
238 +
239 + /**
240 + * Undoes the last action.
241 + *
242 + * @method undo
243 + * @return {Object} Undo level or null if no undo was performed.
244 + */
245 + undo: function() {
246 + var level;
247 +
248 + if (self.typing) {
249 + self.add();
250 + self.typing = false;
251 + }
252 +
253 + if (index > 0) {
254 + level = data[--index];
255 +
256 + editor.setContent(level.content, {format: 'raw'});
257 + editor.selection.moveToBookmark(level.beforeBookmark);
258 + setDirty(true);
259 +
260 + editor.fire('undo', {level: level});
261 + }
262 +
263 + return level;
264 + },
265 +
266 + /**
267 + * Redoes the last action.
268 + *
269 + * @method redo
270 + * @return {Object} Redo level or null if no redo was performed.
271 + */
272 + redo: function() {
273 + var level;
274 +
275 + if (index < data.length - 1) {
276 + level = data[++index];
277 +
278 + editor.setContent(level.content, {format: 'raw'});
279 + editor.selection.moveToBookmark(level.bookmark);
280 + setDirty(true);
281 +
282 + editor.fire('redo', {level: level});
283 + }
284 +
285 + return level;
286 + },
287 +
288 + /**
289 + * Removes all undo levels.
290 + *
291 + * @method clear
292 + */
293 + clear: function() {
294 + data = [];
295 + index = 0;
296 + self.typing = false;
297 + self.data = data;
298 + editor.fire('ClearUndos');
299 + },
300 +
301 + /**
302 + * Returns true/false if the undo manager has any undo levels.
303 + *
304 + * @method hasUndo
305 + * @return {Boolean} true/false if the undo manager has any undo levels.
306 + */
307 + hasUndo: function() {
308 + // Has undo levels or typing and content isn't the same as the initial level
309 + return index > 0 || (self.typing && data[0] && getContent() != data[0].content);
310 + },
311 +
312 + /**
313 + * Returns true/false if the undo manager has any redo levels.
314 + *
315 + * @method hasRedo
316 + * @return {Boolean} true/false if the undo manager has any redo levels.
317 + */
318 + hasRedo: function() {
319 + return index < data.length - 1 && !this.typing;
320 + },
321 +
322 + /**
323 + * Executes the specified mutator function as an undo transaction. The selection
324 + * before the modification will be stored to the undo stack and if the DOM changes
325 + * it will add a new undo level. Any methods within the translation that adds undo levels will
326 + * be ignored. So a translation can include calls to execCommand or editor.insertContent.
327 + *
328 + * @method transact
329 + * @param {function} callback Function that gets executed and has dom manipulation logic in it.
330 + * @return {Object} Undo level that got added or null it a level wasn't needed.
331 + */
332 + transact: function(callback) {
333 + self.beforeChange();
334 +
335 + try {
336 + locks++;
337 + callback();
338 + } finally {
339 + locks--;
340 + }
341 +
342 + return self.add();
343 + },
344 +
345 + /**
346 + * Adds an extra "hidden" undo level by first applying the first mutation and store that to the undo stack
347 + * then roll back that change and do the second mutation on top of the stack. This will produce an extra
348 + * undo level that the user doesn't see until they undo.
349 + *
350 + * @method extra
351 + * @param {function} callback1 Function that does mutation but gets stored as a "hidden" extra undo level.
352 + * @param {function} callback2 Function that does mutation but gets displayed to the user.
353 + */
354 + extra: function (callback1, callback2) {
355 + var lastLevel, bookmark;
356 +
357 + if (self.transact(callback1)) {
358 + bookmark = data[index].bookmark;
359 + lastLevel = data[index - 1];
360 + editor.setContent(lastLevel.content, {format: 'raw'});
361 + editor.selection.moveToBookmark(lastLevel.beforeBookmark);
362 +
363 + if (self.transact(callback2)) {
364 + data[index - 1].beforeBookmark = bookmark;
365 + }
366 + }
367 + }
368 + };
369 +
370 + return self;
371 + };
372 +});
1 +/**
2 + * WindowManager.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * This class handles the creation of native windows and dialogs. This class can be extended to provide for example inline dialogs.
13 + *
14 + * @class tinymce.WindowManager
15 + * @example
16 + * // Opens a new dialog with the file.htm file and the size 320x240
17 + * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog.
18 + * tinymce.activeEditor.windowManager.open({
19 + * url: 'file.htm',
20 + * width: 320,
21 + * height: 240
22 + * }, {
23 + * custom_param: 1
24 + * });
25 + *
26 + * // Displays an alert box using the active editors window manager instance
27 + * tinymce.activeEditor.windowManager.alert('Hello world!');
28 + *
29 + * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm
30 + * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) {
31 + * if (s)
32 + * tinymce.activeEditor.windowManager.alert("Ok");
33 + * else
34 + * tinymce.activeEditor.windowManager.alert("Cancel");
35 + * });
36 + */
37 +define("tinymce/WindowManager", [
38 + "tinymce/ui/Window",
39 + "tinymce/ui/MessageBox"
40 +], function(Window, MessageBox) {
41 + return function(editor) {
42 + var self = this, windows = [];
43 +
44 + function getTopMostWindow() {
45 + if (windows.length) {
46 + return windows[windows.length - 1];
47 + }
48 + }
49 +
50 + function fireOpenEvent(win) {
51 + editor.fire('OpenWindow', {
52 + win: win
53 + });
54 + }
55 +
56 + function fireCloseEvent(win) {
57 + editor.fire('CloseWindow', {
58 + win: win
59 + });
60 + }
61 +
62 + self.windows = windows;
63 +
64 + editor.on('remove', function() {
65 + var i = windows.length;
66 +
67 + while (i--) {
68 + windows[i].close();
69 + }
70 + });
71 +
72 + /**
73 + * Opens a new window.
74 + *
75 + * @method open
76 + * @param {Object} args Optional name/value settings collection contains things like width/height/url etc.
77 + * @param {Object} params Options like title, file, width, height etc.
78 + * @option {String} title Window title.
79 + * @option {String} file URL of the file to open in the window.
80 + * @option {Number} width Width in pixels.
81 + * @option {Number} height Height in pixels.
82 + * @option {Boolean} autoScroll Specifies whether the popup window can have scrollbars if required (i.e. content
83 + * larger than the popup size specified).
84 + */
85 + self.open = function(args, params) {
86 + var win;
87 +
88 + editor.editorManager.setActive(editor);
89 +
90 + args.title = args.title || ' ';
91 +
92 + // Handle URL
93 + args.url = args.url || args.file; // Legacy
94 + if (args.url) {
95 + args.width = parseInt(args.width || 320, 10);
96 + args.height = parseInt(args.height || 240, 10);
97 + }
98 +
99 + // Handle body
100 + if (args.body) {
101 + args.items = {
102 + defaults: args.defaults,
103 + type: args.bodyType || 'form',
104 + items: args.body,
105 + data: args.data,
106 + callbacks: args.commands
107 + };
108 + }
109 +
110 + if (!args.url && !args.buttons) {
111 + args.buttons = [
112 + {text: 'Ok', subtype: 'primary', onclick: function() {
113 + win.find('form')[0].submit();
114 + }},
115 +
116 + {text: 'Cancel', onclick: function() {
117 + win.close();
118 + }}
119 + ];
120 + }
121 +
122 + win = new Window(args);
123 + windows.push(win);
124 +
125 + win.on('close', function() {
126 + var i = windows.length;
127 +
128 + while (i--) {
129 + if (windows[i] === win) {
130 + windows.splice(i, 1);
131 + }
132 + }
133 +
134 + if (!windows.length) {
135 + editor.focus();
136 + }
137 +
138 + fireCloseEvent(win);
139 + });
140 +
141 + // Handle data
142 + if (args.data) {
143 + win.on('postRender', function() {
144 + this.find('*').each(function(ctrl) {
145 + var name = ctrl.name();
146 +
147 + if (name in args.data) {
148 + ctrl.value(args.data[name]);
149 + }
150 + });
151 + });
152 + }
153 +
154 + // store args and parameters
155 + win.features = args || {};
156 + win.params = params || {};
157 +
158 + // Takes a snapshot in the FocusManager of the selection before focus is lost to dialog
159 + if (windows.length === 1) {
160 + editor.nodeChanged();
161 + }
162 +
163 + win = win.renderTo().reflow();
164 +
165 + fireOpenEvent(win);
166 +
167 + return win;
168 + };
169 +
170 + /**
171 + * Creates a alert dialog. Please don't use the blocking behavior of this
172 + * native version use the callback method instead then it can be extended.
173 + *
174 + * @method alert
175 + * @param {String} message Text to display in the new alert dialog.
176 + * @param {function} callback Callback function to be executed after the user has selected ok.
177 + * @param {Object} scope Optional scope to execute the callback in.
178 + * @example
179 + * // Displays an alert box using the active editors window manager instance
180 + * tinymce.activeEditor.windowManager.alert('Hello world!');
181 + */
182 + self.alert = function(message, callback, scope) {
183 + var win;
184 +
185 + win = MessageBox.alert(message, function() {
186 + if (callback) {
187 + callback.call(scope || this);
188 + } else {
189 + editor.focus();
190 + }
191 + });
192 +
193 + win.on('close', function() {
194 + fireCloseEvent(win);
195 + });
196 +
197 + fireOpenEvent(win);
198 + };
199 +
200 + /**
201 + * Creates a confirm dialog. Please don't use the blocking behavior of this
202 + * native version use the callback method instead then it can be extended.
203 + *
204 + * @method confirm
205 + * @param {String} message Text to display in the new confirm dialog.
206 + * @param {function} callback Callback function to be executed after the user has selected ok or cancel.
207 + * @param {Object} scope Optional scope to execute the callback in.
208 + * @example
209 + * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm
210 + * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) {
211 + * if (s)
212 + * tinymce.activeEditor.windowManager.alert("Ok");
213 + * else
214 + * tinymce.activeEditor.windowManager.alert("Cancel");
215 + * });
216 + */
217 + self.confirm = function(message, callback, scope) {
218 + var win;
219 +
220 + win = MessageBox.confirm(message, function(state) {
221 + callback.call(scope || this, state);
222 + });
223 +
224 + win.on('close', function() {
225 + fireCloseEvent(win);
226 + });
227 +
228 + fireOpenEvent(win);
229 + };
230 +
231 + /**
232 + * Closes the top most window.
233 + *
234 + * @method close
235 + */
236 + self.close = function() {
237 + if (getTopMostWindow()) {
238 + getTopMostWindow().close();
239 + }
240 + };
241 +
242 + /**
243 + * Returns the params of the last window open call. This can be used in iframe based
244 + * dialog to get params passed from the tinymce plugin.
245 + *
246 + * @example
247 + * var dialogArguments = top.tinymce.activeEditor.windowManager.getParams();
248 + *
249 + * @method getParams
250 + * @return {Object} Name/value object with parameters passed from windowManager.open call.
251 + */
252 + self.getParams = function() {
253 + return getTopMostWindow() ? getTopMostWindow().params : null;
254 + };
255 +
256 + /**
257 + * Sets the params of the last opened window.
258 + *
259 + * @method setParams
260 + * @param {Object} params Params object to set for the last opened window.
261 + */
262 + self.setParams = function(params) {
263 + if (getTopMostWindow()) {
264 + getTopMostWindow().params = params;
265 + }
266 + };
267 +
268 + /**
269 + * Returns the currently opened window objects.
270 + *
271 + * @method getWindows
272 + * @return {Array} Array of the currently opened windows.
273 + */
274 + self.getWindows = function() {
275 + return windows;
276 + };
277 + };
278 +});
1 +/**
2 + * CaretBookmark.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * This module creates or resolves xpath like string representation of a CaretPositions.
13 + *
14 + * The format is a / separated list of chunks with:
15 + * <element|text()>[index|after|before]
16 + *
17 + * For example:
18 + * p[0]/b[0]/text()[0],1 = <p><b>a|c</b></p>
19 + * p[0]/img[0],before = <p>|<img></p>
20 + * p[0]/img[0],after = <p><img>|</p>
21 + *
22 + * @private
23 + * @static
24 + * @class tinymce.caret.CaretBookmark
25 + * @example
26 + * var bookmark = CaretBookmark.create(rootElm, CaretPosition.before(rootElm.firstChild));
27 + * var caretPosition = CaretBookmark.resolve(bookmark);
28 + */
29 +define('tinymce/caret/CaretBookmark', [
30 + 'tinymce/dom/NodeType',
31 + 'tinymce/dom/DOMUtils',
32 + 'tinymce/util/Fun',
33 + 'tinymce/util/Arr',
34 + 'tinymce/caret/CaretPosition'
35 +], function(NodeType, DomUtils, Fun, Arr, CaretPosition) {
36 + var isText = NodeType.isText,
37 + isBogus = NodeType.isBogus,
38 + nodeIndex = DomUtils.nodeIndex;
39 +
40 + function normalizedParent(node) {
41 + var parentNode = node.parentNode;
42 +
43 + if (isBogus(parentNode)) {
44 + return normalizedParent(parentNode);
45 + }
46 +
47 + return parentNode;
48 + }
49 +
50 + function getChildNodes(node) {
51 + if (!node) {
52 + return [];
53 + }
54 +
55 + return Arr.reduce(node.childNodes, function(result, node) {
56 + if (isBogus(node) && node.nodeName != 'BR') {
57 + result = result.concat(getChildNodes(node));
58 + } else {
59 + result.push(node);
60 + }
61 +
62 + return result;
63 + }, []);
64 + }
65 +
66 + function normalizedTextOffset(textNode, offset) {
67 + while ((textNode = textNode.previousSibling)) {
68 + if (!isText(textNode)) {
69 + break;
70 + }
71 +
72 + offset += textNode.data.length;
73 + }
74 +
75 + return offset;
76 + }
77 +
78 + function equal(targetValue) {
79 + return function(value) {
80 + return targetValue === value;
81 + };
82 + }
83 +
84 + function normalizedNodeIndex(node) {
85 + var nodes, index, numTextFragments;
86 +
87 + nodes = getChildNodes(normalizedParent(node));
88 + index = Arr.findIndex(nodes, equal(node), node);
89 + nodes = nodes.slice(0, index + 1);
90 + numTextFragments = Arr.reduce(nodes, function(result, node, i) {
91 + if (isText(node) && isText(nodes[i - 1])) {
92 + result++;
93 + }
94 +
95 + return result;
96 + }, 0);
97 +
98 + nodes = Arr.filter(nodes, NodeType.matchNodeNames(node.nodeName));
99 + index = Arr.findIndex(nodes, equal(node), node);
100 +
101 + return index - numTextFragments;
102 + }
103 +
104 + function createPathItem(node) {
105 + var name;
106 +
107 + if (isText(node)) {
108 + name = 'text()';
109 + } else {
110 + name = node.nodeName.toLowerCase();
111 + }
112 +
113 + return name + '[' + normalizedNodeIndex(node) + ']';
114 + }
115 +
116 + function parentsUntil(rootNode, node, predicate) {
117 + var parents = [];
118 +
119 + for (node = node.parentNode; node != rootNode; node = node.parentNode) {
120 + if (predicate && predicate(node)) {
121 + break;
122 + }
123 +
124 + parents.push(node);
125 + }
126 +
127 + return parents;
128 + }
129 +
130 + function create(rootNode, caretPosition) {
131 + var container, offset, path = [],
132 + outputOffset, childNodes, parents;
133 +
134 + container = caretPosition.container();
135 + offset = caretPosition.offset();
136 +
137 + if (isText(container)) {
138 + outputOffset = normalizedTextOffset(container, offset);
139 + } else {
140 + childNodes = container.childNodes;
141 + if (offset >= childNodes.length) {
142 + outputOffset = 'after';
143 + offset = childNodes.length - 1;
144 + } else {
145 + outputOffset = 'before';
146 + }
147 +
148 + container = childNodes[offset];
149 + }
150 +
151 + path.push(createPathItem(container));
152 + parents = parentsUntil(rootNode, container);
153 + parents = Arr.filter(parents, Fun.negate(NodeType.isBogus));
154 + path = path.concat(Arr.map(parents, function(node) {
155 + return createPathItem(node);
156 + }));
157 +
158 + return path.reverse().join('/') + ',' + outputOffset;
159 + }
160 +
161 + function resolvePathItem(node, name, index) {
162 + var nodes = getChildNodes(node);
163 +
164 + nodes = Arr.filter(nodes, function(node, index) {
165 + return !isText(node) || !isText(nodes[index - 1]);
166 + });
167 +
168 + nodes = Arr.filter(nodes, NodeType.matchNodeNames(name));
169 + return nodes[index];
170 + }
171 +
172 + function findTextPosition(container, offset) {
173 + var node = container, targetOffset = 0, dataLen;
174 +
175 + while (isText(node)) {
176 + dataLen = node.data.length;
177 +
178 + if (offset >= targetOffset && offset <= targetOffset + dataLen) {
179 + container = node;
180 + offset = offset - targetOffset;
181 + break;
182 + }
183 +
184 + if (!isText(node.nextSibling)) {
185 + container = node;
186 + offset = dataLen;
187 + break;
188 + }
189 +
190 + targetOffset += dataLen;
191 + node = node.nextSibling;
192 + }
193 +
194 + if (offset > container.data.length) {
195 + offset = container.data.length;
196 + }
197 +
198 + return new CaretPosition(container, offset);
199 + }
200 +
201 + function resolve(rootNode, path) {
202 + var parts, container, offset;
203 +
204 + if (!path) {
205 + return null;
206 + }
207 +
208 + parts = path.split(',');
209 + path = parts[0].split('/');
210 + offset = parts.length > 1 ? parts[1] : 'before';
211 +
212 + container = Arr.reduce(path, function(result, value) {
213 + value = /([\w\-\(\)]+)\[([0-9]+)\]/.exec(value);
214 + if (!value) {
215 + return null;
216 + }
217 +
218 + if (value[1] === 'text()') {
219 + value[1] = '#text';
220 + }
221 +
222 + return resolvePathItem(result, value[1], parseInt(value[2], 10));
223 + }, rootNode);
224 +
225 + if (!container) {
226 + return null;
227 + }
228 +
229 + if (!isText(container)) {
230 + if (offset === 'after') {
231 + offset = nodeIndex(container) + 1;
232 + } else {
233 + offset = nodeIndex(container);
234 + }
235 +
236 + return new CaretPosition(container.parentNode, offset);
237 + }
238 +
239 + return findTextPosition(container, parseInt(offset, 10));
240 + }
241 +
242 + return {
243 + /**
244 + * Create a xpath bookmark location for the specified caret position.
245 + *
246 + * @method create
247 + * @param {Node} rootNode Root node to create bookmark within.
248 + * @param {tinymce.caret.CaretPosition} caretPosition Caret position within the root node.
249 + * @return {String} String xpath like location of caret position.
250 + */
251 + create: create,
252 +
253 + /**
254 + * Resolves a xpath like bookmark location to the a caret position.
255 + *
256 + * @method resolve
257 + * @param {Node} rootNode Root node to resolve xpath bookmark within.
258 + * @param {String} bookmark Bookmark string to resolve.
259 + * @return {tinymce.caret.CaretPosition} Caret position resolved from xpath like bookmark.
260 + */
261 + resolve: resolve
262 + };
263 +});
...\ No newline at end of file ...\ No newline at end of file
1 +/**
2 + * CaretCandidate.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * This module contains logic for handling caret candidates. A caret candidate is
13 + * for example text nodes, images, input elements, cE=false elements etc.
14 + *
15 + * @private
16 + * @class tinymce.caret.CaretCandidate
17 + */
18 +define("tinymce/caret/CaretCandidate", [
19 + "tinymce/dom/NodeType",
20 + "tinymce/util/Arr",
21 + "tinymce/caret/CaretContainer"
22 +], function(NodeType, Arr, CaretContainer) {
23 + var isContentEditableTrue = NodeType.isContentEditableTrue,
24 + isContentEditableFalse = NodeType.isContentEditableFalse,
25 + isBr = NodeType.isBr,
26 + isText = NodeType.isText,
27 + isInvalidTextElement = NodeType.matchNodeNames('script style textarea'),
28 + isAtomicInline = NodeType.matchNodeNames('img input textarea hr iframe video audio object'),
29 + isTable = NodeType.matchNodeNames('table'),
30 + isCaretContainer = CaretContainer.isCaretContainer;
31 +
32 + function isCaretCandidate(node) {
33 + if (isCaretContainer(node)) {
34 + return false;
35 + }
36 +
37 + if (isText(node)) {
38 + if (isInvalidTextElement(node.parentNode)) {
39 + return false;
40 + }
41 +
42 + return true;
43 + }
44 +
45 + return isAtomicInline(node) || isBr(node) || isTable(node) || isContentEditableFalse(node);
46 + }
47 +
48 + function isInEditable(node, rootNode) {
49 + for (node = node.parentNode; node && node != rootNode; node = node.parentNode) {
50 + if (isContentEditableFalse(node)) {
51 + return false;
52 + }
53 +
54 + if (isContentEditableTrue(node)) {
55 + return true;
56 + }
57 + }
58 +
59 + return true;
60 + }
61 +
62 + function isAtomicContentEditableFalse(node) {
63 + if (!isContentEditableFalse(node)) {
64 + return false;
65 + }
66 +
67 + return Arr.reduce(node.getElementsByTagName('*'), function(result, elm) {
68 + return result || isContentEditableTrue(elm);
69 + }, false) !== true;
70 + }
71 +
72 + function isAtomic(node) {
73 + return isAtomicInline(node) || isAtomicContentEditableFalse(node);
74 + }
75 +
76 + function isEditableCaretCandidate(node, rootNode) {
77 + return isCaretCandidate(node) && isInEditable(node, rootNode);
78 + }
79 +
80 + return {
81 + isCaretCandidate: isCaretCandidate,
82 + isInEditable: isInEditable,
83 + isAtomic: isAtomic,
84 + isEditableCaretCandidate: isEditableCaretCandidate
85 + };
86 +});
...\ No newline at end of file ...\ No newline at end of file
1 +/**
2 + * CaretContainer.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * This module handles caret containers. A caret container is a node that
13 + * holds the caret for positional purposes.
14 + *
15 + * @private
16 + * @class tinymce.caret.CaretContainer
17 + */
18 +define("tinymce/caret/CaretContainer", [
19 + "tinymce/dom/NodeType",
20 + "tinymce/text/Zwsp"
21 +], function(NodeType, Zwsp) {
22 + var isElement = NodeType.isElement,
23 + isText = NodeType.isText;
24 +
25 + function isCaretContainerBlock(node) {
26 + if (isText(node)) {
27 + node = node.parentNode;
28 + }
29 +
30 + return isElement(node) && node.hasAttribute('data-mce-caret');
31 + }
32 +
33 + function isCaretContainerInline(node) {
34 + return isText(node) && Zwsp.isZwsp(node.data);
35 + }
36 +
37 + function isCaretContainer(node) {
38 + return isCaretContainerBlock(node) || isCaretContainerInline(node);
39 + }
40 +
41 + function removeNode(node) {
42 + var parentNode = node.parentNode;
43 + if (parentNode) {
44 + parentNode.removeChild(node);
45 + }
46 + }
47 +
48 + function getNodeValue(node) {
49 + try {
50 + return node.nodeValue;
51 + } catch (ex) {
52 + // IE sometimes produces "Invalid argument" on nodes
53 + return "";
54 + }
55 + }
56 +
57 + function setNodeValue(node, text) {
58 + if (text.length === 0) {
59 + removeNode(node);
60 + } else {
61 + node.nodeValue = text;
62 + }
63 + }
64 +
65 + function insertInline(node, before) {
66 + var doc, sibling, textNode, parentNode;
67 +
68 + doc = node.ownerDocument;
69 + textNode = doc.createTextNode(Zwsp.ZWSP);
70 + parentNode = node.parentNode;
71 +
72 + if (!before) {
73 + sibling = node.nextSibling;
74 + if (isText(sibling)) {
75 + if (isCaretContainer(sibling)) {
76 + return sibling;
77 + }
78 +
79 + if (startsWithCaretContainer(sibling)) {
80 + sibling.splitText(1);
81 + return sibling;
82 + }
83 + }
84 +
85 + if (node.nextSibling) {
86 + parentNode.insertBefore(textNode, node.nextSibling);
87 + } else {
88 + parentNode.appendChild(textNode);
89 + }
90 + } else {
91 + sibling = node.previousSibling;
92 + if (isText(sibling)) {
93 + if (isCaretContainer(sibling)) {
94 + return sibling;
95 + }
96 +
97 + if (endsWithCaretContainer(sibling)) {
98 + return sibling.splitText(sibling.data.length - 1);
99 + }
100 + }
101 +
102 + parentNode.insertBefore(textNode, node);
103 + }
104 +
105 + return textNode;
106 + }
107 +
108 + function insertBlock(blockName, node, before) {
109 + var doc, blockNode, parentNode;
110 +
111 + doc = node.ownerDocument;
112 + blockNode = doc.createElement(blockName);
113 + blockNode.setAttribute('data-mce-caret', before ? 'before' : 'after');
114 + blockNode.setAttribute('data-mce-bogus', 'all');
115 + blockNode.appendChild(doc.createTextNode('\u00a0'));
116 + parentNode = node.parentNode;
117 +
118 + if (!before) {
119 + if (node.nextSibling) {
120 + parentNode.insertBefore(blockNode, node.nextSibling);
121 + } else {
122 + parentNode.appendChild(blockNode);
123 + }
124 + } else {
125 + parentNode.insertBefore(blockNode, node);
126 + }
127 +
128 + return blockNode;
129 + }
130 +
131 + function remove(caretContainerNode) {
132 + if (isElement(caretContainerNode) && isCaretContainer(caretContainerNode)) {
133 + if (caretContainerNode.innerHTML != '&nbsp;') {
134 + caretContainerNode.removeAttribute('data-mce-caret');
135 + } else {
136 + removeNode(caretContainerNode);
137 + }
138 + }
139 +
140 + if (isText(caretContainerNode)) {
141 + var text = Zwsp.trim(getNodeValue(caretContainerNode));
142 + setNodeValue(caretContainerNode, text);
143 + }
144 + }
145 +
146 + function startsWithCaretContainer(node) {
147 + return isText(node) && node.data[0] == Zwsp.ZWSP;
148 + }
149 +
150 + function endsWithCaretContainer(node) {
151 + return isText(node) && node.data[node.data.length - 1] == Zwsp.ZWSP;
152 + }
153 +
154 + return {
155 + isCaretContainer: isCaretContainer,
156 + isCaretContainerBlock: isCaretContainerBlock,
157 + isCaretContainerInline: isCaretContainerInline,
158 + insertInline: insertInline,
159 + insertBlock: insertBlock,
160 + remove: remove,
161 + startsWithCaretContainer: startsWithCaretContainer,
162 + endsWithCaretContainer: endsWithCaretContainer
163 + };
164 +});
...\ No newline at end of file ...\ No newline at end of file
This diff is collapsed. Click to expand it.
1 +/**
2 + * CaretUtils.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * Utility functions shared by the caret logic.
13 + *
14 + * @private
15 + * @class tinymce.caret.CaretUtils
16 + */
17 +define("tinymce/caret/CaretUtils", [
18 + "tinymce/util/Fun",
19 + "tinymce/dom/TreeWalker",
20 + "tinymce/dom/NodeType",
21 + "tinymce/caret/CaretPosition",
22 + "tinymce/caret/CaretContainer",
23 + "tinymce/caret/CaretCandidate"
24 +], function(Fun, TreeWalker, NodeType, CaretPosition, CaretContainer, CaretCandidate) {
25 + var isContentEditableTrue = NodeType.isContentEditableTrue,
26 + isContentEditableFalse = NodeType.isContentEditableFalse,
27 + isBlockLike = NodeType.matchStyleValues('display', 'block table table-cell table-caption'),
28 + isCaretContainer = CaretContainer.isCaretContainer,
29 + curry = Fun.curry,
30 + isElement = NodeType.isElement,
31 + isCaretCandidate = CaretCandidate.isCaretCandidate;
32 +
33 + function isForwards(direction) {
34 + return direction > 0;
35 + }
36 +
37 + function isBackwards(direction) {
38 + return direction < 0;
39 + }
40 +
41 + function findNode(node, direction, predicateFn, rootNode, shallow) {
42 + var walker = new TreeWalker(node, rootNode);
43 +
44 + if (isBackwards(direction)) {
45 + if (isContentEditableFalse(node)) {
46 + node = walker.prev(true);
47 + if (predicateFn(node)) {
48 + return node;
49 + }
50 + }
51 +
52 + while ((node = walker.prev(shallow))) {
53 + if (predicateFn(node)) {
54 + return node;
55 + }
56 + }
57 + }
58 +
59 + if (isForwards(direction)) {
60 + if (isContentEditableFalse(node)) {
61 + node = walker.next(true);
62 + if (predicateFn(node)) {
63 + return node;
64 + }
65 + }
66 +
67 + while ((node = walker.next(shallow))) {
68 + if (predicateFn(node)) {
69 + return node;
70 + }
71 + }
72 + }
73 +
74 + return null;
75 + }
76 +
77 + function getEditingHost(node, rootNode) {
78 + for (node = node.parentNode; node && node != rootNode; node = node.parentNode) {
79 + if (isContentEditableTrue(node)) {
80 + return node;
81 + }
82 + }
83 +
84 + return rootNode;
85 + }
86 +
87 + function getParentBlock(node, rootNode) {
88 + while (node && node != rootNode) {
89 + if (isBlockLike(node)) {
90 + return node;
91 + }
92 +
93 + node = node.parentNode;
94 + }
95 +
96 + return null;
97 + }
98 +
99 + function isInSameBlock(caretPosition1, caretPosition2, rootNode) {
100 + return getParentBlock(caretPosition1.container(), rootNode) == getParentBlock(caretPosition2.container(), rootNode);
101 + }
102 +
103 + function isInSameEditingHost(caretPosition1, caretPosition2, rootNode) {
104 + return getEditingHost(caretPosition1.container(), rootNode) == getEditingHost(caretPosition2.container(), rootNode);
105 + }
106 +
107 + function getChildNodeAtRelativeOffset(relativeOffset, caretPosition) {
108 + var container, offset;
109 +
110 + if (!caretPosition) {
111 + return null;
112 + }
113 +
114 + container = caretPosition.container();
115 + offset = caretPosition.offset();
116 +
117 + if (!isElement(container)) {
118 + return null;
119 + }
120 +
121 + return container.childNodes[offset + relativeOffset];
122 + }
123 +
124 + function beforeAfter(before, node) {
125 + var range = node.ownerDocument.createRange();
126 +
127 + if (before) {
128 + range.setStartBefore(node);
129 + range.setEndBefore(node);
130 + } else {
131 + range.setStartAfter(node);
132 + range.setEndAfter(node);
133 + }
134 +
135 + return range;
136 + }
137 +
138 + function isNodesInSameBlock(rootNode, node1, node2) {
139 + return getParentBlock(node1, rootNode) == getParentBlock(node2, rootNode);
140 + }
141 +
142 + function lean(left, rootNode, node) {
143 + var sibling, siblingName;
144 +
145 + if (left) {
146 + siblingName = 'previousSibling';
147 + } else {
148 + siblingName = 'nextSibling';
149 + }
150 +
151 + while (node && node != rootNode) {
152 + sibling = node[siblingName];
153 +
154 + if (isCaretContainer(sibling)) {
155 + sibling = sibling[siblingName];
156 + }
157 +
158 + if (isContentEditableFalse(sibling)) {
159 + if (isNodesInSameBlock(rootNode, sibling, node)) {
160 + return sibling;
161 + }
162 +
163 + break;
164 + }
165 +
166 + if (isCaretCandidate(sibling)) {
167 + break;
168 + }
169 +
170 + node = node.parentNode;
171 + }
172 +
173 + return null;
174 + }
175 +
176 + var before = curry(beforeAfter, true);
177 + var after = curry(beforeAfter, false);
178 +
179 + function normalizeRange(direction, rootNode, range) {
180 + var node, container, offset, location;
181 + var leanLeft = curry(lean, true, rootNode);
182 + var leanRight = curry(lean, false, rootNode);
183 +
184 + container = range.startContainer;
185 + offset = range.startOffset;
186 +
187 + if (CaretContainer.isCaretContainerBlock(container)) {
188 + if (!isElement(container)) {
189 + container = container.parentNode;
190 + }
191 +
192 + location = container.getAttribute('data-mce-caret');
193 +
194 + if (location == 'before') {
195 + node = container.nextSibling;
196 + if (isContentEditableFalse(node)) {
197 + return before(node);
198 + }
199 + }
200 +
201 + if (location == 'after') {
202 + node = container.previousSibling;
203 + if (isContentEditableFalse(node)) {
204 + return after(node);
205 + }
206 + }
207 + }
208 +
209 + if (!range.collapsed) {
210 + return range;
211 + }
212 +
213 + if (NodeType.isText(container)) {
214 + if (isCaretContainer(container)) {
215 + if (direction === 1) {
216 + node = leanRight(container);
217 + if (node) {
218 + return before(node);
219 + }
220 +
221 + node = leanLeft(container);
222 + if (node) {
223 + return after(node);
224 + }
225 + }
226 +
227 + if (direction === -1) {
228 + node = leanLeft(container);
229 + if (node) {
230 + return after(node);
231 + }
232 +
233 + node = leanRight(container);
234 + if (node) {
235 + return before(node);
236 + }
237 + }
238 +
239 + return range;
240 + }
241 +
242 + if (CaretContainer.endsWithCaretContainer(container) && offset >= container.data.length - 1) {
243 + if (direction === 1) {
244 + node = leanRight(container);
245 + if (node) {
246 + return before(node);
247 + }
248 + }
249 +
250 + return range;
251 + }
252 +
253 + if (CaretContainer.startsWithCaretContainer(container) && offset <= 1) {
254 + if (direction === -1) {
255 + node = leanLeft(container);
256 + if (node) {
257 + return after(node);
258 + }
259 + }
260 +
261 + return range;
262 + }
263 +
264 + if (offset === container.data.length) {
265 + node = leanRight(container);
266 + if (node) {
267 + return before(node);
268 + }
269 +
270 + return range;
271 + }
272 +
273 + if (offset === 0) {
274 + node = leanLeft(container);
275 + if (node) {
276 + return after(node);
277 + }
278 +
279 + return range;
280 + }
281 + }
282 +
283 + return range;
284 + }
285 +
286 + function isNextToContentEditableFalse(relativeOffset, caretPosition) {
287 + return isContentEditableFalse(getChildNodeAtRelativeOffset(relativeOffset, caretPosition));
288 + }
289 +
290 + return {
291 + isForwards: isForwards,
292 + isBackwards: isBackwards,
293 + findNode: findNode,
294 + getEditingHost: getEditingHost,
295 + getParentBlock: getParentBlock,
296 + isInSameBlock: isInSameBlock,
297 + isInSameEditingHost: isInSameEditingHost,
298 + isBeforeContentEditableFalse: curry(isNextToContentEditableFalse, 0),
299 + isAfterContentEditableFalse: curry(isNextToContentEditableFalse, -1),
300 + normalizeRange: normalizeRange
301 + };
302 +});
1 +/**
2 + * CaretWalker.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * This module contains logic for moving around a virtual caret in logical order within a DOM element.
13 + *
14 + * It ignores the most obvious invalid caret locations such as within a script element or within a
15 + * contentEditable=false element but it will return locations that isn't possible to render visually.
16 + *
17 + * @private
18 + * @class tinymce.caret.CaretWalker
19 + * @example
20 + * var caretWalker = new CaretWalker(rootElm);
21 + *
22 + * var prevLogicalCaretPosition = caretWalker.prev(CaretPosition.fromRangeStart(range));
23 + * var nextLogicalCaretPosition = caretWalker.next(CaretPosition.fromRangeEnd(range));
24 + */
25 +define("tinymce/caret/CaretWalker", [
26 + "tinymce/dom/NodeType",
27 + "tinymce/caret/CaretCandidate",
28 + "tinymce/caret/CaretPosition",
29 + "tinymce/caret/CaretUtils",
30 + "tinymce/util/Arr",
31 + "tinymce/util/Fun"
32 +], function(NodeType, CaretCandidate, CaretPosition, CaretUtils, Arr, Fun) {
33 + var isContentEditableFalse = NodeType.isContentEditableFalse,
34 + isText = NodeType.isText,
35 + isElement = NodeType.isElement,
36 + isBr = NodeType.isBr,
37 + isForwards = CaretUtils.isForwards,
38 + isBackwards = CaretUtils.isBackwards,
39 + isCaretCandidate = CaretCandidate.isCaretCandidate,
40 + isAtomic = CaretCandidate.isAtomic,
41 + isEditableCaretCandidate = CaretCandidate.isEditableCaretCandidate;
42 +
43 + function getParents(node, rootNode) {
44 + var parents = [];
45 +
46 + while (node && node != rootNode) {
47 + parents.push(node);
48 + node = node.parentNode;
49 + }
50 +
51 + return parents;
52 + }
53 +
54 + function nodeAtIndex(container, offset) {
55 + if (container.hasChildNodes() && offset < container.childNodes.length) {
56 + return container.childNodes[offset];
57 + }
58 +
59 + return null;
60 + }
61 +
62 + function getCaretCandidatePosition(direction, node) {
63 + if (isForwards(direction)) {
64 + if (isCaretCandidate(node.previousSibling) && !isText(node.previousSibling)) {
65 + return CaretPosition.before(node);
66 + }
67 +
68 + if (isText(node)) {
69 + return CaretPosition(node, 0);
70 + }
71 + }
72 +
73 + if (isBackwards(direction)) {
74 + if (isCaretCandidate(node.nextSibling) && !isText(node.nextSibling)) {
75 + return CaretPosition.after(node);
76 + }
77 +
78 + if (isText(node)) {
79 + return CaretPosition(node, node.data.length);
80 + }
81 + }
82 +
83 + if (isBackwards(direction)) {
84 + if (isBr(node)) {
85 + return CaretPosition.before(node);
86 + }
87 +
88 + return CaretPosition.after(node);
89 + }
90 +
91 + return CaretPosition.before(node);
92 + }
93 +
94 + // Jumps over BR elements <p>|<br></p><p>a</p> -> <p><br></p><p>|a</p>
95 + function isBrBeforeBlock(node, rootNode) {
96 + var next;
97 +
98 + if (!NodeType.isBr(node)) {
99 + return false;
100 + }
101 +
102 + next = findCaretPosition(1, CaretPosition.after(node), rootNode);
103 + if (!next) {
104 + return false;
105 + }
106 +
107 + return !CaretUtils.isInSameBlock(CaretPosition.before(node), CaretPosition.before(next), rootNode);
108 + }
109 +
110 + function findCaretPosition(direction, startCaretPosition, rootNode) {
111 + var container, offset, node, nextNode, innerNode,
112 + rootContentEditableFalseElm, caretPosition;
113 +
114 + if (!isElement(rootNode) || !startCaretPosition) {
115 + return null;
116 + }
117 +
118 + caretPosition = startCaretPosition;
119 + container = caretPosition.container();
120 + offset = caretPosition.offset();
121 +
122 + if (isText(container)) {
123 + if (isBackwards(direction) && offset > 0) {
124 + return CaretPosition(container, --offset);
125 + }
126 +
127 + if (isForwards(direction) && offset < container.length) {
128 + return CaretPosition(container, ++offset);
129 + }
130 +
131 + node = container;
132 + } else {
133 + if (isBackwards(direction) && offset > 0) {
134 + nextNode = nodeAtIndex(container, offset - 1);
135 + if (isCaretCandidate(nextNode)) {
136 + if (!isAtomic(nextNode)) {
137 + innerNode = CaretUtils.findNode(nextNode, direction, isEditableCaretCandidate, nextNode);
138 + if (innerNode) {
139 + if (isText(innerNode)) {
140 + return CaretPosition(innerNode, innerNode.data.length);
141 + }
142 +
143 + return CaretPosition.after(innerNode);
144 + }
145 + }
146 +
147 + if (isText(nextNode)) {
148 + return CaretPosition(nextNode, nextNode.data.length);
149 + }
150 +
151 + return CaretPosition.before(nextNode);
152 + }
153 + }
154 +
155 + if (isForwards(direction) && offset < container.childNodes.length) {
156 + nextNode = nodeAtIndex(container, offset);
157 + if (isCaretCandidate(nextNode)) {
158 + if (isBrBeforeBlock(nextNode, rootNode)) {
159 + return findCaretPosition(direction, CaretPosition.after(nextNode), rootNode);
160 + }
161 +
162 + if (!isAtomic(nextNode)) {
163 + innerNode = CaretUtils.findNode(nextNode, direction, isEditableCaretCandidate, nextNode);
164 + if (innerNode) {
165 + if (isText(innerNode)) {
166 + return CaretPosition(innerNode, 0);
167 + }
168 +
169 + return CaretPosition.before(innerNode);
170 + }
171 + }
172 +
173 + if (isText(nextNode)) {
174 + return CaretPosition(nextNode, 0);
175 + }
176 +
177 + return CaretPosition.after(nextNode);
178 + }
179 + }
180 +
181 + node = caretPosition.getNode();
182 + }
183 +
184 + if ((isForwards(direction) && caretPosition.isAtEnd()) || (isBackwards(direction) && caretPosition.isAtStart())) {
185 + node = CaretUtils.findNode(node, direction, Fun.constant(true), rootNode, true);
186 + if (isEditableCaretCandidate(node)) {
187 + return getCaretCandidatePosition(direction, node);
188 + }
189 + }
190 +
191 + nextNode = CaretUtils.findNode(node, direction, isEditableCaretCandidate, rootNode);
192 +
193 + rootContentEditableFalseElm = Arr.last(Arr.filter(getParents(container, rootNode), isContentEditableFalse));
194 + if (rootContentEditableFalseElm && (!nextNode || !rootContentEditableFalseElm.contains(nextNode))) {
195 + if (isForwards(direction)) {
196 + caretPosition = CaretPosition.after(rootContentEditableFalseElm);
197 + } else {
198 + caretPosition = CaretPosition.before(rootContentEditableFalseElm);
199 + }
200 +
201 + return caretPosition;
202 + }
203 +
204 + if (nextNode) {
205 + return getCaretCandidatePosition(direction, nextNode);
206 + }
207 +
208 + return null;
209 + }
210 +
211 + return function(rootNode) {
212 + return {
213 + /**
214 + * Returns the next logical caret position from the specificed input
215 + * caretPoisiton or null if there isn't any more positions left for example
216 + * at the end specified root element.
217 + *
218 + * @method next
219 + * @param {tinymce.caret.CaretPosition} caretPosition Caret position to start from.
220 + * @return {tinymce.caret.CaretPosition} CaretPosition or null if no position was found.
221 + */
222 + next: function(caretPosition) {
223 + return findCaretPosition(1, caretPosition, rootNode);
224 + },
225 +
226 + /**
227 + * Returns the previous logical caret position from the specificed input
228 + * caretPoisiton or null if there isn't any more positions left for example
229 + * at the end specified root element.
230 + *
231 + * @method prev
232 + * @param {tinymce.caret.CaretPosition} caretPosition Caret position to start from.
233 + * @return {tinymce.caret.CaretPosition} CaretPosition or null if no position was found.
234 + */
235 + prev: function(caretPosition) {
236 + return findCaretPosition(-1, caretPosition, rootNode);
237 + }
238 + };
239 + };
240 +});
...\ No newline at end of file ...\ No newline at end of file
1 +/**
2 + * FakeCaret.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * This module contains logic for rendering a fake visual caret.
13 + *
14 + * @private
15 + * @class tinymce.caret.FakeCaret
16 + */
17 +define("tinymce/caret/FakeCaret", [
18 + "tinymce/caret/CaretContainer",
19 + "tinymce/caret/CaretPosition",
20 + "tinymce/dom/NodeType",
21 + "tinymce/dom/RangeUtils",
22 + "tinymce/dom/DomQuery",
23 + "tinymce/geom/ClientRect",
24 + "tinymce/util/Delay"
25 +], function(CaretContainer, CaretPosition, NodeType, RangeUtils, $, ClientRect, Delay) {
26 + var isContentEditableFalse = NodeType.isContentEditableFalse;
27 +
28 + return function(rootNode, isBlock) {
29 + var cursorInterval, $lastVisualCaret, caretContainerNode;
30 +
31 + function getAbsoluteClientRect(node, before) {
32 + var clientRect = ClientRect.collapse(node.getBoundingClientRect(), before),
33 + docElm, scrollX, scrollY, margin, rootRect;
34 +
35 + if (rootNode.tagName == 'BODY') {
36 + docElm = rootNode.ownerDocument.documentElement;
37 + scrollX = rootNode.scrollLeft || docElm.scrollLeft;
38 + scrollY = rootNode.scrollTop || docElm.scrollTop;
39 + } else {
40 + rootRect = rootNode.getBoundingClientRect();
41 + scrollX = rootNode.scrollLeft - rootRect.left;
42 + scrollY = rootNode.scrollTop - rootRect.top;
43 + }
44 +
45 + clientRect.left += scrollX;
46 + clientRect.right += scrollX;
47 + clientRect.top += scrollY;
48 + clientRect.bottom += scrollY;
49 + clientRect.width = 1;
50 +
51 + margin = node.offsetWidth - node.clientWidth;
52 +
53 + if (margin > 0) {
54 + if (before) {
55 + margin *= -1;
56 + }
57 +
58 + clientRect.left += margin;
59 + clientRect.right += margin;
60 + }
61 +
62 + return clientRect;
63 + }
64 +
65 + function trimInlineCaretContainers() {
66 + var contentEditableFalseNodes, node, sibling, i, data;
67 +
68 + contentEditableFalseNodes = $('*[contentEditable=false]', rootNode);
69 + for (i = 0; i < contentEditableFalseNodes.length; i++) {
70 + node = contentEditableFalseNodes[i];
71 +
72 + sibling = node.previousSibling;
73 + if (CaretContainer.endsWithCaretContainer(sibling)) {
74 + data = sibling.data;
75 +
76 + if (data.length == 1) {
77 + sibling.parentNode.removeChild(sibling);
78 + } else {
79 + sibling.deleteData(data.length - 1, 1);
80 + }
81 + }
82 +
83 + sibling = node.nextSibling;
84 + if (CaretContainer.startsWithCaretContainer(sibling)) {
85 + data = sibling.data;
86 +
87 + if (data.length == 1) {
88 + sibling.parentNode.removeChild(sibling);
89 + } else {
90 + sibling.deleteData(0, 1);
91 + }
92 + }
93 + }
94 +
95 + return null;
96 + }
97 +
98 + function show(before, node) {
99 + var clientRect, rng, container;
100 +
101 + hide();
102 +
103 + if (isBlock(node)) {
104 + caretContainerNode = CaretContainer.insertBlock('p', node, before);
105 + clientRect = getAbsoluteClientRect(node, before);
106 + $(caretContainerNode).css('top', clientRect.top);
107 +
108 + $lastVisualCaret = $('<div class="mce-visual-caret" data-mce-bogus="all"></div>').css(clientRect).appendTo(rootNode);
109 +
110 + if (before) {
111 + $lastVisualCaret.addClass('mce-visual-caret-before');
112 + }
113 +
114 + startBlink();
115 +
116 + rng = node.ownerDocument.createRange();
117 + container = caretContainerNode.firstChild;
118 + rng.setStart(container, 0);
119 + rng.setEnd(container, 1);
120 + } else {
121 + caretContainerNode = CaretContainer.insertInline(node, before);
122 + rng = node.ownerDocument.createRange();
123 +
124 + if (isContentEditableFalse(caretContainerNode.nextSibling)) {
125 + rng.setStart(caretContainerNode, 0);
126 + rng.setEnd(caretContainerNode, 0);
127 + } else {
128 + rng.setStart(caretContainerNode, 1);
129 + rng.setEnd(caretContainerNode, 1);
130 + }
131 +
132 + return rng;
133 + }
134 +
135 + return rng;
136 + }
137 +
138 + function hide() {
139 + trimInlineCaretContainers();
140 +
141 + if (caretContainerNode) {
142 + CaretContainer.remove(caretContainerNode);
143 + caretContainerNode = null;
144 + }
145 +
146 + if ($lastVisualCaret) {
147 + $lastVisualCaret.remove();
148 + $lastVisualCaret = null;
149 + }
150 +
151 + clearInterval(cursorInterval);
152 + }
153 +
154 + function startBlink() {
155 + cursorInterval = Delay.setInterval(function() {
156 + $('div.mce-visual-caret', rootNode).toggleClass('mce-visual-caret-hidden');
157 + }, 500);
158 + }
159 +
160 + function destroy() {
161 + Delay.clearInterval(cursorInterval);
162 + }
163 +
164 + function getCss() {
165 + return (
166 + '.mce-visual-caret {' +
167 + 'position: absolute;' +
168 + 'background-color: black;' +
169 + 'background-color: currentcolor;' +
170 + '}' +
171 + '.mce-visual-caret-hidden {' +
172 + 'display: none;' +
173 + '}' +
174 + '*[data-mce-caret] {' +
175 + 'position: absolute;' +
176 + 'left: -1000px;' +
177 + 'right: auto;' +
178 + 'top: 0;' +
179 + 'margin: 0;' +
180 + 'padding: 0;' +
181 + '}'
182 + );
183 + }
184 +
185 + return {
186 + show: show,
187 + hide: hide,
188 + getCss: getCss,
189 + destroy: destroy
190 + };
191 + };
192 +});
...\ No newline at end of file ...\ No newline at end of file
1 +/**
2 + * LineUtils.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * Utility functions for working with lines.
13 + *
14 + * @private
15 + * @class tinymce.caret.LineUtils
16 + */
17 +define("tinymce/caret/LineUtils", [
18 + "tinymce/util/Fun",
19 + "tinymce/util/Arr",
20 + "tinymce/dom/NodeType",
21 + "tinymce/dom/Dimensions",
22 + "tinymce/geom/ClientRect",
23 + "tinymce/caret/CaretUtils",
24 + "tinymce/caret/CaretCandidate"
25 +], function(Fun, Arr, NodeType, Dimensions, ClientRect, CaretUtils, CaretCandidate) {
26 + var isContentEditableFalse = NodeType.isContentEditableFalse,
27 + findNode = CaretUtils.findNode,
28 + curry = Fun.curry;
29 +
30 + function distanceToRectLeft(clientRect, clientX) {
31 + return Math.abs(clientRect.left - clientX);
32 + }
33 +
34 + function distanceToRectRight(clientRect, clientX) {
35 + return Math.abs(clientRect.right - clientX);
36 + }
37 +
38 + function findClosestClientRect(clientRects, clientX) {
39 + function isInside(clientX, clientRect) {
40 + return clientX >= clientRect.left && clientX <= clientRect.right;
41 + }
42 +
43 + return Arr.reduce(clientRects, function(oldClientRect, clientRect) {
44 + var oldDistance, newDistance;
45 +
46 + oldDistance = Math.min(distanceToRectLeft(oldClientRect, clientX), distanceToRectRight(oldClientRect, clientX));
47 + newDistance = Math.min(distanceToRectLeft(clientRect, clientX), distanceToRectRight(clientRect, clientX));
48 +
49 + if (isInside(clientX, clientRect)) {
50 + return clientRect;
51 + }
52 +
53 + if (isInside(clientX, oldClientRect)) {
54 + return oldClientRect;
55 + }
56 +
57 + // cE=false has higher priority
58 + if (newDistance == oldDistance && isContentEditableFalse(clientRect.node)) {
59 + return clientRect;
60 + }
61 +
62 + if (newDistance < oldDistance) {
63 + return clientRect;
64 + }
65 +
66 + return oldClientRect;
67 + });
68 + }
69 +
70 + function walkUntil(direction, rootNode, predicateFn, node) {
71 + while ((node = findNode(node, direction, CaretCandidate.isEditableCaretCandidate, rootNode))) {
72 + if (predicateFn(node)) {
73 + return;
74 + }
75 + }
76 + }
77 +
78 + function findLineNodeRects(rootNode, targetNodeRect) {
79 + var clientRects = [];
80 +
81 + function collect(checkPosFn, node) {
82 + var lineRects;
83 +
84 + lineRects = Arr.filter(Dimensions.getClientRects(node), function(clientRect) {
85 + return !checkPosFn(clientRect, targetNodeRect);
86 + });
87 +
88 + clientRects = clientRects.concat(lineRects);
89 +
90 + return lineRects.length === 0;
91 + }
92 +
93 + clientRects.push(targetNodeRect);
94 + walkUntil(-1, rootNode, curry(collect, ClientRect.isAbove), targetNodeRect.node);
95 + walkUntil(1, rootNode, curry(collect, ClientRect.isBelow), targetNodeRect.node);
96 +
97 + return clientRects;
98 + }
99 +
100 + function getContentEditableFalseChildren(rootNode) {
101 + return Arr.filter(Arr.toArray(rootNode.getElementsByTagName('*')), isContentEditableFalse);
102 + }
103 +
104 + function caretInfo(clientRect, clientX) {
105 + return {
106 + node: clientRect.node,
107 + before: distanceToRectLeft(clientRect, clientX) < distanceToRectRight(clientRect, clientX)
108 + };
109 + }
110 +
111 + function closestCaret(rootNode, clientX, clientY) {
112 + var contentEditableFalseNodeRects, closestNodeRect;
113 +
114 + contentEditableFalseNodeRects = Dimensions.getClientRects(getContentEditableFalseChildren(rootNode));
115 + contentEditableFalseNodeRects = Arr.filter(contentEditableFalseNodeRects, function(clientRect) {
116 + return clientY >= clientRect.top && clientY <= clientRect.bottom;
117 + });
118 +
119 + closestNodeRect = findClosestClientRect(contentEditableFalseNodeRects, clientX);
120 + if (closestNodeRect) {
121 + closestNodeRect = findClosestClientRect(findLineNodeRects(rootNode, closestNodeRect), clientX);
122 + if (closestNodeRect && isContentEditableFalse(closestNodeRect.node)) {
123 + return caretInfo(closestNodeRect, clientX);
124 + }
125 + }
126 +
127 + return null;
128 + }
129 +
130 + return {
131 + findClosestClientRect: findClosestClientRect,
132 + findLineNodeRects: findLineNodeRects,
133 + closestCaret: closestCaret
134 + };
135 +});
...\ No newline at end of file ...\ No newline at end of file
1 +/**
2 + * LineWalker.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * This module lets you walk the document line by line
13 + * returing nodes and client rects for each line.
14 + *
15 + * @private
16 + * @class tinymce.caret.LineWalker
17 + */
18 +define("tinymce/caret/LineWalker", [
19 + "tinymce/util/Fun",
20 + "tinymce/util/Arr",
21 + "tinymce/dom/Dimensions",
22 + "tinymce/caret/CaretCandidate",
23 + "tinymce/caret/CaretUtils",
24 + "tinymce/caret/CaretWalker",
25 + "tinymce/caret/CaretPosition",
26 + "tinymce/geom/ClientRect"
27 +], function(Fun, Arr, Dimensions, CaretCandidate, CaretUtils, CaretWalker, CaretPosition, ClientRect) {
28 + var curry = Fun.curry;
29 +
30 + function findUntil(direction, rootNode, predicateFn, node) {
31 + while ((node = CaretUtils.findNode(node, direction, CaretCandidate.isEditableCaretCandidate, rootNode))) {
32 + if (predicateFn(node)) {
33 + return;
34 + }
35 + }
36 + }
37 +
38 + function walkUntil(direction, isAboveFn, isBeflowFn, rootNode, predicateFn, caretPosition) {
39 + var line = 0, node, result = [], targetClientRect;
40 +
41 + function add(node) {
42 + var i, clientRect, clientRects;
43 +
44 + clientRects = Dimensions.getClientRects(node);
45 + if (direction == -1) {
46 + clientRects = clientRects.reverse();
47 + }
48 +
49 + for (i = 0; i < clientRects.length; i++) {
50 + clientRect = clientRects[i];
51 + if (isBeflowFn(clientRect, targetClientRect)) {
52 + continue;
53 + }
54 +
55 + if (result.length > 0 && isAboveFn(clientRect, Arr.last(result))) {
56 + line++;
57 + }
58 +
59 + clientRect.line = line;
60 +
61 + if (predicateFn(clientRect)) {
62 + return true;
63 + }
64 +
65 + result.push(clientRect);
66 + }
67 + }
68 +
69 + targetClientRect = Arr.last(caretPosition.getClientRects());
70 + if (!targetClientRect) {
71 + return result;
72 + }
73 +
74 + node = caretPosition.getNode();
75 + add(node);
76 + findUntil(direction, rootNode, add, node);
77 +
78 + return result;
79 + }
80 +
81 + function aboveLineNumber(lineNumber, clientRect) {
82 + return clientRect.line > lineNumber;
83 + }
84 +
85 + function isLine(lineNumber, clientRect) {
86 + return clientRect.line === lineNumber;
87 + }
88 +
89 + var upUntil = curry(walkUntil, -1, ClientRect.isAbove, ClientRect.isBelow);
90 + var downUntil = curry(walkUntil, 1, ClientRect.isBelow, ClientRect.isAbove);
91 +
92 + function positionsUntil(direction, rootNode, predicateFn, node) {
93 + var caretWalker = new CaretWalker(rootNode), walkFn, isBelowFn, isAboveFn,
94 + caretPosition, result = [], line = 0, clientRect, targetClientRect;
95 +
96 + function getClientRect(caretPosition) {
97 + if (direction == 1) {
98 + return Arr.last(caretPosition.getClientRects());
99 + }
100 +
101 + return Arr.last(caretPosition.getClientRects());
102 + }
103 +
104 + if (direction == 1) {
105 + walkFn = caretWalker.next;
106 + isBelowFn = ClientRect.isBelow;
107 + isAboveFn = ClientRect.isAbove;
108 + caretPosition = CaretPosition.after(node);
109 + } else {
110 + walkFn = caretWalker.prev;
111 + isBelowFn = ClientRect.isAbove;
112 + isAboveFn = ClientRect.isBelow;
113 + caretPosition = CaretPosition.before(node);
114 + }
115 +
116 + targetClientRect = getClientRect(caretPosition);
117 +
118 + do {
119 + if (!caretPosition.isVisible()) {
120 + continue;
121 + }
122 +
123 + clientRect = getClientRect(caretPosition);
124 +
125 + if (isAboveFn(clientRect, targetClientRect)) {
126 + continue;
127 + }
128 +
129 + if (result.length > 0 && isBelowFn(clientRect, Arr.last(result))) {
130 + line++;
131 + }
132 +
133 + clientRect = ClientRect.clone(clientRect);
134 + clientRect.position = caretPosition;
135 + clientRect.line = line;
136 +
137 + if (predicateFn(clientRect)) {
138 + return result;
139 + }
140 +
141 + result.push(clientRect);
142 + } while ((caretPosition = walkFn(caretPosition)));
143 +
144 + return result;
145 + }
146 +
147 + return {
148 + upUntil: upUntil,
149 + downUntil: downUntil,
150 +
151 + /**
152 + * Find client rects with line and caret position until the predicate returns true.
153 + *
154 + * @method positionsUntil
155 + * @param {Number} direction Direction forward/backward 1/-1.
156 + * @param {DOMNode} rootNode Root node to walk within.
157 + * @param {function} predicateFn Gets the client rect as it's input.
158 + * @param {DOMNode} node Node to start walking from.
159 + * @return {Array} Array of client rects with line and position properties.
160 + */
161 + positionsUntil: positionsUntil,
162 +
163 + isAboveLine: curry(aboveLineNumber),
164 + isLine: curry(isLine)
165 + };
166 +});
...\ No newline at end of file ...\ No newline at end of file
1 +/**
2 + * Binding.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * This class gets dynamically extended to provide a binding between two models. This makes it possible to
13 + * sync the state of two properties in two models by a layer of abstraction.
14 + *
15 + * @private
16 + * @class tinymce.data.Binding
17 + */
18 +define("tinymce/data/Binding", [], function() {
19 + /**
20 + * Constructs a new bidning.
21 + *
22 + * @constructor
23 + * @method Binding
24 + * @param {Object} settings Settings to the binding.
25 + */
26 + function Binding(settings) {
27 + this.create = settings.create;
28 + }
29 +
30 + /**
31 + * Creates a binding for a property on a model.
32 + *
33 + * @method create
34 + * @param {tinymce.data.ObservableObject} model Model to create binding to.
35 + * @param {String} name Name of property to bind.
36 + * @return {tinymce.data.Binding} Binding instance.
37 + */
38 + Binding.create = function(model, name) {
39 + return new Binding({
40 + create: function(otherModel, otherName) {
41 + var bindings;
42 +
43 + function fromSelfToOther(e) {
44 + otherModel.set(otherName, e.value);
45 + }
46 +
47 + function fromOtherToSelf(e) {
48 + model.set(name, e.value);
49 + }
50 +
51 + otherModel.on('change:' + otherName, fromOtherToSelf);
52 + model.on('change:' + name, fromSelfToOther);
53 +
54 + // Keep track of the bindings
55 + bindings = otherModel._bindings;
56 +
57 + if (!bindings) {
58 + bindings = otherModel._bindings = [];
59 +
60 + otherModel.on('destroy', function() {
61 + var i = bindings.length;
62 +
63 + while (i--) {
64 + bindings[i]();
65 + }
66 + });
67 + }
68 +
69 + bindings.push(function() {
70 + model.off('change:' + name, fromSelfToOther);
71 + });
72 +
73 + return model.get(name);
74 + }
75 + });
76 + };
77 +
78 + return Binding;
79 +});
...\ No newline at end of file ...\ No newline at end of file
1 +/**
2 + * ObservableArray.js
3 + *
4 + * Released under LGPL License.
5 + * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6 + *
7 + * License: http://www.tinymce.com/license
8 + * Contributing: http://www.tinymce.com/contributing
9 + */
10 +
11 +/**
12 + * This class is an array that emmits events when mutation occurs.
13 + *
14 + * @private
15 + * @class tinymce.data.ObservableArray
16 + */
17 +define("tinymce/data/ObservableArray", [
18 + "tinymce/util/Observable",
19 + "tinymce/util/Class"
20 +], function(Observable, Class) {
21 + var push = Array.prototype.push, slice = Array.prototype.slice, splice = Array.prototype.splice;
22 +
23 + var ObservableArray = Class.extend({
24 + Mixins: [Observable],
25 +
26 + /**
27 + * Number of items in array.
28 + *
29 + * @field length
30 + * @type Number
31 + */
32 + length: 0,
33 +
34 + /**
35 + * Constructs a new observable object instance.
36 + *
37 + * @constructor
38 + * @param {Object} data Optional initial data for the object.
39 + */
40 + init: function(data) {
41 + if (data) {
42 + this.push.apply(this, data);
43 + }
44 + },
45 +
46 + /**
47 + * Adds items to the end of array.
48 + *
49 + * @method push
50 + * @param {Object} item... Item or items to add to the end of array.
51 + * @return {Number} Number of items that got added.
52 + */
53 + push: function() {
54 + var args, index = this.length;
55 +
56 + args = Array.prototype.slice.call(arguments);
57 + push.apply(this, args);
58 +
59 + this.fire('add', {
60 + items: args,
61 + index: index
62 + });
63 +
64 + return args.length;
65 + },
66 +
67 + /**
68 + * Pops the last item off the array.
69 + *
70 + * @method pop
71 + * @return {Object} Item that got popped out.
72 + */
73 + pop: function() {
74 + return this.splice(this.length - 1, 1)[0];
75 + },
76 +
77 + /**
78 + * Slices out a portion of the array as a new array.
79 + *
80 + * @method slice
81 + * @param {Number} begin Beginning of slice.
82 + * @param {Number} end End of slice.
83 + * @return {Array} Native array instance with items.
84 + */
85 + slice: function(begin, end) {
86 + return slice.call(this, begin, end);
87 + },
88 +
89 + /**
90 + * Removes/replaces/inserts items in the array.
91 + *
92 + * @method splice
93 + * @param {Number} index Index to splice at.
94 + * @param {Number} howMany Optional number of items to splice away.
95 + * @param {Object} item ... Item or items to insert at the specified index.
96 + */
97 + splice: function(index) {
98 + var added, removed, args = slice.call(arguments);
99 +
100 + if (args.length === 1) {
101 + args[1] = this.length;
102 + }
103 +
104 + removed = splice.apply(this, args);
105 + added = args.slice(2);
106 +
107 + if (removed.length > 0) {
108 + this.fire('remove', {items: removed, index: index});
109 + }
110 +
111 + if (added.length > 0) {
112 + this.fire('add', {items: added, index: index});
113 + }
114 +
115 + return removed;
116 + },
117 +
118 + /**
119 + * Removes and returns the first item of the array.
120 + *
121 + * @method shift
122 + * @return {Object} First item of the array.
123 + */
124 + shift: function() {
125 + return this.splice(0, 1)[0];
126 + },
127 +
128 + /**
129 + * Appends an item to the top of array.
130 + *
131 + * @method unshift
132 + * @param {Object} item... Item or items to prepend to array.
133 + * @return {Number} Number of items that got added.
134 + */
135 + unshift: function() {
136 + var args = slice.call(arguments);
137 + this.splice.apply(this, [0, 0].concat(args));
138 + return args.length;
139 + },
140 +
141 + /**
142 + * Executes the callback for each item in the array.
143 + *
144 + * @method forEach
145 + * @param {function} callback Callback to execute for each item in array.
146 + * @param {Object} scope Optional scope for this when executing the callback.
147 + */
148 + forEach: function(callback, scope) {
149 + var i;
150 +
151 + scope = scope || this;
152 + for (i = 0; i < this.length; i++) {
153 + callback.call(scope, this[i], i, this);
154 + }
155 + },
156 +
157 + /**
158 + * Returns the index of the specified item or -1 if it wasn't found.
159 + *
160 + * @method indexOf
161 + * @return {Number} Index of item or null if it wasn't found.
162 + */
163 + indexOf: function(item) {
164 + for (var i = 0; i < this.length; i++) {
165 + if (this[i] === item) {
166 + return i;
167 + }
168 + }
169 +
170 + return -1;
171 + },
172 +
173 + /**
174 + * Filters the observable array into a new observable array
175 + * based on the true/false return value of the specified callback.
176 + *
177 + * @method filter
178 + * @param {function} callback Callback function to execute for each item and filter by.
179 + * @param {Object} thisArg Optional scope for this when executing the callback.
180 + * @return {tinymce.data.ObservableArray} Filtered observable array instance.
181 + */
182 + filter: function(callback, thisArg) {
183 + var self = this, out = new ObservableArray();
184 +
185 + this.forEach(function(item, index) {
186 + if (callback.call(thisArg || self, item, index, self)) {
187 + out.push(item);
188 + }
189 + });
190 +
191 + return out;
192 + }
193 + });
194 +
195 + return ObservableArray;
196 +});
...\ No newline at end of file ...\ 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.