f32f3aa5a7a75074c43c8713b172988782f6a59b
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / htmlarea / plugins / BlockElements / block-elements.js
1 /***************************************************************
2 * Copyright notice
3 *
4 * (c) 2007-2008 Stanislas Rolland <stanislas.rolland(arobas)fructifor.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 * TYPO3 SVN ID: $Id$
31 */
32 BlockElements = HTMLArea.Plugin.extend({
33
34 constructor : function(editor, pluginName) {
35 this.base(editor, pluginName);
36 },
37
38 /*
39 * This function gets called by the class constructor
40 */
41 configurePlugin : function (editor) {
42
43 /*
44 * Setting up some properties from PageTSConfig
45 */
46 this.useClass = {
47 Indent : "indent",
48 JustifyLeft : "align-left",
49 JustifyCenter : "align-center",
50 JustifyRight : "align-right",
51 JustifyFull : "align-justify"
52 };
53 this.useAlignAttribute = false;
54 for (var buttonId in this.useClass ) {
55 if (this.useClass.hasOwnProperty(buttonId)) {
56 if (this.editorConfiguration.buttons[buttonId.toLowerCase()]) {
57 this.useClass[buttonId] = this.editorConfiguration.buttons[buttonId.toLowerCase()].useClass ? this.editorConfiguration.buttons[buttonId.toLowerCase()].useClass : this.useClass[buttonId];
58 if (buttonId === "Indent") {
59 this.useBlockquote = this.editorConfiguration.buttons.indent.useBlockquote ? this.editorConfiguration.buttons.indent.useBlockquote : false;
60 } else {
61 if (this.editorConfiguration.buttons[buttonId.toLowerCase()].useAlignAttribute) {
62 this.useAlignAttribute = true;
63 }
64 }
65 }
66 }
67 }
68 this.allowedAttributes = new Array("id", "title", "lang", "xml:lang", "dir", (HTMLArea.is_gecko?"class":"className"));
69 this.indentedList = null;
70
71 /*
72 * Registering plugin "About" information
73 */
74 var pluginInformation = {
75 version : "1.0",
76 developer : "Stanislas Rolland",
77 developerUrl : "http://www.fructifor.ca/",
78 copyrightOwner : "Stanislas Rolland",
79 sponsor : this.localize("Technische Universitat Ilmenau"),
80 sponsorUrl : "http://www.tu-ilmenau.de/",
81 license : "GPL"
82 };
83 this.registerPluginInformation(pluginInformation);
84
85 /*
86 * Registering the dropdown list
87 */
88 var buttonId = "FormatBlock";
89 var dropDownConfiguration = {
90 id : buttonId,
91 tooltip : this.localize(buttonId + "-Tooltip"),
92 options : ((this.editorConfiguration.buttons[buttonId.toLowerCase()] && this.editorConfiguration.buttons[buttonId.toLowerCase()].dropDownOptions) ? this.editorConfiguration.buttons[buttonId.toLowerCase()].dropDownOptions : null),
93 action : "onChange",
94 refresh : null,
95 context : null
96 };
97 this.registerDropDown(dropDownConfiguration);
98
99 /*
100 * Establishing the list of allowed block elements
101 */
102 var blockElements = new Array();
103 for (var option in dropDownConfiguration.options) {
104 if (dropDownConfiguration.options.hasOwnProperty(option)) {
105 blockElements.push(dropDownConfiguration.options[option]);
106 }
107 }
108 this.allowedBlockElements = new RegExp( "^(" + blockElements.join("|") + ")$", "i");
109
110 /*
111 * Registering hot keys
112 */
113 for (var i = 0; i < blockElements.length; ++i) {
114 var configuredHotKey = this.defaultHotKeys[blockElements[i]];
115 if (this.editorConfiguration.buttons.formatblock
116 && this.editorConfiguration.buttons.formatblock.items
117 && this.editorConfiguration.buttons.formatblock.items[blockElements[i]]
118 && this.editorConfiguration.buttons.formatblock.items[blockElements[i]].hotKey) {
119 configuredHotKey = this.editorConfiguration.buttons.formatblock.items[blockElements[i]].hotKey;
120 }
121 if (configuredHotKey) {
122 var hotKeyConfiguration = {
123 id : configuredHotKey,
124 cmd : buttonId,
125 action : "onHotKey",
126 element : blockElements[i]
127 };
128 this.registerHotKey(hotKeyConfiguration);
129 }
130 }
131
132 /*
133 * Registering the buttons
134 */
135 var buttonList = this.buttonList;
136 for (var i = 0; i < buttonList.length; ++i) {
137 var button = buttonList[i];
138 buttonId = button[0];
139 var buttonConfiguration = {
140 id : buttonId,
141 tooltip : this.localize(buttonId + "-Tooltip"),
142 action : "onButtonPress",
143 context : button[1],
144 hotKey : button[2]
145 };
146 this.registerButton(buttonConfiguration);
147 }
148
149 return true;
150 },
151
152 /*
153 * The list of buttons added by this plugin
154 */
155 buttonList : [
156 ["Indent", null, "TAB"],
157 ["Outdent", null, "SHIFT-TAB"],
158 ["Blockquote", null, null],
159 ["InsertParagraphBefore", null, null],
160 ["InsertParagraphAfter", null, null],
161 ["JustifyLeft", null, "l"],
162 ["JustifyCenter", null, "e"],
163 ["JustifyRight", null, "r"],
164 ["JustifyFull", null, "j"]
165 ],
166
167 /*
168 * The list of hotkeys associated with block elements and registered by default by this plugin
169 */
170 defaultHotKeys : {
171 "p" : "n",
172 "h1" : "1",
173 "h2" : "2",
174 "h3" : "3",
175 "h4" : "4",
176 "h5" : "5",
177 "h6" : "6"
178 },
179
180 /*
181 * This function gets called when some block element was selected in the drop-down list
182 */
183 onChange : function (editor, buttonId) {
184 var tbobj = this.editor._toolbarObjects[buttonId];
185 var blockElement = document.getElementById(tbobj.elementId).value;
186 this.applyBlockElement(buttonId, blockElement);
187 },
188
189 applyBlockElement : function(buttonId, blockElement) {
190 switch (blockElement) {
191 case "blockquote" :
192 this.onButtonPress(this.editor, "Blockquote");
193 break;
194 case "div" :
195 case "address" :
196 case "none" :
197 this.onButtonPress(this.editor, blockElement);
198 break;
199 default :
200 var element = blockElement;
201 if (HTMLArea.is_ie) {
202 element = "<" + element + ">";
203 }
204 this.editor.focusEditor();
205 if (HTMLArea.is_safari && !this.editor._doc.body.hasChildNodes()) {
206 this.editor._doc.body.appendChild((this.editor._doc.createElement("br")));
207 }
208 try {
209 this.editor._doc.execCommand(buttonId, false, element);
210 } catch(e) {
211 this.appendToLog("applyBlockElement", e + "\n\nby execCommand(" + buttonId + ");");
212 }
213 }
214 },
215
216 /*
217 * This function gets called when a button was pressed.
218 *
219 * @param object editor: the editor instance
220 * @param string id: the button id or the key
221 * @param object target: the target element of the contextmenu event, when invoked from the context menu
222 *
223 * @return boolean false if action is completed
224 */
225 onButtonPress : function (editor, id, target) {
226 // Could be a button or its hotkey
227 var buttonId = this.translateHotKey(id);
228 buttonId = buttonId ? buttonId : id;
229 this.editor.focusEditor();
230 var selection = editor._getSelection();
231 var range = editor._createRange(selection);
232 var parentElement = this.editor._statusBarTree.selected ? this.editor._statusBarTree.selected : this.editor.getParentElement(selection, range);
233 if (typeof(target) !== "undefined") {
234 parentElement = target;
235 }
236 while (parentElement && (!HTMLArea.isBlockElement(parentElement) || /^li$/i.test(parentElement.nodeName))) {
237 parentElement = parentElement.parentNode;
238 }
239 var blockAncestors = this.getBlockAncestors(parentElement);
240 var tableCell = null;
241
242 if (id === "TAB" || id === "SHIFT-TAB") {
243 for (var i = blockAncestors.length; --i >= 0;) {
244 if (/^(td|th)$/i.test(blockAncestors[i].nodeName)) {
245 tableCell = blockAncestors[i];
246 break;
247 }
248 }
249 }
250 var fullNodeTextSelected = (HTMLArea.is_gecko && parentElement.textContent === range.toString()) || (HTMLArea.is_ie && parentElement.innerText === range.text);
251
252 switch (buttonId) {
253 case "Indent" :
254 if (/^(ol|ul)$/i.test(parentElement.nodeName) && !(fullNodeTextSelected && !/^(li)$/i.test(parentElement.parentNode.nodeName))) {
255 try {
256 this.editor._doc.execCommand(buttonId, false, null);
257 } catch(e) {
258 this.appendToLog("onButtonPress", e + "\n\nby execCommand(" + buttonId + ");");
259 }
260 this.indentedList = parentElement;
261 this.makeNestedList(parentElement);
262 this.editor.selectNodeContents(this.indentedList.lastChild, false);
263 } else if (tableCell) {
264 var nextCell = tableCell.nextSibling ? tableCell.nextSibling : (tableCell.parentNode.nextSibling ? tableCell.parentNode.nextSibling.firstChild : null);
265 if (!nextCell) {
266 nextCell = tableCell.parentNode.parentNode.firstChild.firstChild;
267 }
268 if (nextCell) {
269 this.editor.selectNodeContents(nextCell, true);
270 }
271 } else if (this.useBlockquote) {
272 try {
273 this.editor._doc.execCommand(buttonId, false, null);
274 } catch(e) {
275 this.appendToLog("onButtonPress", e + "\n\nby execCommand(" + buttonId + ");");
276 }
277 } else if (this.allowedBlockElements.test("div")) {
278 if (/^div$/i.test(parentElement.nodeName) && !HTMLArea._hasClass(parentElement, this.useClass[buttonId])) {
279 HTMLArea._addClass(parentElement, this.useClass[buttonId]);
280 } else if (!/^div$/i.test(parentElement.nodeName) && /^div$/i.test(parentElement.parentNode.nodeName) && !HTMLArea._hasClass(parentElement.parentNode, this.useClass[buttonId])) {
281 HTMLArea._addClass(parentElement.parentNode, this.useClass[buttonId]);
282 } else {
283 var bookmark = this.editor.getBookmark(range);
284 var newBlock = this.wrapSelectionInBlockElement("div", this.useClass[buttonId]);
285 var range = this.editor.moveToBookmark(bookmark);
286 this.editor.selectRange(range);
287 }
288 } else {
289 this.addClassOnBlockElements(buttonId);
290 }
291 break;
292 case "Outdent" :
293 if (/^(ol|ul)$/i.test(parentElement.nodeName) && !HTMLArea._hasClass(parentElement, this.useClass.Indent)) {
294 try {
295 this.editor._doc.execCommand(buttonId, false, null);
296 } catch(e) {
297 this.appendToLog("onButtonPress", e + "\n\nby execCommand(" + buttonId + ");");
298 }
299 } else if (tableCell) {
300 var previousCell = tableCell.previousSibling ? tableCell.previousSibling : (tableCell.parentNode.previousSibling ? tableCell.parentNode.previousSibling.lastChild : null);
301 if (!previousCell) {
302 previousCell = tableCell.parentNode.parentNode.lastChild.lastChild;
303 }
304 if (previousCell) {
305 this.editor.selectNodeContents(previousCell, true);
306 }
307 } else if (this.useBlockquote) {
308 try {
309 this.editor._doc.execCommand(buttonId, false, null);
310 } catch(e) {
311 this.appendToLog("onButtonPress", e + "\n\nby execCommand(" + buttonId + ");");
312 }
313 } else if (this.allowedBlockElements.test("div")) {
314 for (var i = blockAncestors.length; --i >= 0;) {
315 if (HTMLArea._hasClass(blockAncestors[i], this.useClass.Indent)) {
316 var bookmark = this.editor.getBookmark(range);
317 var newBlock = this.wrapSelectionInBlockElement("div", false, blockAncestors[i]);
318 // If not directly under the div, we need to backtrack
319 if (newBlock.parentNode !== blockAncestors[i]) {
320 var parent = newBlock.parentNode;
321 this.removeElement(newBlock);
322 while (parent.parentNode !== blockAncestors[i]) {
323 parent = parent.parentNode;
324 }
325 blockAncestors[i].insertBefore(newBlock, parent);
326 newBlock.appendChild(parent);
327 }
328 newBlock.className = blockAncestors[i].className;
329 HTMLArea._removeClass(newBlock, this.useClass.Indent);
330 if (!newBlock.previousSibling) {
331 while (newBlock.hasChildNodes()) {
332 if (newBlock.firstChild.nodeType == 1) {
333 newBlock.firstChild.className = newBlock.className;
334 }
335 blockAncestors[i].parentNode.insertBefore(newBlock.firstChild, blockAncestors[i]);
336 }
337 } else if (!newBlock.nextSibling) {
338 if (blockAncestors[i].nextSibling) {
339 while (newBlock.hasChildNodes()) {
340 if (newBlock.firstChild.nodeType == 1) {
341 newBlock.lastChild.className = newBlock.className;
342 }
343 blockAncestors[i].parentNode.insertBefore(newBlock.lastChild, blockAncestors[i].nextSibling);
344 }
345 } else {
346 while (newBlock.hasChildNodes()) {
347 if (newBlock.firstChild.nodeType == 1) {
348 newBlock.firstChild.className = newBlock.className;
349 }
350 blockAncestors[i].parentNode.appendChild(newBlock.firstChild);
351 }
352 }
353 } else {
354 var clone = blockAncestors[i].cloneNode(false);
355 if (blockAncestors[i].nextSibling) {
356 blockAncestors[i].parentNode.insertBefore(clone, blockAncestors[i].nextSibling);
357 } else {
358 blockAncestors[i].parentNode.appendChild(clone);
359 }
360 while (newBlock.nextSibling) {
361 clone.appendChild(newBlock.nextSibling);
362 }
363 while (newBlock.hasChildNodes()) {
364 if (newBlock.firstChild.nodeType == 1) {
365 newBlock.firstChild.className = newBlock.className;
366 }
367 blockAncestors[i].parentNode.insertBefore(newBlock.firstChild, clone);
368 }
369 }
370 blockAncestors[i].removeChild(newBlock);
371 if (!blockAncestors[i].hasChildNodes()) {
372 blockAncestors[i].parentNode.removeChild(blockAncestors[i]);
373 }
374 var range = this.editor.moveToBookmark(bookmark);
375 this.editor.selectRange(range);
376 break;
377 }
378 }
379 } else {
380 this.addClassOnBlockElements(buttonId);
381 }
382 break;
383 case "InsertParagraphBefore" :
384 case "InsertParagraphAfter" :
385 this.insertParagraph(buttonId === "InsertParagraphAfter");
386 break;
387 case "Blockquote" :
388 var commandState = false;
389 for (var i = blockAncestors.length; --i >= 0;) {
390 if (/^blockquote$/i.test(blockAncestors[i].nodeName)) {
391 commandState = true;
392 this.removeElement(blockAncestors[i]);
393 break;
394 }
395 }
396 if (!commandState) {
397 var bookmark = this.editor.getBookmark(range);
398 var newBlock = this.wrapSelectionInBlockElement("blockquote", null);
399 var range = this.editor.moveToBookmark(bookmark);
400 this.editor.selectRange(range);
401 }
402 break;
403 case "address" :
404 case "div" :
405 var bookmark = this.editor.getBookmark(range);
406 var newBlock = this.wrapSelectionInBlockElement(buttonId, null);
407 var range = this.editor.moveToBookmark(bookmark);
408 this.editor.selectRange(range);
409 break;
410 case "JustifyLeft" :
411 case "JustifyCenter" :
412 case "JustifyRight" :
413 case "JustifyFull" :
414 if (this.useAlignAttribute) {
415 try {
416 this.editor._doc.execCommand(buttonId, false, null);
417 } catch(e) {
418 this.appendToLog("onButtonPress", e + "\n\nby execCommand(" + buttonId + ");");
419 }
420 } else {
421 this.addClassOnBlockElements(buttonId);
422 }
423 break;
424 case "none" :
425 if (this.allowedBlockElements.test(parentElement.nodeName)) {
426 this.removeElement(parentElement);
427 }
428 break;
429 default :
430 break;
431 }
432 return false;
433 },
434
435 /*
436 * Get the block ancestors of an element within a given block
437 */
438 getBlockAncestors : function(element, withinBlock) {
439 var ancestors = new Array();
440 var ancestor = element;
441 while (ancestor && (ancestor.nodeType === 1) && !/^(body)$/i.test(ancestor.nodeName) && ancestor != withinBlock) {
442 if (HTMLArea.isBlockElement(ancestor)) {
443 ancestors.unshift(ancestor);
444 }
445 ancestor = ancestor.parentNode;
446 }
447 ancestors.unshift(ancestor);
448 return ancestors;
449 },
450
451 /*
452 * This function wraps the block elements intersecting the current selection in a block element of the given type
453 */
454 wrapSelectionInBlockElement : function(blockName, useClass, withinBlock) {
455 var endBlocks = this.editor.getEndBlocks(this.editor._getSelection());
456 var startAncestors = this.getBlockAncestors(endBlocks.start, withinBlock);
457 var endAncestors = this.getBlockAncestors(endBlocks.end, withinBlock);
458 var i = 0;
459 while (i < startAncestors.length && i < endAncestors.length && startAncestors[i] === endAncestors[i]) {
460 ++i;
461 }
462
463 if ((endBlocks.start === endBlocks.end && /^(body)$/i.test(endBlocks.start.nodeName)) || !startAncestors[i] || !endAncestors[i]) {
464 --i;
465 }
466 var blockElement = this.editor._doc.createElement(blockName);
467 if (useClass) {
468 HTMLArea._addClass(blockElement, useClass);
469 }
470 var contextElement = endAncestors[0];
471 if (i) {
472 contextElement = endAncestors[i-1];
473 }
474 var nextElement = endAncestors[i].nextSibling;
475 var block = startAncestors[i], sibling;
476 if (!/^(body|td|th)$/i.test(block.nodeName) && block != withinBlock) {
477 while (block && block != nextElement) {
478 sibling = block.nextSibling;
479 blockElement.appendChild(block);
480 block = sibling;
481 }
482 if (nextElement) {
483 blockElement = nextElement.parentNode.insertBefore(blockElement, nextElement);
484 } else {
485 blockElement = contextElement.appendChild(blockElement);
486 }
487 } else {
488 contextElement = block;
489 block = block.firstChild;
490 while (block) {
491 sibling = block.nextSibling;
492 blockElement.appendChild(block);
493 block = sibling;
494 }
495 blockElement = contextElement.appendChild(blockElement);
496 }
497 // Things go wrong in some browsers when the node is empty
498 if (HTMLArea.is_safari && !blockElement.hasChildNodes()) {
499 blockElement = blockElement.appendChild(this.editor._doc.createElement("br"));
500 }
501 return blockElement;
502 },
503
504 /*
505 * This function adds a class attribute on blocks sibling of the block containing the start container of the selection
506 */
507 addClassOnBlockElements : function(buttonId) {
508 var selection = this.editor._getSelection();
509 var endBlocks = this.editor.getEndBlocks(selection);
510 var startAncestors = this.getBlockAncestors(endBlocks.start);
511 var endAncestors = this.getBlockAncestors(endBlocks.end);
512 var index = 0;
513 while (index < startAncestors.length && index < endAncestors.length && startAncestors[index] === endAncestors[index]) {
514 ++index;
515 }
516 if (endBlocks.start === endBlocks.end) {
517 --index;
518 }
519 for (var block = startAncestors[index]; block; block = block.nextSibling) {
520 if (HTMLArea.isBlockElement(block)) {
521 switch (buttonId) {
522 case "Indent" :
523 if (!HTMLArea._hasClass(block, this.useClass[buttonId])) {
524 HTMLArea._addClass(block, this.useClass[buttonId]);
525 }
526 break;
527 case "Outdent" :
528 if (HTMLArea._hasClass(block, this.useClass["Indent"])) {
529 HTMLArea._removeClass(block, this.useClass["Indent"]);
530 }
531 break;
532 case "JustifyLeft" :
533 case "JustifyCenter" :
534 case "JustifyRight" :
535 case "JustifyFull" :
536 this.toggleAlignmentClass(block, buttonId);
537 break;
538 default :
539 break;
540 }
541 }
542 if (block == endAncestors[index]) {
543 break;
544 }
545 }
546 },
547
548 /*
549 * This function toggles the alignment class on the given block
550 */
551 toggleAlignmentClass : function(block, buttonId) {
552 for (var alignmentButtonId in this.useClass) {
553 if (this.useClass.hasOwnProperty(alignmentButtonId) && alignmentButtonId !== "Indent") {
554 if (HTMLArea._hasClass(block, this.useClass[alignmentButtonId])) {
555 HTMLArea._removeClass(block, this.useClass[alignmentButtonId]);
556 } else if (alignmentButtonId === buttonId) {
557 HTMLArea._addClass(block, this.useClass[alignmentButtonId]);
558 }
559 }
560 }
561 if (/^div$/i.test(block.nodeName) && !this.hasAllowedAttributes(block)) {
562 this.removeElement(block);
563 }
564 },
565
566 /*
567 * This function verifies if the element has any of the allowed attributes
568 */
569 hasAllowedAttributes : function(element) {
570 for (var i = 0; i < this.allowedAttributes.length; ++i) {
571 if (element.getAttribute(this.allowedAttributes[i])) {
572 return true;
573 }
574 }
575 return false;
576 },
577
578 /*
579 * This function removes the given element but keeps its contents
580 */
581 removeElement : function(element) {
582 var selection = this.editor._getSelection();
583 var range = this.editor._createRange(selection);
584 var lastChild;
585 var bookmark = this.editor.getBookmark(range);
586 var parent = element.parentNode;
587 while (element.firstChild) {
588 lastChild = parent.insertBefore(element.firstChild, element);
589 }
590 parent.removeChild(element);
591 var range = this.editor.moveToBookmark(bookmark);
592 this.editor.selectRange(range);
593 },
594
595 /*
596 * Make XHTML-compliant nested list
597 */
598 makeNestedList : function(el) {
599 var previous;
600 for (var i = el.firstChild; i; i = i.nextSibling) {
601 if (/^li$/i.test(i.nodeName)) {
602 if (HTMLArea.is_safari) {
603 if (i.hasChildNodes() && /^span$/i.test(i.firstChild.nodeName)) {
604 this.removeElement(i.firstChild);
605 }
606 }
607 for (var j = i.firstChild; j; j = j.nextSibling) {
608 if (/^(ol|ul)$/i.test(j.nodeName)) {
609 this.makeNestedList(j);
610 }
611 }
612 } else if (/^(ol|ul)$/i.test(i.nodeName)) {
613 previous = i.previousSibling;
614 this.indentedList = i.cloneNode(true);
615 if (!previous) {
616 previous = el.insertBefore(this.editor._doc.createElement("li"), i);
617 this.indentedList = previous.appendChild(this.indentedList);
618 } else {
619 this.indentedList = previous.appendChild(this.indentedList);
620 }
621 HTMLArea.removeFromParent(i);
622 this.makeNestedList(el);
623 break;
624 }
625 }
626 },
627
628 /*
629 * Insert a paragraph
630 */
631 insertParagraph : function(after) {
632 var endBlocks = this.editor.getEndBlocks(this.editor._getSelection());
633 var ancestors = after ? this.getBlockAncestors(endBlocks.end) : this.getBlockAncestors(endBlocks.start);
634 var endElement = ancestors[ancestors.length-1];
635 for (var i = ancestors.length; --i >= 0;) {
636 if (/^(table|div|ul|ol|dl|blockquote|address|pre)$/i.test(ancestors[i].nodeName) && !/^(li)$/i.test(ancestors[i].parentNode.nodeName)) {
637 endElement = ancestors[i];
638 break;
639 }
640 }
641 if (endElement) {
642 var parent = endElement.parentNode;
643 var paragraph = this.editor._doc.createElement("p");
644 paragraph.appendChild(this.editor._doc.createElement("br"));
645 if (HTMLArea.is_opera) paragraph.innerHTML = "&nbsp";
646 parent.insertBefore(paragraph, after ? endElement.nextSibling : endElement);
647 if (HTMLArea.is_opera) {
648 this.editor.selectNodeContents(paragraph);
649 } else {
650 this.editor.selectNodeContents(paragraph, true);
651 }
652 }
653 },
654
655 /*
656 * This function gets called when the toolbar is updated
657 */
658 onUpdateToolbar : function () {
659 if (this.editor.getMode() === "textmode" || this.editor._doc.designMode !== "on") {
660 return false;
661 }
662 var parentElement = this.editor._statusBarTree.selected ? this.editor._statusBarTree.selected : this.editor.getParentElement();
663 while (!HTMLArea.isBlockElement(parentElement) || /^li$/i.test(parentElement.nodeName)) {
664 parentElement = parentElement.parentNode;
665 }
666 var blockAncestors = this.getBlockAncestors(parentElement);
667
668 var selection = this.editor._getSelection();
669 var endBlocks = this.editor.getEndBlocks(selection);
670 var startAncestors = this.getBlockAncestors(endBlocks.start);
671 var endAncestors = this.getBlockAncestors(endBlocks.end);
672 var index = 0;
673 while (index < startAncestors.length && index < endAncestors.length && startAncestors[index] === endAncestors[index]) {
674 ++index;
675 }
676 if (endBlocks.start === endBlocks.end || !startAncestors[index]) {
677 --index;
678 }
679 var dropDownConfiguration = this.getDropDownConfiguration("FormatBlock");
680 if ((typeof(dropDownConfiguration) !== "undefined") && this.isButtonInToolbar(dropDownConfiguration.id)) {
681 this.updateDropDown(dropDownConfiguration, blockAncestors[blockAncestors.length-1], startAncestors[index]);
682 }
683
684 var buttonList = this.buttonList;
685 var buttonId, n = buttonList.length, commandState;
686 for (var i = 0; i < n; ++i) {
687 buttonId = buttonList[i][0];
688 commandState = false;
689 if (this.isButtonInToolbar(buttonId)) {
690 switch (buttonId) {
691 case "Outdent" :
692 if (this.useBlockquote) {
693 for (var j = blockAncestors.length; --j >= 0;) {
694 if (/^blockquote$/i.test(blockAncestors[j].nodeName)) {
695 commandState = true;
696 break;
697 }
698 }
699 } else if (/^(ol|ul)$/i.test(parentElement.nodeName)) {
700 commandState = true;
701 } else {
702 for (var j = blockAncestors.length; --j >= 0;) {
703 if (HTMLArea._hasClass(blockAncestors[j], this.useClass.Indent)) {
704 commandState = true;
705 break;
706 }
707 }
708 }
709 this.editor._toolbarObjects[buttonId].state("enabled", commandState);
710 break;
711 case "Indent" :
712 break;
713 case "InsertParagraphBefore" :
714 case "InsertParagraphAfter" :
715 this.editor._toolbarObjects[buttonId].state("enabled", !(/^(body)$/i.test(startAncestors[index].nodeName)));
716 break;
717 case "Blockquote" :
718 for (var j = blockAncestors.length; --j >= 0;) {
719 if (/^blockquote$/i.test(blockAncestors[j].nodeName)) {
720 commandState = true;
721 break;
722 }
723 }
724 this.editor._toolbarObjects[buttonId].state("active", commandState);
725 break;
726 case "JustifyLeft" :
727 case "JustifyCenter" :
728 case "JustifyRight" :
729 case "JustifyFull" :
730 if (this.useAlignAttribute) {
731 try {
732 commandState = this.editor._doc.queryCommandState(buttonId);
733 } catch(e) {
734 commandState = false;
735 }
736 } else {
737 if (/^(body)$/i.test(startAncestors[index].nodeName)) {
738 this.editor._toolbarObjects[buttonId].state("enabled", false);
739 } else {
740 this.editor._toolbarObjects[buttonId].state("enabled", true);
741 commandState = true;
742 for (var block = startAncestors[index]; block; block = block.nextSibling) {
743 commandState = commandState && HTMLArea._hasClass(block, this.useClass[buttonId]);
744 if (block == endAncestors[index]) {
745 break;
746 }
747 }
748 }
749 }
750 this.editor._toolbarObjects[buttonId].state("active", commandState);
751 break;
752 default :
753 break;
754 }
755 }
756 }
757 },
758
759 /*
760 * This function updates the drop-down list of block elemenents
761 */
762 updateDropDown : function(dropDownConfiguration, deepestBlockAncestor, startAncestor) {
763
764 var select = document.getElementById(this.editor._toolbarObjects[dropDownConfiguration.id].elementId);
765 var options = select.options;
766 for (var i = options.length; --i >= 0;) {
767 options[i].selected = false;
768 }
769 select.selectedIndex = 0;
770 options[0].selected = true;
771 options[0].style.display = "list-item";
772 options[0].text = this.localize("No block");
773
774 if (deepestBlockAncestor) {
775 if (!/^(body|div|address|blockquote)$/i.test(deepestBlockAncestor.nodeName) && deepestBlockAncestor.parentNode && !/^(li|td|th)$/i.test(deepestBlockAncestor.parentNode.nodeName)) {
776 options[0].style.display = "none";
777 }
778 var nodeName = deepestBlockAncestor.nodeName.toLowerCase();
779 for (i = options.length; --i >= 0;) {
780 if (nodeName === options[i].value.toLowerCase()) {
781 options[i].selected = true;
782 select.selectedIndex = i;
783 options[0].text = this.localize("Remove block");
784 break;
785 }
786 }
787 }
788 },
789
790 /*
791 * This function handles the hotkey events registered on elements of the dropdown list
792 */
793 onHotKey : function(editor, key) {
794 var blockElement;
795 var hotKeyConfiguration = this.getHotKeyConfiguration(key);
796 if (hotKeyConfiguration) {
797 var blockElement = hotKeyConfiguration.element;
798 }
799 if (blockElement && this.allowedBlockElements.test(blockElement)) {
800 this.applyBlockElement(this.translateHotKey(key), blockElement);
801 return false;
802 }
803 return true;
804 }
805 });
806