d93a7f7f115ed2f48f062bfcf931cd6e89c0320d
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / Resources / Public / JavaScript / Plugins / Abbreviation.js
1 /*
2 * This file is part of the TYPO3 CMS project.
3 *
4 * It is free software; you can redistribute it and/or modify it under
5 * the terms of the GNU General Public License, either version 2
6 * of the License, or any later version.
7 *
8 * For the full copyright and license information, please read the
9 * LICENSE.txt file that was distributed with this source code.
10 *
11 * The TYPO3 project - inspiring people to share!
12 */
13
14 /**
15 * Abbreviation plugin for htmlArea RTE
16 */
17 define(['TYPO3/CMS/Rtehtmlarea/HTMLArea/Plugin/Plugin',
18 'TYPO3/CMS/Rtehtmlarea/HTMLArea/Util/Util'],
19 function (Plugin, Util) {
20
21 var Abbreviation = function (editor, pluginName) {
22 this.constructor.super.call(this, editor, pluginName);
23 };
24 Util.inherit(Abbreviation, Plugin);
25 Util.apply(Abbreviation.prototype, {
26
27 /**
28 * This function gets called by the class constructor
29 */
30 configurePlugin: function(editor) {
31 this.pageTSConfiguration = this.editorConfiguration.buttons.abbreviation;
32 var removeFieldsets = (this.pageTSConfiguration && this.pageTSConfiguration.removeFieldsets) ? this.pageTSConfiguration.removeFieldsets : '';
33 removeFieldsets = removeFieldsets.split(',');
34 var fieldsets = ['abbreviation', 'definedAbbreviation', 'acronym' ,'definedAcronym'];
35 this.enabledFieldsets = {};
36 for (var i = fieldsets.length; --i >= 0;) {
37 this.enabledFieldsets[fieldsets[i]] = removeFieldsets.indexOf(fieldsets[i]) === -1;
38 }
39 /**
40 * Registering plugin "About" information
41 */
42 var pluginInformation = {
43 version : '7.0',
44 developer : 'Stanislas Rolland',
45 developerUrl : 'http://www.sjbr.ca/',
46 copyrightOwner : 'Stanislas Rolland',
47 sponsor : 'SJBR',
48 sponsorUrl : 'http://www.sjbr.ca/',
49 license : 'GPL'
50 };
51 this.registerPluginInformation(pluginInformation);
52 /**
53 * Registering the button
54 */
55 var buttonId = 'Abbreviation';
56 var buttonConfiguration = {
57 id : buttonId,
58 tooltip : this.localize('Insert abbreviation'),
59 action : 'onButtonPress',
60 dialog : true,
61 iconCls : 'htmlarea-action-abbreviation-edit',
62 contextMenuTitle: this.localize(buttonId + '-contextMenuTitle')
63 };
64 this.registerButton(buttonConfiguration);
65 return true;
66 },
67
68 /**
69 * Sets of default configuration values for dialogue form fields
70 */
71 configDefaults: {
72 combo: {
73 editable: true,
74 selectOnFocus: true,
75 typeAhead: true,
76 triggerAction: 'all',
77 forceSelection: true,
78 mode: 'local'
79 }
80 },
81
82 /**
83 * This function gets called when the button was pressed
84 *
85 * @param object editor: the editor instance
86 * @param string id: the button id or the key
87 * @return boolean false if action is completed
88 */
89 onButtonPress: function(editor, id) {
90 // Could be a button or its hotkey
91 var buttonId = this.translateHotKey(id);
92 buttonId = buttonId ? buttonId : id;
93 var abbr = this.getCurrentAbbrElement();
94 var type = typeof abbr === 'object' && abbr !== null ? abbr.nodeName.toLowerCase() : '';
95 this.params = {
96 abbr: abbr,
97 title: typeof abbr === 'object' && abbr !== null ? abbr.title : '',
98 text: typeof abbr === 'object' && abbr !== null ? abbr.innerHTML : this.editor.getSelection().getHtml()
99 };
100 // Open the dialogue window
101 this.openDialogue(
102 this.getButton(buttonId).tooltip,
103 buttonId,
104 this.getWindowDimensions({ width: 580}, buttonId),
105 this.buildTabItemsConfig(abbr),
106 this.buildButtonsConfig(abbr, this.okHandler, this.deleteHandler),
107 type
108 );
109 return false;
110 },
111
112 /**
113 * Get the current abbr or aconym element, if any is selected
114 *
115 * @return object the element or null
116 */
117 getCurrentAbbrElement: function() {
118 var abbr = this.editor.getSelection().getParentElement();
119 // Working around Safari issue
120 if (!abbr && this.editor.statusBar && this.editor.statusBar.getSelection()) {
121 abbr = this.editor.statusBar.getSelection();
122 }
123 if (!abbr || !/^(acronym|abbr)$/i.test(abbr.nodeName)) {
124 abbr = this.editor.getSelection().getFirstAncestorOfType(['abbr', 'acronym']);
125 }
126 return abbr;
127 },
128
129 /**
130 * Open the dialogue window
131 *
132 * @param string title: the window title
133 * @param string buttonId: the itemId of the button that was pressed
134 * @param integer dimensions: the opening width of the window
135 * @param object tabItems: the configuration of the tabbed panel
136 * @param object buttonsConfig: the configuration of the buttons
137 * @param string activeTab: itemId of the opening tab
138 * @return void
139 */
140 openDialogue: function (title, buttonId, dimensions, tabItems, buttonsConfig, activeTab) {
141 this.dialog = new Ext.Window({
142 title: this.getHelpTip('', title),
143 cls: 'htmlarea-window',
144 border: false,
145 width: dimensions.width,
146 height: 'auto',
147 iconCls: this.getButton(buttonId).iconCls,
148 listeners: {
149 close: {
150 fn: this.onClose,
151 scope: this
152 }
153 },
154 items: {
155 xtype: 'tabpanel',
156 activeTab: activeTab ? activeTab : 0,
157 defaults: {
158 xtype: 'container',
159 layout: 'form',
160 defaults: {
161 labelWidth: 150
162 }
163 },
164 listeners: {
165 tabchange: {
166 fn: function (tabpanel, tab) {
167 this.setTabPanelHeight(tabpanel, tab);
168 this.syncHeight(tabpanel, tab);
169 },
170 scope: this
171 }
172 },
173 items: tabItems
174 },
175 buttons: buttonsConfig
176 });
177 this.show();
178 },
179
180 /**
181 * Build the dialogue tab items config
182 *
183 * @param object element: the element being edited, if any
184 * @return object the tab items configuration
185 */
186 buildTabItemsConfig: function (element) {
187 var type = typeof element === 'object' && element !== null ? element.nodeName.toLowerCase() : '';
188 var tabItems = [];
189 var abbrTabItems = [];
190 // abbreviation tab not shown if the current selection is an acronym
191 if (type !== 'acronym') {
192 // definedAbbreviation fieldset not shown if no pre-defined abbreviation exists
193 if (!this.pageTSConfiguration.noAbbr && this.enabledFieldsets['definedAbbreviation']) {
194 this.addConfigElement(this.buildDefinedTermFieldsetConfig((type === 'abbr') ? element : null, 'abbr'), abbrTabItems);
195 }
196 // abbreviation fieldset not shown if the selection is empty or not inside an abbr element
197 if ((!this.editor.getSelection().isEmpty() || type === 'abbr') && this.enabledFieldsets['abbreviation']) {
198 this.addConfigElement(this.buildUseTermFieldsetConfig((type === 'abbr') ? element : null, 'abbr'), abbrTabItems);
199 }
200 }
201 if (abbrTabItems.length > 0) {
202 tabItems.push({
203 title: this.localize('Abbreviation'),
204 itemId: 'abbr',
205 items: abbrTabItems
206 });
207 }
208 var acronymTabItems = [];
209 // acronym tab not shown if the current selection is an abbreviation
210 if (type !== 'abbr') {
211 // definedAcronym fieldset not shown if no pre-defined acronym exists
212 if (!this.pageTSConfiguration.noAcronym && this.enabledFieldsets['definedAcronym']) {
213 this.addConfigElement(this.buildDefinedTermFieldsetConfig((type === 'acronym') ? element : null, 'acronym'), acronymTabItems);
214 }
215 // acronym fieldset not shown if the selection is empty or not inside an acronym element
216 if ((!this.editor.getSelection().isEmpty() || type === 'acronym') && this.enabledFieldsets['acronym']) {
217 this.addConfigElement(this.buildUseTermFieldsetConfig((type === 'acronym') ? element : null, 'acronym'), acronymTabItems);
218 }
219 }
220 if (acronymTabItems.length > 0) {
221 tabItems.push({
222 title: this.localize('Acronym'),
223 itemId: 'acronym',
224 items: acronymTabItems
225 });
226 }
227 return tabItems;
228 },
229
230 /**
231 * Build the dialogue buttons config
232 *
233 * @param object element: the element being edited, if any
234 * @param function okHandler: the handler for the ok button
235 * @param function deleteHandler: the handler for the delete button
236 *
237 * @return object the buttons configuration
238 */
239 buildButtonsConfig: function (element, okHandler, deleteHandler) {
240 var buttonsConfig = [this.buildButtonConfig('OK', okHandler)];
241 if (element) {
242 buttonsConfig.push(this.buildButtonConfig('Delete', deleteHandler));
243 }
244 buttonsConfig.push(this.buildButtonConfig('Cancel', this.onCancel));
245 return buttonsConfig;
246 },
247
248 /**
249 * This function builds the configuration object for the defined Abbreviation or Acronym fieldset
250 *
251 * @param object element: the element being edited, if any
252 * @param string type: 'abbr' or 'acronym'
253 *
254 * @return object the fieldset configuration object
255 */
256 buildDefinedTermFieldsetConfig: function (element, type) {
257 var itemsConfig = [];
258 itemsConfig.push(Util.apply({
259 xtype: 'combo',
260 displayField: 'term',
261 valueField: 'term',
262 fieldLabel: this.getHelpTip('unabridgedTerm', 'Unabridged_term'),
263 itemId: 'termSelector',
264 tpl: '<tpl for="."><div ext:qtip="{abbr}" style="text-align:left;font-size:11px;" class="x-combo-list-item">{term}</div></tpl>',
265 store: new Ext.data.JsonStore({
266 autoDestroy: true,
267 autoLoad: false,
268 root: type,
269 fields: [ { name: 'term'}, { name: 'abbr'}, { name: 'language'}],
270 url: this.pageTSConfiguration.abbreviationUrl
271 }),
272 width: 350,
273 listeners: {
274 afterrender: {
275 fn: function (combo) {
276 // Ensure the store is loaded
277 combo.getStore().load({
278 callback: function () { this.onSelectorRender(combo); },
279 scope: this
280 });
281 },
282 scope: this
283 },
284 select: {
285 fn: this.onTermSelect,
286 scope: this
287 }
288 }
289 }, this.configDefaults['combo']));
290 itemsConfig.push(Util.apply({
291 xtype: 'combo',
292 displayField: 'abbr',
293 valueField: 'abbr',
294 tpl: '<tpl for="."><div ext:qtip="{language}" style="text-align:left;font-size:11px;" class="x-combo-list-item">{abbr}</div></tpl>',
295 fieldLabel: this.getHelpTip('abridgedTerm', 'Abridged_term'),
296 itemId: 'abbrSelector',
297 store: new Ext.data.JsonStore({
298 autoDestroy: true,
299 autoLoad: false,
300 root: type,
301 fields: [ { name: 'term'}, { name: 'abbr'}, { name: 'language'}],
302 url: this.pageTSConfiguration.abbreviationUrl
303 }),
304 width: 100,
305 listeners: {
306 afterrender: {
307 fn: function (combo) {
308 // Ensure the store is loaded
309 combo.getStore().load({
310 callback: function () { this.onSelectorRender(combo); },
311 scope: this
312 });
313 },
314 scope: this
315 },
316 select: {
317 fn: this.onAbbrSelect,
318 scope: this
319 }
320 }
321 }, this.configDefaults['combo']));
322 var languageObject = this.getPluginInstance('Language');
323 if (this.getButton('Language')) {
324 var selectedLanguage = typeof element === 'object' && element !== null ? languageObject.getLanguageAttribute(element) : 'none';
325 itemsConfig.push(Util.apply({
326 xtype: 'combo',
327 fieldLabel: this.getHelpTip('language', 'Language'),
328 itemId: 'language',
329 valueField: 'value',
330 displayField: 'text',
331 tpl: '<tpl for="."><div ext:qtip="{value}" style="text-align:left;font-size:11px;" class="x-combo-list-item">{text}</div></tpl>',
332 store: new Ext.data.JsonStore({
333 autoDestroy: true,
334 root: 'options',
335 fields: [ { name: 'text'}, { name: 'value'} ],
336 url: this.getDropDownConfiguration('Language').dataUrl,
337 listeners: {
338 load: {
339 fn: function (store) {
340 if (selectedLanguage !== 'none') {
341 store.removeAt(0);
342 store.insert(0, new store.recordType({
343 text: languageObject.localize('Remove language mark'),
344 value: 'none'
345 }));
346 }
347 }
348 }
349 }
350 }),
351 width: 200,
352 value: selectedLanguage,
353 listeners: {
354 beforerender: {
355 fn: function (combo) {
356 // Ensure the store is loaded
357 combo.getStore().load({
358 callback: function () { combo.setValue(selectedLanguage); }
359 });
360 }
361 }
362 }
363 }, this.configDefaults['combo']));
364 }
365 return {
366 xtype: 'fieldset',
367 title: this.getHelpTip('preDefined' + ((type == 'abbr') ? 'Abbreviation' : 'Acronym'), 'Defined_' + type),
368 items: itemsConfig,
369 listeners: {
370 render: {
371 fn: this.onDefinedTermFieldsetRender,
372 scope: this
373 }
374 }
375 };
376 },
377
378 /**
379 * Handler on rendering the defined abbreviation fieldset
380 * If an abbr is selected but no term is selected, select any corresponding term with the correct language value, if any
381 */
382 onDefinedTermFieldsetRender: function (fieldset) {
383 var termSelector = fieldset.find('itemId', 'termSelector')[0];
384 var term = termSelector.getValue();
385 var abbrSelector = fieldset.find('itemId', 'abbrSelector')[0];
386 var abbr = abbrSelector.getValue();
387 var language = '';
388 var languageSelector = fieldset.find('itemId', 'language')[0];
389 if (languageSelector) {
390 var language = languageSelector.getValue();
391 if (language == 'none') {
392 language = '';
393 }
394 }
395 if (abbr && !term) {
396 var abbrStore = abbrSelector.getStore();
397 var index = abbrStore.findBy(function (record) {
398 return record.get('abbr') == abbr && (!languageSelector || record.get('language') == language);
399 }, this);
400 if (index !== -1) {
401 term = abbrStore.getAt(index).get('term');
402 termSelector.setValue(term);
403 var useTermField = fieldset.ownerCt.find('itemId', 'useTerm');
404 if (useTermField.length) {
405 useTermField[0].setValue(term);
406 }
407 }
408 }
409 },
410
411 /**
412 * Filter the term and abbr selector lists
413 * Set initial values
414 * If there is already an abbr and the filtered list has only one or no element, hide the fieldset
415 */
416 onSelectorRender: function (combo) {
417 var store = combo.getStore();
418 store.filterBy(function (record) {
419 return !this.params.text || !this.params.title || this.params.text == record.get('term') || this.params.title == record.get('term') || this.params.title == record.get('abbr');
420 }, this);
421 // Make sure the combo list is filtered
422 store.snapshot = store.data;
423 var store = combo.getStore();
424 // Initialize the term and abbr combos
425 if (combo.getItemId() == 'termSelector') {
426 if (this.params.title) {
427 var index = store.findExact('term', this.params.title);
428 if (index !== -1) {
429 var record = store.getAt(index);
430 combo.setValue(record.get('term'));
431 this.onTermSelect(combo, record, index);
432 }
433 } else if (this.params.text) {
434 var index = store.findExact('term', this.params.text);
435 if (index !== -1) {
436 var record = store.getAt(index);
437 combo.setValue(record.get('term'));
438 this.onTermSelect(combo, record, index);
439 }
440 }
441 } else if (combo.getItemId() == 'abbrSelector' && this.params.text) {
442 var index = store.findExact('abbr', this.params.text);
443 if (index !== -1) {
444 var record = store.getAt(index);
445 combo.setValue(record.get('abbr'));
446 this.onAbbrSelect(combo, record, index);
447 }
448 }
449 },
450
451 /**
452 * Handler when a term is selected
453 */
454 onTermSelect: function (combo, record, index) {
455 var fieldset = combo.findParentByType('fieldset');
456 var tab = fieldset.findParentByType('container');
457 var term = record.get('term');
458 var abbr = record.get('abbr');
459 var language = record.get('language');
460 // Update the abbreviation selector
461 var abbrSelector = tab.find('itemId', 'abbrSelector')[0];
462 abbrSelector.setValue(abbr);
463 // Update the language selector
464 var languageSelector = tab.find('itemId', 'language');
465 if (languageSelector.length > 0) {
466 if (language) {
467 languageSelector[0].setValue(language);
468 } else {
469 languageSelector[0].setValue('none');
470 }
471 }
472 // Update the term to use
473 var useTermField = tab.find('itemId', 'useTerm');
474 if (useTermField.length) {
475 useTermField[0].setValue(term);
476 }
477 },
478
479 /**
480 * Handler when an abbreviation or acronym is selected
481 */
482 onAbbrSelect: function (combo, record, index) {
483 var fieldset = combo.findParentByType('fieldset');
484 var tab = fieldset.findParentByType('container');
485 var term = record.get('term');
486 var language = record.get('language');
487 // Update the term selector
488 var termSelector = tab.find('itemId', 'termSelector')[0];
489 termSelector.setValue(term);
490 // Update the language selector
491 var languageSelector = tab.find('itemId', 'language');
492 if (languageSelector.length > 0) {
493 if (language) {
494 languageSelector[0].setValue(language);
495 } else {
496 languageSelector[0].setValue('none');
497 }
498 }
499 // Update the term to use
500 var useTermField = tab.find('itemId', 'useTerm');
501 if (useTermField.length) {
502 useTermField[0].setValue(term);
503 }
504 },
505
506 /**
507 * This function builds the configuration object for the Abbreviation or Acronym to use fieldset
508 *
509 * @param object element: the element being edited, if any
510 *
511 * @return object the fieldset configuration object
512 */
513 buildUseTermFieldsetConfig: function (element, type) {
514 var itemsConfig = [];
515 itemsConfig.push({
516 fieldLabel: this.getHelpTip('useThisTerm', 'Use_this_term'),
517 labelSeparator: '',
518 itemId: 'useTerm',
519 value: element ? element.title : '',
520 width: 300
521 });
522 return {
523 xtype: 'fieldset',
524 title: this.getHelpTip('termToAbridge', 'Term_to_abridge'),
525 defaultType: 'textfield',
526 items: itemsConfig
527 };
528 },
529
530 /**
531 * Handler when the ok button is pressed
532 */
533 okHandler: function (button, event) {
534 this.restoreSelection();
535 var tab = this.dialog.findByType('tabpanel')[0].getActiveTab();
536 var type = tab.getItemId();
537 var languageSelector = tab.find('itemId', 'language');
538 var language = languageSelector && languageSelector.length > 0 ? languageSelector[0].getValue() : '';
539 var termSelector = tab.find('itemId', 'termSelector');
540 var term = termSelector && termSelector.length > 0 ? termSelector[0].getValue() : '';
541 var abbrSelector = tab.find('itemId', 'abbrSelector');
542 var useTermField = tab.find('itemId', 'useTerm');
543 if (!this.params.abbr) {
544 var abbr = this.editor.document.createElement(type);
545 if (useTermField.length) {
546 abbr.title = useTermField[0].getValue();
547 } else {
548 abbr.title = term;
549 }
550 if (term === abbr.title && abbrSelector && abbrSelector.length > 0) {
551 abbr.innerHTML = abbrSelector[0].getValue();
552 } else {
553 abbr.innerHTML = this.params.text;
554 }
555 if (language) {
556 this.getPluginInstance('Language').setLanguageAttributes(abbr, language);
557 }
558 this.editor.getSelection().insertNode(abbr);
559 // Position the cursor just after the inserted abbreviation
560 abbr = this.editor.getSelection().getParentElement();
561 if (abbr.nextSibling) {
562 this.editor.getSelection().selectNodeContents(abbr.nextSibling, true);
563 } else {
564 this.editor.getSelection().selectNodeContents(abbr.parentNode, false);
565 }
566 } else {
567 var abbr = this.params.abbr;
568 if (useTermField.length) {
569 abbr.title = useTermField[0].getValue();
570 } else {
571 abbr.title = term;
572 }
573 if (language) {
574 this.getPluginInstance('Language').setLanguageAttributes(abbr, language);
575 }
576 if (term === abbr.title && abbrSelector && abbrSelector.length > 0) {
577 abbr.innerHTML = abbrSelector[0].getValue();
578 }
579 }
580 this.close();
581 event.stopEvent();
582 },
583
584 /**
585 * Handler when the delete button is pressed
586 */
587 deleteHandler: function (button, event) {
588 this.restoreSelection();
589 var abbr = this.params.abbr;
590 if (abbr) {
591 this.editor.getDomNode().removeMarkup(abbr);
592 }
593 this.close();
594 event.stopEvent();
595 },
596
597 /**
598 * This function gets called when the toolbar is updated
599 */
600 onUpdateToolbar: function (button, mode, selectionEmpty, ancestors) {
601 if ((mode === 'wysiwyg') && this.editor.isEditable()) {
602 var el = this.getCurrentAbbrElement();
603 var nodeName = typeof el === 'object' && el !== null ? el.nodeName.toLowerCase() : '';
604 // Disable the button if the selection and not inside a abbr or acronym element
605 button.setDisabled(
606 (this.editor.getSelection().isEmpty() && nodeName !== 'abbr' && nodeName !== 'acronym')
607 && (this.pageTSConfiguration.noAbbr || !this.enabledFieldsets['definedAbbreviation'])
608 && (this.pageTSConfiguration.noAcronym || !this.enabledFieldsets['definedAcronym'])
609 );
610 button.setInactive(
611 !(nodeName === 'abbr' && (this.enabledFieldsets['definedAbbreviation'] || this.enabledFieldsets['abbreviation']))
612 && !(nodeName === 'acronym' && (this.enabledFieldsets['definedAcronym'] || this.enabledFieldsets['acronym']))
613 );
614 button.setTooltip(this.localize((button.disabled || button.inactive) ? 'Insert abbreviation' : 'Edit abbreviation'));
615 button.contextMenuTitle = '';
616 if (this.dialog) {
617 this.dialog.focus();
618 }
619 }
620 }
621 });
622
623 return Abbreviation;
624
625 });