Subversion Repositories Applications.papyrus

Compare Revisions

Ignore whitespace Rev 2149 → Rev 2150

/trunk/api/js/dojo1.0/dijit/_editor/RichText.js
New file
0,0 → 1,1446
if(!dojo._hasResource["dijit._editor.RichText"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit._editor.RichText"] = true;
dojo.provide("dijit._editor.RichText");
 
dojo.require("dijit._Widget");
dojo.require("dijit._editor.selection");
dojo.require("dojo.i18n");
dojo.requireLocalization("dijit", "Textarea", null, "ROOT");
 
// used to restore content when user leaves this page then comes back
// but do not try doing document.write if we are using xd loading.
// document.write will only work if RichText.js is included in the dojo.js
// file. If it is included in dojo.js and you want to allow rich text saving
// for back/forward actions, then set djConfig.allowXdRichTextSave = true.
if(!djConfig["useXDomain"] || djConfig["allowXdRichTextSave"]){
if(dojo._postLoad){
(function(){
var savetextarea = dojo.doc.createElement('textarea');
savetextarea.id = "dijit._editor.RichText.savedContent";
var s = savetextarea.style;
s.display='none';
s.position='absolute';
s.top="-100px";
s.left="-100px"
s.height="3px";
s.width="3px";
dojo.body().appendChild(savetextarea);
})();
}else{
//dojo.body() is not available before onLoad is fired
try {
dojo.doc.write('<textarea id="dijit._editor.RichText.savedContent" ' +
'style="display:none;position:absolute;top:-100px;left:-100px;height:3px;width:3px;overflow:hidden;"></textarea>');
}catch(e){ }
}
}
dojo.declare("dijit._editor.RichText", [ dijit._Widget ], {
constructor: function(){
// summary:
// dijit._editor.RichText is the core of the WYSIWYG editor in dojo, which
// provides the basic editing features. It also encapsulates the differences
// of different js engines for various browsers
//
// contentPreFilters: Array
// pre content filter function register array.
// these filters will be executed before the actual
// editing area get the html content
this.contentPreFilters = [];
 
// contentPostFilters: Array
// post content filter function register array.
// these will be used on the resulting html
// from contentDomPostFilters. The resuling
// content is the final html (returned by getValue())
this.contentPostFilters = [];
 
// contentDomPreFilters: Array
// pre content dom filter function register array.
// these filters are applied after the result from
// contentPreFilters are set to the editing area
this.contentDomPreFilters = [];
 
// contentDomPostFilters: Array
// post content dom filter function register array.
// these filters are executed on the editing area dom
// the result from these will be passed to contentPostFilters
this.contentDomPostFilters = [];
 
// editingAreaStyleSheets: Array
// array to store all the stylesheets applied to the editing area
this.editingAreaStyleSheets=[];
 
this._keyHandlers = {};
this.contentPreFilters.push(dojo.hitch(this, "_preFixUrlAttributes"));
if(dojo.isMoz){
this.contentPreFilters.push(this._fixContentForMoz);
}
//this.contentDomPostFilters.push(this._postDomFixUrlAttributes);
 
this.onLoadDeferred = new dojo.Deferred();
},
 
// inheritWidth: Boolean
// whether to inherit the parent's width or simply use 100%
inheritWidth: false,
 
// focusOnLoad: Boolean
// whether focusing into this instance of richtext when page onload
focusOnLoad: false,
 
// name: String
// If a save name is specified the content is saved and restored when the user
// leave this page can come back, or if the editor is not properly closed after
// editing has started.
name: "",
 
// styleSheets: String
// semicolon (";") separated list of css files for the editing area
styleSheets: "",
 
// _content: String
// temporary content storage
_content: "",
 
// height: String
// set height to fix the editor at a specific height, with scrolling.
// By default, this is 300px. If you want to have the editor always
// resizes to accommodate the content, use AlwaysShowToolbar plugin
// and set height=""
height: "300px",
 
// minHeight: String
// The minimum height that the editor should have
minHeight: "1em",
// isClosed: Boolean
isClosed: true,
 
// isLoaded: Boolean
isLoaded: false,
 
// _SEPARATOR: String
// used to concat contents from multiple textareas into a single string
_SEPARATOR: "@@**%%__RICHTEXTBOUNDRY__%%**@@",
 
// onLoadDeferred: dojo.Deferred
// deferred which is fired when the editor finishes loading
onLoadDeferred: null,
 
postCreate: function(){
// summary: init
dojo.publish("dijit._editor.RichText::init", [this]);
this.open();
this.setupDefaultShortcuts();
},
 
setupDefaultShortcuts: function(){
// summary: add some default key handlers
// description:
// Overwrite this to setup your own handlers. The default
// implementation does not use Editor commands, but directly
// executes the builtin commands within the underlying browser
// support.
var ctrl = this.KEY_CTRL;
var exec = function(cmd, arg){
return arguments.length == 1 ? function(){ this.execCommand(cmd); } :
function(){ this.execCommand(cmd, arg); }
}
this.addKeyHandler("b", ctrl, exec("bold"));
this.addKeyHandler("i", ctrl, exec("italic"));
this.addKeyHandler("u", ctrl, exec("underline"));
this.addKeyHandler("a", ctrl, exec("selectall"));
this.addKeyHandler("s", ctrl, function () { this.save(true); });
 
this.addKeyHandler("1", ctrl, exec("formatblock", "h1"));
this.addKeyHandler("2", ctrl, exec("formatblock", "h2"));
this.addKeyHandler("3", ctrl, exec("formatblock", "h3"));
this.addKeyHandler("4", ctrl, exec("formatblock", "h4"));
 
this.addKeyHandler("\\", ctrl, exec("insertunorderedlist"));
if(!dojo.isIE){
this.addKeyHandler("Z", ctrl, exec("redo"));
}
},
 
// events: Array
// events which should be connected to the underlying editing area
events: ["onKeyPress", "onKeyDown", "onKeyUp", "onClick"],
 
// events: Array
// events which should be connected to the underlying editing
// area, events in this array will be addListener with
// capture=true
captureEvents: [],
 
_editorCommandsLocalized: false,
_localizeEditorCommands: function(){
if(this._editorCommandsLocalized){
return;
}
this._editorCommandsLocalized = true;
 
//in IE, names for blockformat is locale dependent, so we cache the values here
 
//if the normal way fails, we try the hard way to get the list
 
//do not use _cacheLocalBlockFormatNames here, as it will
//trigger security warning in IE7
 
//in the array below, ul can not come directly after ol,
//otherwise the queryCommandValue returns Normal for it
var formats = ['p', 'pre', 'address', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'div', 'ul'];
var localhtml = "", format, i=0;
while((format=formats[i++])){
if(format.charAt(1) != 'l'){
localhtml += "<"+format+"><span>content</span></"+format+">";
}else{
localhtml += "<"+format+"><li>content</li></"+format+">";
}
}
//queryCommandValue returns empty if we hide editNode, so move it out of screen temporary
var div=document.createElement('div');
div.style.position = "absolute";
div.style.left = "-2000px";
div.style.top = "-2000px";
document.body.appendChild(div);
div.innerHTML = localhtml;
var node = div.firstChild;
while(node){
dijit._editor.selection.selectElement(node.firstChild);
dojo.withGlobal(this.window, "selectElement", dijit._editor.selection, [node.firstChild]);
var nativename = node.tagName.toLowerCase();
this._local2NativeFormatNames[nativename] = document.queryCommandValue("formatblock");//this.queryCommandValue("formatblock");
this._native2LocalFormatNames[this._local2NativeFormatNames[nativename]] = nativename;
node = node.nextSibling;
}
document.body.removeChild(div);
},
 
open: function(/*DomNode?*/element){
// summary:
// Transforms the node referenced in this.domNode into a rich text editing
// node. This will result in the creation and replacement with an <iframe>
// if designMode(FF)/contentEditable(IE) is used.
 
if((!this.onLoadDeferred)||(this.onLoadDeferred.fired >= 0)){
this.onLoadDeferred = new dojo.Deferred();
}
 
if(!this.isClosed){ this.close(); }
dojo.publish("dijit._editor.RichText::open", [ this ]);
 
this._content = "";
if((arguments.length == 1)&&(element["nodeName"])){ this.domNode = element; } // else unchanged
 
if( (this.domNode["nodeName"])&&
(this.domNode.nodeName.toLowerCase() == "textarea")){
// if we were created from a textarea, then we need to create a
// new editing harness node.
this.textarea = this.domNode;
this.name=this.textarea.name;
var html = this._preFilterContent(this.textarea.value);
this.domNode = dojo.doc.createElement("div");
this.domNode.setAttribute('widgetId',this.id);
this.textarea.removeAttribute('widgetId');
this.domNode.cssText = this.textarea.cssText;
this.domNode.className += " "+this.textarea.className;
dojo.place(this.domNode, this.textarea, "before");
var tmpFunc = dojo.hitch(this, function(){
//some browsers refuse to submit display=none textarea, so
//move the textarea out of screen instead
with(this.textarea.style){
display = "block";
position = "absolute";
left = top = "-1000px";
 
if(dojo.isIE){ //nasty IE bug: abnormal formatting if overflow is not hidden
this.__overflow = overflow;
overflow = "hidden";
}
}
});
if(dojo.isIE){
setTimeout(tmpFunc, 10);
}else{
tmpFunc();
}
 
// this.domNode.innerHTML = html;
 
// if(this.textarea.form){
// // FIXME: port: this used to be before advice!!!
// dojo.connect(this.textarea.form, "onsubmit", this, function(){
// // FIXME: should we be calling close() here instead?
// this.textarea.value = this.getValue();
// });
// }
}else{
var html = this._preFilterContent(this.getNodeChildrenHtml(this.domNode));
this.domNode.innerHTML = '';
}
if(html == ""){ html = "&nbsp;"; }
 
var content = dojo.contentBox(this.domNode);
// var content = dojo.contentBox(this.srcNodeRef);
this._oldHeight = content.h;
this._oldWidth = content.w;
 
this.savedContent = html;
 
// If we're a list item we have to put in a blank line to force the
// bullet to nicely align at the top of text
if( (this.domNode["nodeName"]) &&
(this.domNode.nodeName == "LI") ){
this.domNode.innerHTML = " <br>";
}
 
this.editingArea = dojo.doc.createElement("div");
this.domNode.appendChild(this.editingArea);
 
if(this.name != "" && (!djConfig["useXDomain"] || djConfig["allowXdRichTextSave"])){
var saveTextarea = dojo.byId("dijit._editor.RichText.savedContent");
if(saveTextarea.value != ""){
var datas = saveTextarea.value.split(this._SEPARATOR), i=0, dat;
while((dat=datas[i++])){
var data = dat.split(":");
if(data[0] == this.name){
html = data[1];
datas.splice(i, 1);
break;
}
}
}
 
// FIXME: need to do something different for Opera/Safari
dojo.connect(window, "onbeforeunload", this, "_saveContent");
// dojo.connect(window, "onunload", this, "_saveContent");
}
 
this.isClosed = false;
// Safari's selections go all out of whack if we do it inline,
// so for now IE is our only hero
//if (typeof document.body.contentEditable != "undefined") {
if(dojo.isIE || dojo.isSafari || dojo.isOpera){ // contentEditable, easy
var ifr = this.iframe = dojo.doc.createElement('iframe');
ifr.src = 'javascript:void(0)';
this.editorObject = ifr;
ifr.style.border = "none";
ifr.style.width = "100%";
ifr.frameBorder = 0;
// ifr.style.scrolling = this.height ? "auto" : "vertical";
this.editingArea.appendChild(ifr);
this.window = ifr.contentWindow;
this.document = this.window.document;
this.document.open();
this.document.write(this._getIframeDocTxt(html));
this.document.close();
 
if(dojo.isIE >= 7){
if(this.height){
ifr.style.height = this.height;
}
if(this.minHeight){
ifr.style.minHeight = this.minHeight;
}
}else{
ifr.style.height = this.height ? this.height : this.minHeight;
}
 
if(dojo.isIE){
this._localizeEditorCommands();
}
 
this.onLoad();
}else{ // designMode in iframe
this._drawIframe(html);
}
 
// TODO: this is a guess at the default line-height, kinda works
if(this.domNode.nodeName == "LI"){ this.domNode.lastChild.style.marginTop = "-1.2em"; }
this.domNode.className += " RichTextEditable";
},
 
//static cache variables shared among all instance of this class
_local2NativeFormatNames: {},
_native2LocalFormatNames: {},
_localizedIframeTitles: null,
 
_getIframeDocTxt: function(/* String */ html){
var _cs = dojo.getComputedStyle(this.domNode);
if(!this.height && !dojo.isMoz){
html="<div>"+html+"</div>";
}
var font = [ _cs.fontWeight, _cs.fontSize, _cs.fontFamily ].join(" ");
 
// line height is tricky - applying a units value will mess things up.
// if we can't get a non-units value, bail out.
var lineHeight = _cs.lineHeight;
if(lineHeight.indexOf("px") >= 0){
lineHeight = parseFloat(lineHeight)/parseFloat(_cs.fontSize);
// console.debug(lineHeight);
}else if(lineHeight.indexOf("em")>=0){
lineHeight = parseFloat(lineHeight);
}else{
lineHeight = "1.0";
}
return [
this.isLeftToRight() ? "<html><head>" : "<html dir='rtl'><head>",
(dojo.isMoz ? "<title>" + this._localizedIframeTitles.iframeEditTitle + "</title>" : ""),
"<style>",
"body,html {",
" background:transparent;",
" padding: 0;",
" margin: 0;",
"}",
// TODO: left positioning will cause contents to disappear out of view
// if it gets too wide for the visible area
"body{",
" top:0px; left:0px; right:0px;",
((this.height||dojo.isOpera) ? "" : "position: fixed;"),
" font:", font, ";",
// FIXME: IE 6 won't understand min-height?
" min-height:", this.minHeight, ";",
" line-height:", lineHeight,
"}",
"p{ margin: 1em 0 !important; }",
(this.height ?
"" : "body,html{overflow-y:hidden;/*for IE*/} body > div {overflow-x:auto;/*for FF to show vertical scrollbar*/}"
),
"li > ul:-moz-first-node, li > ol:-moz-first-node{ padding-top: 1.2em; } ",
"li{ min-height:1.2em; }",
"</style>",
this._applyEditingAreaStyleSheets(),
"</head><body>"+html+"</body></html>"
].join(""); // String
},
 
_drawIframe: function(/*String*/html){
// summary:
// Draws an iFrame using the existing one if one exists.
// Used by Mozilla, Safari, and Opera
 
if(!this.iframe){
var ifr = this.iframe = dojo.doc.createElement("iframe");
// this.iframe.src = "about:blank";
// document.body.appendChild(this.iframe);
// console.debug(this.iframe.contentDocument.open());
// dojo.body().appendChild(this.iframe);
var ifrs = ifr.style;
// ifrs.border = "1px solid black";
ifrs.border = "none";
ifrs.lineHeight = "0"; // squash line height
ifrs.verticalAlign = "bottom";
// ifrs.scrolling = this.height ? "auto" : "vertical";
this.editorObject = this.iframe;
// get screen reader text for mozilla here, too
this._localizedIframeTitles = dojo.i18n.getLocalization("dijit", "Textarea");
// need to find any associated label element and update iframe document title
var label=dojo.query('label[for="'+this.id+'"]');
if(label.length){
this._localizedIframeTitles.iframeEditTitle = label[0].innerHTML + " " + this._localizedIframeTitles.iframeEditTitle;
}
}
// opera likes this to be outside the with block
// this.iframe.src = "javascript:void(0)";//dojo.uri.dojoUri("src/widget/templates/richtextframe.html") + ((dojo.doc.domain != currentDomain) ? ("#"+dojo.doc.domain) : "");
this.iframe.style.width = this.inheritWidth ? this._oldWidth : "100%";
 
if(this.height){
this.iframe.style.height = this.height;
}else{
this.iframe.height = this._oldHeight;
}
 
if(this.textarea){
var tmpContent = this.srcNodeRef;
}else{
var tmpContent = dojo.doc.createElement('div');
tmpContent.style.display="none";
tmpContent.innerHTML = html;
//append tmpContent to under the current domNode so that the margin
//calculation below is correct
this.editingArea.appendChild(tmpContent);
}
 
this.editingArea.appendChild(this.iframe);
 
//do we want to show the content before the editing area finish loading here?
//if external style sheets are used for the editing area, the appearance now
//and after loading of the editing area won't be the same (and padding/margin
//calculation above may not be accurate)
// tmpContent.style.display = "none";
// this.editingArea.appendChild(this.iframe);
 
var _iframeInitialized = false;
// console.debug(this.iframe);
// var contentDoc = this.iframe.contentWindow.document;
 
 
// note that on Safari lower than 420+, we have to get the iframe
// by ID in order to get something w/ a contentDocument property
 
var contentDoc = this.iframe.contentDocument;
contentDoc.open();
contentDoc.write(this._getIframeDocTxt(html));
contentDoc.close();
 
// now we wait for onload. Janky hack!
var ifrFunc = dojo.hitch(this, function(){
if(!_iframeInitialized){
_iframeInitialized = true;
}else{ return; }
if(!this.editNode){
try{
if(this.iframe.contentWindow){
this.window = this.iframe.contentWindow;
this.document = this.iframe.contentWindow.document
}else if(this.iframe.contentDocument){
// for opera
this.window = this.iframe.contentDocument.window;
this.document = this.iframe.contentDocument;
}
if(!this.document.body){
throw 'Error';
}
}catch(e){
setTimeout(ifrFunc,500);
_iframeInitialized = false;
return;
}
 
dojo._destroyElement(tmpContent);
this.document.designMode = "on";
// try{
// this.document.designMode = "on";
// }catch(e){
// this._tryDesignModeOnClick=true;
// }
 
this.onLoad();
}else{
dojo._destroyElement(tmpContent);
this.editNode.innerHTML = html;
this.onDisplayChanged();
}
this._preDomFilterContent(this.editNode);
});
 
ifrFunc();
},
 
_applyEditingAreaStyleSheets: function(){
// summary:
// apply the specified css files in styleSheets
var files = [];
if(this.styleSheets){
files = this.styleSheets.split(';');
this.styleSheets = '';
}
 
//empty this.editingAreaStyleSheets here, as it will be filled in addStyleSheet
files = files.concat(this.editingAreaStyleSheets);
this.editingAreaStyleSheets = [];
 
var text='', i=0, url;
while((url=files[i++])){
var abstring = (new dojo._Url(dojo.global.location, url)).toString();
this.editingAreaStyleSheets.push(abstring);
text += '<link rel="stylesheet" type="text/css" href="'+abstring+'"/>'
}
return text;
},
 
addStyleSheet: function(/*dojo._Url*/uri){
// summary:
// add an external stylesheet for the editing area
// uri: a dojo.uri.Uri pointing to the url of the external css file
var url=uri.toString();
 
//if uri is relative, then convert it to absolute so that it can be resolved correctly in iframe
if(url.charAt(0) == '.' || (url.charAt(0) != '/' && !uri.host)){
url = (new dojo._Url(dojo.global.location, url)).toString();
}
 
if(dojo.indexOf(this.editingAreaStyleSheets, url) > -1){
console.debug("dijit._editor.RichText.addStyleSheet: Style sheet "+url+" is already applied to the editing area!");
return;
}
 
this.editingAreaStyleSheets.push(url);
if(this.document.createStyleSheet){ //IE
this.document.createStyleSheet(url);
}else{ //other browser
var head = this.document.getElementsByTagName("head")[0];
var stylesheet = this.document.createElement("link");
with(stylesheet){
rel="stylesheet";
type="text/css";
href=url;
}
head.appendChild(stylesheet);
}
},
 
removeStyleSheet: function(/*dojo._Url*/uri){
// summary:
// remove an external stylesheet for the editing area
var url=uri.toString();
//if uri is relative, then convert it to absolute so that it can be resolved correctly in iframe
if(url.charAt(0) == '.' || (url.charAt(0) != '/' && !uri.host)){
url = (new dojo._Url(dojo.global.location, url)).toString();
}
var index = dojo.indexOf(this.editingAreaStyleSheets, url);
if(index == -1){
console.debug("dijit._editor.RichText.removeStyleSheet: Style sheet "+url+" is not applied to the editing area so it can not be removed!");
return;
}
delete this.editingAreaStyleSheets[index];
dojo.withGlobal(this.window,'query', dojo, ['link:[href="'+url+'"]']).orphan()
},
 
disabled: false,
_mozSettingProps: ['styleWithCSS','insertBrOnReturn'],
setDisabled: function(/*Boolean*/ disabled){
if(dojo.isIE || dojo.isSafari || dojo.isOpera){
this.editNode.contentEditable=!disabled;
}else{ //moz
if(disabled){
this._mozSettings=[false,this.blockNodeForEnter==='BR'];
}
this.document.designMode=(disabled?'off':'on');
if(!disabled){
dojo.forEach(this._mozSettingProps, function(s,i){
this.document.execCommand(s,false,this._mozSettings[i]);
},this);
}
// this.document.execCommand('contentReadOnly', false, disabled);
// if(disabled){
// this.blur(); //to remove the blinking caret
// }
//
}
this.disabled=disabled;
},
 
/* Event handlers
*****************/
 
_isResized: function(){ return false; },
 
onLoad: function(/* Event */ e){
// summary: handler after the content of the document finishes loading
this.isLoaded = true;
if(this.height || dojo.isMoz){
this.editNode=this.document.body;
}else{
this.editNode=this.document.body.firstChild;
}
this.editNode.contentEditable = true; //should do no harm in FF
this._preDomFilterContent(this.editNode);
 
var events=this.events.concat(this.captureEvents),i=0,et;
while((et=events[i++])){
this.connect(this.document, et.toLowerCase(), et);
}
if(!dojo.isIE){
try{ // sanity check for Mozilla
// this.document.execCommand("useCSS", false, true); // old moz call
this.document.execCommand("styleWithCSS", false, false); // new moz call
//this.document.execCommand("insertBrOnReturn", false, false); // new moz call
}catch(e2){ }
// FIXME: when scrollbars appear/disappear this needs to be fired
}else{ // IE contentEditable
// give the node Layout on IE
this.editNode.style.zoom = 1.0;
}
 
if(this.focusOnLoad){
this.focus();
}
 
this.onDisplayChanged(e);
if(this.onLoadDeferred){
this.onLoadDeferred.callback(true);
}
},
 
onKeyDown: function(/* Event */ e){
// summary: Fired on keydown
 
// console.info("onkeydown:", e.keyCode);
 
// we need this event at the moment to get the events from control keys
// such as the backspace. It might be possible to add this to Dojo, so that
// keyPress events can be emulated by the keyDown and keyUp detection.
if(dojo.isIE){
if(e.keyCode === dojo.keys.BACKSPACE && this.document.selection.type === "Control"){
// IE has a bug where if a non-text object is selected in the editor,
// hitting backspace would act as if the browser's back button was
// clicked instead of deleting the object. see #1069
dojo.stopEvent(e);
this.execCommand("delete");
}else if( (65 <= e.keyCode&&e.keyCode <= 90) ||
(e.keyCode>=37&&e.keyCode<=40) // FIXME: get this from connect() instead!
){ //arrow keys
e.charCode = e.keyCode;
this.onKeyPress(e);
}
}
else if (dojo.isMoz){
if(e.keyCode == dojo.keys.TAB && !e.shiftKey && !e.ctrlKey && !e.altKey && this.iframe){
// update iframe document title for screen reader
this.iframe.contentDocument.title = this._localizedIframeTitles.iframeFocusTitle;
// Place focus on the iframe. A subsequent tab or shift tab will put focus
// on the correct control.
this.iframe.focus(); // this.focus(); won't work
dojo.stopEvent(e);
}else if (e.keyCode == dojo.keys.TAB && e.shiftKey){
// if there is a toolbar, set focus to it, otherwise ignore
if (this.toolbar){
this.toolbar.focus();
}
dojo.stopEvent(e);
}
}
},
 
onKeyUp: function(e){
// summary: Fired on keyup
return;
},
 
KEY_CTRL: 1,
KEY_SHIFT: 2,
 
onKeyPress: function(e){
// summary: Fired on keypress
 
// console.info("onkeypress:", e.keyCode);
 
// handle the various key events
var modifiers = e.ctrlKey ? this.KEY_CTRL : 0 | e.shiftKey?this.KEY_SHIFT : 0;
 
var key = e.keyChar||e.keyCode;
if(this._keyHandlers[key]){
// console.debug("char:", e.key);
var handlers = this._keyHandlers[key], i = 0, h;
while((h = handlers[i++])){
if(modifiers == h.modifiers){
if(!h.handler.apply(this,arguments)){
e.preventDefault();
}
break;
}
}
}
 
// function call after the character has been inserted
setTimeout(dojo.hitch(this, function(){
this.onKeyPressed(e);
}), 1);
},
 
addKeyHandler: function(/*String*/key, /*Int*/modifiers, /*Function*/handler){
// summary: add a handler for a keyboard shortcut
if(!dojo.isArray(this._keyHandlers[key])){ this._keyHandlers[key] = []; }
this._keyHandlers[key].push({
modifiers: modifiers || 0,
handler: handler
});
},
 
onKeyPressed: function(/*Event*/e){
this.onDisplayChanged(/*e*/); // can't pass in e
},
 
onClick: function(/*Event*/e){
// console.debug('onClick',this._tryDesignModeOnClick);
// if(this._tryDesignModeOnClick){
// try{
// this.document.designMode='on';
// this._tryDesignModeOnClick=false;
// }catch(e){}
// }
this.onDisplayChanged(e); },
_onBlur: function(e){
var _c=this.getValue(true);
if(_c!=this.savedContent){
this.onChange(_c);
this.savedContent=_c;
}
if (dojo.isMoz && this.iframe){
this.iframe.contentDocument.title = this._localizedIframeTitles.iframeEditTitle;
}
// console.info('_onBlur')
},
_initialFocus: true,
_onFocus: function(/*Event*/e){
// console.info('_onFocus')
// summary: Fired on focus
if( (dojo.isMoz)&&(this._initialFocus) ){
this._initialFocus = false;
if(this.editNode.innerHTML.replace(/^\s+|\s+$/g, "") == "&nbsp;"){
this.placeCursorAtStart();
// this.execCommand("selectall");
// this.window.getSelection().collapseToStart();
}
}
},
 
blur: function(){
// summary: remove focus from this instance
if(this.iframe){
this.window.blur();
}else if(this.editNode){
this.editNode.blur();
}
},
 
focus: function(){
// summary: move focus to this instance
if(this.iframe && !dojo.isIE){
dijit.focus(this.iframe);
}else if(this.editNode && this.editNode.focus){
// editNode may be hidden in display:none div, lets just punt in this case
dijit.focus(this.editNode);
}else{
console.debug("Have no idea how to focus into the editor!");
}
},
 
// _lastUpdate: 0,
updateInterval: 200,
_updateTimer: null,
onDisplayChanged: function(/*Event*/e){
// summary:
// This event will be fired everytime the display context
// changes and the result needs to be reflected in the UI.
// description:
// If you don't want to have update too often,
// onNormalizedDisplayChanged should be used instead
 
// var _t=new Date();
if(!this._updateTimer){
// this._lastUpdate=_t;
if(this._updateTimer){
clearTimeout(this._updateTimer);
}
this._updateTimer=setTimeout(dojo.hitch(this,this.onNormalizedDisplayChanged),this.updateInterval);
}
},
onNormalizedDisplayChanged: function(){
// summary:
// This event is fired every updateInterval ms or more
// description:
// If something needs to happen immidiately after a
// user change, please use onDisplayChanged instead
this._updateTimer=null;
},
onChange: function(newContent){
// summary:
// this is fired if and only if the editor loses focus and
// the content is changed
 
// console.log('onChange',newContent);
},
_normalizeCommand: function(/*String*/cmd){
// summary:
// Used as the advice function by dojo.connect to map our
// normalized set of commands to those supported by the target
// browser
 
var command = cmd.toLowerCase();
if(command == "formatblock"){
if(dojo.isSafari){ command = "heading"; }
}else if(command == "hilitecolor" && !dojo.isMoz){
command = "backcolor";
}
 
return command;
},
 
queryCommandAvailable: function(/*String*/command){
// summary:
// Tests whether a command is supported by the host. Clients SHOULD check
// whether a command is supported before attempting to use it, behaviour
// for unsupported commands is undefined.
// command: The command to test for
var ie = 1;
var mozilla = 1 << 1;
var safari = 1 << 2;
var opera = 1 << 3;
var safari420 = 1 << 4;
 
var gt420 = dojo.isSafari;
 
function isSupportedBy(browsers){
return {
ie: Boolean(browsers & ie),
mozilla: Boolean(browsers & mozilla),
safari: Boolean(browsers & safari),
safari420: Boolean(browsers & safari420),
opera: Boolean(browsers & opera)
}
}
 
var supportedBy = null;
 
switch(command.toLowerCase()){
case "bold": case "italic": case "underline":
case "subscript": case "superscript":
case "fontname": case "fontsize":
case "forecolor": case "hilitecolor":
case "justifycenter": case "justifyfull": case "justifyleft":
case "justifyright": case "delete": case "selectall":
supportedBy = isSupportedBy(mozilla | ie | safari | opera);
break;
 
case "createlink": case "unlink": case "removeformat":
case "inserthorizontalrule": case "insertimage":
case "insertorderedlist": case "insertunorderedlist":
case "indent": case "outdent": case "formatblock":
case "inserthtml": case "undo": case "redo": case "strikethrough":
supportedBy = isSupportedBy(mozilla | ie | opera | safari420);
break;
 
case "blockdirltr": case "blockdirrtl":
case "dirltr": case "dirrtl":
case "inlinedirltr": case "inlinedirrtl":
supportedBy = isSupportedBy(ie);
break;
case "cut": case "copy": case "paste":
supportedBy = isSupportedBy( ie | mozilla | safari420);
break;
 
case "inserttable":
supportedBy = isSupportedBy(mozilla | ie);
break;
 
case "insertcell": case "insertcol": case "insertrow":
case "deletecells": case "deletecols": case "deleterows":
case "mergecells": case "splitcell":
supportedBy = isSupportedBy(ie | mozilla);
break;
 
default: return false;
}
 
return (dojo.isIE && supportedBy.ie) ||
(dojo.isMoz && supportedBy.mozilla) ||
(dojo.isSafari && supportedBy.safari) ||
(gt420 && supportedBy.safari420) ||
(dojo.isOpera && supportedBy.opera); // Boolean return true if the command is supported, false otherwise
},
 
execCommand: function(/*String*/command, argument){
// summary: Executes a command in the Rich Text area
// command: The command to execute
// argument: An optional argument to the command
var returnValue;
 
//focus() is required for IE to work
//In addition, focus() makes sure after the execution of
//the command, the editor receives the focus as expected
this.focus();
 
command = this._normalizeCommand(command);
if(argument != undefined){
if(command == "heading"){
throw new Error("unimplemented");
}else if((command == "formatblock") && dojo.isIE){
argument = '<'+argument+'>';
}
}
if(command == "inserthtml"){
//TODO: we shall probably call _preDomFilterContent here as well
argument=this._preFilterContent(argument);
if(dojo.isIE){
var insertRange = this.document.selection.createRange();
insertRange.pasteHTML(argument);
insertRange.select();
//insertRange.collapse(true);
returnValue=true;
}else if(dojo.isMoz && !argument.length){
//mozilla can not inserthtml an empty html to delete current selection
//so we delete the selection instead in this case
dojo.withGlobal(this.window,'remove',dijit._editor.selection); // FIXME
returnValue=true;
}else{
returnValue=this.document.execCommand(command, false, argument);
}
}else if(
(command == "unlink")&&
(this.queryCommandEnabled("unlink"))&&
(dojo.isMoz || dojo.isSafari)
){
// fix up unlink in Mozilla to unlink the link and not just the selection
 
// grab selection
// Mozilla gets upset if we just store the range so we have to
// get the basic properties and recreate to save the selection
var selection = this.window.getSelection();
// var selectionRange = selection.getRangeAt(0);
// var selectionStartContainer = selectionRange.startContainer;
// var selectionStartOffset = selectionRange.startOffset;
// var selectionEndContainer = selectionRange.endContainer;
// var selectionEndOffset = selectionRange.endOffset;
 
// select our link and unlink
var a = dojo.withGlobal(this.window, "getAncestorElement",dijit._editor.selection, ['a']);
dojo.withGlobal(this.window, "selectElement", dijit._editor.selection, [a]);
 
returnValue=this.document.execCommand("unlink", false, null);
}else if((command == "hilitecolor")&&(dojo.isMoz)){
// // mozilla doesn't support hilitecolor properly when useCSS is
// // set to false (bugzilla #279330)
 
this.document.execCommand("styleWithCSS", false, true);
returnValue = this.document.execCommand(command, false, argument);
this.document.execCommand("styleWithCSS", false, false);
 
}else if((dojo.isIE)&&( (command == "backcolor")||(command == "forecolor") )){
// Tested under IE 6 XP2, no problem here, comment out
// IE weirdly collapses ranges when we exec these commands, so prevent it
// var tr = this.document.selection.createRange();
argument = arguments.length > 1 ? argument : null;
returnValue = this.document.execCommand(command, false, argument);
 
// timeout is workaround for weird IE behavior were the text
// selection gets correctly re-created, but subsequent input
// apparently isn't bound to it
// setTimeout(function(){tr.select();}, 1);
}else{
argument = arguments.length > 1 ? argument : null;
// if(dojo.isMoz){
// this.document = this.iframe.contentWindow.document
// }
 
if(argument || command!="createlink"){
returnValue = this.document.execCommand(command, false, argument);
}
}
 
this.onDisplayChanged();
return returnValue;
},
 
queryCommandEnabled: function(/*String*/command){
// summary: check whether a command is enabled or not
command = this._normalizeCommand(command);
if(dojo.isMoz || dojo.isSafari){
if(command == "unlink"){ // mozilla returns true always
// console.debug(dojo.withGlobal(this.window, "hasAncestorElement",dijit._editor.selection, ['a']));
return dojo.withGlobal(this.window, "hasAncestorElement",dijit._editor.selection, ['a']);
}else if (command == "inserttable"){
return true;
}
}
//see #4109
if(dojo.isSafari)
if(command == "copy"){
command="cut";
}else if(command == "paste"){
return true;
}
 
// return this.document.queryCommandEnabled(command);
var elem = (dojo.isIE) ? this.document.selection.createRange() : this.document;
return elem.queryCommandEnabled(command);
},
 
queryCommandState: function(command){
// summary: check the state of a given command
command = this._normalizeCommand(command);
return this.document.queryCommandState(command);
},
 
queryCommandValue: function(command){
// summary: check the value of a given command
command = this._normalizeCommand(command);
if(dojo.isIE && command == "formatblock"){
return this._local2NativeFormatNames[this.document.queryCommandValue(command)];
}
return this.document.queryCommandValue(command);
},
 
// Misc.
 
placeCursorAtStart: function(){
// summary:
// place the cursor at the start of the editing area
this.focus();
 
//see comments in placeCursorAtEnd
var isvalid=false;
if(dojo.isMoz){
var first=this.editNode.firstChild;
while(first){
if(first.nodeType == 3){
if(first.nodeValue.replace(/^\s+|\s+$/g, "").length>0){
isvalid=true;
dojo.withGlobal(this.window, "selectElement", dijit._editor.selection, [first]);
break;
}
}else if(first.nodeType == 1){
isvalid=true;
dojo.withGlobal(this.window, "selectElementChildren",dijit._editor.selection, [first]);
break;
}
first = first.nextSibling;
}
}else{
isvalid=true;
dojo.withGlobal(this.window, "selectElementChildren",dijit._editor.selection, [this.editNode]);
}
if(isvalid){
dojo.withGlobal(this.window, "collapse", dijit._editor.selection, [true]);
}
},
 
placeCursorAtEnd: function(){
// summary:
// place the cursor at the end of the editing area
this.focus();
 
//In mozilla, if last child is not a text node, we have to use selectElementChildren on this.editNode.lastChild
//otherwise the cursor would be placed at the end of the closing tag of this.editNode.lastChild
var isvalid=false;
if(dojo.isMoz){
var last=this.editNode.lastChild;
while(last){
if(last.nodeType == 3){
if(last.nodeValue.replace(/^\s+|\s+$/g, "").length>0){
isvalid=true;
dojo.withGlobal(this.window, "selectElement",dijit._editor.selection, [last]);
break;
}
}else if(last.nodeType == 1){
isvalid=true;
if(last.lastChild){
dojo.withGlobal(this.window, "selectElement",dijit._editor.selection, [last.lastChild]);
}else{
dojo.withGlobal(this.window, "selectElement",dijit._editor.selection, [last]);
}
break;
}
last = last.previousSibling;
}
}else{
isvalid=true;
dojo.withGlobal(this.window, "selectElementChildren",dijit._editor.selection, [this.editNode]);
}
if(isvalid){
dojo.withGlobal(this.window, "collapse", dijit._editor.selection, [false]);
}
},
 
getValue: function(/*Boolean?*/nonDestructive){
// summary:
// return the current content of the editing area (post filters are applied)
if(this.textarea){
if(this.isClosed || !this.isLoaded){
return this.textarea.value;
}
}
 
return this._postFilterContent(null, nonDestructive);
},
 
setValue: function(/*String*/html){
// summary:
// this function set the content. No undo history is preserved
if(this.textarea && (this.isClosed || !this.isLoaded)){
this.textarea.value=html;
}else{
html = this._preFilterContent(html);
if(this.isClosed){
this.domNode.innerHTML = html;
this._preDomFilterContent(this.domNode);
}else{
this.editNode.innerHTML = html;
this._preDomFilterContent(this.editNode);
}
}
},
 
replaceValue: function(/*String*/html){
// summary:
// this function set the content while trying to maintain the undo stack
// (now only works fine with Moz, this is identical to setValue in all
// other browsers)
if(this.isClosed){
this.setValue(html);
}else if(this.window && this.window.getSelection && !dojo.isMoz){ // Safari
// look ma! it's a totally f'd browser!
this.setValue(html);
}else if(this.window && this.window.getSelection){ // Moz
html = this._preFilterContent(html);
this.execCommand("selectall");
if(dojo.isMoz && !html){ html = "&nbsp;" }
this.execCommand("inserthtml", html);
this._preDomFilterContent(this.editNode);
}else if(this.document && this.document.selection){//IE
//In IE, when the first element is not a text node, say
//an <a> tag, when replacing the content of the editing
//area, the <a> tag will be around all the content
//so for now, use setValue for IE too
this.setValue(html);
}
},
 
_preFilterContent: function(/*String*/html){
// summary:
// filter the input before setting the content of the editing area
var ec = html;
dojo.forEach(this.contentPreFilters, function(ef){ if(ef){ ec = ef(ec); } });
return ec;
},
_preDomFilterContent: function(/*DomNode*/dom){
// summary:
// filter the input
dom = dom || this.editNode;
dojo.forEach(this.contentDomPreFilters, function(ef){
if(ef && dojo.isFunction(ef)){
ef(dom);
}
}, this);
},
 
_postFilterContent: function(/*DomNode|DomNode[]?*/dom,/*Boolean?*/nonDestructive){
// summary:
// filter the output after getting the content of the editing area
dom = dom || this.editNode;
if(this.contentDomPostFilters.length){
if(nonDestructive && dom['cloneNode']){
dom = dom.cloneNode(true);
}
dojo.forEach(this.contentDomPostFilters, function(ef){
dom = ef(dom);
});
}
var ec = this.getNodeChildrenHtml(dom);
if(!ec.replace(/^(?:\s|\xA0)+/g, "").replace(/(?:\s|\xA0)+$/g,"").length){ ec = ""; }
 
// if(dojo.isIE){
// //removing appended <P>&nbsp;</P> for IE
// ec = ec.replace(/(?:<p>&nbsp;</p>[\n\r]*)+$/i,"");
// }
dojo.forEach(this.contentPostFilters, function(ef){
ec = ef(ec);
});
 
return ec;
},
 
_saveContent: function(/*Event*/e){
// summary:
// Saves the content in an onunload event if the editor has not been closed
var saveTextarea = dojo.byId("dijit._editor.RichText.savedContent");
saveTextarea.value += this._SEPARATOR + this.name + ":" + this.getValue();
},
 
 
escapeXml: function(/*String*/str, /*Boolean*/noSingleQuotes){
//summary:
// Adds escape sequences for special characters in XML: &<>"'
// Optionally skips escapes for single quotes
str = str.replace(/&/gm, "&amp;").replace(/</gm, "&lt;").replace(/>/gm, "&gt;").replace(/"/gm, "&quot;");
if(!noSingleQuotes){
str = str.replace(/'/gm, "&#39;");
}
return str; // string
},
 
getNodeHtml: function(/* DomNode */node){
switch(node.nodeType){
case 1: //element node
var output = '<'+node.tagName.toLowerCase();
if(dojo.isMoz){
if(node.getAttribute('type')=='_moz'){
node.removeAttribute('type');
}
if(node.getAttribute('_moz_dirty') != undefined){
node.removeAttribute('_moz_dirty');
}
}
//store the list of attributes and sort it to have the
//attributes appear in the dictionary order
var attrarray = [];
if(dojo.isIE){
var s = node.outerHTML;
s = s.substr(0,s.indexOf('>'));
s = s.replace(/(?:['"])[^"']*\1/g, '');//to make the following regexp safe
var reg = /([^\s=]+)=/g;
var m, key;
while((m = reg.exec(s)) != undefined){
key=m[1];
if(key.substr(0,3) != '_dj'){
if(key == 'src' || key == 'href'){
if(node.getAttribute('_djrealurl')){
attrarray.push([key,node.getAttribute('_djrealurl')]);
continue;
}
}
if(key == 'class'){
attrarray.push([key,node.className]);
}else{
attrarray.push([key,node.getAttribute(key)]);
}
}
}
}else{
var attr, i=0, attrs = node.attributes;
while((attr=attrs[i++])){
//ignore all attributes starting with _dj which are
//internal temporary attributes used by the editor
if(attr.name.substr(0,3) != '_dj' /*&&
(attr.specified == undefined || attr.specified)*/){
var v = attr.value;
if(attr.name == 'src' || attr.name == 'href'){
if(node.getAttribute('_djrealurl')){
v = node.getAttribute('_djrealurl');
}
}
attrarray.push([attr.name,v]);
}
}
}
attrarray.sort(function(a,b){
return a[0]<b[0]?-1:(a[0]==b[0]?0:1);
});
i=0;
while((attr=attrarray[i++])){
output += ' '+attr[0]+'="'+attr[1]+'"';
}
if(node.childNodes.length){
output += '>' + this.getNodeChildrenHtml(node)+'</'+node.tagName.toLowerCase()+'>';
}else{
output += ' />';
}
break;
case 3: //text
// FIXME:
var output = this.escapeXml(node.nodeValue,true);
break;
case 8: //comment
// FIXME:
var output = '<!--'+this.escapeXml(node.nodeValue,true)+'-->';
break;
default:
var output = "Element not recognized - Type: " + node.nodeType + " Name: " + node.nodeName;
}
return output;
},
 
getNodeChildrenHtml: function(/* DomNode */dom){
// summary: Returns the html content of a DomNode and children
var out = "";
if(!dom){ return out; }
var nodes = dom["childNodes"]||dom;
var i=0;
var node;
while((node=nodes[i++])){
out += this.getNodeHtml(node);
}
return out; // String
},
 
close: function(/*Boolean*/save, /*Boolean*/force){
// summary:
// Kills the editor and optionally writes back the modified contents to the
// element from which it originated.
// save:
// Whether or not to save the changes. If false, the changes are discarded.
// force:
if(this.isClosed){return false; }
 
if(!arguments.length){ save = true; }
this._content = this.getValue();
var changed = (this.savedContent != this._content);
 
// line height is squashed for iframes
// FIXME: why was this here? if (this.iframe){ this.domNode.style.lineHeight = null; }
 
if(this.interval){ clearInterval(this.interval); }
 
if(this.textarea){
with(this.textarea.style){
position = "";
left = top = "";
if(dojo.isIE){
overflow = this.__overflow;
this.__overflow = null;
}
}
if(save){
this.textarea.value = this._content;
}else{
this.textarea.value = this.savedContent;
}
dojo._destroyElement(this.domNode);
this.domNode = this.textarea;
}else{
if(save){
//why we treat moz differently? comment out to fix #1061
// if(dojo.isMoz){
// var nc = dojo.doc.createElement("span");
// this.domNode.appendChild(nc);
// nc.innerHTML = this.editNode.innerHTML;
// }else{
// this.domNode.innerHTML = this._content;
// }
this.domNode.innerHTML = this._content;
}else{
this.domNode.innerHTML = this.savedContent;
}
}
 
dojo.removeClass(this.domNode, "RichTextEditable");
this.isClosed = true;
this.isLoaded = false;
// FIXME: is this always the right thing to do?
delete this.editNode;
 
if(this.window && this.window._frameElement){
this.window._frameElement = null;
}
 
this.window = null;
this.document = null;
this.editingArea = null;
this.editorObject = null;
 
return changed; // Boolean: whether the content has been modified
},
 
destroyRendering: function(){
// summary: stub
},
 
destroy: function(){
this.destroyRendering();
if(!this.isClosed){ this.close(false); }
this.inherited("destroy",arguments);
//dijit._editor.RichText.superclass.destroy.call(this);
},
 
_fixContentForMoz: function(/* String */ html){
// summary:
// Moz can not handle strong/em tags correctly, convert them to b/i
html = html.replace(/<(\/)?strong([ \>])/gi, '<$1b$2' );
html = html.replace(/<(\/)?em([ \>])/gi, '<$1i$2' );
return html; // String
},
 
_srcInImgRegex : /(?:(<img(?=\s).*?\ssrc=)("|')(.*?)\2)|(?:(<img\s.*?src=)([^"'][^ >]+))/gi ,
_hrefInARegex : /(?:(<a(?=\s).*?\shref=)("|')(.*?)\2)|(?:(<a\s.*?href=)([^"'][^ >]+))/gi ,
 
_preFixUrlAttributes: function(/* String */ html){
html = html.replace(this._hrefInARegex, '$1$4$2$3$5$2 _djrealurl=$2$3$5$2') ;
html = html.replace(this._srcInImgRegex, '$1$4$2$3$5$2 _djrealurl=$2$3$5$2') ;
return html; // String
}
});
 
}