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

Last change on this file since 1312 was 1312, checked in by wymsy, 7 years ago

Ticket #1298

  • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
File size: 24.1 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(/^[^:]*:\s*(.*)\s*\$$/, '$1'),
38  developer     : "The Xinha Core Developer Team",
39  developer_url : "$HeadURL$".replace(/^[^:]*:\s*(.*)\s*\$$/, '$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  try
579  {
580  var sel = this.getSelection();
581  sel.removeAllRanges();
582  sel.addRange(savedSelection);
583  }
584  catch (e) {}
585}
586/**
587 * Selects the contents of the given node.  If the node is a "control" type element, (image, form input, table)
588 * the node itself is selected for manipulation.
589 *
590 * @param node DomNode
591 * @param collapseToStart A boolean that, when supplied, says to collapse the selection. True collapses to the start, and false to the end.
592 */
593 
594Xinha.prototype.selectNodeContents = function(node, collapseToStart)
595{
596  this.focusEditor();
597  this.forceRedraw();
598  var range;
599  var collapsed = typeof collapseToStart == "undefined" ? true : false;
600  var sel = this.getSelection();
601  range = this._doc.createRange();
602  if ( !node )
603  {
604     sel.removeAllRanges();
605     return;
606  }
607  // Tables and Images get selected as "objects" rather than the text contents
608  if ( collapsed && node.tagName && node.tagName.toLowerCase().match(/table|img|input|textarea|select/) )
609  {
610    range.selectNode(node);
611  }
612  else
613  {
614    range.selectNodeContents(node);
615  }
616  sel.removeAllRanges();
617  sel.addRange(range);
618  if (typeof collapseToStart != "undefined")
619  {
620    if (collapseToStart)
621    {
622      sel.collapse(range.startContainer, range.startOffset);
623    } else
624    {
625      sel.collapse(range.endContainer, range.endOffset);
626    }
627  }
628};
629 
630/** Insert HTML at the current position, deleting the selection if any.
631 * 
632 *  @param html string
633 */
634 
635Xinha.prototype.insertHTML = function(html)
636{
637  var sel = this.getSelection();
638  var range = this.createRange(sel);
639  this.focusEditor();
640  // construct a new document fragment with the given HTML
641  var fragment = this._doc.createDocumentFragment();
642  var div = this._doc.createElement("div");
643  div.innerHTML = html;
644  while ( div.firstChild )
645  {
646    // the following call also removes the node from div
647    fragment.appendChild(div.firstChild);
648  }
649  // this also removes the selection
650  var node = this.insertNodeAtSelection(fragment);
651};
652
653/** Get the HTML of the current selection.  HTML returned has not been passed through outwardHTML.
654 *
655 * @returns string
656 */
657 
658Xinha.prototype.getSelectedHTML = function()
659{
660  var sel = this.getSelection();
661  if (sel.isCollapsed) return '';
662  var range = this.createRange(sel);
663  return Xinha.getHTML(range.cloneContents(), false, this);
664};
665 
666
667/** Get a Selection object of the current selection.  Note that selection objects are browser specific.
668 *
669 * @returns Selection
670 */
671 
672Xinha.prototype.getSelection = function()
673{
674  return this._iframe.contentWindow.getSelection();
675};
676 
677/** Create a Range object from the given selection.  Note that range objects are browser specific.
678 *
679 *  @param sel Selection object (see getSelection)
680 *  @returns Range
681 */
682 
683Xinha.prototype.createRange = function(sel)
684{
685  this.activateEditor();
686  if ( typeof sel != "undefined" )
687  {
688    try
689    {
690      return sel.getRangeAt(0);
691    }
692    catch(ex)
693    {
694      return this._doc.createRange();
695    }
696  }
697  else
698  {
699    return this._doc.createRange();
700  }
701};
702
703/** Determine if the given event object is a keydown/press event.
704 *
705 *  @param event Event
706 *  @returns true|false
707 */
708 
709Xinha.prototype.isKeyEvent = function(event)
710{
711  return event.type == "keypress";
712}
713
714/** Return the character (as a string) of a keyEvent  - ie, press the 'a' key and
715 *  this method will return 'a', press SHIFT-a and it will return 'A'.
716 *
717 *  @param   keyEvent
718 *  @returns string
719 */
720                                   
721Xinha.prototype.getKey = function(keyEvent)
722{
723  return String.fromCharCode(keyEvent.charCode);
724}
725
726/** Return the HTML string of the given Element, including the Element.
727 *
728 * @param element HTML Element DomNode
729 * @returns string
730 */
731 
732Xinha.getOuterHTML = function(element)
733{
734  return (new XMLSerializer()).serializeToString(element);
735};
736
737//Control character for retaining edit location when switching modes
738Xinha.cc = String.fromCharCode(8286);
739
740Xinha.prototype.setCC = function ( target )
741{
742  var cc = Xinha.cc;
743  try
744  {
745    if ( target == "textarea" )
746    {
747      var ta = this._textArea;
748      var index = ta.selectionStart;
749      var before = ta.value.substring( 0, index )
750      var after = ta.value.substring( index, ta.value.length );
751
752      if ( after.match(/^[^<]*>/) ) // make sure cursor is in an editable area (outside tags, script blocks, entities, and inside the body)
753      {
754        var tagEnd = after.indexOf(">") + 1;
755        ta.value = before + after.substring( 0, tagEnd ) + cc + after.substring( tagEnd, after.length );
756      }
757      else ta.value = before + cc + after;
758      ta.value = ta.value.replace(new RegExp ('(&[^'+cc+';]*?)('+cc+')([^'+cc+']*?;)'), "$1$3$2");
759      ta.value = ta.value.replace(new RegExp ('(<script[^>]*>[^'+cc+']*?)('+cc+')([^'+cc+']*?<\/script>)'), "$1$3$2");
760      ta.value = ta.value.replace(new RegExp ('^([^'+cc+']*)('+cc+')([^'+cc+']*<body[^>]*>)(.*?)'), "$1$3$2$4");
761    }
762    else
763    {
764      var sel = this.getSelection();
765      sel.getRangeAt(0).insertNode( this._doc.createTextNode( cc ) );
766    }
767  } catch (e) {}
768};
769
770Xinha.prototype.findCC = function ( target )
771{
772  if ( target == 'textarea' )
773  {
774  var ta = this._textArea;
775  var pos = ta.value.indexOf( Xinha.cc );
776  if ( pos == -1 ) return;
777  var end = pos + Xinha.cc.length;
778  var before =  ta.value.substring( 0, pos );
779  var after = ta.value.substring( end, ta.value.length );
780  ta.value = before ;
781
782  ta.scrollTop = ta.scrollHeight;
783  var scrollPos = ta.scrollTop;
784 
785  ta.value += after;
786  ta.setSelectionRange(pos,pos);
787
788  ta.focus();
789 
790  ta.scrollTop = scrollPos;
791
792  }
793  else
794  {
795    try
796    {
797      var doc = this._doc;
798      doc.body.innerHTML = doc.body.innerHTML.replace(new RegExp(Xinha.cc),'<span id="XinhaEditingPostion"></span>');
799      var posEl = doc.getElementById('XinhaEditingPostion');
800      this.selectNodeContents(posEl);
801      this.scrollToElement(posEl);
802      posEl.parentNode.removeChild(posEl);
803
804      this._iframe.contentWindow.focus();
805    } catch (e) {}
806  }
807};
808/*--------------------------------------------------------------------------*/
809/*------------ EXTEND SOME STANDARD "Xinha.prototype" METHODS --------------*/
810/*--------------------------------------------------------------------------*/
811
812Xinha.prototype._standardToggleBorders = Xinha.prototype._toggleBorders;
813Xinha.prototype._toggleBorders = function()
814{
815  var result = this._standardToggleBorders();
816 
817  // flashing the display forces moz to listen (JB:18-04-2005) - #102
818  var tables = this._doc.getElementsByTagName('TABLE');
819  for(var i = 0; i < tables.length; i++)
820  {
821    tables[i].style.display="none";
822    tables[i].style.display="table";
823  }
824 
825  return result;
826};
827
828/** Return the doctype of a document, if set
829 *
830 * @param doc DOM element document
831 * @returns string the actual doctype
832 */
833Xinha.getDoctype = function (doc)
834{
835  var d = '';
836  if (doc.doctype)
837  {
838    d += '<!DOCTYPE ' + doc.doctype.name + " PUBLIC ";
839    d +=  doc.doctype.publicId ? '"' + doc.doctype.publicId + '"' : ''; 
840    d +=  doc.doctype.systemId ? ' "'+ doc.doctype.systemId + '"' : '';
841    d += ">";
842  }
843  return d;
844};
Note: See TracBrowser for help on using the repository browser.