source: trunk/modules/Opera/Opera.js @ 970

Last change on this file since 970 was 970, checked in by gogo, 11 years ago

Final part of commit for initial Opera 9.2 (or better, 9.5) support, see #938 .

  • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
File size: 25.2 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 Opera 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 an Opera 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 Opera should
25    --   be a part of the Opera.prototype, we won't trample on namespace
26    --   that way.
27    --
28    --  $HeadURL$
29    --  $LastChangedDate$
30    --  $LastChangedRevision$
31    --  $LastChangedBy$
32    --------------------------------------------------------------------------*/
33                                                   
34Opera._pluginInfo = {
35  name          : "Opera",
36  origin        : "Xinha Core",
37  version       : "$LastChangedRevision$".replace(/^[^:]*: (.*) \$$/, '$1'),
38  developer     : "The Xinha Core Developer Team",
39  developer_url : "$HeadURL$".replace(/^[^:]*: (.*) \$$/, '$1'),
40  sponsor       : "Gogo Internet Services Limited",
41  sponsor_url   : "http://www.gogo.co.nz/",
42  license       : "htmlArea"
43};
44
45function Opera(editor) {
46  this.editor = editor; 
47  editor.Opera = this;
48}
49
50/** Allow Opera to handle some key events in a special way.
51 */
52 
53Opera.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 Opera onKeyPress will execute this method.
288 *  I don't remember what the exact purpose of this is though :-(
289 */
290 
291Opera.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
328Opera.prototype.inwardHtml = function(html)
329{
330   // Both IE and Opera use strike internally instead of del (#523)
331   // Xinha will present del externally (see Xinha.prototype.outwardHtml
332   html = html.replace(/<(\/?)del(\s|>|\/)/ig, "<$1strike$2");
333   
334   return html;
335}
336
337Opera.prototype.outwardHtml = function(html)
338{
339
340  return html;
341}
342
343Opera.prototype.onExecCommand = function(cmdID, UI, param)
344{   
345   
346  switch(cmdID)
347  {
348   
349    /*case 'paste':
350    {
351      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."));
352      return true; // Indicate paste is done, stop command being issued to browser by Xinha.prototype.execCommand
353    }
354    break;*/
355   
356    case 'removeformat':
357      var editor = this.editor;
358      var sel = editor.getSelection();
359      var selSave = editor.saveSelection(sel);
360      var range = editor.createRange(sel);
361
362      var els = editor._doc.body.getElementsByTagName('*');
363
364      var start = ( range.startContainer.nodeType == 1 ) ? range.startContainer : range.startContainer.parentNode;
365      var i, el;
366      if (sel.isCollapsed) range.selectNodeContents(editor._doc.body);
367     
368      for (i=0; i<els.length;i++)
369      {
370        el = els[i];
371        if ( range.isPointInRange(el, 0) || (els[i] == start && range.startOffset == 0))
372        {
373          el.removeAttribute('style');
374        }
375      }
376      this.editor._doc.execCommand(cmdID, UI, param);
377      editor.restoreSelection(selSave);
378      return true;
379    break;
380   
381  }
382 
383  return false;
384}
385
386Opera.prototype.onMouseDown = function(ev)
387{   
388 
389}
390
391
392/*--------------------------------------------------------------------------*/
393/*------- IMPLEMENTATION OF THE ABSTRACT "Xinha.prototype" METHODS ---------*/
394/*--------------------------------------------------------------------------*/
395
396
397
398/** Insert a node at the current selection point.
399 * @param toBeInserted DomNode
400 */
401
402Xinha.prototype.insertNodeAtSelection = function(toBeInserted)
403{
404  if ( toBeInserted.ownerDocument != this._doc )
405  {
406    try
407    {
408      toBeInserted = this._doc.adoptNode( toBeInserted );
409    } catch (e) {}
410  }
411 
412  this.focusEditor();
413 
414  var sel = this.getSelection();     
415  var range = this.createRange(sel);
416   
417  range.deleteContents();
418 
419  var node = range.startContainer;
420  var pos = range.startOffset;
421  var selnode = toBeInserted;
422 
423  sel.removeAllRanges();
424 
425  switch ( node.nodeType )
426  {
427    case 3: // Node.TEXT_NODE
428      // we have to split it at the caret position.
429      if ( toBeInserted.nodeType == 3 )
430      {
431        // do optimized insertion
432        node.insertData(pos, toBeInserted.data);
433        range = this.createRange();
434        range.setEnd(node, pos + toBeInserted.length);
435        range.setStart(node, pos + toBeInserted.length);
436        sel.addRange(range);
437      }
438      else
439      {
440        node = node.splitText(pos);
441        if ( toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */ )
442        {
443          selnode = selnode.firstChild;
444        }
445        node.parentNode.insertBefore(toBeInserted, node);
446        this.selectNodeContents(selnode);
447        this.updateToolbar();
448      }
449    break;
450   
451    case 1: // Node.ELEMENT_NODE
452      if ( toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */ )
453      {
454        selnode = selnode.firstChild;
455      }
456      node.insertBefore(toBeInserted, node.childNodes[pos]);
457      this.selectNodeContents(selnode);
458      this.updateToolbar();
459    break;
460  }
461};
462 
463/** Get the parent element of the supplied or current selection.
464 *  @param   sel optional selection as returned by getSelection
465 *  @returns DomNode
466 */
467 
468Xinha.prototype.getParentElement = function(sel)
469{
470  if ( typeof sel == 'undefined' )
471  {
472    sel = this.getSelection();
473  }
474  var range = this.createRange(sel);
475  try
476  {
477    var p = range.commonAncestorContainer;
478    if ( !range.collapsed && range.startContainer == range.endContainer &&
479        range.startOffset - range.endOffset <= 1 && range.startContainer.hasChildNodes() )
480    {
481      p = range.startContainer.childNodes[range.startOffset];
482    }
483
484    while ( p.nodeType == 3 )
485    {
486      p = p.parentNode;
487    }
488    return p;
489  }
490  catch (ex)
491  {
492    return null;
493  }
494};
495
496/**
497 * Returns the selected element, if any.  That is,
498 * the element that you have last selected in the "path"
499 * at the bottom of the editor, or a "control" (eg image)
500 *
501 * @returns null | DomNode
502 */
503
504Xinha.prototype.activeElement = function(sel)
505{
506  if ( ( sel === null ) || this.selectionEmpty(sel) )
507  {
508    return null;
509  }
510
511  // For Mozilla we just see if the selection is not collapsed (something is selected)
512  // and that the anchor (start of selection) is an element.  This might not be totally
513  // correct, we possibly should do a simlar check to IE?
514  if ( !sel.isCollapsed )
515  {     
516    if ( sel.anchorNode.childNodes.length > sel.anchorOffset && sel.anchorNode.childNodes[sel.anchorOffset].nodeType == 1 )
517    {
518      return sel.anchorNode.childNodes[sel.anchorOffset];
519    }
520    else if ( sel.anchorNode.nodeType == 1 )
521    {
522      return sel.anchorNode;
523    }
524    else
525    {
526      return null; // return sel.anchorNode.parentNode;
527    }
528  }
529  return null;
530};
531
532/**
533 * Determines if the given selection is empty (collapsed).
534 * @param selection Selection object as returned by getSelection
535 * @returns true|false
536 */
537 
538Xinha.prototype.selectionEmpty = function(sel)
539{
540  if ( !sel )
541  {
542    return true;
543  }
544
545  if ( typeof sel.isCollapsed != 'undefined' )
546  {     
547    return sel.isCollapsed;
548  }
549
550  return true;
551};
552
553/**
554 * Returns a range object to be stored
555 * and later restored with Xinha.prototype.restoreSelection()
556 *
557 * @returns Range
558 */
559Xinha.prototype.saveSelection = function()
560{
561  return this.createRange(this.getSelection()).cloneRange();
562}
563/**
564 * Restores a selection previously stored
565 * @param savedSelection Range object as returned by Xinha.prototype.restoreSelection()
566 */
567Xinha.prototype.restoreSelection = function(savedSelection)
568{
569  var sel = this.getSelection();
570  sel.removeAllRanges();
571  sel.addRange(savedSelection);
572}
573/**
574 * Selects the contents of the given node.  If the node is a "control" type element, (image, form input, table)
575 * the node itself is selected for manipulation.
576 *
577 * @param node DomNode
578 * @param pos  Set to a numeric position inside the node to collapse the cursor here if possible.
579 */
580 
581Xinha.prototype.selectNodeContents = function(node, pos)
582{
583  this.focusEditor();
584  this.forceRedraw();
585  var range;
586  var collapsed = typeof pos == "undefined" ? true : false;
587  var sel = this.getSelection();
588  range = this._doc.createRange();
589  // Tables and Images get selected as "objects" rather than the text contents
590  if ( collapsed && node.tagName && node.tagName.toLowerCase().match(/table|img|input|textarea|select/) )
591  {
592    range.selectNode(node);
593  }
594  else
595  {
596    range.selectNodeContents(node);
597    //(collapsed) && range.collapse(pos);
598  }
599  sel.removeAllRanges();
600  sel.addRange(range);
601};
602 
603/** Insert HTML at the current position, deleting the selection if any.
604 * 
605 *  @param html string
606 */
607 
608Xinha.prototype.insertHTML = function(html)
609{
610  var sel = this.getSelection();
611  var range = this.createRange(sel);
612  this.focusEditor();
613  // construct a new document fragment with the given HTML
614  var fragment = this._doc.createDocumentFragment();
615  var div = this._doc.createElement("div");
616  div.innerHTML = html;
617  while ( div.firstChild )
618  {
619    // the following call also removes the node from div
620    fragment.appendChild(div.firstChild);
621  }
622  // this also removes the selection
623  var node = this.insertNodeAtSelection(fragment);
624};
625
626/** Get the HTML of the current selection.  HTML returned has not been passed through outwardHTML.
627 *
628 * @returns string
629 */
630 
631Xinha.prototype.getSelectedHTML = function()
632{
633  var sel = this.getSelection();
634  if (sel.isCollapsed) return '';
635  var range = this.createRange(sel);
636  return Xinha.getHTML(range.cloneContents(), false, this);
637};
638 
639
640/** Get a Selection object of the current selection.  Note that selection objects are browser specific.
641 *
642 * @returns Selection
643 */
644 
645Xinha.prototype.getSelection = function()
646{
647  var sel = this._iframe.contentWindow.getSelection();
648  if(sel && sel.focusNode && sel.focusNode.tagName && sel.focusNode.tagName == 'HTML')
649  { // Bad news batman, this selection is wonky 
650    var bod = this._doc.getElementsByTagName('body')[0];
651    var rng = this.createRange();
652    rng.selectNodeContents(bod);
653    sel.removeAllRanges();
654    sel.addRange(rng);
655    sel.collapseToEnd();   
656  }
657  return sel;
658};
659 
660/** Create a Range object from the given selection.  Note that range objects are browser specific.
661 *
662 *  @param sel Selection object (see getSelection)
663 *  @returns Range
664 */
665 
666Xinha.prototype.createRange = function(sel)
667{
668  this.activateEditor();
669  if ( typeof sel != "undefined" )
670  {
671    try
672    {
673      return sel.getRangeAt(0);
674    }
675    catch(ex)
676    {
677      return this._doc.createRange();
678    }
679  }
680  else
681  {
682    return this._doc.createRange();
683  }
684};
685
686/** Determine if the given event object is a keydown/press event.
687 *
688 *  @param event Event
689 *  @returns true|false
690 */
691 
692Xinha.prototype.isKeyEvent = function(event)
693{
694  return event.type == "keypress";
695}
696
697/** Return the character (as a string) of a keyEvent  - ie, press the 'a' key and
698 *  this method will return 'a', press SHIFT-a and it will return 'A'.
699 *
700 *  @param   keyEvent
701 *  @returns string
702 */
703                                   
704Xinha.prototype.getKey = function(keyEvent)
705{
706  return String.fromCharCode(keyEvent.charCode);
707}
708
709/** Return the HTML string of the given Element, including the Element.
710 *
711 * @param element HTML Element DomNode
712 * @returns string
713 */
714 
715Xinha.getOuterHTML = function(element)
716{
717  return (new XMLSerializer()).serializeToString(element);
718};
719
720
721/* Caret position remembering when switch between text and html mode.
722 * Largely this is the same as for Gecko except:
723 *
724 * Opera does not have window.find() so we use instead an element with known
725 * id (<span id="XinhaOperaCaretMarker">MARK</span>) so that we can
726 * do _doc.getElementById() on it.
727 *
728 * Opera for some reason will not set the insert point (flashing caret)
729 * anyway though, in theory, the iframe is focused (in findCC below) and then
730 * the selection (containing the span above) is collapsed, it should show
731 * caret.  I don't know why not.  Seems to require user to actually
732 * click to get the caret to show (or type anything without it acting wierd)?
733 * Very annoying.
734 *
735 */
736 
737Xinha.prototype.setCC = function ( target )
738{
739  // Do a two step caret insertion, first a single char, then we'll replace that with the
740  // id'd span.
741  var cc = String.fromCharCode(8286);
742 
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      ta.value = ta.value.replace(cc, '<span id="XinhaOperaCaretMarker">MARK</span>');
763    }
764    else
765    {
766      var sel = this.getSelection();
767     
768      var mark =  this._doc.createElement('span');
769      mark.id  = 'XinhaOperaCaretMarker';
770      sel.getRangeAt(0).insertNode( mark );
771     
772    }
773  } catch (e) {}
774};
775
776Xinha.prototype.findCC = function ( target )
777{
778  if ( target == 'textarea' )
779  { 
780    var ta = this._textArea;
781    var pos = ta.value.search( /(<span\s+id="XinhaOperaCaretMarker"\s*\/?>((\s|(MARK))*<\/span>)?)/ );   
782    if ( pos == -1 ) return;
783    var cc = RegExp.$1;
784    var end = pos + cc.length;
785    var before =  ta.value.substring( 0, pos );
786    var after = ta.value.substring( end, ta.value.length );
787    ta.value = before ;
788 
789    ta.scrollTop = ta.scrollHeight;
790    var scrollPos = ta.scrollTop;
791   
792    ta.value += after;
793    ta.setSelectionRange(pos,pos);
794 
795    ta.focus();
796   
797    ta.scrollTop = scrollPos;
798
799  }
800  else
801  {   
802    var marker = this._doc.getElementById('XinhaOperaCaretMarker');
803    if(marker)
804    {
805      this.focusEditor();// this._iframe.contentWindow.focus();
806     
807      var rng = this.createRange();
808      rng.selectNode(marker);
809     
810      var sel = this.getSelection();
811      sel.addRange(rng);
812      sel.collapseToStart();
813     
814      this.scrollToElement(marker);
815     
816      marker.parentNode.removeChild(marker);
817      return;
818    }
819  }
820};
821
822/*--------------------------------------------------------------------------*/
823/*------------ EXTEND SOME STANDARD "Xinha.prototype" METHODS --------------*/
824/*--------------------------------------------------------------------------*/
825
826/*
827Xinha.prototype._standardToggleBorders = Xinha.prototype._toggleBorders;
828Xinha.prototype._toggleBorders = function()
829{
830  var result = this._standardToggleBorders();
831   
832  // flashing the display forces moz to listen (JB:18-04-2005) - #102
833  var tables = this._doc.getElementsByTagName('TABLE');
834  for(var i = 0; i < tables.length; i++)
835  {
836    tables[i].style.display="none";
837    tables[i].style.display="table";
838  }
839 
840  return result;
841};
842*/
843
844/** Return the doctype of a document, if set
845 *
846 * @param doc DOM element document
847 * @returns string the actual doctype
848 */
849Xinha.getDoctype = function (doc)
850{
851  var d = '';
852  if (doc.doctype)
853  {
854    d += '<!DOCTYPE ' + doc.doctype.name + " PUBLIC ";
855    d +=  doc.doctype.publicId ? '"' + doc.doctype.publicId + '"' : ''; 
856    d +=  doc.doctype.systemId ? ' "'+ doc.doctype.systemId + '"' : '';
857    d += ">";
858  }
859  return d;
860};
861
862Xinha.prototype._standardInitIframe = Xinha.prototype.initIframe;
863Xinha.prototype.initIframe = function()
864{
865  if(!this._iframeLoadDone)
866  {   
867    if(this._iframe.contentWindow && this._iframe.contentWindow.xinhaReadyToRoll)
868    {
869      this._iframeLoadDone = true;
870      this._standardInitIframe();
871    }
872    else
873    {
874      // Timeout a little (Opera is a bit dodgy about using an event)
875      var editor = this;
876      setTimeout( function() { editor.initIframe() }, 5);
877    }
878  }
879}
880
881// For some reason, Opera doesn't listen to addEventListener for at least select elements with event = change
882// so we will have to intercept those and use a Dom0 event, these are used eg for the fontname/size/format
883// dropdowns in the toolbar
884Xinha._addEventOperaOrig = Xinha._addEvent;
885Xinha._addEvent = function(el, evname, func)
886{
887  if(el.tagName && el.tagName.toLowerCase() == 'select' && evname == 'change')
888  {
889    return Xinha.addDom0Event(el,evname,func);
890  }
891 
892  return Xinha._addEventOperaOrig(el,evname,func);
893}
Note: See TracBrowser for help on using the repository browser.