source: trunk/modules/Gecko/Gecko.js @ 998

Last change on this file since 998 was 998, checked in by ray, 11 years ago
  • #1201 Add inwardHtml/outwardHtml functions to Config object
  • Improved method for finding editing position in Gecko/Webkit?
  • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
File size: 23.8 KB
Line 
1
2  /*--------------------------------------:noTabs=true:tabSize=2:indentSize=2:--
3    --  Xinha (is not htmlArea) - http://xinha.gogo.co.nz/
4    --
5    --  Use of Xinha is granted by the terms of the htmlArea License (based on
6    --  BSD license)  please read license.txt in this package for details.
7    --
8    --  Xinha was originally based on work by Mihai Bazon which is:
9    --      Copyright (c) 2003-2004 dynarch.com.
10    --      Copyright (c) 2002-2003 interactivetools.com, inc.
11    --      This copyright notice MUST stay intact for use.
12    --
13    -- This is the Gecko compatability plugin, part of the Xinha core.
14    --
15    --  The file is loaded as a special plugin by the Xinha Core when
16    --  Xinha is being run under a Gecko based browser with the Midas
17    --  editing API.
18    --
19    --  It provides implementation and specialisation for various methods
20    --  in the core where different approaches per browser are required.
21    --
22    --  Design Notes::
23    --   Most methods here will simply be overriding Xinha.prototype.<method>
24    --   and should be called that, but methods specific to Gecko should
25    --   be a part of the Gecko.prototype, we won't trample on namespace
26    --   that way.
27    --
28    --  $HeadURL$
29    --  $LastChangedDate$
30    --  $LastChangedRevision$
31    --  $LastChangedBy$
32    --------------------------------------------------------------------------*/
33                                                   
34Gecko._pluginInfo = {
35  name          : "Gecko",
36  origin        : "Xinha Core",
37  version       : "$LastChangedRevision$".replace(/^[^:]*: (.*) \$$/, '$1'),
38  developer     : "The Xinha Core Developer Team",
39  developer_url : "$HeadURL$".replace(/^[^:]*: (.*) \$$/, '$1'),
40  sponsor       : "",
41  sponsor_url   : "",
42  license       : "htmlArea"
43};
44
45function Gecko(editor) {
46  this.editor = editor; 
47  editor.Gecko = this;
48}
49
50/** Allow Gecko to handle some key events in a special way.
51 */
52 
53Gecko.prototype.onKeyPress = function(ev)
54{
55  var editor = this.editor;
56  var s = editor.getSelection();
57 
58  // Handle shortcuts
59  if(editor.isShortCut(ev))
60  {
61    switch(editor.getKey(ev).toLowerCase())
62    {
63      case 'z':
64      {
65        if(editor._unLink && editor._unlinkOnUndo)
66        {
67          Xinha._stopEvent(ev);
68          editor._unLink();
69          editor.updateToolbar();
70          return true;
71        }
72      }
73      break;
74     
75      case 'a':
76      {
77        // KEY select all
78        sel = editor.getSelection();
79        sel.removeAllRanges();
80        range = editor.createRange();
81        range.selectNodeContents(editor._doc.body);
82        sel.addRange(range);
83        Xinha._stopEvent(ev);
84        return true;
85      }
86      break;
87     
88      case 'v':
89      {
90        // If we are not using htmlareaPaste, don't let Xinha try and be fancy but let the
91        // event be handled normally by the browser (don't stopEvent it)
92        if(!editor.config.htmlareaPaste)
93        {         
94          return true;
95        }
96      }
97      break;
98    }
99  }
100 
101  // Handle normal characters
102  switch(editor.getKey(ev))
103  {
104    // Space, see if the text just typed looks like a URL, or email address
105    // and link it appropriatly
106    case ' ':
107    {     
108      var autoWrap = function (textNode, tag)
109      {
110        var rightText = textNode.nextSibling;
111        if ( typeof tag == 'string')
112        {
113          tag = editor._doc.createElement(tag);
114        }
115        var a = textNode.parentNode.insertBefore(tag, rightText);
116        Xinha.removeFromParent(textNode);
117        a.appendChild(textNode);
118        rightText.data = ' ' + rightText.data;
119   
120        s.collapse(rightText, 1);
121   
122        editor._unLink = function()
123        {
124          var t = a.firstChild;
125          a.removeChild(t);
126          a.parentNode.insertBefore(t, a);
127          Xinha.removeFromParent(a);
128          editor._unLink = null;
129          editor._unlinkOnUndo = false;
130        };
131        editor._unlinkOnUndo = true;
132   
133        return a;
134      };
135 
136      if ( editor.config.convertUrlsToLinks && s && s.isCollapsed && s.anchorNode.nodeType == 3 && s.anchorNode.data.length > 3 && s.anchorNode.data.indexOf('.') >= 0 )
137      {
138        var midStart = s.anchorNode.data.substring(0,s.anchorOffset).search(/\S{4,}$/);
139        if ( midStart == -1 )
140        {
141          break;
142        }
143
144        if ( editor._getFirstAncestor(s, 'a') )
145        {
146          break; // already in an anchor
147        }
148
149        var matchData = s.anchorNode.data.substring(0,s.anchorOffset).replace(/^.*?(\S*)$/, '$1');
150
151        var mEmail = matchData.match(Xinha.RE_email);
152        if ( mEmail )
153        {
154          var leftTextEmail  = s.anchorNode;
155          var rightTextEmail = leftTextEmail.splitText(s.anchorOffset);
156          var midTextEmail   = leftTextEmail.splitText(midStart);
157
158          autoWrap(midTextEmail, 'a').href = 'mailto:' + mEmail[0];
159          break;
160        }
161
162        RE_date = /([0-9]+\.)+/; //could be date or ip or something else ...
163        RE_ip = /(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/;
164        var mUrl = matchData.match(Xinha.RE_url);
165        if ( mUrl )
166        {
167          if (RE_date.test(matchData))
168          {
169            break; //ray: disabling linking of IP numbers because of general bugginess (see Ticket #1085)
170            /*if (!RE_ip.test(matchData))
171            {
172              break;
173            }*/
174          }
175          var leftTextUrl  = s.anchorNode;
176          var rightTextUrl = leftTextUrl.splitText(s.anchorOffset);
177          var midTextUrl   = leftTextUrl.splitText(midStart);
178          autoWrap(midTextUrl, 'a').href = (mUrl[1] ? mUrl[1] : 'http://') + mUrl[2];
179          break;
180        }
181      }
182    }
183    break;   
184  }
185 
186  // Handle special keys
187  switch ( ev.keyCode )
188  {   
189/*  This is now handled by a plugin 
190    case 13: // ENTER
191
192    break;*/
193
194    case 27: // ESCAPE
195    {
196      if ( editor._unLink )
197      {
198        editor._unLink();
199        Xinha._stopEvent(ev);
200      }
201      break;
202    }
203    break;
204   
205    case 8: // KEY backspace
206    case 46: // KEY delete
207    {
208      // We handle the mozilla backspace directly??
209      if ( !ev.shiftKey && this.handleBackspace() )
210      {
211        Xinha._stopEvent(ev);
212      }
213    }
214   
215    default:
216    {
217        editor._unlinkOnUndo = false;
218
219        // Handle the "auto-linking", specifically this bit of code sets up a handler on
220        // an self-titled anchor (eg <a href="http://www.gogo.co.nz/">www.gogo.co.nz</a>)
221        // when the text content is edited, such that it will update the href on the anchor
222       
223        if ( s.anchorNode && s.anchorNode.nodeType == 3 )
224        {
225          // See if we might be changing a link
226          var a = editor._getFirstAncestor(s, 'a');
227          // @todo: we probably need here to inform the setTimeout below that we not changing a link and not start another setTimeout
228          if ( !a )
229          {
230            break; // not an anchor
231          }
232         
233          if ( !a._updateAnchTimeout )
234          {
235            if ( s.anchorNode.data.match(Xinha.RE_email) && a.href.match('mailto:' + s.anchorNode.data.trim()) )
236            {
237              var textNode = s.anchorNode;
238              var fnAnchor = function()
239              {
240                a.href = 'mailto:' + textNode.data.trim();
241                // @fixme: why the hell do another timeout is started ?
242                //         This lead to never ending timer if we dont remove this line
243                //         But when removed, the email is not correctly updated
244                //
245                // - to fix this we should make fnAnchor check to see if textNode.data has
246                //   stopped changing for say 5 seconds and if so we do not make this setTimeout
247                a._updateAnchTimeout = setTimeout(fnAnchor, 250);
248              };
249              a._updateAnchTimeout = setTimeout(fnAnchor, 1000);
250              break;
251            }
252
253            var m = s.anchorNode.data.match(Xinha.RE_url);
254
255            if ( m && a.href.match(new RegExp( 'http(s)?://' + Xinha.escapeStringForRegExp( s.anchorNode.data.trim() ) ) ) )
256            {
257              var txtNode = s.anchorNode;
258              var fnUrl = function()
259              {
260                // Sometimes m is undefined becase the url is not an url anymore (was www.url.com and become for example www.url)
261                // ray: shouldn't the link be un-linked then?
262                m = txtNode.data.match(Xinha.RE_url);
263                if(m)
264                {
265                  a.href = (m[1] ? m[1] : 'http://') + m[2];
266                }
267               
268                // @fixme: why the hell do another timeout is started ?
269                //         This lead to never ending timer if we dont remove this line
270                //         But when removed, the url is not correctly updated
271                //
272                // - to fix this we should make fnUrl check to see if textNode.data has
273                //   stopped changing for say 5 seconds and if so we do not make this setTimeout
274                a._updateAnchTimeout = setTimeout(fnUrl, 250);
275              };
276              a._updateAnchTimeout = setTimeout(fnUrl, 1000);
277            }
278          }       
279        }               
280    }
281    break;
282  }
283
284  return false; // Let other plugins etc continue from here.
285}
286
287/** When backspace is hit, the Gecko onKeyPress will execute this method.
288 *  I don't remember what the exact purpose of this is though :-(
289 */
290 
291Gecko.prototype.handleBackspace = function()
292{
293  var editor = this.editor;
294  setTimeout(
295    function()
296    {
297      var sel   = editor.getSelection();
298      var range = editor.createRange(sel);
299      var SC = range.startContainer;
300      var SO = range.startOffset;
301      var EC = range.endContainer;
302      var EO = range.endOffset;
303      var newr = SC.nextSibling;
304      if ( SC.nodeType == 3 )
305      {
306        SC = SC.parentNode;
307      }
308      if ( ! ( /\S/.test(SC.tagName) ) )
309      {
310        var p = document.createElement("p");
311        while ( SC.firstChild )
312        {
313          p.appendChild(SC.firstChild);
314        }
315        SC.parentNode.insertBefore(p, SC);
316        Xinha.removeFromParent(SC);
317        var r = range.cloneRange();
318        r.setStartBefore(newr);
319        r.setEndAfter(newr);
320        r.extractContents();
321        sel.removeAllRanges();
322        sel.addRange(r);
323      }
324    },
325    10);
326};
327
328Gecko.prototype.inwardHtml = function(html)
329{
330   // Midas uses b and i internally instead of strong and em
331   // Xinha will use strong and em externally (see Xinha.prototype.outwardHtml)   
332   html = html.replace(/<(\/?)strong(\s|>|\/)/ig, "<$1b$2");
333   html = html.replace(/<(\/?)em(\s|>|\/)/ig, "<$1i$2");   
334   
335   // Both IE and Gecko use strike internally instead of del (#523)
336   // Xinha will present del externally (see Xinha.prototype.outwardHtml
337   html = html.replace(/<(\/?)del(\s|>|\/)/ig, "<$1strike$2");
338   
339   return html;
340}
341
342Gecko.prototype.outwardHtml = function(html)
343{
344  // ticket:56, the "greesemonkey" plugin for Firefox adds this junk,
345  // so we strip it out.  Original submitter gave a plugin, but that's
346  // a bit much just for this IMHO - james
347  html = html.replace(/<script[\s]*src[\s]*=[\s]*['"]chrome:\/\/.*?["']>[\s]*<\/script>/ig, '');
348
349  return html;
350}
351
352Gecko.prototype.onExecCommand = function(cmdID, UI, param)
353{   
354  try
355  {
356    // useCSS deprecated & replaced by styleWithCSS
357    this.editor._doc.execCommand('useCSS', false, true); //switch useCSS off (true=off)
358    this.editor._doc.execCommand('styleWithCSS', false, false); //switch styleWithCSS off     
359  } catch (ex) {}
360   
361  switch(cmdID)
362  {
363    case 'paste':
364    {
365      alert(Xinha._lc("The Paste button does not work in Mozilla based web browsers (technical security reasons). Press CTRL-V on your keyboard to paste directly."));
366      return true; // Indicate paste is done, stop command being issued to browser by Xinha.prototype.execCommand
367    }
368    break;
369    case 'removeformat':
370      var editor = this.editor;
371      var sel = editor.getSelection();
372      var selSave = editor.saveSelection(sel);
373      var range = editor.createRange(sel);
374
375      var els = editor._doc.body.getElementsByTagName('*');
376
377      var start = ( range.startContainer.nodeType == 1 ) ? range.startContainer : range.startContainer.parentNode;
378      var i, el;
379      if (sel.isCollapsed) range.selectNodeContents(editor._doc.body);
380     
381      for (i=0; i<els.length;i++)
382      {
383        el = els[i];
384        if ( range.isPointInRange(el, 0) || (els[i] == start && range.startOffset == 0))
385        {
386          el.removeAttribute('style');
387        }
388      }
389      this.editor._doc.execCommand(cmdID, UI, param);
390      editor.restoreSelection(selSave);
391      return true;
392    break;
393  }
394 
395  return false;
396}
397Gecko.prototype.onMouseDown = function(ev)
398{   
399  // Gecko doesn't select hr's on single click
400  if (ev.target.tagName.toLowerCase() == "hr")
401  {
402    var sel = this.editor.getSelection();
403    var range = this.editor.createRange(sel);
404    range.selectNode(ev.target);
405  }
406}
407
408
409/*--------------------------------------------------------------------------*/
410/*------- IMPLEMENTATION OF THE ABSTRACT "Xinha.prototype" METHODS ---------*/
411/*--------------------------------------------------------------------------*/
412
413/** Insert a node at the current selection point.
414 * @param toBeInserted DomNode
415 */
416
417Xinha.prototype.insertNodeAtSelection = function(toBeInserted)
418{
419  if ( toBeInserted.ownerDocument != this._doc ) // as of FF3, Gecko is strict regarding the ownerDocument of an element
420  {
421    try
422        {
423                toBeInserted = this._doc.adoptNode( toBeInserted );
424        } catch (e) {}
425  }
426  var sel = this.getSelection();
427  var range = this.createRange(sel);
428  // remove the current selection
429  sel.removeAllRanges();
430  range.deleteContents();
431  var node = range.startContainer;
432  var pos = range.startOffset;
433  var selnode = toBeInserted;
434 
435  switch ( node.nodeType )
436  {
437    case 3: // Node.TEXT_NODE
438      // we have to split it at the caret position.
439      if ( toBeInserted.nodeType == 3 )
440      {
441        // do optimized insertion
442        node.insertData(pos, toBeInserted.data);
443        range = this.createRange();
444        range.setEnd(node, pos + toBeInserted.length);
445        range.setStart(node, pos + toBeInserted.length);
446        sel.addRange(range);
447      }
448      else
449      {
450        node = node.splitText(pos);
451        if ( toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */ )
452        {
453          selnode = selnode.firstChild;
454        }
455        node.parentNode.insertBefore(toBeInserted, node);
456        this.selectNodeContents(selnode);
457        this.updateToolbar();
458      }
459    break;
460    case 1: // Node.ELEMENT_NODE
461      if ( toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */ )
462      {
463        selnode = selnode.firstChild;
464      }
465      node.insertBefore(toBeInserted, node.childNodes[pos]);
466      this.selectNodeContents(selnode);
467      this.updateToolbar();
468    break;
469  }
470};
471 
472/** Get the parent element of the supplied or current selection.
473 *  @param   sel optional selection as returned by getSelection
474 *  @returns DomNode
475 */
476 
477Xinha.prototype.getParentElement = function(sel)
478{
479  if ( typeof sel == 'undefined' )
480  {
481    sel = this.getSelection();
482  }
483  var range = this.createRange(sel);
484  try
485  {
486    var p = range.commonAncestorContainer;
487    if ( !range.collapsed && range.startContainer == range.endContainer &&
488        range.startOffset - range.endOffset <= 1 && range.startContainer.hasChildNodes() )
489    {
490      p = range.startContainer.childNodes[range.startOffset];
491    }
492
493    while ( p.nodeType == 3 )
494    {
495      p = p.parentNode;
496    }
497    return p;
498  }
499  catch (ex)
500  {
501    return null;
502  }
503};
504
505/**
506 * Returns the selected element, if any.  That is,
507 * the element that you have last selected in the "path"
508 * at the bottom of the editor, or a "control" (eg image)
509 *
510 * @returns null | DomNode
511 */
512
513Xinha.prototype.activeElement = function(sel)
514{
515  if ( ( sel === null ) || this.selectionEmpty(sel) )
516  {
517    return null;
518  }
519
520  // For Mozilla we just see if the selection is not collapsed (something is selected)
521  // and that the anchor (start of selection) is an element.  This might not be totally
522  // correct, we possibly should do a simlar check to IE?
523  if ( !sel.isCollapsed )
524  {     
525    if ( sel.anchorNode.childNodes.length > sel.anchorOffset && sel.anchorNode.childNodes[sel.anchorOffset].nodeType == 1 )
526    {
527      return sel.anchorNode.childNodes[sel.anchorOffset];
528    }
529    else if ( sel.anchorNode.nodeType == 1 )
530    {
531      return sel.anchorNode;
532    }
533    else
534    {
535      return null; // return sel.anchorNode.parentNode;
536    }
537  }
538  return null;
539};
540
541/**
542 * Determines if the given selection is empty (collapsed).
543 * @param selection Selection object as returned by getSelection
544 * @returns true|false
545 */
546 
547Xinha.prototype.selectionEmpty = function(sel)
548{
549  if ( !sel )
550  {
551    return true;
552  }
553
554  if ( typeof sel.isCollapsed != 'undefined' )
555  {     
556    return sel.isCollapsed;
557  }
558
559  return true;
560};
561
562/**
563 * Returns a range object to be stored
564 * and later restored with Xinha.prototype.restoreSelection()
565 *
566 * @returns Range
567 */
568Xinha.prototype.saveSelection = function()
569{
570  return this.createRange(this.getSelection()).cloneRange();
571}
572/**
573 * Restores a selection previously stored
574 * @param savedSelection Range object as returned by Xinha.prototype.restoreSelection()
575 */
576Xinha.prototype.restoreSelection = function(savedSelection)
577{
578  var sel = this.getSelection();
579  sel.removeAllRanges();
580  sel.addRange(savedSelection);
581}
582/**
583 * Selects the contents of the given node.  If the node is a "control" type element, (image, form input, table)
584 * the node itself is selected for manipulation.
585 *
586 * @param node DomNode
587 * @param pos  Set to a numeric position inside the node to collapse the cursor here if possible.
588 */
589 
590Xinha.prototype.selectNodeContents = function(node, pos)
591{
592  this.focusEditor();
593  this.forceRedraw();
594  var range;
595  var collapsed = typeof pos == "undefined" ? true : false;
596  var sel = this.getSelection();
597  range = this._doc.createRange();
598  if ( !node )
599  {
600     sel.removeAllRanges();
601     return;
602  }
603  // Tables and Images get selected as "objects" rather than the text contents
604  if ( collapsed && node.tagName && node.tagName.toLowerCase().match(/table|img|input|textarea|select/) )
605  {
606    range.selectNode(node);
607  }
608  else
609  {
610    range.selectNodeContents(node);
611    //(collapsed) && range.collapse(pos);
612  }
613  sel.removeAllRanges();
614  sel.addRange(range);
615};
616 
617/** Insert HTML at the current position, deleting the selection if any.
618 * 
619 *  @param html string
620 */
621 
622Xinha.prototype.insertHTML = function(html)
623{
624  var sel = this.getSelection();
625  var range = this.createRange(sel);
626  this.focusEditor();
627  // construct a new document fragment with the given HTML
628  var fragment = this._doc.createDocumentFragment();
629  var div = this._doc.createElement("div");
630  div.innerHTML = html;
631  while ( div.firstChild )
632  {
633    // the following call also removes the node from div
634    fragment.appendChild(div.firstChild);
635  }
636  // this also removes the selection
637  var node = this.insertNodeAtSelection(fragment);
638};
639
640/** Get the HTML of the current selection.  HTML returned has not been passed through outwardHTML.
641 *
642 * @returns string
643 */
644 
645Xinha.prototype.getSelectedHTML = function()
646{
647  var sel = this.getSelection();
648  if (sel.isCollapsed) return '';
649  var range = this.createRange(sel);
650  return Xinha.getHTML(range.cloneContents(), false, this);
651};
652 
653
654/** Get a Selection object of the current selection.  Note that selection objects are browser specific.
655 *
656 * @returns Selection
657 */
658 
659Xinha.prototype.getSelection = function()
660{
661  return this._iframe.contentWindow.getSelection();
662};
663 
664/** Create a Range object from the given selection.  Note that range objects are browser specific.
665 *
666 *  @param sel Selection object (see getSelection)
667 *  @returns Range
668 */
669 
670Xinha.prototype.createRange = function(sel)
671{
672  this.activateEditor();
673  if ( typeof sel != "undefined" )
674  {
675    try
676    {
677      return sel.getRangeAt(0);
678    }
679    catch(ex)
680    {
681      return this._doc.createRange();
682    }
683  }
684  else
685  {
686    return this._doc.createRange();
687  }
688};
689
690/** Determine if the given event object is a keydown/press event.
691 *
692 *  @param event Event
693 *  @returns true|false
694 */
695 
696Xinha.prototype.isKeyEvent = function(event)
697{
698  return event.type == "keypress";
699}
700
701/** Return the character (as a string) of a keyEvent  - ie, press the 'a' key and
702 *  this method will return 'a', press SHIFT-a and it will return 'A'.
703 *
704 *  @param   keyEvent
705 *  @returns string
706 */
707                                   
708Xinha.prototype.getKey = function(keyEvent)
709{
710  return String.fromCharCode(keyEvent.charCode);
711}
712
713/** Return the HTML string of the given Element, including the Element.
714 *
715 * @param element HTML Element DomNode
716 * @returns string
717 */
718 
719Xinha.getOuterHTML = function(element)
720{
721  return (new XMLSerializer()).serializeToString(element);
722};
723
724//Control character for retaining edit location when switching modes
725Xinha.prototype.cc = String.fromCharCode(8286);
726
727Xinha.prototype.setCC = function ( target )
728{
729  var cc = this.cc;
730  try
731  {
732    if ( target == "textarea" )
733    {
734      var ta = this._textArea;
735      var index = ta.selectionStart;
736      var before = ta.value.substring( 0, index )
737      var after = ta.value.substring( index, ta.value.length );
738
739      if ( after.match(/^[^<]*>/) ) // make sure cursor is in an editable area (outside tags, script blocks, entities, and inside the body)
740      {
741        var tagEnd = after.indexOf(">") + 1;
742        ta.value = before + after.substring( 0, tagEnd ) + cc + after.substring( tagEnd, after.length );
743      }
744      else ta.value = before + cc + after;
745      ta.value = ta.value.replace(new RegExp ('(&[^'+cc+']*?)('+cc+')([^'+cc+']*?;)'), "$1$3$2");
746      ta.value = ta.value.replace(new RegExp ('(<script[^>]*>[^'+cc+']*?)('+cc+')([^'+cc+']*?<\/script>)'), "$1$3$2");
747      ta.value = ta.value.replace(new RegExp ('^([^'+cc+']*)('+cc+')([^'+cc+']*<body[^>]*>)(.*?)'), "$1$3$2$4");
748    }
749    else
750    {
751      var sel = this.getSelection();
752      sel.getRangeAt(0).insertNode( this._doc.createTextNode( cc ) );
753    }
754  } catch (e) {}
755};
756
757Xinha.prototype.findCC = function ( target )
758{
759  if ( target == 'textarea' )
760  {
761  var ta = this._textArea;
762  var pos = ta.value.indexOf( this.cc );
763  if ( pos == -1 ) return;
764  var end = pos + this.cc.length;
765  var before =  ta.value.substring( 0, pos );
766  var after = ta.value.substring( end, ta.value.length );
767  ta.value = before ;
768
769  ta.scrollTop = ta.scrollHeight;
770  var scrollPos = ta.scrollTop;
771 
772  ta.value += after;
773  ta.setSelectionRange(pos,pos);
774
775  ta.focus();
776 
777  ta.scrollTop = scrollPos;
778
779  }
780  else
781  {
782    try
783    {
784      var doc = this._doc;
785      doc.body.innerHTML = doc.body.innerHTML.replace(new RegExp(this.cc),'<span id="XinhaEditingPostion"></span>');
786      var posEl = doc.getElementById('XinhaEditingPostion');
787      this.selectNodeContents(posEl);
788      posEl.scrollIntoView(true);
789      posEl.parentNode.removeChild(posEl);
790
791      this._iframe.contentWindow.focus();
792    } catch (e) {}
793  }
794};
795/*--------------------------------------------------------------------------*/
796/*------------ EXTEND SOME STANDARD "Xinha.prototype" METHODS --------------*/
797/*--------------------------------------------------------------------------*/
798
799Xinha.prototype._standardToggleBorders = Xinha.prototype._toggleBorders;
800Xinha.prototype._toggleBorders = function()
801{
802  var result = this._standardToggleBorders();
803 
804  // flashing the display forces moz to listen (JB:18-04-2005) - #102
805  var tables = this._doc.getElementsByTagName('TABLE');
806  for(var i = 0; i < tables.length; i++)
807  {
808    tables[i].style.display="none";
809    tables[i].style.display="table";
810  }
811 
812  return result;
813};
814
815/** Return the doctype of a document, if set
816 *
817 * @param doc DOM element document
818 * @returns string the actual doctype
819 */
820Xinha.getDoctype = function (doc)
821{
822  var d = '';
823  if (doc.doctype)
824  {
825    d += '<!DOCTYPE ' + doc.doctype.name + " PUBLIC ";
826    d +=  doc.doctype.publicId ? '"' + doc.doctype.publicId + '"' : ''; 
827    d +=  doc.doctype.systemId ? ' "'+ doc.doctype.systemId + '"' : '';
828    d += ">";
829  }
830  return d;
831};
Note: See TracBrowser for help on using the repository browser.