diff options
Diffstat (limited to 'js/dojo-1.7.2/dijit/_editor/plugins')
| -rw-r--r-- | js/dojo-1.7.2/dijit/_editor/plugins/AlwaysShowToolbar.js | 200 | ||||
| -rw-r--r-- | js/dojo-1.7.2/dijit/_editor/plugins/EnterKeyHandling.js | 626 | ||||
| -rw-r--r-- | js/dojo-1.7.2/dijit/_editor/plugins/FontChoice.js | 592 | ||||
| -rw-r--r-- | js/dojo-1.7.2/dijit/_editor/plugins/FullScreen.js | 458 | ||||
| -rw-r--r-- | js/dojo-1.7.2/dijit/_editor/plugins/LinkDialog.js | 587 | ||||
| -rw-r--r-- | js/dojo-1.7.2/dijit/_editor/plugins/NewPage.js | 84 | ||||
| -rw-r--r-- | js/dojo-1.7.2/dijit/_editor/plugins/Print.js | 130 | ||||
| -rw-r--r-- | js/dojo-1.7.2/dijit/_editor/plugins/TabIndent.js | 70 | ||||
| -rw-r--r-- | js/dojo-1.7.2/dijit/_editor/plugins/TextColor.js | 120 | ||||
| -rw-r--r-- | js/dojo-1.7.2/dijit/_editor/plugins/ToggleDir.js | 78 | ||||
| -rw-r--r-- | js/dojo-1.7.2/dijit/_editor/plugins/ViewSource.js | 565 |
11 files changed, 3510 insertions, 0 deletions
diff --git a/js/dojo-1.7.2/dijit/_editor/plugins/AlwaysShowToolbar.js b/js/dojo-1.7.2/dijit/_editor/plugins/AlwaysShowToolbar.js new file mode 100644 index 0000000..1f66aef --- /dev/null +++ b/js/dojo-1.7.2/dijit/_editor/plugins/AlwaysShowToolbar.js @@ -0,0 +1,200 @@ +//>>built +define("dijit/_editor/plugins/AlwaysShowToolbar", [ + "dojo/_base/declare", // declare + "dojo/dom-class", // domClass.add domClass.remove + "dojo/dom-construct", // domConstruct.place + "dojo/dom-geometry", + "dojo/_base/lang", // lang.hitch + "dojo/_base/sniff", // has("ie") has("opera") + "dojo/_base/window", // win.body + "../_Plugin" +], function(declare, domClass, domConstruct, domGeometry, lang, has, win, _Plugin){ + +/*===== + var _Plugin = dijit._editor._Plugin; +=====*/ + +// module: +// dijit/_editor/plugins/AlwaysShowToolbar +// summary: +// This plugin is required for Editors in auto-expand mode. +// It handles the auto-expansion as the user adds/deletes text, +// and keeps the editor's toolbar visible even when the top of the editor +// has scrolled off the top of the viewport (usually when editing a long +// document). + + +return declare("dijit._editor.plugins.AlwaysShowToolbar", _Plugin, { + // summary: + // This plugin is required for Editors in auto-expand mode. + // It handles the auto-expansion as the user adds/deletes text, + // and keeps the editor's toolbar visible even when the top of the editor + // has scrolled off the top of the viewport (usually when editing a long + // document). + // description: + // Specify this in extraPlugins (or plugins) parameter and also set + // height to "". + // example: + // | <div data-dojo-type="dijit.Editor" height="" + // | data-dojo-props="extraPlugins: [dijit._editor.plugins.AlwaysShowToolbar]"> + + // _handleScroll: Boolean + // Enables/disables the handler for scroll events + _handleScroll: true, + + setEditor: function(e){ + // Overrides _Plugin.setEditor(). + if(!e.iframe){ + console.log('Port AlwaysShowToolbar plugin to work with Editor without iframe'); + return; + } + + this.editor = e; + + e.onLoadDeferred.addCallback(lang.hitch(this, this.enable)); + }, + + enable: function(d){ + // summary: + // Enable plugin. Called when Editor has finished initializing. + // tags: + // private + + this._updateHeight(); + this.connect(window, 'onscroll', "globalOnScrollHandler"); + this.connect(this.editor, 'onNormalizedDisplayChanged', "_updateHeight"); + return d; + }, + + _updateHeight: function(){ + // summary: + // Updates the height of the editor area to fit the contents. + var e = this.editor; + if(!e.isLoaded){ return; } + if(e.height){ return; } + + var height = domGeometry.getMarginSize(e.editNode).h; + if(has("opera")){ + height = e.editNode.scrollHeight; + } + // console.debug('height',height); + // alert(this.editNode); + + //height maybe zero in some cases even though the content is not empty, + //we try the height of body instead + if(!height){ + height = domGeometry.getMarginSize(e.document.body).h; + } + + if(height == 0){ + console.debug("Can not figure out the height of the editing area!"); + return; //prevent setting height to 0 + } + if(has("ie") <= 7 && this.editor.minHeight){ + var min = parseInt(this.editor.minHeight); + if(height < min){ height = min; } + } + if(height != this._lastHeight){ + this._lastHeight = height; + // this.editorObject.style.height = this._lastHeight + "px"; + domGeometry.setMarginBox(e.iframe, { h: this._lastHeight }); + } + }, + + // _lastHeight: Integer + // Height in px of the editor at the last time we did sizing + _lastHeight: 0, + + globalOnScrollHandler: function(){ + // summary: + // Handler for scroll events that bubbled up to <html> + // tags: + // private + + var isIE6 = has("ie") < 7; + if(!this._handleScroll){ return; } + var tdn = this.editor.header; + if(!this._scrollSetUp){ + this._scrollSetUp = true; + this._scrollThreshold = domGeometry.position(tdn, true).y; +// var db = win.body; +// console.log("threshold:", this._scrollThreshold); + //what's this for?? comment out for now +// if((isIE6)&&(db)&&(domStyle.set or get TODO(db, "backgroundIimage")=="none")){ +// db.style.backgroundImage = "url(" + dojo.uri.moduleUri("dijit", "templates/blank.gif") + ")"; +// db.style.backgroundAttachment = "fixed"; +// } + } + + var scrollPos = domGeometry.docScroll().y; + var s = tdn.style; + + if(scrollPos > this._scrollThreshold && scrollPos < this._scrollThreshold+this._lastHeight){ + // dojo.debug(scrollPos); + if(!this._fixEnabled){ + var tdnbox = domGeometry.getMarginSize(tdn); + this.editor.iframe.style.marginTop = tdnbox.h+"px"; + + if(isIE6){ + s.left = domGeometry.position(tdn).x; + if(tdn.previousSibling){ + this._IEOriginalPos = ['after',tdn.previousSibling]; + }else if(tdn.nextSibling){ + this._IEOriginalPos = ['before',tdn.nextSibling]; + }else{ + this._IEOriginalPos = ['last',tdn.parentNode]; + } + win.body().appendChild(tdn); + domClass.add(tdn,'dijitIEFixedToolbar'); + }else{ + s.position = "fixed"; + s.top = "0px"; + } + + domGeometry.setMarginBox(tdn, { w: tdnbox.w }); + s.zIndex = 2000; + this._fixEnabled = true; + } + // if we're showing the floating toolbar, make sure that if + // we've scrolled past the bottom of the editor that we hide + // the toolbar for this instance of the editor. + + // TODO: when we get multiple editor toolbar support working + // correctly, ensure that we check this against the scroll + // position of the bottom-most editor instance. + var eHeight = (this.height) ? parseInt(this.editor.height) : this.editor._lastHeight; + s.display = (scrollPos > this._scrollThreshold+eHeight) ? "none" : ""; + }else if(this._fixEnabled){ + this.editor.iframe.style.marginTop = ''; + s.position = ""; + s.top = ""; + s.zIndex = ""; + s.display = ""; + if(isIE6){ + s.left = ""; + domClass.remove(tdn,'dijitIEFixedToolbar'); + if(this._IEOriginalPos){ + domConstruct.place(tdn, this._IEOriginalPos[1], this._IEOriginalPos[0]); + this._IEOriginalPos = null; + }else{ + domConstruct.place(tdn, this.editor.iframe, 'before'); + } + } + s.width = ""; + this._fixEnabled = false; + } + }, + + destroy: function(){ + // Overrides _Plugin.destroy(). TODO: call this.inherited() rather than repeating code. + this._IEOriginalPos = null; + this._handleScroll = false; + this.inherited(arguments); + + if(has("ie") < 7){ + domClass.remove(this.editor.header, 'dijitIEFixedToolbar'); + } + } +}); + +}); diff --git a/js/dojo-1.7.2/dijit/_editor/plugins/EnterKeyHandling.js b/js/dojo-1.7.2/dijit/_editor/plugins/EnterKeyHandling.js new file mode 100644 index 0000000..a7f36c8 --- /dev/null +++ b/js/dojo-1.7.2/dijit/_editor/plugins/EnterKeyHandling.js @@ -0,0 +1,626 @@ +//>>built +define("dijit/_editor/plugins/EnterKeyHandling", [ + "dojo/_base/declare", // declare + "dojo/dom-construct", // domConstruct.destroy domConstruct.place + "dojo/_base/event", // event.stop + "dojo/keys", // keys.ENTER + "dojo/_base/lang", + "dojo/_base/sniff", // has("ie") has("mozilla") has("webkit") + "dojo/_base/window", // win.global win.withGlobal + "dojo/window", // winUtils.scrollIntoView + "../_Plugin", + "../RichText", + "../range", + "../selection" +], function(declare, domConstruct, event, keys, lang, has, win, winUtils, _Plugin, RichText, rangeapi, selectionapi){ + +/*===== + var _Plugin = dijit._editor._Plugin; +=====*/ + +// module: +// dijit/_editor/plugins/EnterKeyHandling +// summary: +// This plugin tries to make all browsers behave consistently with regard to +// how ENTER behaves in the editor window. It traps the ENTER key and alters +// the way DOM is constructed in certain cases to try to commonize the generated +// DOM and behaviors across browsers. + + +return declare("dijit._editor.plugins.EnterKeyHandling", _Plugin, { + // summary: + // This plugin tries to make all browsers behave consistently with regard to + // how ENTER behaves in the editor window. It traps the ENTER key and alters + // the way DOM is constructed in certain cases to try to commonize the generated + // DOM and behaviors across browsers. + // + // description: + // This plugin has three modes: + // + // * blockNodeForEnter=BR + // * blockNodeForEnter=DIV + // * blockNodeForEnter=P + // + // In blockNodeForEnter=P, the ENTER key starts a new + // paragraph, and shift-ENTER starts a new line in the current paragraph. + // For example, the input: + // + // | first paragraph <shift-ENTER> + // | second line of first paragraph <ENTER> + // | second paragraph + // + // will generate: + // + // | <p> + // | first paragraph + // | <br/> + // | second line of first paragraph + // | </p> + // | <p> + // | second paragraph + // | </p> + // + // In BR and DIV mode, the ENTER key conceptually goes to a new line in the + // current paragraph, and users conceptually create a new paragraph by pressing ENTER twice. + // For example, if the user enters text into an editor like this: + // + // | one <ENTER> + // | two <ENTER> + // | three <ENTER> + // | <ENTER> + // | four <ENTER> + // | five <ENTER> + // | six <ENTER> + // + // It will appear on the screen as two 'paragraphs' of three lines each. Markupwise, this generates: + // + // BR: + // | one<br/> + // | two<br/> + // | three<br/> + // | <br/> + // | four<br/> + // | five<br/> + // | six<br/> + // + // DIV: + // | <div>one</div> + // | <div>two</div> + // | <div>three</div> + // | <div> </div> + // | <div>four</div> + // | <div>five</div> + // | <div>six</div> + + // blockNodeForEnter: String + // This property decides the behavior of Enter key. It can be either P, + // DIV, BR, or empty (which means disable this feature). Anything else + // will trigger errors. The default is 'BR' + // + // See class description for more details. + blockNodeForEnter: 'BR', + + constructor: function(args){ + if(args){ + if("blockNodeForEnter" in args){ + args.blockNodeForEnter = args.blockNodeForEnter.toUpperCase(); + } + lang.mixin(this,args); + } + }, + + setEditor: function(editor){ + // Overrides _Plugin.setEditor(). + if(this.editor === editor){ return; } + this.editor = editor; + if(this.blockNodeForEnter == 'BR'){ + // While Moz has a mode tht mostly works, it's still a little different, + // So, try to just have a common mode and be consistent. Which means + // we need to enable customUndo, if not already enabled. + this.editor.customUndo = true; + editor.onLoadDeferred.addCallback(lang.hitch(this,function(d){ + this.connect(editor.document, "onkeypress", function(e){ + if(e.charOrCode == keys.ENTER){ + // Just do it manually. The handleEnterKey has a shift mode that + // Always acts like <br>, so just use it. + var ne = lang.mixin({},e); + ne.shiftKey = true; + if(!this.handleEnterKey(ne)){ + event.stop(e); + } + } + }); + return d; + })); + }else if(this.blockNodeForEnter){ + // add enter key handler + // FIXME: need to port to the new event code!! + var h = lang.hitch(this,this.handleEnterKey); + editor.addKeyHandler(13, 0, 0, h); //enter + editor.addKeyHandler(13, 0, 1, h); //shift+enter + this.connect(this.editor,'onKeyPressed','onKeyPressed'); + } + }, + onKeyPressed: function(){ + // summary: + // Handler for keypress events. + // tags: + // private + if(this._checkListLater){ + if(win.withGlobal(this.editor.window, 'isCollapsed', dijit)){ + var liparent=win.withGlobal(this.editor.window, 'getAncestorElement', selection, ['LI']); + if(!liparent){ + // circulate the undo detection code by calling RichText::execCommand directly + RichText.prototype.execCommand.call(this.editor, 'formatblock',this.blockNodeForEnter); + // set the innerHTML of the new block node + var block = win.withGlobal(this.editor.window, 'getAncestorElement', selection, [this.blockNodeForEnter]); + if(block){ + block.innerHTML=this.bogusHtmlContent; + if(has("ie")){ + // move to the start by moving backwards one char + var r = this.editor.document.selection.createRange(); + r.move('character',-1); + r.select(); + } + }else{ + console.error('onKeyPressed: Cannot find the new block node'); // FIXME + } + }else{ + if(has("mozilla")){ + if(liparent.parentNode.parentNode.nodeName == 'LI'){ + liparent=liparent.parentNode.parentNode; + } + } + var fc=liparent.firstChild; + if(fc && fc.nodeType == 1 && (fc.nodeName == 'UL' || fc.nodeName == 'OL')){ + liparent.insertBefore(fc.ownerDocument.createTextNode('\xA0'),fc); + var newrange = rangeapi.create(this.editor.window); + newrange.setStart(liparent.firstChild,0); + var selection = rangeapi.getSelection(this.editor.window, true); + selection.removeAllRanges(); + selection.addRange(newrange); + } + } + } + this._checkListLater = false; + } + if(this._pressedEnterInBlock){ + // the new created is the original current P, so we have previousSibling below + if(this._pressedEnterInBlock.previousSibling){ + this.removeTrailingBr(this._pressedEnterInBlock.previousSibling); + } + delete this._pressedEnterInBlock; + } + }, + + // bogusHtmlContent: [private] String + // HTML to stick into a new empty block + bogusHtmlContent: ' ', // + + // blockNodes: [private] Regex + // Regex for testing if a given tag is a block level (display:block) tag + blockNodes: /^(?:P|H1|H2|H3|H4|H5|H6|LI)$/, + + handleEnterKey: function(e){ + // summary: + // Handler for enter key events when blockNodeForEnter is DIV or P. + // description: + // Manually handle enter key event to make the behavior consistent across + // all supported browsers. See class description for details. + // tags: + // private + + var selection, range, newrange, startNode, endNode, brNode, doc=this.editor.document,br,rs,txt; + if(e.shiftKey){ // shift+enter always generates <br> + var parent = win.withGlobal(this.editor.window, "getParentElement", selectionapi); + var header = rangeapi.getAncestor(parent,this.blockNodes); + if(header){ + if(header.tagName == 'LI'){ + return true; // let browser handle + } + selection = rangeapi.getSelection(this.editor.window); + range = selection.getRangeAt(0); + if(!range.collapsed){ + range.deleteContents(); + selection = rangeapi.getSelection(this.editor.window); + range = selection.getRangeAt(0); + } + if(rangeapi.atBeginningOfContainer(header, range.startContainer, range.startOffset)){ + br=doc.createElement('br'); + newrange = rangeapi.create(this.editor.window); + header.insertBefore(br,header.firstChild); + newrange.setStartAfter(br); + selection.removeAllRanges(); + selection.addRange(newrange); + }else if(rangeapi.atEndOfContainer(header, range.startContainer, range.startOffset)){ + newrange = rangeapi.create(this.editor.window); + br=doc.createElement('br'); + header.appendChild(br); + header.appendChild(doc.createTextNode('\xA0')); + newrange.setStart(header.lastChild,0); + selection.removeAllRanges(); + selection.addRange(newrange); + }else{ + rs = range.startContainer; + if(rs && rs.nodeType == 3){ + // Text node, we have to split it. + txt = rs.nodeValue; + win.withGlobal(this.editor.window, function(){ + startNode = doc.createTextNode(txt.substring(0, range.startOffset)); + endNode = doc.createTextNode(txt.substring(range.startOffset)); + brNode = doc.createElement("br"); + + if(endNode.nodeValue == "" && has("webkit")){ + endNode = doc.createTextNode('\xA0') + } + domConstruct.place(startNode, rs, "after"); + domConstruct.place(brNode, startNode, "after"); + domConstruct.place(endNode, brNode, "after"); + domConstruct.destroy(rs); + newrange = rangeapi.create(); + newrange.setStart(endNode,0); + selection.removeAllRanges(); + selection.addRange(newrange); + }); + return false; + } + return true; // let browser handle + } + }else{ + selection = rangeapi.getSelection(this.editor.window); + if(selection.rangeCount){ + range = selection.getRangeAt(0); + if(range && range.startContainer){ + if(!range.collapsed){ + range.deleteContents(); + selection = rangeapi.getSelection(this.editor.window); + range = selection.getRangeAt(0); + } + rs = range.startContainer; + if(rs && rs.nodeType == 3){ + // Text node, we have to split it. + win.withGlobal(this.editor.window, lang.hitch(this, function(){ + var endEmpty = false; + + 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)); + endNode = doc.createTextNode(txt.substring(offset)); + brNode = doc.createElement("br"); + + if(!endNode.length){ + endNode = doc.createTextNode('\xA0'); + endEmpty = true; + } + + if(startNode.length){ + domConstruct.place(startNode, rs, "after"); + }else{ + startNode = rs; + } + domConstruct.place(brNode, startNode, "after"); + domConstruct.place(endNode, brNode, "after"); + domConstruct.destroy(rs); + newrange = rangeapi.create(); + newrange.setStart(endNode,0); + newrange.setEnd(endNode, endNode.length); + selection.removeAllRanges(); + selection.addRange(newrange); + if(endEmpty && !has("webkit")){ + selectionapi.remove(); + }else{ + selectionapi.collapse(true); + } + })); + }else{ + var targetNode; + if(range.startOffset >= 0){ + targetNode = rs.childNodes[range.startOffset]; + } + win.withGlobal(this.editor.window, lang.hitch(this, function(){ + var brNode = doc.createElement("br"); + var endNode = doc.createTextNode('\xA0'); + if(!targetNode){ + rs.appendChild(brNode); + rs.appendChild(endNode); + }else{ + domConstruct.place(brNode, targetNode, "before"); + domConstruct.place(endNode, brNode, "after"); + } + newrange = rangeapi.create(win.global); + newrange.setStart(endNode,0); + newrange.setEnd(endNode, endNode.length); + selection.removeAllRanges(); + selection.addRange(newrange); + selectionapi.collapse(true); + })); + } + } + }else{ + // don't change this: do not call this.execCommand, as that may have other logic in subclass + RichText.prototype.execCommand.call(this.editor, 'inserthtml', '<br>'); + } + } + return false; + } + var _letBrowserHandle = true; + + // first remove selection + selection = rangeapi.getSelection(this.editor.window); + range = selection.getRangeAt(0); + if(!range.collapsed){ + range.deleteContents(); + selection = rangeapi.getSelection(this.editor.window); + range = selection.getRangeAt(0); + } + + var block = rangeapi.getBlockAncestor(range.endContainer, null, this.editor.editNode); + var blockNode = block.blockNode; + + // if this is under a LI or the parent of the blockNode is LI, just let browser to handle it + if((this._checkListLater = (blockNode && (blockNode.nodeName == 'LI' || blockNode.parentNode.nodeName == 'LI')))){ + if(has("mozilla")){ + // press enter in middle of P may leave a trailing <br/>, let's remove it later + this._pressedEnterInBlock = blockNode; + } + // if this li only contains spaces, set the content to empty so the browser will outdent this item + if(/^(\s| | |\xA0|<span\b[^>]*\bclass=['"]Apple-style-span['"][^>]*>(\s| | |\xA0)<\/span>)?(<br>)?$/.test(blockNode.innerHTML)){ + // empty LI node + blockNode.innerHTML = ''; + if(has("webkit")){ // WebKit tosses the range when innerHTML is reset + newrange = rangeapi.create(this.editor.window); + newrange.setStart(blockNode, 0); + selection.removeAllRanges(); + selection.addRange(newrange); + } + this._checkListLater = false; // nothing to check since the browser handles outdent + } + return true; + } + + // text node directly under body, let's wrap them in a node + if(!block.blockNode || block.blockNode===this.editor.editNode){ + try{ + RichText.prototype.execCommand.call(this.editor, 'formatblock',this.blockNodeForEnter); + }catch(e2){ /*squelch FF3 exception bug when editor content is a single BR*/ } + // get the newly created block node + // FIXME + block = {blockNode:win.withGlobal(this.editor.window, "getAncestorElement", selectionapi, [this.blockNodeForEnter]), + blockContainer: this.editor.editNode}; + if(block.blockNode){ + if(block.blockNode != this.editor.editNode && + (!(block.blockNode.textContent || block.blockNode.innerHTML).replace(/^\s+|\s+$/g, "").length)){ + this.removeTrailingBr(block.blockNode); + return false; + } + }else{ // we shouldn't be here if formatblock worked + block.blockNode = this.editor.editNode; + } + selection = rangeapi.getSelection(this.editor.window); + range = selection.getRangeAt(0); + } + + var newblock = doc.createElement(this.blockNodeForEnter); + newblock.innerHTML=this.bogusHtmlContent; + this.removeTrailingBr(block.blockNode); + var endOffset = range.endOffset; + var node = range.endContainer; + if(node.length < endOffset){ + //We are not checking the right node, try to locate the correct one + var ret = this._adjustNodeAndOffset(node, endOffset); + node = ret.node; + endOffset = ret.offset; + } + if(rangeapi.atEndOfContainer(block.blockNode, node, endOffset)){ + if(block.blockNode === block.blockContainer){ + block.blockNode.appendChild(newblock); + }else{ + domConstruct.place(newblock, block.blockNode, "after"); + } + _letBrowserHandle = false; + // lets move caret to the newly created block + newrange = rangeapi.create(this.editor.window); + newrange.setStart(newblock, 0); + selection.removeAllRanges(); + selection.addRange(newrange); + if(this.editor.height){ + winUtils.scrollIntoView(newblock); + } + }else if(rangeapi.atBeginningOfContainer(block.blockNode, + range.startContainer, range.startOffset)){ + domConstruct.place(newblock, block.blockNode, block.blockNode === block.blockContainer ? "first" : "before"); + if(newblock.nextSibling && this.editor.height){ + // position input caret - mostly WebKit needs this + newrange = rangeapi.create(this.editor.window); + newrange.setStart(newblock.nextSibling, 0); + selection.removeAllRanges(); + selection.addRange(newrange); + // browser does not scroll the caret position into view, do it manually + winUtils.scrollIntoView(newblock.nextSibling); + } + _letBrowserHandle = false; + }else{ //press enter in the middle of P/DIV/Whatever/ + if(block.blockNode === block.blockContainer){ + block.blockNode.appendChild(newblock); + }else{ + domConstruct.place(newblock, block.blockNode, "after"); + } + _letBrowserHandle = false; + + // Clone any block level styles. + if(block.blockNode.style){ + if(newblock.style){ + if(block.blockNode.style.cssText){ + newblock.style.cssText = block.blockNode.style.cssText; + } + } + } + + // Okay, we probably have to split. + rs = range.startContainer; + var firstNodeMoved; + if(rs && rs.nodeType == 3){ + // Text node, we have to split it. + var nodeToMove, tNode; + 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)); + endNode = doc.createTextNode(txt.substring(endOffset, txt.length)); + + // Place the split, then remove original nodes. + domConstruct.place(startNode, rs, "before"); + domConstruct.place(endNode, rs, "after"); + 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; + while(parentC !== block.blockNode){ + var tg = parentC.tagName; + 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; + } + } + } + // If font also need to clone over any font data. + if(parentC.tagName === "FONT"){ + if(parentC.color){ + newTg.color = parentC.color; + } + if(parentC.face){ + newTg.face = parentC.face; + } + if(parentC.size){ // this check was necessary on IE + newTg.size = parentC.size; + } + } + + nodeToMove = endNode; + while(nodeToMove){ + tNode = nodeToMove.nextSibling; + newTg.appendChild(nodeToMove); + nodeToMove = tNode; + } + domConstruct.place(newTg, parentC, "after"); + startNode = parentC; + endNode = newTg; + parentC = parentC.parentNode; + } + + // Lastly, move the split out tags to the new block. + // as they should now be split properly. + 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 = ""; + } + firstNodeMoved = nodeToMove; + while(nodeToMove){ + tNode = nodeToMove.nextSibling; + newblock.appendChild(nodeToMove); + nodeToMove = tNode; + } + } + + //lets move caret to the newly created block + newrange = rangeapi.create(this.editor.window); + var nodeForCursor; + var innerMostFirstNodeMoved = firstNodeMoved; + if(this.blockNodeForEnter !== 'BR'){ + while(innerMostFirstNodeMoved){ + nodeForCursor = innerMostFirstNodeMoved; + tNode = innerMostFirstNodeMoved.firstChild; + innerMostFirstNodeMoved = tNode; + } + if(nodeForCursor && nodeForCursor.parentNode){ + newblock = nodeForCursor.parentNode; + newrange.setStart(newblock, 0); + selection.removeAllRanges(); + selection.addRange(newrange); + if(this.editor.height){ + winUtils.scrollIntoView(newblock); + } + if(has("mozilla")){ + // press enter in middle of P may leave a trailing <br/>, let's remove it later + this._pressedEnterInBlock = block.blockNode; + } + }else{ + _letBrowserHandle = true; + } + }else{ + newrange.setStart(newblock, 0); + selection.removeAllRanges(); + selection.addRange(newrange); + if(this.editor.height){ + winUtils.scrollIntoView(newblock); + } + if(has("mozilla")){ + // press enter in middle of P may leave a trailing <br/>, let's remove it later + this._pressedEnterInBlock = block.blockNode; + } + } + } + return _letBrowserHandle; + }, + + _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}; + }, + + removeTrailingBr: function(container){ + // summary: + // If last child of container is a <br>, then remove it. + // tags: + // private + var para = /P|DIV|LI/i.test(container.tagName) ? + container : selectionapi.getParentOfType(container,['P','DIV','LI']); + + if(!para){ return; } + if(para.lastChild){ + if((para.childNodes.length > 1 && para.lastChild.nodeType == 3 && /^[\s\xAD]*$/.test(para.lastChild.nodeValue)) || + para.lastChild.tagName=='BR'){ + + domConstruct.destroy(para.lastChild); + } + } + if(!para.childNodes.length){ + para.innerHTML=this.bogusHtmlContent; + } + } +}); + +}); diff --git a/js/dojo-1.7.2/dijit/_editor/plugins/FontChoice.js b/js/dojo-1.7.2/dijit/_editor/plugins/FontChoice.js new file mode 100644 index 0000000..22d01dc --- /dev/null +++ b/js/dojo-1.7.2/dijit/_editor/plugins/FontChoice.js @@ -0,0 +1,592 @@ +//>>built +define("dijit/_editor/plugins/FontChoice", [ + "dojo/_base/array", // array.indexOf array.map + "dojo/_base/declare", // declare + "dojo/dom-construct", // domConstruct.place + "dojo/i18n", // i18n.getLocalization + "dojo/_base/lang", // lang.delegate lang.hitch lang.isString + "dojo/store/Memory", // MemoryStore + "dojo/_base/window", // win.withGlobal + "../../registry", // registry.getUniqueId + "../../_Widget", + "../../_TemplatedMixin", + "../../_WidgetsInTemplateMixin", + "../../form/FilteringSelect", + "../_Plugin", + "../range", + "../selection", + "dojo/i18n!../nls/FontChoice" +], function(array, declare, domConstruct, i18n, lang, MemoryStore, win, + registry, _Widget, _TemplatedMixin, _WidgetsInTemplateMixin, FilteringSelect, _Plugin, rangeapi, selectionapi){ + +/*===== + var _Plugin = dijit._editor._Plugin; + var _Widget = dijit._Widget; + var _TemplatedMixin = dijit._TemplatedMixin; + var _WidgetsInTemplateMixin = dijit._WidgetsInTemplateMixin; + var FilteringSelect = dijit.form.FilteringSelect; +=====*/ + + +// module: +// dijit/_editor/plugins/FontChoice +// summary: +// fontchoice, fontsize, and formatblock editor plugins + + +var _FontDropDown = declare("dijit._editor.plugins._FontDropDown", + [_Widget, _TemplatedMixin, _WidgetsInTemplateMixin], { + // summary: + // Base class for widgets that contains a label (like "Font:") + // and a FilteringSelect drop down to pick a value. + // Used as Toolbar entry. + + // label: [public] String + // The label to apply to this particular FontDropDown. + label: "", + + // plainText: [public] boolean + // Flag to indicate that the returned label should be plain text + // instead of an example. + plainText: false, + + // templateString: [public] String + // The template used to construct the labeled dropdown. + templateString: + "<span style='white-space: nowrap' class='dijit dijitReset dijitInline'>" + + "<label class='dijitLeft dijitInline' for='${selectId}'>${label}</label>" + + "<input data-dojo-type='dijit.form.FilteringSelect' required='false' " + + "data-dojo-props='labelType:\"html\", labelAttr:\"label\", searchAttr:\"name\"' " + + "tabIndex='-1' id='${selectId}' data-dojo-attach-point='select' value=''/>" + + "</span>", + + postMixInProperties: function(){ + // summary: + // Over-ride to set specific properties. + this.inherited(arguments); + + this.strings = i18n.getLocalization("dijit._editor", "FontChoice"); + + // Set some substitution variables used in the template + this.label = this.strings[this.command]; + this.id = registry.getUniqueId(this.declaredClass.replace(/\./g,"_")); // TODO: unneeded?? + this.selectId = this.id + "_select"; // used in template + + this.inherited(arguments); + }, + + postCreate: function(){ + // summary: + // Over-ride for the default postCreate action + // This establishes the filtering selects and the like. + + // Initialize the list of items in the drop down by creating data store with items like: + // {value: 1, name: "xx-small", label: "<font size=1>xx-small</font-size>" } + this.select.set("store", new MemoryStore({ + idProperty: "value", + data: array.map(this.values, function(value){ + var name = this.strings[value] || value; + return { + label: this.getLabel(value, name), + name: name, + value: value + }; + }, this) + })); + + this.select.set("value", "", false); + this.disabled = this.select.get("disabled"); + }, + + _setValueAttr: function(value, priorityChange){ + // summary: + // Over-ride for the default action of setting the + // widget value, maps the input to known values + // value: Object|String + // The value to set in the select. + // priorityChange: + // Optional parameter used to tell the select whether or not to fire + // onChange event. + + // if the value is not a permitted value, just set empty string to prevent showing the warning icon + priorityChange = priorityChange !== false; + this.select.set('value', array.indexOf(this.values,value) < 0 ? "" : value, priorityChange); + if(!priorityChange){ + // Clear the last state in case of updateState calls. Ref: #10466 + this.select._lastValueReported=null; + } + }, + + _getValueAttr: function(){ + // summary: + // Allow retrieving the value from the composite select on + // call to button.get("value"); + return this.select.get('value'); + }, + + focus: function(){ + // summary: + // Over-ride for focus control of this widget. Delegates focus down to the + // filtering select. + this.select.focus(); + }, + + _setDisabledAttr: function(value){ + // summary: + // Over-ride for the button's 'disabled' attribute so that it can be + // disabled programmatically. + + // Save off ths disabled state so the get retrieves it correctly + //without needing to have a function proxy it. + this.disabled = value; + this.select.set("disabled", value); + } +}); + + +var _FontNameDropDown = declare("dijit._editor.plugins._FontNameDropDown", _FontDropDown, { + // summary: + // Dropdown to select a font; goes in editor toolbar. + + // generic: Boolean + // Use generic (web standard) font names + generic: false, + + // command: [public] String + // The editor 'command' implemented by this plugin. + command: "fontName", + + postMixInProperties: function(){ + // summary: + // Over-ride for the default posr mixin control + if(!this.values){ + this.values = this.generic ? + ["serif", "sans-serif", "monospace", "cursive", "fantasy"] : // CSS font-family generics + ["Arial", "Times New Roman", "Comic Sans MS", "Courier New"]; + } + this.inherited(arguments); + }, + + getLabel: function(value, name){ + // summary: + // Function used to generate the labels of the format dropdown + // will return a formatted, or plain label based on the value + // of the plainText option. + // value: String + // The 'insert value' associated with a name + // name: String + // The text name of the value + if(this.plainText){ + return name; + }else{ + return "<div style='font-family: "+value+"'>" + name + "</div>"; + } + }, + + _setValueAttr: function(value, priorityChange){ + // summary: + // Over-ride for the default action of setting the + // widget value, maps the input to known values + + priorityChange = priorityChange !== false; + if(this.generic){ + var map = { + "Arial": "sans-serif", + "Helvetica": "sans-serif", + "Myriad": "sans-serif", + "Times": "serif", + "Times New Roman": "serif", + "Comic Sans MS": "cursive", + "Apple Chancery": "cursive", + "Courier": "monospace", + "Courier New": "monospace", + "Papyrus": "fantasy", + "Estrangelo Edessa": "cursive", // Windows 7 + "Gabriola": "fantasy" // Windows 7 + }; + value = map[value] || value; + } + this.inherited(arguments, [value, priorityChange]); + } +}); + +var _FontSizeDropDown = declare("dijit._editor.plugins._FontSizeDropDown", _FontDropDown, { + // summary: + // Dropdown to select a font size; goes in editor toolbar. + + // command: [public] String + // The editor 'command' implemented by this plugin. + command: "fontSize", + + // values: [public] Number[] + // The HTML font size values supported by this plugin + values: [1,2,3,4,5,6,7], // sizes according to the old HTML FONT SIZE + + getLabel: function(value, name){ + // summary: + // Function used to generate the labels of the format dropdown + // will return a formatted, or plain label based on the value + // of the plainText option. + // We're stuck using the deprecated FONT tag to correspond + // with the size measurements used by the editor + // value: String + // The 'insert value' associated with a name + // name: String + // The text name of the value + if(this.plainText){ + return name; + }else{ + return "<font size=" + value + "'>" + name + "</font>"; + } + }, + + _setValueAttr: function(value, priorityChange){ + // summary: + // Over-ride for the default action of setting the + // widget value, maps the input to known values + priorityChange = priorityChange !== false; + if(value.indexOf && value.indexOf("px") != -1){ + var pixels = parseInt(value, 10); + value = {10:1, 13:2, 16:3, 18:4, 24:5, 32:6, 48:7}[pixels] || value; + } + + this.inherited(arguments, [value, priorityChange]); + } +}); + + +var _FormatBlockDropDown = declare("dijit._editor.plugins._FormatBlockDropDown", _FontDropDown, { + // summary: + // Dropdown to select a format (like paragraph or heading); goes in editor toolbar. + + // command: [public] String + // The editor 'command' implemented by this plugin. + command: "formatBlock", + + // values: [public] Array + // The HTML format tags supported by this plugin + values: ["noFormat", "p", "h1", "h2", "h3", "pre"], + + postCreate: function(){ + // Init and set the default value to no formatting. Update state will adjust it + // as needed. + this.inherited(arguments); + this.set("value", "noFormat", false); + }, + + getLabel: function(value, name){ + // summary: + // Function used to generate the labels of the format dropdown + // will return a formatted, or plain label based on the value + // of the plainText option. + // value: String + // The 'insert value' associated with a name + // name: String + // The text name of the value + if(this.plainText || value == "noFormat"){ + return name; + }else{ + return "<" + value + ">" + name + "</" + value + ">"; + } + }, + + _execCommand: function(editor, command, choice){ + // summary: + // Over-ride for default exec-command label. + // Allows us to treat 'none' as special. + if(choice === "noFormat"){ + var start; + var end; + var sel = rangeapi.getSelection(editor.window); + if(sel && sel.rangeCount > 0){ + var range = sel.getRangeAt(0); + var node, tag; + if(range){ + start = range.startContainer; + end = range.endContainer; + + // find containing nodes of start/end. + while(start && start !== editor.editNode && + start !== editor.document.body && + start.nodeType !== 1){ + start = start.parentNode; + } + + while(end && end !== editor.editNode && + end !== editor.document.body && + end.nodeType !== 1){ + end = end.parentNode; + } + + var processChildren = lang.hitch(this, function(node, ary){ + if(node.childNodes && node.childNodes.length){ + var i; + for(i = 0; i < node.childNodes.length; i++){ + var c = node.childNodes[i]; + if(c.nodeType == 1){ + if(win.withGlobal(editor.window, "inSelection", selectionapi, [c])){ + var tag = c.tagName? c.tagName.toLowerCase(): ""; + if(array.indexOf(this.values, tag) !== -1){ + ary.push(c); + } + processChildren(c, ary); + } + } + } + } + }); + + var unformatNodes = lang.hitch(this, function(nodes){ + // summary: + // Internal function to clear format nodes. + // nodes: + // The array of nodes to strip formatting from. + if(nodes && nodes.length){ + editor.beginEditing(); + while(nodes.length){ + this._removeFormat(editor, nodes.pop()); + } + editor.endEditing(); + } + }); + + var clearNodes = []; + if(start == end){ + //Contained within the same block, may be collapsed, but who cares, see if we + // have a block element to remove. + var block; + node = start; + while(node && node !== editor.editNode && node !== editor.document.body){ + if(node.nodeType == 1){ + tag = node.tagName? node.tagName.toLowerCase(): ""; + if(array.indexOf(this.values, tag) !== -1){ + block = node; + break; + } + } + node = node.parentNode; + } + + //Also look for all child nodes in the selection that may need to be + //cleared of formatting + processChildren(start, clearNodes); + if(block){ clearNodes = [block].concat(clearNodes); } + unformatNodes(clearNodes); + }else{ + // Probably a multi select, so we have to process it. Whee. + node = start; + while(win.withGlobal(editor.window, "inSelection", selectionapi, [node])){ + if(node.nodeType == 1){ + tag = node.tagName? node.tagName.toLowerCase(): ""; + if(array.indexOf(this.values, tag) !== -1){ + clearNodes.push(node); + } + processChildren(node,clearNodes); + } + node = node.nextSibling; + } + unformatNodes(clearNodes); + } + editor.onDisplayChanged(); + } + } + }else{ + editor.execCommand(command, choice); + } + }, + + _removeFormat: function(editor, node){ + // summary: + // function to remove the block format node. + // node: + // The block format node to remove (and leave the contents behind) + if(editor.customUndo){ + // So of course IE doesn't work right with paste-overs. + // We have to do this manually, which is okay since IE already uses + // customUndo and we turned it on for WebKit. WebKit pasted funny, + // so couldn't use the execCommand approach + while(node.firstChild){ + domConstruct.place(node.firstChild, node, "before"); + } + node.parentNode.removeChild(node); + }else{ + // Everyone else works fine this way, a paste-over and is native + // undo friendly. + win.withGlobal(editor.window, + "selectElementChildren", selectionapi, [node]); + var html = win.withGlobal(editor.window, + "getSelectedHtml", selectionapi, [null]); + win.withGlobal(editor.window, + "selectElement", selectionapi, [node]); + editor.execCommand("inserthtml", html||""); + } + } +}); + +// TODO: for 2.0, split into FontChoice plugin into three separate classes, +// one for each command (and change registry below) +var FontChoice = declare("dijit._editor.plugins.FontChoice", _Plugin,{ + // summary: + // This plugin provides three drop downs for setting style in the editor + // (font, font size, and format block), as controlled by command. + // + // description: + // The commands provided by this plugin are: + // + // * fontName + // | Provides a drop down to select from a list of font names + // * fontSize + // | Provides a drop down to select from a list of font sizes + // * formatBlock + // | Provides a drop down to select from a list of block styles + // | + // + // which can easily be added to an editor by including one or more of the above commands + // in the `plugins` attribute as follows: + // + // | plugins="['fontName','fontSize',...]" + // + // It is possible to override the default dropdown list by providing an Array for the `custom` property when + // instantiating this plugin, e.g. + // + // | plugins="[{name:'dijit._editor.plugins.FontChoice', command:'fontName', custom:['Verdana','Myriad','Garamond']},...]" + // + // Alternatively, for `fontName` only, `generic:true` may be specified to provide a dropdown with + // [CSS generic font families](http://www.w3.org/TR/REC-CSS2/fonts.html#generic-font-families) + // + // Note that the editor is often unable to properly handle font styling information defined outside + // the context of the current editor instance, such as pre-populated HTML. + + // useDefaultCommand: [protected] Boolean + // Override _Plugin.useDefaultCommand... + // processing is handled by this plugin, not by dijit.Editor. + useDefaultCommand: false, + + _initButton: function(){ + // summary: + // Overrides _Plugin._initButton(), to initialize the FilteringSelect+label in toolbar, + // rather than a simple button. + // tags: + // protected + + // Create the widget to go into the toolbar (the so-called "button") + var clazz = { + fontName: _FontNameDropDown, + fontSize: _FontSizeDropDown, + formatBlock: _FormatBlockDropDown + }[this.command], + params = this.params; + + // For back-compat reasons support setting custom values via "custom" parameter + // rather than "values" parameter + if(this.params.custom){ + params.values = this.params.custom; + } + + var editor = this.editor; + this.button = new clazz(lang.delegate({dir: editor.dir, lang: editor.lang}, params)); + + // Reflect changes to the drop down in the editor + this.connect(this.button.select, "onChange", function(choice){ + // User invoked change, since all internal updates set priorityChange to false and will + // not trigger an onChange event. + this.editor.focus(); + + if(this.command == "fontName" && choice.indexOf(" ") != -1){ choice = "'" + choice + "'"; } + + // Invoke, the editor already normalizes commands called through its + // execCommand. + if(this.button._execCommand){ + this.button._execCommand(this.editor, this.command, choice); + }else{ + this.editor.execCommand(this.command, choice); + } + }); + }, + + updateState: function(){ + // summary: + // Overrides _Plugin.updateState(). This controls updating the menu + // options to the right values on state changes in the document (that trigger a + // test of the actions.) + // It set value of drop down in toolbar to reflect font/font size/format block + // of text at current caret position. + // tags: + // protected + var _e = this.editor; + var _c = this.command; + if(!_e || !_e.isLoaded || !_c.length){ return; } + + if(this.button){ + var disabled = this.get("disabled"); + this.button.set("disabled", disabled); + if(disabled){ return; } + var value; + try{ + value = _e.queryCommandValue(_c) || ""; + }catch(e){ + //Firefox may throw error above if the editor is just loaded, ignore it + value = ""; + } + + // strip off single quotes, if any + var quoted = lang.isString(value) && value.match(/'([^']*)'/); + if(quoted){ value = quoted[1]; } + + if(_c === "formatBlock"){ + if(!value || value == "p"){ + // Some browsers (WebKit) doesn't actually get the tag info right. + // and IE returns paragraph when in a DIV!, so incorrect a lot, + // so we have double-check it. + value = null; + var elem; + // Try to find the current element where the caret is. + var sel = rangeapi.getSelection(this.editor.window); + if(sel && sel.rangeCount > 0){ + var range = sel.getRangeAt(0); + if(range){ + elem = range.endContainer; + } + } + + // Okay, now see if we can find one of the formatting types we're in. + while(elem && elem !== _e.editNode && elem !== _e.document){ + var tg = elem.tagName?elem.tagName.toLowerCase():""; + if(tg && array.indexOf(this.button.values, tg) > -1){ + value = tg; + break; + } + elem = elem.parentNode; + } + if(!value){ + // Still no value, so lets select 'none'. + value = "noFormat"; + } + }else{ + // Check that the block format is one allowed, if not, + // null it so that it gets set to empty. + if(array.indexOf(this.button.values, value) < 0){ + value = "noFormat"; + } + } + } + if(value !== this.button.get("value")){ + // Set the value, but denote it is not a priority change, so no + // onchange fires. + this.button.set('value', value, false); + } + } + } +}); + +// Register these plugins +array.forEach(["fontName", "fontSize", "formatBlock"], function(name){ + _Plugin.registry[name] = function(args){ + return new FontChoice({ + command: name, + plainText: args.plainText + }); + }; +}); + +}); diff --git a/js/dojo-1.7.2/dijit/_editor/plugins/FullScreen.js b/js/dojo-1.7.2/dijit/_editor/plugins/FullScreen.js new file mode 100644 index 0000000..bb77cd6 --- /dev/null +++ b/js/dojo-1.7.2/dijit/_editor/plugins/FullScreen.js @@ -0,0 +1,458 @@ +//>>built +define("dijit/_editor/plugins/FullScreen", [ + "dojo/aspect", + "dojo/_base/declare", // declare + "dojo/dom-class", // domClass.add domClass.remove + "dojo/dom-geometry", + "dojo/dom-style", + "dojo/_base/event", // event.stop + "dojo/i18n", // i18n.getLocalization + "dojo/keys", // keys.F11 keys.TAB + "dojo/_base/lang", // lang.hitch + "dojo/on", // on() + "dojo/_base/sniff", // has("ie"), has("quirks") + "dojo/_base/window", // win.body + "dojo/window", // winUtils.getBox winUtils.scrollIntoView + "../../focus", // focus.focus(), focus.curNode + "../_Plugin", + "../../form/ToggleButton", + "../../registry", // registry.getEnclosingWidget() + "dojo/i18n!../nls/commands" +], function(aspect, declare, domClass, domGeometry, domStyle, event, i18n, keys, lang, on, has, win, winUtils, + focus, _Plugin, ToggleButton, registry){ + +/*===== + var _Plugin = dijit._editor._Plugin; +=====*/ + + +// module: +// dijit/_editor/plugins/FullScreen +// summary: +// This plugin provides FullScreen capability to the editor. When +// toggled on, it will render the editor into the full window and +// overlay everything. It also binds to the hotkey: CTRL-SHIFT-F11 +// for toggling fullscreen mode. + + +var FullScreen = declare("dijit._editor.plugins.FullScreen",_Plugin,{ + // summary: + // This plugin provides FullScreen capability to the editor. When + // toggled on, it will render the editor into the full window and + // overlay everything. It also binds to the hotkey: CTRL-SHIFT-F11 + // for toggling fullscreen mode. + + // zIndex: [public] Number + // zIndex value used for overlaying the full page. + // default is 500. + zIndex: 500, + + // _origState: [private] Object + // The original view state of the editor. + _origState: null, + + // _origiFrameState: [private] Object + // The original view state of the iframe of the editor. + _origiFrameState: null, + + // _resizeHandle: [private] Object + // Connection point used for handling resize when window resizes. + _resizeHandle: null, + + // isFullscreen: [const] boolean + // Read-Only variable used to denote of the editor is in fullscreen mode or not. + isFullscreen: false, + + toggle: function(){ + // summary: + // Function to allow programmatic toggling of the view. + this.button.set("checked", !this.button.get("checked")); + }, + + _initButton: function(){ + // summary: + // Over-ride for creation of the resize button. + var strings = i18n.getLocalization("dijit._editor", "commands"), + editor = this.editor; + this.button = new ToggleButton({ + label: strings["fullScreen"], + dir: editor.dir, + lang: editor.lang, + showLabel: false, + iconClass: this.iconClassPrefix + " " + this.iconClassPrefix + "FullScreen", + tabIndex: "-1", + onChange: lang.hitch(this, "_setFullScreen") + }); + }, + + setEditor: function(editor){ + // summary: + // Over-ride for the setting of the editor. + // editor: Object + // The editor to configure for this plugin to use. + this.editor = editor; + this._initButton(); + + this.editor.addKeyHandler(keys.F11, true, true, lang.hitch(this, function(e){ + // Enable the CTRL-SHIFT-F11 hotkey for fullscreen mode. + this.toggle(); + event.stop(e); + setTimeout(lang.hitch(this, function(){this.editor.focus();}), 250); + return true; + })); + this.connect(this.editor.domNode, "onkeydown", "_containFocus"); + }, + + _containFocus: function(e){ + // summary: + // When in Full Screen mode, it's good to try and retain focus in the editor + // so this function is intended to try and constrain the TAB key. + // e: Event + // The key event. + // tags: + // private + if(this.isFullscreen){ + var ed = this.editor; + if(!ed.isTabIndent && + ed._fullscreen_oldOnKeyDown && + e.keyCode === keys.TAB){ + // If we're in fullscreen mode, we want to take over how tab moves focus a bit. + // to keep it within the editor since it's hiding the rest of the page. + // IE hates changing focus IN the event handler, so need to put calls + // in a timeout. Gotta love IE. + // Also need to check for alternate view nodes if present and active. + var f = focus.curNode; + var avn = this._getAltViewNode(); + if(f == ed.iframe || + (avn && f === avn)){ + setTimeout(lang.hitch(this, function(){ + ed.toolbar.focus(); + }), 10); + }else{ + if(avn && domStyle.get(ed.iframe, "display") === "none"){ + setTimeout(lang.hitch(this, function(){ + focus.focus(avn); + }), 10); + }else{ + setTimeout(lang.hitch(this, function(){ + ed.focus(); + }), 10); + } + } + event.stop(e); + }else if(ed._fullscreen_oldOnKeyDown){ + // Only call up when it's a different function. Traps corner case event issue + // on IE which caused stack overflow on handler cleanup. + ed._fullscreen_oldOnKeyDown(e); + } + } + }, + + _resizeEditor: function(){ + // summary: + // Function to handle resizing the editor as the viewport + // resizes (window scaled) + // tags: + // private + var vp = winUtils.getBox(); + domGeometry.setMarginBox(this.editor.domNode, { + w: vp.w, + h: vp.h + }); + + //Adjust the internal heights too, as they can be a bit off. + var hHeight = this.editor.getHeaderHeight(); + var fHeight = this.editor.getFooterHeight(); + var extents = domGeometry.getPadBorderExtents(this.editor.domNode); + var fcpExtents = domGeometry.getPadBorderExtents(this.editor.iframe.parentNode); + var fcmExtents = domGeometry.getMarginExtents(this.editor.iframe.parentNode); + + var cHeight = vp.h - (hHeight + extents.h + fHeight); + domGeometry.setMarginBox(this.editor.iframe.parentNode, { + h: cHeight, + w: vp.w + }); + domGeometry.setMarginBox(this.editor.iframe, { + h: cHeight - (fcpExtents.h + fcmExtents.h) + }); + }, + + _getAltViewNode: function(){ + // summary: + // This function is intended as a hook point for setting an + // alternate view node for when in full screen mode and the + // editable iframe is hidden. + // tags: + // protected. + }, + + _setFullScreen: function(full){ + // summary: + // Function to handle toggling between full screen and + // regular view. + // tags: + // private + var vp = winUtils.getBox(); + + //Alias this for shorter code. + var ed = this.editor; + var body = win.body(); + var editorParent = ed.domNode.parentNode; + + this.isFullscreen = full; + + if(full){ + //Parent classes can royally screw up this plugin, so we + //have to set everything to position static. + while(editorParent && editorParent !== win.body()){ + domClass.add(editorParent, "dijitForceStatic"); + editorParent = editorParent.parentNode; + } + + // Save off the resize function. We want to kill its behavior. + this._editorResizeHolder = this.editor.resize; + ed.resize = function(){} ; + + // Try to constrain focus control. + ed._fullscreen_oldOnKeyDown = ed.onKeyDown; + ed.onKeyDown = lang.hitch(this, this._containFocus); + + this._origState = {}; + this._origiFrameState = {}; + + // Store the basic editor state we have to restore later. + // Not using domStyle.get here, had problems, didn't + // give me stuff like 100%, gave me pixel calculated values. + // Need the exact original values. + var domNode = ed.domNode, + rawStyle = domNode && domNode.style || {}; + this._origState = { + width: rawStyle.width || "", + height: rawStyle.height || "", + top: domStyle.get(domNode, "top") || "", + left: domStyle.get(domNode, "left") || "", + position: domStyle.get(domNode, "position") || "static", + marginBox: domGeometry.getMarginBox(ed.domNode) + }; + + // Store the iframe state we have to restore later. + // Not using domStyle.get here, had problems, didn't + // give me stuff like 100%, gave me pixel calculated values. + // Need the exact original values. + var iframe = ed.iframe, + iframeStyle = iframe && iframe.style || {}; + + var bc = domStyle.get(ed.iframe, "backgroundColor"); + this._origiFrameState = { + backgroundColor: bc || "transparent", + width: iframeStyle.width || "auto", + height: iframeStyle.height || "auto", + zIndex: iframeStyle.zIndex || "" + }; + + // Okay, size everything. + domStyle.set(ed.domNode, { + position: "absolute", + top: "0px", + left: "0px", + zIndex: this.zIndex, + width: vp.w + "px", + height: vp.h + "px" + }); + + domStyle.set(ed.iframe, { + height: "100%", + width: "100%", + zIndex: this.zIndex, + backgroundColor: bc !== "transparent" && + bc !== "rgba(0, 0, 0, 0)"?bc:"white" + }); + + domStyle.set(ed.iframe.parentNode, { + height: "95%", + width: "100%" + }); + + // Store the overflow state we have to restore later. + // IE had issues, so have to check that it's defined. Ugh. + if(body.style && body.style.overflow){ + this._oldOverflow = domStyle.get(body, "overflow"); + }else{ + this._oldOverflow = ""; + } + + if(has("ie") && !has("quirks")){ + // IE will put scrollbars in anyway, html (parent of body) + // also controls them in standards mode, so we have to + // remove them, argh. + if(body.parentNode && + body.parentNode.style && + body.parentNode.style.overflow){ + this._oldBodyParentOverflow = body.parentNode.style.overflow; + }else{ + try{ + this._oldBodyParentOverflow = domStyle.get(body.parentNode, "overflow"); + }catch(e){ + this._oldBodyParentOverflow = "scroll"; + } + } + domStyle.set(body.parentNode, "overflow", "hidden"); + } + domStyle.set(body, "overflow", "hidden"); + + var resizer = function(){ + // function to handle resize events. + // Will check current VP and only resize if + // different. + var vp = winUtils.getBox(); + if("_prevW" in this && "_prevH" in this){ + // No actual size change, ignore. + if(vp.w === this._prevW && vp.h === this._prevH){ + return; + } + }else{ + this._prevW = vp.w; + this._prevH = vp.h; + } + if(this._resizer){ + clearTimeout(this._resizer); + delete this._resizer; + } + // Timeout it to help avoid spamming resize on IE. + // Works for all browsers. + this._resizer = setTimeout(lang.hitch(this, function(){ + delete this._resizer; + this._resizeEditor(); + }), 10); + }; + this._resizeHandle = on(window, "resize", lang.hitch(this, resizer)); + + // Also monitor for direct calls to resize and adapt editor. + this._resizeHandle2 = aspect.after(ed, "onResize", lang.hitch(this, function(){ + if(this._resizer){ + clearTimeout(this._resizer); + delete this._resizer; + } + this._resizer = setTimeout(lang.hitch(this, function(){ + delete this._resizer; + this._resizeEditor(); + }), 10); + })); + + // Call it once to work around IE glitchiness. Safe for other browsers too. + this._resizeEditor(); + var dn = this.editor.toolbar.domNode; + setTimeout(function(){winUtils.scrollIntoView(dn);}, 250); + }else{ + if(this._resizeHandle){ + // Cleanup resizing listeners + this._resizeHandle.remove(); + this._resizeHandle = null; + } + if(this._resizeHandle2){ + // Cleanup resizing listeners + this._resizeHandle2.remove(); + this._resizeHandle2 = null; + } + if(this._rst){ + clearTimeout(this._rst); + this._rst = null; + } + + //Remove all position static class assigns. + while(editorParent && editorParent !== win.body()){ + domClass.remove(editorParent, "dijitForceStatic"); + editorParent = editorParent.parentNode; + } + + // Restore resize function + if(this._editorResizeHolder){ + this.editor.resize = this._editorResizeHolder; + } + + if(!this._origState && !this._origiFrameState){ + // If we actually didn't toggle, then don't do anything. + return; + } + if(ed._fullscreen_oldOnKeyDown){ + ed.onKeyDown = ed._fullscreen_oldOnKeyDown; + delete ed._fullscreen_oldOnKeyDown; + } + + // Add a timeout to make sure we don't have a resize firing in the + // background at the time of minimize. + var self = this; + setTimeout(function(){ + // Restore all the editor state. + var mb = self._origState.marginBox; + var oh = self._origState.height; + if(has("ie") && !has("quirks")){ + body.parentNode.style.overflow = self._oldBodyParentOverflow; + delete self._oldBodyParentOverflow; + } + domStyle.set(body, "overflow", self._oldOverflow); + delete self._oldOverflow; + + domStyle.set(ed.domNode, self._origState); + domStyle.set(ed.iframe.parentNode, { + height: "", + width: "" + }); + domStyle.set(ed.iframe, self._origiFrameState); + delete self._origState; + delete self._origiFrameState; + // In case it is contained in a layout and the layout changed size, + // go ahead and call resize. + var pWidget = registry.getEnclosingWidget(ed.domNode.parentNode); + if(pWidget && pWidget.resize){ + pWidget.resize(); + }else{ + if(!oh || oh.indexOf("%") < 0){ + // Resize if the original size wasn't set + // or wasn't in percent. Timeout is to avoid + // an IE crash in unit testing. + setTimeout(lang.hitch(this, function(){ed.resize({h: mb.h});}), 0); + } + } + winUtils.scrollIntoView(self.editor.toolbar.domNode); + }, 100); + } + }, + + updateState: function(){ + // summary: + // Over-ride for button state control for disabled to work. + this.button.set("disabled", this.get("disabled")); + }, + + destroy: function(){ + // summary: + // Over-ride to ensure the resize handle gets cleaned up. + if(this._resizeHandle){ + // Cleanup resizing listeners + this._resizeHandle.remove(); + this._resizeHandle = null; + } + if(this._resizeHandle2){ + // Cleanup resizing listeners + this._resizeHandle2.remove(); + this._resizeHandle2 = null; + } + if(this._resizer){ + clearTimeout(this._resizer); + this._resizer = null; + } + this.inherited(arguments); + } +}); + +// Register this plugin. +// For back-compat accept "fullscreen" (all lowercase) too, remove in 2.0 +_Plugin.registry["fullScreen"] = _Plugin.registry["fullscreen"] = function(args){ + return new FullScreen({ + zIndex: ("zIndex" in args)?args.zIndex:500 + }); +}; + +return FullScreen; +}); diff --git a/js/dojo-1.7.2/dijit/_editor/plugins/LinkDialog.js b/js/dojo-1.7.2/dijit/_editor/plugins/LinkDialog.js new file mode 100644 index 0000000..3be832e --- /dev/null +++ b/js/dojo-1.7.2/dijit/_editor/plugins/LinkDialog.js @@ -0,0 +1,587 @@ +//>>built +define("dijit/_editor/plugins/LinkDialog", [ + "require", + "dojo/_base/declare", // declare + "dojo/dom-attr", // domAttr.get + "dojo/keys", // keys.ENTER + "dojo/_base/lang", // lang.delegate lang.hitch lang.trim + "dojo/_base/sniff", // has("ie") + "dojo/string", // string.substitute + "dojo/_base/window", // win.withGlobal + "../../_Widget", + "../_Plugin", + "../../form/DropDownButton", + "../range", + "../selection" +], function(require, declare, domAttr, keys, lang, has, string, win, + _Widget, _Plugin, DropDownButton, rangeapi, selectionapi){ + +/*===== + var _Plugin = dijit._editor._Plugin; +=====*/ + +// module: +// dijit/_editor/plugins/LinkDialog +// summary: +// Editor plugins: LinkDialog (for inserting links) and ImgLinkDialog (for inserting images) + + +var LinkDialog = declare("dijit._editor.plugins.LinkDialog", _Plugin, { + // summary: + // This plugin provides the basis for an 'anchor' (link) dialog and an extension of it + // provides the image link dialog. + // + // description: + // The command provided by this plugin is: + // * createLink + + // Override _Plugin.buttonClass. This plugin is controlled by a DropDownButton + // (which triggers a TooltipDialog). + buttonClass: DropDownButton, + + // Override _Plugin.useDefaultCommand... processing is handled by this plugin, not by dijit.Editor. + useDefaultCommand: false, + + // urlRegExp: [protected] String + // Used for validating input as correct URL. While file:// urls are not terribly + // useful, they are technically valid. + urlRegExp: "((https?|ftps?|file)\\://|\./|/|)(/[a-zA-Z]{1,1}:/|)(((?:(?:[\\da-zA-Z](?:[-\\da-zA-Z]{0,61}[\\da-zA-Z])?)\\.)*(?:[a-zA-Z](?:[-\\da-zA-Z]{0,80}[\\da-zA-Z])?)\\.?)|(((\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])|(0[xX]0*[\\da-fA-F]?[\\da-fA-F]\\.){3}0[xX]0*[\\da-fA-F]?[\\da-fA-F]|(0+[0-3][0-7][0-7]\\.){3}0+[0-3][0-7][0-7]|(0|[1-9]\\d{0,8}|[1-3]\\d{9}|4[01]\\d{8}|42[0-8]\\d{7}|429[0-3]\\d{6}|4294[0-8]\\d{5}|42949[0-5]\\d{4}|429496[0-6]\\d{3}|4294967[01]\\d{2}|42949672[0-8]\\d|429496729[0-5])|0[xX]0*[\\da-fA-F]{1,8}|([\\da-fA-F]{1,4}\\:){7}[\\da-fA-F]{1,4}|([\\da-fA-F]{1,4}\\:){6}((\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])))(\\:\\d+)?(/(?:[^?#\\s/]+/)*(?:[^?#\\s/]{0,}(?:\\?[^?#\\s/]*)?(?:#.*)?)?)?", + + // emailRegExp: [protected] String + // Used for validating input as correct email address. Taken from dojox.validate + emailRegExp: "<?(mailto\\:)([!#-'*+\\-\\/-9=?A-Z^-~]+[.])*[!#-'*+\\-\\/-9=?A-Z^-~]+" /*username*/ + "@" + + "((?:(?:[\\da-zA-Z](?:[-\\da-zA-Z]{0,61}[\\da-zA-Z])?)\\.)+(?:[a-zA-Z](?:[-\\da-zA-Z]{0,6}[\\da-zA-Z])?)\\.?)|localhost|^[^-][a-zA-Z0-9_-]*>?", // host. + + // htmlTemplate: [protected] String + // String used for templating the HTML to insert at the desired point. + htmlTemplate: "<a href=\"${urlInput}\" _djrealurl=\"${urlInput}\"" + + " target=\"${targetSelect}\"" + + ">${textInput}</a>", + + // tag: [protected] String + // Tag used for the link type. + tag: "a", + + // _hostRxp [private] RegExp + // Regular expression used to validate url fragments (ip address, hostname, etc) + _hostRxp: /^((([^\[:]+):)?([^@]+)@)?(\[([^\]]+)\]|([^\[:]*))(:([0-9]+))?$/, + + // _userAtRxp [private] RegExp + // Regular expression used to validate e-mail address fragment. + _userAtRxp: /^([!#-'*+\-\/-9=?A-Z^-~]+[.])*[!#-'*+\-\/-9=?A-Z^-~]+@/i, + + // linkDialogTemplate: [protected] String + // Template for contents of TooltipDialog to pick URL + linkDialogTemplate: [ + "<table><tr><td>", + "<label for='${id}_urlInput'>${url}</label>", + "</td><td>", + "<input data-dojo-type='dijit.form.ValidationTextBox' required='true' " + + "id='${id}_urlInput' name='urlInput' data-dojo-props='intermediateChanges:true'/>", + "</td></tr><tr><td>", + "<label for='${id}_textInput'>${text}</label>", + "</td><td>", + "<input data-dojo-type='dijit.form.ValidationTextBox' required='true' id='${id}_textInput' " + + "name='textInput' data-dojo-props='intermediateChanges:true'/>", + "</td></tr><tr><td>", + "<label for='${id}_targetSelect'>${target}</label>", + "</td><td>", + "<select id='${id}_targetSelect' name='targetSelect' data-dojo-type='dijit.form.Select'>", + "<option selected='selected' value='_self'>${currentWindow}</option>", + "<option value='_blank'>${newWindow}</option>", + "<option value='_top'>${topWindow}</option>", + "<option value='_parent'>${parentWindow}</option>", + "</select>", + "</td></tr><tr><td colspan='2'>", + "<button data-dojo-type='dijit.form.Button' type='submit' id='${id}_setButton'>${set}</button>", + "<button data-dojo-type='dijit.form.Button' type='button' id='${id}_cancelButton'>${buttonCancel}</button>", + "</td></tr></table>" + ].join(""), + + _initButton: function(){ + this.inherited(arguments); + + // Setup to lazy create TooltipDialog first time the button is clicked + this.button.loadDropDown = lang.hitch(this, "_loadDropDown"); + + this._connectTagEvents(); + }, + _loadDropDown: function(callback){ + // Called the first time the button is pressed. Initialize TooltipDialog. + require([ + "dojo/i18n", // i18n.getLocalization + "../../TooltipDialog", + "../../registry", // registry.byId, registry.getUniqueId + "../../form/Button", // used by template + "../../form/Select", // used by template + "../../form/ValidationTextBox", // used by template + "dojo/i18n!../../nls/common", + "dojo/i18n!../nls/LinkDialog" + ], lang.hitch(this, function(i18n, TooltipDialog, registry){ + var _this = this; + this.tag = this.command == 'insertImage' ? 'img' : 'a'; + var messages = lang.delegate(i18n.getLocalization("dijit", "common", this.lang), + i18n.getLocalization("dijit._editor", "LinkDialog", this.lang)); + var dropDown = (this.dropDown = this.button.dropDown = new TooltipDialog({ + title: messages[this.command + "Title"], + execute: lang.hitch(this, "setValue"), + onOpen: function(){ + _this._onOpenDialog(); + TooltipDialog.prototype.onOpen.apply(this, arguments); + }, + onCancel: function(){ + setTimeout(lang.hitch(_this, "_onCloseDialog"),0); + } + })); + messages.urlRegExp = this.urlRegExp; + messages.id = registry.getUniqueId(this.editor.id); + this._uniqueId = messages.id; + this._setContent(dropDown.title + + "<div style='border-bottom: 1px black solid;padding-bottom:2pt;margin-bottom:4pt'></div>" + + string.substitute(this.linkDialogTemplate, messages)); + dropDown.startup(); + this._urlInput = registry.byId(this._uniqueId + "_urlInput"); + this._textInput = registry.byId(this._uniqueId + "_textInput"); + this._setButton = registry.byId(this._uniqueId + "_setButton"); + this.connect(registry.byId(this._uniqueId + "_cancelButton"), "onClick", function(){ + this.dropDown.onCancel(); + }); + if(this._urlInput){ + this.connect(this._urlInput, "onChange", "_checkAndFixInput"); + } + if(this._textInput){ + this.connect(this._textInput, "onChange", "_checkAndFixInput"); + } + + // Build up the dual check for http/https/file:, and mailto formats. + this._urlRegExp = new RegExp("^" + this.urlRegExp + "$", "i"); + this._emailRegExp = new RegExp("^" + this.emailRegExp + "$", "i"); + this._urlInput.isValid = lang.hitch(this, function(){ + // Function over-ride of isValid to test if the input matches a url or a mailto style link. + var value = this._urlInput.get("value"); + return this._urlRegExp.test(value) || this._emailRegExp.test(value); + }); + + // Listen for enter and execute if valid. + this.connect(dropDown.domNode, "onkeypress", function(e){ + if(e && e.charOrCode == keys.ENTER && + !e.shiftKey && !e.metaKey && !e.ctrlKey && !e.altKey){ + if(!this._setButton.get("disabled")){ + dropDown.onExecute(); + dropDown.execute(dropDown.get('value')); + } + } + }); + + callback(); + })); + }, + + _checkAndFixInput: function(){ + // summary: + // A function to listen for onChange events and test the input contents + // for valid information, such as valid urls with http/https/ftp and if + // not present, try and guess if the input url is relative or not, and if + // not, append http:// to it. Also validates other fields as determined by + // the internal _isValid function. + var self = this; + var url = this._urlInput.get("value"); + var fixupUrl = function(url){ + var appendHttp = false; + var appendMailto = false; + if(url && url.length > 1){ + url = lang.trim(url); + if(url.indexOf("mailto:") !== 0){ + if(url.indexOf("/") > 0){ + if(url.indexOf("://") === -1){ + // Check that it doesn't start with / or ./, which would + // imply 'target server relativeness' + if(url.charAt(0) !== '/' && url.indexOf("./") !== 0){ + if(self._hostRxp.test(url)){ + appendHttp = true; + } + } + } + }else if(self._userAtRxp.test(url)){ + // If it looks like a foo@, append a mailto. + appendMailto = true; + } + } + } + if(appendHttp){ + self._urlInput.set("value", "http://" + url); + } + if(appendMailto){ + self._urlInput.set("value", "mailto:" + url); + } + self._setButton.set("disabled", !self._isValid()); + }; + if(this._delayedCheck){ + clearTimeout(this._delayedCheck); + this._delayedCheck = null; + } + this._delayedCheck = setTimeout(function(){ + fixupUrl(url); + }, 250); + }, + + _connectTagEvents: function(){ + // summary: + // Over-ridable function that connects tag specific events. + this.editor.onLoadDeferred.addCallback(lang.hitch(this, function(){ + this.connect(this.editor.editNode, "ondblclick", this._onDblClick); + })); + }, + + _isValid: function(){ + // summary: + // Internal function to allow validating of the inputs + // for a link to determine if set should be disabled or not + // tags: + // protected + return this._urlInput.isValid() && this._textInput.isValid(); + }, + + _setContent: function(staticPanel){ + // summary: + // Helper for _initButton above. Not sure why it's a separate method. + this.dropDown.set({ + parserScope: "dojo", // make parser search for dojoType/data-dojo-type even if page is multi-version + content: staticPanel + }); + }, + + _checkValues: function(args){ + // summary: + // Function to check the values in args and 'fix' them up as needed. + // args: Object + // Content being set. + // tags: + // protected + if(args && args.urlInput){ + args.urlInput = args.urlInput.replace(/"/g, """); + } + return args; + }, + + setValue: function(args){ + // summary: + // Callback from the dialog when user presses "set" button. + // tags: + // private + //TODO: prevent closing popup if the text is empty + this._onCloseDialog(); + if(has("ie") < 9){ //see #4151 + var sel = rangeapi.getSelection(this.editor.window); + var range = sel.getRangeAt(0); + var a = range.endContainer; + if(a.nodeType === 3){ + // Text node, may be the link contents, so check parent. + // This plugin doesn't really support nested HTML elements + // in the link, it assumes all link content is text. + a = a.parentNode; + } + if(a && (a.nodeName && a.nodeName.toLowerCase() !== this.tag)){ + // Still nothing, one last thing to try on IE, as it might be 'img' + // and thus considered a control. + a = win.withGlobal(this.editor.window, + "getSelectedElement", selectionapi, [this.tag]); + } + if(a && (a.nodeName && a.nodeName.toLowerCase() === this.tag)){ + // Okay, we do have a match. IE, for some reason, sometimes pastes before + // instead of removing the targeted paste-over element, so we unlink the + // old one first. If we do not the <a> tag remains, but it has no content, + // so isn't readily visible (but is wrong for the action). + if(this.editor.queryCommandEnabled("unlink")){ + // Select all the link children, then unlink. The following insert will + // then replace the selected text. + win.withGlobal(this.editor.window, + "selectElementChildren", selectionapi, [a]); + this.editor.execCommand("unlink"); + } + } + } + // make sure values are properly escaped, etc. + args = this._checkValues(args); + this.editor.execCommand('inserthtml', + string.substitute(this.htmlTemplate, args)); + }, + + _onCloseDialog: function(){ + // summary: + // Handler for close event on the dialog + this.editor.focus(); + }, + + _getCurrentValues: function(a){ + // summary: + // Over-ride for getting the values to set in the dropdown. + // a: + // The anchor/link to process for data for the dropdown. + // tags: + // protected + var url, text, target; + if(a && a.tagName.toLowerCase() === this.tag){ + url = a.getAttribute('_djrealurl') || a.getAttribute('href'); + target = a.getAttribute('target') || "_self"; + text = a.textContent || a.innerText; + win.withGlobal(this.editor.window, "selectElement", selectionapi, [a, true]); + }else{ + text = win.withGlobal(this.editor.window, selectionapi.getSelectedText); + } + return {urlInput: url || '', textInput: text || '', targetSelect: target || ''}; //Object; + }, + + _onOpenDialog: function(){ + // summary: + // Handler for when the dialog is opened. + // If the caret is currently in a URL then populate the URL's info into the dialog. + var a; + if(has("ie") < 9){ + // IE is difficult to select the element in, using the range unified + // API seems to work reasonably well. + var sel = rangeapi.getSelection(this.editor.window); + var range = sel.getRangeAt(0); + a = range.endContainer; + if(a.nodeType === 3){ + // Text node, may be the link contents, so check parent. + // This plugin doesn't really support nested HTML elements + // in the link, it assumes all link content is text. + a = a.parentNode; + } + if(a && (a.nodeName && a.nodeName.toLowerCase() !== this.tag)){ + // Still nothing, one last thing to try on IE, as it might be 'img' + // and thus considered a control. + a = win.withGlobal(this.editor.window, + "getSelectedElement", selectionapi, [this.tag]); + } + }else{ + a = win.withGlobal(this.editor.window, + "getAncestorElement", selectionapi, [this.tag]); + } + this.dropDown.reset(); + this._setButton.set("disabled", true); + this.dropDown.set("value", this._getCurrentValues(a)); + }, + + _onDblClick: function(e){ + // summary: + // Function to define a behavior on double clicks on the element + // type this dialog edits to select it and pop up the editor + // dialog. + // e: Object + // The double-click event. + // tags: + // protected. + if(e && e.target){ + var t = e.target; + var tg = t.tagName? t.tagName.toLowerCase() : ""; + if(tg === this.tag && domAttr.get(t,"href")){ + var editor = this.editor; + + win.withGlobal(editor.window, + "selectElement", + selectionapi, [t]); + + editor.onDisplayChanged(); + + // Call onNormalizedDisplayChange() now, rather than on timer. + // On IE, when focus goes to the first <input> in the TooltipDialog, the editor loses it's selection. + // Later if onNormalizedDisplayChange() gets called via the timer it will disable the LinkDialog button + // (actually, all the toolbar buttons), at which point clicking the <input> will close the dialog, + // since (for unknown reasons) focus.js ignores disabled controls. + if(editor._updateTimer){ + clearTimeout(editor._updateTimer); + delete editor._updateTimer; + } + editor.onNormalizedDisplayChanged(); + + var button = this.button; + setTimeout(function(){ + // Focus shift outside the event handler. + // IE doesn't like focus changes in event handles. + button.set("disabled", false); + button.loadAndOpenDropDown().then(function(){ + if(button.dropDown.focus){ + button.dropDown.focus(); + } + }); + }, 10); + } + } + } +}); + +var ImgLinkDialog = declare("dijit._editor.plugins.ImgLinkDialog", [LinkDialog], { + // summary: + // This plugin extends LinkDialog and adds in a plugin for handling image links. + // provides the image link dialog. + // + // description: + // The command provided by this plugin is: + // * insertImage + + // linkDialogTemplate: [protected] String + // Over-ride for template since img dialog doesn't need target that anchor tags may. + linkDialogTemplate: [ + "<table><tr><td>", + "<label for='${id}_urlInput'>${url}</label>", + "</td><td>", + "<input dojoType='dijit.form.ValidationTextBox' regExp='${urlRegExp}' " + + "required='true' id='${id}_urlInput' name='urlInput' data-dojo-props='intermediateChanges:true'/>", + "</td></tr><tr><td>", + "<label for='${id}_textInput'>${text}</label>", + "</td><td>", + "<input data-dojo-type='dijit.form.ValidationTextBox' required='false' id='${id}_textInput' " + + "name='textInput' data-dojo-props='intermediateChanges:true'/>", + "</td></tr><tr><td>", + "</td><td>", + "</td></tr><tr><td colspan='2'>", + "<button data-dojo-type='dijit.form.Button' type='submit' id='${id}_setButton'>${set}</button>", + "<button data-dojo-type='dijit.form.Button' type='button' id='${id}_cancelButton'>${buttonCancel}</button>", + "</td></tr></table>" + ].join(""), + + // htmlTemplate: [protected] String + // String used for templating the <img> HTML to insert at the desired point. + htmlTemplate: "<img src=\"${urlInput}\" _djrealurl=\"${urlInput}\" alt=\"${textInput}\" />", + + // tag: [protected] String + // Tag used for the link type (img). + tag: "img", + + _getCurrentValues: function(img){ + // summary: + // Over-ride for getting the values to set in the dropdown. + // a: + // The anchor/link to process for data for the dropdown. + // tags: + // protected + var url, text; + if(img && img.tagName.toLowerCase() === this.tag){ + url = img.getAttribute('_djrealurl') || img.getAttribute('src'); + text = img.getAttribute('alt'); + win.withGlobal(this.editor.window, + "selectElement", selectionapi, [img, true]); + }else{ + text = win.withGlobal(this.editor.window, selectionapi.getSelectedText); + } + return {urlInput: url || '', textInput: text || ''}; //Object; + }, + + _isValid: function(){ + // summary: + // Over-ride for images. You can have alt text of blank, it is valid. + // tags: + // protected + return this._urlInput.isValid(); + }, + + _connectTagEvents: function(){ + // summary: + // Over-ridable function that connects tag specific events. + this.inherited(arguments); + this.editor.onLoadDeferred.addCallback(lang.hitch(this, function(){ + // Use onmousedown instead of onclick. Seems that IE eats the first onclick + // to wrap it in a selector box, then the second one acts as onclick. See #10420 + this.connect(this.editor.editNode, "onmousedown", this._selectTag); + })); + }, + + _selectTag: function(e){ + // summary: + // A simple event handler that lets me select an image if it is clicked on. + // makes it easier to select images in a standard way across browsers. Otherwise + // selecting an image for edit becomes difficult. + // e: Event + // The mousedown event. + // tags: + // private + if(e && e.target){ + var t = e.target; + var tg = t.tagName? t.tagName.toLowerCase() : ""; + if(tg === this.tag){ + win.withGlobal(this.editor.window, + "selectElement", + selectionapi, [t]); + } + } + }, + + _checkValues: function(args){ + // summary: + // Function to check the values in args and 'fix' them up as needed + // (special characters in the url or alt text) + // args: Object + // Content being set. + // tags: + // protected + if(args && args.urlInput){ + args.urlInput = args.urlInput.replace(/"/g, """); + } + if(args && args.textInput){ + args.textInput = args.textInput.replace(/"/g, """); + } + return args; + }, + + _onDblClick: function(e){ + // summary: + // Function to define a behavior on double clicks on the element + // type this dialog edits to select it and pop up the editor + // dialog. + // e: Object + // The double-click event. + // tags: + // protected. + if(e && e.target){ + var t = e.target; + var tg = t.tagName ? t.tagName.toLowerCase() : ""; + if(tg === this.tag && domAttr.get(t,"src")){ + var editor = this.editor; + + win.withGlobal(editor.window, + "selectElement", + selectionapi, [t]); + editor.onDisplayChanged(); + + // Call onNormalizedDisplayChange() now, rather than on timer. + // On IE, when focus goes to the first <input> in the TooltipDialog, the editor loses it's selection. + // Later if onNormalizedDisplayChange() gets called via the timer it will disable the LinkDialog button + // (actually, all the toolbar buttons), at which point clicking the <input> will close the dialog, + // since (for unknown reasons) focus.js ignores disabled controls. + if(editor._updateTimer){ + clearTimeout(editor._updateTimer); + delete editor._updateTimer; + } + editor.onNormalizedDisplayChanged(); + + var button = this.button; + setTimeout(function(){ + // Focus shift outside the event handler. + // IE doesn't like focus changes in event handles. + button.set("disabled", false); + button.loadAndOpenDropDown().then(function(){ + if(button.dropDown.focus){ + button.dropDown.focus(); + } + }); + }, 10); + } + } + } +}); + +// Register these plugins +_Plugin.registry["createLink"] = function(){ + return new LinkDialog({command: "createLink"}); +}; +_Plugin.registry["insertImage"] = function(){ + return new ImgLinkDialog({command: "insertImage"}); +}; + + +// Export both LinkDialog and ImgLinkDialog +LinkDialog.ImgLinkDialog = ImgLinkDialog; +return LinkDialog; +}); diff --git a/js/dojo-1.7.2/dijit/_editor/plugins/NewPage.js b/js/dojo-1.7.2/dijit/_editor/plugins/NewPage.js new file mode 100644 index 0000000..be837ae --- /dev/null +++ b/js/dojo-1.7.2/dijit/_editor/plugins/NewPage.js @@ -0,0 +1,84 @@ +//>>built +define("dijit/_editor/plugins/NewPage", [ + "dojo/_base/declare", // declare + "dojo/i18n", // i18n.getLocalization + "dojo/_base/lang", // lang.hitch + "../_Plugin", + "../../form/Button", + "dojo/i18n!../nls/commands" +], function(declare, i18n, lang, _Plugin, Button){ + +/*===== + var _Plugin = dijit._editor._Plugin; +=====*/ + +// module: +// dijit/_editor/plugins/NewPage +// summary: +// This plugin provides a simple 'new page' capability. In other +// words, set content to some default user defined string. + + +var NewPage = declare("dijit._editor.plugins.NewPage",_Plugin,{ + // summary: + // This plugin provides a simple 'new page' capability. In other + // words, set content to some default user defined string. + + // content: [public] String + // The default content to insert into the editor as the new page. + // The default is the <br> tag, a single blank line. + content: "<br>", + + _initButton: function(){ + // summary: + // Over-ride for creation of the Print button. + var strings = i18n.getLocalization("dijit._editor", "commands"), + editor = this.editor; + this.button = new Button({ + label: strings["newPage"], + dir: editor.dir, + lang: editor.lang, + showLabel: false, + iconClass: this.iconClassPrefix + " " + this.iconClassPrefix + "NewPage", + tabIndex: "-1", + onClick: lang.hitch(this, "_newPage") + }); + }, + + setEditor: function(/*dijit.Editor*/ editor){ + // summary: + // Tell the plugin which Editor it is associated with. + // editor: Object + // The editor object to attach the newPage capability to. + this.editor = editor; + this._initButton(); + }, + + updateState: function(){ + // summary: + // Over-ride for button state control for disabled to work. + this.button.set("disabled", this.get("disabled")); + }, + + _newPage: function(){ + // summary: + // Function to set the content to blank. + // tags: + // private + this.editor.beginEditing(); + this.editor.set("value", this.content); + this.editor.endEditing(); + this.editor.focus(); + } +}); + +// Register this plugin. +// For back-compat accept "newpage" (all lowercase) too, remove in 2.0 +_Plugin.registry["newPage"] = _Plugin.registry["newpage"] = function(args){ + return new NewPage({ + content: ("content" in args)?args.content:"<br>" + }); +}; + +return NewPage; +}); diff --git a/js/dojo-1.7.2/dijit/_editor/plugins/Print.js b/js/dojo-1.7.2/dijit/_editor/plugins/Print.js new file mode 100644 index 0000000..16058ba --- /dev/null +++ b/js/dojo-1.7.2/dijit/_editor/plugins/Print.js @@ -0,0 +1,130 @@ +//>>built +define("dijit/_editor/plugins/Print", [ + "dojo/_base/declare", // declare + "dojo/i18n", // i18n.getLocalization + "dojo/_base/lang", // lang.hitch + "dojo/_base/sniff", // has("chrome") has("opera") + "../../focus", // focus.focus() + "../_Plugin", + "../../form/Button", + "dojo/i18n!../nls/commands" +], function(declare, i18n, lang, has, focus, _Plugin, Button){ + +/*===== + var _Plugin = dijit._editor._Plugin; +=====*/ + +// module: +// dijit/_editor/plugins/Print +// summary: +// This plugin provides Print capability to the editor. When +// clicked, the document in the editor frame will be printed. + + +var Print = declare("dijit._editor.plugins.Print",_Plugin,{ + // summary: + // This plugin provides Print capability to the editor. When + // clicked, the document in the editor frame will be printed. + + _initButton: function(){ + // summary: + // Over-ride for creation of the Print button. + var strings = i18n.getLocalization("dijit._editor", "commands"), + editor = this.editor; + this.button = new Button({ + label: strings["print"], + dir: editor.dir, + lang: editor.lang, + showLabel: false, + iconClass: this.iconClassPrefix + " " + this.iconClassPrefix + "Print", + tabIndex: "-1", + onClick: lang.hitch(this, "_print") + }); + }, + + setEditor: function(/*dijit.Editor*/ editor){ + // summary: + // Tell the plugin which Editor it is associated with. + // editor: Object + // The editor object to attach the print capability to. + this.editor = editor; + this._initButton(); + + // Set up a check that we have a print function + // and disable button if we do not. + this.editor.onLoadDeferred.addCallback( + lang.hitch(this, function(){ + if(!this.editor.iframe.contentWindow["print"]){ + this.button.set("disabled", true); + } + }) + ); + }, + + updateState: function(){ + // summary: + // Over-ride for button state control for disabled to work. + var disabled = this.get("disabled"); + if(!this.editor.iframe.contentWindow["print"]){ + disabled = true; + } + this.button.set("disabled", disabled); + }, + + _print: function(){ + // summary: + // Function to trigger printing of the editor document + // tags: + // private + var edFrame = this.editor.iframe; + if(edFrame.contentWindow["print"]){ + // IE requires the frame to be focused for + // print to work, but since this is okay for all + // no special casing. + if(!has("opera") && !has("chrome")){ + focus.focus(edFrame); + edFrame.contentWindow.print(); + }else{ + // Neither Opera nor Chrome 3 et you print single frames. + // So, open a new 'window', print it, and close it. + // Also, can't use size 0x0, have to use 1x1 + var edDoc = this.editor.document; + var content = this.editor.get("value"); + content = "<html><head><meta http-equiv='Content-Type' " + + "content='text/html; charset='UTF-8'></head><body>" + + content + "</body></html>"; + var win = window.open("javascript: ''", + "", + "status=0,menubar=0,location=0,toolbar=0," + + "width=1,height=1,resizable=0,scrollbars=0"); + win.document.open(); + win.document.write(content); + win.document.close(); + + var styleNodes = edDoc.getElementsByTagName("style"); + if(styleNodes){ + // Clone over any editor view styles, since we can't print the iframe + // directly. + var i; + for(i = 0; i < styleNodes.length; i++){ + var style = styleNodes[i].innerHTML; + var sNode = win.document.createElement("style"); + sNode.appendChild(win.document.createTextNode(style)); + win.document.getElementsByTagName("head")[0].appendChild(sNode); + } + } + win.print(); + win.close(); + } + } + } +}); + +// Register this plugin. +_Plugin.registry["print"] = function(){ + return new Print({command: "print"}); +}; + + +return Print; +}); diff --git a/js/dojo-1.7.2/dijit/_editor/plugins/TabIndent.js b/js/dojo-1.7.2/dijit/_editor/plugins/TabIndent.js new file mode 100644 index 0000000..01032fa --- /dev/null +++ b/js/dojo-1.7.2/dijit/_editor/plugins/TabIndent.js @@ -0,0 +1,70 @@ +//>>built +define("dijit/_editor/plugins/TabIndent", [ + "dojo/_base/declare", // declare + "dojo/_base/kernel", // kernel.experimental + "../_Plugin", + "../../form/ToggleButton" +], function(declare, kernel, _Plugin, ToggleButton){ + +/*===== + var _Plugin = dijit._editor._Plugin; +=====*/ + + // module: + // dijit/_editor/plugins/TabIndent + // summary: + // This plugin is used to allow the use of the tab and shift-tab keys + // to indent/outdent list items. This overrides the default behavior + // of moving focus from/to the toolbar + + + kernel.experimental("dijit._editor.plugins.TabIndent"); + + + var TabIndent = declare("dijit._editor.plugins.TabIndent", _Plugin, { + // summary: + // This plugin is used to allow the use of the tab and shift-tab keys + // to indent/outdent list items. This overrides the default behavior + // of moving focus from/to the toolbar + + // Override _Plugin.useDefaultCommand... processing is handled by this plugin, not by dijit.Editor. + useDefaultCommand: false, + + // Override _Plugin.buttonClass to use a ToggleButton for this plugin rather than a vanilla Button + buttonClass: ToggleButton, + + command: "tabIndent", + + _initButton: function(){ + // Override _Plugin._initButton() to setup listener on button click + this.inherited(arguments); + + var e = this.editor; + this.connect(this.button, "onChange", function(val){ + e.set("isTabIndent", val); + }); + + // Set initial checked state of button based on Editor.isTabIndent + this.updateState(); + }, + + updateState: function(){ + // Overrides _Plugin.updateState(). + // Ctrl-m in the editor will switch tabIndent mode on/off, so we need to react to that. + var disabled = this.get("disabled"); + this.button.set("disabled", disabled); + if(disabled){ + return; + } + this.button.set('checked', this.editor.isTabIndent, false); + } + }); + + // Register this plugin. + _Plugin.registry["tabIndent"] = function(){ + return new TabIndent({command: "tabIndent"}); + }; + + + return TabIndent; +}); diff --git a/js/dojo-1.7.2/dijit/_editor/plugins/TextColor.js b/js/dojo-1.7.2/dijit/_editor/plugins/TextColor.js new file mode 100644 index 0000000..b93a716 --- /dev/null +++ b/js/dojo-1.7.2/dijit/_editor/plugins/TextColor.js @@ -0,0 +1,120 @@ +//>>built +define("dijit/_editor/plugins/TextColor", [ + "require", + "dojo/colors", // colors.fromRgb + "dojo/_base/declare", // declare + "dojo/_base/lang", + "../_Plugin", + "../../form/DropDownButton" +], function(require, colors, declare, lang, _Plugin, DropDownButton){ + +/*===== + var _Plugin = dijit._editor._Plugin; +=====*/ + +// module: +// dijit/_editor/plugins/TextColor +// summary: +// This plugin provides dropdown color pickers for setting text color and background color + + +var TextColor = declare("dijit._editor.plugins.TextColor", _Plugin, { + // summary: + // This plugin provides dropdown color pickers for setting text color and background color + // + // description: + // The commands provided by this plugin are: + // * foreColor - sets the text color + // * hiliteColor - sets the background color + + // Override _Plugin.buttonClass to use DropDownButton (with ColorPalette) to control this plugin + buttonClass: DropDownButton, + + // useDefaultCommand: Boolean + // False as we do not use the default editor command/click behavior. + useDefaultCommand: false, + + _initButton: function(){ + this.inherited(arguments); + + // Setup to lazy load ColorPalette first time the button is clicked + var self = this; + this.button.loadDropDown = function(callback){ + require(["../../ColorPalette"], lang.hitch(this, function(ColorPalette){ + this.dropDown = new ColorPalette({ + value: self.value, + onChange: function(color){ + self.editor.execCommand(self.command, color); + } + }); + callback(); + })); + }; + }, + + updateState: function(){ + // summary: + // Overrides _Plugin.updateState(). This updates the ColorPalette + // to show the color of the currently selected text. + // tags: + // protected + + var _e = this.editor; + var _c = this.command; + if(!_e || !_e.isLoaded || !_c.length){ + return; + } + + if(this.button){ + var disabled = this.get("disabled"); + this.button.set("disabled", disabled); + if(disabled){ return; } + + var value; + try{ + value = _e.queryCommandValue(_c)|| ""; + }catch(e){ + //Firefox may throw error above if the editor is just loaded, ignore it + value = ""; + } + } + + if(value == ""){ + value = "#000000"; + } + if(value == "transparent"){ + value = "#ffffff"; + } + + if(typeof value == "string"){ + //if RGB value, convert to hex value + if(value.indexOf("rgb")> -1){ + value = colors.fromRgb(value).toHex(); + } + }else{ //it's an integer(IE returns an MS access #) + value =((value & 0x0000ff)<< 16)|(value & 0x00ff00)|((value & 0xff0000)>>> 16); + value = value.toString(16); + value = "#000000".slice(0, 7 - value.length)+ value; + + } + + this.value = value; + + var dropDown = this.button.dropDown; + if(dropDown && value !== dropDown.get('value')){ + dropDown.set('value', value, false); + } + } +}); + +// Register this plugin. +_Plugin.registry["foreColor"] = function(){ + return new TextColor({command: "foreColor"}); +}; +_Plugin.registry["hiliteColor"] = function(){ + return new TextColor({command: "hiliteColor"}); +}; + + +return TextColor; +}); diff --git a/js/dojo-1.7.2/dijit/_editor/plugins/ToggleDir.js b/js/dojo-1.7.2/dijit/_editor/plugins/ToggleDir.js new file mode 100644 index 0000000..df46f0a --- /dev/null +++ b/js/dojo-1.7.2/dijit/_editor/plugins/ToggleDir.js @@ -0,0 +1,78 @@ +//>>built +define("dijit/_editor/plugins/ToggleDir", [ + "dojo/_base/declare", // declare + "dojo/dom-style", // domStyle.getComputedStyle + "dojo/_base/kernel", // kernel.experimental + "dojo/_base/lang", // lang.hitch + "../_Plugin", + "../../form/ToggleButton" +], function(declare, domStyle, kernel, lang, _Plugin, ToggleButton){ + +/*===== + var _Plugin = dijit._editor._Plugin; +=====*/ + + // module: + // dijit/_editor/plugins/ToggleDir + // summary: + // This plugin is used to toggle direction of the edited document, + // independent of what direction the whole page is. + + + kernel.experimental("dijit._editor.plugins.ToggleDir"); + + var ToggleDir = declare("dijit._editor.plugins.ToggleDir", _Plugin, { + // summary: + // This plugin is used to toggle direction of the edited document, + // independent of what direction the whole page is. + + // Override _Plugin.useDefaultCommand: processing is done in this plugin + // rather than by sending commands to the Editor + useDefaultCommand: false, + + command: "toggleDir", + + // Override _Plugin.buttonClass to use a ToggleButton for this plugin rather than a vanilla Button + buttonClass: ToggleButton, + + _initButton: function(){ + // Override _Plugin._initButton() to setup handler for button click events. + this.inherited(arguments); + this.editor.onLoadDeferred.addCallback(lang.hitch(this, function(){ + var editDoc = this.editor.editorObject.contentWindow.document.documentElement; + //IE direction has to toggle on the body, not document itself. + //If you toggle just the document, things get very strange in the + //view. But, the nice thing is this works for all supported browsers. + editDoc = editDoc.getElementsByTagName("body")[0]; + var isLtr = domStyle.getComputedStyle(editDoc).direction == "ltr"; + this.button.set("checked", !isLtr); + this.connect(this.button, "onChange", "_setRtl"); + })); + }, + + updateState: function(){ + // summary: + // Over-ride for button state control for disabled to work. + this.button.set("disabled", this.get("disabled")); + }, + + _setRtl: function(rtl){ + // summary: + // Handler for button click events, to switch the text direction of the editor + var dir = "ltr"; + if(rtl){ + dir = "rtl"; + } + var editDoc = this.editor.editorObject.contentWindow.document.documentElement; + editDoc = editDoc.getElementsByTagName("body")[0]; + editDoc.dir/*html node*/ = dir; + } + }); + + // Register this plugin. + _Plugin.registry["toggleDir"] = function(){ + return new ToggleDir({command: "toggleDir"}); + }; + + return ToggleDir; +}); diff --git a/js/dojo-1.7.2/dijit/_editor/plugins/ViewSource.js b/js/dojo-1.7.2/dijit/_editor/plugins/ViewSource.js new file mode 100644 index 0000000..f1115c1 --- /dev/null +++ b/js/dojo-1.7.2/dijit/_editor/plugins/ViewSource.js @@ -0,0 +1,565 @@ +//>>built +define("dijit/_editor/plugins/ViewSource", [ + "dojo/_base/array", // array.forEach + "dojo/_base/declare", // declare + "dojo/dom-attr", // domAttr.set + "dojo/dom-construct", // domConstruct.create domConstruct.place + "dojo/dom-geometry", // domGeometry.setMarginBox domGeometry.position + "dojo/dom-style", // domStyle.set + "dojo/_base/event", // event.stop + "dojo/i18n", // i18n.getLocalization + "dojo/keys", // keys.F12 + "dojo/_base/lang", // lang.hitch + "dojo/on", // on() + "dojo/_base/sniff", // has("ie") has("webkit") + "dojo/_base/window", // win.body win.global + "dojo/window", // winUtils.getBox + "../../focus", // focus.focus() + "../_Plugin", + "../../form/ToggleButton", + "../..", // dijit._scopeName + "../../registry", // registry.getEnclosingWidget() + "dojo/i18n!../nls/commands" +], function(array, declare, domAttr, domConstruct, domGeometry, domStyle, event, i18n, keys, lang, on, has, win, + winUtils, focus, _Plugin, ToggleButton, dijit, registry){ + +/*===== + var _Plugin = dijit._editor._Plugin; +=====*/ + +// module: +// dijit/_editor/plugins/ViewSource +// summary: +// This plugin provides a simple view source capability. + + +var ViewSource = declare("dijit._editor.plugins.ViewSource",_Plugin, { + // summary: + // This plugin provides a simple view source capability. When view + // source mode is enabled, it disables all other buttons/plugins on the RTE. + // It also binds to the hotkey: CTRL-SHIFT-F11 for toggling ViewSource mode. + + // stripScripts: [public] Boolean + // Boolean flag used to indicate if script tags should be stripped from the document. + // Defaults to true. + stripScripts: true, + + // stripComments: [public] Boolean + // Boolean flag used to indicate if comment tags should be stripped from the document. + // Defaults to true. + stripComments: true, + + // stripComments: [public] Boolean + // Boolean flag used to indicate if iframe tags should be stripped from the document. + // Defaults to true. + stripIFrames: true, + + // readOnly: [const] Boolean + // Boolean flag used to indicate if the source view should be readonly or not. + // Cannot be changed after initialization of the plugin. + // Defaults to false. + readOnly: false, + + // _fsPlugin: [private] Object + // Reference to a registered fullscreen plugin so that viewSource knows + // how to scale. + _fsPlugin: null, + + toggle: function(){ + // summary: + // Function to allow programmatic toggling of the view. + + // For Webkit, we have to focus a very particular way. + // when swapping views, otherwise focus doesn't shift right + // but can't focus this way all the time, only for VS changes. + // If we did it all the time, buttons like bold, italic, etc + // break. + if(has("webkit")){this._vsFocused = true;} + this.button.set("checked", !this.button.get("checked")); + + }, + + _initButton: function(){ + // summary: + // Over-ride for creation of the resize button. + var strings = i18n.getLocalization("dijit._editor", "commands"), + editor = this.editor; + this.button = new ToggleButton({ + label: strings["viewSource"], + dir: editor.dir, + lang: editor.lang, + showLabel: false, + iconClass: this.iconClassPrefix + " " + this.iconClassPrefix + "ViewSource", + tabIndex: "-1", + onChange: lang.hitch(this, "_showSource") + }); + + // IE 7 has a horrible bug with zoom, so we have to create this node + // to cross-check later. Sigh. + if(has("ie") == 7){ + this._ieFixNode = domConstruct.create("div", { + style: { + opacity: "0", + zIndex: "-1000", + position: "absolute", + top: "-1000px" + } + }, win.body()); + } + // Make sure readonly mode doesn't make the wrong cursor appear over the button. + this.button.set("readOnly", false); + }, + + + setEditor: function(/*dijit.Editor*/ editor){ + // summary: + // Tell the plugin which Editor it is associated with. + // editor: Object + // The editor object to attach the print capability to. + this.editor = editor; + this._initButton(); + + this.editor.addKeyHandler(keys.F12, true, true, lang.hitch(this, function(e){ + // Move the focus before switching + // It'll focus back. Hiding a focused + // node causes issues. + this.button.focus(); + this.toggle(); + event.stop(e); + + // Call the focus shift outside of the handler. + setTimeout(lang.hitch(this, function(){ + // We over-ride focus, so we just need to call. + this.editor.focus(); + }), 100); + })); + }, + + _showSource: function(source){ + // summary: + // Function to toggle between the source and RTE views. + // source: boolean + // Boolean value indicating if it should be in source mode or not. + // tags: + // private + var ed = this.editor; + var edPlugins = ed._plugins; + var html; + this._sourceShown = source; + var self = this; + try{ + if(!this.sourceArea){ + this._createSourceView(); + } + if(source){ + // Update the QueryCommandEnabled function to disable everything but + // the source view mode. Have to over-ride a function, then kick all + // plugins to check their state. + ed._sourceQueryCommandEnabled = ed.queryCommandEnabled; + ed.queryCommandEnabled = function(cmd){ + return cmd.toLowerCase() === "viewsource"; + }; + this.editor.onDisplayChanged(); + html = ed.get("value"); + html = this._filter(html); + ed.set("value", html); + array.forEach(edPlugins, function(p){ + // Turn off any plugins not controlled by queryCommandenabled. + if(!(p instanceof ViewSource)){ + p.set("disabled", true) + } + }); + + // We actually do need to trap this plugin and adjust how we + // display the textarea. + if(this._fsPlugin){ + this._fsPlugin._getAltViewNode = function(){ + return self.sourceArea; + }; + } + + this.sourceArea.value = html; + + // Since neither iframe nor textarea have margin, border, or padding, + // just set sizes equal + this.sourceArea.style.height = ed.iframe.style.height; + this.sourceArea.style.width = ed.iframe.style.width; + domStyle.set(ed.iframe, "display", "none"); + domStyle.set(this.sourceArea, { + display: "block" + }); + + var resizer = function(){ + // function to handle resize events. + // Will check current VP and only resize if + // different. + var vp = winUtils.getBox(); + + if("_prevW" in this && "_prevH" in this){ + // No actual size change, ignore. + if(vp.w === this._prevW && vp.h === this._prevH){ + return; + }else{ + this._prevW = vp.w; + this._prevH = vp.h; + } + }else{ + this._prevW = vp.w; + this._prevH = vp.h; + } + if(this._resizer){ + clearTimeout(this._resizer); + delete this._resizer; + } + // Timeout it to help avoid spamming resize on IE. + // Works for all browsers. + this._resizer = setTimeout(lang.hitch(this, function(){ + delete this._resizer; + this._resize(); + }), 10); + }; + this._resizeHandle = on(window, "resize", lang.hitch(this, resizer)); + + //Call this on a delay once to deal with IE glitchiness on initial size. + setTimeout(lang.hitch(this, this._resize), 100); + + //Trigger a check for command enablement/disablement. + this.editor.onNormalizedDisplayChanged(); + + this.editor.__oldGetValue = this.editor.getValue; + this.editor.getValue = lang.hitch(this, function(){ + var txt = this.sourceArea.value; + txt = this._filter(txt); + return txt; + }); + }else{ + // First check that we were in source view before doing anything. + // corner case for being called with a value of false and we hadn't + // actually been in source display mode. + if(!ed._sourceQueryCommandEnabled){ + return; + } + this._resizeHandle.remove(); + delete this._resizeHandle; + + if(this.editor.__oldGetValue){ + this.editor.getValue = this.editor.__oldGetValue; + delete this.editor.__oldGetValue; + } + + // Restore all the plugin buttons state. + ed.queryCommandEnabled = ed._sourceQueryCommandEnabled; + if(!this._readOnly){ + html = this.sourceArea.value; + html = this._filter(html); + ed.beginEditing(); + ed.set("value", html); + ed.endEditing(); + } + + array.forEach(edPlugins, function(p){ + // Turn back on any plugins we turned off. + p.set("disabled", false); + }); + + domStyle.set(this.sourceArea, "display", "none"); + domStyle.set(ed.iframe, "display", "block"); + delete ed._sourceQueryCommandEnabled; + + //Trigger a check for command enablement/disablement. + this.editor.onDisplayChanged(); + } + // Call a delayed resize to wait for some things to display in header/footer. + setTimeout(lang.hitch(this, function(){ + // Make resize calls. + var parent = ed.domNode.parentNode; + if(parent){ + var container = registry.getEnclosingWidget(parent); + if(container && container.resize){ + container.resize(); + } + } + ed.resize(); + }), 300); + }catch(e){ + console.log(e); + } + }, + + updateState: function(){ + // summary: + // Over-ride for button state control for disabled to work. + this.button.set("disabled", this.get("disabled")); + }, + + _resize: function(){ + // summary: + // Internal function to resize the source view + // tags: + // private + var ed = this.editor; + var tbH = ed.getHeaderHeight(); + var fH = ed.getFooterHeight(); + var eb = domGeometry.position(ed.domNode); + + // Styles are now applied to the internal source container, so we have + // to subtract them off. + var containerPadding = domGeometry.getPadBorderExtents(ed.iframe.parentNode); + var containerMargin = domGeometry.getMarginExtents(ed.iframe.parentNode); + + var extents = domGeometry.getPadBorderExtents(ed.domNode); + var edb = { + w: eb.w - extents.w, + h: eb.h - (tbH + extents.h + + fH) + }; + + // Fullscreen gets odd, so we need to check for the FS plugin and + // adapt. + if(this._fsPlugin && this._fsPlugin.isFullscreen){ + //Okay, probably in FS, adjust. + var vp = winUtils.getBox(); + edb.w = (vp.w - extents.w); + edb.h = (vp.h - (tbH + extents.h + fH)); + } + + if(has("ie")){ + // IE is always off by 2px, so we have to adjust here + // Note that IE ZOOM is broken here. I can't get + //it to scale right. + edb.h -= 2; + } + + // IE has a horrible zoom bug. So, we have to try and account for + // it and fix up the scaling. + if(this._ieFixNode){ + var _ie7zoom = -this._ieFixNode.offsetTop / 1000; + edb.w = Math.floor((edb.w + 0.9) / _ie7zoom); + edb.h = Math.floor((edb.h + 0.9) / _ie7zoom); + } + + domGeometry.setMarginBox(this.sourceArea, { + w: edb.w - (containerPadding.w + containerMargin.w), + h: edb.h - (containerPadding.h + containerMargin.h) + }); + + // Scale the parent container too in this case. + domGeometry.setMarginBox(ed.iframe.parentNode, { + h: edb.h + }); + }, + + _createSourceView: function(){ + // summary: + // Internal function for creating the source view area. + // tags: + // private + var ed = this.editor; + var edPlugins = ed._plugins; + this.sourceArea = domConstruct.create("textarea"); + if(this.readOnly){ + domAttr.set(this.sourceArea, "readOnly", true); + this._readOnly = true; + } + domStyle.set(this.sourceArea, { + padding: "0px", + margin: "0px", + borderWidth: "0px", + borderStyle: "none" + }); + domConstruct.place(this.sourceArea, ed.iframe, "before"); + + if(has("ie") && ed.iframe.parentNode.lastChild !== ed.iframe){ + // There's some weirdo div in IE used for focus control + // But is messed up scaling the textarea if we don't config + // it some so it doesn't have a varying height. + domStyle.set(ed.iframe.parentNode.lastChild,{ + width: "0px", + height: "0px", + padding: "0px", + margin: "0px", + borderWidth: "0px", + borderStyle: "none" + }); + } + + // We also need to take over editor focus a bit here, so that focus calls to + // focus the editor will focus to the right node when VS is active. + ed._viewsource_oldFocus = ed.focus; + var self = this; + ed.focus = function(){ + if(self._sourceShown){ + self.setSourceAreaCaret(); + }else{ + try{ + if(this._vsFocused){ + delete this._vsFocused; + // Must focus edit node in this case (webkit only) or + // focus doesn't shift right, but in normal + // cases we focus with the regular function. + focus.focus(ed.editNode); + }else{ + ed._viewsource_oldFocus(); + } + }catch(e){ + console.log(e); + } + } + }; + + var i, p; + for(i = 0; i < edPlugins.length; i++){ + // We actually do need to trap this plugin and adjust how we + // display the textarea. + p = edPlugins[i]; + if(p && (p.declaredClass === "dijit._editor.plugins.FullScreen" || + p.declaredClass === (dijit._scopeName + + "._editor.plugins.FullScreen"))){ + this._fsPlugin = p; + break; + } + } + if(this._fsPlugin){ + // Found, we need to over-ride the alt-view node function + // on FullScreen with our own, chain up to parent call when appropriate. + this._fsPlugin._viewsource_getAltViewNode = this._fsPlugin._getAltViewNode; + this._fsPlugin._getAltViewNode = function(){ + return self._sourceShown?self.sourceArea:this._viewsource_getAltViewNode(); + }; + } + + // Listen to the source area for key events as well, as we need to be able to hotkey toggle + // it from there too. + this.connect(this.sourceArea, "onkeydown", lang.hitch(this, function(e){ + if(this._sourceShown && e.keyCode == keys.F12 && e.ctrlKey && e.shiftKey){ + this.button.focus(); + this.button.set("checked", false); + setTimeout(lang.hitch(this, function(){ed.focus();}), 100); + event.stop(e); + } + })); + }, + + _stripScripts: function(html){ + // summary: + // Strips out script tags from the HTML used in editor. + // html: String + // The HTML to filter + // tags: + // private + if(html){ + // Look for closed and unclosed (malformed) script attacks. + html = html.replace(/<\s*script[^>]*>((.|\s)*?)<\\?\/\s*script\s*>/ig, ""); + html = html.replace(/<\s*script\b([^<>]|\s)*>?/ig, ""); + html = html.replace(/<[^>]*=(\s|)*[("|')]javascript:[^$1][(\s|.)]*[$1][^>]*>/ig, ""); + } + return html; + }, + + _stripComments: function(html){ + // summary: + // Strips out comments from the HTML used in editor. + // html: String + // The HTML to filter + // tags: + // private + if(html){ + html = html.replace(/<!--(.|\s){1,}?-->/g, ""); + } + return html; + }, + + _stripIFrames: function(html){ + // summary: + // Strips out iframe tags from the content, to avoid iframe script + // style injection attacks. + // html: String + // The HTML to filter + // tags: + // private + if(html){ + html = html.replace(/<\s*iframe[^>]*>((.|\s)*?)<\\?\/\s*iframe\s*>/ig, ""); + } + return html; + }, + + _filter: function(html){ + // summary: + // Internal function to perform some filtering on the HTML. + // html: String + // The HTML to filter + // tags: + // private + if(html){ + if(this.stripScripts){ + html = this._stripScripts(html); + } + if(this.stripComments){ + html = this._stripComments(html); + } + if(this.stripIFrames){ + html = this._stripIFrames(html); + } + } + return html; + }, + + setSourceAreaCaret: function(){ + // summary: + // Internal function to set the caret in the sourceArea + // to 0x0 + var global = win.global; + var elem = this.sourceArea; + focus.focus(elem); + if(this._sourceShown && !this.readOnly){ + if(has("ie")){ + if(this.sourceArea.createTextRange){ + var range = elem.createTextRange(); + range.collapse(true); + range.moveStart("character", -99999); // move to 0 + range.moveStart("character", 0); // delta from 0 is the correct position + range.moveEnd("character", 0); + range.select(); + } + }else if(global.getSelection){ + if(elem.setSelectionRange){ + elem.setSelectionRange(0,0); + } + } + } + }, + + destroy: function(){ + // summary: + // Over-ride to remove the node used to correct for IE's + // zoom bug. + if(this._ieFixNode){ + win.body().removeChild(this._ieFixNode); + } + if(this._resizer){ + clearTimeout(this._resizer); + delete this._resizer; + } + if(this._resizeHandle){ + this._resizeHandle.remove(); + delete this._resizeHandle; + } + this.inherited(arguments); + } +}); + +// Register this plugin. +// For back-compat accept "viewsource" (all lowercase) too, remove in 2.0 +_Plugin.registry["viewSource"] = _Plugin.registry["viewsource"] = function(args){ + return new ViewSource({ + readOnly: ("readOnly" in args)?args.readOnly:false, + stripComments: ("stripComments" in args)?args.stripComments:true, + stripScripts: ("stripScripts" in args)?args.stripScripts:true, + stripIFrames: ("stripIFrames" in args)?args.stripIFrames:true + }); +}; + + + + +return ViewSource; +}); |
