c02eaf316a39908641972eb8e104e2c9c095a684
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / htmlarea / plugins / Plugin.js
1 /**
2 * HTMLArea.plugin class
3 *
4 * Every plugin should be a subclass of this class
5 *
6 */
7 HTMLArea.Plugin = function (editor, pluginName) {
8 };
9 HTMLArea.Plugin = Ext.extend(HTMLArea.Plugin, {
10 /**
11 * HTMLArea.Plugin constructor
12 *
13 * @param object editor: instance of RTE
14 * @param string pluginName: name of the plugin
15 *
16 * @return boolean true if the plugin was configured
17 */
18 constructor: function (editor, pluginName) {
19 this.editor = editor;
20 this.editorNumber = editor.editorId;
21 this.editorId = editor.editorId;
22 this.editorConfiguration = editor.config;
23 this.name = pluginName;
24 try {
25 this.I18N = HTMLArea.I18N[this.name];
26 } catch(e) {
27 this.I18N = new Object();
28 }
29 this.configurePlugin(editor);
30 },
31 /**
32 * Configures the plugin
33 * This function is invoked by the class constructor.
34 * This function should be redefined by the plugin subclass. Normal steps would be:
35 * - registering plugin ingormation with method registerPluginInformation;
36 * - registering any buttons with method registerButton;
37 * - registering any drop-down lists with method registerDropDown.
38 *
39 * @param object editor: instance of RTE
40 *
41 * @return boolean true if the plugin was configured
42 */
43 configurePlugin: function (editor) {
44 return false;
45 },
46 /**
47 * Registers the plugin "About" information
48 *
49 * @param object pluginInformation:
50 * version : the version,
51 * developer : the name of the developer,
52 * developerUrl : the url of the developer,
53 * copyrightOwner : the name of the copyright owner,
54 * sponsor : the name of the sponsor,
55 * sponsorUrl : the url of the sponsor,
56 * license : the type of license (should be "GPL")
57 *
58 * @return boolean true if the information was registered
59 */
60 registerPluginInformation: function (pluginInformation) {
61 if (typeof(pluginInformation) !== 'object') {
62 this.appendToLog('registerPluginInformation', 'Plugin information was not provided', 'warn');
63 return false;
64 } else {
65 this.pluginInformation = pluginInformation;
66 this.pluginInformation.name = this.name;
67 return true;
68 }
69 },
70
71 /**
72 * Returns the plugin information
73 *
74 * @return object the plugin information object
75 */
76 getPluginInformation: function () {
77 return this.pluginInformation;
78 },
79
80 /**
81 * Returns a plugin object
82 *
83 * @param string pluinName: the name of some plugin
84 * @return object the plugin object or null
85 */
86 getPluginInstance: function (pluginName) {
87 return this.editor.getPlugin(pluginName);
88 },
89
90 /**
91 * Returns a current editor mode
92 *
93 * @return string editor mode
94 */
95 getEditorMode: function () {
96 return this.editor.getMode();
97 },
98
99 /**
100 * Returns true if the button is enabled in the toolbar configuration
101 *
102 * @param string buttonId: identification of the button
103 *
104 * @return boolean true if the button is enabled in the toolbar configuration
105 */
106 isButtonInToolbar: function (buttonId) {
107 var index = -1;
108 var i, j, n, m;
109 for (i = 0, n = this.editorConfiguration.toolbar.length; i < n; i++) {
110 var row = this.editorConfiguration.toolbar[i];
111 for (j = 0, m = row.length; j < m; j++) {
112 var group = row[j];
113 index = group.indexOf(buttonId);
114 if (index !== -1) {
115 break;
116 }
117 }
118 if (index !== -1) {
119 break;
120 }
121 }
122 return index !== -1;
123 },
124
125 /**
126 * Returns the button object from the toolbar
127 *
128 * @param string buttonId: identification of the button
129 *
130 * @return object the toolbar button object
131 */
132 getButton: function (buttonId) {
133 return this.editor.toolbar.getButton(buttonId);
134 },
135 /**
136 * Registers a button for inclusion in the toolbar
137 *
138 * @param object buttonConfiguration: the configuration object of the button:
139 * id : unique id for the button
140 * tooltip : tooltip for the button
141 * textMode : enable in text mode
142 * action : name of the function invoked when the button is pressed
143 * context : will be disabled if not inside one of listed elements
144 * hide : hide in menu and show only in context menu (deprecated, use hidden)
145 * hidden : synonym of hide
146 * selection : will be disabled if there is no selection
147 * hotkey : hotkey character
148 * dialog : if true, the button opens a dialogue
149 * dimensions : the opening dimensions object of the dialogue window
150 *
151 * @return boolean true if the button was successfully registered
152 */
153 registerButton: function (buttonConfiguration) {
154 if (this.isButtonInToolbar(buttonConfiguration.id)) {
155 if (Ext.isString(buttonConfiguration.action) && Ext.isFunction(this[buttonConfiguration.action])) {
156 buttonConfiguration.plugins = this;
157 if (buttonConfiguration.dialog) {
158 if (!buttonConfiguration.dimensions) {
159 buttonConfiguration.dimensions = { width: 250, height: 250};
160 }
161 buttonConfiguration.dimensions.top = buttonConfiguration.dimensions.top ? buttonConfiguration.dimensions.top : this.editorConfiguration.dialogueWindows.defaultPositionFromTop;
162 buttonConfiguration.dimensions.left = buttonConfiguration.dimensions.left ? buttonConfiguration.dimensions.left : this.editorConfiguration.dialogueWindows.defaultPositionFromLeft;
163 }
164 buttonConfiguration.hidden = buttonConfiguration.hide;
165 // Apply additional ExtJS config properties set in Page TSConfig
166 // May not always work for values that must be integers
167 if (this.editorConfiguration.buttons[this.editorConfiguration.convertButtonId[buttonConfiguration.id]]) {
168 Ext.applyIf(buttonConfiguration, this.editorConfiguration.buttons[this.editorConfiguration.convertButtonId[buttonConfiguration.id]]);
169 }
170 if (this.editorConfiguration.registerButton(buttonConfiguration)) {
171 var hotKey = buttonConfiguration.hotKey ? buttonConfiguration.hotKey :
172 ((this.editorConfiguration.buttons[this.editorConfiguration.convertButtonId[buttonConfiguration.id]] && this.editorConfiguration.buttons[this.editorConfiguration.convertButtonId[buttonConfiguration.id]].hotKey) ? this.editorConfiguration.buttons[this.editorConfiguration.convertButtonId[buttonConfiguration.id]].hotKey : null);
173 if (!hotKey && buttonConfiguration.hotKey == "0") {
174 hotKey = "0";
175 }
176 if (!hotKey && this.editorConfiguration.buttons[this.editorConfiguration.convertButtonId[buttonConfiguration.id]] && this.editorConfiguration.buttons[this.editorConfiguration.convertButtonId[buttonConfiguration.id]].hotKey == "0") {
177 hotKey = "0";
178 }
179 if (hotKey || hotKey == "0") {
180 var hotKeyConfiguration = {
181 id : hotKey,
182 cmd : buttonConfiguration.id
183 };
184 return this.registerHotKey(hotKeyConfiguration);
185 }
186 return true;
187 }
188 } else {
189 this.appendToLog('registerButton', 'Function ' + buttonConfiguration.action + ' was not defined when registering button ' + buttonConfiguration.id, 'error');
190 }
191 }
192 return false;
193 },
194 /**
195 * Registers a drop-down list for inclusion in the toolbar
196 *
197 * @param object dropDownConfiguration: the configuration object of the drop-down:
198 * id : unique id for the drop-down
199 * tooltip : tooltip for the drop-down
200 * action : name of function to invoke when an option is selected
201 * textMode : enable in text mode
202 *
203 * @return boolean true if the drop-down list was successfully registered
204 */
205 registerDropDown: function (dropDownConfiguration) {
206 if (this.isButtonInToolbar(dropDownConfiguration.id)) {
207 if (Ext.isString(dropDownConfiguration.action) && Ext.isFunction(this[dropDownConfiguration.action])) {
208 dropDownConfiguration.plugins = this;
209 dropDownConfiguration.hidden = dropDownConfiguration.hide;
210 dropDownConfiguration.xtype = 'htmlareacombo';
211 // Apply additional ExtJS config properties set in Page TSConfig
212 // May not always work for values that must be integers
213 if (this.editorConfiguration.buttons[this.editorConfiguration.convertButtonId[dropDownConfiguration.id]]) {
214 Ext.applyIf(dropDownConfiguration, this.editorConfiguration.buttons[this.editorConfiguration.convertButtonId[dropDownConfiguration.id]]);
215 }
216 return this.editorConfiguration.registerButton(dropDownConfiguration);
217 } else {
218 this.appendToLog('registerDropDown', 'Function ' + dropDownConfiguration.action + ' was not defined when registering drop-down ' + dropDownConfiguration.id, 'error');
219 }
220 }
221 return false;
222 },
223 /**
224 * Registers a text element for inclusion in the toolbar
225 *
226 * @param object textConfiguration: the configuration object of the text element:
227 * id : unique id for the text item
228 * text : the text litteral
229 * tooltip : tooltip for the text item
230 * cls : a css class to be assigned to the text element
231 *
232 * @return boolean true if the drop-down list was successfully registered
233 */
234 registerText: function (textConfiguration) {
235 if (this.isButtonInToolbar(textConfiguration.id)) {
236 textConfiguration.plugins = this;
237 textConfiguration.xtype = 'htmlareatoolbartext';
238 return this.editorConfiguration.registerButton(textConfiguration);
239 }
240 return false;
241 },
242
243 /**
244 * Returns the drop-down configuration
245 *
246 * @param string dropDownId: the unique id of the drop-down
247 *
248 * @return object the drop-down configuration object
249 */
250 getDropDownConfiguration : function(dropDownId) {
251 return this.editorConfiguration.buttonsConfig[dropDownId];
252 },
253
254 /**
255 * Registors a hotkey
256 *
257 * @param object hotKeyConfiguration: the configuration object of the hotkey:
258 * id : the key
259 * cmd : name of the button corresponding to the hot key
260 * element : value of the record to be selected in the dropDown item
261 *
262 * @return boolean true if the hotkey was successfully registered
263 */
264 registerHotKey : function (hotKeyConfiguration) {
265 return this.editorConfiguration.registerHotKey(hotKeyConfiguration);
266 },
267
268 /**
269 * Returns the buttonId corresponding to the hotkey, if any
270 *
271 * @param string key: the hotkey
272 *
273 * @return string the buttonId or ""
274 */
275 translateHotKey : function(key) {
276 if (typeof this.editorConfiguration.hotKeyList[key] !== 'undefined') {
277 var buttonId = this.editorConfiguration.hotKeyList[key].cmd;
278 if (typeof buttonId !== 'undefined') {
279 return buttonId;
280 } else {
281 return "";
282 }
283 }
284 return "";
285 },
286
287 /**
288 * Returns the hotkey configuration
289 *
290 * @param string key: the hotkey
291 *
292 * @return object the hotkey configuration object
293 */
294 getHotKeyConfiguration: function(key) {
295 if (typeof this.editorConfiguration.hotKeyList[key] !== 'undefined') {
296 return this.editorConfiguration.hotKeyList[key];
297 } else {
298 return null;
299 }
300 },
301 /**
302 * Initializes the plugin
303 * Is invoked when the toolbar component is created (subclass of Ext.ux.HTMLAreaButton or Ext.ux.form.HTMLAreaCombo)
304 *
305 * @param object button: the component
306 *
307 * @return void
308 */
309 init: Ext.emptyFn,
310 /**
311 * The toolbar refresh handler of the plugin
312 * This function may be defined by the plugin subclass.
313 * If defined, the function will be invoked whenever the toolbar state is refreshed.
314 *
315 * @return boolean
316 */
317 onUpdateToolbar: Ext.emptyFn,
318 /**
319 * The onMode event handler
320 * This function may be redefined by the plugin subclass.
321 * The function is invoked whenever the editor changes mode.
322 *
323 * @param string mode: "wysiwyg" or "textmode"
324 *
325 * @return boolean
326 */
327 onMode: function(mode) {
328 if (mode === "textmode" && this.dialog && !(this.dialog.buttonId && this.editorConfiguration.buttons[this.dialog.buttonId] && this.editorConfiguration.buttons[this.dialog.buttonId].textMode)) {
329 this.dialog.close();
330 }
331 },
332 /**
333 * The onGenerate event handler
334 * This function may be defined by the plugin subclass.
335 * The function is invoked when the editor is initialized
336 *
337 * @return boolean
338 */
339 onGenerate: Ext.emptyFn,
340 /**
341 * Localize a string
342 *
343 * @param string label: the name of the label to localize
344 *
345 * @return string the localization of the label
346 */
347 localize: function (label, plural) {
348 var i = plural || 0;
349 var localized = this.I18N[label];
350 if (typeof localized === 'object' && typeof localized[i] !== 'undefined') {
351 localized = localized[i]['target'];
352 } else {
353 localized = HTMLArea.localize(label, plural);
354 }
355 return localized;
356 },
357 /**
358 * Get localized label wrapped with contextual help markup when available
359 *
360 * @param string fieldName: the name of the field in the CSH file
361 * @param string label: the name of the label to localize
362 * @param string pluginName: overrides this.name
363 *
364 * @return string localized label with CSH markup
365 */
366 getHelpTip: function (fieldName, label, pluginName) {
367 if (typeof TYPO3.ContextHelp !== 'undefined') {
368 var pluginName = typeof pluginName !== 'undefined' ? pluginName : this.name;
369 if (!Ext.isEmpty(fieldName)) {
370 fieldName = fieldName.replace(/-|\s/gi, '_');
371 }
372 return '<span class="t3-help-link" href="#" data-table="xEXT_rtehtmlarea_' + pluginName + '" data-field="' + fieldName + '"><abbr class="t3-help-teaser">' + (this.localize(label) || label) + '</abbr></span>';
373 } else {
374 return this.localize(label) || label;
375 }
376 },
377 /**
378 * Load a Javascript file asynchronously
379 *
380 * @param string url: url of the file to load
381 * @param function callBack: the callBack function
382 *
383 * @return boolean true on success of the request submission
384 */
385 getJavascriptFile: function (url, callback) {
386 return this.editor.ajax.getJavascriptFile(url, callback, this);
387 },
388 /**
389 * Post data to the server
390 *
391 * @param string url: url to post data to
392 * @param object data: data to be posted
393 * @param function callback: function that will handle the response returned by the server
394 *
395 * @return boolean true on success
396 */
397 postData: function (url, data, callback) {
398 return this.editor.ajax.postData(url, data, callback, this);
399 },
400 /*
401 * Open a window with container iframe
402 *
403 * @param string buttonId: the id of the button
404 * @param string title: the window title (will be localized here)
405 * @param object dimensions: the opening dimensions od the window
406 * @param string url: the url to load ino the iframe
407 *
408 * @ return void
409 */
410 openContainerWindow: function (buttonId, title, dimensions, url) {
411 this.dialog = new Ext.Window({
412 id: this.editor.editorId + buttonId,
413 title: this.localize(title) || title,
414 cls: 'htmlarea-window',
415 width: dimensions.width,
416 border: false,
417 iconCls: this.getButton(buttonId).iconCls,
418 listeners: {
419 afterrender: {
420 fn: this.onContainerResize
421 },
422 resize: {
423 fn: this.onContainerResize
424 },
425 close: {
426 fn: this.onClose,
427 scope: this
428 }
429 },
430 items: {
431 // The content iframe
432 xtype: 'box',
433 height: dimensions.height-20,
434 itemId: 'content-iframe',
435 autoEl: {
436 tag: 'iframe',
437 cls: 'content-iframe',
438 src: url
439 }
440 },
441 maximizable: true
442 });
443 this.show();
444 },
445 /*
446 * Handler invoked when the container window is rendered or resized in order to resize the content iframe to maximum size
447 */
448 onContainerResize: function (panel) {
449 var iframe = panel.getComponent('content-iframe');
450 if (iframe.rendered) {
451 iframe.getEl().setSize(panel.getInnerWidth(), panel.getInnerHeight());
452 }
453 },
454 /*
455 * Get the opening diment=sions of the window
456 *
457 * @param object dimensions: default opening width and height set by the plugin
458 * @param string buttonId: the id of the button that is triggering the opening of the window
459 *
460 * @return object opening width and height of the window
461 */
462 getWindowDimensions: function (dimensions, buttonId) {
463 // Apply default dimensions
464 this.dialogueWindowDimensions = {
465 width: 250,
466 height: 250
467 };
468 // Apply default values as per PageTSConfig
469 if (this.editorConfiguration.dialogueWindows) {
470 Ext.apply(this.dialogueWindowDimensions, this.editorConfiguration.dialogueWindows);
471 }
472 // Apply dimensions as per button registration
473 if (this.editorConfiguration.buttonsConfig[buttonId]) {
474 Ext.apply(this.dialogueWindowDimensions, this.editorConfiguration.buttonsConfig[buttonId].dimensions);
475 }
476 // Apply dimensions as per call
477 Ext.apply(this.dialogueWindowDimensions, dimensions);
478 // Overrride dimensions as per PageTSConfig
479 var buttonConfiguration = this.editorConfiguration.buttons[this.editorConfiguration.convertButtonId[buttonId]];
480 if (buttonConfiguration && buttonConfiguration.dialogueWindow) {
481 Ext.apply(this.dialogueWindowDimensions, buttonConfiguration.dialogueWindow);
482 }
483 return this.dialogueWindowDimensions;
484 },
485 /**
486 * Make url from module path
487 *
488 * @param string modulePath: module path
489 * @param string parameters: additional parameters
490 *
491 * @return string the url
492 */
493 makeUrlFromModulePath: function(modulePath, parameters) {
494 return modulePath + (modulePath.indexOf("?") === -1 ? "?" : "&") + this.editorConfiguration.RTEtsConfigParams + '&editorNo=' + this.editor.editorId + '&sys_language_content=' + this.editorConfiguration.sys_language_content + '&contentTypo3Language=' + this.editorConfiguration.typo3ContentLanguage + (parameters?parameters:'');
495 },
496 /**
497 * Append an entry at the end of the troubleshooting log
498 *
499 * @param string functionName: the name of the plugin function writing to the log
500 * @param string text: the text of the message
501 * @param string type: the typeof of message: 'log', 'info', 'warn' or 'error'
502 *
503 * @return void
504 */
505 appendToLog: function (functionName, text, type) {
506 this.editor.appendToLog(this.name, functionName, text, type);
507 },
508 /*
509 * Add a config element to config array if not empty
510 *
511 * @param object configElement: the config element
512 * @param array configArray: the config array
513 *
514 * @return void
515 */
516 addConfigElement: function (configElement, configArray) {
517 if (!Ext.isEmpty(configElement)) {
518 configArray.push(configElement);
519 }
520 },
521 /*
522 * Handler for Ext.TabPanel tabchange event
523 * Force window ghost height synchronization
524 * Working around ExtJS 3.1 bug
525 */
526 syncHeight: function (tabPanel, tab) {
527 var position = this.dialog.getPosition();
528 if (position[0] > 0) {
529 this.dialog.setPosition(position);
530 }
531 },
532 /*
533 * Show the dialogue window
534 */
535 show: function () {
536 // Close the window if the editor changes mode
537 this.dialog.mon(this.editor, 'HTMLAreaEventModeChange', this.close, this, {single: true });
538 this.saveSelection();
539 if (typeof this.dialogueWindowDimensions !== 'undefined') {
540 this.dialog.setPosition(this.dialogueWindowDimensions.positionFromLeft, this.dialogueWindowDimensions.positionFromTop);
541 }
542 this.dialog.show();
543 this.restoreSelection();
544 },
545 /*
546 * Close the dialogue window (after saving the selection, if IE)
547 */
548 close: function () {
549 this.saveSelection();
550 this.dialog.close();
551 },
552 /*
553 * Dialogue window onClose handler
554 */
555 onClose: function () {
556 this.editor.focus();
557 this.restoreSelection();
558 this.editor.updateToolbar();
559 },
560 /*
561 * Handler for window cancel
562 */
563 onCancel: function () {
564 this.dialog.close();
565 this.editor.focus();
566 },
567 /*
568 * Save selection
569 * Should be called after processing button other than Cancel
570 */
571 saveSelection: function () {
572 // If IE, save the current selection
573 if (Ext.isIE) {
574 this.savedRange = this.editor.getSelection().createRange();
575 }
576 },
577 /*
578 * Restore selection
579 * Should be called before processing dialogue button or result
580 */
581 restoreSelection: function () {
582 // If IE, restore the selection saved when the window was shown
583 if (Ext.isIE && this.savedRange) {
584 // Restoring the selection will not work if the inner html was replaced by the plugin
585 try {
586 this.editor.getSelection().selectRange(this.savedRange);
587 } catch (e) {}
588 }
589 },
590 /*
591 * Build the configuration object of a button
592 *
593 * @param string button: the text of the button
594 * @param function handler: button handler
595 *
596 * @return object the button configuration object
597 */
598 buildButtonConfig: function (button, handler) {
599 return {
600 xtype: 'button',
601 text: this.localize(button),
602 listeners: {
603 click: {
604 fn: handler,
605 scope: this
606 }
607 }
608 };
609 }
610 });