a53e7e6d61655aa13a9c2732f2c75d49101ca60a
[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([
18 'TYPO3/CMS/Rtehtmlarea/HTMLArea/Plugin/Plugin',
19 'TYPO3/CMS/Rtehtmlarea/HTMLArea/Util/Util',
20 'jquery',
21 'TYPO3/CMS/Backend/Modal',
22 'TYPO3/CMS/Backend/Severity'
23 ], function (Plugin, Util, $, Modal, Severity) {
24
25 var Abbreviation = function (editor, pluginName) {
26 this.constructor.super.call(this, editor, pluginName);
27 };
28 Util.inherit(Abbreviation, Plugin);
29 Util.apply(Abbreviation.prototype, {
30
31 /**
32 * This function gets called by the class constructor
33 */
34 configurePlugin: function(editor) {
35 this.pageTSConfiguration = this.editorConfiguration.buttons.abbreviation;
36 var removeFieldsets = (this.pageTSConfiguration && this.pageTSConfiguration.removeFieldsets) ? this.pageTSConfiguration.removeFieldsets : '';
37 removeFieldsets = removeFieldsets.split(',');
38 var fieldsets = ['abbreviation', 'definedAbbreviation', 'acronym' ,'definedAcronym'];
39 this.enabledFieldsets = {};
40 for (var i = fieldsets.length; --i >= 0;) {
41 this.enabledFieldsets[fieldsets[i]] = removeFieldsets.indexOf(fieldsets[i]) === -1;
42 }
43 /**
44 * Registering plugin "About" information
45 */
46 var pluginInformation = {
47 version : '7.0',
48 developer : 'Stanislas Rolland',
49 developerUrl : 'http://www.sjbr.ca/',
50 copyrightOwner : 'Stanislas Rolland',
51 sponsor : 'SJBR',
52 sponsorUrl : 'http://www.sjbr.ca/',
53 license : 'GPL'
54 };
55 this.registerPluginInformation(pluginInformation);
56 /**
57 * Registering the button
58 */
59 var buttonId = 'Abbreviation';
60 var buttonConfiguration = {
61 id : buttonId,
62 tooltip : this.localize('Insert abbreviation'),
63 action : 'onButtonPress',
64 dialog : true,
65 iconCls : 'htmlarea-action-abbreviation-edit',
66 contextMenuTitle: this.localize(buttonId + '-contextMenuTitle')
67 };
68 this.registerButton(buttonConfiguration);
69 return true;
70 },
71
72 /**
73 * This function gets called when the button was pressed
74 *
75 * @param {Object} editor The editor instance
76 * @param {String} id The button id or the key
77 * @return {Boolean} False if action is completed
78 */
79 onButtonPress: function(editor, id) {
80 // Could be a button or its hotkey
81 var buttonId = this.translateHotKey(id);
82 buttonId = buttonId ? buttonId : id;
83 var abbr = this.getCurrentAbbrElement();
84 var type = typeof abbr === 'object' && abbr !== null ? abbr.nodeName.toLowerCase() : '';
85 this.params = {
86 abbr: abbr,
87 title: typeof abbr === 'object' && abbr !== null ? abbr.title : '',
88 text: typeof abbr === 'object' && abbr !== null ? abbr.innerHTML : this.editor.getSelection().getHtml()
89 };
90 // Open the dialogue window
91 this.openDialogue(
92 this.getButton(buttonId).tooltip,
93 buttonId,
94 this.buildTabItemsConfig(abbr),
95 [
96 this.buildButtonConfig(this.localize('Cancel'), $.proxy(this.onCancel, this), true),
97 this.buildButtonConfig(this.localize('Delete'), $.proxy(this.deleteHandler, this)),
98 this.buildButtonConfig(this.localize('OK'), $.proxy(this.okHandler, this), false, Severity.notice)
99 ],
100 type
101 );
102 return false;
103 },
104
105 /**
106 * Get the current abbr or aconym element, if any is selected
107 *
108 * @return {Object} The element or null
109 */
110 getCurrentAbbrElement: function() {
111 var abbr = this.editor.getSelection().getParentElement();
112 // Working around Safari issue
113 if (!abbr && this.editor.statusBar && this.editor.statusBar.getSelection()) {
114 abbr = this.editor.statusBar.getSelection();
115 }
116 if (!abbr || !/^(acronym|abbr)$/i.test(abbr.nodeName)) {
117 abbr = this.editor.getSelection().getFirstAncestorOfType(['abbr', 'acronym']);
118 }
119 return abbr;
120 },
121
122 /**
123 * Open the dialogue window
124 *
125 * @param {String} title: the window title
126 * @param {String} buttonId: the itemId of the button that was pressed
127 * @param {Object} tabItems: the configuration of the tabbed panel
128 * @param {Object} buttonsConfig: the configuration of the buttons
129 * @param {String} activeTab: itemId of the opening tab
130 */
131 openDialogue: function (title, buttonId, tabItems, buttonsConfig, activeTab) {
132 this.dialog = Modal.show(title, tabItems, Severity.notice, buttonsConfig);
133 this.dialog.on('modal-dismiss', $.proxy(this.onClose, this));
134 },
135
136 /**
137 * Build the dialogue tab items config
138 *
139 * @param {Object} element: the element being edited, if any
140 * @return {Object} the tab items configuration
141 */
142 buildTabItemsConfig: function (element) {
143 var type = typeof element === 'object' && element !== null ? element.nodeName.toLowerCase() : '',
144 abbrTabItems = [],
145 acronymTabItems = [],
146 $finalMarkup,
147 $tabs = $('<ul />', {'class': 'nav nav-tabs', role: 'tablist'}),
148 $tabContent = $('<div />', {'class': 'tab-content'});
149
150 // abbreviation tab not shown if the current selection is an acronym
151 if (type !== 'acronym') {
152 // definedAbbreviation fieldset not shown if no pre-defined abbreviation exists
153 if (!this.pageTSConfiguration.noAbbr && this.enabledFieldsets['definedAbbreviation']) {
154 this.addConfigElement(this.buildDefinedTermFieldsetConfig((type === 'abbr') ? element : null, 'abbr'), abbrTabItems);
155 }
156 // abbreviation fieldset not shown if the selection is empty or not inside an abbr element
157 if ((!this.editor.getSelection().isEmpty() || type === 'abbr') && this.enabledFieldsets['abbreviation']) {
158 this.addConfigElement(this.buildUseTermFieldsetConfig((type === 'abbr') ? element : null, 'abbr'), abbrTabItems);
159 }
160 }
161
162 if (abbrTabItems.length > 0) {
163 this.buildTabMarkup($tabs, $tabContent, 'abbr', abbrTabItems, this.localize('Abbreviation'));
164 }
165
166 // acronym tab not shown if the current selection is an abbreviation
167 if (type !== 'abbr') {
168 // definedAcronym fieldset not shown if no pre-defined acronym exists
169 if (!this.pageTSConfiguration.noAcronym && this.enabledFieldsets['definedAcronym']) {
170 this.addConfigElement(this.buildDefinedTermFieldsetConfig((type === 'acronym') ? element : null, 'acronym'), acronymTabItems);
171 }
172 // acronym fieldset not shown if the selection is empty or not inside an acronym element
173 if ((!this.editor.getSelection().isEmpty() || type === 'acronym') && this.enabledFieldsets['acronym']) {
174 this.addConfigElement(this.buildUseTermFieldsetConfig((type === 'acronym') ? element : null, 'acronym'), acronymTabItems);
175 }
176 }
177 if (acronymTabItems.length > 0) {
178 this.buildTabMarkup($tabs, $tabContent, 'acronym', acronymTabItems, this.localize('Acronym'));
179 }
180
181 $tabs.find('li:first').addClass('active');
182 $tabContent.find('.tab-pane:first').addClass('active');
183
184 $finalMarkup = $('<form />', {'class': 'form-horizontal'}).append($tabs, $tabContent);
185
186 return $finalMarkup;
187 },
188
189 /**
190 * This function builds the configuration object for the defined Abbreviation or Acronym fieldset
191 *
192 * @param {Object} element: the element being edited, if any
193 * @param {String} type: 'abbr' or 'acronym'
194 * @return {Object} the fieldset configuration object
195 */
196 buildDefinedTermFieldsetConfig: function (element, type) {
197 var self = this,
198 $fieldset = $('<fieldset />');
199
200 $fieldset.append(
201 $('<h4 />', {'class': 'form-section-headline'}).html(this.getHelpTip('preDefined' + ((type == 'abbr') ? 'Abbreviation' : 'Acronym'), 'Defined_' + type))
202 );
203
204 $fieldset.append(
205 $('<div />', {'class': 'form-group'}).append(
206 $('<label />', {'class': 'col-sm-2'}).html(this.getHelpTip('unabridgedTerm', 'Unabridged_term')),
207 $('<div />', {'class': 'col-sm-10'}).append(
208 $('<select />', {name: 'termSelector', 'class': 'form-control'})
209 .on('change', $.proxy(this.onTermSelect, this))
210 )
211 ),
212 $('<div />', {'class': 'form-group'}).append(
213 $('<label />', {'class': 'col-sm-2'}).html(this.getHelpTip('abridgedTerm', 'Abridged_term')),
214 $('<div />', {'class': 'col-sm-10'}).append(
215 $('<select />', {name: 'abbrSelector', 'class': 'form-control'})
216 .on('change', $.proxy(this.onAbbrSelect, this))
217 )
218 )
219 );
220
221 $.ajax({
222 url: this.pageTSConfiguration.abbreviationUrl,
223 dataType: 'json',
224 success: function (response) {
225 var $termSelector = $fieldset.find('select[name="termSelector"]'),
226 $abbrSelector = $fieldset.find('select[name="abbrSelector"]');
227
228 for (var item in response.type) {
229 if (response.type.hasOwnProperty(item)) {
230 var current = response.type[item],
231 attributeConfiguration = {
232 value: current.term,
233 'data-abbr': current.abbr,
234 'data-language': current.language
235 };
236 $termSelector.append(
237 $('<option />', attributeConfiguration).text(current.term)
238 );
239
240 attributeConfiguration = {
241 value: current.abbr,
242 'data-term': current.term,
243 'data-language': current.language
244 };
245 $abbrSelector.append(
246 $('<option />', attributeConfiguration).text(current.abbr)
247 );
248 }
249 }
250
251 self.onSelectorRender($termSelector);
252 self.onSelectorRender($abbrSelector);
253 }
254 });
255
256 var languageObject = this.getPluginInstance('Language');
257 if (this.getButton('Language')) {
258 var selectedLanguage = typeof element === 'object' && element !== null ? languageObject.getLanguageAttribute(element) : 'none';
259
260 $fieldset.append(
261 $('<div />', {'class': 'form-group'}).append(
262 $('<label />', {'class': 'col-sm-2'}).html(this.getHelpTip('language', 'Language')),
263 $('<div />', {'class': 'col-sm-10'}).append(
264 $('<select />', {name: 'language', 'class': 'form-control'})
265 )
266 )
267 );
268
269 $.ajax({
270 url: this.getDropDownConfiguration('Language').dataUrl,
271 dataType: 'json',
272 success: function (response) {
273 var $select = $fieldset.find('select[name="language"]');
274
275 for (var language in response.options) {
276 if (response.options.hasOwnProperty(language)) {
277 if (selectedLanguage !== 'none') {
278 response.options[language].value = 'none';
279 response.options[language].text = languageObject.localize('Remove language mark');
280 }
281 var attributeConfiguration = {value: response.options[language].value};
282 if (selectedLanguage === response.options[language].value) {
283 attributeConfiguration.selected = 'selected';
284 }
285 $select.append(
286 $('<option />', attributeConfiguration).text(response.options[language].text)
287 );
288 }
289 }
290 }
291 });
292 }
293
294 this.onDefinedTermFieldsetRender($fieldset);
295
296 return $fieldset;
297 },
298
299 /**
300 * Handler on rendering the defined abbreviation fieldset
301 * If an abbr is selected but no term is selected, select any corresponding term with the correct language value, if any
302 *
303 * @param {Object} fieldset
304 */
305 onDefinedTermFieldsetRender: function (fieldset) {
306 var termSelector = fieldset.find('[name="termSelector"]');
307 var term = termSelector.val();
308 var abbrSelector = fieldset.find('[name="abbrSelector"]');
309 var abbr = abbrSelector.val();
310 var language = '';
311 var languageSelector = fieldset.find('[name="language"]');
312 if (languageSelector) {
313 language = languageSelector.val();
314 if (language === 'none') {
315 language = '';
316 }
317 }
318 if (abbr && !term) {
319 var $activeEl = null;
320 abbrSelector.children().each(function(key) {
321 var $me = $(this);
322 if ($me.data('term') === abbr && (!languageSelector || $me.data('language') === language)) {
323 $activeEl = $me;
324 return false;
325 }
326 });
327 if ($activeEl !== null) {
328 term = $activeEl.data('term');
329 termSelector.val(term);
330 var useTermField = fieldset.closest('.tab-pane').find('[name="useTerm"]');
331 if (useTermField.length) {
332 useTermField.val(term);
333 }
334 }
335 }
336 },
337
338 /**
339 * Filter the term and abbr selector lists
340 * Set initial values
341 * If there is already an abbr and the filtered list has only one or no element, hide the fieldset
342 */
343 onSelectorRender: function (combo) {
344 var self = this,
345 filteredStore = [],
346 index = -1;
347
348 combo.children().each(function() {
349 var $me = $(this),
350 term = $me.data('term'),
351 abbr = $me.data('abbr');
352
353 if (!self.params.text
354 || !self.params.title
355 || self.params.text === term
356 || self.params.title === term
357 || self.params.title === abbr
358 ) {
359 filteredStore.push($me);
360 }
361 });
362 // Initialize the term and abbr combos
363 if (combo.attr('name') === 'termSelector') {
364 if (this.params.title) {
365 for (var i = 0; i < filteredStore.length; ++i) {
366 if (filteredStore[i].data('term') === this.params.title) {
367 index = i;
368 break;
369 }
370 }
371
372 if (index !== -1) {
373 record = filteredStore[i];
374 combo.val(record.value);
375 }
376 } else if (this.params.text) {
377 for (var i = 0; i < filteredStore.length; ++i) {
378 if (filteredStore[i].data('term') === this.params.text) {
379 index = i;
380 break;
381 }
382 }
383 if (index !== -1) {
384 record = filteredStore[i];
385 combo.val(record.value);
386 }
387 }
388 } else if (combo.attr('name') === 'abbrSelector' && this.params.text) {
389 for (var i = 0; i < filteredStore.length; ++i) {
390 if (filteredStore[i].data('abbr') === this.params.text) {
391 index = i;
392 break;
393 }
394 }
395 if (index !== -1) {
396 var record = filteredStore[index];
397 combo.val(record.value);
398 }
399 }
400 },
401
402 /**
403 * Handler when a term is selected
404 *
405 * @param {Event} event
406 */
407 onTermSelect: function (event) {
408 var $me = $(event.currentTarget),
409 fieldset = $me.closest('fieldset'),
410 tab = fieldset.closest('.tab-pane'),
411 term = $me.data('term'),
412 abbr = $me.data('abbr'),
413 language = $me.data('language');
414
415 // Update the abbreviation selector
416 var abbrSelector = tab.find('[name="abbrSelector"]');
417 abbrSelector.val(abbr);
418
419 // Update the language selector
420 var languageSelector = tab.find('[name="language"]');
421 if (languageSelector.length > 0) {
422 if (language) {
423 languageSelector.val(language);
424 } else {
425 languageSelector.val('none');
426 }
427 }
428
429 // Update the term to use
430 var useTermField = tab.find('[name="useTerm"]');
431 if (useTermField.length) {
432 useTermField.val(term);
433 }
434 },
435
436 /**
437 * Handler when an abbreviation or acronym is selected
438 *
439 * @param {Event} event
440 */
441 onAbbrSelect: function (event) {
442 var $me = $(event.currentTarget),
443 fieldset = $me.closest('fieldset'),
444 tab = fieldset.closest('.tab-pane'),
445 term = $me.data('term'),
446 language = $me.data('language');
447
448 // Update the term selector
449 var termSelector = tab.find('[name="termSelector"]');
450 termSelector.val(term);
451
452 // Update the language selector
453 var languageSelector = tab.find('[name="language"]');
454 if (languageSelector.length > 0) {
455 if (language) {
456 languageSelector.val(language);
457 } else {
458 languageSelector.val('none');
459 }
460 }
461
462 // Update the term to use
463 var useTermField = tab.find('[name="useTerm"]');
464 if (useTermField.length) {
465 useTermField.val(term);
466 }
467 },
468
469 /**
470 * This function builds the configuration object for the Abbreviation or Acronym to use fieldset
471 *
472 * @param {Object} element The element being edited, if any
473 * @return {Object} The fieldset configuration object
474 */
475 buildUseTermFieldsetConfig: function (element) {
476 var $fieldset = $('<fieldset />');
477
478 $fieldset.append(
479 $('<h4 />', {'class': 'form-section-headline'}).html(this.getHelpTip('termToAbridge', 'Term_to_abridge')),
480 $('<div />', {'class': 'form-group'}).append(
481 $('<label />', {'class': 'col-sm-2'}).html(this.getHelpTip('useThisTerm', 'Use_this_term')),
482 $('<div />', {'class': 'col-sm-10'}).append(
483 $('<input />', {name: 'useTerm', 'class': 'form-control', value: element ? element.title : ''})
484 )
485 )
486 );
487
488 return $fieldset;
489 },
490
491 /**
492 * Handler when the ok button is pressed
493 *
494 * @param {Event} event
495 */
496 okHandler: function (event) {
497 this.restoreSelection();
498 var tab = this.dialog.find('.tab-pane.active');
499 var type = tab.attr('id');
500 var languageSelector = tab.find('[name="language"]');
501 var language = languageSelector && languageSelector.length > 0 ? languageSelector.val() : '';
502 var termSelector = tab.find('[name="termSelector"]');
503 var term = termSelector && termSelector.length > 0 ? termSelector.val() : '';
504 var abbrSelector = tab.find('[name="abbrSelector"]');
505 var useTermField = tab.find('[name="useTerm"]');
506 if (!this.params.abbr) {
507 var abbr = this.editor.document.createElement(type);
508 if (useTermField.length) {
509 abbr.title = useTermField.val();
510 } else {
511 abbr.title = term;
512 }
513 if (term === abbr.title && abbrSelector && abbrSelector.length > 0) {
514 abbr.innerHTML = abbrSelector.val();
515 } else {
516 abbr.innerHTML = this.params.text;
517 }
518 if (language) {
519 this.getPluginInstance('Language').setLanguageAttributes(abbr, language);
520 }
521 this.editor.getSelection().insertNode(abbr);
522 // Position the cursor just after the inserted abbreviation
523 abbr = this.editor.getSelection().getParentElement();
524 if (abbr.nextSibling) {
525 this.editor.getSelection().selectNodeContents(abbr.nextSibling, true);
526 } else {
527 this.editor.getSelection().selectNodeContents(abbr.parentNode, false);
528 }
529 } else {
530 var abbr = this.params.abbr;
531 if (useTermField.length) {
532 abbr.title = useTermField.val();
533 } else {
534 abbr.title = term;
535 }
536 if (language) {
537 this.getPluginInstance('Language').setLanguageAttributes(abbr, language);
538 }
539 if (term === abbr.title && abbrSelector && abbrSelector.length > 0) {
540 abbr.innerHTML = abbrSelector.val();
541 }
542 }
543 this.close();
544 event.stopImmediatePropagation();
545 },
546
547 /**
548 * Handler when the delete button is pressed
549 *
550 * @param {Event} event
551 */
552 deleteHandler: function (event) {
553 this.restoreSelection();
554 var abbr = this.params.abbr;
555 if (abbr) {
556 this.editor.getDomNode().removeMarkup(abbr);
557 }
558 this.close();
559 event.stopImmediatePropagation();
560 },
561
562 /**
563 * This function gets called when the toolbar is updated
564 *
565 * @param {Object} button
566 * @param {String} mode
567 */
568 onUpdateToolbar: function (button, mode) {
569 if (mode === 'wysiwyg' && this.editor.isEditable()) {
570 var el = this.getCurrentAbbrElement();
571 var nodeName = typeof el === 'object' && el !== null ? el.nodeName.toLowerCase() : '';
572 // Disable the button if the selection and not inside a abbr or acronym element
573 button.setDisabled(
574 (this.editor.getSelection().isEmpty() && nodeName !== 'abbr' && nodeName !== 'acronym')
575 && (this.pageTSConfiguration.noAbbr || !this.enabledFieldsets['definedAbbreviation'])
576 && (this.pageTSConfiguration.noAcronym || !this.enabledFieldsets['definedAcronym'])
577 );
578 button.setInactive(
579 !(nodeName === 'abbr' && (this.enabledFieldsets['definedAbbreviation'] || this.enabledFieldsets['abbreviation']))
580 && !(nodeName === 'acronym' && (this.enabledFieldsets['definedAcronym'] || this.enabledFieldsets['acronym']))
581 );
582 button.setTooltip(this.localize((button.disabled || button.inactive) ? 'Insert abbreviation' : 'Edit abbreviation'));
583 button.contextMenuTitle = '';
584 if (this.dialog) {
585 this.dialog.focus();
586 }
587 }
588 }
589 });
590
591 return Abbreviation;
592 });