1/**
2 * plugin.js
3 *
4 * Released under LGPL License.
5 * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
6 *
7 * License: http://www.tinymce.com/license
8 * Contributing: http://www.tinymce.com/contributing
9 */
10
11/*global tinymce:true, console:true */
12/*eslint no-console:0, new-cap:0 */
13
14/**
15 * This plugin adds missing events form the 4.x API back. Not every event is
16 * properly supported but most things should work.
17 *
18 * Unsupported things:
19 * - No editor.onEvent
20 * - Can't cancel execCommands with beforeExecCommand
21 */
22(function (tinymce) {
23 var reported;
24
25 function noop() {
26 }
27
28 function log(apiCall) {
29 if (!reported && window && window.console) {
30 reported = true;
31 console.log("Deprecated TinyMCE API call: " + apiCall);
32 }
33 }
34
35 function Dispatcher(target, newEventName, argsMap, defaultScope) {
36 target = target || this;
37 var cbs = [];
38
39 if (!newEventName) {
40 this.add = this.addToTop = this.remove = this.dispatch = noop;
41 return;
42 }
43
44 this.add = function (callback, scope, prepend) {
45 log('<target>.on' + newEventName + ".add(..)");
46
47 // Convert callback({arg1:x, arg2:x}) -> callback(arg1, arg2)
48 function patchedEventCallback(e) {
49 var callbackArgs = [];
50
51 if (typeof argsMap == "string") {
52 argsMap = argsMap.split(" ");
53 }
54
55 if (argsMap && typeof argsMap !== "function") {
56 for (var i = 0; i < argsMap.length; i++) {
57 callbackArgs.push(e[argsMap[i]]);
58 }
59 }
60
61 if (typeof argsMap == "function") {
62 callbackArgs = argsMap(newEventName, e, target);
63 if (!callbackArgs) {
64 return;
65 }
66 }
67
68 if (!argsMap) {
69 callbackArgs = [e];
70 }
71
72 callbackArgs.unshift(defaultScope || target);
73
74 if (callback.apply(scope || defaultScope || target, callbackArgs) === false) {
75 e.stopImmediatePropagation();
76 }
77 }
78
79 target.on(newEventName, patchedEventCallback, prepend);
80
81 var handlers = {
82 original: callback,
83 patched: patchedEventCallback
84 };
85
86 cbs.push(handlers);
87 return patchedEventCallback;
88 };
89
90 this.addToTop = function (callback, scope) {
91 this.add(callback, scope, true);
92 };
93
94 this.remove = function (callback) {
95 cbs.forEach(function (item, i) {
96 if (item.original === callback) {
97 cbs.splice(i, 1);
98 return target.off(newEventName, item.patched);
99 }
100 });
101
102 return target.off(newEventName, callback);
103 };
104
105 this.dispatch = function () {
106 target.fire(newEventName);
107 return true;
108 };
109 }
110
111 tinymce.util.Dispatcher = Dispatcher;
112 tinymce.onBeforeUnload = new Dispatcher(tinymce, "BeforeUnload");
113 tinymce.onAddEditor = new Dispatcher(tinymce, "AddEditor", "editor");
114 tinymce.onRemoveEditor = new Dispatcher(tinymce, "RemoveEditor", "editor");
115
116 tinymce.util.Cookie = {
117 get: noop, getHash: noop, remove: noop, set: noop, setHash: noop
118 };
119
120 function patchEditor(editor) {
121
122 function translate(str) {
123 var prefix = editor.settings.language || "en";
124 var prefixedStr = [prefix, str].join('.');
125 var translatedStr = tinymce.i18n.translate(prefixedStr);
126
127 return prefixedStr !== translatedStr ? translatedStr : tinymce.i18n.translate(str);
128 }
129
130 function patchEditorEvents(oldEventNames, argsMap) {
131 tinymce.each(oldEventNames.split(" "), function (oldName) {
132 editor["on" + oldName] = new Dispatcher(editor, oldName, argsMap);
133 });
134 }
135
136 function convertUndoEventArgs(type, event, target) {
137 return [
138 event.level,
139 target
140 ];
141 }
142
143 function filterSelectionEvents(needsSelection) {
144 return function (type, e) {
145 if ((!e.selection && !needsSelection) || e.selection == needsSelection) {
146 return [e];
147 }
148 };
149 }
150
151 if (editor.controlManager) {
152 return;
153 }
154
155 function cmNoop() {
156 var obj = {}, methods = 'add addMenu addSeparator collapse createMenu destroy displayColor expand focus ' +
157 'getLength hasMenus hideMenu isActive isCollapsed isDisabled isRendered isSelected mark ' +
158 'postRender remove removeAll renderHTML renderMenu renderNode renderTo select selectByIndex ' +
159 'setActive setAriaProperty setColor setDisabled setSelected setState showMenu update';
160
161 log('editor.controlManager.*');
162
163 function _noop() {
164 return cmNoop();
165 }
166
167 tinymce.each(methods.split(' '), function (method) {
168 obj[method] = _noop;
169 });
170
171 return obj;
172 }
173
174 editor.controlManager = {
175 buttons: {},
176
177 setDisabled: function (name, state) {
178 log("controlManager.setDisabled(..)");
179
180 if (this.buttons[name]) {
181 this.buttons[name].disabled(state);
182 }
183 },
184
185 setActive: function (name, state) {
186 log("controlManager.setActive(..)");
187
188 if (this.buttons[name]) {
189 this.buttons[name].active(state);
190 }
191 },
192
193 onAdd: new Dispatcher(),
194 onPostRender: new Dispatcher(),
195
196 add: function (obj) {
197 return obj;
198 },
199 createButton: cmNoop,
200 createColorSplitButton: cmNoop,
201 createControl: cmNoop,
202 createDropMenu: cmNoop,
203 createListBox: cmNoop,
204 createMenuButton: cmNoop,
205 createSeparator: cmNoop,
206 createSplitButton: cmNoop,
207 createToolbar: cmNoop,
208 createToolbarGroup: cmNoop,
209 destroy: noop,
210 get: noop,
211 setControlType: cmNoop
212 };
213
214 patchEditorEvents("PreInit BeforeRenderUI PostRender Load Init Remove Activate Deactivate", "editor");
215 patchEditorEvents("Click MouseUp MouseDown DblClick KeyDown KeyUp KeyPress ContextMenu Paste Submit Reset");
216 patchEditorEvents("BeforeExecCommand ExecCommand", "command ui value args"); // args.terminate not supported
217 patchEditorEvents("PreProcess PostProcess LoadContent SaveContent Change");
218 patchEditorEvents("BeforeSetContent BeforeGetContent SetContent GetContent", filterSelectionEvents(false));
219 patchEditorEvents("SetProgressState", "state time");
220 patchEditorEvents("VisualAid", "element hasVisual");
221 patchEditorEvents("Undo Redo", convertUndoEventArgs);
222
223 patchEditorEvents("NodeChange", function (type, e) {
224 return [
225 editor.controlManager,
226 e.element,
227 editor.selection.isCollapsed(),
228 e
229 ];
230 });
231
232 var originalAddButton = editor.addButton;
233 editor.addButton = function (name, settings) {
234 var originalOnPostRender;
235
236 function patchedPostRender() {
237 editor.controlManager.buttons[name] = this;
238
239 if (originalOnPostRender) {
240 return originalOnPostRender.apply(this, arguments);
241 }
242 }
243
244 for (var key in settings) {
245 if (key.toLowerCase() === "onpostrender") {
246 originalOnPostRender = settings[key];
247 settings.onPostRender = patchedPostRender;
248 }
249 }
250
251 if (!originalOnPostRender) {
252 settings.onPostRender = patchedPostRender;
253 }
254
255 if (settings.title) {
256 settings.title = translate(settings.title);
257 }
258
259 return originalAddButton.call(this, name, settings);
260 };
261
262 editor.on('init', function () {
263 var undoManager = editor.undoManager, selection = editor.selection;
264
265 undoManager.onUndo = new Dispatcher(editor, "Undo", convertUndoEventArgs, null, undoManager);
266 undoManager.onRedo = new Dispatcher(editor, "Redo", convertUndoEventArgs, null, undoManager);
267 undoManager.onBeforeAdd = new Dispatcher(editor, "BeforeAddUndo", null, undoManager);
268 undoManager.onAdd = new Dispatcher(editor, "AddUndo", null, undoManager);
269
270 selection.onBeforeGetContent = new Dispatcher(editor, "BeforeGetContent", filterSelectionEvents(true), selection);
271 selection.onGetContent = new Dispatcher(editor, "GetContent", filterSelectionEvents(true), selection);
272 selection.onBeforeSetContent = new Dispatcher(editor, "BeforeSetContent", filterSelectionEvents(true), selection);
273 selection.onSetContent = new Dispatcher(editor, "SetContent", filterSelectionEvents(true), selection);
274 });
275
276 editor.on('BeforeRenderUI', function () {
277 var windowManager = editor.windowManager;
278
279 windowManager.onOpen = new Dispatcher();
280 windowManager.onClose = new Dispatcher();
281 windowManager.createInstance = function (className, a, b, c, d, e) {
282 log("windowManager.createInstance(..)");
283
284 var constr = tinymce.resolve(className);
285 return new constr(a, b, c, d, e);
286 };
287 });
288 }
289
290 tinymce.on('SetupEditor', function (e) {
291 patchEditor(e.editor);
292 });
293
294 tinymce.PluginManager.add("compat3x", patchEditor);
295
296 tinymce.addI18n = function (prefix, o) {
297 var I18n = tinymce.util.I18n, each = tinymce.each;
298
299 if (typeof prefix == "string" && prefix.indexOf('.') === -1) {
300 I18n.add(prefix, o);
301 return;
302 }
303
304 if (!tinymce.is(prefix, 'string')) {
305 each(prefix, function (o, lc) {
306 each(o, function (o, g) {
307 each(o, function (o, k) {
308 if (g === 'common') {
309 I18n.data[lc + '.' + k] = o;
310 } else {
311 I18n.data[lc + '.' + g + '.' + k] = o;
312 }
313 });
314 });
315 });
316 } else {
317 each(o, function (o, k) {
318 I18n.data[prefix + '.' + k] = o;
319 });
320 }
321 };
322})(tinymce);
323