12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096 |
- /*
- Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
- Available via Academic Free License >= 2.1 OR the modified BSD license.
- see: http://dojotoolkit.org/license for details
- */
- 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._CssStateMixin");
- dojo.require("dijit._editor.selection");
- dojo.require("dijit._editor.range");
- dojo.require("dijit._editor.html");
- // 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.value";
- dojo.style(savetextarea, {
- display:'none',
- position:'absolute',
- top:"-100px",
- height:"3px",
- 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.value" ' +
- '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, dijit._CssStateMixin], {
- constructor: function(params){
- // summary:
- // dijit._editor.RichText is the core of dijit.Editor, which provides basic
- // WYSIWYG editing features.
- //
- // description:
- // dijit._editor.RichText is the core of dijit.Editor, which provides basic
- // WYSIWYG editing features. It also encapsulates the differences
- // of different js engines for various browsers. Do not use this widget
- // with an HTML <TEXTAREA> tag, since the browser unescapes XML escape characters,
- // like <. This can have unexpected behavior and lead to security issues
- // such as scripting attacks.
- //
- // tags:
- // private
- // contentPreFilters: Function(String)[]
- // Pre content filter function register array.
- // these filters will be executed before the actual
- // editing area gets the html content.
- this.contentPreFilters = [];
- // contentPostFilters: Function(String)[]
- // post content filter function register array.
- // These will be used on the resulting html
- // from contentDomPostFilters. The resulting
- // content is the final html (returned by getValue()).
- this.contentPostFilters = [];
- // contentDomPreFilters: Function(DomNode)[]
- // 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: Function(DomNode)[]
- // 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: dojo._URL[]
- // array to store all the stylesheets applied to the editing area
- this.editingAreaStyleSheets = [];
- // Make a copy of this.events before we start writing into it, otherwise we
- // will modify the prototype which leads to bad things on pages w/multiple editors
- this.events = [].concat(this.events);
- this._keyHandlers = {};
- if(params && dojo.isString(params.value)){
- this.value = params.value;
- }
- this.onLoadDeferred = new dojo.Deferred();
- },
- baseClass: "dijitEditor",
- // inheritWidth: Boolean
- // whether to inherit the parent's width or simply use 100%
- inheritWidth: false,
- // focusOnLoad: [deprecated] Boolean
- // Focus into this widget when the page is loaded
- focusOnLoad: false,
- // name: String?
- // Specifies the name of a (hidden) <textarea> node on the page that's used to save
- // the editor content on page leave. Used to restore editor contents after navigating
- // to a new page and then hitting the back button.
- name: "",
- // styleSheets: [const] String
- // semicolon (";") separated list of css files for the editing area
- styleSheets: "",
- // 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="". If this editor is used within a layout widget,
- // set height="100%".
- height: "300px",
- // minHeight: String
- // The minimum height that the editor should have.
- minHeight: "1em",
- // isClosed: [private] Boolean
- isClosed: true,
- // isLoaded: [private] Boolean
- isLoaded: false,
- // _SEPARATOR: [private] String
- // Used to concat contents from multiple editors into a single string,
- // so they can be saved into a single <textarea> node. See "name" attribute.
- _SEPARATOR: "@@**%%__RICHTEXTBOUNDRY__%%**@@",
- // _NAME_CONTENT_SEP: [private] String
- // USed to separate name from content. Just a colon isn't safe.
- _NAME_CONTENT_SEP: "@@**%%:%%**@@",
- // onLoadDeferred: [readonly] dojo.Deferred
- // Deferred which is fired when the editor finishes loading.
- // Call myEditor.onLoadDeferred.then(callback) it to be informed
- // when the rich-text area initialization is finalized.
- onLoadDeferred: null,
- // isTabIndent: Boolean
- // Make tab key and shift-tab indent and outdent rather than navigating.
- // Caution: sing this makes web pages inaccessible to users unable to use a mouse.
- isTabIndent: false,
- // disableSpellCheck: [const] Boolean
- // When true, disables the browser's native spell checking, if supported.
- // Works only in Firefox.
- disableSpellCheck: false,
- postCreate: function(){
- if("textarea" == this.domNode.tagName.toLowerCase()){
- console.warn("RichText should not be used with the TEXTAREA tag. See dijit._editor.RichText docs.");
- }
- // Push in the builtin filters now, making them the first executed, but not over-riding anything
- // users passed in. See: #6062
- this.contentPreFilters = [dojo.hitch(this, "_preFixUrlAttributes")].concat(this.contentPreFilters);
- if(dojo.isMoz){
- this.contentPreFilters = [this._normalizeFontStyle].concat(this.contentPreFilters);
- this.contentPostFilters = [this._removeMozBogus].concat(this.contentPostFilters);
- }
- if(dojo.isWebKit){
- // Try to clean up WebKit bogus artifacts. The inserted classes
- // made by WebKit sometimes messes things up.
- this.contentPreFilters = [this._removeWebkitBogus].concat(this.contentPreFilters);
- this.contentPostFilters = [this._removeWebkitBogus].concat(this.contentPostFilters);
- }
- if(dojo.isIE){
- // IE generates <strong> and <em> but we want to normalize to <b> and <i>
- this.contentPostFilters = [this._normalizeFontStyle].concat(this.contentPostFilters);
- }
- this.inherited(arguments);
- 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.
- // tags:
- // protected
- var exec = dojo.hitch(this, function(cmd, arg){
- return function(){
- return !this.execCommand(cmd,arg);
- };
- });
- var ctrlKeyHandlers = {
- b: exec("bold"),
- i: exec("italic"),
- u: exec("underline"),
- a: exec("selectall"),
- s: function(){ this.save(true); },
- m: function(){ this.isTabIndent = !this.isTabIndent; },
- "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, true, false, ctrlKeyHandlers[key]);
- }
- },
- // events: [private] String[]
- // events which should be connected to the underlying editing area
- events: ["onKeyPress", "onKeyDown", "onKeyUp"], // onClick handled specially
- // captureEvents: [deprecated] String[]
- // Events which should be connected to the underlying editing
- // area, events in this array will be addListener with
- // capture=true.
- // TODO: looking at the code I don't see any distinction between events and captureEvents,
- // so get rid of this for 2.0 if not sooner
- captureEvents: [],
- _editorCommandsLocalized: false,
- _localizeEditorCommands: function(){
- // summary:
- // When IE is running in a non-English locale, the API actually changes,
- // so that we have to say (for example) danraku instead of p (for paragraph).
- // Handle that here.
- // tags:
- // private
- if(dijit._editor._editorCommandsLocalized){
- // Use the already generate cache of mappings.
- this._local2NativeFormatNames = dijit._editor._local2NativeFormatNames;
- this._native2LocalFormatNames = dijit._editor._native2LocalFormatNames;
- return;
- }
- dijit._editor._editorCommandsLocalized = true;
- dijit._editor._local2NativeFormatNames = {};
- dijit._editor._native2LocalFormatNames = {};
- this._local2NativeFormatNames = dijit._editor._local2NativeFormatNames;
- this._native2LocalFormatNames = dijit._editor._native2LocalFormatNames;
- //in IE, names for blockformat is locale dependent, so we cache the values here
- //put p after div, so if IE returns Normal, we show it as paragraph
- //We can distinguish p and div if IE returns Normal, however, in order to detect that,
- //we have to call this.document.selection.createRange().parentElement() or such, which
- //could slow things down. Leave it as it is for now
- var formats = ['div', 'p', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', 'address'];
- var localhtml = "", format, i=0;
- while((format=formats[i++])){
- //append a <br> after each element to separate the elements more reliably
- if(format.charAt(1) !== 'l'){
- localhtml += "<"+format+"><span>content</span></"+format+"><br/>";
- }else{
- localhtml += "<"+format+"><li>content</li></"+format+"><br/>";
- }
- }
- // queryCommandValue returns empty if we hide editNode, so move it out of screen temporary
- // Also, IE9 does weird stuff unless we do it inside the editor iframe.
- var style = { position: "absolute", top: "0px", zIndex: 10, opacity: 0.01 };
- var div = dojo.create('div', {style: style, innerHTML: localhtml});
- dojo.body().appendChild(div);
- // IE9 has a timing issue with doing this right after setting
- // the inner HTML, so put a delay in.
- var inject = dojo.hitch(this, function(){
- var node = div.firstChild;
- while(node){
- try{
- dijit._editor.selection.selectElement(node.firstChild);
- var nativename = node.tagName.toLowerCase();
- this._local2NativeFormatNames[nativename] = document.queryCommandValue("formatblock");
- this._native2LocalFormatNames[this._local2NativeFormatNames[nativename]] = nativename;
- node = node.nextSibling.nextSibling;
- //console.log("Mapped: ", nativename, " to: ", this._local2NativeFormatNames[nativename]);
- }catch(e) { /*Sqelch the occasional IE9 error */ }
- }
- div.parentNode.removeChild(div);
- div.innerHTML = "";
- });
- setTimeout(inject, 0);
- },
- open: function(/*DomNode?*/ element){
- // summary:
- // Transforms the node referenced in this.domNode into a rich text editing
- // node.
- // description:
- // Sets up the editing area asynchronously. This will result in
- // the creation and replacement with an iframe.
- // tags:
- // private
- 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 ]);
- if(arguments.length == 1 && element.nodeName){ // else unchanged
- this.domNode = element;
- }
- var dn = this.domNode;
- // "html" will hold the innerHTML of the srcNodeRef and will be used to
- // initialize the editor.
- var html;
- if(dojo.isString(this.value)){
- // Allow setting the editor content programmatically instead of
- // relying on the initial content being contained within the target
- // domNode.
- html = this.value;
- delete this.value;
- dn.innerHTML = "";
- }else if(dn.nodeName && dn.nodeName.toLowerCase() == "textarea"){
- // if we were created from a textarea, then we need to create a
- // new editing harness node.
- var ta = (this.textarea = dn);
- this.name = ta.name;
- html = ta.value;
- dn = this.domNode = dojo.doc.createElement("div");
- dn.setAttribute('widgetId', this.id);
- ta.removeAttribute('widgetId');
- dn.cssText = ta.cssText;
- dn.className += " " + ta.className;
- dojo.place(dn, ta, "before");
- var tmpFunc = dojo.hitch(this, function(){
- //some browsers refuse to submit display=none textarea, so
- //move the textarea off screen instead
- dojo.style(ta, {
- display: "block",
- position: "absolute",
- top: "-1000px"
- });
- if(dojo.isIE){ //nasty IE bug: abnormal formatting if overflow is not hidden
- var s = ta.style;
- this.__overflow = s.overflow;
- s.overflow = "hidden";
- }
- });
- if(dojo.isIE){
- setTimeout(tmpFunc, 10);
- }else{
- tmpFunc();
- }
- if(ta.form){
- var resetValue = ta.value;
- this.reset = function(){
- var current = this.getValue();
- if(current != resetValue){
- this.replaceValue(resetValue);
- }
- };
- dojo.connect(ta.form, "onsubmit", this, function(){
- // Copy value to the <textarea> so it gets submitted along with form.
- // FIXME: should we be calling close() here instead?
- dojo.attr(ta, 'disabled', this.disabled); // don't submit the value if disabled
- ta.value = this.getValue();
- });
- }
- }else{
- html = dijit._editor.getChildrenHtml(dn);
- dn.innerHTML = "";
- }
- var content = dojo.contentBox(dn);
- this._oldHeight = content.h;
- this._oldWidth = content.w;
- this.value = 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(dn.nodeName && dn.nodeName == "LI"){
- dn.innerHTML = " <br>";
- }
-
- // Construct the editor div structure.
- this.header = dn.ownerDocument.createElement("div");
- dn.appendChild(this.header);
- this.editingArea = dn.ownerDocument.createElement("div");
- dn.appendChild(this.editingArea);
- this.footer = dn.ownerDocument.createElement("div");
- dn.appendChild(this.footer);
- if(!this.name){
- this.name = this.id + "_AUTOGEN";
- }
- // User has pressed back/forward button so we lost the text in the editor, but it's saved
- // in a hidden <textarea> (which contains the data for all the editors on this page),
- // so get editor value from there
- if(this.name !== "" && (!dojo.config["useXDomain"] || dojo.config["allowXdRichTextSave"])){
- var saveTextarea = dojo.byId(dijit._scopeName + "._editor.RichText.value");
- if(saveTextarea && saveTextarea.value !== ""){
- var datas = saveTextarea.value.split(this._SEPARATOR), i=0, dat;
- while((dat=datas[i++])){
- var data = dat.split(this._NAME_CONTENT_SEP);
- if(data[0] == this.name){
- html = data[1];
- datas = datas.splice(i, 1);
- saveTextarea.value = datas.join(this._SEPARATOR);
- break;
- }
- }
- }
- if(!dijit._editor._globalSaveHandler){
- dijit._editor._globalSaveHandler = {};
- dojo.addOnUnload(function() {
- var id;
- for(id in dijit._editor._globalSaveHandler){
- var f = dijit._editor._globalSaveHandler[id];
- if(dojo.isFunction(f)){
- f();
- }
- }
- });
- }
- dijit._editor._globalSaveHandler[this.id] = dojo.hitch(this, "_saveContent");
- }
- this.isClosed = false;
- var ifr = (this.editorObject = this.iframe = dojo.doc.createElement('iframe'));
- ifr.id = this.id+"_iframe";
- this._iframeSrc = this._getIframeDocTxt();
- ifr.style.border = "none";
- ifr.style.width = "100%";
- if(this._layoutMode){
- // iframe should be 100% height, thus getting it's height from surrounding
- // <div> (which has the correct height set by Editor)
- ifr.style.height = "100%";
- }else{
- 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;
- }
- }
- ifr.frameBorder = 0;
- ifr._loadFunc = dojo.hitch( this, function(win){
- this.window = win;
- this.document = this.window.document;
- if(dojo.isIE){
- this._localizeEditorCommands();
- }
-
- // Do final setup and set initial contents of editor
- this.onLoad(html);
- });
- // Set the iframe's initial (blank) content.
- var s = 'javascript:parent.' + dijit._scopeName + '.byId("'+this.id+'")._iframeSrc';
- ifr.setAttribute('src', s);
- this.editingArea.appendChild(ifr);
- if(dojo.isSafari <= 4){
- var src = ifr.getAttribute("src");
- if(!src || src.indexOf("javascript") == -1){
- // Safari 4 and earlier sometimes act oddly
- // So we have to set it again.
- setTimeout(function(){ifr.setAttribute('src', s);},0);
- }
- }
- // TODO: this is a guess at the default line-height, kinda works
- if(dn.nodeName == "LI"){
- dn.lastChild.style.marginTop = "-1.2em";
- }
- dojo.addClass(this.domNode, this.baseClass);
- },
- //static cache variables shared among all instance of this class
- _local2NativeFormatNames: {},
- _native2LocalFormatNames: {},
- _getIframeDocTxt: function(){
- // summary:
- // Generates the boilerplate text of the document inside the iframe (ie, <html><head>...</head><body/></html>).
- // Editor content (if not blank) should be added afterwards.
- // tags:
- // private
- var _cs = dojo.getComputedStyle(this.domNode);
- // The contents inside of <body>. The real contents are set later via a call to setValue().
- var html = "";
- var setBodyId = true;
- if(dojo.isIE || dojo.isWebKit || (!this.height && !dojo.isMoz)){
- // In auto-expand mode, need a wrapper div for AlwaysShowToolbar plugin to correctly
- // expand/contract the editor as the content changes.
- html = "<div id='dijitEditorBody'></div>";
- setBodyId = false;
- }else if(dojo.isMoz){
- // workaround bug where can't select then delete text (until user types something
- // into the editor)... and/or issue where typing doesn't erase selected text
- this._cursorToStart = true;
- html = " ";
- }
- 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{
- // If we can't get a non-units value, just default
- // it to the CSS spec default of 'normal'. Seems to
- // work better, esp on IE, than '1.0'
- lineHeight = "normal";
- }
- var userStyle = "";
- var self = this;
- this.style.replace(/(^|;)\s*(line-|font-?)[^;]+/ig, function(match){
- match = match.replace(/^;/ig,"") + ';';
- var s = match.split(":")[0];
- if(s){
- s = dojo.trim(s);
- s = s.toLowerCase();
- var i;
- var sC = "";
- for(i = 0; i < s.length; i++){
- var c = s.charAt(i);
- switch(c){
- case "-":
- i++;
- c = s.charAt(i).toUpperCase();
- default:
- sC += c;
- }
- }
- dojo.style(self.domNode, sC, "");
- }
- userStyle += match + ';';
- });
- // need to find any associated label element and update iframe document title
- var label=dojo.query('label[for="'+this.id+'"]');
- return [
- this.isLeftToRight() ? "<html>\n<head>\n" : "<html dir='rtl'>\n<head>\n",
- (dojo.isMoz && label.length ? "<title>" + label[0].innerHTML + "</title>\n" : ""),
- "<meta http-equiv='Content-Type' content='text/html'>\n",
- "<style>\n",
- "\tbody,html {\n",
- "\t\tbackground:transparent;\n",
- "\t\tpadding: 1px 0 0 0;\n",
- "\t\tmargin: -1px 0 0 0;\n", // remove extraneous vertical scrollbar on safari and firefox
- // Set the html/body sizing. Webkit always needs this, other browsers
- // only set it when height is defined (not auto-expanding), otherwise
- // scrollers do not appear.
- ((dojo.isWebKit)?"\t\twidth: 100%;\n":""),
- ((dojo.isWebKit)?"\t\theight: 100%;\n":""),
- "\t}\n",
-
- // TODO: left positioning will cause contents to disappear out of view
- // if it gets too wide for the visible area
- "\tbody{\n",
- "\t\ttop:0px;\n",
- "\t\tleft:0px;\n",
- "\t\tright:0px;\n",
- "\t\tfont:", font, ";\n",
- ((this.height||dojo.isOpera) ? "" : "\t\tposition: fixed;\n"),
- // FIXME: IE 6 won't understand min-height?
- "\t\tmin-height:", this.minHeight, ";\n",
- "\t\tline-height:", lineHeight,";\n",
- "\t}\n",
- "\tp{ margin: 1em 0; }\n",
-
- // Determine how scrollers should be applied. In autoexpand mode (height = "") no scrollers on y at all.
- // But in fixed height mode we want both x/y scrollers. Also, if it's using wrapping div and in auto-expand
- // (Mainly IE) we need to kill the y scroller on body and html.
- (!setBodyId && !this.height ? "\tbody,html {overflow-y: hidden;}\n" : ""),
- "\t#dijitEditorBody{overflow-x: auto; overflow-y:" + (this.height ? "auto;" : "hidden;") + " outline: 0px;}\n",
- "\tli > ul:-moz-first-node, li > ol:-moz-first-node{ padding-top: 1.2em; }\n",
- // Can't set min-height in IE9, it puts layout on li, which puts move/resize handles.
- (!dojo.isIE ? "\tli{ min-height:1.2em; }\n" : ""),
- "</style>\n",
- this._applyEditingAreaStyleSheets(),"\n",
- "</head>\n<body ",
- (setBodyId?"id='dijitEditorBody' ":""),
- "onload='frameElement._loadFunc(window,document)' style='"+userStyle+"'>", html, "</body>\n</html>"
- ].join(""); // String
- },
- _applyEditingAreaStyleSheets: function(){
- // summary:
- // apply the specified css files in styleSheets
- // tags:
- // private
- 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);
- this.onLoadDeferred.addCallback(dojo.hitch(this, function(){
- 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");
- stylesheet.rel="stylesheet";
- stylesheet.type="text/css";
- stylesheet.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: Boolean
- // The editor is disabled; the text cannot be changed.
- disabled: false,
- _mozSettingProps: {'styleWithCSS':false},
- _setDisabledAttr: function(/*Boolean*/ value){
- value = !!value;
- this._set("disabled", value);
- if(!this.isLoaded){ return; } // this method requires init to be complete
- if(dojo.isIE || dojo.isWebKit || dojo.isOpera){
- var preventIEfocus = dojo.isIE && (this.isLoaded || !this.focusOnLoad);
- if(preventIEfocus){ this.editNode.unselectable = "on"; }
- this.editNode.contentEditable = !value;
- if(preventIEfocus){
- var _this = this;
- setTimeout(function(){ _this.editNode.unselectable = "off"; }, 0);
- }
- }else{ //moz
- try{
- this.document.designMode=(value?'off':'on');
- }catch(e){ return; } // ! _disabledOK
- if(!value && this._mozSettingProps){
- var ps = this._mozSettingProps;
- for(var n in ps){
- if(ps.hasOwnProperty(n)){
- try{
- this.document.execCommand(n,false,ps[n]);
- }catch(e2){}
- }
- }
- }
- // this.document.execCommand('contentReadOnly', false, value);
- // if(value){
- // this.blur(); //to remove the blinking caret
- // }
- }
- this._disabledOK = true;
- },
- /* Event handlers
- *****************/
- onLoad: function(/*String*/ html){
- // summary:
- // Handler after the iframe finishes loading.
- // html: String
- // Editor contents should be set to this value
- // tags:
- // protected
- // TODO: rename this to _onLoad, make empty public onLoad() method, deprecate/make protected onLoadDeferred handler?
- if(!this.window.__registeredWindow){
- this.window.__registeredWindow = true;
- this._iframeRegHandle = dijit.registerIframe(this.iframe);
- }
- if(!dojo.isIE && !dojo.isWebKit && (this.height || dojo.isMoz)){
- this.editNode=this.document.body;
- }else{
- // there's a wrapper div around the content, see _getIframeDocTxt().
- this.editNode=this.document.body.firstChild;
- var _this = this;
- if(dojo.isIE){ // #4996 IE wants to focus the BODY tag
- this.tabStop = dojo.create('div', { tabIndex: -1 }, this.editingArea);
- this.iframe.onfocus = function(){ _this.editNode.setActive(); };
- }
- }
- this.focusNode = this.editNode; // for InlineEditBox
- var events = this.events.concat(this.captureEvents);
- var ap = this.iframe ? this.document : this.editNode;
- dojo.forEach(events, function(item){
- this.connect(ap, item.toLowerCase(), item);
- }, this);
- this.connect(ap, "onmouseup", "onClick"); // mouseup in the margin does not generate an onclick event
- if(dojo.isIE){ // IE contentEditable
- this.connect(this.document, "onmousedown", "_onIEMouseDown"); // #4996 fix focus
- // give the node Layout on IE
- // TODO: this may no longer be needed, since we've reverted IE to using an iframe,
- // not contentEditable. Removing it would also probably remove the need for creating
- // the extra <div> in _getIframeDocTxt()
- this.editNode.style.zoom = 1.0;
- }else{
- this.connect(this.document, "onmousedown", function(){
- // Clear the moveToStart focus, as mouse
- // down will set cursor point. Required to properly
- // work with selection/position driven plugins and clicks in
- // the window. refs: #10678
- delete this._cursorToStart;
- });
- }
-
- if(dojo.isWebKit){
- //WebKit sometimes doesn't fire right on selections, so the toolbar
- //doesn't update right. Therefore, help it out a bit with an additional
- //listener. A mouse up will typically indicate a display change, so fire this
- //and get the toolbar to adapt. Reference: #9532
- this._webkitListener = this.connect(this.document, "onmouseup", "onDisplayChanged");
- this.connect(this.document, "onmousedown", function(e){
- var t = e.target;
- if(t && (t === this.document.body || t === this.document)){
- // Since WebKit uses the inner DIV, we need to check and set position.
- // See: #12024 as to why the change was made.
- setTimeout(dojo.hitch(this, "placeCursorAtEnd"), 0);
- }
- });
- }
-
- if(dojo.isIE){
- // Try to make sure 'hidden' elements aren't visible in edit mode (like browsers other than IE
- // do). See #9103
- try{
- this.document.execCommand('RespectVisibilityInDesign', true, null);
- }catch(e){/* squelch */}
- }
- this.isLoaded = true;
- this.set('disabled', this.disabled); // initialize content to editable (or not)
- // Note that setValue() call will only work after isLoaded is set to true (above)
- // Set up a function to allow delaying the setValue until a callback is fired
- // This ensures extensions like dijit.Editor have a way to hold the value set
- // until plugins load (and do things like register filters).
- var setContent = dojo.hitch(this, function(){
- this.setValue(html);
- if(this.onLoadDeferred){
- this.onLoadDeferred.callback(true);
- }
- this.onDisplayChanged();
- if(this.focusOnLoad){
- // after the document loads, then set focus after updateInterval expires so that
- // onNormalizedDisplayChanged has run to avoid input caret issues
- dojo.addOnLoad(dojo.hitch(this, function(){ setTimeout(dojo.hitch(this, "focus"), this.updateInterval); }));
- }
- // Save off the initial content now
- this.value = this.getValue(true);
- });
- if(this.setValueDeferred){
- this.setValueDeferred.addCallback(setContent);
- }else{
- setContent();
- }
- },
- onKeyDown: function(/* Event */ e){
- // summary:
- // Handler for onkeydown event
- // tags:
- // protected
- // 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(e.keyCode === dojo.keys.TAB && this.isTabIndent ){
- dojo.stopEvent(e); //prevent tab from moving focus out of editor
- // FIXME: this is a poor-man's indent/outdent. It would be
- // better if it added 4 " " chars in an undoable way.
- // Unfortunately pasteHTML does not prove to be undoable
- if(this.queryCommandEnabled((e.shiftKey ? "outdent" : "indent"))){
- this.execCommand((e.shiftKey ? "outdent" : "indent"));
- }
- }
- if(dojo.isIE){
- if(e.keyCode == dojo.keys.TAB && !this.isTabIndent){
- if(e.shiftKey && !e.ctrlKey && !e.altKey){
- // focus the BODY so the browser will tab away from it instead
- this.iframe.focus();
- }else if(!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);
- }
- }
- return true;
- },
- onKeyUp: function(e){
- // summary:
- // Handler for onkeyup event
- // tags:
- // callback
- return;
- },
- setDisabled: function(/*Boolean*/ disabled){
- // summary:
- // Deprecated, use set('disabled', ...) instead.
- // tags:
- // deprecated
- dojo.deprecated('dijit.Editor::setDisabled is deprecated','use dijit.Editor::attr("disabled",boolean) instead', 2.0);
- this.set('disabled',disabled);
- },
- _setValueAttr: function(/*String*/ value){
- // summary:
- // Registers that attr("value", foo) should call setValue(foo)
- this.setValue(value);
- },
- _setDisableSpellCheckAttr: function(/*Boolean*/ disabled){
- if(this.document){
- dojo.attr(this.document.body, "spellcheck", !disabled);
- }else{
- // try again after the editor is finished loading
- this.onLoadDeferred.addCallback(dojo.hitch(this, function(){
- dojo.attr(this.document.body, "spellcheck", !disabled);
- }));
- }
- this._set("disableSpellCheck", disabled);
- },
- onKeyPress: function(e){
- // summary:
- // Handle the various key events
- // tags:
- // protected
- var c = (e.keyChar && e.keyChar.toLowerCase()) || e.keyCode,
- handlers = this._keyHandlers[c],
- args = arguments;
- if(handlers && !e.altKey){
- dojo.some(handlers, function(h){
- // treat meta- same as ctrl-, for benefit of mac users
- if(!(h.shift ^ e.shiftKey) && !(h.ctrl ^ (e.ctrlKey||e.metaKey))){
- if(!h.handler.apply(this, args)){
- e.preventDefault();
- }
- return true;
- }
- }, this);
- }
- // function call after the character has been inserted
- if(!this._onKeyHitch){
- this._onKeyHitch = dojo.hitch(this, "onKeyPressed");
- }
- setTimeout(this._onKeyHitch, 1);
- return true;
- },
- addKeyHandler: function(/*String*/ key, /*Boolean*/ ctrl, /*Boolean*/ shift, /*Function*/ handler){
- // summary:
- // Add a handler for a keyboard shortcut
- // description:
- // The key argument should be in lowercase if it is a letter character
- // tags:
- // protected
- if(!dojo.isArray(this._keyHandlers[key])){
- this._keyHandlers[key] = [];
- }
- //TODO: would be nice to make this a hash instead of an array for quick lookups
- this._keyHandlers[key].push({
- shift: shift || false,
- ctrl: ctrl || false,
- handler: handler
- });
- },
- onKeyPressed: function(){
- // summary:
- // Handler for after the user has pressed a key, and the display has been updated.
- // (Runs on a timer so that it runs after the display is updated)
- // tags:
- // private
- this.onDisplayChanged(/*e*/); // can't pass in e
- },
- onClick: function(/*Event*/ e){
- // summary:
- // Handler for when the user clicks.
- // tags:
- // private
- // console.info('onClick',this._tryDesignModeOn);
- this.onDisplayChanged(e);
- },
- _onIEMouseDown: function(/*Event*/ e){
- // summary:
- // IE only to prevent 2 clicks to focus
- // tags:
- // protected
- if(!this._focused && !this.disabled){
- this.focus();
- }
- },
- _onBlur: function(e){
- // summary:
- // Called from focus manager when focus has moved away from this editor
- // tags:
- // protected
- // console.info('_onBlur')
- this.inherited(arguments);
- var newValue = this.getValue(true);
- if(newValue != this.value){
- this.onChange(newValue);
- }
- this._set("value", newValue);
- },
- _onFocus: function(/*Event*/ e){
- // summary:
- // Called from focus manager when focus has moved into this editor
- // tags:
- // protected
- // console.info('_onFocus')
- if(!this.disabled){
- if(!this._disabledOK){
- this.set('disabled', false);
- }
- this.inherited(arguments);
- }
- },
- // TODO: remove in 2.0
- blur: function(){
- // summary:
- // Remove focus from this instance.
- // tags:
- // deprecated
- 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 editor
- if(!this.isLoaded){
- this.focusOnLoad = true;
- return;
- }
- if(this._cursorToStart){
- delete this._cursorToStart;
- if(this.editNode.childNodes){
- this.placeCursorAtStart(); // this calls focus() so return
- return;
- }
- }
- 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
- // tags:
- // private
- // var _t=new Date();
- if(this._updateTimer){
- clearTimeout(this._updateTimer);
- }
- if(!this._updateHandler){
- this._updateHandler = dojo.hitch(this,"onNormalizedDisplayChanged");
- }
- this._updateTimer = setTimeout(this._updateHandler, this.updateInterval);
-
- // Technically this should trigger a call to watch("value", ...) registered handlers,
- // but getValue() is too slow to call on every keystroke so we don't.
- },
- onNormalizedDisplayChanged: function(){
- // summary:
- // This event is fired every updateInterval ms or more
- // description:
- // If something needs to happen immediately after a
- // user change, please use onDisplayChanged instead.
- // tags:
- // private
- delete this._updateTimer;
- },
- onChange: function(newContent){
- // summary:
- // This is fired if and only if the editor loses focus and
- // the content is changed.
- },
- _normalizeCommand: function(/*String*/ cmd, /*Anything?*/argument){
- // summary:
- // Used as the advice function by dojo.connect to map our
- // normalized set of commands to those supported by the target
- // browser.
- // tags:
- // private
- var command = cmd.toLowerCase();
- if(command == "formatblock"){
- if(dojo.isSafari && argument === undefined){ command = "heading"; }
- }else if(command == "hilitecolor" && !dojo.isMoz){
- command = "backcolor";
- }
- return command;
- },
- _qcaCache: {},
- 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
- // tags:
- // private
- // memoizing version. See _queryCommandAvailable for computing version
- var ca = this._qcaCache[command];
- if(ca !== undefined){ return ca; }
- return (this._qcaCache[command] = this._queryCommandAvailable(command));
- },
- _queryCommandAvailable: function(/*String*/ command){
- // summary:
- // See queryCommandAvailable().
- // tags:
- // private
- var ie = 1;
- var mozilla = 1 << 1;
- var webkit = 1 << 2;
- var opera = 1 << 3;
- function isSupportedBy(browsers){
- return {
- ie: Boolean(browsers & ie),
- mozilla: Boolean(browsers & mozilla),
- webkit: Boolean(browsers & webkit),
- 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 | webkit | 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": case "tabindent":
- supportedBy = isSupportedBy(mozilla | ie | opera | webkit);
- 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 | webkit);
- 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.isWebKit && supportedBy.webkit) ||
- (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
- // tags:
- // protected
- 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, argument);
- if(argument !== undefined){
- if(command == "heading"){
- throw new Error("unimplemented");
- }else if((command == "formatblock") && dojo.isIE){
- argument = '<'+argument+'>';
- }
- }
- //Check to see if we have any over-rides for commands, they will be functions on this
- //widget of the form _commandImpl. If we don't, fall through to the basic native
- //exec command of the browser.
- var implFunc = "_" + command + "Impl";
- if(this[implFunc]){
- returnValue = this[implFunc](argument);
- }else{
- argument = arguments.length > 1 ? argument : null;
- 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.
- // tags:
- // protected
- if(this.disabled || !this._disabledOK){ return false; }
- command = this._normalizeCommand(command);
- if(dojo.isMoz || dojo.isWebKit){
- if(command == "unlink"){ // mozilla returns true always
- // console.debug(this._sCall("hasAncestorElement", ['a']));
- return this._sCall("hasAncestorElement", ["a"]);
- }else if(command == "inserttable"){
- return true;
- }
- }
- //see #4109
- if(dojo.isWebKit){
- if(command == "cut" || command == "copy") {
- // WebKit deems clipboard activity as a security threat and natively would return false
- var sel = this.window.getSelection();
- if(sel){ sel = sel.toString(); }
- return !!sel;
- }else if(command == "paste"){
- return true;
- }
- }
- var elem = dojo.isIE ? this.document.selection.createRange() : this.document;
- try{
- return elem.queryCommandEnabled(command);
- }catch(e){
- //Squelch, occurs if editor is hidden on FF 3 (and maybe others.)
- return false;
- }
- },
- queryCommandState: function(command){
- // summary:
- // Check the state of a given command and returns true or false.
- // tags:
- // protected
- if(this.disabled || !this._disabledOK){ return false; }
- command = this._normalizeCommand(command);
- try{
- return this.document.queryCommandState(command);
- }catch(e){
- //Squelch, occurs if editor is hidden on FF 3 (and maybe others.)
- return false;
- }
- },
- queryCommandValue: function(command){
- // summary:
- // Check the value of a given command. This matters most for
- // custom selections and complex values like font value setting.
- // tags:
- // protected
- if(this.disabled || !this._disabledOK){ return false; }
- var r;
- command = this._normalizeCommand(command);
- if(dojo.isIE && command == "formatblock"){
- r = this._native2LocalFormatNames[this.document.queryCommandValue(command)];
- }else if(dojo.isMoz && command === "hilitecolor"){
- var oldValue;
- try{
- oldValue = this.document.queryCommandValue("styleWithCSS");
- }catch(e){
- oldValue = false;
- }
- this.document.execCommand("styleWithCSS", false, true);
- r = this.document.queryCommandValue(command);
- this.document.execCommand("styleWithCSS", false, oldValue);
- }else{
- r = this.document.queryCommandValue(command);
- }
- return r;
- },
- // Misc.
- _sCall: function(name, args){
- // summary:
- // Run the named method of dijit._editor.selection over the
- // current editor instance's window, with the passed args.
- // tags:
- // private
- return dojo.withGlobal(this.window, name, dijit._editor.selection, args);
- },
- // FIXME: this is a TON of code duplication. Why?
- placeCursorAtStart: function(){
- // summary:
- // Place the cursor at the start of the editing area.
- // tags:
- // private
- this.focus();
- //see comments in placeCursorAtEnd
- var isvalid=false;
- if(dojo.isMoz){
- // TODO: Is this branch even necessary?
- var first=this.editNode.firstChild;
- while(first){
- if(first.nodeType == 3){
- if(first.nodeValue.replace(/^\s+|\s+$/g, "").length>0){
- isvalid=true;
- this._sCall("selectElement", [ first ]);
- break;
- }
- }else if(first.nodeType == 1){
- isvalid=true;
- var tg = first.tagName ? first.tagName.toLowerCase() : "";
- // Collapse before childless tags.
- if(/br|input|img|base|meta|area|basefont|hr|link/.test(tg)){
- this._sCall("selectElement", [ first ]);
- }else{
- // Collapse inside tags with children.
- this._sCall("selectElementChildren", [ first ]);
- }
- break;
- }
- first = first.nextSibling;
- }
- }else{
- isvalid=true;
- this._sCall("selectElementChildren", [ this.editNode ]);
- }
- if(isvalid){
- this._sCall("collapse", [ true ]);
- }
- },
- placeCursorAtEnd: function(){
- // summary:
- // Place the cursor at the end of the editing area.
- // tags:
- // private
- 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;
- this._sCall("selectElement", [ last ]);
- break;
- }
- }else if(last.nodeType == 1){
- isvalid=true;
- if(last.lastChild){
- this._sCall("selectElement", [ last.lastChild ]);
- }else{
- this._sCall("selectElement", [ last ]);
- }
- break;
- }
- last = last.previousSibling;
- }
- }else{
- isvalid=true;
- this._sCall("selectElementChildren", [ this.editNode ]);
- }
- if(isvalid){
- this._sCall("collapse", [ false ]);
- }
- },
- getValue: function(/*Boolean?*/ nonDestructive){
- // summary:
- // Return the current content of the editing area (post filters
- // are applied). Users should call get('value') instead.
- // nonDestructive:
- // defaults to false. Should the post-filtering be run over a copy
- // of the live DOM? Most users should pass "true" here unless they
- // *really* know that none of the installed filters are going to
- // mess up the editing session.
- // tags:
- // private
- if(this.textarea){
- if(this.isClosed || !this.isLoaded){
- return this.textarea.value;
- }
- }
- return this._postFilterContent(null, nonDestructive);
- },
- _getValueAttr: function(){
- // summary:
- // Hook to make attr("value") work
- return this.getValue(true);
- },
- setValue: function(/*String*/ html){
- // summary:
- // This function sets the content. No undo history is preserved.
- // Users should use set('value', ...) instead.
- // tags:
- // deprecated
- // TODO: remove this and getValue() for 2.0, and move code to _setValueAttr()
- if(!this.isLoaded){
- // try again after the editor is finished loading
- this.onLoadDeferred.addCallback(dojo.hitch(this, function(){
- this.setValue(html);
- }));
- return;
- }
- this._cursorToStart = true;
- if(this.textarea && (this.isClosed || !this.isLoaded)){
- this.textarea.value=html;
- }else{
- html = this._preFilterContent(html);
- var node = this.isClosed ? this.domNode : this.editNode;
- if(html && dojo.isMoz && html.toLowerCase() == "<p></p>"){
- html = "<p> </p>";
- }
- // Use to avoid webkit problems where editor is disabled until the user clicks it
- if(!html && dojo.isWebKit){
- html = " ";
- }
- node.innerHTML = html;
- this._preDomFilterContent(node);
- }
- this.onDisplayChanged();
- this._set("value", this.getValue(true));
- },
- 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)
- // tags:
- // protected
- 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(!html){
- this._cursorToStart = true;
- 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);
- }
- this._set("value", this.getValue(true));
- },
- _preFilterContent: function(/*String*/ html){
- // summary:
- // Filter the input before setting the content of the editing
- // area. DOM pre-filtering may happen after this
- // string-based filtering takes place but as of 1.2, this is not
- // guaranteed for operations such as the inserthtml command.
- // tags:
- // private
- var ec = html;
- dojo.forEach(this.contentPreFilters, function(ef){ if(ef){ ec = ef(ec); } });
- return ec;
- },
- _preDomFilterContent: function(/*DomNode*/ dom){
- // summary:
- // filter the input's live DOM. All filter operations should be
- // considered to be "live" and operating on the DOM that the user
- // will be interacting with in their editing session.
- // tags:
- // private
- 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
- //
- // description:
- // post-filtering allows plug-ins and users to specify any number
- // of transforms over the editor's content, enabling many common
- // use-cases such as transforming absolute to relative URLs (and
- // vice-versa), ensuring conformance with a particular DTD, etc.
- // The filters are registered in the contentDomPostFilters and
- // contentPostFilters arrays. Each item in the
- // contentDomPostFilters array is a function which takes a DOM
- // Node or array of nodes as its only argument and returns the
- // same. It is then passed down the chain for further filtering.
- // The contentPostFilters array behaves the same way, except each
- // member operates on strings. Together, the DOM and string-based
- // filtering allow the full range of post-processing that should
- // be necessaray to enable even the most agressive of post-editing
- // conversions to take place.
- //
- // If nonDestructive is set to "true", the nodes are cloned before
- // filtering proceeds to avoid potentially destructive transforms
- // to the content which may still needed to be edited further.
- // Once DOM filtering has taken place, the serialized version of
- // the DOM which is passed is run through each of the
- // contentPostFilters functions.
- //
- // dom:
- // a node, set of nodes, which to filter using each of the current
- // members of the contentDomPostFilters and contentPostFilters arrays.
- //
- // nonDestructive:
- // defaults to "false". If true, ensures that filtering happens on
- // a clone of the passed-in content and not the actual node
- // itself.
- //
- // tags:
- // private
- var ec;
- if(!dojo.isString(dom)){
- dom = dom || this.editNode;
- if(this.contentDomPostFilters.length){
- if(nonDestructive){
- dom = dojo.clone(dom);
- }
- dojo.forEach(this.contentDomPostFilters, function(ef){
- dom = ef(dom);
- });
- }
- ec = dijit._editor.getChildrenHtml(dom);
- }else{
- ec = dom;
- }
- if(!dojo.trim(ec.replace(/^\xA0\xA0*/, '').replace(/\xA0\xA0*$/, '')).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
- // tags:
- // private
- var saveTextarea = dojo.byId(dijit._scopeName + "._editor.RichText.value");
- if(saveTextarea.value){
- saveTextarea.value += this._SEPARATOR;
- }
- saveTextarea.value += this.name + this._NAME_CONTENT_SEP + this.getValue(true);
- },
- escapeXml: function(/*String*/ str, /*Boolean*/ noSingleQuotes){
- // summary:
- // Adds escape sequences for special characters in XML.
- // Optionally skips escapes for single quotes
- // tags:
- // private
- str = str.replace(/&/gm, "&").replace(/</gm, "<").replace(/>/gm, ">").replace(/"/gm, """);
- if(!noSingleQuotes){
- str = str.replace(/'/gm, "'");
- }
- return str; // string
- },
- getNodeHtml: function(/* DomNode */ node){
- // summary:
- // Deprecated. Use dijit._editor._getNodeHtml() instead.
- // tags:
- // deprecated
- dojo.deprecated('dijit.Editor::getNodeHtml is deprecated','use dijit._editor.getNodeHtml instead', 2);
- return dijit._editor.getNodeHtml(node); // String
- },
- getNodeChildrenHtml: function(/* DomNode */ dom){
- // summary:
- // Deprecated. Use dijit._editor.getChildrenHtml() instead.
- // tags:
- // deprecated
- dojo.deprecated('dijit.Editor::getNodeChildrenHtml is deprecated','use dijit._editor.getChildrenHtml instead', 2);
- return dijit._editor.getChildrenHtml(dom);
- },
- close: function(/*Boolean?*/ save){
- // 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.
- // tags:
- // private
- if(this.isClosed){ return; }
- if(!arguments.length){ save = true; }
- if(save){
- this._set("value", this.getValue(true));
- }
- // 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._webkitListener){
- //Cleaup of WebKit fix: #9532
- this.disconnect(this._webkitListener);
- delete this._webkitListener;
- }
- // Guard against memory leaks on IE (see #9268)
- if(dojo.isIE){
- this.iframe.onfocus = null;
- }
- this.iframe._loadFunc = null;
- if(this._iframeRegHandle){
- dijit.unregisterIframe(this._iframeRegHandle);
- delete this._iframeRegHandle;
- }
- if(this.textarea){
- var s = this.textarea.style;
- s.position = "";
- s.left = s.top = "";
- if(dojo.isIE){
- s.overflow = this.__overflow;
- this.__overflow = null;
- }
- this.textarea.value = this.value;
- dojo.destroy(this.domNode);
- this.domNode = this.textarea;
- }else{
- // Note that this destroys the iframe
- this.domNode.innerHTML = this.value;
- }
- delete this.iframe;
- dojo.removeClass(this.domNode, this.baseClass);
- this.isClosed = true;
- this.isLoaded = false;
- delete this.editNode;
- delete this.focusNode;
- if(this.window && this.window._frameElement){
- this.window._frameElement = null;
- }
- this.window = null;
- this.document = null;
- this.editingArea = null;
- this.editorObject = null;
- },
- destroy: function(){
- if(!this.isClosed){ this.close(false); }
- this.inherited(arguments);
- if(dijit._editor._globalSaveHandler){
- delete dijit._editor._globalSaveHandler[this.id];
- }
- },
- _removeMozBogus: function(/* String */ html){
- // summary:
- // Post filter to remove unwanted HTML attributes generated by mozilla
- // tags:
- // private
- return html.replace(/\stype="_moz"/gi, '').replace(/\s_moz_dirty=""/gi, '').replace(/_moz_resizing="(true|false)"/gi,''); // String
- },
- _removeWebkitBogus: function(/* String */ html){
- // summary:
- // Post filter to remove unwanted HTML attributes generated by webkit
- // tags:
- // private
- html = html.replace(/\sclass="webkit-block-placeholder"/gi, '');
- html = html.replace(/\sclass="apple-style-span"/gi, '');
- // For some reason copy/paste sometime adds extra meta tags for charset on
- // webkit (chrome) on mac.They need to be removed. See: #12007"
- html = html.replace(/<meta charset=\"utf-8\" \/>/gi, '');
- return html; // String
- },
- _normalizeFontStyle: function(/* String */ html){
- // summary:
- // Convert 'strong' and 'em' to 'b' and 'i'.
- // description:
- // Moz can not handle strong/em tags correctly, so to help
- // mozilla and also to normalize output, convert them to 'b' and 'i'.
- //
- // Note the IE generates 'strong' and 'em' rather than 'b' and 'i'
- // tags:
- // private
- return html.replace(/<(\/)?strong([ \>])/gi, '<$1b$2')
- .replace(/<(\/)?em([ \>])/gi, '<$1i$2' ); // String
- },
- _preFixUrlAttributes: function(/* String */ html){
- // summary:
- // Pre-filter to do fixing to href attributes on <a> and <img> tags
- // tags:
- // private
- return html.replace(/(?:(<a(?=\s).*?\shref=)("|')(.*?)\2)|(?:(<a\s.*?href=)([^"'][^ >]+))/gi,
- '$1$4$2$3$5$2 _djrealurl=$2$3$5$2')
- .replace(/(?:(<img(?=\s).*?\ssrc=)("|')(.*?)\2)|(?:(<img\s.*?src=)([^"'][^ >]+))/gi,
- '$1$4$2$3$5$2 _djrealurl=$2$3$5$2'); // String
- },
- /*****************************************************************************
- The following functions implement HTML manipulation commands for various
- browser/contentEditable implementations. The goal of them is to enforce
- standard behaviors of them.
- ******************************************************************************/
- _inserthorizontalruleImpl: function(argument){
- // summary:
- // This function implements the insertion of HTML 'HR' tags.
- // into a point on the page. IE doesn't to it right, so
- // we have to use an alternate form
- // argument:
- // arguments to the exec command, if any.
- // tags:
- // protected
- if(dojo.isIE){
- return this._inserthtmlImpl("<hr>");
- }
- return this.document.execCommand("inserthorizontalrule", false, argument);
- },
- _unlinkImpl: function(argument){
- // summary:
- // This function implements the unlink of an 'a' tag.
- // argument:
- // arguments to the exec command, if any.
- // tags:
- // protected
- if((this.queryCommandEnabled("unlink")) && (dojo.isMoz || dojo.isWebKit)){
- var a = this._sCall("getAncestorElement", [ "a" ]);
- this._sCall("selectElement", [ a ]);
- return this.document.execCommand("unlink", false, null);
- }
- return this.document.execCommand("unlink", false, argument);
- },
- _hilitecolorImpl: function(argument){
- // summary:
- // This function implements the hilitecolor command
- // argument:
- // arguments to the exec command, if any.
- // tags:
- // protected
- var returnValue;
- if(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("hilitecolor", false, argument);
- this.document.execCommand("styleWithCSS", false, false);
- }else{
- returnValue = this.document.execCommand("hilitecolor", false, argument);
- }
- return returnValue;
- },
- _backcolorImpl: function(argument){
- // summary:
- // This function implements the backcolor command
- // argument:
- // arguments to the exec command, if any.
- // tags:
- // protected
- if(dojo.isIE){
- // 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 = argument ? argument : null;
- }
- return this.document.execCommand("backcolor", false, argument);
- },
- _forecolorImpl: function(argument){
- // summary:
- // This function implements the forecolor command
- // argument:
- // arguments to the exec command, if any.
- // tags:
- // protected
- if(dojo.isIE){
- // 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 = argument? argument : null;
- }
- return this.document.execCommand("forecolor", false, argument);
- },
- _inserthtmlImpl: function(argument){
- // summary:
- // This function implements the insertion of HTML content into
- // a point on the page.
- // argument:
- // The content to insert, if any.
- // tags:
- // protected
- argument = this._preFilterContent(argument);
- var rv = true;
- 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);
- }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
- this._sCall("remove"); // FIXME
- }else{
- rv = this.document.execCommand("inserthtml", false, argument);
- }
- return rv;
- },
- _boldImpl: function(argument){
- // summary:
- // This function implements an over-ride of the bold command.
- // argument:
- // Not used, operates by selection.
- // tags:
- // protected
- if(dojo.isIE){
- this._adaptIESelection()
- }
- return this.document.execCommand("bold", false, argument);
- },
-
- _italicImpl: function(argument){
- // summary:
- // This function implements an over-ride of the italic command.
- // argument:
- // Not used, operates by selection.
- // tags:
- // protected
- if(dojo.isIE){
- this._adaptIESelection()
- }
- return this.document.execCommand("italic", false, argument);
- },
- _underlineImpl: function(argument){
- // summary:
- // This function implements an over-ride of the underline command.
- // argument:
- // Not used, operates by selection.
- // tags:
- // protected
- if(dojo.isIE){
- this._adaptIESelection()
- }
- return this.document.execCommand("underline", false, argument);
- },
-
- _strikethroughImpl: function(argument){
- // summary:
- // This function implements an over-ride of the strikethrough command.
- // argument:
- // Not used, operates by selection.
- // tags:
- // protected
- if(dojo.isIE){
- this._adaptIESelection()
- }
- return this.document.execCommand("strikethrough", false, argument);
- },
- getHeaderHeight: function(){
- // summary:
- // A function for obtaining the height of the header node
- return this._getNodeChildrenHeight(this.header); // Number
- },
- getFooterHeight: function(){
- // summary:
- // A function for obtaining the height of the footer node
- return this._getNodeChildrenHeight(this.footer); // Number
- },
- _getNodeChildrenHeight: function(node){
- // summary:
- // An internal function for computing the cumulative height of all child nodes of 'node'
- // node:
- // The node to process the children of;
- var h = 0;
- if(node && node.childNodes){
- // IE didn't compute it right when position was obtained on the node directly is some cases,
- // so we have to walk over all the children manually.
- var i;
- for(i = 0; i < node.childNodes.length; i++){
- var size = dojo.position(node.childNodes[i]);
- h += size.h;
- }
- }
- return h; // Number
- },
-
- _isNodeEmpty: function(node, startOffset){
- // summary:
- // Function to test if a node is devoid of real content.
- // node:
- // The node to check.
- // tags:
- // private.
- if(node.nodeType == 1/*element*/){
- if(node.childNodes.length > 0){
- return this._isNodeEmpty(node.childNodes[0], startOffset);
- }
- return true;
- }else if(node.nodeType == 3/*text*/){
- return (node.nodeValue.substring(startOffset) == "");
- }
- return false;
- },
-
- _removeStartingRangeFromRange: function(node, range){
- // summary:
- // Function to adjust selection range by removing the current
- // start node.
- // node:
- // The node to remove from the starting range.
- // range:
- // The range to adapt.
- // tags:
- // private
- if(node.nextSibling){
- range.setStart(node.nextSibling,0);
- }else{
- var parent = node.parentNode;
- while(parent && parent.nextSibling == null){
- //move up the tree until we find a parent that has another node, that node will be the next node
- parent = parent.parentNode;
- }
- if(parent){
- range.setStart(parent.nextSibling,0);
- }
- }
- return range;
- },
-
- _adaptIESelection: function(){
- // summary:
- // Function to adapt the IE range by removing leading 'newlines'
- // Needed to fix issue with bold/italics/underline not working if
- // range included leading 'newlines'.
- // In IE, if a user starts a selection at the very end of a line,
- // then the native browser commands will fail to execute correctly.
- // To work around the issue, we can remove all empty nodes from
- // the start of the range selection.
- var selection = dijit.range.getSelection(this.window);
- if(selection && selection.rangeCount && !selection.isCollapsed){
- var range = selection.getRangeAt(0);
- var firstNode = range.startContainer;
- var startOffset = range.startOffset;
- while(firstNode.nodeType == 3/*text*/ && startOffset >= firstNode.length && firstNode.nextSibling){
- //traverse the text nodes until we get to the one that is actually highlighted
- startOffset = startOffset - firstNode.length;
- firstNode = firstNode.nextSibling;
- }
- //Remove the starting ranges until the range does not start with an empty node.
- var lastNode=null;
- while(this._isNodeEmpty(firstNode, startOffset) && firstNode != lastNode){
- lastNode =firstNode; //this will break the loop in case we can't find the next sibling
- range = this._removeStartingRangeFromRange(firstNode, range); //move the start container to the next node in the range
- firstNode = range.startContainer;
- startOffset = 0; //start at the beginning of the new starting range
- }
- selection.removeAllRanges();// this will work as long as users cannot select multiple ranges. I have not been able to do that in the editor.
- selection.addRange(range);
- }
- }
- });
- }
|