f92710a3b720e2d64bab6628feb6b05f49ba6545
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / htmlarea / htmlarea-gecko.js
1 /***************************************************************
2 * Copyright notice
3 *
4 * (c) 2002-2004, interactivetools.com, inc.
5 * (c) 2003-2004 dynarch.com
6 * (c) 2004-2007 Stanislas Rolland <stanislas.rolland(arobas)fructifor.ca>
7 * All rights reserved
8 *
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.
14 *
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.
19 *
20 *
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.
25 *
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.
28 *
29 * This copyright notice MUST APPEAR in all copies of the script!
30 ***************************************************************/
31 /*
32 * TYPO3 CVS ID: $Id$
33 */
34
35 /***************************************************
36 * GECKO-SPECIFIC FUNCTIONS
37 ***************************************************/
38
39 /***************************************************
40 * MOZILLA/FIREFOX EDIT MODE INITILIZATION
41 ***************************************************/
42
43 HTMLArea.prototype._initEditMode = function () {
44 // We can't set designMode when we are in a hidden TYPO3 tab
45 // Then we will set it when the tab comes in the front.
46 var isNested = false;
47 var allDisplayed = true;
48
49 if (this.nested.sorted && this.nested.sorted.length) {
50 isNested = true;
51 allDisplayed = HTMLArea.allElementsAreDisplayed(this.nested.sorted);
52 }
53
54 if (!HTMLArea.is_wamcom) {
55 try {
56 if (!isNested || allDisplayed) this._doc.designMode = "on";
57 } catch(e) { }
58 } else {
59 try {
60 this._doc.designMode = "on";
61 } catch(e) {
62 if (!isNested || allDisplayed) {
63 this._doc.open();
64 this._doc.close();
65 this._initIframeTimer = window.setTimeout("HTMLArea.initIframe(" + this._editorNumber + ");", 500);
66 return false;
67 }
68 }
69 }
70 // When the TYPO3 TCA feature div2tab is used, the editor iframe may become hidden with style.display = "none"
71 // 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"
72 // Here we rely on TYPO3 naming conventions for the div id and class name
73 if (this.nested.sorted && this.nested.sorted.length) {
74 var nestedObj, listenerFunction;
75 for (var i=0, length=this.nested.sorted.length; i < length; i++) {
76 nestedObj = document.getElementById(this.nested.sorted[i]);
77 listenerFunction = HTMLArea.NestedListener(this, nestedObj, false);
78 HTMLArea._addEvent(nestedObj, 'DOMAttrModified', listenerFunction);
79 }
80 }
81 try { this._doc.execCommand("useCSS", false, !this.config.useCSS); } catch (e) {};
82 try { this._doc.execCommand("styleWithCSS", false, this.config.useCSS); } catch (e) {};
83 return true;
84 };
85
86 /***************************************************
87 * SELECTIONS AND RANGES
88 ***************************************************/
89
90 /*
91 * Get the current selection object
92 */
93 HTMLArea.prototype._getSelection = function() {
94 if (HTMLArea.is_safari) return window.getSelection();
95 return this._iframe.contentWindow.getSelection();
96 };
97
98 /*
99 * Create a range for the current selection
100 */
101 HTMLArea.prototype._createRange = function(sel) {
102 if (HTMLArea.is_safari) {
103 var range = this._doc.createRange();
104 if (typeof(sel) == "undefined") return range;
105 switch (sel.type) {
106 case "Range":
107 range.setStart(sel.baseNode,sel.baseOffset);
108 range.setEnd(sel.extentNode,sel.extentOffset);
109 break;
110 case "Caret":
111 range.setStart(sel.baseNode,sel.baseOffset);
112 range.setEnd(sel.baseNode,sel.baseOffset);
113 break;
114 case "None":
115 range.setStart(this._doc.body,0);
116 range.setEnd(this._doc.body,0);
117 }
118 return range;
119 }
120 if (typeof(sel) == "undefined") return this._doc.createRange();
121 try {
122 return sel.getRangeAt(0);
123 } catch(e) {
124 return this._doc.createRange();
125 }
126 };
127
128 /*
129 * Select a node AND the contents inside the node
130 */
131 HTMLArea.prototype.selectNode = function(node,pos) {
132 this.focusEditor();
133 var sel = this._getSelection();
134 var range = this._doc.createRange();
135 if (node.nodeType == 1 && node.tagName.toLowerCase() == "body") range.selectNodeContents(node);
136 else range.selectNode(node);
137 if ((typeof(pos) != "undefined")) range.collapse(pos);
138 if (HTMLArea.is_safari) {
139 sel.empty();
140 sel.setBaseAndExtent(range.startContainer,range.startOffset,range.endContainer,range.endOffset);
141 } else {
142 sel.removeAllRanges();
143 sel.addRange(range);
144 }
145 };
146
147 /*
148 * Select ONLY the contents inside the given node
149 */
150 HTMLArea.prototype.selectNodeContents = function(node,pos) {
151 this.focusEditor();
152 var sel = this._getSelection();
153 var range = this._doc.createRange();
154 range.selectNodeContents(node);
155 if ((typeof(pos) != "undefined")) range.collapse(pos);
156 if (HTMLArea.is_safari) {
157 sel.empty();
158 sel.setBaseAndExtent(range.startContainer,range.startOffset,range.endContainer,range.endOffset);
159 } else {
160 sel.removeAllRanges();
161 sel.addRange(range);
162 }
163 };
164
165 /*
166 * Retrieve the HTML contents of selected block
167 */
168 HTMLArea.prototype.getSelectedHTML = function() {
169 var sel = this._getSelection();
170 var range = this._createRange(sel);
171 var cloneContents = "";
172 try {cloneContents = range.cloneContents();} catch(e) { }
173 return (cloneContents ? HTMLArea.getHTML(cloneContents,false,this) : "");
174 };
175
176 /*
177 * Retrieve simply HTML contents of the selected block, IE ignoring control ranges
178 */
179 HTMLArea.prototype.getSelectedHTMLContents = function() {
180 return this.getSelectedHTML();
181 };
182
183 /*
184 * Get the deepest node that contains both endpoints of the current selection.
185 */
186 HTMLArea.prototype.getParentElement = function(sel,range) {
187 if(!sel) var sel = this._getSelection();
188 if (typeof(range) == "undefined") var range = this._createRange(sel);
189 try {
190 var p = range.commonAncestorContainer;
191 if(!range.collapsed && range.startContainer == range.endContainer &&
192 range.startOffset - range.endOffset <= 1 && range.startContainer.hasChildNodes())
193 p = range.startContainer.childNodes[range.startOffset];
194 while (p.nodeType == 3) {p = p.parentNode;}
195 return p;
196 } catch (e) {
197 return this._doc.body;
198 }
199 };
200
201 /*
202 * Get the selected element, if any. That is, the element that you have last selected in the "path"
203 * at the bottom of the editor, or a "control" (eg image)
204 *
205 * @returns null | element
206 * Borrowed from Xinha (is not htmlArea) - http://xinha.gogo.co.nz/
207 */
208 HTMLArea.prototype._activeElement = function(sel) {
209 if(sel == null) return null;
210 if(this._selectionEmpty(sel)) return null;
211 // Check if the selection is not collapsed (something is selected) and if the anchor (start of selection) is an element.
212 if(!sel.isCollapsed && sel.anchorNode.nodeType == 1) return sel.anchorNode;
213 else return null;
214 };
215
216 /*
217 * Determine if the current selection is empty or not.
218 */
219 HTMLArea.prototype._selectionEmpty = function(sel) {
220 if (!sel) return true;
221 if (typeof(sel.isCollapsed) != 'undefined') {
222 if (HTMLArea.is_opera) this._createRange(sel).collapsed;
223 else sel.isCollapsed;
224 } else {
225 return true;
226 }
227 };
228
229 /***************************************************
230 * DOM TREE MANIPULATION
231 ***************************************************/
232
233 /*
234 * Insert a node at the current position.
235 * Delete the current selection, if any.
236 * Split the text node, if needed.
237 */
238 HTMLArea.prototype.insertNodeAtSelection = function(toBeInserted) {
239 this.focusEditor();
240 var sel = this._getSelection(),
241 range = this._createRange(sel),
242 node = range.startContainer,
243 pos = range.startOffset,
244 selnode = toBeInserted;
245 if (HTMLArea.is_safari) sel.empty();
246 else sel.removeAllRanges();
247 range.deleteContents();
248 switch (node.nodeType) {
249 case 3: // Node.TEXT_NODE: we have to split it at the caret position.
250 if(toBeInserted.nodeType == 3) {
251 node.insertData(pos,toBeInserted.data);
252 range = this._createRange();
253 range.setEnd(node, pos + toBeInserted.length);
254 range.setStart(node, pos + toBeInserted.length);
255 if (HTMLArea.is_safari) sel.setBaseAndExtent(range.startContainer, range.startOffset, range.endContainer, range.endOffset);
256 else sel.addRange(range);
257 } else {
258 node = node.splitText(pos);
259 if (toBeInserted.nodeType == 11) selnode = selnode.lastChild;
260 node = node.parentNode.insertBefore(toBeInserted, node);
261 this.selectNode(selnode, false);
262 this.updateToolbar();
263 }
264 break;
265 case 1:
266 if (toBeInserted.nodeType == 11) selnode = selnode.lastChild;
267 node = node.insertBefore(toBeInserted, node.childNodes[pos]);
268 this.selectNode(selnode, false);
269 this.updateToolbar();
270 break;
271 }
272 };
273
274 /*
275 * Insert HTML source code at the current position.
276 * Delete the current selection, if any.
277 */
278 HTMLArea.prototype.insertHTML = function(html) {
279 this.focusEditor();
280 var fragment = this._doc.createDocumentFragment();
281 var div = this._doc.createElement("div");
282 div.innerHTML = html;
283 while (div.firstChild) {fragment.appendChild(div.firstChild);}
284 this.insertNodeAtSelection(fragment);
285 };
286
287 /***************************************************
288 * EVENTS HANDLERS
289 ***************************************************/
290
291 /*
292 * TYPO3 hidden tab and inline event listener (gets event calls)
293 */
294 HTMLArea.NestedListener = function (editor,nestedObj,noOpenCloseAction) {
295 return (function(ev) {
296 if(!ev) var ev = window.event;
297 HTMLArea.NestedHandler(ev,editor,nestedObj,noOpenCloseAction);
298 });
299 };
300
301 /*
302 * TYPO3 hidden tab and inline event handler (performs actions on event calls)
303 */
304 HTMLArea.NestedHandler = function(ev,editor,nestedObj,noOpenCloseAction) {
305 window.setTimeout(function() {
306 var target = (ev.target) ? ev.target : ev.srcElement;
307 if(target == nestedObj && editor._editMode == "wysiwyg" && ev.attrName=='style' && (target.style.display == '' || target.style.display == 'block')) {
308 // Check if all affected nested elements are displayed (style.display!='none'):
309 if (HTMLArea.allElementsAreDisplayed(editor.nested.sorted)) {
310 window.setTimeout(function() {
311 try {
312 editor._doc.designMode = "on";
313 if (editor.config.sizeIncludesToolbar && editor._initialToolbarOffsetHeight != editor._toolbar.offsetHeight) {
314 editor.sizeIframe(-2);
315 }
316 try { editor._doc.execCommand("useCSS", false, !this.config.useCSS); } catch (e) {};
317 try { editor._doc.execCommand("styleWithCSS", false, this.config.useCSS); } catch (e) {};
318 } catch(e) {
319 // If an event of a parent tab ("nested tabs") is triggered, the following lines should not be
320 // processed, because this causes some trouble on all event handlers...
321 if (!noOpenCloseAction) {
322 editor._doc.open();
323 editor._doc.close();
324 }
325 editor.initIframe();
326 }
327 }, 50);
328 }
329 HTMLArea._stopEvent(ev);
330 }
331 }, 50);
332 };
333
334 /*
335 * Handle statusbar element events
336 */
337 HTMLArea.statusBarHandler = function (ev) {
338 if(!ev) var ev = window.event;
339 var target = (ev.target) ? ev.target : ev.srcElement;
340 var editor = target.editor;
341 target.blur();
342 editor.selectNode(target.el);
343 editor.updateToolbar(true);
344 switch (ev.type) {
345 case "click" :
346 case "mousedown" :
347 HTMLArea._stopEvent(ev);
348 return false;
349 case "contextmenu" :
350 return editor.plugins["ContextMenu"] ? editor.plugins["ContextMenu"].instance.popupMenu(ev,target.el) : false;
351 }
352 };
353
354 /*
355 * Paste exception handler
356 */
357 HTMLArea.prototype._mozillaPasteException = function(cmdID, UI, param) {
358 // Mozilla lauches an exception, but can paste anyway on ctrl-V
359 // UI is false on keyboard shortcut, and undefined on button click
360 if(typeof(UI) != "undefined") {
361 this._doc.execCommand(cmdID, UI, param);
362 if (cmdID == "Paste" && this.config.killWordOnPaste) HTMLArea._wordClean(this._doc.body);
363 } else if (this.config.enableMozillaExtension) {
364 if (confirm(HTMLArea.I18N.msg["Allow-Clipboard-Helper-Extension"])) {
365 if (InstallTrigger.enabled()) {
366 HTMLArea._mozillaXpi = new Object();
367 HTMLArea._mozillaXpi["AllowClipboard Helper"] = _editor_mozAllowClipboard_url;
368 InstallTrigger.install(HTMLArea._mozillaXpi,HTMLArea._mozillaInstallCallback);
369 } else {
370 alert(HTMLArea.I18N.msg["Mozilla-Org-Install-Not-Enabled"]);
371 HTMLArea._appendToLog("WARNING [HTMLArea::execCommand]: Mozilla install was not enabled.");
372 return;
373 }
374 }
375 } else if (confirm(HTMLArea.I18N.msg["Moz-Clipboard"])) {
376 window.open("http://mozilla.org/editor/midasdemo/securityprefs.html");
377 }
378 }
379
380 HTMLArea._mozillaInstallCallback = function(url,returnCode) {
381 if (returnCode == 0) {
382 if (HTMLArea._mozillaXpi["TYPO3 htmlArea RTE Preferences"]) alert(HTMLArea.I18N.msg["Moz-Extension-Success"]);
383 else alert(HTMLArea.I18N.msg["Allow-Clipboard-Helper-Extension-Success"]);
384 return;
385 } else {
386 alert(HTMLArea.I18N.msg["Moz-Extension-Failure"]);
387 HTMLArea._appendToLog("WARNING [HTMLArea::execCommand]: Mozilla install return code was: " + returnCode + ".");
388 return;
389 }
390 };
391
392 /*
393 * Backspace event handler
394 */
395 HTMLArea.prototype._checkBackspace = function() {
396 var self = this;
397 self.focusEditor();
398 var sel = self._getSelection();
399 var range = self._createRange(sel);
400 var SC = range.startContainer;
401 var SO = range.startOffset;
402 var EC = range.endContainer;
403 var EO = range.endOffset;
404 var newr = SC.nextSibling;
405 while (SC.nodeType == 3 || /^a$/i.test(SC.tagName)) SC = SC.parentNode;
406 if (!self.config.disableEnterParagraphs && /^td$/i.test(SC.parentNode.tagName) && SC.parentNode.firstChild == SC && SO == 0 && range.collapsed) return true;
407 window.setTimeout(function() {
408 // Remove br tag inserted by Mozilla
409 if (!self.config.disableEnterParagraphs && (/^p$/i.test(SC.tagName) || !/\S/.test(SC.tagName)) && SO == 0) {
410 if (SC.firstChild && /^br$/i.test(SC.firstChild.tagName)) {
411 HTMLArea.removeFromParent(SC.firstChild);
412 return true;
413 }
414 }
415 if (!/\S/.test(SC.tagName)) {
416 var p = document.createElement("p");
417 while (SC.firstChild) p.appendChild(SC.firstChild);
418 SC.parentNode.insertBefore(p, SC);
419 HTMLArea.removeFromParent(SC);
420 var r = range.cloneRange();
421 r.setStartBefore(newr);
422 r.setEndAfter(newr);
423 r.extractContents();
424 if(HTMLArea.is_safari) {
425 sel.empty();
426 sel.setBaseAndExtent(r.startContainer,r.startOffset,r.endContainer,r.endOffset);
427 } else {
428 sel.removeAllRanges();
429 sel.addRange(r);
430 }
431 return true;
432 }
433 },10);
434 return false;
435 };
436
437 /*
438 * Enter event handler
439 */
440 HTMLArea.prototype._checkInsertP = function(ev) {
441 var editor = this;
442 this.focusEditor();
443 var i, left, right, rangeClone,
444 sel = this._getSelection(),
445 range = this._createRange(sel),
446 p = this.getAllAncestors(),
447 block = null,
448 a = null,
449 doc = this._doc;
450
451 for (i = 0; i < p.length; ++i) {
452 if (HTMLArea.isBlockElement(p[i]) && !/html|body|table|tbody|tr/i.test(p[i].tagName)) {
453 block = p[i];
454 break;
455 }
456 }
457 if (!range.collapsed) range.deleteContents();
458 if (HTMLArea.is_safari) sel.empty();
459 else sel.removeAllRanges();
460 if (!block || /^(td|div)$/i.test(block.tagName)) {
461 if (!block) var block = doc.body;
462 if (/\S/.test(HTMLArea.getInnerText(block))) {
463 rangeClone = range.cloneRange();
464 rangeClone.setStartBefore(block.firstChild);
465 rangeClone.surroundContents(left = doc.createElement('p'));
466 if (!/\S/.test(HTMLArea.getInnerText(left))) {
467 // Remove any element created empty
468 a = left.lastChild;
469 if (a && !/\S/.test(HTMLArea.getInnerText(a))) HTMLArea.removeFromParent(a);
470 left.appendChild(doc.createElement('br'));
471 }
472 left.normalize();
473 range.setEndAfter(block.lastChild);
474 range.surroundContents(right = doc.createElement('p'));
475 // Remove any element created empty
476 a = right.previousSibling;
477 if (a && !/\S/.test(HTMLArea.getInnerText(a))) HTMLArea.removeFromParent(a);
478 if (!/\S/.test(HTMLArea.getInnerText(right))) {
479 // Remove any element created empty
480 a = right.lastChild;
481 if (a && !/\S/.test(HTMLArea.getInnerText(a))) HTMLArea.removeFromParent(a);
482 right.appendChild(doc.createElement('br'));
483 }
484 right.normalize();
485 } else {
486 range = doc.createRange();
487 var first = block.firstChild;
488 block.removeChild(first);
489 block.appendChild(right = doc.createElement('p'));
490 right.appendChild(first);
491 }
492 range.selectNodeContents(right);
493 } else {
494 range.setEndAfter(block);
495 var df = range.extractContents(), left_empty = false;
496 if(!/\S/.test(HTMLArea.getInnerText(block))) {
497 block.innerHTML = "<br />";
498 left_empty = true;
499 }
500 p = df.firstChild;
501 if (p) {
502 if(!/\S/.test(HTMLArea.getInnerText(p))) {
503 if (/^h[1-6]$/i.test(p.tagName)) p = this.convertNode(p,"p");
504 p.innerHTML = "<br />";
505 }
506 if(/^li$/i.test(p.tagName) && left_empty && !block.nextSibling) {
507 left = block.parentNode;
508 left.removeChild(block);
509 range.setEndAfter(left);
510 range.collapse(false);
511 p = this.convertNode(p, /^li$/i.test(left.parentNode.tagName) ? "br" : "p");
512 }
513 range.insertNode(df);
514 // Remove any anchor created empty
515 var a = p.previousSibling.lastChild;
516 if (a && /^a$/i.test(a.tagName) && !/\S/.test(a.innerHTML)) HTMLArea.removeFromParent(a);
517 range.selectNodeContents(p);
518 }
519 }
520 range.collapse(true);
521 if (HTMLArea.is_safari) sel.setBaseAndExtent(r.startContainer,r.startOffset,r.endContainer,r.endOffset);
522 else sel.addRange(range);
523 this.scrollToCaret();
524 return true;
525 };
526
527 /*
528 * Detect emails and urls as they are typed in Mozilla
529 * Borrowed from Xinha (is not htmlArea) - http://xinha.gogo.co.nz/
530 */
531 HTMLArea.prototype._detectURL = function(ev) {
532 var editor = this;
533 var s = this._getSelection();
534 var autoWrap = function (textNode, tag) {
535 var rightText = textNode.nextSibling;
536 if (typeof(tag) == 'string') tag = editor._doc.createElement(tag);
537 var a = textNode.parentNode.insertBefore(tag, rightText);
538 HTMLArea.removeFromParent(textNode);
539 a.appendChild(textNode);
540 rightText.data += " ";
541 s.collapse(rightText, rightText.data.length);
542 HTMLArea._stopEvent(ev);
543
544 editor._unLink = function() {
545 var t = a.firstChild;
546 a.removeChild(t);
547 a.parentNode.insertBefore(t, a);
548 HTMLArea.removeFromParent(a);
549 t.parentNode.normalize();
550 editor._unLink = null;
551 editor._unlinkOnUndo = false;
552 };
553
554 editor._unlinkOnUndo = true;
555 return a;
556 };
557
558 switch(ev.which) {
559 // Space or Enter or >, see if the text just typed looks like a URL, or email address and link it accordingly
560 case 13: // Enter
561 if(ev.shiftKey || editor.config.disableEnterParagraphs) break;
562 //Space
563 case 32:
564 if(s && s.isCollapsed && s.anchorNode.nodeType == 3 && s.anchorNode.data.length > 3 && s.anchorNode.data.indexOf('.') >= 0) {
565 var midStart = s.anchorNode.data.substring(0,s.anchorOffset).search(/[a-zA-Z0-9]+\S{3,}$/);
566 if(midStart == -1) break;
567 if(this._getFirstAncestor(s, 'a')) break; // already in an anchor
568 var matchData = s.anchorNode.data.substring(0,s.anchorOffset).replace(/^.*?(\S*)$/, '$1');
569 if (matchData.indexOf('@') != -1) {
570 var m = matchData.match(HTMLArea.RE_email);
571 if(m) {
572 var leftText = s.anchorNode;
573 var rightText = leftText.splitText(s.anchorOffset);
574 var midText = leftText.splitText(midStart);
575 var midEnd = midText.data.search(/[^a-zA-Z0-9\.@_\-]/);
576 if (midEnd != -1) var endText = midText.splitText(midEnd);
577 autoWrap(midText, 'a').href = 'mailto:' + m[0];
578 break;
579 }
580 }
581 var m = matchData.match(HTMLArea.RE_url);
582 if(m) {
583 var leftText = s.anchorNode;
584 var rightText = leftText.splitText(s.anchorOffset);
585 var midText = leftText.splitText(midStart);
586 var midEnd = midText.data.search(/[^a-zA-Z0-9\._\-\/\&\?=:@]/);
587 if (midEnd != -1) var endText = midText.splitText(midEnd);
588 autoWrap(midText, 'a').href = (m[1] ? m[1] : 'http://') + m[2];
589 break;
590 }
591 }
592 break;
593 default:
594 if(ev.keyCode == 27 || (editor._unlinkOnUndo && ev.ctrlKey && ev.which == 122) ) {
595 if(this._unLink) {
596 this._unLink();
597 HTMLArea._stopEvent(ev);
598 }
599 break;
600 } else if(ev.which || ev.keyCode == 8 || ev.keyCode == 46) {
601 this._unlinkOnUndo = false;
602 if(s.anchorNode && s.anchorNode.nodeType == 3) {
603 // See if we might be changing a link
604 var a = this._getFirstAncestor(s, 'a');
605 if(!a) break; // not an anchor
606 if(!a._updateAnchTimeout) {
607 if(s.anchorNode.data.match(HTMLArea.RE_email) && (a.href.match('mailto:' + s.anchorNode.data.trim()))) {
608 var textNode = s.anchorNode;
609 var fn = function() {
610 a.href = 'mailto:' + textNode.data.trim();
611 a._updateAnchTimeout = setTimeout(fn, 250);
612 };
613 a._updateAnchTimeout = setTimeout(fn, 250);
614 break;
615 }
616 var m = s.anchorNode.data.match(HTMLArea.RE_url);
617 if(m && a.href.match(s.anchorNode.data.trim())) {
618 var textNode = s.anchorNode;
619 var fn = function() {
620 var m = textNode.data.match(HTMLArea.RE_url);
621 a.href = (m[1] ? m[1] : 'http://') + m[2];
622 a._updateAnchTimeout = setTimeout(fn, 250);
623 }
624 a._updateAnchTimeout = setTimeout(fn, 250);
625 }
626 }
627 }
628 }
629 break;
630 }
631 };