blob: 633c0a2d9ee4255be5572b7df18eea75b432fe85 [file] [log] [blame]
/*--------------------------------------:noTabs=true:tabSize=2:indentSize=2:--
--
-- NOTICE Modern IE does not use this engine any more
--
-- IE 11 identifies as Gecko
-- Edge identifies as WebKit
--
-- The last IE version to use this engine is probably IE10, people should
-- not use such an old version of IE and should upgrade or use 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 Internet Explorer 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 Internet Explorer based browser.
--
-- 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 IE should
-- be a part of the InternetExplorer.prototype, we won't trample on
-- namespace that way.
--
-- $HeadURL: http://svn.xinha.org/trunk/modules/InternetExplorer/InternetExplorer.js $
-- $LastChangedDate: 2018-02-19 20:35:49 +1300 (Mon, 19 Feb 2018) $
-- $LastChangedRevision: 1402 $
-- $LastChangedBy: gogo $
--------------------------------------------------------------------------*/
InternetExplorer._pluginInfo = {
name : "Internet Explorer",
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/InternetExplorer/InternetExplorer.js $".replace(/^[^:]*:\s*(.*)\s*\$$/, '$1'),
sponsor : "",
sponsor_url : "",
license : "htmlArea"
};
function InternetExplorer(editor) {
this.editor = editor;
editor.InternetExplorer = this; // So we can do my_editor.InternetExplorer.doSomethingIESpecific();
}
/** Allow Internet Explorer to handle some key events in a special way.
*/
InternetExplorer.prototype.onKeyPress = function(ev)
{
var editor = this.editor;
// Shortcuts
if(this.editor.isShortCut(ev))
{
switch(this.editor.getKey(ev).toLowerCase())
{
case 'n':
{
this.editor.execCommand('formatblock', false, '<p>');
Xinha._stopEvent(ev);
return true;
}
break;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
{
this.editor.execCommand('formatblock', false, '<h'+this.editor.getKey(ev).toLowerCase()+'>');
Xinha._stopEvent(ev);
return true;
}
break;
}
}
switch(ev.keyCode)
{
case 8: // KEY backspace
case 46: // KEY delete
{
if(this.handleBackspace())
{
Xinha._stopEvent(ev);
return true;
}
}
break;
case 9: // KEY tab
{
// Note that the ListOperations plugin will handle tabs in list items and indent/outdent those
// at some point TableOperations might do also
// so this only has to handle a tab/untab in text
if(editor.config.tabSpanClass)
{
if(!ev.shiftKey)
{ // v-- Avoid lc_parse_strings.php
editor.insertHTML('<'+'span class="'+editor.config.tabSpanClass+'">'+editor.config.tabSpanContents+'</span>');
var s = editor.getSelection();
var r = editor.createRange(s);
r.collapse(true);
r.select();
}
else
{
// Shift tab is not trivial to fix in old IE
// and I don't care enough about it to try hard
}
}
Xinha._stopEvent(ev);
return true;
}
break;
}
return false;
}
/** When backspace is hit, the IE onKeyPress will execute this method.
* It preserves links when you backspace over them and apparently
* deletes control elements (tables, images, form fields) in a better
* way.
*
* @returns true|false True when backspace has been handled specially
* false otherwise (should pass through).
*/
InternetExplorer.prototype.handleBackspace = function()
{
var editor = this.editor;
var sel = editor.getSelection();
if ( sel.type == 'Control' )
{
var elm = editor.activeElement(sel);
Xinha.removeFromParent(elm);
return true;
}
// This bit of code preseves links when you backspace over the
// endpoint of the link in IE. Without it, if you have something like
// link_here |
// where | is the cursor, and backspace over the last e, then the link
// will de-link, which is a bit tedious
var range = editor.createRange(sel);
var r2 = range.duplicate();
r2.moveStart("character", -1);
var a = r2.parentElement();
// @fixme: why using again a regex to test a single string ???
if ( a != range.parentElement() && ( /^a$/i.test(a.tagName) ) )
{
r2.collapse(true);
r2.moveEnd("character", 1);
r2.pasteHTML('');
r2.select();
return true;
}
};
InternetExplorer.prototype.inwardHtml = function(html)
{
// Both IE and Gecko use strike internally instead of del (#523)
// Xinha will present del externally (see Xinha.prototype.outwardHtml
html = html.replace(/<(\/?)del(\s|>|\/)/ig, "<$1strike$2");
// ie eats scripts and comments at beginning of page, so
// make sure there is something before the first script on the page
html = html.replace(/(<script|<!--)/i,"&nbsp;$1");
// We've got a workaround for certain issues with saving and restoring
// selections that may cause us to fill in junk span tags. We'll clean
// those here
html = html.replace(/<span[^>]+id="__InsertSpan_Workaround_[a-z]+".*?>([\s\S]*?)<\/span>/i,"$1");
return html;
}
InternetExplorer.prototype.outwardHtml = function(html)
{
// remove space added before first script on the page
html = html.replace(/&nbsp;(\s*)(<script|<!--)/i,"$1$2");
// We've got a workaround for certain issues with saving and restoring
// selections that may cause us to fill in junk span tags. We'll clean
// those here
html = html.replace(/<span[^>]+id="__InsertSpan_Workaround_[a-z]+".*?>([\s\S]*?)<\/span>/i,"$1");
return html;
}
InternetExplorer.prototype.onExecCommand = function(cmdID, UI, param)
{
switch(cmdID)
{
// #645 IE only saves the initial content of the iframe, so we create a temporary iframe with the current editor contents
case 'saveas':
var doc = null;
var editor = this.editor;
var iframe = document.createElement("iframe");
iframe.src = "about:blank";
iframe.style.display = 'none';
document.body.appendChild(iframe);
try
{
if ( iframe.contentDocument )
{
doc = iframe.contentDocument;
}
else
{
doc = iframe.contentWindow.document;
}
}
catch(ex)
{
//hope there's no exception
}
doc.open("text/html","replace");
var html = '';
if ( editor.config.browserQuirksMode === false )
{
var doctype = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">';
}
else if ( editor.config.browserQuirksMode === true )
{
var doctype = '';
}
else
{
var doctype = Xinha.getDoctype(document);
}
if ( !editor.config.fullPage )
{
html += doctype + "\n";
html += "<html>\n";
html += "<head>\n";
html += "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=" + editor.config.charSet + "\">\n";
if ( typeof editor.config.baseHref != 'undefined' && editor.config.baseHref !== null )
{
html += "<base href=\"" + editor.config.baseHref + "\"/>\n";
}
if ( typeof editor.config.pageStyleSheets !== 'undefined' )
{
for ( var i = 0; i < editor.config.pageStyleSheets.length; i++ )
{
if ( editor.config.pageStyleSheets[i].length > 0 )
{
html += "<link rel=\"stylesheet\" type=\"text/css\" href=\"" + editor.config.pageStyleSheets[i] + "\">";
//html += "<style> @import url('" + editor.config.pageStyleSheets[i] + "'); </style>\n";
}
}
}
if ( editor.config.pageStyle )
{
html += "<style type=\"text/css\">\n" + editor.config.pageStyle + "\n</style>";
}
html += "</head>\n";
html += "<body>\n";
html += editor.getEditorContent();
html += "</body>\n";
html += "</html>";
}
else
{
html = editor.getEditorContent();
if ( html.match(Xinha.RE_doctype) )
{
editor.setDoctype(RegExp.$1);
}
}
doc.write(html);
doc.close();
doc.execCommand(cmdID, UI, param);
document.body.removeChild(iframe);
return true;
break;
case 'removeformat':
var editor = this.editor;
var sel = editor.getSelection();
var selSave = editor.saveSelection(sel);
var i, el, els;
function clean (el)
{
if (el.nodeType != 1) return;
el.removeAttribute('style');
for (var j=0; j<el.childNodes.length;j++)
{
clean(el.childNodes[j]);
}
if ( (el.tagName.toLowerCase() == 'span' && !el.attributes.length ) || el.tagName.toLowerCase() == 'font')
{
el.outerHTML = el.innerHTML;
}
}
if ( editor.selectionEmpty(sel) )
{
els = editor._doc.body.childNodes;
for (i = 0; i < els.length; i++)
{
el = els[i];
if (el.nodeType != 1) continue;
if (el.tagName.toLowerCase() == 'span')
{
newNode = editor.convertNode(el, 'div');
el.parentNode.replaceChild(newNode, el);
el = newNode;
}
clean(el);
}
}
editor._doc.execCommand(cmdID, UI, param);
editor.restoreSelection(selSave);
return true;
break;
}
return false;
};
/*--------------------------------------------------------------------------*/
/*------- IMPLEMENTATION OF THE ABSTRACT "Xinha.prototype" METHODS ---------*/
/*--------------------------------------------------------------------------*/
/** Insert a node at the current selection point.
* @param toBeInserted DomNode
*/
Xinha.prototype.insertNodeAtSelection = function(toBeInserted)
{
this.insertHTML(toBeInserted.outerHTML);
};
/** 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);
switch ( sel.type )
{
case "Text":
// try to circumvent a bug in IE:
// the parent returned is not always the real parent element
var parent = range.parentElement();
while ( true )
{
var TestRange = range.duplicate();
TestRange.moveToElementText(parent);
if ( TestRange.inRange(range) )
{
break;
}
if ( ( parent.nodeType != 1 ) || ( parent.tagName.toLowerCase() == 'body' ) )
{
break;
}
parent = parent.parentElement;
}
return parent;
case "None":
// It seems that even for selection of type "None",
// there _is_ a parent element and it's value is not
// only correct, but very important to us. MSIE is
// certainly the buggiest browser in the world and I
// wonder, God, how can Earth stand it?
try
{
return range.parentElement();
}
catch(e)
{
return this._doc.body; // ??
}
case "Control":
return range.item(0);
default:
return this._doc.body;
}
};
/**
* 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;
}
if ( sel.type.toLowerCase() == "control" )
{
return sel.createRange().item(0);
}
else
{
// If it's not a control, then we need to see if
// the selection is the _entire_ text of a parent node
// (this happens when a node is clicked in the tree)
var range = sel.createRange();
var p_elm = this.getParentElement(sel);
if ( p_elm.innerHTML == range.htmlText )
{
return p_elm;
}
/*
if ( p_elm )
{
var p_rng = this._doc.body.createTextRange();
p_rng.moveToElementText(p_elm);
if ( p_rng.isEqual(range) )
{
return p_elm;
}
}
if ( range.parentElement() )
{
var prnt_range = this._doc.body.createTextRange();
prnt_range.moveToElementText(range.parentElement());
if ( prnt_range.isEqual(range) )
{
return range.parentElement();
}
}
*/
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;
}
return this.createRange(sel).htmlText === '';
};
/**
* Returns a range object to be stored
* and later restored with Xinha.prototype.restoreSelection()
*
* @returns Range
*/
Xinha.prototype.saveSelection = function(sel)
{
return this.createRange(sel ? sel : this.getSelection())
}
/**
* Restores a selection previously stored
* @param savedSelection Range object as returned by Xinha.prototype.restoreSelection()
*/
Xinha.prototype.restoreSelection = function(savedSelection)
{
if (!savedSelection) return;
// Ticket #1387
// avoid problem where savedSelection does not implement parentElement().
// This condition occurs if there was no text selection at the time saveSelection() was called. In the case
// an image selection, the situation is confusing... the image may be selected in two different ways: 1) by
// simply clicking the image it will appear to be selected by a box with sizing handles; or 2) by clicking and
// dragging over the image as you might click and drag over text. In the first case, the resulting selection
// object does not implement parentElement(), leading to a crash later on in the code below. The following
// hack avoids that problem.
// Ticket #1488
// fix control selection in IE8
var savedParentElement = null;
if (savedSelection.parentElement)
{
savedParentElement = savedSelection.parentElement();
}
else
{
savedParentElement = savedSelection.item(0);
}
// In order to prevent triggering the IE bug mentioned below, we will try to
// optimize by not restoring the selection if it happens to match the current
// selection.
var range = this.createRange(this.getSelection());
var rangeParentElement = null;
if (range.parentElement)
{
rangeParentElement = range.parentElement();
}
else
{
rangeParentElement = range.item(0);
}
// We can't compare two selections that come from different documents, so we
// must make sure they're from the same document.
var findDoc = function(el)
{
for (var root=el; root; root=root.parentNode)
{
if (root.tagName.toLowerCase() == 'html')
{
return root.parentNode;
}
}
return null;
}
if (savedSelection.parentElement && findDoc(savedParentElement) == findDoc(rangeParentElement))
{
if (range.isEqual(savedSelection))
{
// The selection hasn't moved, no need to restore.
return;
}
}
try { savedSelection.select() } catch (e) {};
range = this.createRange(this.getSelection());
if (range.parentElement)
{
rangeParentElement = range.parentElement();
}
else
{
rangeParentElement = range.item(0);
}
if (rangeParentElement != savedParentElement)
{
// IE has a problem with selections at the end of text nodes that
// immediately precede block nodes. Example markup:
// <div>Text Node<p>Text in Block</p></div>
// ^
// The problem occurs when the cursor is after the 'e' in Node.
var solution = this.config.selectWorkaround || 'VisibleCue';
switch (solution)
{
case 'SimulateClick':
// Try to get the bounding box of the selection and then simulate a
// mouse click in the upper right corner to return the cursor to the
// correct location.
// No code yet, fall through to InsertSpan
case 'InsertSpan':
// This workaround inserts an empty span element so that we are no
// longer trying to select a text node,
var parentDoc = findDoc(savedParentElement);
// A function used to generate a unique ID for our temporary span.
var randLetters = function(count)
{
// Build a list of 26 letters.
var Letters = '';
for (var index = 0; index<26; ++index)
{
Letters += String.fromCharCode('a'.charCodeAt(0) + index);
}
var result = '';
for (var index=0; index<count; ++index)
{
result += Letters.substr(Math.floor(Math.random()*Letters.length + 1), 1);
}
return result;
}
// We'll try to find a unique ID to use for finding our element.
var keyLength = 1;
var tempId = '__InsertSpan_Workaround_' + randLetters(keyLength);
while (parentDoc.getElementById(tempId))
{
// Each time there's a collision, we'll increase our key length by
// one, making the chances of a collision exponentially more rare.
keyLength += 1;
tempId = '__InsertSpan_Workaround_' + randLetters(keyLength);
}
// Now that we have a uniquely identifiable element, we'll stick it and
// and use it to orient our selection.
savedSelection.pasteHTML('<span id="' + tempId + '"></span>');
var tempSpan = parentDoc.getElementById(tempId);
savedSelection.moveToElementText(tempSpan);
savedSelection.select();
break;
case 'JustificationHack':
// Setting the justification on an element causes IE to alter the
// markup so that the selection we want to make is possible.
// Unfortunately, this can force block elements to be kicked out of
// their containing element, so it is not recommended.
// Set a non-valid character and use it to anchor our selection.
var magicString = String.fromCharCode(1);
savedSelection.pasteHTML(magicString);
savedSelection.findText(magicString,-1);
savedSelection.select();
// I don't know how to find out if there's an existing justification on
// this element. Hopefully, you're doing all of your styling outside,
// so I'll just clear. I already told you this was a hack.
savedSelection.execCommand('JustifyNone');
savedSelection.pasteHTML('');
break;
case 'VisibleCue':
default:
// This method will insert a little box character to hold our selection
// in the desired spot. We're depending on the user to see this ugly
// box and delete it themselves.
var magicString = String.fromCharCode(1);
savedSelection.pasteHTML(magicString);
savedSelection.findText(magicString,-1);
savedSelection.select();
}
}
}
/**
* 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;
// Tables and Images get selected as "objects" rather than the text contents
if ( collapsed && node.tagName && node.tagName.toLowerCase().match(/table|img|input|select|textarea/) )
{
range = this._doc.body.createControlRange();
range.add(node);
}
else
{
range = this._doc.body.createTextRange();
if (3 == node.nodeType)
{
// Special handling for text nodes, since moveToElementText fails when
// attempting to select a text node
// Since the TextRange has a quite limited API, our strategy here is to
// select (where possible) neighboring nodes, and then move our ranges
// endpoints to be just inside of neighboring selections.
if (node.parentNode)
{
range.moveToElementText(node.parentNode);
} else
{
range.moveToElementText(this._doc.body);
}
var trimmingRange = this._doc.body.createTextRange();
// In rare situations (mostly html that's been monkeyed about with by
// javascript, but that's what we're doing) there can be two adjacent
// text nodes. Since we won't be able to handle these, we'll have to
// hack an offset by 'move'ing the number of characters they contain.
var texthackOffset = 0;
var borderElement=node.previousSibling;
for (; borderElement && (1 != borderElement.nodeType); borderElement = borderElement.previousSibling)
{
if (3 == borderElement.nodeType)
{
// IE doesn't count '\r' as a character, so we have to adjust the offset.
texthackOffset += borderElement.nodeValue.length-borderElement.nodeValue.split('\r').length-1;
}
}
if (borderElement && (1 == borderElement.nodeType))
{
trimmingRange.moveToElementText(borderElement);
range.setEndPoint('StartToEnd', trimmingRange);
}
if (texthackOffset)
{
// We now need to move the selection forward the number of characters
// in all text nodes in between our text node and our ranges starting
// border.
range.moveStart('character',texthackOffset);
}
// Youpi! Now we get to repeat this trimming on the right side.
texthackOffset = 0;
borderElement=node.nextSibling;
for (; borderElement && (1 != borderElement.nodeType); borderElement = borderElement.nextSibling)
{
if (3 == borderElement.nodeType)
{
// IE doesn't count '\r' as a character, so we have to adjust the offset.
texthackOffset += borderElement.nodeValue.length-borderElement.nodeValue.split('\r').length-1;
if (!borderElement.nextSibling)
{
// When a text node is the last child, IE adds an extra selection
// "placeholder" for the newline character. We need to adjust for
// this character as well.
texthackOffset += 1;
}
}
}
if (borderElement && (1 == borderElement.nodeType))
{
trimmingRange.moveToElementText(borderElement);
range.setEndPoint('EndToStart', trimmingRange);
}
if (texthackOffset)
{
// We now need to move the selection backward the number of characters
// in all text nodes in between our text node and our ranges ending
// border.
range.moveEnd('character',-texthackOffset);
}
if (!node.nextSibling)
{
// Above we performed a slight adjustment to the offset if the text
// node contains a selectable "newline". We need to do the same if the
// node we are trying to select contains a newline.
range.moveEnd('character',-1);
}
}
else
{
range.moveToElementText(node);
}
}
if (typeof collapseToStart != "undefined")
{
range.collapse(collapseToStart);
if (!collapseToStart)
{
range.moveStart('character',-1);
range.moveEnd('character',-1);
}
}
range.select();
};
/** Insert HTML at the current position, deleting the selection if any.
*
* @param html string
*/
Xinha.prototype.insertHTML = function(html)
{
this.focusEditor();
var sel = this.getSelection();
var range = this.createRange(sel);
range.pasteHTML(html);
};
/** 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 (this.selectionEmpty(sel)) return '';
var range = this.createRange(sel);
// Need to be careful of control ranges which won't have htmlText
if( range.htmlText )
{
return range.htmlText;
}
else if(range.length >= 1)
{
return range.item(0).outerHTML;
}
return '';
};
/** Get a Selection object of the current selection. Note that selection objects are browser specific.
*
* @returns Selection
*/
Xinha.prototype.getSelection = function()
{
return this._doc.selection;
};
/** 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)
{
if (!sel) sel = this.getSelection();
// ticket:1508 - when you do a key event within a
// absolute position div, in IE, the toolbar update
// for formatblock etc causes a getParentElement() (above)
// which produces a "None" select, then if we focusEditor() it
// defocuses the absolute div and focuses into the iframe outside of the
// div somewhere.
//
// Removing this is probably a workaround and maybe it breaks something else
// focusEditor is used in a number of spots, I woudl have thought it should
// do nothing if the editor is already focused.
//
// if(sel.type == 'None') this.focusEditor();
return sel.createRange();
};
/** 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 in 9-10 modes
if(keyEvent.key.match(/^(Tab|Backspace|Del)/))
{
return true;
}
}
// Legacy
else
{
// Found using IE11 in 5-8 modes
if(keyEvent.keyCode == 9 // Tab
|| keyEvent.keyCode == 8 // Backspace
|| keyEvent.keyCode == 46 // Del
) return true;
}
};
/** 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.keyCode);
}
/** Return the HTML string of the given Element, including the Element.
*
* @param element HTML Element DomNode
* @returns string
*/
Xinha.getOuterHTML = function(element)
{
return element.outerHTML;
};
// Control character for retaining edit location when switching modes
Xinha.cc = String.fromCharCode(0x2009);
Xinha.prototype.setCC = function ( target )
{
var cc = Xinha.cc;
if ( target == "textarea" )
{
var ta = this._textArea;
var pos = document.selection.createRange();
pos.collapse();
pos.text = cc;
var index = ta.value.indexOf( cc );
var before = ta.value.substring( 0, index );
var after = ta.value.substring( index + cc.length , 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");
}
else
{
var sel = this.getSelection();
var r = sel.createRange();
if ( sel.type == 'Control' )
{
var control = r.item(0);
control.outerHTML += cc;
}
else
{
r.collapse();
r.text = cc;
}
}
};
Xinha.prototype.findCC = function ( target )
{
var findIn = ( target == 'textarea' ) ? this._textArea : this._doc.body;
range = findIn.createTextRange();
// in case the cursor is inside a link automatically created from a url
// the cc also appears in the url and we have to strip it out additionally
if( range.findText( escape(Xinha.cc) ) )
{
range.select();
range.text = '';
range.select();
}
if( range.findText( Xinha.cc ) )
{
range.select();
range.text = '';
range.select();
}
if ( target == 'textarea' ) this._textArea.focus();
};
/** Return a doctype or empty string depending on whether the document is in Qirksmode or Standards Compliant Mode
* It's hardly possible to detect the actual doctype without unreasonable effort, so we set HTML 4.01 just to trigger the rendering mode
*
* @param doc DOM element document
* @returns string doctype || empty
*/
Xinha.getDoctype = function (doc)
{
return (doc.compatMode == "CSS1Compat" && Xinha.ie_version < 8 ) ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">' : '';
};