babc5c18fb24cf11758af53141f82b7073667823
1 /***************************************************************
4 * (c) 2007-2008 Stanislas Rolland <typo3(arobas)sjbr.ca>
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.
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.
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.
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
28 * BlockElements Plugin for TYPO3 htmlArea RTE
30 * TYPO3 SVN ID: $Id: block-elements.js $
32 BlockElements
= HTMLArea
.Plugin
.extend({
34 constructor : function(editor
, pluginName
) {
35 this.base(editor
, pluginName
);
39 * This function gets called by the class constructor
41 configurePlugin : function (editor
) {
44 * Setting up some properties from PageTSConfig
46 this.buttonsConfiguration
= this.editorConfiguration
.buttons
;
47 if (this.buttonsConfiguration
.blockstyle
) {
48 this.tags
= this.editorConfiguration
.buttons
.blockstyle
.tags
;
52 JustifyLeft
: "align-left",
53 JustifyCenter
: "align-center",
54 JustifyRight
: "align-right",
55 JustifyFull
: "align-justify"
57 this.useAlignAttribute
= false;
58 for (var buttonId
in this.useClass
) {
59 if (this.useClass
.hasOwnProperty(buttonId
)) {
60 if (this.editorConfiguration
.buttons
[this.buttonList
[buttonId
][2]]) {
61 this.useClass
[buttonId
] = this.editorConfiguration
.buttons
[this.buttonList
[buttonId
][2]].useClass
? this.editorConfiguration
.buttons
[this.buttonList
[buttonId
][2]].useClass
: this.useClass
[buttonId
];
62 if (buttonId
=== "Indent") {
63 this.useBlockquote
= this.editorConfiguration
.buttons
.indent
.useBlockquote
? this.editorConfiguration
.buttons
.indent
.useBlockquote
: false;
65 if (this.editorConfiguration
.buttons
[this.buttonList
[buttonId
][2]].useAlignAttribute
) {
66 this.useAlignAttribute
= true;
72 this.allowedAttributes
= new Array("id", "title", "lang", "xml:lang", "dir", (HTMLArea
.is_gecko
?"class":"className"));
73 this.indentedList
= null;
76 * Registering plugin "About" information
78 var pluginInformation
= {
80 developer
: "Stanislas Rolland",
81 developerUrl
: "http://www.sjbr.ca/",
82 copyrightOwner
: "Stanislas Rolland",
83 sponsor
: this.localize("Technische Universitat Ilmenau"),
84 sponsorUrl
: "http://www.tu-ilmenau.de/",
87 this.registerPluginInformation(pluginInformation
);
90 * Registering the dropdown list
92 var buttonId
= "FormatBlock";
93 var dropDownConfiguration
= {
95 tooltip
: this.localize(buttonId
+ "-Tooltip"),
96 options
: ((this.editorConfiguration
.buttons
.formatblock
&& this.editorConfiguration
.buttons
.formatblock
.dropDownOptions
) ? this.editorConfiguration
.buttons
.formatblock
.dropDownOptions
: null),
101 this.registerDropDown(dropDownConfiguration
);
104 * Establishing the list of allowed block elements
106 var blockElements
= new Array();
107 for (var option
in dropDownConfiguration
.options
) {
108 if (dropDownConfiguration
.options
.hasOwnProperty(option
)) {
109 blockElements
.push(dropDownConfiguration
.options
[option
]);
112 this.allowedBlockElements
= new RegExp( "^(" + blockElements
.join("|") + ")$", "i");
115 * Registering hot keys
117 for (var i
= 0; i
< blockElements
.length
; ++i
) {
118 var configuredHotKey
= this.defaultHotKeys
[blockElements
[i
]];
119 if (this.editorConfiguration
.buttons
.formatblock
120 && this.editorConfiguration
.buttons
.formatblock
.items
121 && this.editorConfiguration
.buttons
.formatblock
.items
[blockElements
[i
]]
122 && this.editorConfiguration
.buttons
.formatblock
.items
[blockElements
[i
]].hotKey
) {
123 configuredHotKey
= this.editorConfiguration
.buttons
.formatblock
.items
[blockElements
[i
]].hotKey
;
125 if (configuredHotKey
) {
126 var hotKeyConfiguration
= {
127 id
: configuredHotKey
,
130 element
: blockElements
[i
]
132 this.registerHotKey(hotKeyConfiguration
);
137 * Registering the buttons
139 for (var buttonId
in this.buttonList
) {
140 if (this.buttonList
.hasOwnProperty(buttonId
)) {
141 var button
= this.buttonList
[buttonId
];
142 var buttonConfiguration
= {
144 tooltip
: this.localize(buttonId
+ "-Tooltip"),
145 action
: "onButtonPress",
147 hotKey
: (this.buttonsConfiguration
[button
[2]] ? this.buttonsConfiguration
[button
[2]].hotKey
: (button
[1] ? button
[1] : null))
149 this.registerButton(buttonConfiguration
);
157 * The list of buttons added by this plugin
160 Indent
: [null, "TAB", "indent"],
161 Outdent
: [null, "SHIFT-TAB", "outdent"],
162 Blockquote
: [null, null, "blockquote"],
163 InsertParagraphBefore
: [null, null, "insertparagraphbefore"],
164 InsertParagraphAfter
: [null, null, "insertparagraphafter"],
165 JustifyLeft
: [null, "l", "left"],
166 JustifyCenter
: [null, "e", "center"],
167 JustifyRight
: [null, "r", "right"],
168 JustifyFull
: [null, "j", "justifyfull"],
169 InsertOrderedList
: [null, null, "orderedlist"],
170 InsertUnorderedList
: [null, null, "unorderedlist"]
174 * The list of hotkeys associated with block elements and registered by default by this plugin
187 * The function returns true if the type of block element is allowed in the current configuration
189 isAllowedBlockElement : function (blockName
) {
190 return this.allowedBlockElements
.test(blockName
);
194 * This function gets called when some block element was selected in the drop-down list
196 onChange : function (editor
, buttonId
) {
197 var tbobj
= this.editor
._toolbarObjects
[buttonId
];
198 var blockElement
= document
.getElementById(tbobj
.elementId
).value
;
199 this.applyBlockElement(buttonId
, blockElement
);
202 applyBlockElement : function(buttonId
, blockElement
) {
203 switch (blockElement
) {
205 this.onButtonPress(this.editor
, "Blockquote");
210 this.onButtonPress(this.editor
, blockElement
);
213 var element
= blockElement
;
214 if (HTMLArea
.is_ie
) {
215 element
= "<" + element
+ ">";
217 this.editor
.focusEditor();
218 if (HTMLArea
.is_safari
&& !this.editor
._doc
.body
.hasChildNodes()) {
219 this.editor
._doc
.body
.appendChild((this.editor
._doc
.createElement("br")));
222 this.editor
._doc
.execCommand(buttonId
, false, element
);
224 this.appendToLog("applyBlockElement", e
+ "\n\nby execCommand(" + buttonId
+ ");");
230 * This function gets called when a button was pressed.
232 * @param object editor: the editor instance
233 * @param string id: the button id or the key
234 * @param object target: the target element of the contextmenu event, when invoked from the context menu
236 * @return boolean false if action is completed
238 onButtonPress : function (editor
, id
, target
) {
239 // Could be a button or its hotkey
240 var buttonId
= this.translateHotKey(id
);
241 buttonId
= buttonId
? buttonId
: id
;
242 this.editor
.focusEditor();
243 var selection
= editor
._getSelection();
244 var range
= editor
._createRange(selection
);
245 var parentElement
= this.editor
._statusBarTree
.selected
? this.editor
._statusBarTree
.selected
: this.editor
.getParentElement(selection
, range
);
247 parentElement
= target
;
249 while (parentElement
&& (!HTMLArea
.isBlockElement(parentElement
) || /^li$/i.test(parentElement
.nodeName
))) {
250 parentElement
= parentElement
.parentNode
;
252 var blockAncestors
= this.getBlockAncestors(parentElement
);
253 var tableCell
= null;
255 if (id
=== "TAB" || id
=== "SHIFT-TAB") {
256 for (var i
= blockAncestors
.length
; --i
>= 0;) {
257 if (/^(td|th)$/i.test(blockAncestors
[i
].nodeName
)) {
258 tableCell
= blockAncestors
[i
];
263 var fullNodeTextSelected
= (HTMLArea
.is_gecko
&& parentElement
.textContent
=== range
.toString()) || (HTMLArea
.is_ie
&& parentElement
.innerText
=== range
.text
);
267 if (/^(ol|ul)$/i.test(parentElement
.nodeName
) && !(fullNodeTextSelected
&& !/^(li)$/i.test(parentElement
.parentNode
.nodeName
))) {
268 if (HTMLArea
.is_opera
) {
270 this.editor
._doc
.execCommand(buttonId
, false, null);
272 this.appendToLog("onButtonPress", e
+ "\n\nby execCommand(" + buttonId
+ ");");
274 this.indentedList
= parentElement
;
275 this.makeNestedList(parentElement
);
276 this.editor
.selectNodeContents(this.indentedList
.lastChild
, false);
278 this.indentSelectedListElements(parentElement
, range
);
280 } else if (tableCell
) {
281 var tablePart
= tableCell
.parentNode
.parentNode
;
282 // Get next cell in same table part
283 var nextCell
= tableCell
.nextSibling
? tableCell
.nextSibling
: (tableCell
.parentNode
.nextSibling
? tableCell
.parentNode
.nextSibling
.cells
[0] : null);
284 // Next cell is in other table part
286 switch (tablePart
.nodeName
.toLowerCase()) {
288 nextCell
= tablePart
.parentNode
.tBodies
[0].rows
[0].cells
[0];
291 nextCell
= tablePart
.nextSibling
? tablePart
.nextSibling
.rows
[0].cells
[0] : null;
294 this.editor
.selectNodeContents(tablePart
.parentNode
.lastChild
.lastChild
.lastChild
, true);
299 if (this.editor
.plugins
.TableOperations
) {
300 this.editor
.plugins
.TableOperations
.instance
.onButtonPress(this.editor
, "TO-row-insert-under");
302 nextCell
= tablePart
.parentNode
.rows
[0].cells
[0];
306 this.editor
.selectNodeContents(nextCell
, true);
308 } else if (this.useBlockquote
) {
310 this.editor
._doc
.execCommand(buttonId
, false, null);
312 this.appendToLog("onButtonPress", e
+ "\n\nby execCommand(" + buttonId
+ ");");
314 } else if (this.isAllowedBlockElement("div")) {
315 if (/^div$/i.test(parentElement
.nodeName
) && !HTMLArea
._hasClass(parentElement
, this.useClass
[buttonId
])) {
316 HTMLArea
._addClass(parentElement
, this.useClass
[buttonId
]);
317 } else if (!/^div$/i.test(parentElement
.nodeName
) && /^div$/i.test(parentElement
.parentNode
.nodeName
) && !HTMLArea
._hasClass(parentElement
.parentNode
, this.useClass
[buttonId
])) {
318 HTMLArea
._addClass(parentElement
.parentNode
, this.useClass
[buttonId
]);
320 var bookmark
= this.editor
.getBookmark(range
);
321 var newBlock
= this.wrapSelectionInBlockElement("div", this.useClass
[buttonId
], null, true);
322 this.editor
.selectRange(this.editor
.moveToBookmark(bookmark
));
325 this.addClassOnBlockElements(buttonId
);
329 if (/^(ol|ul)$/i.test(parentElement
.nodeName
) && !HTMLArea
._hasClass(parentElement
, this.useClass
.Indent
)) {
330 if (/^(li)$/i.test(parentElement
.parentNode
.nodeName
)) {
331 if (HTMLArea
.is_opera
) {
333 this.editor
._doc
.execCommand(buttonId
, false, null);
335 this.appendToLog("onButtonPress", e
+ "\n\nby execCommand(" + buttonId
+ ");");
338 this.outdentSelectedListElements(parentElement
, range
);
341 } else if (tableCell
) {
342 var previousCell
= tableCell
.previousSibling
? tableCell
.previousSibling
: (tableCell
.parentNode
.previousSibling
? tableCell
.parentNode
.previousSibling
.lastChild
: null);
344 var table
= tableCell
.parentNode
.parentNode
.parentNode
;
345 var tablePart
= tableCell
.parentNode
.parentNode
.nodeName
.toLowerCase();
349 previousCell
= table
.tHead
.rows
[table
.tHead
.rows
.length
-1].cells
[table
.tHead
.rows
[table
.tHead
.rows
.length
-1].cells
.length
-1];
354 previousCell
= table
.tFoot
.rows
[table
.tFoot
.rows
.length
-1].cells
[table
.tFoot
.rows
[table
.tFoot
.rows
.length
-1].cells
.length
-1];
358 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];
362 this.editor
.selectNodeContents(previousCell
, true);
364 } else if (this.useBlockquote
) {
366 this.editor
._doc
.execCommand(buttonId
, false, null);
368 this.appendToLog("onButtonPress", e
+ "\n\nby execCommand(" + buttonId
+ ");");
370 } else if (this.isAllowedBlockElement("div")) {
371 for (var i
= blockAncestors
.length
; --i
>= 0;) {
372 if (HTMLArea
._hasClass(blockAncestors
[i
], this.useClass
.Indent
)) {
373 var bookmark
= this.editor
.getBookmark(range
);
374 var newBlock
= this.wrapSelectionInBlockElement("div", false, blockAncestors
[i
]);
375 // If not directly under the div, we need to backtrack
376 if (newBlock
.parentNode
!== blockAncestors
[i
]) {
377 var parent
= newBlock
.parentNode
;
378 this.removeElement(newBlock
);
379 while (parent
.parentNode
!== blockAncestors
[i
]) {
380 parent
= parent
.parentNode
;
382 blockAncestors
[i
].insertBefore(newBlock
, parent
);
383 newBlock
.appendChild(parent
);
385 newBlock
.className
= blockAncestors
[i
].className
;
386 HTMLArea
._removeClass(newBlock
, this.useClass
.Indent
);
387 if (!newBlock
.previousSibling
) {
388 while (newBlock
.hasChildNodes()) {
389 if (newBlock
.firstChild
.nodeType
== 1) {
390 newBlock
.firstChild
.className
= newBlock
.className
;
392 blockAncestors
[i
].parentNode
.insertBefore(newBlock
.firstChild
, blockAncestors
[i
]);
394 } else if (!newBlock
.nextSibling
) {
395 if (blockAncestors
[i
].nextSibling
) {
396 while (newBlock
.hasChildNodes()) {
397 if (newBlock
.firstChild
.nodeType
== 1) {
398 newBlock
.lastChild
.className
= newBlock
.className
;
400 blockAncestors
[i
].parentNode
.insertBefore(newBlock
.lastChild
, blockAncestors
[i
].nextSibling
);
403 while (newBlock
.hasChildNodes()) {
404 if (newBlock
.firstChild
.nodeType
== 1) {
405 newBlock
.firstChild
.className
= newBlock
.className
;
407 blockAncestors
[i
].parentNode
.appendChild(newBlock
.firstChild
);
411 var clone
= blockAncestors
[i
].cloneNode(false);
412 if (blockAncestors
[i
].nextSibling
) {
413 blockAncestors
[i
].parentNode
.insertBefore(clone
, blockAncestors
[i
].nextSibling
);
415 blockAncestors
[i
].parentNode
.appendChild(clone
);
417 while (newBlock
.nextSibling
) {
418 clone
.appendChild(newBlock
.nextSibling
);
420 while (newBlock
.hasChildNodes()) {
421 if (newBlock
.firstChild
.nodeType
== 1) {
422 newBlock
.firstChild
.className
= newBlock
.className
;
424 blockAncestors
[i
].parentNode
.insertBefore(newBlock
.firstChild
, clone
);
427 blockAncestors
[i
].removeChild(newBlock
);
428 if (!blockAncestors
[i
].hasChildNodes()) {
429 blockAncestors
[i
].parentNode
.removeChild(blockAncestors
[i
]);
431 this.editor
.selectRange(this.editor
.moveToBookmark(bookmark
));
436 this.addClassOnBlockElements(buttonId
);
439 case "InsertParagraphBefore" :
440 case "InsertParagraphAfter" :
441 this.insertParagraph(buttonId
=== "InsertParagraphAfter");
444 var commandState
= false;
445 for (var i
= blockAncestors
.length
; --i
>= 0;) {
446 if (/^blockquote$/i.test(blockAncestors
[i
].nodeName
)) {
448 this.removeElement(blockAncestors
[i
]);
453 var bookmark
= this.editor
.getBookmark(range
);
454 var newBlock
= this.wrapSelectionInBlockElement("blockquote", null, null, true);
455 this.editor
.selectRange(this.editor
.moveToBookmark(bookmark
));
460 var bookmark
= this.editor
.getBookmark(range
);
461 var newBlock
= this.wrapSelectionInBlockElement(buttonId
, null, null, true);
462 this.editor
.selectRange(this.editor
.moveToBookmark(bookmark
));
465 case "JustifyCenter" :
466 case "JustifyRight" :
468 if (this.useAlignAttribute
) {
470 this.editor
._doc
.execCommand(buttonId
, false, null);
472 this.appendToLog("onButtonPress", e
+ "\n\nby execCommand(" + buttonId
+ ");");
475 this.addClassOnBlockElements(buttonId
);
478 case "InsertOrderedList":
479 case "InsertUnorderedList":
480 this.insertList(buttonId
, parentElement
);
483 if (this.isAllowedBlockElement(parentElement
.nodeName
)) {
484 this.removeElement(parentElement
);
494 * Get the block ancestors of an element within a given block
496 getBlockAncestors : function(element
, withinBlock
) {
497 var ancestors
= new Array();
498 var ancestor
= element
;
499 while (ancestor
&& (ancestor
.nodeType
=== 1) && !/^(body)$/i.test(ancestor
.nodeName
) && ancestor
!= withinBlock
) {
500 if (HTMLArea
.isBlockElement(ancestor
)) {
501 ancestors
.unshift(ancestor
);
503 ancestor
= ancestor
.parentNode
;
505 ancestors
.unshift(ancestor
);
510 * This function wraps the block elements intersecting the current selection in a block element of the given type
512 * @param string blockName: the type of element to be used as wrapping block
513 * @param string useClass: a class to be assigned to the wrapping block
514 * @param object withinBlock: only elements contained in this block will be wrapped
515 * @param boolean keepValid: make only valid wraps (working wraps may produce temporary invalid hierarchies)
517 * @return object the wrapping block
519 wrapSelectionInBlockElement : function(blockName
, useClass
, withinBlock
, keepValid
) {
520 var endBlocks
= this.editor
.getEndBlocks(this.editor
._getSelection());
521 var startAncestors
= this.getBlockAncestors(endBlocks
.start
, withinBlock
);
522 var endAncestors
= this.getBlockAncestors(endBlocks
.end
, withinBlock
);
524 while (i
< startAncestors
.length
&& i
< endAncestors
.length
&& startAncestors
[i
] === endAncestors
[i
]) {
528 if ((endBlocks
.start
=== endBlocks
.end
&& /^(body)$/i.test(endBlocks
.start
.nodeName
)) || !startAncestors
[i
] || !endAncestors
[i
]) {
532 if (endBlocks
.start
=== endBlocks
.end
) {
533 while (i
&& /^(thead|tbody|tfoot|tr|dt)$/i.test(startAncestors
[i
].nodeName
)) {
537 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
))) {
542 var blockElement
= this.editor
._doc
.createElement(blockName
);
544 HTMLArea
._addClass(blockElement
, useClass
);
546 var contextElement
= endAncestors
[0];
548 contextElement
= endAncestors
[i
-1];
550 var nextElement
= endAncestors
[i
].nextSibling
;
551 var block
= startAncestors
[i
], sibling
;
552 if ((!/^(body|td|th|li|dd)$/i.test(block
.nodeName
) || /^(ol|ul|dl)$/i.test(blockName
)) && block
!= withinBlock
) {
553 while (block
&& block
!= nextElement
) {
554 sibling
= block
.nextSibling
;
555 blockElement
.appendChild(block
);
559 blockElement
= nextElement
.parentNode
.insertBefore(blockElement
, nextElement
);
561 blockElement
= contextElement
.appendChild(blockElement
);
564 contextElement
= block
;
565 block
= block
.firstChild
;
567 sibling
= block
.nextSibling
;
568 blockElement
.appendChild(block
);
571 blockElement
= contextElement
.appendChild(blockElement
);
573 // Things go wrong in some browsers when the node is empty
574 if (HTMLArea
.is_safari
&& !blockElement
.hasChildNodes()) {
575 blockElement
= blockElement
.appendChild(this.editor
._doc
.createElement("br"));
581 * This function adds a class attribute on blocks sibling of the block containing the start container of the selection
583 addClassOnBlockElements : function(buttonId
) {
584 var selection
= this.editor
._getSelection();
585 var endBlocks
= this.editor
.getEndBlocks(selection
);
586 var startAncestors
= this.getBlockAncestors(endBlocks
.start
);
587 var endAncestors
= this.getBlockAncestors(endBlocks
.end
);
589 while (index
< startAncestors
.length
&& index
< endAncestors
.length
&& startAncestors
[index
] === endAncestors
[index
]) {
592 if (endBlocks
.start
=== endBlocks
.end
) {
595 for (var block
= startAncestors
[index
]; block
; block
= block
.nextSibling
) {
596 if (HTMLArea
.isBlockElement(block
)) {
599 if (!HTMLArea
._hasClass(block
, this.useClass
[buttonId
])) {
600 HTMLArea
._addClass(block
, this.useClass
[buttonId
]);
604 if (HTMLArea
._hasClass(block
, this.useClass
["Indent"])) {
605 HTMLArea
._removeClass(block
, this.useClass
["Indent"]);
609 case "JustifyCenter" :
610 case "JustifyRight" :
612 this.toggleAlignmentClass(block
, buttonId
);
618 if (block
== endAncestors
[index
]) {
625 * This function toggles the alignment class on the given block
627 toggleAlignmentClass : function(block
, buttonId
) {
628 for (var alignmentButtonId
in this.useClass
) {
629 if (this.useClass
.hasOwnProperty(alignmentButtonId
) && alignmentButtonId
!== "Indent") {
630 if (HTMLArea
._hasClass(block
, this.useClass
[alignmentButtonId
])) {
631 HTMLArea
._removeClass(block
, this.useClass
[alignmentButtonId
]);
632 } else if (alignmentButtonId
=== buttonId
) {
633 HTMLArea
._addClass(block
, this.useClass
[alignmentButtonId
]);
637 if (/^div$/i.test(block
.nodeName
) && !this.hasAllowedAttributes(block
)) {
638 this.removeElement(block
);
643 * This function verifies if the element has any of the allowed attributes
645 hasAllowedAttributes : function(element
) {
646 for (var i
= 0; i
< this.allowedAttributes
.length
; ++i
) {
647 if (element
.getAttribute(this.allowedAttributes
[i
])) {
655 * This function removes the given element but keeps its contents
657 removeElement : function(element
) {
658 var selection
= this.editor
._getSelection();
659 var range
= this.editor
._createRange(selection
);
661 var bookmark
= this.editor
.getBookmark(range
);
662 var parent
= element
.parentNode
;
663 while (element
.firstChild
) {
664 lastChild
= parent
.insertBefore(element
.firstChild
, element
);
666 parent
.removeChild(element
);
667 var range
= this.editor
.moveToBookmark(bookmark
);
668 this.editor
.selectRange(range
);
671 insertList : function (buttonId
, parentElement
) {
672 if (/^(dd)$/i.test(parentElement
.nodeName
)) {
673 var list
= parentElement
.appendChild(this.editor
._doc
.createElement((buttonId
=== "OrderedList") ? "ol" : "ul"));
674 var first
= list
.appendChild(this.editor
._doc
.createElement("li"));
675 first
.innerHTML
= "<br />";
676 this.editor
.selectNodeContents(first
,true);
679 this.editor
._doc
.execCommand(buttonId
, false, null);
681 this.appendToLog("onButtonPress", e
+ "\n\nby execCommand(" + buttonId
+ ");");
683 if (HTMLArea
.is_safari
) {
684 this.cleanAppleSpanTags(parentElement
);
690 * Indent selected list elements
692 indentSelectedListElements : function (list
, range
) {
693 var bookmark
= this.editor
.getBookmark(range
);
694 // The selected elements are wrapped into a list element
695 var indentedList
= this.wrapSelectionInBlockElement(list
.nodeName
.toLowerCase(), null, list
);
696 // which breaks the range
697 var range
= this.editor
.moveToBookmark(bookmark
);
698 bookmark
= this.editor
.getBookmark(range
);
700 // Check if the last element has children. If so, outdent those that do not intersect the selection
701 var last
= indentedList
.lastChild
.lastChild
;
702 if (last
&& /^(ol|ul)$/i.test(last
.nodeName
)) {
703 var child
= last
.firstChild
, next
;
705 next
= child
.nextSibling
;
706 if (!this.editor
.rangeIntersectsNode(range
, child
)) {
707 indentedList
.appendChild(child
);
711 if (!last
.hasChildNodes()) {
712 HTMLArea
.removeFromParent(last
);
715 if (indentedList
.previousSibling
&& indentedList
.previousSibling
.hasChildNodes()) {
716 // Indenting some elements not including the first one
717 if (/^(ol|ul)$/i.test(indentedList
.previousSibling
.lastChild
.nodeName
)) {
718 // Some indented elements exist just above our selection
719 // Moving to regroup with these elements
720 while (indentedList
.hasChildNodes()) {
721 indentedList
.previousSibling
.lastChild
.appendChild(indentedList
.firstChild
);
723 list
.removeChild(indentedList
);
725 indentedList
= indentedList
.previousSibling
.appendChild(indentedList
);
728 // Indenting the first element and possibly some more
729 var first
= this.editor
._doc
.createElement("li");
730 first
.innerHTML
= " ";
731 list
.insertBefore(first
, indentedList
);
732 indentedList
= first
.appendChild(indentedList
);
734 this.editor
.selectRange(this.editor
.moveToBookmark(bookmark
));
738 * Outdent selected list elements
740 outdentSelectedListElements : function (list
, range
) {
741 // We wrap the selected li elements and thereafter move them one level up
742 var bookmark
= this.editor
.getBookmark(range
);
743 var wrappedList
= this.wrapSelectionInBlockElement(list
.nodeName
.toLowerCase(), null, list
);
744 // which breaks the range
745 var range
= this.editor
.moveToBookmark(bookmark
);
746 bookmark
= this.editor
.getBookmark(range
);
748 if (!wrappedList
.previousSibling
) {
749 // Outdenting the first element(s) of an indented list
750 var next
= list
.parentNode
.nextSibling
;
751 var last
= wrappedList
.lastChild
;
752 while (wrappedList
.hasChildNodes()) {
754 list
.parentNode
.parentNode
.insertBefore(wrappedList
.firstChild
, next
);
756 list
.parentNode
.parentNode
.appendChild(wrappedList
.firstChild
);
759 list
.removeChild(wrappedList
);
760 last
.appendChild(list
);
761 } else if (!wrappedList
.nextSibling
) {
762 // Outdenting the last element(s) of the list
763 // This will break the gecko bookmark
764 this.editor
.moveToBookmark(bookmark
);
765 while (wrappedList
.hasChildNodes()) {
766 if (list
.parentNode
.nextSibling
) {
767 list
.parentNode
.parentNode
.insertBefore(wrappedList
.firstChild
, list
.parentNode
.nextSibling
);
769 list
.parentNode
.parentNode
.appendChild(wrappedList
.firstChild
);
772 list
.removeChild(wrappedList
);
773 this.editor
.selectNodeContents(list
.parentNode
.nextSibling
, true);
774 bookmark
= this.editor
.getBookmark(this.editor
._createRange(this.editor
._getSelection()));
776 // Outdenting the middle of a list
777 var next
= list
.parentNode
.nextSibling
;
778 var last
= wrappedList
.lastChild
;
779 var sibling
= wrappedList
.nextSibling
;
780 while (wrappedList
.hasChildNodes()) {
782 list
.parentNode
.parentNode
.insertBefore(wrappedList
.firstChild
, next
);
784 list
.parentNode
.parentNode
.appendChild(wrappedList
.firstChild
);
788 wrappedList
.appendChild(sibling
);
789 sibling
= sibling
.nextSibling
;
791 last
.appendChild(wrappedList
);
793 // Remove the list if all its elements have been moved up
794 if (!list
.hasChildNodes()) {
795 list
.parentNode
.removeChild(list
);
797 this.editor
.selectRange(this.editor
.moveToBookmark(bookmark
));
801 * Clean Apple span tags
803 cleanAppleSpanTags : function(element
) {
804 var spans
= element
.getElementsByTagName("span");
805 for (var i
= spans
.length
; --i
>= 0;) {
806 if (HTMLArea
._hasClass(spans
[i
], "Apple-style-span")) {
807 HTMLArea
.removeFromParent(spans
[i
]);
813 * Make XHTML-compliant nested list
814 * We need this for Opera
816 makeNestedList : function(el
) {
818 for (var i
= el
.firstChild
; i
; i
= i
.nextSibling
) {
819 if (/^li$/i.test(i
.nodeName
)) {
820 for (var j
= i
.firstChild
; j
; j
= j
.nextSibling
) {
821 if (/^(ol|ul)$/i.test(j
.nodeName
)) {
822 this.makeNestedList(j
);
825 } else if (/^(ol|ul)$/i.test(i
.nodeName
)) {
826 previous
= i
.previousSibling
;
827 this.indentedList
= i
.cloneNode(true);
829 previous
= el
.insertBefore(this.editor
._doc
.createElement("li"), i
);
830 this.indentedList
= previous
.appendChild(this.indentedList
);
832 this.indentedList
= previous
.appendChild(this.indentedList
);
834 HTMLArea
.removeFromParent(i
);
835 this.makeNestedList(el
);
844 insertParagraph : function(after
) {
845 var endBlocks
= this.editor
.getEndBlocks(this.editor
._getSelection());
846 var ancestors
= after
? this.getBlockAncestors(endBlocks
.end
) : this.getBlockAncestors(endBlocks
.start
);
847 var endElement
= ancestors
[ancestors
.length
-1];
848 for (var i
= ancestors
.length
; --i
>= 0;) {
849 if (/^(table|div|ul|ol|dl|blockquote|address|pre)$/i.test(ancestors
[i
].nodeName
) && !/^(li)$/i.test(ancestors
[i
].parentNode
.nodeName
)) {
850 endElement
= ancestors
[i
];
855 var parent
= endElement
.parentNode
;
856 var paragraph
= this.editor
._doc
.createElement("p");
857 if (HTMLArea
.is_ie
) {
858 paragraph
.innerHTML
= " ";
860 paragraph
.appendChild(this.editor
._doc
.createElement("br"));
862 if (after
&& !endElement
.nextSibling
) {
863 parent
.appendChild(paragraph
);
865 parent
.insertBefore(paragraph
, after
? endElement
.nextSibling
: endElement
);
867 this.editor
.selectNodeContents(paragraph
, true);
872 * This function gets called by the main editor event handler when a key was pressed.
873 * It will process the enter key for IE when the cursor is at the end of a dt or a dd element
875 onKeyPress : function (ev
) {
876 if (HTMLArea
.is_ie
&& ev
.keyCode
== 13 && !ev
.shiftKey
) {
877 var selection
= this.editor
._getSelection();
878 if (this.editor
._selectionEmpty(selection
)) {
879 var range
= this.editor
._createRange(selection
);
880 var parentElement
= this.editor
.getParentElement(selection
, range
);
881 while (parentElement
&& !HTMLArea
.isBlockElement(parentElement
)) {
882 parentElement
= parentElement
.parentNode
;
884 if (/^(dt|dd)$/i.test(parentElement
.nodeName
)) {
885 var nodeRange
= this.editor
._createRange();
886 nodeRange
.moveToElementText(parentElement
);
887 range
.setEndPoint("EndToEnd", nodeRange
);
888 if (!range
.text
|| range
.text
== "\x20") {
889 var item
= parentElement
.parentNode
.insertBefore(this.editor
._doc
.createElement((parentElement
.nodeName
.toLowerCase() === "dt") ? "dd" : "dt"), parentElement
.nextSibling
);
890 item
.innerHTML
= "\x20";
891 this.editor
.selectNodeContents(item
, true);
894 } else if (/^(li)$/i.test(parentElement
.nodeName
)
895 && !parentElement
.innerText
896 && parentElement
.parentNode
.parentNode
897 && /^(dd|td|th)$/i.test(parentElement
.parentNode
.parentNode
.nodeName
)) {
898 var item
= parentElement
.parentNode
.parentNode
.insertBefore(this.editor
._doc
.createTextNode("\x20"), parentElement
.parentNode
.nextSibling
);
899 this.editor
.selectNodeContents(parentElement
.parentNode
.parentNode
, false);
900 parentElement
.parentNode
.removeChild(parentElement
);
909 * This function gets called when the toolbar is updated
911 onUpdateToolbar : function () {
912 if (this.editor
.getMode() === "textmode" || !this.editor
.isEditable()) {
915 var parentElement
= this.editor
._statusBarTree
.selected
? this.editor
._statusBarTree
.selected
: this.editor
.getParentElement();
916 if (parentElement
.nodeName
.toLowerCase() === "body") return false;
917 while (parentElement
&& !HTMLArea
.isBlockElement(parentElement
) || /^li$/i.test(parentElement
.nodeName
)) {
918 parentElement
= parentElement
.parentNode
;
920 var blockAncestors
= this.getBlockAncestors(parentElement
);
922 var selection
= this.editor
._getSelection();
923 var endBlocks
= this.editor
.getEndBlocks(selection
);
924 var startAncestors
= this.getBlockAncestors(endBlocks
.start
);
925 var endAncestors
= this.getBlockAncestors(endBlocks
.end
);
927 while (index
< startAncestors
.length
&& index
< endAncestors
.length
&& startAncestors
[index
] === endAncestors
[index
]) {
930 if (endBlocks
.start
=== endBlocks
.end
|| !startAncestors
[index
]) {
933 var dropDownConfiguration
= this.getDropDownConfiguration("FormatBlock");
934 if ((typeof(dropDownConfiguration
) !== "undefined") && this.isButtonInToolbar(dropDownConfiguration
.id
)) {
935 this.updateDropDown(dropDownConfiguration
, blockAncestors
[blockAncestors
.length
-1], startAncestors
[index
]);
939 for (var buttonId
in this.buttonList
) {
940 commandState
= false;
941 if (this.buttonList
.hasOwnProperty(buttonId
) && this.isButtonInToolbar(buttonId
)) {
944 if (this.useBlockquote
) {
945 for (var j
= blockAncestors
.length
; --j
>= 0;) {
946 if (/^blockquote$/i.test(blockAncestors
[j
].nodeName
)) {
951 } else if (/^(ol|ul)$/i.test(parentElement
.nodeName
)) {
954 for (var j
= blockAncestors
.length
; --j
>= 0;) {
955 if (HTMLArea
._hasClass(blockAncestors
[j
], this.useClass
.Indent
)) {
961 this.editor
._toolbarObjects
[buttonId
].state("enabled", commandState
);
965 case "InsertParagraphBefore" :
966 case "InsertParagraphAfter" :
967 this.editor
._toolbarObjects
[buttonId
].state("enabled", !(/^(body)$/i.test(startAncestors
[index
].nodeName
)));
970 for (var j
= blockAncestors
.length
; --j
>= 0;) {
971 if (/^blockquote$/i.test(blockAncestors
[j
].nodeName
)) {
976 this.editor
._toolbarObjects
[buttonId
].state("active", commandState
);
979 case "JustifyCenter" :
980 case "JustifyRight" :
982 if (this.useAlignAttribute
) {
984 commandState
= this.editor
._doc
.queryCommandState(buttonId
);
986 commandState
= false;
989 if (/^(body)$/i.test(startAncestors
[index
].nodeName
)) {
990 this.editor
._toolbarObjects
[buttonId
].state("enabled", false);
992 this.editor
._toolbarObjects
[buttonId
].state("enabled", true);
994 for (var block
= startAncestors
[index
]; block
; block
= block
.nextSibling
) {
995 commandState
= commandState
&& HTMLArea
._hasClass(block
, this.useClass
[buttonId
]);
996 if (block
== endAncestors
[index
]) {
1002 this.editor
._toolbarObjects
[buttonId
].state("active", commandState
);
1004 case "InsertOrderedList":
1005 case "InsertUnorderedList":
1007 commandState
= this.editor
._doc
.queryCommandState(buttonId
);
1009 commandState
= false;
1011 this.editor
._toolbarObjects
[buttonId
].state("active", commandState
);
1021 * This function updates the drop-down list of block elemenents
1023 updateDropDown : function(dropDownConfiguration
, deepestBlockAncestor
, startAncestor
) {
1025 var select
= document
.getElementById(this.editor
._toolbarObjects
[dropDownConfiguration
.id
].elementId
);
1026 var options
= select
.options
;
1027 for (var i
= options
.length
; --i
>= 0;) {
1028 options
[i
].selected
= false;
1030 select
.selectedIndex
= 0;
1031 options
[0].selected
= true;
1032 options
[0].style
.display
= "list-item";
1033 options
[0].text
= this.localize("No block");
1035 if (deepestBlockAncestor
) {
1036 var nodeName
= deepestBlockAncestor
.nodeName
.toLowerCase();
1037 for (i
= options
.length
; --i
>= 0;) {
1038 if (nodeName
=== options
[i
].value
.toLowerCase()) {
1039 options
[i
].selected
= true;
1040 select
.selectedIndex
= i
;
1041 options
[0].text
= this.localize("Remove block");
1049 * This function handles the hotkey events registered on elements of the dropdown list
1051 onHotKey : function(editor
, key
) {
1053 var hotKeyConfiguration
= this.getHotKeyConfiguration(key
);
1054 if (hotKeyConfiguration
) {
1055 var blockElement
= hotKeyConfiguration
.element
;
1057 if (blockElement
&& this.isAllowedBlockElement(blockElement
)) {
1058 this.applyBlockElement(this.translateHotKey(key
), blockElement
);