1450 lines
47 KiB
JavaScript
1450 lines
47 KiB
JavaScript
|
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("dijit._editor.html");
|
||
|
dojo.require("dojo.i18n");
|
||
|
dojo.requireLocalization("dijit.form", "Textarea", null, "zh,pt,da,tr,ru,de,ROOT,sv,ja,he,fi,nb,el,ar,pt-pt,cs,fr,es,ko,nl,zh-tw,pl,it,hu");
|
||
|
|
||
|
// used to restore content when user leaves this page then comes back
|
||
|
// but do not try doing dojo.doc.write if we are using xd loading.
|
||
|
// dojo.doc.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 dojo.config.allowXdRichTextSave = true.
|
||
|
if(!dojo.config["useXDomain"] || dojo.config["allowXdRichTextSave"]){
|
||
|
if(dojo._postLoad){
|
||
|
(function(){
|
||
|
var savetextarea = dojo.doc.createElement('textarea');
|
||
|
savetextarea.id = dijit._scopeName + "._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._scopeName + '._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.contentPostFilters.push(this._removeMozBogus);
|
||
|
}else if(dojo.isSafari){
|
||
|
this.contentPostFilters.push(this._removeSafariBogus);
|
||
|
}
|
||
|
//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._scopeName + "._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 exec = function(cmd, arg){
|
||
|
return arguments.length == 1 ? function(){ this.execCommand(cmd); } :
|
||
|
function(){ this.execCommand(cmd, arg); };
|
||
|
};
|
||
|
|
||
|
var ctrlKeyHandlers = { b: exec("bold"),
|
||
|
i: exec("italic"),
|
||
|
u: exec("underline"),
|
||
|
a: exec("selectall"),
|
||
|
s: function(){ this.save(true); },
|
||
|
|
||
|
"1": exec("formatblock", "h1"),
|
||
|
"2": exec("formatblock", "h2"),
|
||
|
"3": exec("formatblock", "h3"),
|
||
|
"4": exec("formatblock", "h4"),
|
||
|
|
||
|
"\\": exec("insertunorderedlist") };
|
||
|
|
||
|
if(!dojo.isIE){
|
||
|
ctrlKeyHandlers.Z = exec("redo"); //FIXME: undo?
|
||
|
}
|
||
|
|
||
|
for(var key in ctrlKeyHandlers){
|
||
|
this.addKeyHandler(key, this.KEY_CTRL, ctrlKeyHandlers[key]);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// 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=dojo.doc.createElement('div');
|
||
|
div.style.position = "absolute";
|
||
|
div.style.left = "-2000px";
|
||
|
div.style.top = "-2000px";
|
||
|
dojo.doc.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] = dojo.doc.queryCommandValue("formatblock");//this.queryCommandValue("formatblock");
|
||
|
this._native2LocalFormatNames[this._local2NativeFormatNames[nativename]] = nativename;
|
||
|
node = node.nextSibling;
|
||
|
}
|
||
|
dojo.doc.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._scopeName + "._editor.RichText::open", [ this ]);
|
||
|
|
||
|
this._content = "";
|
||
|
if((arguments.length == 1)&&(element["nodeName"])){ this.domNode = element; } // else unchanged
|
||
|
|
||
|
var html;
|
||
|
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;
|
||
|
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
|
||
|
dojo.attr(this.textarea, 'tabIndex', '-1');
|
||
|
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{
|
||
|
html = this._preFilterContent(dijit._editor.getChildrenHtml(this.domNode));
|
||
|
this.domNode.innerHTML = '';
|
||
|
}
|
||
|
if(html == ""){ html = " "; }
|
||
|
|
||
|
var content = dojo.contentBox(this.domNode);
|
||
|
// var content = dojo.contentBox(this.srcNodeRef);
|
||
|
this._oldHeight = content.h;
|
||
|
this._oldWidth = content.w;
|
||
|
|
||
|
// 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 != "" && (!dojo.config["useXDomain"] || dojo.config["allowXdRichTextSave"])){
|
||
|
var saveTextarea = dojo.byId(dijit._scopeName + "._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
|
||
|
this.connect(window, "onbeforeunload", "_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 dojo.doc.body.contentEditable != "undefined"){
|
||
|
if(dojo.isIE || dojo.isSafari || dojo.isOpera){ // contentEditable, easy
|
||
|
|
||
|
if(dojo.config["useXDomain"] && !dojo.config["dojoBlankHtmlUrl"]){
|
||
|
console.debug("dijit._editor.RichText: When using cross-domain Dojo builds,"
|
||
|
+ " please save dojo/resources/blank.html to your domain and set djConfig.dojoBlankHtmlUrl"
|
||
|
+ " to the path on your domain to blank.html");
|
||
|
}
|
||
|
|
||
|
var burl = dojo.config["dojoBlankHtmlUrl"] || (dojo.moduleUrl("dojo", "resources/blank.html")+"");
|
||
|
var ifr = this.editorObject = this.iframe = dojo.doc.createElement('iframe');
|
||
|
ifr.id = this.id+"_iframe";
|
||
|
ifr.src = burl;
|
||
|
ifr.style.border = "none";
|
||
|
ifr.style.width = "100%";
|
||
|
ifr.frameBorder = 0;
|
||
|
// ifr.style.scrolling = this.height ? "auto" : "vertical";
|
||
|
this.editingArea.appendChild(ifr);
|
||
|
var h = null; // set later in non-ie6 branch
|
||
|
var loadFunc = dojo.hitch( this, function(){
|
||
|
if(h){ dojo.disconnect(h); h = null; }
|
||
|
this.window = ifr.contentWindow;
|
||
|
var d = this.document = this.window.document;
|
||
|
d.open();
|
||
|
d.write(this._getIframeDocTxt(html));
|
||
|
d.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();
|
||
|
this.savedContent = this.getValue(true);
|
||
|
});
|
||
|
if(dojo.isIE && dojo.isIE < 7){ // IE 6 is a steaming pile...
|
||
|
var t = setInterval(function(){
|
||
|
if(ifr.contentWindow.isLoaded){
|
||
|
clearInterval(t);
|
||
|
loadFunc();
|
||
|
}
|
||
|
}, 100);
|
||
|
}else{ // blissful sanity!
|
||
|
h = dojo.connect(
|
||
|
((dojo.isIE) ? ifr.contentWindow : ifr), "onload", loadFunc
|
||
|
);
|
||
|
}
|
||
|
}else{ // designMode in iframe
|
||
|
this._drawIframe(html);
|
||
|
this.savedContent = this.getValue(true);
|
||
|
}
|
||
|
|
||
|
// 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(dojo.isIE || (!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;",
|
||
|
" font:", font, ";",
|
||
|
" padding: 1em 0 0 0;",
|
||
|
" margin: -1em 0 0 0;", // remove extraneous vertical scrollbar on safari and firefox
|
||
|
" height: 100%;",
|
||
|
"}",
|
||
|
// 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;"),
|
||
|
// FIXME: IE 6 won't understand min-height?
|
||
|
" min-height:", this.minHeight, ";",
|
||
|
" line-height:", lineHeight,
|
||
|
"}",
|
||
|
"p{ margin: 1em 0 !important; }",
|
||
|
(this.height ? // height:auto undoes the height:100%
|
||
|
"" : "body,html{height:auto;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");
|
||
|
ifr.id=this.id;
|
||
|
// this.iframe.src = "about:blank";
|
||
|
// dojo.doc.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.form", "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;
|
||
|
}
|
||
|
|
||
|
var tmpContent;
|
||
|
if(this.textarea){
|
||
|
tmpContent = this.srcNodeRef;
|
||
|
}else{
|
||
|
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();
|
||
|
if(dojo.isAIR){
|
||
|
contentDoc.body.innerHTML = html;
|
||
|
}else{
|
||
|
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.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");
|
||
|
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+" has not been applied");
|
||
|
return;
|
||
|
}
|
||
|
delete this.editingAreaStyleSheets[index];
|
||
|
dojo.withGlobal(this.window,'query', dojo, ['link:[href="'+url+'"]']).orphan()
|
||
|
},
|
||
|
|
||
|
disabled: true,
|
||
|
_mozSettingProps: ['styleWithCSS','insertBrOnReturn'],
|
||
|
setDisabled: function(/*Boolean*/ disabled){
|
||
|
if(dojo.isIE || dojo.isSafari || dojo.isOpera){
|
||
|
if(dojo.isIE){ this.editNode.unselectable = "on"; } // prevent IE from setting focus
|
||
|
this.editNode.contentEditable = !disabled;
|
||
|
if(dojo.isIE){
|
||
|
var _this = this;
|
||
|
setTimeout(function(){ _this.editNode.unselectable = "off"; }, 0);
|
||
|
}
|
||
|
}else{ //moz
|
||
|
if(disabled){
|
||
|
//AP: why isn't this set in the constructor, or put in mozSettingProps as a hash?
|
||
|
this._mozSettings=[false,this.blockNodeForEnter==='BR'];
|
||
|
}
|
||
|
this.document.designMode=(disabled?'off':'on');
|
||
|
if(!disabled && this._mozSettings){
|
||
|
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.window.__registeredWindow){
|
||
|
this.window.__registeredWindow=true;
|
||
|
dijit.registerWin(this.window);
|
||
|
}
|
||
|
if(!dojo.isIE && (this.height || dojo.isMoz)){
|
||
|
this.editNode=this.document.body;
|
||
|
}else{
|
||
|
this.editNode=this.document.body.firstChild;
|
||
|
var _this = this;
|
||
|
if(dojo.isIE){ // #4996 IE wants to focus the BODY tag
|
||
|
var tabStop = this.tabStop = dojo.doc.createElement('<div tabIndex=-1>');
|
||
|
this.editingArea.appendChild(tabStop);
|
||
|
this.iframe.onfocus = function(){ _this.editNode.setActive(); }
|
||
|
}
|
||
|
}
|
||
|
|
||
|
try{
|
||
|
this.setDisabled(false);
|
||
|
}catch(e){
|
||
|
// Firefox throws an exception if the editor is initially hidden
|
||
|
// so, if this fails, try again onClick by adding "once" advice
|
||
|
var handle = dojo.connect(this, "onClick", this, function(){
|
||
|
this.setDisabled(false);
|
||
|
dojo.disconnect(handle);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
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
|
||
|
//AP: what's the point of this?
|
||
|
// 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.connect(this.document, "onmousedown", "_onMouseDown"); // #4996 fix focus
|
||
|
this.editNode.style.zoom = 1.0;
|
||
|
}
|
||
|
|
||
|
if(this.focusOnLoad){
|
||
|
setTimeout(dojo.hitch(this, "focus"), 0); // have to wait for IE to set unselectable=off
|
||
|
}
|
||
|
|
||
|
this.onDisplayChanged(e);
|
||
|
if(this.onLoadDeferred){
|
||
|
this.onLoadDeferred.callback(true);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
onKeyDown: function(/* Event */ e){
|
||
|
// summary: Fired on keydown
|
||
|
|
||
|
// 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.TAB && e.shiftKey && !e.ctrlKey && !e.altKey){
|
||
|
// focus the BODY so the browser will tab away from it instead
|
||
|
this.iframe.focus();
|
||
|
}else if(e.keyCode == dojo.keys.TAB && !e.shiftKey && !e.ctrlKey && !e.altKey){
|
||
|
// focus the BODY so the browser will tab away from it instead
|
||
|
this.tabStop.focus();
|
||
|
}else 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
|
||
|
|
||
|
// handle the various key events
|
||
|
var modifiers = (e.ctrlKey && !e.altKey) ? 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.info('onClick',this._tryDesignModeOn);
|
||
|
this.onDisplayChanged(e);
|
||
|
},
|
||
|
|
||
|
_onMouseDown: function(/*Event*/e){ // IE only to prevent 2 clicks to focus
|
||
|
if(!this._focused && !this.disabled){
|
||
|
this.focus();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_onBlur: function(e){
|
||
|
this.inherited(arguments);
|
||
|
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;
|
||
|
}
|
||
|
},
|
||
|
_initialFocus: true,
|
||
|
_onFocus: function(/*Event*/e){
|
||
|
// summary: Fired on focus
|
||
|
this.inherited(arguments);
|
||
|
if(dojo.isMoz && this._initialFocus){
|
||
|
this._initialFocus = false;
|
||
|
if(this.editNode.innerHTML.replace(/^\s+|\s+$/g, "") == " "){
|
||
|
this.placeCursorAtStart();
|
||
|
// this.execCommand("selectall");
|
||
|
// this.window.getSelection().collapseToStart();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// TODO: why is this needed - should we deprecate this ?
|
||
|
blur: function(){
|
||
|
// summary: remove focus from this instance
|
||
|
if(!dojo.isIE && this.window.document.documentElement && this.window.document.documentElement.focus){
|
||
|
this.window.document.documentElement.focus();
|
||
|
}else if(dojo.doc.body.focus){
|
||
|
dojo.doc.body.focus();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
focus: function(){
|
||
|
// summary: move focus to this instance
|
||
|
if(!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
|
||
|
//this.editNode.focus(); -> causes IE to scroll always (strict and quirks mode) to the top the Iframe
|
||
|
// if we fire the event manually and let the browser handle the focusing, the latest
|
||
|
// cursor position is focused like in FF
|
||
|
this.iframe.fireEvent('onfocus', document.createEventObject()); // createEventObject only in IE
|
||
|
// }else{
|
||
|
// TODO: should we throw here?
|
||
|
// 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 == "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": case "toggledir":
|
||
|
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"){
|
||
|
argument=this._preFilterContent(argument);
|
||
|
if(dojo.isIE){
|
||
|
var insertRange = this.document.selection.createRange();
|
||
|
if(this.document.selection.type.toUpperCase()=='CONTROL'){
|
||
|
var n=insertRange.item(0);
|
||
|
while(insertRange.length){
|
||
|
insertRange.remove(insertRange.item(0));
|
||
|
}
|
||
|
n.outerHTML=argument;
|
||
|
}else{
|
||
|
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);
|
||
|
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
|
||
|
|
||
|
if(this.disabled){ return false; }
|
||
|
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
|
||
|
|
||
|
if(this.disabled){ return false; }
|
||
|
command = this._normalizeCommand(command);
|
||
|
return this.document.queryCommandState(command);
|
||
|
},
|
||
|
|
||
|
queryCommandValue: function(command){
|
||
|
// summary: check the value of a given command
|
||
|
|
||
|
if(this.disabled){ return false; }
|
||
|
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.isLoaded){
|
||
|
// try again after the editor is finished loading
|
||
|
this.onLoadDeferred.addCallback(dojo.hitch(this, function(){
|
||
|
this.setValue(html);
|
||
|
}));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if(this.textarea && (this.isClosed || !this.isLoaded)){
|
||
|
this.textarea.value=html;
|
||
|
}else{
|
||
|
html = this._preFilterContent(html);
|
||
|
var node = this.isClosed ? this.domNode : this.editNode;
|
||
|
node.innerHTML = html;
|
||
|
this._preDomFilterContent(node);
|
||
|
}
|
||
|
|
||
|
this.onDisplayChanged();
|
||
|
},
|
||
|
|
||
|
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 = " " }
|
||
|
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[]|String?*/dom,/*Boolean?*/nonDestructive){
|
||
|
// summary:
|
||
|
// filter the output after getting the content of the editing area
|
||
|
var ec;
|
||
|
if(!dojo.isString(dom)){
|
||
|
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);
|
||
|
});
|
||
|
}
|
||
|
ec = dijit._editor.getChildrenHtml(dom);
|
||
|
}else{
|
||
|
ec = dom;
|
||
|
}
|
||
|
|
||
|
if(!ec.replace(/^(?:\s|\xA0)+/g, "").replace(/(?:\s|\xA0)+$/g,"").length){ ec = ""; }
|
||
|
|
||
|
// if(dojo.isIE){
|
||
|
// //removing appended <P> </P> for IE
|
||
|
// ec = ec.replace(/(?:<p> </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._scopeName + "._editor.RichText.savedContent");
|
||
|
saveTextarea.value += this._SEPARATOR + this.name + ":" + this.getValue();
|
||
|
},
|
||
|
|
||
|
escapeXml: function(/*String*/str, /*Boolean*/noSingleQuotes){
|
||
|
dojo.deprecated('dijit.Editor::escapeXml is deprecated','use dijit._editor.escapeXml instead', 2);
|
||
|
return dijit._editor.escapeXml(str,noSingleQuotes);
|
||
|
},
|
||
|
|
||
|
getNodeHtml: function(/* DomNode */node){
|
||
|
dojo.deprecated('dijit.Editor::getNodeHtml is deprecated','use dijit._editor.getNodeHtml instead', 2);
|
||
|
return dijit._editor.getNodeHtml(node);
|
||
|
},
|
||
|
|
||
|
getNodeChildrenHtml: function(/* DomNode */dom){
|
||
|
dojo.deprecated('dijit.Editor::getNodeChildrenHtml is deprecated','use dijit._editor.getChildrenHtml instead', 2);
|
||
|
return dijit._editor.getChildrenHtml(dom);
|
||
|
},
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
this.textarea.value = save ? this._content : 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 = save ? this._content : 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);
|
||
|
},
|
||
|
|
||
|
_removeMozBogus: function(/* String */ html){
|
||
|
return html.replace(/\stype="_moz"/gi, '').replace(/\s_moz_dirty=""/gi, ''); // String
|
||
|
},
|
||
|
_removeSafariBogus: function(/* String */ html){
|
||
|
return html.replace(/\sclass="webkit-block-placeholder"/gi, ''); // String
|
||
|
},
|
||
|
_fixContentForMoz: function(/* String */ html){
|
||
|
// summary:
|
||
|
// Moz can not handle strong/em tags correctly, convert them to b/i
|
||
|
return html.replace(/<(\/)?strong([ \>])/gi, '<$1b$2')
|
||
|
.replace(/<(\/)?em([ \>])/gi, '<$1i$2' ); // String
|
||
|
},
|
||
|
|
||
|
_srcInImgRegex : /(?:(<img(?=\s).*?\ssrc=)("|')(.*?)\2)|(?:(<img\s.*?src=)([^"'][^ >]+))/gi ,
|
||
|
_hrefInARegex : /(?:(<a(?=\s).*?\shref=)("|')(.*?)\2)|(?:(<a\s.*?href=)([^"'][^ >]+))/gi ,
|
||
|
|
||
|
_preFixUrlAttributes: function(/* String */ html){
|
||
|
return html.replace(this._hrefInARegex, '$1$4$2$3$5$2 _djrealurl=$2$3$5$2')
|
||
|
.replace(this._srcInImgRegex, '$1$4$2$3$5$2 _djrealurl=$2$3$5$2'); // String
|
||
|
}
|
||
|
});
|
||
|
|
||
|
}
|