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

Last change on this file since 1378 was 1378, checked in by gogo, 8 months ago

Add improved and standardised tab handling support for Gecko and WebKit?, configurable by the tabSpanClass and tabSpanContents options.

Hitting tab will insert a span with the given class and contents, hitting more than once will insert nested tab spans, hitting shift-tab will remove the current tab span if you are in one, do nothing otherwise.

tested in FF and Chrome, it seems to work well.

  • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
File size: 25.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 9: // KEY tab
206    {
207      // Note that the ListOperations plugin will handle tabs in list items and indent/outdent those
208      // at some point TableOperations might do also
209      // so this only has to handle a tab/untab in text
210      if(editor.config.tabSpanClass)
211      {
212        if(!ev.shiftKey)
213        {
214          editor.insertHTML('<span class="'+editor.config.tabSpanClass+'">'+editor.config.tabSpanContents+'</span>');
215          var s = editor.getSelection().collapseToEnd();
216        }
217        else
218        {
219          var existingTab = editor.getParentElement();
220          if(existingTab && existingTab.className.match(editor.config.tabSpanClass))
221          {
222            var s = editor.getSelection();
223            s.removeAllRanges();
224            var r = editor.createRange();
225            r.selectNode(existingTab);
226            s.addRange(r);
227            s.deleteFromDocument();
228          }
229        }
230      }
231     
232      Xinha._stopEvent(ev);
233     
234    }
235    break;
236   
237    case 8: // KEY backspace
238    case 46: // KEY delete
239    {
240      // We handle the mozilla backspace directly??
241      if ( !ev.shiftKey && this.handleBackspace() )
242      {
243        Xinha._stopEvent(ev);
244      }
245    }
246   
247   
248    default:
249    {
250        editor._unlinkOnUndo = false;
251
252        // Handle the "auto-linking", specifically this bit of code sets up a handler on
253        // an self-titled anchor (eg <a href="http://www.gogo.co.nz/">www.gogo.co.nz</a>)
254        // when the text content is edited, such that it will update the href on the anchor
255       
256        if ( s.anchorNode && s.anchorNode.nodeType == 3 )
257        {
258          // See if we might be changing a link
259          var a = editor._getFirstAncestor(s, 'a');
260          // @todo: we probably need here to inform the setTimeout below that we not changing a link and not start another setTimeout
261          if ( !a )
262          {
263            break; // not an anchor
264          }
265         
266          if ( !a._updateAnchTimeout )
267          {
268            if ( s.anchorNode.data.match(Xinha.RE_email) && a.href.match('mailto:' + s.anchorNode.data.trim()) )
269            {
270              var textNode = s.anchorNode;
271              var fnAnchor = function()
272              {
273                a.href = 'mailto:' + textNode.data.trim();
274                // @fixme: why the hell do another timeout is started ?
275                //         This lead to never ending timer if we dont remove this line
276                //         But when removed, the email is not correctly updated
277                //
278                // - to fix this we should make fnAnchor check to see if textNode.data has
279                //   stopped changing for say 5 seconds and if so we do not make this setTimeout
280                a._updateAnchTimeout = setTimeout(fnAnchor, 250);
281              };
282              a._updateAnchTimeout = setTimeout(fnAnchor, 1000);
283              break;
284            }
285
286            var m = s.anchorNode.data.match(Xinha.RE_url);
287
288            if ( m && a.href.match(new RegExp( 'http(s)?://' + Xinha.escapeStringForRegExp( s.anchorNode.data.trim() ) ) ) )
289            {
290              var txtNode = s.anchorNode;
291              var fnUrl = function()
292              {
293                // Sometimes m is undefined becase the url is not an url anymore (was www.url.com and become for example www.url)
294                // ray: shouldn't the link be un-linked then?
295                m = txtNode.data.match(Xinha.RE_url);
296                if(m)
297                {
298                  a.href = (m[1] ? m[1] : 'http://') + m[2];
299                }
300               
301                // @fixme: why the hell do another timeout is started ?
302                //         This lead to never ending timer if we dont remove this line
303                //         But when removed, the url is not correctly updated
304                //
305                // - to fix this we should make fnUrl check to see if textNode.data has
306                //   stopped changing for say 5 seconds and if so we do not make this setTimeout
307                a._updateAnchTimeout = setTimeout(fnUrl, 250);
308              };
309              a._updateAnchTimeout = setTimeout(fnUrl, 1000);
310            }
311          }       
312        }               
313    }
314    break;
315  }
316
317  return false; // Let other plugins etc continue from here.
318}
319
320/** When backspace is hit, the Gecko onKeyPress will execute this method.
321 *  I don't remember what the exact purpose of this is though :-(
322 */
323 
324Gecko.prototype.handleBackspace = function()
325{
326  var editor = this.editor;
327  setTimeout(
328    function()
329    {
330      var sel   = editor.getSelection();
331      var range = editor.createRange(sel);
332      var SC = range.startContainer;
333      var SO = range.startOffset;
334      var EC = range.endContainer;
335      var EO = range.endOffset;
336      var newr = SC.nextSibling;
337      if ( SC.nodeType == 3 )
338      {
339        SC = SC.parentNode;
340      }
341      if ( ! ( /\S/.test(SC.tagName) ) )
342      {
343        var p = document.createElement("p");
344        while ( SC.firstChild )
345        {
346          p.appendChild(SC.firstChild);
347        }
348        SC.parentNode.insertBefore(p, SC);
349        Xinha.removeFromParent(SC);
350        var r = range.cloneRange();
351        r.setStartBefore(newr);
352        r.setEndAfter(newr);
353        r.extractContents();
354        sel.removeAllRanges();
355        sel.addRange(r);
356      }
357    },
358    10);
359};
360
361Gecko.prototype.inwardHtml = function(html)
362{
363   // Midas uses b and i internally instead of strong and em
364   // Xinha will use strong and em externally (see Xinha.prototype.outwardHtml)   
365   html = html.replace(/<(\/?)strong(\s|>|\/)/ig, "<$1b$2");
366   html = html.replace(/<(\/?)em(\s|>|\/)/ig, "<$1i$2");   
367   
368   // Both IE and Gecko use strike internally instead of del (#523)
369   // Xinha will present del externally (see Xinha.prototype.outwardHtml
370   html = html.replace(/<(\/?)del(\s|>|\/)/ig, "<$1strike$2");
371   
372   return html;
373}
374
375Gecko.prototype.outwardHtml = function(html)
376{
377  // ticket:56, the "greesemonkey" plugin for Firefox adds this junk,
378  // so we strip it out.  Original submitter gave a plugin, but that's
379  // a bit much just for this IMHO - james
380  html = html.replace(/<script[\s]*src[\s]*=[\s]*['"]chrome:\/\/.*?["']>[\s]*<\/script>/ig, '');
381
382  return html;
383}
384
385Gecko.prototype.onExecCommand = function(cmdID, UI, param)
386{   
387  try
388  {
389    // useCSS deprecated & replaced by styleWithCSS
390    this.editor._doc.execCommand('useCSS', false, true); //switch useCSS off (true=off)
391    this.editor._doc.execCommand('styleWithCSS', false, false); //switch styleWithCSS off     
392  } catch (ex) {}
393   
394  switch(cmdID)
395  {
396    case 'paste':
397    {
398      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."));
399      return true; // Indicate paste is done, stop command being issued to browser by Xinha.prototype.execCommand
400    }
401    break;
402    case 'removeformat':
403      var editor = this.editor;
404      var sel = editor.getSelection();
405      var selSave = editor.saveSelection(sel);
406      var range = editor.createRange(sel);
407
408      var els = editor._doc.body.getElementsByTagName('*');
409
410      var start = ( range.startContainer.nodeType == 1 ) ? range.startContainer : range.startContainer.parentNode;
411      var i, el;
412      if (sel.isCollapsed) range.selectNodeContents(editor._doc.body);
413     
414      for (i=0; i<els.length;i++)
415      {
416        el = els[i];
417        if ( range.isPointInRange(el, 0) || (els[i] == start && range.startOffset == 0))
418        {
419          el.removeAttribute('style');
420        }
421      }
422      this.editor._doc.execCommand(cmdID, UI, param);
423      editor.restoreSelection(selSave);
424      return true;
425    break;
426  }
427 
428  return false;
429}
430Gecko.prototype.onMouseDown = function(ev)
431{   
432  // Gecko doesn't select hr's on single click
433  if (ev.target.tagName.toLowerCase() == "hr")
434  {
435    var sel = this.editor.getSelection();
436    var range = this.editor.createRange(sel);
437    range.selectNode(ev.target);
438  }
439}
440
441
442/*--------------------------------------------------------------------------*/
443/*------- IMPLEMENTATION OF THE ABSTRACT "Xinha.prototype" METHODS ---------*/
444/*--------------------------------------------------------------------------*/
445
446/** Insert a node at the current selection point.
447 * @param toBeInserted DomNode
448 */
449
450Xinha.prototype.insertNodeAtSelection = function(toBeInserted)
451{
452  if ( toBeInserted.ownerDocument != this._doc ) // as of FF3, Gecko is strict regarding the ownerDocument of an element
453  {
454    try
455        {
456                toBeInserted = this._doc.adoptNode( toBeInserted );
457        } catch (e) {}
458  }
459  var sel = this.getSelection();
460  var range = this.createRange(sel);
461  // remove the current selection
462  sel.removeAllRanges();
463  range.deleteContents();
464  var node = range.startContainer;
465  var pos = range.startOffset;
466  var selnode = toBeInserted;
467 
468  switch ( node.nodeType )
469  {
470    case 3: // Node.TEXT_NODE
471      // we have to split it at the caret position.
472      if ( toBeInserted.nodeType == 3 )
473      {
474        // do optimized insertion
475        node.insertData(pos, toBeInserted.data);
476        range = this.createRange();
477        range.setEnd(node, pos + toBeInserted.length);
478        range.setStart(node, pos + toBeInserted.length);
479        sel.addRange(range);
480      }
481      else
482      {
483        node = node.splitText(pos);
484        if ( toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */ )
485        {
486          selnode = selnode.firstChild;
487        }
488        node.parentNode.insertBefore(toBeInserted, node);
489        this.selectNodeContents(selnode);
490        this.updateToolbar();
491      }
492    break;
493    case 1: // Node.ELEMENT_NODE
494      if ( toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */ )
495      {
496        selnode = selnode.firstChild;
497      }
498      node.insertBefore(toBeInserted, node.childNodes[pos]);
499      this.selectNodeContents(selnode);
500      this.updateToolbar();
501    break;
502  }
503};
504 
505/** Get the parent element of the supplied or current selection.
506 *  @param   sel optional selection as returned by getSelection
507 *  @returns DomNode
508 */
509 
510Xinha.prototype.getParentElement = function(sel)
511{
512  if ( typeof sel == 'undefined' )
513  {
514    sel = this.getSelection();
515  }
516  var range = this.createRange(sel);
517  try
518  {
519    var p = range.commonAncestorContainer;
520    if ( !range.collapsed && range.startContainer == range.endContainer &&
521        range.startOffset - range.endOffset <= 1 && range.startContainer.hasChildNodes() )
522    {
523      p = range.startContainer.childNodes[range.startOffset];
524    }
525
526    while ( p.nodeType == 3 )
527    {
528      p = p.parentNode;
529    }
530    return p;
531  }
532  catch (ex)
533  {
534    return null;
535  }
536};
537
538/**
539 * Returns the selected element, if any.  That is,
540 * the element that you have last selected in the "path"
541 * at the bottom of the editor, or a "control" (eg image)
542 *
543 * @returns null | DomNode
544 */
545
546Xinha.prototype.activeElement = function(sel)
547{
548  if ( ( sel === null ) || this.selectionEmpty(sel) )
549  {
550    return null;
551  }
552
553  // For Mozilla we just see if the selection is not collapsed (something is selected)
554  // and that the anchor (start of selection) is an element.  This might not be totally
555  // correct, we possibly should do a simlar check to IE?
556  if ( !sel.isCollapsed )
557  {     
558    if ( sel.anchorNode.childNodes.length > sel.anchorOffset && sel.anchorNode.childNodes[sel.anchorOffset].nodeType == 1 )
559    {
560      return sel.anchorNode.childNodes[sel.anchorOffset];
561    }
562    else if ( sel.anchorNode.nodeType == 1 )
563    {
564      return sel.anchorNode;
565    }
566    else
567    {
568      return null; // return sel.anchorNode.parentNode;
569    }
570  }
571  return null;
572};
573
574/**
575 * Determines if the given selection is empty (collapsed).
576 * @param selection Selection object as returned by getSelection
577 * @returns true|false
578 */
579 
580Xinha.prototype.selectionEmpty = function(sel)
581{
582  if ( !sel )
583  {
584    return true;
585  }
586
587  if ( typeof sel.isCollapsed != 'undefined' )
588  {     
589    return sel.isCollapsed;
590  }
591
592  return true;
593};
594
595/**
596 * Returns a range object to be stored
597 * and later restored with Xinha.prototype.restoreSelection()
598 *
599 * @returns Range
600 */
601Xinha.prototype.saveSelection = function()
602{
603  return this.createRange(this.getSelection()).cloneRange();
604}
605/**
606 * Restores a selection previously stored
607 * @param savedSelection Range object as returned by Xinha.prototype.restoreSelection()
608 */
609Xinha.prototype.restoreSelection = function(savedSelection)
610{
611  try
612  {
613  var sel = this.getSelection();
614  sel.removeAllRanges();
615  sel.addRange(savedSelection);
616  }
617  catch (e) {}
618}
619/**
620 * Selects the contents of the given node.  If the node is a "control" type element, (image, form input, table)
621 * the node itself is selected for manipulation.
622 *
623 * @param node DomNode
624 * @param collapseToStart A boolean that, when supplied, says to collapse the selection. True collapses to the start, and false to the end.
625 */
626 
627Xinha.prototype.selectNodeContents = function(node, collapseToStart)
628{
629  this.focusEditor();
630  this.forceRedraw();
631  var range;
632  var collapsed = typeof collapseToStart == "undefined" ? true : false;
633  var sel = this.getSelection();
634  range = this._doc.createRange();
635  if ( !node )
636  {
637     sel.removeAllRanges();
638     return;
639  }
640  // Tables and Images get selected as "objects" rather than the text contents
641  if ( collapsed && node.tagName && node.tagName.toLowerCase().match(/table|img|input|textarea|select/) )
642  {
643    range.selectNode(node);
644  }
645  else
646  {
647    range.selectNodeContents(node);
648  }
649  sel.removeAllRanges();
650  sel.addRange(range);
651  if (typeof collapseToStart != "undefined")
652  {
653    if (collapseToStart)
654    {
655      sel.collapse(range.startContainer, range.startOffset);
656    } else
657    {
658      sel.collapse(range.endContainer, range.endOffset);
659    }
660  }
661};
662 
663/** Insert HTML at the current position, deleting the selection if any.
664 * 
665 *  @param html string
666 */
667 
668Xinha.prototype.insertHTML = function(html)
669{
670  var sel = this.getSelection();
671  var range = this.createRange(sel);
672  this.focusEditor();
673  // construct a new document fragment with the given HTML
674  var fragment = this._doc.createDocumentFragment();
675  var div = this._doc.createElement("div");
676  div.innerHTML = html;
677  while ( div.firstChild )
678  {
679    // the following call also removes the node from div
680    fragment.appendChild(div.firstChild);
681  }
682  // this also removes the selection
683  var node = this.insertNodeAtSelection(fragment);
684};
685
686/** Get the HTML of the current selection.  HTML returned has not been passed through outwardHTML.
687 *
688 * @returns string
689 */
690 
691Xinha.prototype.getSelectedHTML = function()
692{
693  var sel = this.getSelection();
694  if (sel.isCollapsed) return '';
695  var range = this.createRange(sel);
696  return Xinha.getHTML(range.cloneContents(), false, this);
697};
698 
699
700/** Get a Selection object of the current selection.  Note that selection objects are browser specific.
701 *
702 * @returns Selection
703 */
704 
705Xinha.prototype.getSelection = function()
706{
707  return this._iframe.contentWindow.getSelection();
708};
709 
710/** Create a Range object from the given selection.  Note that range objects are browser specific.
711 *
712 *  @param sel Selection object (see getSelection)
713 *  @returns Range
714 */
715 
716Xinha.prototype.createRange = function(sel)
717{
718  this.activateEditor();
719  if ( typeof sel != "undefined" )
720  {
721    try
722    {
723      return sel.getRangeAt(0);
724    }
725    catch(ex)
726    {
727      return this._doc.createRange();
728    }
729  }
730  else
731  {
732    return this._doc.createRange();
733  }
734};
735
736/** Determine if the given event object is a keydown/press event.
737 *
738 *  @param event Event
739 *  @returns true|false
740 */
741 
742Xinha.prototype.isKeyEvent = function(event)
743{
744  return event.type == "keypress";
745}
746
747/** Return the character (as a string) of a keyEvent  - ie, press the 'a' key and
748 *  this method will return 'a', press SHIFT-a and it will return 'A'.
749 *
750 *  @param   keyEvent
751 *  @returns string
752 */
753                                   
754Xinha.prototype.getKey = function(keyEvent)
755{
756  return String.fromCharCode(keyEvent.charCode);
757}
758
759/** Return the HTML string of the given Element, including the Element.
760 *
761 * @param element HTML Element DomNode
762 * @returns string
763 */
764 
765Xinha.getOuterHTML = function(element)
766{
767  return (new XMLSerializer()).serializeToString(element);
768};
769
770//Control character for retaining edit location when switching modes
771Xinha.cc = String.fromCharCode(8286);
772
773Xinha.prototype.setCC = function ( target )
774{
775  var cc = Xinha.cc;
776  try
777  {
778    if ( target == "textarea" )
779    {
780      var ta = this._textArea;
781      var index = ta.selectionStart;
782      var before = ta.value.substring( 0, index )
783      var after = ta.value.substring( index, ta.value.length );
784
785      if ( after.match(/^[^<]*>/) ) // make sure cursor is in an editable area (outside tags, script blocks, entities, and inside the body)
786      {
787        var tagEnd = after.indexOf(">") + 1;
788        ta.value = before + after.substring( 0, tagEnd ) + cc + after.substring( tagEnd, after.length );
789      }
790      else ta.value = before + cc + after;
791      ta.value = ta.value.replace(new RegExp ('(&[^'+cc+';]*?)('+cc+')([^'+cc+']*?;)'), "$1$3$2");
792      ta.value = ta.value.replace(new RegExp ('(<script[^>]*>[^'+cc+']*?)('+cc+')([^'+cc+']*?<\/script>)'), "$1$3$2");
793      ta.value = ta.value.replace(new RegExp ('^([^'+cc+']*)('+cc+')([^'+cc+']*<body[^>]*>)(.*?)'), "$1$3$2$4");
794    }
795    else
796    {
797      var sel = this.getSelection();
798      sel.getRangeAt(0).insertNode( this._doc.createTextNode( cc ) );
799    }
800  } catch (e) {}
801};
802
803Xinha.prototype.findCC = function ( target )
804{
805  if ( target == 'textarea' )
806  {
807  var ta = this._textArea;
808  var pos = ta.value.indexOf( Xinha.cc );
809  if ( pos == -1 ) return;
810  var end = pos + Xinha.cc.length;
811  var before =  ta.value.substring( 0, pos );
812  var after = ta.value.substring( end, ta.value.length );
813  ta.value = before ;
814
815  ta.scrollTop = ta.scrollHeight;
816  var scrollPos = ta.scrollTop;
817 
818  ta.value += after;
819  ta.setSelectionRange(pos,pos);
820
821  ta.focus();
822 
823  ta.scrollTop = scrollPos;
824
825  }
826  else
827  {
828    try
829    {
830      var doc = this._doc;
831      doc.body.innerHTML = doc.body.innerHTML.replace(new RegExp(Xinha.cc),'<span id="XinhaEditingPostion"></span>');
832      var posEl = doc.getElementById('XinhaEditingPostion');
833      this.selectNodeContents(posEl);
834      this.scrollToElement(posEl);
835      posEl.parentNode.removeChild(posEl);
836
837      this._iframe.contentWindow.focus();
838    } catch (e) {}
839  }
840};
841/*--------------------------------------------------------------------------*/
842/*------------ EXTEND SOME STANDARD "Xinha.prototype" METHODS --------------*/
843/*--------------------------------------------------------------------------*/
844
845Xinha.prototype._standardToggleBorders = Xinha.prototype._toggleBorders;
846Xinha.prototype._toggleBorders = function()
847{
848  var result = this._standardToggleBorders();
849 
850  // flashing the display forces moz to listen (JB:18-04-2005) - #102
851  var tables = this._doc.getElementsByTagName('TABLE');
852  for(var i = 0; i < tables.length; i++)
853  {
854    tables[i].style.display="none";
855    tables[i].style.display="table";
856  }
857 
858  return result;
859};
860
861/** Return the doctype of a document, if set
862 *
863 * @param doc DOM element document
864 * @returns string the actual doctype
865 */
866Xinha.getDoctype = function (doc)
867{
868  var d = '';
869  if (doc.doctype)
870  {
871    d += '<!DOCTYPE ' + doc.doctype.name + " PUBLIC ";
872    d +=  doc.doctype.publicId ? '"' + doc.doctype.publicId + '"' : ''; 
873    d +=  doc.doctype.systemId ? ' "'+ doc.doctype.systemId + '"' : '';
874    d += ">";
875  }
876  return d;
877};
Note: See TracBrowser for help on using the repository browser.