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