1 /***************************************************************
4 * (c) 2002-2004, interactivetools.com, inc.
5 * (c) 2003-2004 dynarch.com
6 * (c) 2004-2007 Stanislas Rolland <stanislas.rolland(arobas)fructifor.ca>
9 * This script is part of the TYPO3 project. The TYPO3 project is
10 * free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * The GNU General Public License can be found at
16 * http://www.gnu.org/copyleft/gpl.html.
17 * A copy is found in the textfile GPL.txt and important notices to the license
18 * from the author is found in LICENSE.txt distributed with these scripts.
21 * This script is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
26 * This script is a modified version of a script published under the htmlArea License.
27 * A copy of the htmlArea License may be found in the textfile HTMLAREA_LICENSE.txt.
29 * This copyright notice MUST APPEAR in all copies of the script!
30 ***************************************************************/
35 /***************************************************
36 * GECKO-SPECIFIC FUNCTIONS
37 ***************************************************/
38 HTMLArea
.prototype.isEditable
= function() {
39 return (this._doc
.designMode
=== "on");
42 /***************************************************
43 * MOZILLA/FIREFOX EDIT MODE INITILIZATION
44 ***************************************************/
46 HTMLArea
.prototype._initEditMode
= function () {
47 // We can't set designMode when we are in a hidden TYPO3 tab
48 // Then we will set it when the tab comes in the front.
50 var allDisplayed
= true;
52 if (this.nested
.sorted
&& this.nested
.sorted
.length
) {
54 allDisplayed
= HTMLArea
.allElementsAreDisplayed(this.nested
.sorted
);
56 if (!isNested
|| allDisplayed
) {
58 this._doc
.designMode
= "on";
59 if (this._doc
.queryCommandEnabled("insertbronreturn")) this._doc
.execCommand("insertbronreturn", false, this.config
.disableEnterParagraphs
);
60 if (this._doc
.queryCommandEnabled("enableInlineTableEditing")) this._doc
.execCommand("enableInlineTableEditing", false, (this.config
.buttons
.table
&& this.config
.buttons
.table
.enableHandles
) ? true : false);
61 if (this._doc
.queryCommandEnabled("styleWithCSS")) this._doc
.execCommand("styleWithCSS", false, this.config
.useCSS
);
62 else if (this._doc
.queryCommandEnabled("useCSS")) this._doc
.execCommand("useCSS", false, !this.config
.useCSS
);
64 if (HTMLArea
.is_wamcom
) {
67 this._initIframeTimer
= window
.setTimeout("HTMLArea.initIframe(" + this._editorNumber
+ ");", 500);
72 // When the TYPO3 TCA feature div2tab is used, the editor iframe may become hidden with style.display = "none"
73 // This breaks the editor in Mozilla/Firefox browsers
: the designMode attribute needs to be resetted after the style
.display of the containing div is resetted to
"block"
74 // Here we rely on TYPO3 naming conventions for the div id and class name
75 if (this.nested
.sorted
&& this.nested
.sorted
.length
) {
76 var nestedObj
, listenerFunction
;
77 for (var i
=0, length
=this.nested
.sorted
.length
; i
< length
; i
++) {
78 nestedObj
= document
.getElementById(this.nested
.sorted
[i
]);
79 listenerFunction
= HTMLArea
.NestedListener(this, nestedObj
, false);
80 HTMLArea
._addEvent(nestedObj
, 'DOMAttrModified', listenerFunction
);
86 /***************************************************
87 * SELECTIONS AND RANGES
88 ***************************************************/
91 * Get the current selection object
93 HTMLArea
.prototype._getSelection
= function() {
94 return this._iframe
.contentWindow
.getSelection();
98 * Empty the selection object
100 HTMLArea
.prototype.emptySelection
= function(selection
) {
101 if (HTMLArea
.is_safari
) {
104 selection
.removeAllRanges();
106 if (HTMLArea
.is_opera
) {
107 this._iframe
.focus();
112 * Add a range to the selection
114 HTMLArea
.prototype.addRangeToSelection
= function(selection
, range
) {
115 if (HTMLArea
.is_safari
) {
116 selection
.setBaseAndExtent(range
.startContainer
, range
.startOffset
, range
.endContainer
, range
.endOffset
);
118 selection
.addRange(range
);
123 * Create a range for the current selection
125 HTMLArea
.prototype._createRange
= function(sel
) {
126 if (HTMLArea
.is_safari
) {
127 var range
= this._doc
.createRange();
128 if (typeof(sel
) == "undefined") {
130 } else if (sel
.baseNode
== null) {
131 range
.setStart(this._doc
.body
,0);
132 range
.setEnd(this._doc
.body
,0);
135 range
.setStart(sel
.baseNode
, sel
.baseOffset
);
136 range
.setEnd(sel
.extentNode
, sel
.extentOffset
);
137 if (range
.collapsed
!= sel
.isCollapsed
) {
138 range
.setStart(sel
.extentNode
, sel
.extentOffset
);
139 range
.setEnd(sel
.baseNode
, sel
.baseOffset
);
144 if (typeof(sel
) == "undefined") return this._doc
.createRange();
146 return sel
.getRangeAt(0);
148 return this._doc
.createRange();
153 * Select a node AND the contents inside the node
155 HTMLArea
.prototype.selectNode
= function(node
, endPoint
) {
157 var selection
= this._getSelection();
158 var range
= this._doc
.createRange();
159 if (node
.nodeType
== 1 && node
.nodeName
.toLowerCase() == "body") {
160 range
.selectNodeContents(node
);
162 range
.selectNode(node
);
164 if (typeof(endPoint
) != "undefined") {
165 range
.collapse(endPoint
);
167 this.emptySelection(selection
);
168 this.addRangeToSelection(selection
, range
);
172 * Select ONLY the contents inside the given node
174 HTMLArea
.prototype.selectNodeContents
= function(node
, endPoint
) {
176 var selection
= this._getSelection();
177 var range
= this._doc
.createRange();
178 range
.selectNodeContents(node
);
179 if (typeof(endPoint
) !== "undefined") {
180 range
.collapse(endPoint
);
182 this.emptySelection(selection
);
183 this.addRangeToSelection(selection
, range
);
186 HTMLArea
.prototype.rangeIntersectsNode
= function(range
, node
) {
187 var nodeRange
= this._doc
.createRange();
189 nodeRange
.selectNode(node
);
191 nodeRange
.selectNodeContents(node
);
193 // Note: sometimes Safari inverts the end points
194 return (range
.compareBoundaryPoints(range
.END_TO_START
, nodeRange
) == -1 && range
.compareBoundaryPoints(range
.START_TO_END
, nodeRange
) == 1) ||
195 (range
.compareBoundaryPoints(range
.END_TO_START
, nodeRange
) == 1 && range
.compareBoundaryPoints(range
.START_TO_END
, nodeRange
) == -1);
199 * Get the selection type
201 HTMLArea
.prototype.getSelectionType
= function(selection
) {
202 // By default set the type to "Text".
205 var selection
= this._getSelection();
207 // Check if the actual selection is a Control
208 if (selection
&& selection
.rangeCount
== 1) {
209 var range
= selection
.getRangeAt(0) ;
210 if (range
.startContainer
== range
.endContainer
211 && (range
.endOffset
- range
.startOffset
) == 1
212 && range
.startContainer
.nodeType
== 1
213 && /^(img|hr|li|table|tr|td|embed|object|ol|ul)$/i.test(range
.startContainer
.childNodes
[range
.startOffset
].nodeName
)) {
221 * Retrieves the selected element (if any), just in the case that a single element (object like and image or a table) is selected.
223 HTMLArea
.prototype.getSelectedElement
= function(selection
) {
224 var selectedElement
= null;
226 var selection
= this._getSelection();
228 if (selection
&& selection
.anchorNode
&& selection
.anchorNode
.nodeType
== 1) {
229 if (this.getSelectionType(selection
) == "Control") {
230 selectedElement
= selection
.anchorNode
.childNodes
[selection
.anchorOffset
];
231 // For Safari, the anchor node for a control selection is the control itself
232 if (!selectedElement
) {
233 selectedElement
= selection
.anchorNode
;
234 } else if (selectedElement
.nodeType
!= 1) {
239 return selectedElement
;
243 * Retrieve the HTML contents of selected block
245 HTMLArea
.prototype.getSelectedHTML
= function() {
246 var range
= this._createRange(this._getSelection());
247 if (range
.collapsed
) return "";
248 var cloneContents
= range
.cloneContents();
249 if (!cloneContents
) {
250 cloneContents
= this._doc
.createDocumentFragment();
252 return HTMLArea
.getHTML(cloneContents
, false, this);
256 * Retrieve simply HTML contents of the selected block, IE ignoring control ranges
258 HTMLArea
.prototype.getSelectedHTMLContents
= function() {
259 return this.getSelectedHTML();
263 * Get the deepest node that contains both endpoints of the current selection.
265 HTMLArea
.prototype.getParentElement
= function(selection
, range
) {
267 var selection
= this._getSelection();
269 if (this.getSelectionType(selection
) === "Control") {
270 return this.getSelectedElement(selection
);
272 if (typeof(range
) === "undefined") {
273 var range
= this._createRange(selection
);
275 var parentElement
= range
.commonAncestorContainer
;
276 // For some reason, Firefox 3 may report the Document as commonAncestorContainer
277 if (parentElement
.nodeType
== 9) return this._doc
.body
;
278 while (parentElement
&& parentElement
.nodeType
== 3) {
279 parentElement
= parentElement
.parentNode
;
281 return parentElement
;
285 * Get the selected element, if any. That is, the element that you have last selected in the "path"
286 * at the bottom of the editor, or a "control" (eg image)
288 * @returns null | element
289 * Borrowed from Xinha (is not htmlArea) - http://xinha.gogo.co.nz/
291 HTMLArea
.prototype._activeElement
= function(selection
) {
292 if (this._selectionEmpty(selection
)) {
295 // Check if the anchor (start of selection) is an element.
296 if (selection
.anchorNode
.nodeType
== 1) {
297 return selection
.anchorNode
;
304 * Determine if the current selection is empty or not.
306 HTMLArea
.prototype._selectionEmpty
= function(sel
) {
307 if (!sel
) return true;
308 return sel
.isCollapsed
;
313 * Adapted from FCKeditor
314 * This is an "intrusive" way to create a bookmark. It includes <span> tags
315 * in the range boundaries. The advantage of it is that it is possible to
316 * handle DOM mutations when moving back to the bookmark.
318 HTMLArea
.prototype.getBookmark
= function (range
) {
319 // For performance, includeNodes=true if intended to SelectBookmark.
320 // Create the bookmark info (random IDs).
322 startId
: (new Date()).valueOf() + Math
.floor(Math
.random()*1000) + 'S',
323 endId
: (new Date()).valueOf() + Math
.floor(Math
.random()*1000) + 'E'
328 var rangeClone
= range
.cloneRange();
330 // For collapsed ranges, add just the start marker.
331 if (!range
.collapsed
) {
332 endSpan
= this._doc
.createElement("span");
333 endSpan
.style
.display
= "none";
334 endSpan
.id
= bookmark
.endId
;
335 endSpan
.setAttribute("HTMLArea_bookmark", true);
336 endSpan
.innerHTML
= " ";
337 rangeClone
.collapse(false);
338 rangeClone
.insertNode(endSpan
);
341 startSpan
= this._doc
.createElement("span");
342 startSpan
.style
.display
= "none";
343 startSpan
.id
= bookmark
.startId
;
344 startSpan
.setAttribute("HTMLArea_bookmark", true);
345 startSpan
.innerHTML
= " ";
346 var rangeClone
= range
.cloneRange();
347 rangeClone
.collapse(true);
348 rangeClone
.insertNode(startSpan
);
349 bookmark
.startNode
= startSpan
;
350 bookmark
.endNode
= endSpan
;
351 // Update the range position.
353 range
.setEndBefore(endSpan
);
354 range
.setStartAfter(startSpan
);
356 range
.setEndAfter(startSpan
);
357 range
.collapse(false);
363 * Get the end point of the bookmark
364 * Adapted from FCKeditor
366 HTMLArea
.prototype.getBookmarkNode
= function(bookmark
, endPoint
) {
368 return bookmark
.startNode
|| this._doc
.getElementById(bookmark
.startId
);
370 return bookmark
.endNode
|| this._doc
.getElementById(bookmark
.endId
);
375 * Move the range to the bookmark
376 * Adapted from FCKeditor
378 HTMLArea
.prototype.moveToBookmark
= function (bookmark
) {
379 var startSpan
= this.getBookmarkNode(bookmark
, true);
380 var endSpan
= this.getBookmarkNode(bookmark
, false);
382 var range
= this._createRange();
383 range
.setStartBefore(startSpan
);
384 HTMLArea
.removeFromParent(startSpan
);
385 // If collapsed, the end span will not be available.
387 range
.setEndBefore(endSpan
);
388 HTMLArea
.removeFromParent(endSpan
);
390 range
.collapse(true);
398 HTMLArea
.prototype.selectRange
= function (range
) {
399 var selection
= this._getSelection();
400 this.emptySelection(selection
);
401 this.addRangeToSelection(selection
, range
);
404 /***************************************************
405 * DOM TREE MANIPULATION
406 ***************************************************/
409 * Insert a node at the current position.
410 * Delete the current selection, if any.
411 * Split the text node, if needed.
413 HTMLArea
.prototype.insertNodeAtSelection
= function(toBeInserted
) {
415 var range
= this._createRange(this._getSelection());
416 range
.deleteContents();
417 var toBeSelected
= (toBeInserted
.nodeType
=== 11) ? toBeInserted
.lastChild
: toBeInserted
;
418 range
.insertNode(toBeInserted
);
419 this.selectNodeContents(toBeSelected
, false);
423 * Insert HTML source code at the current position.
424 * Delete the current selection, if any.
426 HTMLArea
.prototype.insertHTML
= function(html
) {
428 var fragment
= this._doc
.createDocumentFragment();
429 var div
= this._doc
.createElement("div");
430 div
.innerHTML
= html
;
431 while (div
.firstChild
) {
432 fragment
.appendChild(div
.firstChild
);
434 this.insertNodeAtSelection(fragment
);
437 /***************************************************
439 ***************************************************/
442 * TYPO3 hidden tab and inline event listener (gets event calls)
444 HTMLArea
.NestedListener
= function (editor
,nestedObj
,noOpenCloseAction
) {
445 return (function(ev
) {
446 if(!ev
) var ev
= window
.event
;
447 HTMLArea
.NestedHandler(ev
,editor
,nestedObj
,noOpenCloseAction
);
452 * TYPO3 hidden tab and inline event handler (performs actions on event calls)
454 HTMLArea
.NestedHandler
= function(ev
,editor
,nestedObj
,noOpenCloseAction
) {
455 window
.setTimeout(function() {
456 var target
= (ev
.target
) ? ev
.target
: ev
.srcElement
;
457 if(target
== nestedObj
&& editor
._editMode
== "wysiwyg" && ev
.attrName
=='style' && (target
.style
.display
== '' || target
.style
.display
== 'block')) {
458 // Check if all affected nested elements are displayed (style.display!='none'):
459 if (HTMLArea
.allElementsAreDisplayed(editor
.nested
.sorted
)) {
460 window
.setTimeout(function() {
462 editor
._doc
.designMode
= "on";
463 if (editor
.config
.sizeIncludesToolbar
&& editor
._initialToolbarOffsetHeight
!= editor
._toolbar
.offsetHeight
) {
464 editor
.sizeIframe(-2);
466 if (editor
._doc
.queryCommandEnabled("insertbronreturn")) editor
._doc
.execCommand("insertbronreturn", false, editor
.config
.disableEnterParagraphs
);
467 if (editor
._doc
.queryCommandEnabled("enableInlineTableEditing")) editor
._doc
.execCommand("enableInlineTableEditing", false, (editor
.config
.buttons
.table
&& editor
.config
.buttons
.table
.enableHandles
) ? true : false);
468 if (editor
._doc
.queryCommandEnabled("styleWithCSS")) editor
._doc
.execCommand("styleWithCSS", false, editor
.config
.useCSS
);
469 else if (editor
._doc
.queryCommandEnabled("useCSS")) editor
._doc
.execCommand("useCSS", false, !editor
.config
.useCSS
);
471 // If an event of a parent tab ("nested tabs") is triggered, the following lines should not be
472 // processed, because this causes some trouble on all event handlers...
473 if (!noOpenCloseAction
) {
481 HTMLArea
._stopEvent(ev
);
487 * Handle statusbar element events
489 HTMLArea
.statusBarHandler
= function (ev
) {
490 if(!ev
) var ev
= window
.event
;
491 var target
= (ev
.target
) ? ev
.target
: ev
.srcElement
;
492 var editor
= target
.editor
;
494 editor
.selectNodeContents(target
.el
);
495 editor
._statusBarTree
.selected
= target
.el
;
496 editor
.updateToolbar(true);
500 HTMLArea
._stopEvent(ev
);
503 return editor
.plugins
["ContextMenu"] ? editor
.plugins
["ContextMenu"].instance
.popupMenu(ev
,target
.el
) : false;
508 * Paste exception handler
510 HTMLArea
.prototype._mozillaPasteException
= function(cmdID
, UI
, param
) {
511 // Mozilla lauches an exception, but can paste anyway on ctrl-V
512 // UI is false on keyboard shortcut, and undefined on button click
513 if(typeof(UI
) != "undefined") {
514 this._doc
.execCommand(cmdID
, UI
, param
);
515 if (cmdID
== "Paste" && this.config
.killWordOnPaste
) HTMLArea
._wordClean(this._doc
.body
);
516 } else if (this.config
.enableMozillaExtension
) {
517 if (confirm(HTMLArea
.I18N
.msg
["Allow-Clipboard-Helper-Extension"])) {
518 if (InstallTrigger
.enabled()) {
519 HTMLArea
._mozillaXpi
= new Object();
520 HTMLArea
._mozillaXpi
["AllowClipboard Helper"] = _editor_mozAllowClipboard_url
;
521 InstallTrigger
.install(HTMLArea
._mozillaXpi
,HTMLArea
._mozillaInstallCallback
);
523 alert(HTMLArea
.I18N
.msg
["Mozilla-Org-Install-Not-Enabled"]);
524 HTMLArea
._appendToLog("WARNING [HTMLArea::execCommand]: Mozilla install was not enabled.");
528 } else if (confirm(HTMLArea
.I18N
.msg
["Moz-Clipboard"])) {
529 window
.open("http://mozilla.org/editor/midasdemo/securityprefs.html");
533 HTMLArea
._mozillaInstallCallback
= function(url
,returnCode
) {
534 if (returnCode
== 0) {
535 if (HTMLArea
._mozillaXpi
["TYPO3 htmlArea RTE Preferences"]) alert(HTMLArea
.I18N
.msg
["Moz-Extension-Success"]);
536 else alert(HTMLArea
.I18N
.msg
["Allow-Clipboard-Helper-Extension-Success"]);
539 alert(HTMLArea
.I18N
.msg
["Moz-Extension-Failure"]);
540 HTMLArea
._appendToLog("WARNING [HTMLArea::execCommand]: Mozilla install return code was: " + returnCode
+ ".");
546 * Backspace event handler
548 HTMLArea
.prototype._checkBackspace
= function() {
551 var sel
= self
._getSelection();
552 var range
= self
._createRange(sel
);
553 var SC
= range
.startContainer
;
554 var SO
= range
.startOffset
;
555 var EC
= range
.endContainer
;
556 var EO
= range
.endOffset
;
557 var newr
= SC
.nextSibling
;
558 while (SC
.nodeType
== 3 || /^a$/i.test(SC
.tagName
)) SC
= SC
.parentNode
;
559 if (!self
.config
.disableEnterParagraphs
&& /^td$/i.test(SC
.parentNode
.tagName
) && SC
.parentNode
.firstChild
== SC
&& SO
== 0 && range
.collapsed
) return true;
560 window
.setTimeout(function() {
561 // Remove br tag inserted by Mozilla
562 if (!self
.config
.disableEnterParagraphs
&& (/^p$/i.test(SC
.tagName
) || !/\S/.test(SC
.tagName
)) && SO
== 0) {
563 if (SC
.firstChild
&& /^br$/i.test(SC
.firstChild
.tagName
)) {
564 HTMLArea
.removeFromParent(SC
.firstChild
);
568 if (!/\S/.test(SC
.tagName
)) {
569 var p
= document
.createElement("p");
570 while (SC
.firstChild
) p
.appendChild(SC
.firstChild
);
571 SC
.parentNode
.insertBefore(p
, SC
);
572 HTMLArea
.removeFromParent(SC
);
573 var r
= range
.cloneRange();
574 r
.setStartBefore(newr
);
577 this.emptySelection(sel
);
578 this.addRangeToSelection(sel
, r
);
586 * Enter event handler
588 HTMLArea
.prototype._checkInsertP
= function() {
591 var i
, left
, right
, rangeClone
,
592 sel
= this._getSelection(),
593 range
= this._createRange(sel
),
594 p
= this.getAllAncestors(),
598 for (i
= 0; i
< p
.length
; ++i
) {
599 if (HTMLArea
.isBlockElement(p
[i
]) && !/^(html|body|table|tbody|thead|tr)$/i.test(p
[i
].nodeName
)) {
604 if (block
&& /^(td|th|table|tbody|thead|tr)$/i.test(block
.nodeName
) && this.config
.buttons
.table
&& this.config
.buttons
.table
.disableEnterParagraphs
) return false;
605 if (!range
.collapsed
) {
606 range
.deleteContents();
608 this.emptySelection(sel
);
609 if (!block
|| /^(td|div)$/i.test(block
.nodeName
)) {
610 if (!block
) var block
= doc
.body
;
611 if (block
.hasChildNodes()) {
612 rangeClone
= range
.cloneRange();
613 rangeClone
.setStartBefore(block
.firstChild
);
614 // Working around Opera issue: The following gives a range exception
615 // rangeClone.surroundContents(left = doc.createElement("p"));
616 left
= doc
.createElement("p");
617 left
.appendChild(rangeClone
.extractContents());
618 if (!left
.textContent
&& !left
.getElementsByTagName("img") && !left
.getElementsByTagName("table")) {
619 left
.innerHTML
= "<br />";
621 if (block
.hasChildNodes()) {
622 left
= block
.insertBefore(left
, block
.firstChild
);
624 left
= block
.appendChild(left
);
627 range
.setEndAfter(block
.lastChild
);
628 range
.setStartAfter(left
);
629 // Working around Safari issue: The following gives a range exception
630 // range.surroundContents(right = doc.createElement("p"));
631 right
= doc
.createElement("p");
632 right
.appendChild(range
.extractContents());
633 if (!right
.textContent
&& !left
.getElementsByTagName("img") && !left
.getElementsByTagName("table")) {
634 right
.innerHTML
= "<br />";
636 block
.appendChild(right
);
639 var first
= block
.firstChild
;
640 if (first
) block
.removeChild(first
);
641 right
= doc
.createElement("p");
642 if (HTMLArea
.is_safari
|| HTMLArea
.is_opera
) {
643 right
.innerHTML
= "<br />";
645 right
= block
.appendChild(right
);
647 this.selectNodeContents(right
, true);
649 range
.setEndAfter(block
);
650 var df
= range
.extractContents(), left_empty
= false;
651 if (!/\S/.test(block
.innerHTML
)) {
652 if (!HTMLArea
.is_opera
) {
653 block
.innerHTML
= "<br />";
659 if (!/\S/.test(p
.innerHTML
)) {
660 if (/^h[1-6]$/i.test(p
.nodeName
)) {
661 p
= this.convertNode(p
, "p");
663 if (!HTMLArea
.is_opera
) {
664 p
.innerHTML
= "<br />";
667 if(/^li$/i.test(p
.nodeName
) && left_empty
&& !block
.nextSibling
) {
668 left
= block
.parentNode
;
669 left
.removeChild(block
);
670 range
.setEndAfter(left
);
671 range
.collapse(false);
672 p
= this.convertNode(p
, /^li$/i.test(left
.parentNode
.nodeName
) ? "br" : "p");
674 range
.insertNode(df
);
675 // Remove any anchor created empty
676 if (p
.previousSibling
) {
677 var a
= p
.previousSibling
.lastChild
;
678 if (a
&& /^a$/i.test(a
.nodeName
) && !/\S/.test(a
.innerHTML
)) HTMLArea
.removeFromParent(a
);
680 this.selectNodeContents(p
, true);
682 if (block
.nodeName
.toLowerCase() === "li") {
683 p
= doc
.createElement("li");
685 p
= doc
.createElement("p");
687 if (!HTMLArea
.is_opera
) {
688 p
.innerHTML
= "<br />";
690 if (block
.nextSibling
) {
691 p
= block
.parentNode
.insertBefore(p
, block
.nextSibling
);
693 p
= block
.parentNode
.appendChild(p
);
695 this.selectNodeContents(p
, true);
698 this.scrollToCaret();
703 * Detect emails and urls as they are typed in Mozilla
704 * Borrowed from Xinha (is not htmlArea) - http://xinha.gogo.co.nz/
706 HTMLArea
.prototype._detectURL
= function(ev
) {
708 var s
= this._getSelection();
709 var autoWrap
= function (textNode
, tag
) {
710 var rightText
= textNode
.nextSibling
;
711 if (typeof(tag
) == 'string') tag
= editor
._doc
.createElement(tag
);
712 var a
= textNode
.parentNode
.insertBefore(tag
, rightText
);
713 HTMLArea
.removeFromParent(textNode
);
714 a
.appendChild(textNode
);
715 rightText
.data
+= " ";
716 s
.collapse(rightText
, rightText
.data
.length
);
717 HTMLArea
._stopEvent(ev
);
719 editor
._unLink
= function() {
720 var t
= a
.firstChild
;
722 a
.parentNode
.insertBefore(t
, a
);
723 HTMLArea
.removeFromParent(a
);
724 t
.parentNode
.normalize();
725 editor
._unLink
= null;
726 editor
._unlinkOnUndo
= false;
729 editor
._unlinkOnUndo
= true;
734 // Space or Enter or >, see if the text just typed looks like a URL, or email address and link it accordingly
736 if(ev
.shiftKey
|| editor
.config
.disableEnterParagraphs
) break;
739 if(s
&& s
.isCollapsed
&& s
.anchorNode
.nodeType
== 3 && s
.anchorNode
.data
.length
> 3 && s
.anchorNode
.data
.indexOf('.') >= 0) {
740 var midStart
= s
.anchorNode
.data
.substring(0,s
.anchorOffset
).search(/[a-zA-Z0-9]+\S{3,}$/);
741 if(midStart
== -1) break;
742 if(this._getFirstAncestor(s
, 'a')) break; // already in an anchor
743 var matchData
= s
.anchorNode
.data
.substring(0,s
.anchorOffset
).replace(/^.*?(\S*)$/, '$1');
744 if (matchData
.indexOf('@') != -1) {
745 var m
= matchData
.match(HTMLArea
.RE_email
);
747 var leftText
= s
.anchorNode
;
748 var rightText
= leftText
.splitText(s
.anchorOffset
);
749 var midText
= leftText
.splitText(midStart
);
750 var midEnd
= midText
.data
.search(/[^a-zA-Z0-9\.@_\-]/);
751 if (midEnd
!= -1) var endText
= midText
.splitText(midEnd
);
752 autoWrap(midText
, 'a').href
= 'mailto:' + m
[0];
756 var m
= matchData
.match(HTMLArea
.RE_url
);
758 var leftText
= s
.anchorNode
;
759 var rightText
= leftText
.splitText(s
.anchorOffset
);
760 var midText
= leftText
.splitText(midStart
);
761 var midEnd
= midText
.data
.search(/[^a-zA-Z0-9\._\-\/\&\?=:@]/);
762 if (midEnd
!= -1) var endText
= midText
.splitText(midEnd
);
763 autoWrap(midText
, 'a').href
= (m
[1] ? m
[1] : 'http://') + m
[2];
769 if(ev
.keyCode
== 27 || (editor
._unlinkOnUndo
&& ev
.ctrlKey
&& ev
.which
== 122) ) {
772 HTMLArea
._stopEvent(ev
);
775 } else if(ev
.which
|| ev
.keyCode
== 8 || ev
.keyCode
== 46) {
776 this._unlinkOnUndo
= false;
777 if(s
.anchorNode
&& s
.anchorNode
.nodeType
== 3) {
778 // See if we might be changing a link
779 var a
= this._getFirstAncestor(s
, 'a');
780 if(!a
) break; // not an anchor
781 if(!a
._updateAnchTimeout
) {
782 if(s
.anchorNode
.data
.match(HTMLArea
.RE_email
) && (a
.href
.match('mailto:' + s
.anchorNode
.data
.trim()))) {
783 var textNode
= s
.anchorNode
;
784 var fn
= function() {
785 a
.href
= 'mailto:' + textNode
.data
.trim();
786 a
._updateAnchTimeout
= setTimeout(fn
, 250);
788 a
._updateAnchTimeout
= setTimeout(fn
, 250);
791 var m
= s
.anchorNode
.data
.match(HTMLArea
.RE_url
);
792 if(m
&& a
.href
.match(s
.anchorNode
.data
.trim())) {
793 var textNode
= s
.anchorNode
;
794 var fn
= function() {
795 var m
= textNode
.data
.match(HTMLArea
.RE_url
);
796 a
.href
= (m
[1] ? m
[1] : 'http://') + m
[2];
797 a
._updateAnchTimeout
= setTimeout(fn
, 250);
799 a
._updateAnchTimeout
= setTimeout(fn
, 250);