b89fcc1792a678b6c56ec70b52e7ff57593d3de7
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / htmlarea / plugins / BlockElements / block-elements.js
1 /***************************************************************
2 * Copyright notice
3 *
4 * (c) 2007-2012 Stanislas Rolland <typo3(arobas)sjbr.ca>
5 * All rights reserved
6 *
7 * This script is part of the TYPO3 project. The TYPO3 project is
8 * free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * The GNU General Public License can be found at
14 * http://www.gnu.org/copyleft/gpl.html.
15 * A copy is found in the textfile GPL.txt and important notices to the license
16 * from the author is found in LICENSE.txt distributed with these scripts.
17 *
18 *
19 * This script is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27 /*
28 * BlockElements Plugin for TYPO3 htmlArea RTE
29 */
30 HTMLArea.BlockElements = Ext.extend(HTMLArea.Plugin, {
31 /*
32 * This function gets called by the class constructor
33 */
34 configurePlugin: function (editor) {
35 /*
36 * Setting up some properties from PageTSConfig
37 */
38 this.buttonsConfiguration = this.editorConfiguration.buttons;
39 if (this.buttonsConfiguration.blockstyle) {
40 this.tags = this.editorConfiguration.buttons.blockstyle.tags;
41 }
42 this.useClass = {
43 Indent : "indent",
44 JustifyLeft : "align-left",
45 JustifyCenter : "align-center",
46 JustifyRight : "align-right",
47 JustifyFull : "align-justify"
48 };
49 this.useAlignAttribute = false;
50 for (var buttonId in this.useClass) {
51 if (this.useClass.hasOwnProperty(buttonId)) {
52 if (this.editorConfiguration.buttons[this.buttonList[buttonId][2]]) {
53 this.useClass[buttonId] = this.editorConfiguration.buttons[this.buttonList[buttonId][2]].useClass ? this.editorConfiguration.buttons[this.buttonList[buttonId][2]].useClass : this.useClass[buttonId];
54 if (buttonId === "Indent") {
55 this.useBlockquote = this.editorConfiguration.buttons.indent.useBlockquote ? this.editorConfiguration.buttons.indent.useBlockquote : false;
56 } else {
57 if (this.editorConfiguration.buttons[this.buttonList[buttonId][2]].useAlignAttribute) {
58 this.useAlignAttribute = true;
59 }
60 }
61 }
62 }
63 }
64 this.allowedAttributes = new Array('id', 'title', 'lang', 'xml:lang', 'dir', 'class', 'itemscope', 'itemtype', 'itemprop');
65 if (Ext.isIE) {
66 this.addAllowedAttribute('className');
67 }
68 this.indentedList = null;
69 // Standard block formating items
70 var standardElements = new Array('address', 'article', 'aside', 'blockquote', 'div', 'footer', 'header', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'nav', 'p', 'pre', 'section');
71 this.standardBlockElements = new RegExp( '^(' + standardElements.join('|') + ')$', 'i');
72 // Process block formating customization configuration
73 this.formatBlockItems = {};
74 if (this.buttonsConfiguration
75 && this.buttonsConfiguration.formatblock
76 && this.buttonsConfiguration.formatblock.items) {
77 this.formatBlockItems = this.buttonsConfiguration.formatblock.items;
78 }
79 // Build lists of mutually exclusive class names
80 for (var tagName in this.formatBlockItems) {
81 if (this.formatBlockItems.hasOwnProperty(tagName) && this.formatBlockItems[tagName].tagName && this.formatBlockItems[tagName].addClass) {
82 if (!this.formatBlockItems[this.formatBlockItems[tagName].tagName]) {
83 this.formatBlockItems[this.formatBlockItems[tagName].tagName] = {};
84 }
85 if (!this.formatBlockItems[this.formatBlockItems[tagName].tagName].classList) {
86 this.formatBlockItems[this.formatBlockItems[tagName].tagName].classList = new Array();
87 }
88 this.formatBlockItems[this.formatBlockItems[tagName].tagName].classList.push(this.formatBlockItems[tagName].addClass);
89 }
90 }
91 for (var tagName in this.formatBlockItems) {
92 if (this.formatBlockItems.hasOwnProperty(tagName) && this.formatBlockItems[tagName].classList) {
93 this.formatBlockItems[tagName].classList = new RegExp( "^(" + this.formatBlockItems[tagName].classList.join("|") + ")$");
94 }
95 }
96 /*
97 * Registering plugin "About" information
98 */
99 var pluginInformation = {
100 version : '3.0',
101 developer : 'Stanislas Rolland',
102 developerUrl : 'http://www.sjbr.ca/',
103 copyrightOwner : 'Stanislas Rolland',
104 sponsor : this.localize('Technische Universitat Ilmenau'),
105 sponsorUrl : 'http://www.tu-ilmenau.de/',
106 license : 'GPL'
107 };
108 this.registerPluginInformation(pluginInformation);
109
110 /*
111 * Registering the dropdown list
112 */
113 var buttonId = "FormatBlock";
114 var dropDownConfiguration = {
115 id: buttonId,
116 tooltip: this.localize(buttonId + "-Tooltip"),
117 options: this.buttonsConfiguration.formatblock ? this.buttonsConfiguration.formatblock.options : [],
118 action: "onChange"
119 };
120 if (this.buttonsConfiguration.formatblock) {
121 dropDownConfiguration.width = this.buttonsConfiguration.formatblock.width ? parseInt(this.buttonsConfiguration.formatblock.width, 10) : 200;
122 if (this.buttonsConfiguration.formatblock.listWidth) {
123 dropDownConfiguration.listWidth = parseInt(this.buttonsConfiguration.formatblock.listWidth, 10);
124 }
125 if (this.buttonsConfiguration.formatblock.maxHeight) {
126 dropDownConfiguration.maxHeight = parseInt(this.buttonsConfiguration.formatblock.maxHeight, 10);
127 }
128 }
129 this.registerDropDown(dropDownConfiguration);
130 /*
131 * Establishing the list of allowed block elements
132 */
133 var blockElements = new Array();
134 Ext.each(dropDownConfiguration.options, function (option) {
135 if (option[1] != 'none') {
136 blockElements.push(option[1]);
137 }
138 });
139 if (blockElements.length) {
140 this.allowedBlockElements = new RegExp( "^(" + blockElements.join("|") + ")$", "i");
141 } else {
142 this.allowedBlockElements = this.standardBlockElements;
143 }
144 /*
145 * Registering hot keys for the dropdown list items
146 */
147 Ext.each(blockElements, function (blockElement) {
148 var configuredHotKey = this.defaultHotKeys[blockElement];
149 if (this.editorConfiguration.buttons.formatblock
150 && this.editorConfiguration.buttons.formatblock.items
151 && this.editorConfiguration.buttons.formatblock.items[blockElement]
152 && this.editorConfiguration.buttons.formatblock.items[blockElement].hotKey) {
153 configuredHotKey = this.editorConfiguration.buttons.formatblock.items[blockElement].hotKey;
154 }
155 if (configuredHotKey) {
156 var hotKeyConfiguration = {
157 id : configuredHotKey,
158 cmd : buttonId,
159 element : blockElement
160 };
161 this.registerHotKey(hotKeyConfiguration);
162 }
163 }, this);
164 /*
165 * Registering the buttons
166 */
167 for (var buttonId in this.buttonList) {
168 if (this.buttonList.hasOwnProperty(buttonId)) {
169 var button = this.buttonList[buttonId];
170 var buttonConfiguration = {
171 id : buttonId,
172 tooltip : this.localize(buttonId + '-Tooltip'),
173 iconCls : 'htmlarea-action-' + button[3],
174 contextMenuTitle: this.localize(buttonId + '-contextMenuTitle'),
175 helpText : this.localize(buttonId + '-helpText'),
176 action : 'onButtonPress',
177 hotKey : ((this.buttonsConfiguration[button[2]] && this.buttonsConfiguration[button[2]].hotKey) ? this.buttonsConfiguration[button[2]].hotKey : (button[1] ? button[1] : null))
178 };
179 this.registerButton(buttonConfiguration);
180 }
181 }
182 return true;
183 },
184 /*
185 * The list of buttons added by this plugin
186 */
187 buttonList: {
188 Indent : [null, 'TAB', 'indent', 'indent'],
189 Outdent : [null, 'SHIFT-TAB', 'outdent', 'outdent'],
190 Blockquote : [null, null, 'blockquote', 'blockquote'],
191 InsertParagraphBefore : [null, null, 'insertparagraphbefore', 'paragraph-insert-before'],
192 InsertParagraphAfter : [null, null, 'insertparagraphafter', 'paragraph-insert-after'],
193 JustifyLeft : [null, 'l', 'left', 'justify-left'],
194 JustifyCenter : [null, 'e', 'center', 'justify-center'],
195 JustifyRight : [null, 'r', 'right', 'justify-right'],
196 JustifyFull : [null, 'j', 'justifyfull', 'justify-full'],
197 InsertOrderedList : [null, null, 'orderedlist', 'ordered-list'],
198 InsertUnorderedList : [null, null, 'unorderedlist', 'unordered-list'],
199 InsertHorizontalRule : [null, null, 'inserthorizontalrule', 'horizontal-rule-insert']
200 },
201 /*
202 * The list of hotkeys associated with block elements and registered by default by this plugin
203 */
204 defaultHotKeys: {
205 'p' : 'n',
206 'h1' : '1',
207 'h2' : '2',
208 'h3' : '3',
209 'h4' : '4',
210 'h5' : '5',
211 'h6' : '6'
212 },
213 /*
214 * The function returns true if the type of block element is allowed in the current configuration
215 */
216 isAllowedBlockElement: function (blockName) {
217 return this.allowedBlockElements.test(blockName);
218 },
219 /*
220 * This function adds an attribute to the array of attributes allowed on block elements
221 *
222 * @param string attribute: the name of the attribute to be added to the array
223 *
224 * @return void
225 */
226 addAllowedAttribute: function (attribute) {
227 this.allowedAttributes.push(attribute);
228 },
229 /*
230 * This function gets called when some block element was selected in the drop-down list
231 */
232 onChange: function (editor, combo, record, index) {
233 this.applyBlockElement(combo.itemId, combo.getValue());
234 },
235 applyBlockElement: function (buttonId, blockElement) {
236 var tagName = blockElement;
237 var className = null;
238 if (this.formatBlockItems[tagName]) {
239 if (this.formatBlockItems[tagName].addClass) {
240 className = this.formatBlockItems[tagName].addClass;
241 }
242 if (this.formatBlockItems[tagName].tagName) {
243 tagName = this.formatBlockItems[tagName].tagName;
244 }
245 }
246 if (this.standardBlockElements.test(tagName) || tagName == "none") {
247 switch (tagName) {
248 case 'blockquote':
249 this.onButtonPress(this.editor, 'Blockquote', null, className);
250 break;
251 case 'address':
252 case 'article':
253 case 'aside':
254 case 'div':
255 case 'footer':
256 case 'header':
257 case 'nav':
258 case 'section':
259 case 'none':
260 this.onButtonPress(this.editor, tagName, null, className);
261 break;
262 default :
263 var element = tagName;
264 if (Ext.isIE) {
265 element = '<' + element + '>';
266 }
267 this.editor.focus();
268 if (Ext.isWebKit) {
269 if (!this.editor.document.body.hasChildNodes()) {
270 this.editor.document.body.appendChild((this.editor.document.createElement('br')));
271 }
272 // WebKit sometimes leaves empty block at the end of the selection
273 this.editor.document.body.normalize();
274 }
275 try {
276 this.editor.getSelection().execCommand(buttonId, false, element);
277 } catch(e) {
278 this.appendToLog('applyBlockElement', e + '\n\nby execCommand(' + buttonId + ');', 'error');
279 }
280 this.addClassOnBlockElements(tagName, className);
281 }
282 }
283 },
284 /*
285 * This function gets called when a button was pressed.
286 *
287 * @param object editor: the editor instance
288 * @param string id: the button id or the key
289 * @param object target: the target element of the contextmenu event, when invoked from the context menu
290 * @param string className: the className to be assigned to the element
291 *
292 * @return boolean false if action is completed
293 */
294 onButtonPress: function (editor, id, target, className) {
295 // Could be a button or its hotkey
296 var buttonId = this.translateHotKey(id);
297 buttonId = buttonId ? buttonId : id;
298 var range = editor.getSelection().createRange();
299 var statusBarSelection = this.editor.statusBar ? this.editor.statusBar.getSelection() : null;
300 var parentElement = statusBarSelection ? statusBarSelection : this.editor.getSelection().getParentElement();
301 if (target) {
302 parentElement = target;
303 }
304 while (parentElement && (!HTMLArea.DOM.isBlockElement(parentElement) || /^li$/i.test(parentElement.nodeName))) {
305 parentElement = parentElement.parentNode;
306 }
307 var blockAncestors = HTMLArea.DOM.getBlockAncestors(parentElement);
308 var tableCell = null;
309 if (id === "TAB" || id === "SHIFT-TAB") {
310 for (var i = blockAncestors.length; --i >= 0;) {
311 if (/^(td|th)$/i.test(blockAncestors[i].nodeName)) {
312 tableCell = blockAncestors[i];
313 break;
314 }
315 }
316 }
317 var fullNodeTextSelected = (!Ext.isIE && parentElement.textContent === range.toString()) || (Ext.isIE && parentElement.innerText === range.text);
318 switch (buttonId) {
319 case "Indent" :
320 if (/^(ol|ul)$/i.test(parentElement.nodeName) && !(fullNodeTextSelected && !/^(li)$/i.test(parentElement.parentNode.nodeName))) {
321 if (Ext.isOpera) {
322 try {
323 this.editor.getSelection().execCommand(buttonId, false, null);
324 } catch(e) {
325 this.appendToLog('onButtonPress', e + '\n\nby execCommand(' + buttonId + ');', 'error');
326 }
327 this.indentedList = parentElement;
328 this.makeNestedList(parentElement);
329 this.editor.getSelection().selectNodeContents(this.indentedList.lastChild, false);
330 } else {
331 this.indentSelectedListElements(parentElement, range);
332 }
333 } else if (tableCell) {
334
335 var tablePart = tableCell.parentNode.parentNode;
336 // Get next cell in same table part
337 var nextCell = tableCell.nextSibling ? tableCell.nextSibling : (tableCell.parentNode.nextSibling ? tableCell.parentNode.nextSibling.cells[0] : null);
338 // Next cell is in other table part
339 if (!nextCell) {
340 switch (tablePart.nodeName.toLowerCase()) {
341 case "thead":
342 nextCell = tablePart.parentNode.tBodies[0].rows[0].cells[0];
343 break;
344 case "tbody":
345 nextCell = tablePart.nextSibling ? tablePart.nextSibling.rows[0].cells[0] : null;
346 break;
347 case "tfoot":
348 this.editor.getSelection().selectNodeContents(tablePart.parentNode.lastChild.lastChild.lastChild, true);
349 }
350 }
351 if (!nextCell) {
352 if (this.getPluginInstance('TableOperations')) {
353 this.getPluginInstance('TableOperations').onButtonPress(this.editor, 'TO-row-insert-under');
354 } else {
355 nextCell = tablePart.parentNode.rows[0].cells[0];
356 }
357 }
358 if (nextCell) {
359 if (Ext.isOpera && !nextCell.hasChildNodes()) {
360 nextCell.appendChild(this.editor.document.createElement('br'));
361 }
362 this.editor.getSelection().selectNodeContents(nextCell, true);
363 }
364 } else if (this.useBlockquote) {
365 try {
366 this.editor.getSelection().execCommand(buttonId, false, null);
367 } catch(e) {
368 this.appendToLog('onButtonPress', e + '\n\nby execCommand(' + buttonId + ');', 'error');
369 }
370 } else if (this.isAllowedBlockElement("div")) {
371 if (/^div$/i.test(parentElement.nodeName) && !HTMLArea.DOM.hasClass(parentElement, this.useClass[buttonId])) {
372 HTMLArea.DOM.addClass(parentElement, this.useClass[buttonId]);
373 } else if (!/^div$/i.test(parentElement.nodeName) && /^div$/i.test(parentElement.parentNode.nodeName) && !HTMLArea.DOM.hasClass(parentElement.parentNode, this.useClass[buttonId])) {
374 HTMLArea.DOM.addClass(parentElement.parentNode, this.useClass[buttonId]);
375 } else {
376 var bookmark = this.editor.getBookMark().get(range);
377 var newBlock = this.wrapSelectionInBlockElement('div', this.useClass[buttonId], null, true);
378 this.editor.getSelection().selectRange(this.editor.getBookMark().moveTo(bookmark));
379 }
380 } else {
381 this.addClassOnBlockElements(buttonId);
382 }
383 break;
384 case "Outdent" :
385 if (/^(ol|ul)$/i.test(parentElement.nodeName) && !HTMLArea.DOM.hasClass(parentElement, this.useClass.Indent)) {
386 if (/^(li)$/i.test(parentElement.parentNode.nodeName)) {
387 if (Ext.isOpera) {
388 try {
389 this.editor.getSelection().execCommand(buttonId, false, null);
390 } catch(e) {
391 this.appendToLog('onButtonPress', e + '\n\nby execCommand(' + buttonId + ');', 'error');
392 }
393 } else {
394 this.outdentSelectedListElements(parentElement, range);
395 }
396 }
397 } else if (tableCell) {
398 var previousCell = tableCell.previousSibling ? tableCell.previousSibling : (tableCell.parentNode.previousSibling ? tableCell.parentNode.previousSibling.lastChild : null);
399 if (!previousCell) {
400 var table = tableCell.parentNode.parentNode.parentNode;
401 var tablePart = tableCell.parentNode.parentNode.nodeName.toLowerCase();
402 switch (tablePart) {
403 case "tbody":
404 if (table.tHead) {
405 previousCell = table.tHead.rows[table.tHead.rows.length-1].cells[table.tHead.rows[table.tHead.rows.length-1].cells.length-1];
406 break;
407 }
408 case "thead":
409 if (table.tFoot) {
410 previousCell = table.tFoot.rows[table.tFoot.rows.length-1].cells[table.tFoot.rows[table.tFoot.rows.length-1].cells.length-1];
411 break;
412 }
413 case "tfoot":
414 previousCell = table.tBodies[table.tBodies.length-1].rows[table.tBodies[table.tBodies.length-1].rows.length-1].cells[table.tBodies[table.tBodies.length-1].rows[table.tBodies[table.tBodies.length-1].rows.length-1].cells.length-1];
415 }
416 }
417 if (previousCell) {
418 if (Ext.isOpera && !previousCell.hasChildNodes()) {
419 previousCell.appendChild(this.editor.document.createElement('br'));
420 }
421 this.editor.getSelection().selectNodeContents(previousCell, true);
422 }
423 } else if (this.useBlockquote) {
424 try {
425 this.editor.getSelection().execCommand(buttonId, false, null);
426 } catch(e) {
427 this.appendToLog('onButtonPress', e + '\n\nby execCommand(' + buttonId + ');', 'error');
428 }
429 } else if (this.isAllowedBlockElement("div")) {
430 for (var i = blockAncestors.length; --i >= 0;) {
431 if (HTMLArea.DOM.hasClass(blockAncestors[i], this.useClass.Indent)) {
432 var bookmark = this.editor.getBookMark().get(range);
433 var newBlock = this.wrapSelectionInBlockElement('div', false, blockAncestors[i]);
434 // If not directly under the div, we need to backtrack
435 if (newBlock.parentNode !== blockAncestors[i]) {
436 var parent = newBlock.parentNode;
437 this.editor.getDomNode().removeMarkup(newBlock);
438 while (parent.parentNode !== blockAncestors[i]) {
439 parent = parent.parentNode;
440 }
441 blockAncestors[i].insertBefore(newBlock, parent);
442 newBlock.appendChild(parent);
443 }
444 newBlock.className = blockAncestors[i].className;
445 HTMLArea.DOM.removeClass(newBlock, this.useClass.Indent);
446 if (!newBlock.previousSibling) {
447 while (newBlock.hasChildNodes()) {
448 if (newBlock.firstChild.nodeType === HTMLArea.DOM.ELEMENT_NODE) {
449 newBlock.firstChild.className = newBlock.className;
450 }
451 blockAncestors[i].parentNode.insertBefore(newBlock.firstChild, blockAncestors[i]);
452 }
453 } else if (!newBlock.nextSibling) {
454 if (blockAncestors[i].nextSibling) {
455 while (newBlock.hasChildNodes()) {
456 if (newBlock.firstChild.nodeType === HTMLArea.DOM.ELEMENT_NODE) {
457 newBlock.lastChild.className = newBlock.className;
458 }
459 blockAncestors[i].parentNode.insertBefore(newBlock.lastChild, blockAncestors[i].nextSibling);
460 }
461 } else {
462 while (newBlock.hasChildNodes()) {
463 if (newBlock.firstChild.nodeType === HTMLArea.DOM.ELEMENT_NODE) {
464 newBlock.firstChild.className = newBlock.className;
465 }
466 blockAncestors[i].parentNode.appendChild(newBlock.firstChild);
467 }
468 }
469 } else {
470 var clone = blockAncestors[i].cloneNode(false);
471 if (blockAncestors[i].nextSibling) {
472 blockAncestors[i].parentNode.insertBefore(clone, blockAncestors[i].nextSibling);
473 } else {
474 blockAncestors[i].parentNode.appendChild(clone);
475 }
476 while (newBlock.nextSibling) {
477 clone.appendChild(newBlock.nextSibling);
478 }
479 while (newBlock.hasChildNodes()) {
480 if (newBlock.firstChild.nodeType === HTMLArea.DOM.ELEMENT_NODE) {
481 newBlock.firstChild.className = newBlock.className;
482 }
483 blockAncestors[i].parentNode.insertBefore(newBlock.firstChild, clone);
484 }
485 }
486 blockAncestors[i].removeChild(newBlock);
487 if (!blockAncestors[i].hasChildNodes()) {
488 blockAncestors[i].parentNode.removeChild(blockAncestors[i]);
489 }
490 this.editor.getSelection().selectRange(this.editor.getBookMark().moveTo(bookmark));
491 break;
492 }
493 }
494 } else {
495 this.addClassOnBlockElements(buttonId);
496 }
497 break;
498 case "InsertParagraphBefore" :
499 case "InsertParagraphAfter" :
500 this.insertParagraph(buttonId === "InsertParagraphAfter");
501 break;
502 case "Blockquote" :
503 var commandState = false;
504 for (var i = blockAncestors.length; --i >= 0;) {
505 if (/^blockquote$/i.test(blockAncestors[i].nodeName)) {
506 commandState = true;
507 this.editor.getDomNode().removeMarkup(blockAncestors[i]);
508 break;
509 }
510 }
511 if (!commandState) {
512 var bookmark = this.editor.getBookMark().get(range);
513 var newBlock = this.wrapSelectionInBlockElement('blockquote', className, null, true);
514 this.editor.getSelection().selectRange(this.editor.getBookMark().moveTo(bookmark));
515 }
516 break;
517 case 'address':
518 case 'article':
519 case 'aside':
520 case 'div':
521 case 'footer':
522 case 'header':
523 case 'nav':
524 case 'section':
525 var bookmark = this.editor.getBookMark().get(range);
526 var newBlock = this.wrapSelectionInBlockElement(buttonId, className, null, true);
527 this.editor.getSelection().selectRange(this.editor.getBookMark().moveTo(bookmark));
528 break;
529 case "JustifyLeft" :
530 case "JustifyCenter" :
531 case "JustifyRight" :
532 case "JustifyFull" :
533 if (this.useAlignAttribute) {
534 try {
535 this.editor.getSelection().execCommand(buttonId, false, null);
536 } catch(e) {
537 this.appendToLog('onButtonPress', e + '\n\nby execCommand(' + buttonId + ');', 'error');
538 }
539 } else {
540 this.addClassOnBlockElements(buttonId);
541 }
542 break;
543 case "InsertOrderedList":
544 case "InsertUnorderedList":
545 this.insertList(buttonId, parentElement);
546 break;
547 case "InsertHorizontalRule":
548 this.insertHorizontalRule();
549 break;
550 case "none" :
551 if (this.isAllowedBlockElement(parentElement.nodeName)) {
552 this.editor.getDomNode().removeMarkup(parentElement);
553 }
554 break;
555 default :
556 break;
557 }
558 return false;
559 },
560 /*
561 * This function wraps the block elements intersecting the current selection in a block element of the given type
562 *
563 * @param string blockName: the type of element to be used as wrapping block
564 * @param string useClass: a class to be assigned to the wrapping block
565 * @param object withinBlock: only elements contained in this block will be wrapped
566 * @param boolean keepValid: make only valid wraps (working wraps may produce temporary invalid hierarchies)
567 *
568 * @return object the wrapping block
569 */
570 wrapSelectionInBlockElement: function (blockName, useClass, withinBlock, keepValid) {
571 var endBlocks = this.editor.getSelection().getEndBlocks();
572 var startAncestors = HTMLArea.DOM.getBlockAncestors(endBlocks.start, withinBlock);
573 var endAncestors = HTMLArea.DOM.getBlockAncestors(endBlocks.end, withinBlock);
574 var i = 0;
575 while (i < startAncestors.length && i < endAncestors.length && startAncestors[i] === endAncestors[i]) {
576 ++i;
577 }
578
579 if ((endBlocks.start === endBlocks.end && /^(body)$/i.test(endBlocks.start.nodeName)) || !startAncestors[i] || !endAncestors[i]) {
580 --i;
581 }
582 if (keepValid) {
583 if (endBlocks.start === endBlocks.end) {
584 while (i && /^(thead|tbody|tfoot|tr|dt)$/i.test(startAncestors[i].nodeName)) {
585 --i;
586 }
587 } else {
588 while (i && (/^(thead|tbody|tfoot|tr|td|li|dd|dt)$/i.test(startAncestors[i].nodeName) || /^(thead|tbody|tfoot|tr|td|li|dd|dt)$/i.test(endAncestors[i].nodeName))) {
589 --i;
590 }
591 }
592 }
593 var blockElement = this.editor.document.createElement(blockName);
594 if (useClass) {
595 HTMLArea.DOM.addClass(blockElement, useClass);
596 }
597 var contextElement = endAncestors[0];
598 if (i) {
599 contextElement = endAncestors[i-1];
600 }
601 var nextElement = endAncestors[i].nextSibling;
602 var block = startAncestors[i], sibling;
603 if ((!/^(body|td|th|li|dd)$/i.test(block.nodeName) || /^(ol|ul|dl)$/i.test(blockName)) && block != withinBlock) {
604 while (block && block != nextElement) {
605 sibling = block.nextSibling;
606 blockElement.appendChild(block);
607 block = sibling;
608 }
609 if (nextElement) {
610 blockElement = nextElement.parentNode.insertBefore(blockElement, nextElement);
611 } else {
612 blockElement = contextElement.appendChild(blockElement);
613 }
614 } else {
615 contextElement = block;
616 block = block.firstChild;
617 while (block) {
618 sibling = block.nextSibling;
619 blockElement.appendChild(block);
620 block = sibling;
621 }
622 blockElement = contextElement.appendChild(blockElement);
623 }
624 // Things go wrong in some browsers when the node is empty
625 if (Ext.isWebKit && !blockElement.hasChildNodes()) {
626 blockElement = blockElement.appendChild(this.editor.document.createElement('br'));
627 }
628 return blockElement;
629 },
630 /*
631 * This function adds a class attribute on blocks sibling of the block containing the start container of the selection
632 */
633 addClassOnBlockElements: function (buttonId, className) {
634 var endBlocks = this.editor.getSelection().getEndBlocks();
635 var startAncestors = HTMLArea.DOM.getBlockAncestors(endBlocks.start);
636 var endAncestors = HTMLArea.DOM.getBlockAncestors(endBlocks.end);
637 var index = 0;
638 while (index < startAncestors.length && index < endAncestors.length && startAncestors[index] === endAncestors[index]) {
639 ++index;
640 }
641 if (endBlocks.start === endBlocks.end) {
642 --index;
643 }
644 if (!/^(body)$/i.test(startAncestors[index].nodeName)) {
645 for (var block = startAncestors[index]; block; block = block.nextSibling) {
646 if (HTMLArea.DOM.isBlockElement(block)) {
647 switch (buttonId) {
648 case "Indent" :
649 if (!HTMLArea.DOM.hasClass(block, this.useClass[buttonId])) {
650 HTMLArea.DOM.addClass(block, this.useClass[buttonId]);
651 }
652 break;
653 case "Outdent" :
654 if (HTMLArea.DOM.hasClass(block, this.useClass["Indent"])) {
655 HTMLArea.DOM.removeClass(block, this.useClass["Indent"]);
656 }
657 break;
658 case "JustifyLeft" :
659 case "JustifyCenter" :
660 case "JustifyRight" :
661 case "JustifyFull" :
662 this.toggleAlignmentClass(block, buttonId);
663 break;
664 default :
665 if (this.standardBlockElements.test(buttonId.toLowerCase()) && buttonId.toLowerCase() == block.nodeName.toLowerCase()) {
666 this.cleanClasses(block);
667 if (className) {
668 HTMLArea.DOM.addClass(block, className);
669 }
670 }
671 break;
672 }
673 }
674 if (block == endAncestors[index]) {
675 break;
676 }
677 }
678 }
679 },
680 /*
681 * This function toggles the alignment class on the given block
682 */
683 toggleAlignmentClass: function (block, buttonId) {
684 for (var alignmentButtonId in this.useClass) {
685 if (this.useClass.hasOwnProperty(alignmentButtonId) && alignmentButtonId !== "Indent") {
686 if (HTMLArea.DOM.hasClass(block, this.useClass[alignmentButtonId])) {
687 HTMLArea.DOM.removeClass(block, this.useClass[alignmentButtonId]);
688 } else if (alignmentButtonId === buttonId) {
689 HTMLArea.DOM.addClass(block, this.useClass[alignmentButtonId]);
690 }
691 }
692 }
693 if (/^div$/i.test(block.nodeName) && !HTMLArea.DOM.hasAllowedAttributes(block, this.allowedAttributes)) {
694 this.editor.getDomNode().removeMarkup(block);
695 }
696 },
697
698 insertList: function (buttonId, parentElement) {
699 if (/^(dd)$/i.test(parentElement.nodeName)) {
700 var list = parentElement.appendChild(this.editor.document.createElement((buttonId === 'OrderedList') ? 'ol' : 'ul'));
701 var first = list.appendChild(this.editor.document.createElement('li'));
702 first.innerHTML = '<br />';
703 this.editor.getSelection().selectNodeContents(first, true);
704 } else {
705 // parentElement may be removed by following command
706 var parentNode = parentElement.parentNode;
707 try {
708 this.editor.getSelection().execCommand(buttonId, false, null);
709 } catch(e) {
710 this.appendToLog('onButtonPress', e + '\n\nby execCommand(' + buttonId + ');', 'error');
711 }
712 if (Ext.isWebKit) {
713 this.editor.getDomNode().cleanAppleStyleSpans(parentNode);
714 }
715 }
716 },
717 /*
718 * Indent selected list elements
719 */
720 indentSelectedListElements: function (list, range) {
721 var bookmark = this.editor.getBookMark().get(range);
722 // The selected elements are wrapped into a list element
723 var indentedList = this.wrapSelectionInBlockElement(list.nodeName.toLowerCase(), null, list);
724 // which breaks the range
725 var range = this.editor.getBookMark().moveTo(bookmark);
726 bookmark = this.editor.getBookMark().get(range);
727
728 // Check if the last element has children. If so, outdent those that do not intersect the selection
729 var last = indentedList.lastChild.lastChild;
730 if (last && /^(ol|ul)$/i.test(last.nodeName)) {
731 var child = last.firstChild, next;
732 while (child) {
733 next = child.nextSibling;
734 if (!HTMLArea.DOM.rangeIntersectsNode(range, child)) {
735 indentedList.appendChild(child);
736 }
737 child = next;
738 }
739 if (!last.hasChildNodes()) {
740 HTMLArea.DOM.removeFromParent(last);
741 }
742 }
743 if (indentedList.previousSibling && indentedList.previousSibling.hasChildNodes()) {
744 // Indenting some elements not including the first one
745 if (/^(ol|ul)$/i.test(indentedList.previousSibling.lastChild.nodeName)) {
746 // Some indented elements exist just above our selection
747 // Moving to regroup with these elements
748 while (indentedList.hasChildNodes()) {
749 indentedList.previousSibling.lastChild.appendChild(indentedList.firstChild);
750 }
751 list.removeChild(indentedList);
752 } else {
753 indentedList = indentedList.previousSibling.appendChild(indentedList);
754 }
755 } else {
756 // Indenting the first element and possibly some more
757 var first = this.editor.document.createElement("li");
758 first.innerHTML = "&nbsp;";
759 list.insertBefore(first, indentedList);
760 indentedList = first.appendChild(indentedList);
761 }
762 this.editor.getSelection().selectRange(this.editor.getBookMark().moveTo(bookmark));
763 },
764 /*
765 * Outdent selected list elements
766 */
767 outdentSelectedListElements: function (list, range) {
768 // We wrap the selected li elements and thereafter move them one level up
769 var bookmark = this.editor.getBookMark().get(range);
770 var wrappedList = this.wrapSelectionInBlockElement(list.nodeName.toLowerCase(), null, list);
771 // which breaks the range
772 var range = this.editor.getBookMark().moveTo(bookmark);
773 bookmark = this.editor.getBookMark().get(range);
774
775 if (!wrappedList.previousSibling) {
776 // Outdenting the first element(s) of an indented list
777 var next = list.parentNode.nextSibling;
778 var last = wrappedList.lastChild;
779 while (wrappedList.hasChildNodes()) {
780 if (next) {
781 list.parentNode.parentNode.insertBefore(wrappedList.firstChild, next);
782 } else {
783 list.parentNode.parentNode.appendChild(wrappedList.firstChild);
784 }
785 }
786 list.removeChild(wrappedList);
787 last.appendChild(list);
788 } else if (!wrappedList.nextSibling) {
789 // Outdenting the last element(s) of the list
790 // This will break the gecko bookmark
791 this.editor.getBookMark().moveTo(bookmark);
792 while (wrappedList.hasChildNodes()) {
793 if (list.parentNode.nextSibling) {
794 list.parentNode.parentNode.insertBefore(wrappedList.firstChild, list.parentNode.nextSibling);
795 } else {
796 list.parentNode.parentNode.appendChild(wrappedList.firstChild);
797 }
798 }
799 list.removeChild(wrappedList);
800 this.editor.getSelection().selectNodeContents(list.parentNode.nextSibling, true);
801 bookmark = this.editor.getBookMark().get(this.editor.getSelection().createRange());
802 } else {
803 // Outdenting the middle of a list
804 var next = list.parentNode.nextSibling;
805 var last = wrappedList.lastChild;
806 var sibling = wrappedList.nextSibling;
807 while (wrappedList.hasChildNodes()) {
808 if (next) {
809 list.parentNode.parentNode.insertBefore(wrappedList.firstChild, next);
810 } else {
811 list.parentNode.parentNode.appendChild(wrappedList.firstChild);
812 }
813 }
814 while (sibling) {
815 wrappedList.appendChild(sibling);
816 sibling = sibling.nextSibling;
817 }
818 last.appendChild(wrappedList);
819 }
820 // Remove the list if all its elements have been moved up
821 if (!list.hasChildNodes()) {
822 list.parentNode.removeChild(list);
823 }
824 this.editor.getSelection().selectRange(this.editor.getBookMark().moveTo(bookmark));
825 },
826 /*
827 * Make XHTML-compliant nested list
828 * We need this for Opera
829 */
830 makeNestedList: function (el) {
831 var previous;
832 for (var i = el.firstChild; i; i = i.nextSibling) {
833 if (/^li$/i.test(i.nodeName)) {
834 for (var j = i.firstChild; j; j = j.nextSibling) {
835 if (/^(ol|ul)$/i.test(j.nodeName)) {
836 this.makeNestedList(j);
837 }
838 }
839 } else if (/^(ol|ul)$/i.test(i.nodeName)) {
840 previous = i.previousSibling;
841 this.indentedList = i.cloneNode(true);
842 if (!previous) {
843 previous = el.insertBefore(this.editor.document.createElement('li'), i);
844 this.indentedList = previous.appendChild(this.indentedList);
845 } else {
846 this.indentedList = previous.appendChild(this.indentedList);
847 }
848 HTMLArea.DOM.removeFromParent(i);
849 this.makeNestedList(el);
850 break;
851 }
852 }
853 },
854 /*
855 * Insert a paragraph
856 */
857 insertParagraph: function (after) {
858 var endBlocks = this.editor.getSelection().getEndBlocks();
859 var ancestors = after ? HTMLArea.DOM.getBlockAncestors(endBlocks.end) : HTMLArea.DOM.getBlockAncestors(endBlocks.start);
860 var endElement = ancestors[ancestors.length-1];
861 for (var i = ancestors.length; --i >= 0;) {
862 if (/^(table|div|ul|ol|dl|blockquote|address|pre)$/i.test(ancestors[i].nodeName) && !/^(li)$/i.test(ancestors[i].parentNode.nodeName)) {
863 endElement = ancestors[i];
864 break;
865 }
866 }
867 if (endElement) {
868 var parent = endElement.parentNode;
869 var paragraph = this.editor.document.createElement("p");
870 if (Ext.isIE) {
871 paragraph.innerHTML = "&nbsp";
872 } else {
873 paragraph.appendChild(this.editor.document.createElement("br"));
874 }
875 if (after && !endElement.nextSibling) {
876 parent.appendChild(paragraph);
877 } else {
878 parent.insertBefore(paragraph, after ? endElement.nextSibling : endElement);
879 }
880 this.editor.getSelection().selectNodeContents(paragraph, true);
881 }
882 },
883 /*
884 * Insert horizontal line
885 */
886 insertHorizontalRule: function () {
887 this.editor.getSelection().execCommand('InsertHorizontalRule');
888 // Apply enterParagraphs rule
889 if (!Ext.isIE && !Ext.isOpera && !this.editor.config.disableEnterParagraphs) {
890 var range = this.editor.getSelection().createRange();
891 var startContainer = range.startContainer;
892 if (/^body$/i.test(startContainer.nodeName)) {
893 startContainer.normalize();
894 var ruler = startContainer.childNodes[range.startOffset-1];
895 if (ruler.nextSibling) {
896 if (ruler.nextSibling.nodeType === HTMLArea.DOM.TEXT_NODE) {
897 if (/\S/.test(ruler.nextSibling.textContent)) {
898 var paragraph = this.editor.document.createElement('p');
899 paragraph = startContainer.appendChild(paragraph);
900 paragraph = startContainer.insertBefore(paragraph, ruler.nextSibling);
901 paragraph.appendChild(ruler.nextSibling);
902 } else {
903 HTMLArea.DOM.removeFromParent(ruler.nextSibling);
904 var paragraph = ruler.nextSibling;
905 }
906 } else {
907 var paragraph = ruler.nextSibling;
908 }
909 // Cannot set the cursor on the hr element
910 if (/^hr$/i.test(paragraph.nodeName)) {
911 var inBetweenParagraph = this.editor.document.createElement('p');
912 inBetweenParagraph.innerHTML = '<br />';
913 paragraph = startContainer.insertBefore(inBetweenParagraph, paragraph);
914 }
915 } else {
916 var paragraph = this.editor.document.createElement('p');
917 if (Ext.isWebKit) {
918 paragraph.innerHTML = '<br />';
919 }
920 paragraph = startContainer.appendChild(paragraph);
921 }
922 this.editor.getSelection().selectNodeContents(paragraph, true);
923 }
924 }
925 },
926 /*
927 * This function gets called when the plugin is generated
928 */
929 onGenerate: function () {
930 // Register the enter key handler for IE when the cursor is at the end of a dt or a dd element
931 if (Ext.isIE) {
932 this.editor.iframe.keyMap.addBinding({
933 key: Ext.EventObject.ENTER,
934 shift: false,
935 handler: this.onKey,
936 scope: this
937 });
938 }
939 },
940 /*
941 * This function gets called when the enter key was pressed in IE
942 * It will process the enter key for IE when the cursor is at the end of a dt or a dd element
943 *
944 * @param string key: the key code
945 * @param object event: the Ext event object (keydown)
946 *
947 * @return boolean false, if the event was taken care of
948 */
949 onKey: function (key, event) {
950 if (this.editor.getSelection().isEmpty()) {
951 var range = this.editor.getSelection().createRange();
952 var parentElement = this.editor.getSelection().getParentElement();
953 while (parentElement && !HTMLArea.DOM.isBlockElement(parentElement)) {
954 parentElement = parentElement.parentNode;
955 }
956 if (/^(dt|dd)$/i.test(parentElement.nodeName)) {
957 var nodeRange = this.editor.getSelection().createRange();
958 nodeRange.moveToElementText(parentElement);
959 range.setEndPoint("EndToEnd", nodeRange);
960 if (!range.text || range.text == "\x20") {
961 var item = parentElement.parentNode.insertBefore(this.editor.document.createElement((parentElement.nodeName.toLowerCase() === "dt") ? "dd" : "dt"), parentElement.nextSibling);
962 item.innerHTML = "\x20";
963 this.editor.getSelection().selectNodeContents(item, true);
964 event.stopEvent();
965 return false;
966 }
967 } else if (/^(li)$/i.test(parentElement.nodeName)
968 && !parentElement.innerText
969 && parentElement.parentNode.parentNode
970 && /^(dd|td|th)$/i.test(parentElement.parentNode.parentNode.nodeName)) {
971 var item = parentElement.parentNode.parentNode.insertBefore(this.editor.document.createTextNode("\x20"), parentElement.parentNode.nextSibling);
972 this.editor.getSelection().selectNodeContents(parentElement.parentNode.parentNode, false);
973 parentElement.parentNode.removeChild(parentElement);
974 event.stopEvent();
975 return false;
976 }
977 }
978 return true;
979 },
980 /*
981 * This function removes any disallowed class or mutually exclusive classes from the class attribute of the node
982 */
983 cleanClasses: function (node) {
984 var classNames = node.className.trim().split(" ");
985 var nodeName = node.nodeName.toLowerCase();
986 for (var i = classNames.length; --i >= 0;) {
987 if (!HTMLArea.reservedClassNames.test(classNames[i])) {
988 if (this.tags && this.tags[nodeName] && this.tags[nodeName].allowedClasses) {
989 if (!this.tags[nodeName].allowedClasses.test(classNames[i])) {
990 HTMLArea.DOM.removeClass(node, classNames[i]);
991 }
992 } else if (this.tags && this.tags.all && this.tags.all.allowedClasses) {
993 if (!this.tags.all.allowedClasses.test(classNames[i])) {
994 HTMLArea.DOM.removeClass(node, classNames[i]);
995 }
996 }
997 if (this.formatBlockItems[nodeName] && this.formatBlockItems[nodeName].classList && this.formatBlockItems[nodeName].classList.test(classNames[i])) {
998 HTMLArea.DOM.removeClass(node, classNames[i]);
999 }
1000 }
1001 }
1002 },
1003 /*
1004 * This function gets called when the toolbar is updated
1005 */
1006 onUpdateToolbar: function (button, mode, selectionEmpty, ancestors, endPointsInSameBlock) {
1007 if (mode === 'wysiwyg' && this.editor.isEditable()) {
1008 var statusBarSelection = this.editor.statusBar ? this.editor.statusBar.getSelection() : null;
1009 var parentElement = statusBarSelection ? statusBarSelection : this.editor.getSelection().getParentElement();
1010 if (!/^body$/i.test(parentElement.nodeName)) {
1011 while (parentElement && !HTMLArea.DOM.isBlockElement(parentElement) || /^li$/i.test(parentElement.nodeName)) {
1012 parentElement = parentElement.parentNode;
1013 }
1014 var blockAncestors = HTMLArea.DOM.getBlockAncestors(parentElement);
1015 var endBlocks = this.editor.getSelection().getEndBlocks();
1016 var startAncestors = HTMLArea.DOM.getBlockAncestors(endBlocks.start);
1017 var endAncestors = HTMLArea.DOM.getBlockAncestors(endBlocks.end);
1018 var index = 0;
1019 while (index < startAncestors.length && index < endAncestors.length && startAncestors[index] === endAncestors[index]) {
1020 ++index;
1021 }
1022 if (endBlocks.start === endBlocks.end || !startAncestors[index]) {
1023 --index;
1024 }
1025 var commandState = false;
1026 switch (button.itemId) {
1027 case 'FormatBlock':
1028 this.updateDropDown(button, blockAncestors[blockAncestors.length-1], startAncestors[index]);
1029 break;
1030 case "Outdent" :
1031 if (this.useBlockquote) {
1032 for (var j = blockAncestors.length; --j >= 0;) {
1033 if (/^blockquote$/i.test(blockAncestors[j].nodeName)) {
1034 commandState = true;
1035 break;
1036 }
1037 }
1038 } else if (/^(ol|ul)$/i.test(parentElement.nodeName)) {
1039 commandState = true;
1040 } else {
1041 for (var j = blockAncestors.length; --j >= 0;) {
1042 if (HTMLArea.DOM.hasClass(blockAncestors[j], this.useClass.Indent) || /^(td|th)$/i.test(blockAncestors[j].nodeName)) {
1043 commandState = true;
1044 break;
1045 }
1046 }
1047 }
1048 button.setDisabled(!commandState);
1049 break;
1050 case "Indent" :
1051 break;
1052 case "InsertParagraphBefore" :
1053 case "InsertParagraphAfter" :
1054 button.setDisabled(/^(body)$/i.test(startAncestors[index].nodeName));
1055 break;
1056 case "Blockquote" :
1057 for (var j = blockAncestors.length; --j >= 0;) {
1058 if (/^blockquote$/i.test(blockAncestors[j].nodeName)) {
1059 commandState = true;
1060 break;
1061 }
1062 }
1063 button.setInactive(!commandState);
1064 break;
1065 case "JustifyLeft" :
1066 case "JustifyCenter" :
1067 case "JustifyRight" :
1068 case "JustifyFull" :
1069 if (this.useAlignAttribute) {
1070 try {
1071 commandState = this.editor.document.queryCommandState(button.itemId);
1072 } catch(e) {
1073 commandState = false;
1074 }
1075 } else {
1076 if (/^(body)$/i.test(startAncestors[index].nodeName)) {
1077 button.setDisabled(true);
1078 } else {
1079 button.setDisabled(false);
1080 commandState = true;
1081 for (var block = startAncestors[index]; block; block = block.nextSibling) {
1082 commandState = commandState && HTMLArea.DOM.hasClass(block, this.useClass[button.itemId]);
1083 if (block == endAncestors[index]) {
1084 break;
1085 }
1086 }
1087 }
1088 }
1089 button.setInactive(!commandState);
1090 break;
1091 case "InsertOrderedList":
1092 case "InsertUnorderedList":
1093 try {
1094 commandState = this.editor.document.queryCommandState(button.itemId);
1095 } catch(e) {
1096 commandState = false;
1097 }
1098 button.setInactive(!commandState);
1099 break;
1100 default :
1101 break;
1102 }
1103 } else {
1104 // The selection is not contained in any block
1105 switch (button.itemId) {
1106 case 'FormatBlock':
1107 this.updateDropDown(button);
1108 break;
1109 case 'Outdent' :
1110 button.setDisabled(true);
1111 break;
1112 case 'Indent' :
1113 break;
1114 case 'InsertParagraphBefore' :
1115 case 'InsertParagraphAfter' :
1116 button.setDisabled(true);
1117 break;
1118 case 'Blockquote' :
1119 button.setInactive(true);
1120 break;
1121 case 'JustifyLeft' :
1122 case 'JustifyCenter' :
1123 case 'JustifyRight' :
1124 case 'JustifyFull' :
1125 button.setInactive(true);
1126 button.setDisabled(true);
1127 break;
1128 case 'InsertOrderedList':
1129 case 'InsertUnorderedList':
1130 button.setInactive(true);
1131 break;
1132 default :
1133 break;
1134 }
1135 }
1136 }
1137 },
1138 /*
1139 * This function updates the drop-down list of block elements
1140 */
1141 updateDropDown: function(select, deepestBlockAncestor, startAncestor) {
1142 var store = select.getStore();
1143 store.removeAt(0);
1144 var index = -1;
1145 if (deepestBlockAncestor) {
1146 var nodeName = deepestBlockAncestor.nodeName.toLowerCase();
1147 // Could be a custom item ...
1148 index = store.findBy(function(record, id) {
1149 var item = this.formatBlockItems[record.get('value')];
1150 return item && item.tagName == nodeName && item.addClass && HTMLArea.DOM.hasClass(deepestBlockAncestor, item.addClass);
1151 }, this);
1152 if (index == -1) {
1153 // ... or a standard one
1154 index = store.findExact('value', nodeName);
1155 }
1156 }
1157 if (index == -1) {
1158 store.insert(0, new store.recordType({
1159 text: this.localize('No block'),
1160 value: 'none'
1161 }));
1162 select.setValue('none');
1163 } else {
1164 store.insert(0, new store.recordType({
1165 text: this.localize('Remove block'),
1166 value: 'none'
1167 }));
1168 select.setValue(store.getAt(index+1).get('value'));
1169 }
1170 },
1171 /*
1172 * This function handles the hotkey events registered on elements of the dropdown list
1173 */
1174 onHotKey: function(editor, key) {
1175 var blockElement;
1176 var hotKeyConfiguration = this.getHotKeyConfiguration(key);
1177 if (hotKeyConfiguration) {
1178 var blockElement = hotKeyConfiguration.element;
1179 }
1180 if (blockElement && this.isAllowedBlockElement(blockElement)) {
1181 this.applyBlockElement(this.translateHotKey(key), blockElement);
1182 return false;
1183 }
1184 return true;
1185 }
1186 });