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