blob: 9567d4567096abe4979c835a255ea831773c4786 [file] [log] [blame]
/*--------------------------------------:noTabs=true:tabSize=2:indentSize=2:--
--
--
-- NOTICE Modern Opera does not use this engine any more, it identifies as
-- and uses WebKit (Opera is these days based on Blink).
--
-- People using older versions of Opera that need this engine should
-- upgrade to a WebKit or Gecko based browser.
--
-- This engine is no longer officially supported or tested.
--
-----------------------------------------------------------------------------
-- Xinha (is not htmlArea) - http://xinha.gogo.co.nz/
--
-- Use of Xinha is granted by the terms of the htmlArea License (based on
-- BSD license) please read license.txt in this package for details.
--
-- Xinha was originally based on work by Mihai Bazon which is:
-- Copyright (c) 2003-2004 dynarch.com.
-- Copyright (c) 2002-2003 interactivetools.com, inc.
-- This copyright notice MUST stay intact for use.
--
-- This is the Opera compatability plugin, part of the Xinha core.
--
-- The file is loaded as a special plugin by the Xinha Core when
-- Xinha is being run under an Opera based browser with the Midas
-- editing API.
--
-- It provides implementation and specialisation for various methods
-- in the core where different approaches per browser are required.
--
-- Design Notes::
-- Most methods here will simply be overriding Xinha.prototype.<method>
-- and should be called that, but methods specific to Opera should
-- be a part of the Opera.prototype, we won't trample on namespace
-- that way.
--
-- $HeadURL: http://svn.xinha.org/trunk/modules/Opera/Opera.js $
-- $LastChangedDate: 2018-02-19 20:35:49 +1300 (Mon, 19 Feb 2018) $
-- $LastChangedRevision: 1402 $
-- $LastChangedBy: gogo $
--------------------------------------------------------------------------*/
Opera._pluginInfo = {
name : "Opera",
origin : "Xinha Core",
version : "$LastChangedRevision: 1402 $".replace(/^[^:]*:\s*(.*)\s*\$$/, '$1'),
developer : "The Xinha Core Developer Team",
developer_url : "$HeadURL: http://svn.xinha.org/trunk/modules/Opera/Opera.js $".replace(/^[^:]*:\s*(.*)\s*\$$/, '$1'),
sponsor : "Gogo Internet Services Limited",
sponsor_url : "http://www.gogo.co.nz/",
license : "htmlArea"
};
function Opera(editor) {
this.editor = editor;
editor.Opera = this;
}
/** Allow Opera to handle some key events in a special way.
*/
Opera.prototype.onKeyPress = function(ev)
{
var editor = this.editor;
var s = editor.getSelection();
// Handle shortcuts
if(editor.isShortCut(ev))
{
switch(editor.getKey(ev).toLowerCase())
{
case 'z':
{
if(editor._unLink && editor._unlinkOnUndo)
{
Xinha._stopEvent(ev);
editor._unLink();
editor.updateToolbar();
return true;
}
}
break;
case 'a':
{
// KEY select all
sel = editor.getSelection();
sel.removeAllRanges();
range = editor.createRange();
range.selectNodeContents(editor._doc.body);
sel.addRange(range);
Xinha._stopEvent(ev);
return true;
}
break;
case 'v':
{
// If we are not using htmlareaPaste, don't let Xinha try and be fancy but let the
// event be handled normally by the browser (don't stopEvent it)
if(!editor.config.htmlareaPaste)
{
return true;
}
}
break;
}
}
// Handle normal characters
switch(editor.getKey(ev))
{
// Space, see if the text just typed looks like a URL, or email address
// and link it appropriatly
case ' ':
{
var autoWrap = function (textNode, tag)
{
var rightText = textNode.nextSibling;
if ( typeof tag == 'string')
{
tag = editor._doc.createElement(tag);
}
var a = textNode.parentNode.insertBefore(tag, rightText);
Xinha.removeFromParent(textNode);
a.appendChild(textNode);
rightText.data = ' ' + rightText.data;
s.collapse(rightText, 1);
editor._unLink = function()
{
var t = a.firstChild;
a.removeChild(t);
a.parentNode.insertBefore(t, a);
Xinha.removeFromParent(a);
editor._unLink = null;
editor._unlinkOnUndo = false;
};
editor._unlinkOnUndo = true;
return a;
};
if ( editor.config.convertUrlsToLinks && s && s.isCollapsed && s.anchorNode.nodeType == 3 && s.anchorNode.data.length > 3 && s.anchorNode.data.indexOf('.') >= 0 )
{
var midStart = s.anchorNode.data.substring(0,s.anchorOffset).search(/\S{4,}$/);
if ( midStart == -1 )
{
break;
}
if ( editor._getFirstAncestor(s, 'a') )
{
break; // already in an anchor
}
var matchData = s.anchorNode.data.substring(0,s.anchorOffset).replace(/^.*?(\S*)$/, '$1');
var mEmail = matchData.match(Xinha.RE_email);
if ( mEmail )
{
var leftTextEmail = s.anchorNode;
var rightTextEmail = leftTextEmail.splitText(s.anchorOffset);
var midTextEmail = leftTextEmail.splitText(midStart);
autoWrap(midTextEmail, 'a').href = 'mailto:' + mEmail[0];
break;
}
RE_date = /([0-9]+\.)+/; //could be date or ip or something else ...
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]?)/;
var mUrl = matchData.match(Xinha.RE_url);
if ( mUrl )
{
if (RE_date.test(matchData))
{
break; //ray: disabling linking of IP numbers because of general bugginess (see Ticket #1085)
/*if (!RE_ip.test(matchData))
{
break;
}*/
}
var leftTextUrl = s.anchorNode;
var rightTextUrl = leftTextUrl.splitText(s.anchorOffset);
var midTextUrl = leftTextUrl.splitText(midStart);
autoWrap(midTextUrl, 'a').href = (mUrl[1] ? mUrl[1] : 'http://') + mUrl[2];
break;
}
}
}
break;
}
// Handle special keys
switch ( ev.keyCode )
{
/* This is now handled by a plugin
case 13: // ENTER
break;*/
case 27: // ESCAPE
{
if ( editor._unLink )
{
editor._unLink();
Xinha._stopEvent(ev);
}
break;
}
break;
case 8: // KEY backspace
case 46: // KEY delete
{
// We handle the mozilla backspace directly??
if ( !ev.shiftKey && this.handleBackspace() )
{
Xinha._stopEvent(ev);
}
}
default:
{
editor._unlinkOnUndo = false;
// Handle the "auto-linking", specifically this bit of code sets up a handler on
// an self-titled anchor (eg <a href="http://www.gogo.co.nz/">www.gogo.co.nz</a>)
// when the text content is edited, such that it will update the href on the anchor
if ( s.anchorNode && s.anchorNode.nodeType == 3 )
{
// See if we might be changing a link
var a = editor._getFirstAncestor(s, 'a');
// @todo: we probably need here to inform the setTimeout below that we not changing a link and not start another setTimeout
if ( !a )
{
break; // not an anchor
}
if ( !a._updateAnchTimeout )
{
if ( s.anchorNode.data.match(Xinha.RE_email) && a.href.match('mailto:' + s.anchorNode.data.trim()) )
{
var textNode = s.anchorNode;
var fnAnchor = function()
{
a.href = 'mailto:' + textNode.data.trim();
// @fixme: why the hell do another timeout is started ?
// This lead to never ending timer if we dont remove this line
// But when removed, the email is not correctly updated
//
// - to fix this we should make fnAnchor check to see if textNode.data has
// stopped changing for say 5 seconds and if so we do not make this setTimeout
a._updateAnchTimeout = setTimeout(fnAnchor, 250);
};
a._updateAnchTimeout = setTimeout(fnAnchor, 1000);
break;
}
var m = s.anchorNode.data.match(Xinha.RE_url);
if ( m && a.href.match(new RegExp( 'http(s)?://' + Xinha.escapeStringForRegExp( s.anchorNode.data.trim() ) ) ) )
{
var txtNode = s.anchorNode;
var fnUrl = function()
{
// Sometimes m is undefined becase the url is not an url anymore (was www.url.com and become for example www.url)
// ray: shouldn't the link be un-linked then?
m = txtNode.data.match(Xinha.RE_url);
if(m)
{
a.href = (m[1] ? m[1] : 'http://') + m[2];
}
// @fixme: why the hell do another timeout is started ?
// This lead to never ending timer if we dont remove this line
// But when removed, the url is not correctly updated
//
// - to fix this we should make fnUrl check to see if textNode.data has
// stopped changing for say 5 seconds and if so we do not make this setTimeout
a._updateAnchTimeout = setTimeout(fnUrl, 250);
};
a._updateAnchTimeout = setTimeout(fnUrl, 1000);
}
}
}
}
break;
}
return false; // Let other plugins etc continue from here.
}
/** When backspace is hit, the Opera onKeyPress will execute this method.
* I don't remember what the exact purpose of this is though :-(
*/
Opera.prototype.handleBackspace = function()
{
var editor = this.editor;
setTimeout(
function()
{
var sel = editor.getSelection();
var range = editor.createRange(sel);
var SC = range.startContainer;
var SO = range.startOffset;
var EC = range.endContainer;
var EO = range.endOffset;
var newr = SC.nextSibling;
if ( SC.nodeType == 3 )
{
SC = SC.parentNode;
}
if ( ! ( /\S/.test(SC.tagName) ) )
{
var p = document.createElement("p");
while ( SC.firstChild )
{
p.appendChild(SC.firstChild);
}
SC.parentNode.insertBefore(p, SC);
Xinha.removeFromParent(SC);
var r = range.cloneRange();
r.setStartBefore(newr);
r.setEndAfter(newr);
r.extractContents();
sel.removeAllRanges();
sel.addRange(r);
}
},
10);
};
Opera.prototype.inwardHtml = function(html)
{
// Both IE and Opera use strike internally instead of del (#523)
// Xinha will present del externally (see Xinha.prototype.outwardHtml
html = html.replace(/<(\/?)del(\s|>|\/)/ig, "<$1strike$2");
return html;
}
Opera.prototype.outwardHtml = function(html)
{
return html;
}
Opera.prototype.onExecCommand = function(cmdID, UI, param)
{
switch(cmdID)
{
/*case 'paste':
{
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.", "Opera"));
return true; // Indicate paste is done, stop command being issued to browser by Xinha.prototype.execCommand
}
break;*/
case 'removeformat':
var editor = this.editor;
var sel = editor.getSelection();
var selSave = editor.saveSelection(sel);
var range = editor.createRange(sel);
var els = editor._doc.body.getElementsByTagName('*');
var start = ( range.startContainer.nodeType == 1 ) ? range.startContainer : range.startContainer.parentNode;
var i, el;
if (sel.isCollapsed) range.selectNodeContents(editor._doc.body);
for (i=0; i<els.length;i++)
{
el = els[i];
if ( range.isPointInRange(el, 0) || (els[i] == start && range.startOffset == 0))
{
el.removeAttribute('style');
}
}
this.editor._doc.execCommand(cmdID, UI, param);
editor.restoreSelection(selSave);
return true;
break;
}
return false;
}
Opera.prototype.onMouseDown = function(ev)
{
}
/*--------------------------------------------------------------------------*/
/*------- IMPLEMENTATION OF THE ABSTRACT "Xinha.prototype" METHODS ---------*/
/*--------------------------------------------------------------------------*/
/** Insert a node at the current selection point.
* @param toBeInserted DomNode
*/
Xinha.prototype.insertNodeAtSelection = function(toBeInserted)
{
if ( toBeInserted.ownerDocument != this._doc )
{
try
{
toBeInserted = this._doc.adoptNode( toBeInserted );
} catch (e) {}
}
this.focusEditor();
var sel = this.getSelection();
var range = this.createRange(sel);
range.deleteContents();
var node = range.startContainer;
var pos = range.startOffset;
var selnode = toBeInserted;
sel.removeAllRanges();
switch ( node.nodeType )
{
case 3: // Node.TEXT_NODE
// we have to split it at the caret position.
if ( toBeInserted.nodeType == 3 )
{
// do optimized insertion
node.insertData(pos, toBeInserted.data);
range = this.createRange();
range.setEnd(node, pos + toBeInserted.length);
range.setStart(node, pos + toBeInserted.length);
sel.addRange(range);
}
else
{
node = node.splitText(pos);
if ( toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */ )
{
selnode = selnode.firstChild;
}
node.parentNode.insertBefore(toBeInserted, node);
this.selectNodeContents(selnode);
this.updateToolbar();
}
break;
case 1: // Node.ELEMENT_NODE
if ( toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */ )
{
selnode = selnode.firstChild;
}
node.insertBefore(toBeInserted, node.childNodes[pos]);
this.selectNodeContents(selnode);
this.updateToolbar();
break;
}
};
/** Get the parent element of the supplied or current selection.
* @param sel optional selection as returned by getSelection
* @returns DomNode
*/
Xinha.prototype.getParentElement = function(sel)
{
if ( typeof sel == 'undefined' )
{
sel = this.getSelection();
}
var range = this.createRange(sel);
try
{
var p = range.commonAncestorContainer;
if ( !range.collapsed && range.startContainer == range.endContainer &&
range.startOffset - range.endOffset <= 1 && range.startContainer.hasChildNodes() )
{
p = range.startContainer.childNodes[range.startOffset];
}
while ( p.nodeType == 3 )
{
p = p.parentNode;
}
return p;
}
catch (ex)
{
return null;
}
};
/**
* Returns the selected element, if any. That is,
* the element that you have last selected in the "path"
* at the bottom of the editor, or a "control" (eg image)
*
* @returns null | DomNode
*/
Xinha.prototype.activeElement = function(sel)
{
if ( ( sel === null ) || this.selectionEmpty(sel) )
{
return null;
}
// For Mozilla we just see if the selection is not collapsed (something is selected)
// and that the anchor (start of selection) is an element. This might not be totally
// correct, we possibly should do a simlar check to IE?
if ( !sel.isCollapsed )
{
if ( sel.anchorNode.childNodes.length > sel.anchorOffset && sel.anchorNode.childNodes[sel.anchorOffset].nodeType == 1 )
{
return sel.anchorNode.childNodes[sel.anchorOffset];
}
else if ( sel.anchorNode.nodeType == 1 )
{
return sel.anchorNode;
}
else
{
return null; // return sel.anchorNode.parentNode;
}
}
return null;
};
/**
* Determines if the given selection is empty (collapsed).
* @param selection Selection object as returned by getSelection
* @returns true|false
*/
Xinha.prototype.selectionEmpty = function(sel)
{
if ( !sel )
{
return true;
}
if ( typeof sel.isCollapsed != 'undefined' )
{
return sel.isCollapsed;
}
return true;
};
/**
* Returns a range object to be stored
* and later restored with Xinha.prototype.restoreSelection()
*
* @returns Range
*/
Xinha.prototype.saveSelection = function()
{
return this.createRange(this.getSelection()).cloneRange();
}
/**
* Restores a selection previously stored
* @param savedSelection Range object as returned by Xinha.prototype.restoreSelection()
*/
Xinha.prototype.restoreSelection = function(savedSelection)
{
var sel = this.getSelection();
sel.removeAllRanges();
sel.addRange(savedSelection);
}
/**
* Selects the contents of the given node. If the node is a "control" type element, (image, form input, table)
* the node itself is selected for manipulation.
*
* @param node DomNode
* @param collapseToStart A boolean that, when supplied, says to collapse the selection. True collapses to the start, and false to the end.
*/
Xinha.prototype.selectNodeContents = function(node, collapseToStart)
{
this.focusEditor();
this.forceRedraw();
var range;
var collapsed = typeof collapseToStart == "undefined" ? true : false;
var sel = this.getSelection();
range = this._doc.createRange();
// Tables and Images get selected as "objects" rather than the text contents
if ( collapsed && node.tagName && node.tagName.toLowerCase().match(/table|img|input|textarea|select/) )
{
range.selectNode(node);
}
else
{
range.selectNodeContents(node);
}
sel.removeAllRanges();
sel.addRange(range);
if (typeof collapseToStart != "undefined")
{
if (collapseToStart)
{
sel.collapse(range.startContainer, range.startOffset);
} else
{
sel.collapse(range.endContainer, range.endOffset);
}
}
};
/** Insert HTML at the current position, deleting the selection if any.
*
* @param html string
*/
Xinha.prototype.insertHTML = function(html)
{
var sel = this.getSelection();
var range = this.createRange(sel);
this.focusEditor();
// construct a new document fragment with the given HTML
var fragment = this._doc.createDocumentFragment();
var div = this._doc.createElement("div");
div.innerHTML = html;
while ( div.firstChild )
{
// the following call also removes the node from div
fragment.appendChild(div.firstChild);
}
// this also removes the selection
var node = this.insertNodeAtSelection(fragment);
};
/** Get the HTML of the current selection. HTML returned has not been passed through outwardHTML.
*
* @returns string
*/
Xinha.prototype.getSelectedHTML = function()
{
var sel = this.getSelection();
if (sel.isCollapsed) return '';
var range = this.createRange(sel);
return Xinha.getHTML(range.cloneContents(), false, this);
};
/** Get a Selection object of the current selection. Note that selection objects are browser specific.
*
* @returns Selection
*/
Xinha.prototype.getSelection = function()
{
var sel = this._iframe.contentWindow.getSelection();
if(sel && sel.focusNode && sel.focusNode.tagName && sel.focusNode.tagName == 'HTML')
{ // Bad news batman, this selection is wonky
var bod = this._doc.getElementsByTagName('body')[0];
var rng = this.createRange();
rng.selectNodeContents(bod);
sel.removeAllRanges();
sel.addRange(rng);
sel.collapseToEnd();
}
return sel;
};
/** Create a Range object from the given selection. Note that range objects are browser specific.
*
* @param sel Selection object (see getSelection)
* @returns Range
*/
Xinha.prototype.createRange = function(sel)
{
this.activateEditor();
if ( typeof sel != "undefined" )
{
try
{
return sel.getRangeAt(0);
}
catch(ex)
{
return this._doc.createRange();
}
}
else
{
return this._doc.createRange();
}
};
/**
* @NOTE I don't have a way to test this any more (don't have old opera version
* and don't care enough to find one), I'm assuming it will probably be close
* to Gecko so I have just copied it directly.
*
* Due to browser differences, some keys which Xinha prefers to call a keyPress
* do not get an actual keypress event. This browser specific function
* overridden in the browser's engine (eg modules/WebKit/WebKit.js) as required
* takes a keydown event type and tells us if we should treat it as a
* keypress event type.
*
* To be clear, the keys we are interested here are
* Escape, Tab, Backspace, Delete, Enter
* these are "non printable" characters which we still want to regard generally
* as a keypress.
*
* If the browser does not report these as a keypress
* ( https://dvcs.w3.org/hg/d4e/raw-file/tip/key-event-test.html )
* then this function must return true for such keydown events as it is
* given.
*
* @param KeyboardEvent with keyEvent.type == keydown
* @return boolean
*/
Xinha.prototype.isKeyDownThatShouldGetButDoesNotGetAKeyPressEvent = function(keyEvent)
{
// Dom 3
if(typeof keyEvent.key != 'undefined')
{
// Found using IE11 (which uses Gecko)
// this seems to be a reasonable way to distinguish
// between IE11 and other Gecko browsers which do
// not provide the "Old DOM3" .char property
if(typeof keyEvent.char != 'undefined')
{
if(keyEvent.key.match(/^(Tab|Backspace|Del)/))
{
return true;
}
}
// Firefox reports everything we need as a keypress
// correctly (in terms of Xinha)
}
// Legacy
else
{
// Even very old firefox reports everything we need as a keypress
// correctly (in terms of Xinha)
}
};
/** Return the character (as a string) of a keyEvent - ie, press the 'a' key and
* this method will return 'a', press SHIFT-a and it will return 'A'.
*
* @param keyEvent
* @returns string
*/
Xinha.prototype.getKey = function(keyEvent)
{
return String.fromCharCode(keyEvent.charCode);
}
/** Return the HTML string of the given Element, including the Element.
*
* @param element HTML Element DomNode
* @returns string
*/
Xinha.getOuterHTML = function(element)
{
return (new XMLSerializer()).serializeToString(element);
};
/* Caret position remembering when switch between text and html mode.
* Largely this is the same as for Gecko except:
*
* Opera does not have window.find() so we use instead an element with known
* id (<span id="XinhaOperaCaretMarker">MARK</span>) so that we can
* do _doc.getElementById() on it.
*
* Opera for some reason will not set the insert point (flashing caret)
* anyway though, in theory, the iframe is focused (in findCC below) and then
* the selection (containing the span above) is collapsed, it should show
* caret. I don't know why not. Seems to require user to actually
* click to get the caret to show (or type anything without it acting wierd)?
* Very annoying.
*
*/
Xinha.cc = String.fromCharCode(8286);
Xinha.prototype.setCC = function ( target )
{
// Do a two step caret insertion, first a single char, then we'll replace that with the
// id'd span.
var cc = Xinha.cc;
try
{
if ( target == "textarea" )
{
var ta = this._textArea;
var index = ta.selectionStart;
var before = ta.value.substring( 0, index )
var after = ta.value.substring( index, ta.value.length );
if ( after.match(/^[^<]*>/) ) // make sure cursor is in an editable area (outside tags, script blocks, entities, and inside the body)
{
var tagEnd = after.indexOf(">") + 1;
ta.value = before + after.substring( 0, tagEnd ) + cc + after.substring( tagEnd, after.length );
}
else ta.value = before + cc + after;
ta.value = ta.value.replace(new RegExp ('(&[^'+cc+';]*?)('+cc+')([^'+cc+']*?;)'), "$1$3$2");
ta.value = ta.value.replace(new RegExp ('(<script[^>]*>[^'+cc+']*?)('+cc+')([^'+cc+']*?<\/script>)'), "$1$3$2");
ta.value = ta.value.replace(new RegExp ('^([^'+cc+']*)('+cc+')([^'+cc+']*<body[^>]*>)(.*?)'), "$1$3$2$4");
ta.value = ta.value.replace(cc, '<span id="XinhaOperaCaretMarker">MARK</span>');
}
else
{
var sel = this.getSelection();
var mark = this._doc.createElement('span');
mark.id = 'XinhaOperaCaretMarker';
sel.getRangeAt(0).insertNode( mark );
}
} catch (e) {}
};
Xinha.prototype.findCC = function ( target )
{
if ( target == 'textarea' )
{
var ta = this._textArea;
var pos = ta.value.search( /(<span\s+id="XinhaOperaCaretMarker"\s*\/?>((\s|(MARK))*<\/span>)?)/ );
if ( pos == -1 ) return;
var cc = RegExp.$1;
var end = pos + cc.length;
var before = ta.value.substring( 0, pos );
var after = ta.value.substring( end, ta.value.length );
ta.value = before ;
ta.scrollTop = ta.scrollHeight;
var scrollPos = ta.scrollTop;
ta.value += after;
ta.setSelectionRange(pos,pos);
ta.focus();
ta.scrollTop = scrollPos;
}
else
{
var marker = this._doc.getElementById('XinhaOperaCaretMarker');
if(marker)
{
this.focusEditor();// this._iframe.contentWindow.focus();
var rng = this.createRange();
rng.selectNode(marker);
var sel = this.getSelection();
sel.addRange(rng);
sel.collapseToStart();
this.scrollToElement(marker);
marker.parentNode.removeChild(marker);
return;
}
}
};
/*--------------------------------------------------------------------------*/
/*------------ EXTEND SOME STANDARD "Xinha.prototype" METHODS --------------*/
/*--------------------------------------------------------------------------*/
/*
Xinha.prototype._standardToggleBorders = Xinha.prototype._toggleBorders;
Xinha.prototype._toggleBorders = function()
{
var result = this._standardToggleBorders();
// flashing the display forces moz to listen (JB:18-04-2005) - #102
var tables = this._doc.getElementsByTagName('TABLE');
for(var i = 0; i < tables.length; i++)
{
tables[i].style.display="none";
tables[i].style.display="table";
}
return result;
};
*/
/** Return the doctype of a document, if set
*
* @param doc DOM element document
* @returns string the actual doctype
*/
Xinha.getDoctype = function (doc)
{
var d = '';
if (doc.doctype)
{
d += '<!DOCTYPE ' + doc.doctype.name + " PUBLIC ";
d += doc.doctype.publicId ? '"' + doc.doctype.publicId + '"' : '';
d += doc.doctype.systemId ? ' "'+ doc.doctype.systemId + '"' : '';
d += ">";
}
return d;
};
Xinha.prototype._standardInitIframe = Xinha.prototype.initIframe;
Xinha.prototype.initIframe = function()
{
if(!this._iframeLoadDone)
{
if(this._iframe.contentWindow && this._iframe.contentWindow.xinhaReadyToRoll)
{
this._iframeLoadDone = true;
this._standardInitIframe();
}
else
{
// Timeout a little (Opera is a bit dodgy about using an event)
var editor = this;
setTimeout( function() { editor.initIframe() }, 5);
}
}
}
// For some reason, Opera doesn't listen to addEventListener for at least select elements with event = change
// so we will have to intercept those and use a Dom0 event, these are used eg for the fontname/size/format
// dropdowns in the toolbar
Xinha._addEventOperaOrig = Xinha._addEvent;
Xinha._addEvent = function(el, evname, func)
{
if(el.tagName && el.tagName.toLowerCase() == 'select' && evname == 'change')
{
return Xinha.addDom0Event(el,evname,func);
}
return Xinha._addEventOperaOrig(el,evname,func);
}