diff options
author | Barak Korren <[email protected]> | 2013-04-02 20:38:07 +0300 |
---|---|---|
committer | Barak Korren <[email protected]> | 2013-04-02 20:38:07 +0300 |
commit | 58a2577d48790c79adfd44bcfd662c980ce6cfe4 (patch) | |
tree | 523d814ea0b7b6f617fe515b186099c6e83fed72 /lib/dijit/_editor/RichText.js.uncompressed.js | |
parent | e470a273cf09562fb2f9c0c899002303f19c8d16 (diff) | |
parent | cc332603431102a682feda22b9cf0093a29f0176 (diff) |
Merge branch 'master' of https://github.com/gothfox/Tiny-Tiny-RSS.git
Diffstat (limited to 'lib/dijit/_editor/RichText.js.uncompressed.js')
-rw-r--r-- | lib/dijit/_editor/RichText.js.uncompressed.js | 2874 |
1 files changed, 0 insertions, 2874 deletions
diff --git a/lib/dijit/_editor/RichText.js.uncompressed.js b/lib/dijit/_editor/RichText.js.uncompressed.js deleted file mode 100644 index 5fd3e0962..000000000 --- a/lib/dijit/_editor/RichText.js.uncompressed.js +++ /dev/null @@ -1,2874 +0,0 @@ -define("dijit/_editor/RichText", [ - "dojo/_base/array", // array.forEach array.indexOf array.some - "dojo/_base/config", // config - "dojo/_base/declare", // declare - "dojo/_base/Deferred", // Deferred - "dojo/dom", // dom.byId - "dojo/dom-attr", // domAttr.set or get - "dojo/dom-class", // domClass.add domClass.remove - "dojo/dom-construct", // domConstruct.create domConstruct.destroy domConstruct.place - "dojo/dom-geometry", // domGeometry.position - "dojo/dom-style", // domStyle.getComputedStyle domStyle.set - "dojo/_base/event", // event.stop - "dojo/_base/kernel", // kernel.deprecated - "dojo/keys", // keys.BACKSPACE keys.TAB - "dojo/_base/lang", // lang.clone lang.hitch lang.isArray lang.isFunction lang.isString lang.trim - "dojo/on", // on() - "dojo/query", // query - "dojo/ready", // ready - "dojo/sniff", // has("ie") has("mozilla") has("opera") has("safari") has("webkit") - "dojo/topic", // topic.publish() (publish) - "dojo/_base/unload", // unload - "dojo/_base/url", // url - "dojo/_base/window", // win.global - "../_Widget", - "../_CssStateMixin", - "./selection", - "./range", - "./html", - "../focus", - "../main" // dijit._scopeName -], function(array, config, declare, Deferred, dom, domAttr, domClass, domConstruct, domGeometry, domStyle, - event, kernel, keys, lang, on, query, ready, has, topic, unload, _Url, win, - _Widget, _CssStateMixin, selectionapi, rangeapi, htmlapi, focus, dijit){ - -// module: -// dijit/_editor/RichText -// summary: -// dijit/_editor/RichText is the core of dijit/Editor, which provides basic -// WYSIWYG editing features. - -// if you want to allow for rich text saving with back/forward actions, you must add a text area to your page with -// the id==dijit._scopeName + "._editor.RichText.value" (typically "dijit/_editor/RichText.value). For example, -// something like this will work: -// -// <textarea id="dijit._editor.RichText.value" style="display:none;position:absolute;top:-100px;left:-100px;height:3px;width:3px;overflow:hidden;"></textarea> -// - -var RichText = declare("dijit._editor.RichText", [_Widget, _CssStateMixin], { - // 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 - - constructor: function(params /*===== , srcNodeRef =====*/){ - // summary: - // Create the widget. - // params: Object|null - // Initial settings for any of the widget attributes, except readonly attributes. - // srcNodeRef: DOMNode - // The widget replaces the specified DOMNode. - - // 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 && lang.isString(params.value)){ - this.value = params.value; - } - - this.onLoadDeferred = new 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 = [lang.hitch(this, "_preFixUrlAttributes")].concat(this.contentPreFilters); - if(has("mozilla")){ - this.contentPreFilters = [this._normalizeFontStyle].concat(this.contentPreFilters); - this.contentPostFilters = [this._removeMozBogus].concat(this.contentPostFilters); - } - if(has("webkit")){ - // 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(has("ie")){ - // IE generates <strong> and <em> but we want to normalize to <b> and <i> - this.contentPostFilters = [this._normalizeFontStyle].concat(this.contentPostFilters); - this.contentDomPostFilters = [lang.hitch(this, this._stripBreakerNodes)].concat(this.contentDomPostFilters); - } - this.inherited(arguments); - - topic.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 = lang.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(!has("ie")){ - ctrlKeyHandlers.Z = exec("redo"); //FIXME: undo? - } - - var key; - for(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(RichText._editorCommandsLocalized){ - // Use the already generate cache of mappings. - this._local2NativeFormatNames = RichText._local2NativeFormatNames; - this._native2LocalFormatNames = RichText._native2LocalFormatNames; - return; - } - RichText._editorCommandsLocalized = true; - RichText._local2NativeFormatNames = {}; - RichText._native2LocalFormatNames = {}; - this._local2NativeFormatNames = RichText._local2NativeFormatNames; - this._native2LocalFormatNames = RichText._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 = domConstruct.create('div', {style: style, innerHTML: localhtml}); - this.ownerDocumentBody.appendChild(div); - - // IE9 has a timing issue with doing this right after setting - // the inner HTML, so put a delay in. - var inject = lang.hitch(this, function(){ - var node = div.firstChild; - while(node){ - try{ - this._sCall("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 */ } - } - domConstruct.destroy(div); - }); - this.defer(inject); - }, - - 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 Deferred(); - } - - if(!this.isClosed){ this.close(); } - topic.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(lang.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 = this.ownerDocument.createElement("div"); - dn.setAttribute('widgetId', this.id); - ta.removeAttribute('widgetId'); - dn.cssText = ta.cssText; - dn.className += " " + ta.className; - domConstruct.place(dn, ta, "before"); - var tmpFunc = lang.hitch(this, function(){ - //some browsers refuse to submit display=none textarea, so - //move the textarea off screen instead - domStyle.set(ta, { - display: "block", - position: "absolute", - top: "-1000px" - }); - - if(has("ie")){ //nasty IE bug: abnormal formatting if overflow is not hidden - var s = ta.style; - this.__overflow = s.overflow; - s.overflow = "hidden"; - } - }); - if(has("ie")){ - this.defer(tmpFunc, 10); - }else{ - tmpFunc(); - } - - if(ta.form){ - var resetValue = ta.value; - this.reset = function(){ - var current = this.getValue(); - if(current !== resetValue){ - this.replaceValue(resetValue); - } - }; - on(ta.form, "submit", lang.hitch(this, function(){ - // Copy value to the <textarea> so it gets submitted along with form. - // FIXME: should we be calling close() here instead? - domAttr.set(ta, 'disabled', this.disabled); // don't submit the value if disabled - ta.value = this.getValue(); - })); - } - }else{ - html = htmlapi.getChildrenHtml(dn); - dn.innerHTML = ""; - } - - 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 !== "" && (!config["useXDomain"] || config["allowXdRichTextSave"])){ - var saveTextarea = dom.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(!RichText._globalSaveHandler){ - RichText._globalSaveHandler = {}; - unload.addOnUnload(function(){ - var id; - for(id in RichText._globalSaveHandler){ - var f = RichText._globalSaveHandler[id]; - if(lang.isFunction(f)){ - f(); - } - } - }); - } - RichText._globalSaveHandler[this.id] = lang.hitch(this, "_saveContent"); - } - - this.isClosed = false; - - var ifr = (this.editorObject = this.iframe = this.ownerDocument.createElement('iframe')); - ifr.id = this.id+"_iframe"; - 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(has("ie") >= 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 = lang.hitch( this, function(w){ - this.window = w; - this.document = this.window.document; - - if(has("ie")){ - this._localizeEditorCommands(); - } - - // Do final setup and set initial contents of editor - this.onLoad(html); - }); - - // Set the iframe's initial (blank) content. - var src = this._getIframeDocTxt(), - s = "javascript: '" + src.replace(/\\/g, "\\\\").replace(/'/g, "\\'") + "'"; - ifr.setAttribute('src', s); - this.editingArea.appendChild(ifr); - - if(has("safari") <= 4){ - src = ifr.getAttribute("src"); - if(!src || src.indexOf("javascript") === -1){ - // Safari 4 and earlier sometimes act oddly - // So we have to set it again. - this.defer(function(){ ifr.setAttribute('src', s); }); - } - } - - // TODO: this is a guess at the default line-height, kinda works - if(dn.nodeName === "LI"){ - dn.lastChild.style.marginTop = "-1.2em"; - } - - domClass.add(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 = domStyle.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(has("ie") || has("webkit") || (!this.height && !has("mozilla"))){ - // 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(has("mozilla")){ - // 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 = lang.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; - } - } - domStyle.set(self.domNode, sC, ""); - } - userStyle += match + ';'; - }); - - - // need to find any associated label element and update iframe document title - var label=query('label[for="'+this.id+'"]'); - - return [ - this.isLeftToRight() ? "<html>\n<head>\n" : "<html dir='rtl'>\n<head>\n", - (has("mozilla") && 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. - ((has("webkit"))?"\t\twidth: 100%;\n":""), - ((has("webkit"))?"\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||has("opera")) ? "" : "\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. - (!has("ie") ? "\tli{ min-height:1.2em; }\n" : ""), - "</style>\n", - this._applyEditingAreaStyleSheets(),"\n", - "</head>\n<body ", - (setBodyId?"id='dijitEditorBody' ":""), - - // Onload handler fills in real editor content. - // On IE9, sometimes onload is called twice, and the first time frameElement is null (test_FullScreen.html) - "onload='frameElement && 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 _Url(win.global.location, url)).toString(); - this.editingAreaStyleSheets.push(abstring); - text += '<link rel="stylesheet" type="text/css" href="'+abstring+'"/>'; - } - return text; - }, - - addStyleSheet: function(/*dojo/_base/url*/ uri){ - // summary: - // add an external stylesheet for the editing area - // uri: - // 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 _Url(win.global.location, url)).toString(); - } - - if(array.indexOf(this.editingAreaStyleSheets, url) > -1){ -// console.debug("dijit/_editor/RichText.addStyleSheet(): Style sheet "+url+" is already applied"); - return; - } - - this.editingAreaStyleSheets.push(url); - this.onLoadDeferred.then(lang.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/_base/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 _Url(win.global.location, url)).toString(); - } - var index = array.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]; - query('link:[href="'+url+'"]', this.window.document).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(has("ie") || has("webkit") || has("opera")){ - var preventIEfocus = has("ie") && (this.isLoaded || !this.focusOnLoad); - if(preventIEfocus){ this.editNode.unselectable = "on"; } - this.editNode.contentEditable = !value; - if(preventIEfocus){ - this.defer(function(){ - if(this.editNode){ // guard in case widget destroyed before timeout - this.editNode.unselectable = "off"; - } - }); - } - }else{ //moz - try{ - this.document.designMode=(value?'off':'on'); - }catch(e){ return; } // ! _disabledOK - if(!value && this._mozSettingProps){ - var ps = this._mozSettingProps; - var n; - for(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 = focus.registerIframe(this.iframe); - } - if(!has("ie") && !has("webkit") && (this.height || has("mozilla"))){ - 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(has("ie")){ // #4996 IE wants to focus the BODY tag - this.tabStop = domConstruct.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; - array.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(has("ie")){ // 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(has("webkit")){ - //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. - this.defer("placeCursorAtEnd"); - } - }); - } - - if(has("ie")){ - // 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 = lang.hitch(this, function(){ - this.setValue(html); - if(this.onLoadDeferred){ - this.onLoadDeferred.resolve(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 - ready(lang.hitch(this, "defer", "focus", this.updateInterval)); - } - // Save off the initial content now - this.value = this.getValue(true); - }); - if(this.setValueDeferred){ - this.setValueDeferred.then(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 === keys.TAB && this.isTabIndent ){ - event.stop(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(has("ie")){ - if(e.keyCode == 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 === 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 - event.stop(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); - } - } - if(has("ff")){ - if(e.keyCode === keys.PAGE_UP || e.keyCode === keys.PAGE_DOWN ){ - if(this.editNode.clientHeight >= this.editNode.scrollHeight){ - // Stop the event to prevent firefox from trapping the cursor when there is no scroll bar. - e.preventDefault(); - } - } - } - return true; - }, - - onKeyUp: function(/*===== e =====*/){ - // summary: - // Handler for onkeyup event - // tags: - // callback - }, - - setDisabled: function(/*Boolean*/ disabled){ - // summary: - // Deprecated, use set('disabled', ...) instead. - // tags: - // deprecated - kernel.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){ - domAttr.set(this.document.body, "spellcheck", !disabled); - }else{ - // try again after the editor is finished loading - this.onLoadDeferred.then(lang.hitch(this, function(){ - domAttr.set(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){ - array.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 = lang.hitch(this, "onKeyPressed"); - } - this.defer("_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(!lang.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(){ - // 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(!has("ie") && this.window.document.documentElement && this.window.document.documentElement.focus){ - this.window.document.documentElement.focus(); - }else if(this.ownerDocumentBody.focus){ - this.ownerDocumentBody.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(!has("ie")){ - focus.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 every time 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){ - this._updateTimer.remove(); - } - this._updateTimer = this.defer("onNormalizedDisplayChanged", 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 to map our - // normalized set of commands to those supported by the target - // browser. - // tags: - // private - - var command = cmd.toLowerCase(); - if(command === "formatblock"){ - if(has("safari") && argument === undefined){ command = "heading"; } - }else if(command === "hilitecolor" && !has("mozilla")){ - 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 | opera); - 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 (has("ie") && supportedBy.ie) || - (has("mozilla") && supportedBy.mozilla) || - (has("webkit") && supportedBy.webkit) || - (has("opera") && 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") && has("ie")){ - 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. - // command: - // The command to execute - // tags: - // protected - if(this.disabled || !this._disabledOK){ return false; } - - command = this._normalizeCommand(command); - - //Check to see if we have any over-rides for commands, they will be functions on this - //widget of the form _commandEnabledImpl. If we don't, fall through to the basic native - //command of the browser. - var implFunc = "_" + command + "EnabledImpl"; - - if(this[implFunc]){ - return this[implFunc](command); - }else{ - return this._browserQueryCommandEnabled(command); - } - }, - - 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(has("ie") && command === "formatblock"){ - r = this._native2LocalFormatNames[this.document.queryCommandValue(command)]; - }else if(has("mozilla") && 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 win.withGlobal(this.window, name, selectionapi, 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(has("mozilla")){ - // 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(has("mozilla")){ - 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; - this._sCall("selectElement", [ last.lastChild || 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.then(lang.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 && has("mozilla") && html.toLowerCase() === "<p></p>"){ - html = "<p> </p>"; // - } - - // Use to avoid webkit problems where editor is disabled until the user clicks it - if(!html && has("webkit")){ - 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 && !has("mozilla")){ // 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; - array.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; - array.forEach(this.contentDomPreFilters, function(ef){ - if(ef && lang.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(!lang.isString(dom)){ - dom = dom || this.editNode; - if(this.contentDomPostFilters.length){ - if(nonDestructive){ - dom = lang.clone(dom); - } - array.forEach(this.contentDomPostFilters, function(ef){ - dom = ef(dom); - }); - } - ec = htmlapi.getChildrenHtml(dom); - }else{ - ec = dom; - } - - if(!lang.trim(ec.replace(/^\xA0\xA0*/, '').replace(/\xA0\xA0*$/, '')).length){ - ec = ""; - } - - // if(has("ie")){ - // //removing appended <P> </P> for IE - // ec = ec.replace(/(?:<p> </p>[\n\r]*)+$/i,""); - // } - array.forEach(this.contentPostFilters, function(ef){ - ec = ef(ec); - }); - - return ec; - }, - - _saveContent: function(){ - // summary: - // Saves the content in an onunload event if the editor has not been closed - // tags: - // private - - var saveTextarea = dom.byId(dijit._scopeName + "._editor.RichText.value"); - if(saveTextarea){ - 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/html::_getNodeHtml() instead. - // tags: - // deprecated - kernel.deprecated('dijit.Editor::getNodeHtml is deprecated','use dijit/_editor/html::getNodeHtml instead', 2); - return htmlapi.getNodeHtml(node); // String - }, - - getNodeChildrenHtml: function(/* DomNode */ dom){ - // summary: - // Deprecated. Use dijit/_editor/html::getChildrenHtml() instead. - // tags: - // deprecated - kernel.deprecated('dijit.Editor::getNodeChildrenHtml is deprecated','use dijit/_editor/html::getChildrenHtml instead', 2); - return htmlapi.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(has("ie")){ - this.iframe.onfocus = null; - } - this.iframe._loadFunc = null; - - if(this._iframeRegHandle){ - this._iframeRegHandle.remove(); - delete this._iframeRegHandle; - } - - if(this.textarea){ - var s = this.textarea.style; - s.position = ""; - s.left = s.top = ""; - if(has("ie")){ - s.overflow = this.__overflow; - this.__overflow = null; - } - this.textarea.value = this.value; - domConstruct.destroy(this.domNode); - this.domNode = this.textarea; - }else{ - // Note that this destroys the iframe - this.domNode.innerHTML = this.value; - } - delete this.iframe; - - domClass.remove(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); } - if(this._updateTimer){ - this._updateTimer.remove(); - } - this.inherited(arguments); - if(RichText._globalSaveHandler){ - delete RichText._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. - ******************************************************************************/ - - /*** queryCommandEnabled implementations ***/ - - _browserQueryCommandEnabled: function(command){ - // summary: - // Implementation to call to the native queryCommandEnabled of the browser. - // command: - // The command to check. - // tags: - // protected - if(!command) { return false; } - var elem = has("ie") ? this.document.selection.createRange() : this.document; - try{ - return elem.queryCommandEnabled(command); - }catch(e){ - return false; - } - }, - - _createlinkEnabledImpl: function(/*===== argument =====*/){ - // summary: - // This function implements the test for if the create link - // command should be enabled or not. - // argument: - // arguments to the exec command, if any. - // tags: - // protected - var enabled = true; - if(has("opera")){ - var sel = this.window.getSelection(); - if(sel.isCollapsed){ - enabled = true; - }else{ - enabled = this.document.queryCommandEnabled("createlink"); - } - }else{ - enabled = this._browserQueryCommandEnabled("createlink"); - } - return enabled; - }, - - _unlinkEnabledImpl: function(/*===== argument =====*/){ - // summary: - // This function implements the test for if the unlink - // command should be enabled or not. - // argument: - // arguments to the exec command, if any. - // tags: - // protected - var enabled = true; - if(has("mozilla") || has("webkit")){ - enabled = this._sCall("hasAncestorElement", ["a"]); - }else{ - enabled = this._browserQueryCommandEnabled("unlink"); - } - return enabled; - }, - - _inserttableEnabledImpl: function(/*===== argument =====*/){ - // summary: - // This function implements the test for if the inserttable - // command should be enabled or not. - // argument: - // arguments to the exec command, if any. - // tags: - // protected - var enabled = true; - if(has("mozilla") || has("webkit")){ - enabled = true; - }else{ - enabled = this._browserQueryCommandEnabled("inserttable"); - } - return enabled; - }, - - _cutEnabledImpl: function(/*===== argument =====*/){ - // summary: - // This function implements the test for if the cut - // command should be enabled or not. - // argument: - // arguments to the exec command, if any. - // tags: - // protected - var enabled = true; - if(has("webkit")){ - // WebKit deems clipboard activity as a security threat and natively would return false - var sel = this.window.getSelection(); - if(sel){ sel = sel.toString(); } - enabled = !!sel; - }else{ - enabled = this._browserQueryCommandEnabled("cut"); - } - return enabled; - }, - - _copyEnabledImpl: function(/*===== argument =====*/){ - // summary: - // This function implements the test for if the copy - // command should be enabled or not. - // argument: - // arguments to the exec command, if any. - // tags: - // protected - var enabled = true; - if(has("webkit")){ - // WebKit deems clipboard activity as a security threat and natively would return false - var sel = this.window.getSelection(); - if(sel){ sel = sel.toString(); } - enabled = !!sel; - }else{ - enabled = this._browserQueryCommandEnabled("copy"); - } - return enabled; - }, - - _pasteEnabledImpl: function(/*===== argument =====*/){ - // summary:c - // This function implements the test for if the paste - // command should be enabled or not. - // argument: - // arguments to the exec command, if any. - // tags: - // protected - var enabled = true; - if(has("webkit")){ - return true; - }else{ - enabled = this._browserQueryCommandEnabled("paste"); - } - return enabled; - }, - - /*** execCommand implementations ***/ - - _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(has("ie")){ - 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")) && (has("mozilla") || has("webkit"))){ - 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; - var isApplied = this._handleTextColorOrProperties("hilitecolor", argument); - if(!isApplied){ - if(has("mozilla")){ - // mozilla doesn't support hilitecolor properly when useCSS is - // set to false (bugzilla #279330) - this.document.execCommand("styleWithCSS", false, true); - console.log("Executing color command."); - 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(has("ie")){ - // 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; - } - var isApplied = this._handleTextColorOrProperties("backcolor", argument); - if(!isApplied){ - isApplied = this.document.execCommand("backcolor", false, argument); - } - return isApplied; - }, - - _forecolorImpl: function(argument){ - // summary: - // This function implements the forecolor command - // argument: - // arguments to the exec command, if any. - // tags: - // protected - if(has("ie")){ - // 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; - } - var isApplied = false; - isApplied = this._handleTextColorOrProperties("forecolor", argument); - if(!isApplied){ - isApplied = this.document.execCommand("forecolor", false, argument); - } - return isApplied; - }, - - _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(has("ie")){ - 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(has("mozilla") && !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 - var applied = false; - if(has("ie")){ - this._adaptIESelection(); - applied = this._adaptIEFormatAreaAndExec("bold"); - } - if(!applied){ - applied = this.document.execCommand("bold", false, argument); - } - return applied; - }, - - _italicImpl: function(argument){ - // summary: - // This function implements an over-ride of the italic command. - // argument: - // Not used, operates by selection. - // tags: - // protected - var applied = false; - if(has("ie")){ - this._adaptIESelection(); - applied = this._adaptIEFormatAreaAndExec("italic"); - } - if(!applied){ - applied = this.document.execCommand("italic", false, argument); - } - return applied; - }, - - _underlineImpl: function(argument){ - // summary: - // This function implements an over-ride of the underline command. - // argument: - // Not used, operates by selection. - // tags: - // protected - var applied = false; - if(has("ie")){ - this._adaptIESelection(); - applied = this._adaptIEFormatAreaAndExec("underline"); - } - if(!applied){ - applied = this.document.execCommand("underline", false, argument); - } - return applied; - }, - - _strikethroughImpl: function(argument){ - // summary: - // This function implements an over-ride of the strikethrough command. - // argument: - // Not used, operates by selection. - // tags: - // protected - var applied = false; - if(has("ie")){ - this._adaptIESelection(); - applied = this._adaptIEFormatAreaAndExec("strikethrough"); - } - if(!applied){ - applied = this.document.execCommand("strikethrough", false, argument); - } - return applied; - }, - - _superscriptImpl: function(argument){ - // summary: - // This function implements an over-ride of the superscript command. - // argument: - // Not used, operates by selection. - // tags: - // protected - var applied = false; - if(has("ie")){ - this._adaptIESelection(); - applied = this._adaptIEFormatAreaAndExec("superscript"); - } - if(!applied){ - applied = this.document.execCommand("superscript", false, argument); - } - return applied; - }, - - _subscriptImpl: function(argument){ - // summary: - // This function implements an over-ride of the superscript command. - // argument: - // Not used, operates by selection. - // tags: - // protected - var applied = false; - if(has("ie")){ - this._adaptIESelection(); - applied = this._adaptIEFormatAreaAndExec("subscript"); - - } - if(!applied){ - applied = this.document.execCommand("subscript", false, argument); - } - return applied; - }, - - _fontnameImpl: function(argument){ - // summary: - // This function implements the fontname command - // argument: - // arguments to the exec command, if any. - // tags: - // protected - var isApplied; - if(has("ie")){ - isApplied = this._handleTextColorOrProperties("fontname", argument); - } - if(!isApplied){ - isApplied = this.document.execCommand("fontname", false, argument); - } - return isApplied; - }, - - _fontsizeImpl: function(argument){ - // summary: - // This function implements the fontsize command - // argument: - // arguments to the exec command, if any. - // tags: - // protected - var isApplied; - if(has("ie")){ - isApplied = this._handleTextColorOrProperties("fontsize", argument); - } - if(!isApplied){ - isApplied = this.document.execCommand("fontsize", false, argument); - } - return isApplied; - }, - - _insertorderedlistImpl: function(argument){ - // summary: - // This function implements the insertorderedlist command - // argument: - // arguments to the exec command, if any. - // tags: - // protected - var applied = false; - if(has("ie")){ - applied = this._adaptIEList("insertorderedlist", argument); - } - if(!applied){ - applied = this.document.execCommand("insertorderedlist", false, argument); - } - return applied; - }, - - _insertunorderedlistImpl: function(argument){ - // summary: - // This function implements the insertunorderedlist command - // argument: - // arguments to the exec command, if any. - // tags: - // protected - var applied = false; - if(has("ie")){ - applied = this._adaptIEList("insertunorderedlist", argument); - } - if(!applied){ - applied = this.document.execCommand("insertunorderedlist", false, argument); - } - return applied; - }, - - 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 = domGeometry.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 = rangeapi.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); - } - }, - - _adaptIEFormatAreaAndExec: function(command){ - // summary: - // Function to handle IE's quirkiness regarding how it handles - // format commands on a word. This involves a lit of node splitting - // and format cloning. - // command: - // The format command, needed to check if the desired - // command is true or not. - var selection = rangeapi.getSelection(this.window); - var doc = this.document; - var rs, ret, range, txt, startNode, endNode, breaker, sNode; - if(command && selection && selection.isCollapsed){ - var isApplied = this.queryCommandValue(command); - if(isApplied){ - - // We have to split backwards until we hit the format - var nNames = this._tagNamesForCommand(command); - range = selection.getRangeAt(0); - var fs = range.startContainer; - if(fs.nodeType === 3){ - var offset = range.endOffset; - if(fs.length < offset){ - //We are not looking from the right node, try to locate the correct one - ret = this._adjustNodeAndOffset(rs, offset); - fs = ret.node; - offset = ret.offset; - } - } - var topNode; - while(fs && fs !== this.editNode){ - // We have to walk back and see if this is still a format or not. - // Hm, how do I do this? - var tName = fs.tagName? fs.tagName.toLowerCase() : ""; - if(array.indexOf(nNames, tName) > -1){ - topNode = fs; - break; - } - fs = fs.parentNode; - } - - // Okay, we have a stopping place, time to split things apart. - if(topNode){ - // Okay, we know how far we have to split backwards, so we have to split now. - rs = range.startContainer; - var newblock = doc.createElement(topNode.tagName); - domConstruct.place(newblock, topNode, "after"); - if(rs && rs.nodeType === 3){ - // Text node, we have to split it. - var nodeToMove, tNode; - var endOffset = range.endOffset; - if(rs.length < endOffset){ - //We are not splitting the right node, try to locate the correct one - ret = this._adjustNodeAndOffset(rs, endOffset); - rs = ret.node; - endOffset = ret.offset; - } - - txt = rs.nodeValue; - startNode = doc.createTextNode(txt.substring(0, endOffset)); - var endText = txt.substring(endOffset, txt.length); - if(endText){ - endNode = doc.createTextNode(endText); - } - // Place the split, then remove original nodes. - domConstruct.place(startNode, rs, "before"); - if(endNode){ - breaker = doc.createElement("span"); - breaker.className = "ieFormatBreakerSpan"; - domConstruct.place(breaker, rs, "after"); - domConstruct.place(endNode, breaker, "after"); - endNode = breaker; - } - domConstruct.destroy(rs); - - // Okay, we split the text. Now we need to see if we're - // parented to the block element we're splitting and if - // not, we have to split all the way up. Ugh. - var parentC = startNode.parentNode; - var tagList = []; - var tagData; - while(parentC !== topNode){ - var tg = parentC.tagName; - tagData = {tagName: tg}; - tagList.push(tagData); - - var newTg = doc.createElement(tg); - // Clone over any 'style' data. - if(parentC.style){ - if(newTg.style){ - if(parentC.style.cssText){ - newTg.style.cssText = parentC.style.cssText; - tagData.cssText = parentC.style.cssText; - } - } - } - // If font also need to clone over any font data. - if(parentC.tagName === "FONT"){ - if(parentC.color){ - newTg.color = parentC.color; - tagData.color = parentC.color; - } - if(parentC.face){ - newTg.face = parentC.face; - tagData.face = parentC.face; - } - if(parentC.size){ // this check was necessary on IE - newTg.size = parentC.size; - tagData.size = parentC.size; - } - } - if(parentC.className){ - newTg.className = parentC.className; - tagData.className = parentC.className; - } - - // Now move end node and every sibling - // after it over into the new tag. - if(endNode){ - nodeToMove = endNode; - while(nodeToMove){ - tNode = nodeToMove.nextSibling; - newTg.appendChild(nodeToMove); - nodeToMove = tNode; - } - } - if(newTg.tagName == parentC.tagName){ - breaker = doc.createElement("span"); - breaker.className = "ieFormatBreakerSpan"; - domConstruct.place(breaker, parentC, "after"); - domConstruct.place(newTg, breaker, "after"); - }else{ - domConstruct.place(newTg, parentC, "after"); - } - startNode = parentC; - endNode = newTg; - parentC = parentC.parentNode; - } - - // Lastly, move the split out all the split tags - // to the new block as they should now be split properly. - if(endNode){ - nodeToMove = endNode; - if(nodeToMove.nodeType === 1 || (nodeToMove.nodeType === 3 && nodeToMove.nodeValue)){ - // Non-blank text and non-text nodes need to clear out that blank space - // before moving the contents. - newblock.innerHTML = ""; - } - while(nodeToMove){ - tNode = nodeToMove.nextSibling; - newblock.appendChild(nodeToMove); - nodeToMove = tNode; - } - } - - // We had intermediate tags, we have to now recreate them inbetween the split - // and restore what styles, classnames, etc, we can. - var newrange; - if(tagList.length){ - tagData = tagList.pop(); - var newContTag = doc.createElement(tagData.tagName); - if(tagData.cssText && newContTag.style){ - newContTag.style.cssText = tagData.cssText; - } - if(tagData.className){ - newContTag.className = tagData.className; - } - if(tagData.tagName === "FONT"){ - if(tagData.color){ - newContTag.color = tagData.color; - } - if(tagData.face){ - newContTag.face = tagData.face; - } - if(tagData.size){ - newContTag.size = tagData.size; - } - } - domConstruct.place(newContTag, newblock, "before"); - while(tagList.length){ - tagData = tagList.pop(); - var newTgNode = doc.createElement(tagData.tagName); - if(tagData.cssText && newTgNode.style){ - newTgNode.style.cssText = tagData.cssText; - } - if(tagData.className){ - newTgNode.className = tagData.className; - } - if(tagData.tagName === "FONT"){ - if(tagData.color){ - newTgNode.color = tagData.color; - } - if(tagData.face){ - newTgNode.face = tagData.face; - } - if(tagData.size){ - newTgNode.size = tagData.size; - } - } - newContTag.appendChild(newTgNode); - newContTag = newTgNode; - } - - // Okay, everything is theoretically split apart and removed from the content - // so insert the dummy text to select, select it, then - // clear to position cursor. - sNode = doc.createTextNode("."); - breaker.appendChild(sNode); - newContTag.appendChild(sNode); - newrange = rangeapi.create(this.window); - newrange.setStart(sNode, 0); - newrange.setEnd(sNode, sNode.length); - selection.removeAllRanges(); - selection.addRange(newrange); - this._sCall("collapse", [false]); - sNode.parentNode.innerHTML = ""; - }else{ - // No extra tags, so we have to insert a breaker point and rely - // on filters to remove it later. - breaker = doc.createElement("span"); - breaker.className="ieFormatBreakerSpan"; - sNode = doc.createTextNode("."); - breaker.appendChild(sNode); - domConstruct.place(breaker, newblock, "before"); - newrange = rangeapi.create(this.window); - newrange.setStart(sNode, 0); - newrange.setEnd(sNode, sNode.length); - selection.removeAllRanges(); - selection.addRange(newrange); - this._sCall("collapse", [false]); - sNode.parentNode.innerHTML = ""; - } - if(!newblock.firstChild){ - // Empty, we don't need it. Split was at end or similar - // So, remove it. - domConstruct.destroy(newblock); - } - return true; - } - } - return false; - }else{ - range = selection.getRangeAt(0); - rs = range.startContainer; - if(rs && rs.nodeType === 3){ - // Text node, we have to split it. - var offset = range.startOffset; - if(rs.length < offset){ - //We are not splitting the right node, try to locate the correct one - ret = this._adjustNodeAndOffset(rs, offset); - rs = ret.node; - offset = ret.offset; - } - txt = rs.nodeValue; - startNode = doc.createTextNode(txt.substring(0, offset)); - var endText = txt.substring(offset); - if(endText !== ""){ - endNode = doc.createTextNode(txt.substring(offset)); - } - // Create a space, we'll select and bold it, so - // the whole word doesn't get bolded - breaker = doc.createElement("span"); - sNode = doc.createTextNode("."); - breaker.appendChild(sNode); - if(startNode.length){ - domConstruct.place(startNode, rs, "after"); - }else{ - startNode = rs; - } - domConstruct.place(breaker, startNode, "after"); - if(endNode){ - domConstruct.place(endNode, breaker, "after"); - } - domConstruct.destroy(rs); - var newrange = rangeapi.create(this.window); - newrange.setStart(sNode, 0); - newrange.setEnd(sNode, sNode.length); - selection.removeAllRanges(); - selection.addRange(newrange); - doc.execCommand(command); - domConstruct.place(breaker.firstChild, breaker, "before"); - domConstruct.destroy(breaker); - newrange.setStart(sNode, 0); - newrange.setEnd(sNode, sNode.length); - selection.removeAllRanges(); - selection.addRange(newrange); - this._sCall("collapse", [false]); - sNode.parentNode.innerHTML = ""; - return true; - } - } - }else{ - return false; - } - }, - - _adaptIEList: function(command /*===== , argument =====*/){ - // summary: - // This function handles normalizing the IE list behavior as - // much as possible. - // command: - // The list command to execute. - // argument: - // Any additional argument. - // tags: - // private - var selection = rangeapi.getSelection(this.window); - if(selection.isCollapsed){ - // In the case of no selection, lets commonize the behavior and - // make sure that it indents if needed. - if(selection.rangeCount && !this.queryCommandValue(command)){ - var range = selection.getRangeAt(0); - var sc = range.startContainer; - if(sc && sc.nodeType == 3){ - // text node. Lets see if there is a node before it that isn't - // some sort of breaker. - if(!range.startOffset){ - // We're at the beginning of a text area. It may have been br split - // Who knows? In any event, we must create the list manually - // or IE may shove too much into the list element. It seems to - // grab content before the text node too if it's br split. - // Why can't IE work like everyone else? - - // Create a space, we'll select and bold it, so - // the whole word doesn't get bolded - var lType = "ul"; - if(command === "insertorderedlist"){ - lType = "ol"; - } - var list = this.document.createElement(lType); - var li = domConstruct.create("li", null, list); - domConstruct.place(list, sc, "before"); - // Move in the text node as part of the li. - li.appendChild(sc); - // We need a br after it or the enter key handler - // sometimes throws errors. - domConstruct.create("br", null, list, "after"); - // Okay, now lets move our cursor to the beginning. - var newrange = rangeapi.create(this.window); - newrange.setStart(sc, 0); - newrange.setEnd(sc, sc.length); - selection.removeAllRanges(); - selection.addRange(newrange); - this._sCall("collapse", [true]); - return true; - } - } - } - } - return false; - }, - - _handleTextColorOrProperties: function(command, argument){ - // summary: - // This function handles appplying text color as best it is - // able to do so when the selection is collapsed, making the - // behavior cross-browser consistent. It also handles the name - // and size for IE. - // command: - // The command. - // argument: - // Any additional arguments. - // tags: - // private - var selection = rangeapi.getSelection(this.window); - var doc = this.document; - var rs, ret, range, txt, startNode, endNode, breaker, sNode; - argument = argument || null; - if(command && selection && selection.isCollapsed){ - if(selection.rangeCount){ - range = selection.getRangeAt(0); - rs = range.startContainer; - if(rs && rs.nodeType === 3){ - // Text node, we have to split it. - var offset = range.startOffset; - if(rs.length < offset){ - //We are not splitting the right node, try to locate the correct one - ret = this._adjustNodeAndOffset(rs, offset); - rs = ret.node; - offset = ret.offset; - } - txt = rs.nodeValue; - startNode = doc.createTextNode(txt.substring(0, offset)); - var endText = txt.substring(offset); - if(endText !== ""){ - endNode = doc.createTextNode(txt.substring(offset)); - } - // Create a space, we'll select and bold it, so - // the whole word doesn't get bolded - breaker = doc.createElement("span"); - sNode = doc.createTextNode("."); - breaker.appendChild(sNode); - // Create a junk node to avoid it trying to style the breaker. - // This will get destroyed later. - var extraSpan = doc.createElement("span"); - breaker.appendChild(extraSpan); - if(startNode.length){ - domConstruct.place(startNode, rs, "after"); - }else{ - startNode = rs; - } - domConstruct.place(breaker, startNode, "after"); - if(endNode){ - domConstruct.place(endNode, breaker, "after"); - } - domConstruct.destroy(rs); - var newrange = rangeapi.create(this.window); - newrange.setStart(sNode, 0); - newrange.setEnd(sNode, sNode.length); - selection.removeAllRanges(); - selection.addRange(newrange); - if(has("webkit")){ - // WebKit is frustrating with positioning the cursor. - // It stinks to have a selected space, but there really - // isn't much choice here. - var style = "color"; - if(command === "hilitecolor" || command === "backcolor"){ - style = "backgroundColor"; - } - domStyle.set(breaker, style, argument); - this._sCall("remove", []); - domConstruct.destroy(extraSpan); - breaker.innerHTML = " "; // - this._sCall("selectElement", [breaker]); - this.focus(); - }else{ - this.execCommand(command, argument); - domConstruct.place(breaker.firstChild, breaker, "before"); - domConstruct.destroy(breaker); - newrange.setStart(sNode, 0); - newrange.setEnd(sNode, sNode.length); - selection.removeAllRanges(); - selection.addRange(newrange); - this._sCall("collapse", [false]); - sNode.parentNode.removeChild(sNode); - } - return true; - } - } - } - return false; - }, - - _adjustNodeAndOffset: function(/*DomNode*/node, /*Int*/offset){ - // summary: - // In the case there are multiple text nodes in a row the offset may not be within the node. - // If the offset is larger than the node length, it will attempt to find - // the next text sibling until it locates the text node in which the offset refers to - // node: - // The node to check. - // offset: - // The position to find within the text node - // tags: - // private. - while(node.length < offset && node.nextSibling && node.nextSibling.nodeType === 3){ - //Adjust the offset and node in the case of multiple text nodes in a row - offset = offset - node.length; - node = node.nextSibling; - } - return {"node": node, "offset": offset}; - }, - - _tagNamesForCommand: function(command){ - // summary: - // Function to return the tab names that are associated - // with a particular style. - // command: String - // The command to return tags for. - // tags: - // private - if(command === "bold"){ - return ["b", "strong"]; - }else if(command === "italic"){ - return ["i","em"]; - }else if(command === "strikethrough"){ - return ["s", "strike"]; - }else if(command === "superscript"){ - return ["sup"]; - }else if(command === "subscript"){ - return ["sub"]; - }else if(command === "underline"){ - return ["u"]; - } - return []; - }, - - _stripBreakerNodes: function(/*DOMNode*/ node){ - // summary: - // Function for stripping out the breaker spans inserted by the formatting command. - // Registered as a filter for IE, handles the breaker spans needed to fix up - // How bold/italic/etc, work when selection is collapsed (single cursor). - if(!this.isLoaded){ return; } // this method requires init to be complete - query(".ieFormatBreakerSpan", node).forEach(function(b){ - while(b.firstChild){ - domConstruct.place(b.firstChild, b, "before"); - } - domConstruct.destroy(b); - }); - return node; - } -}); - -return RichText; - -}); |