PluginProbe ʕ •ᴥ•ʔ
Advanced Editor Tools / 4.6.7
Advanced Editor Tools v4.6.7
trunk 1.0 1.0.1 2.0 2.2 3.0 3.0.1 3.1 3.2 3.2.4 3.2.7 3.3.9 3.3.9.1 3.3.9.2 3.4.2 3.4.2.1 3.4.5 3.4.5.1 3.4.9 3.5.8 3.5.9 3.5.9.1 4.0 4.0.1 4.0.2 4.1 4.1.1 4.1.7 4.1.9 4.2.3 4.2.3.1 4.2.5 4.2.8 4.3.10 4.3.10.1 4.3.8 4.4.1 4.4.3 4.5.6 4.6.3 4.6.7 4.7.11 4.8.0 4.8.1 4.8.2 5.0.0 5.0.1 5.1.0 5.2 5.2.1 5.3 5.4.0 5.5.0 5.5.1 5.6.0 5.9.0 5.9.1 5.9.2
tinymce-advanced / mce / contextmenu / plugin.js
tinymce-advanced / mce / contextmenu Last commit date
plugin.js 8 years ago plugin.min.js 8 years ago
plugin.js
950 lines
1 (function () {
2
3 var defs = {}; // id -> {dependencies, definition, instance (possibly undefined)}
4
5 // Used when there is no 'main' module.
6 // The name is probably (hopefully) unique so minification removes for releases.
7 var register_3795 = function (id) {
8 var module = dem(id);
9 var fragments = id.split('.');
10 var target = Function('return this;')();
11 for (var i = 0; i < fragments.length - 1; ++i) {
12 if (target[fragments[i]] === undefined)
13 target[fragments[i]] = {};
14 target = target[fragments[i]];
15 }
16 target[fragments[fragments.length - 1]] = module;
17 };
18
19 var instantiate = function (id) {
20 var actual = defs[id];
21 var dependencies = actual.deps;
22 var definition = actual.defn;
23 var len = dependencies.length;
24 var instances = new Array(len);
25 for (var i = 0; i < len; ++i)
26 instances[i] = dem(dependencies[i]);
27 var defResult = definition.apply(null, instances);
28 if (defResult === undefined)
29 throw 'module [' + id + '] returned undefined';
30 actual.instance = defResult;
31 };
32
33 var def = function (id, dependencies, definition) {
34 if (typeof id !== 'string')
35 throw 'module id must be a string';
36 else if (dependencies === undefined)
37 throw 'no dependencies for ' + id;
38 else if (definition === undefined)
39 throw 'no definition function for ' + id;
40 defs[id] = {
41 deps: dependencies,
42 defn: definition,
43 instance: undefined
44 };
45 };
46
47 var dem = function (id) {
48 var actual = defs[id];
49 if (actual === undefined)
50 throw 'module [' + id + '] was undefined';
51 else if (actual.instance === undefined)
52 instantiate(id);
53 return actual.instance;
54 };
55
56 var req = function (ids, callback) {
57 var len = ids.length;
58 var instances = new Array(len);
59 for (var i = 0; i < len; ++i)
60 instances.push(dem(ids[i]));
61 callback.apply(null, callback);
62 };
63
64 var ephox = {};
65
66 ephox.bolt = {
67 module: {
68 api: {
69 define: def,
70 require: req,
71 demand: dem
72 }
73 }
74 };
75
76 var define = def;
77 var require = req;
78 var demand = dem;
79 // this helps with minificiation when using a lot of global references
80 var defineGlobal = function (id, ref) {
81 define(id, [], function () { return ref; });
82 };
83 /*jsc
84 ["tinymce.plugins.contextmenu.Plugin","tinymce.core.dom.DOMUtils","tinymce.core.Env","tinymce.core.PluginManager","tinymce.core.ui.Factory","tinymce.core.util.Tools","tinymce.plugins.contextmenu.RangePoint","global!tinymce.util.Tools.resolve","ephox.katamari.api.Arr","ephox.katamari.api.Option","global!Array","global!Error","global!String","ephox.katamari.api.Fun","global!Object"]
85 jsc*/
86 defineGlobal("global!tinymce.util.Tools.resolve", tinymce.util.Tools.resolve);
87 /**
88 * ResolveGlobal.js
89 *
90 * Released under LGPL License.
91 * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
92 *
93 * License: http://www.tinymce.com/license
94 * Contributing: http://www.tinymce.com/contributing
95 */
96
97 define(
98 'tinymce.core.dom.DOMUtils',
99 [
100 'global!tinymce.util.Tools.resolve'
101 ],
102 function (resolve) {
103 return resolve('tinymce.dom.DOMUtils');
104 }
105 );
106
107 /**
108 * ResolveGlobal.js
109 *
110 * Released under LGPL License.
111 * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
112 *
113 * License: http://www.tinymce.com/license
114 * Contributing: http://www.tinymce.com/contributing
115 */
116
117 define(
118 'tinymce.core.Env',
119 [
120 'global!tinymce.util.Tools.resolve'
121 ],
122 function (resolve) {
123 return resolve('tinymce.Env');
124 }
125 );
126
127 /**
128 * ResolveGlobal.js
129 *
130 * Released under LGPL License.
131 * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
132 *
133 * License: http://www.tinymce.com/license
134 * Contributing: http://www.tinymce.com/contributing
135 */
136
137 define(
138 'tinymce.core.PluginManager',
139 [
140 'global!tinymce.util.Tools.resolve'
141 ],
142 function (resolve) {
143 return resolve('tinymce.PluginManager');
144 }
145 );
146
147 /**
148 * ResolveGlobal.js
149 *
150 * Released under LGPL License.
151 * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
152 *
153 * License: http://www.tinymce.com/license
154 * Contributing: http://www.tinymce.com/contributing
155 */
156
157 define(
158 'tinymce.core.ui.Factory',
159 [
160 'global!tinymce.util.Tools.resolve'
161 ],
162 function (resolve) {
163 return resolve('tinymce.ui.Factory');
164 }
165 );
166
167 /**
168 * ResolveGlobal.js
169 *
170 * Released under LGPL License.
171 * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
172 *
173 * License: http://www.tinymce.com/license
174 * Contributing: http://www.tinymce.com/contributing
175 */
176
177 define(
178 'tinymce.core.util.Tools',
179 [
180 'global!tinymce.util.Tools.resolve'
181 ],
182 function (resolve) {
183 return resolve('tinymce.util.Tools');
184 }
185 );
186
187 defineGlobal("global!Array", Array);
188 defineGlobal("global!Error", Error);
189 define(
190 'ephox.katamari.api.Fun',
191
192 [
193 'global!Array',
194 'global!Error'
195 ],
196
197 function (Array, Error) {
198
199 var noop = function () { };
200
201 var compose = function (fa, fb) {
202 return function () {
203 return fa(fb.apply(null, arguments));
204 };
205 };
206
207 var constant = function (value) {
208 return function () {
209 return value;
210 };
211 };
212
213 var identity = function (x) {
214 return x;
215 };
216
217 var tripleEquals = function(a, b) {
218 return a === b;
219 };
220
221 // Don't use array slice(arguments), makes the whole function unoptimisable on Chrome
222 var curry = function (f) {
223 // equivalent to arguments.slice(1)
224 // starting at 1 because 0 is the f, makes things tricky.
225 // Pay attention to what variable is where, and the -1 magic.
226 // thankfully, we have tests for this.
227 var args = new Array(arguments.length - 1);
228 for (var i = 1; i < arguments.length; i++) args[i-1] = arguments[i];
229
230 return function () {
231 var newArgs = new Array(arguments.length);
232 for (var j = 0; j < newArgs.length; j++) newArgs[j] = arguments[j];
233
234 var all = args.concat(newArgs);
235 return f.apply(null, all);
236 };
237 };
238
239 var not = function (f) {
240 return function () {
241 return !f.apply(null, arguments);
242 };
243 };
244
245 var die = function (msg) {
246 return function () {
247 throw new Error(msg);
248 };
249 };
250
251 var apply = function (f) {
252 return f();
253 };
254
255 var call = function(f) {
256 f();
257 };
258
259 var never = constant(false);
260 var always = constant(true);
261
262
263 return {
264 noop: noop,
265 compose: compose,
266 constant: constant,
267 identity: identity,
268 tripleEquals: tripleEquals,
269 curry: curry,
270 not: not,
271 die: die,
272 apply: apply,
273 call: call,
274 never: never,
275 always: always
276 };
277 }
278 );
279
280 defineGlobal("global!Object", Object);
281 define(
282 'ephox.katamari.api.Option',
283
284 [
285 'ephox.katamari.api.Fun',
286 'global!Object'
287 ],
288
289 function (Fun, Object) {
290
291 var never = Fun.never;
292 var always = Fun.always;
293
294 /**
295 Option objects support the following methods:
296
297 fold :: this Option a -> ((() -> b, a -> b)) -> Option b
298
299 is :: this Option a -> a -> Boolean
300
301 isSome :: this Option a -> () -> Boolean
302
303 isNone :: this Option a -> () -> Boolean
304
305 getOr :: this Option a -> a -> a
306
307 getOrThunk :: this Option a -> (() -> a) -> a
308
309 getOrDie :: this Option a -> String -> a
310
311 or :: this Option a -> Option a -> Option a
312 - if some: return self
313 - if none: return opt
314
315 orThunk :: this Option a -> (() -> Option a) -> Option a
316 - Same as "or", but uses a thunk instead of a value
317
318 map :: this Option a -> (a -> b) -> Option b
319 - "fmap" operation on the Option Functor.
320 - same as 'each'
321
322 ap :: this Option a -> Option (a -> b) -> Option b
323 - "apply" operation on the Option Apply/Applicative.
324 - Equivalent to <*> in Haskell/PureScript.
325
326 each :: this Option a -> (a -> b) -> Option b
327 - same as 'map'
328
329 bind :: this Option a -> (a -> Option b) -> Option b
330 - "bind"/"flatMap" operation on the Option Bind/Monad.
331 - Equivalent to >>= in Haskell/PureScript; flatMap in Scala.
332
333 flatten :: {this Option (Option a))} -> () -> Option a
334 - "flatten"/"join" operation on the Option Monad.
335
336 exists :: this Option a -> (a -> Boolean) -> Boolean
337
338 forall :: this Option a -> (a -> Boolean) -> Boolean
339
340 filter :: this Option a -> (a -> Boolean) -> Option a
341
342 equals :: this Option a -> Option a -> Boolean
343
344 equals_ :: this Option a -> (Option a, a -> Boolean) -> Boolean
345
346 toArray :: this Option a -> () -> [a]
347
348 */
349
350 var none = function () { return NONE; };
351
352 var NONE = (function () {
353 var eq = function (o) {
354 return o.isNone();
355 };
356
357 // inlined from peanut, maybe a micro-optimisation?
358 var call = function (thunk) { return thunk(); };
359 var id = function (n) { return n; };
360 var noop = function () { };
361
362 var me = {
363 fold: function (n, s) { return n(); },
364 is: never,
365 isSome: never,
366 isNone: always,
367 getOr: id,
368 getOrThunk: call,
369 getOrDie: function (msg) {
370 throw new Error(msg || 'error: getOrDie called on none.');
371 },
372 or: id,
373 orThunk: call,
374 map: none,
375 ap: none,
376 each: noop,
377 bind: none,
378 flatten: none,
379 exists: never,
380 forall: always,
381 filter: none,
382 equals: eq,
383 equals_: eq,
384 toArray: function () { return []; },
385 toString: Fun.constant("none()")
386 };
387 if (Object.freeze) Object.freeze(me);
388 return me;
389 })();
390
391
392 /** some :: a -> Option a */
393 var some = function (a) {
394
395 // inlined from peanut, maybe a micro-optimisation?
396 var constant_a = function () { return a; };
397
398 var self = function () {
399 // can't Fun.constant this one
400 return me;
401 };
402
403 var map = function (f) {
404 return some(f(a));
405 };
406
407 var bind = function (f) {
408 return f(a);
409 };
410
411 var me = {
412 fold: function (n, s) { return s(a); },
413 is: function (v) { return a === v; },
414 isSome: always,
415 isNone: never,
416 getOr: constant_a,
417 getOrThunk: constant_a,
418 getOrDie: constant_a,
419 or: self,
420 orThunk: self,
421 map: map,
422 ap: function (optfab) {
423 return optfab.fold(none, function(fab) {
424 return some(fab(a));
425 });
426 },
427 each: function (f) {
428 f(a);
429 },
430 bind: bind,
431 flatten: constant_a,
432 exists: bind,
433 forall: bind,
434 filter: function (f) {
435 return f(a) ? me : NONE;
436 },
437 equals: function (o) {
438 return o.is(a);
439 },
440 equals_: function (o, elementEq) {
441 return o.fold(
442 never,
443 function (b) { return elementEq(a, b); }
444 );
445 },
446 toArray: function () {
447 return [a];
448 },
449 toString: function () {
450 return 'some(' + a + ')';
451 }
452 };
453 return me;
454 };
455
456 /** from :: undefined|null|a -> Option a */
457 var from = function (value) {
458 return value === null || value === undefined ? NONE : some(value);
459 };
460
461 return {
462 some: some,
463 none: none,
464 from: from
465 };
466 }
467 );
468
469 defineGlobal("global!String", String);
470 define(
471 'ephox.katamari.api.Arr',
472
473 [
474 'ephox.katamari.api.Option',
475 'global!Array',
476 'global!Error',
477 'global!String'
478 ],
479
480 function (Option, Array, Error, String) {
481 // Use the native Array.indexOf if it is available (IE9+) otherwise fall back to manual iteration
482 // https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf
483 var rawIndexOf = (function () {
484 var pIndexOf = Array.prototype.indexOf;
485
486 var fastIndex = function (xs, x) { return pIndexOf.call(xs, x); };
487
488 var slowIndex = function(xs, x) { return slowIndexOf(xs, x); };
489
490 return pIndexOf === undefined ? slowIndex : fastIndex;
491 })();
492
493 var indexOf = function (xs, x) {
494 // The rawIndexOf method does not wrap up in an option. This is for performance reasons.
495 var r = rawIndexOf(xs, x);
496 return r === -1 ? Option.none() : Option.some(r);
497 };
498
499 var contains = function (xs, x) {
500 return rawIndexOf(xs, x) > -1;
501 };
502
503 // Using findIndex is likely less optimal in Chrome (dynamic return type instead of bool)
504 // but if we need that micro-optimisation we can inline it later.
505 var exists = function (xs, pred) {
506 return findIndex(xs, pred).isSome();
507 };
508
509 var range = function (num, f) {
510 var r = [];
511 for (var i = 0; i < num; i++) {
512 r.push(f(i));
513 }
514 return r;
515 };
516
517 // It's a total micro optimisation, but these do make some difference.
518 // Particularly for browsers other than Chrome.
519 // - length caching
520 // http://jsperf.com/browser-diet-jquery-each-vs-for-loop/69
521 // - not using push
522 // http://jsperf.com/array-direct-assignment-vs-push/2
523
524 var chunk = function (array, size) {
525 var r = [];
526 for (var i = 0; i < array.length; i += size) {
527 var s = array.slice(i, i + size);
528 r.push(s);
529 }
530 return r;
531 };
532
533 var map = function(xs, f) {
534 // pre-allocating array size when it's guaranteed to be known
535 // http://jsperf.com/push-allocated-vs-dynamic/22
536 var len = xs.length;
537 var r = new Array(len);
538 for (var i = 0; i < len; i++) {
539 var x = xs[i];
540 r[i] = f(x, i, xs);
541 }
542 return r;
543 };
544
545 // Unwound implementing other functions in terms of each.
546 // The code size is roughly the same, and it should allow for better optimisation.
547 var each = function(xs, f) {
548 for (var i = 0, len = xs.length; i < len; i++) {
549 var x = xs[i];
550 f(x, i, xs);
551 }
552 };
553
554 var eachr = function (xs, f) {
555 for (var i = xs.length - 1; i >= 0; i--) {
556 var x = xs[i];
557 f(x, i, xs);
558 }
559 };
560
561 var partition = function(xs, pred) {
562 var pass = [];
563 var fail = [];
564 for (var i = 0, len = xs.length; i < len; i++) {
565 var x = xs[i];
566 var arr = pred(x, i, xs) ? pass : fail;
567 arr.push(x);
568 }
569 return { pass: pass, fail: fail };
570 };
571
572 var filter = function(xs, pred) {
573 var r = [];
574 for (var i = 0, len = xs.length; i < len; i++) {
575 var x = xs[i];
576 if (pred(x, i, xs)) {
577 r.push(x);
578 }
579 }
580 return r;
581 };
582
583 /*
584 * Groups an array into contiguous arrays of like elements. Whether an element is like or not depends on f.
585 *
586 * f is a function that derives a value from an element - e.g. true or false, or a string.
587 * Elements are like if this function generates the same value for them (according to ===).
588 *
589 *
590 * Order of the elements is preserved. Arr.flatten() on the result will return the original list, as with Haskell groupBy function.
591 * For a good explanation, see the group function (which is a special case of groupBy)
592 * http://hackage.haskell.org/package/base-4.7.0.0/docs/Data-List.html#v:group
593 */
594 var groupBy = function (xs, f) {
595 if (xs.length === 0) {
596 return [];
597 } else {
598 var wasType = f(xs[0]); // initial case for matching
599 var r = [];
600 var group = [];
601
602 for (var i = 0, len = xs.length; i < len; i++) {
603 var x = xs[i];
604 var type = f(x);
605 if (type !== wasType) {
606 r.push(group);
607 group = [];
608 }
609 wasType = type;
610 group.push(x);
611 }
612 if (group.length !== 0) {
613 r.push(group);
614 }
615 return r;
616 }
617 };
618
619 var foldr = function (xs, f, acc) {
620 eachr(xs, function (x) {
621 acc = f(acc, x);
622 });
623 return acc;
624 };
625
626 var foldl = function (xs, f, acc) {
627 each(xs, function (x) {
628 acc = f(acc, x);
629 });
630 return acc;
631 };
632
633 var find = function (xs, pred) {
634 for (var i = 0, len = xs.length; i < len; i++) {
635 var x = xs[i];
636 if (pred(x, i, xs)) {
637 return Option.some(x);
638 }
639 }
640 return Option.none();
641 };
642
643 var findIndex = function (xs, pred) {
644 for (var i = 0, len = xs.length; i < len; i++) {
645 var x = xs[i];
646 if (pred(x, i, xs)) {
647 return Option.some(i);
648 }
649 }
650
651 return Option.none();
652 };
653
654 var slowIndexOf = function (xs, x) {
655 for (var i = 0, len = xs.length; i < len; ++i) {
656 if (xs[i] === x) {
657 return i;
658 }
659 }
660
661 return -1;
662 };
663
664 var push = Array.prototype.push;
665 var flatten = function (xs) {
666 // Note, this is possible because push supports multiple arguments:
667 // http://jsperf.com/concat-push/6
668 // Note that in the past, concat() would silently work (very slowly) for array-like objects.
669 // With this change it will throw an error.
670 var r = [];
671 for (var i = 0, len = xs.length; i < len; ++i) {
672 // Ensure that each value is an array itself
673 if (! Array.prototype.isPrototypeOf(xs[i])) throw new Error('Arr.flatten item ' + i + ' was not an array, input: ' + xs);
674 push.apply(r, xs[i]);
675 }
676 return r;
677 };
678
679 var bind = function (xs, f) {
680 var output = map(xs, f);
681 return flatten(output);
682 };
683
684 var forall = function (xs, pred) {
685 for (var i = 0, len = xs.length; i < len; ++i) {
686 var x = xs[i];
687 if (pred(x, i, xs) !== true) {
688 return false;
689 }
690 }
691 return true;
692 };
693
694 var equal = function (a1, a2) {
695 return a1.length === a2.length && forall(a1, function (x, i) {
696 return x === a2[i];
697 });
698 };
699
700 var slice = Array.prototype.slice;
701 var reverse = function (xs) {
702 var r = slice.call(xs, 0);
703 r.reverse();
704 return r;
705 };
706
707 var difference = function (a1, a2) {
708 return filter(a1, function (x) {
709 return !contains(a2, x);
710 });
711 };
712
713 var mapToObject = function(xs, f) {
714 var r = {};
715 for (var i = 0, len = xs.length; i < len; i++) {
716 var x = xs[i];
717 r[String(x)] = f(x, i);
718 }
719 return r;
720 };
721
722 var pure = function(x) {
723 return [x];
724 };
725
726 var sort = function (xs, comparator) {
727 var copy = slice.call(xs, 0);
728 copy.sort(comparator);
729 return copy;
730 };
731
732 return {
733 map: map,
734 each: each,
735 eachr: eachr,
736 partition: partition,
737 filter: filter,
738 groupBy: groupBy,
739 indexOf: indexOf,
740 foldr: foldr,
741 foldl: foldl,
742 find: find,
743 findIndex: findIndex,
744 flatten: flatten,
745 bind: bind,
746 forall: forall,
747 exists: exists,
748 contains: contains,
749 equal: equal,
750 reverse: reverse,
751 chunk: chunk,
752 difference: difference,
753 mapToObject: mapToObject,
754 pure: pure,
755 sort: sort,
756 range: range
757 };
758 }
759 );
760 /**
761 * RangePoint.js
762 *
763 * Released under LGPL License.
764 * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
765 *
766 * License: http://www.tinymce.com/license
767 * Contributing: http://www.tinymce.com/contributing
768 */
769
770 define(
771 'tinymce.plugins.contextmenu.RangePoint',
772 [
773 'ephox.katamari.api.Arr'
774 ],
775 function (Arr) {
776 var containsXY = function (clientRect, clientX, clientY) {
777 return (
778 clientX >= clientRect.left &&
779 clientX <= clientRect.right &&
780 clientY >= clientRect.top &&
781 clientY <= clientRect.bottom
782 );
783 };
784
785 var isXYWithinRange = function (clientX, clientY, range) {
786 if (range.collapsed) {
787 return false;
788 }
789
790 return Arr.foldl(range.getClientRects(), function (state, rect) {
791 return state || containsXY(rect, clientX, clientY);
792 }, false);
793 };
794
795 return {
796 isXYWithinRange: isXYWithinRange
797 };
798 }
799 );
800 /**
801 * Plugin.js
802 *
803 * Released under LGPL License.
804 * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
805 *
806 * License: http://www.tinymce.com/license
807 * Contributing: http://www.tinymce.com/contributing
808 */
809
810 /**
811 * This class contains all core logic for the contextmenu plugin.
812 *
813 * @class tinymce.contextmenu.Plugin
814 * @private
815 */
816 define(
817 'tinymce.plugins.contextmenu.Plugin',
818 [
819 'tinymce.core.dom.DOMUtils',
820 'tinymce.core.Env',
821 'tinymce.core.PluginManager',
822 'tinymce.core.ui.Factory',
823 'tinymce.core.util.Tools',
824 'tinymce.plugins.contextmenu.RangePoint'
825 ],
826 function (DOMUtils, Env, PluginManager, Factory, Tools, RangePoint) {
827 var DOM = DOMUtils.DOM;
828
829 PluginManager.add('contextmenu', function (editor) {
830 var menu, visibleState, contextmenuNeverUseNative = editor.settings.contextmenu_never_use_native;
831
832 var isNativeOverrideKeyEvent = function (e) {
833 return e.ctrlKey && !contextmenuNeverUseNative;
834 };
835
836 var isMacWebKit = function () {
837 return Env.mac && Env.webkit;
838 };
839
840 var isContextMenuVisible = function () {
841 return visibleState === true;
842 };
843
844 var isImage = function (elm) {
845 return elm && elm.nodeName === 'IMG';
846 };
847
848 var isEventOnImageOutsideRange = function (evt, range) {
849 return isImage(evt.target) && RangePoint.isXYWithinRange(evt.clientX, evt.clientY, range) === false;
850 };
851
852 /**
853 * This takes care of a os x native issue where it expands the selection
854 * to the word at the caret position to do "lookups". Since we are overriding
855 * the context menu we also need to override this expanding so the behavior becomes
856 * normalized. Firefox on os x doesn't expand to the word when using the context menu.
857 */
858 editor.on('mousedown', function (e) {
859 if (isMacWebKit() && e.button === 2 && !isNativeOverrideKeyEvent(e) && editor.selection.isCollapsed()) {
860 editor.once('contextmenu', function (e2) {
861 if (!isImage(e2.target)) {
862 editor.selection.placeCaretAt(e2.clientX, e2.clientY);
863 }
864 });
865 }
866 });
867
868 editor.on('contextmenu', function (e) {
869 var contextmenu;
870
871 if (isNativeOverrideKeyEvent(e)) {
872 return;
873 }
874
875 if (isEventOnImageOutsideRange(e, editor.selection.getRng())) {
876 editor.selection.select(e.target);
877 }
878
879 e.preventDefault();
880 contextmenu = editor.settings.contextmenu || 'link openlink image inserttable | cell row column deletetable';
881
882 // Render menu
883 if (!menu) {
884 var items = [];
885
886 Tools.each(contextmenu.split(/[ ,]/), function (name) {
887 var item = editor.menuItems[name];
888
889 if (name == '|') {
890 item = { text: name };
891 }
892
893 if (item) {
894 item.shortcut = ''; // Hide shortcuts
895 items.push(item);
896 }
897 });
898
899 for (var i = 0; i < items.length; i++) {
900 if (items[i].text == '|') {
901 if (i === 0 || i == items.length - 1) {
902 items.splice(i, 1);
903 }
904 }
905 }
906
907 menu = Factory.create('menu', {
908 items: items,
909 context: 'contextmenu',
910 classes: 'contextmenu'
911 }).renderTo();
912
913 menu.on('hide', function (e) {
914 if (e.control === this) {
915 visibleState = false;
916 }
917 });
918
919 editor.on('remove', function () {
920 menu.remove();
921 menu = null;
922 });
923
924 } else {
925 menu.show();
926 }
927
928 // Position menu
929 var pos = { x: e.pageX, y: e.pageY };
930
931 if (!editor.inline) {
932 pos = DOM.getPos(editor.getContentAreaContainer());
933 pos.x += e.clientX;
934 pos.y += e.clientY;
935 }
936
937 menu.moveTo(pos.x, pos.y);
938 visibleState = true;
939 });
940
941 return {
942 isContextMenuVisible: isContextMenuVisible
943 };
944 });
945 return function () { };
946 }
947 );
948 dem('tinymce.plugins.contextmenu.Plugin')();
949 })();
950