require({cache:{ 'dijit/_editor/plugins/BuxEnterKeyHandling':function(){ define("dijit/_editor/plugins/BuxEnterKeyHandling", [ "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", "../BuxRichText", "../range", "../selection" ], function(declare, domConstruct, event, keys, lang, has, win, winUtils, _Plugin, BuxRichText, 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 // | second line of first paragraph // | second paragraph // // will generate: // // |

// | first paragraph // |
// | second line of first paragraph // |

// |

// | second paragraph // |

// // 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 // | two // | three // | // | four // | five // | six // // It will appear on the screen as two 'paragraphs' of three lines each. Markupwise, this generates: // // BR: // | one
// | two
// | three
// |
// | four
// | five
// | six
// // DIV: // |
one
// |
two
// |
three
// |
 
// |
four
// |
five
// |
six
// 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.then(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
, so just use it. var ne = lang.mixin({},e); ne.shiftKey = true; if(!this.handleEnterKey(ne)){ event.stop(e); } } }); if(has("ie") >= 9){ this.connect(editor.document, "onpaste", function(e){ setTimeout(dojo.hitch(this, function(){ // Use the old range/selection code to kick IE 9 into updating // its range by moving it back, then forward, one 'character'. var r = this.editor.document.selection.createRange(); r.move('character',-1); r.select(); r.move('character',1); r.select(); }),0); }); } 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', selectionapi, ['LI']); if(!liparent){ // circulate the undo detection code by calling BuxRichText::execCommand directly BuxRichText.prototype.execCommand.call(this.editor, 'formatblock',this.blockNodeForEnter); // set the innerHTML of the new block node var block = win.withGlobal(this.editor.window, 'getAncestorElement', selectionapi, [this.blockNodeForEnter]); if(block){ block.innerHTML=this.bogusHtmlContent; if(has("ie") <= 9){ // 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
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 BuxRichText.prototype.execCommand.call(this.editor, 'inserthtml', '
'); } } 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
, 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|]*\bclass=['"]Apple-style-span['"][^>]*>(\s| | |\xA0)<\/span>)?(
)?$/.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{ BuxRichText.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
, 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
, 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
, 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; } } }); }); }, 'dijit/BuxEditor':function(){ define("dijit/BuxEditor", [ "dojo/_base/array", // array.forEach "dojo/_base/declare", // declare "dojo/_base/Deferred", // Deferred "dojo/i18n", // i18n.getLocalization "dojo/dom-attr", // domAttr.set "dojo/dom-class", // domClass.add "dojo/dom-geometry", "dojo/dom-style", // domStyle.set, get "dojo/_base/event", // event.stop "dojo/keys", // keys.F1 keys.F15 keys.TAB "dojo/_base/lang", // lang.getObject lang.hitch "dojo/_base/sniff", // has("ie") has("mac") has("webkit") "dojo/string", // string.substitute "dojo/topic", // topic.publish() "dojo/_base/window", // win.withGlobal "./_base/focus", // dijit.getBookmark() "./_Container", "./Toolbar", "./ToolbarSeparator", "./layout/_LayoutWidget", "./form/ToggleButton", "./_editor/_Plugin", "./_editor/plugins/BuxEnterKeyHandling", "./_editor/html", "./_editor/range", "./_editor/BuxRichText", ".", // dijit._scopeName "dojo/i18n!./_editor/nls/commands" ], function(array, declare, Deferred, i18n, domAttr, domClass, domGeometry, domStyle, event, keys, lang, has, string, topic, win, focusBase, _Container, Toolbar, ToolbarSeparator, _LayoutWidget, ToggleButton, _Plugin, BuxEnterKeyHandling, html, rangeapi, BuxRichText, dijit){ // module: // dijit/BuxEditor // summary: // A rich text Editing widget var Editor = declare("dijit.BuxEditor", BuxRichText, { // summary: // A rich text Editing widget // // description: // This widget provides basic WYSIWYG editing features, based on the browser's // underlying rich text editing capability, accompanied by a toolbar (`dijit.Toolbar`). // A plugin model is available to extend the editor's capabilities as well as the // the options available in the toolbar. Content generation may vary across // browsers, and clipboard operations may have different results, to name // a few limitations. Note: this widget should not be used with the HTML // <TEXTAREA> tag -- see dijit._editor.BuxRichText for details. // plugins: [const] Object[] // A list of plugin names (as strings) or instances (as objects) // for this widget. // // When declared in markup, it might look like: // | plugins="['bold',{name:'dijit._editor.plugins.FontChoice', command:'fontName', generic:true}]" plugins: null, // extraPlugins: [const] Object[] // A list of extra plugin names which will be appended to plugins array extraPlugins: null, constructor: function(){ // summary: // Runs on widget initialization to setup arrays etc. // tags: // private if(!lang.isArray(this.plugins)){ this.plugins=["undo","redo","|","cut","copy","paste","|","bold","italic","underline","strikethrough","|", "insertOrderedList","insertUnorderedList","indent","outdent","|","justifyLeft","justifyRight","justifyCenter","justifyFull", BuxEnterKeyHandling /*, "createLink"*/]; } this._plugins=[]; this._editInterval = this.editActionInterval * 1000; //IE will always lose focus when other element gets focus, while for FF and safari, //when no iframe is used, focus will be lost whenever another element gets focus. //For IE, we can connect to onBeforeDeactivate, which will be called right before //the focus is lost, so we can obtain the selected range. For other browsers, //no equivalent of onBeforeDeactivate, so we need to do two things to make sure //selection is properly saved before focus is lost: 1) when user clicks another //element in the page, in which case we listen to mousedown on the entire page and //see whether user clicks out of a focus editor, if so, save selection (focus will //only lost after onmousedown event is fired, so we can obtain correct caret pos.) //2) when user tabs away from the editor, which is handled in onKeyDown below. if(has("ie") || has("trident")){ this.events.push("onBeforeDeactivate"); this.events.push("onBeforeActivate"); } }, postMixInProperties: function(){ // summary: // Extension to make sure a deferred is in place before certain functions // execute, like making sure all the plugins are properly inserted. // Set up a deferred so that the value isn't applied to the editor // until all the plugins load, needed to avoid timing condition // reported in #10537. this.setValueDeferred = new Deferred(); this.inherited(arguments); }, postCreate: function(){ //for custom undo/redo, if enabled. this._steps=this._steps.slice(0); this._undoedSteps=this._undoedSteps.slice(0); if(lang.isArray(this.extraPlugins)){ this.plugins=this.plugins.concat(this.extraPlugins); } this.inherited(arguments); this.commands = i18n.getLocalization("dijit._editor", "commands", this.lang); if(!this.toolbar){ // if we haven't been assigned a toolbar, create one this.toolbar = new Toolbar({ dir: this.dir, lang: this.lang }); this.header.appendChild(this.toolbar.domNode); } array.forEach(this.plugins, this.addPlugin, this); // Okay, denote the value can now be set. this.setValueDeferred.callback(true); domClass.add(this.iframe.parentNode, "dijitEditorIFrameContainer"); domClass.add(this.iframe, "dijitEditorIFrame"); domAttr.set(this.iframe, "allowTransparency", true); if(has("webkit")){ // Disable selecting the entire editor by inadvertent double-clicks. // on buttons, title bar, etc. Otherwise clicking too fast on // a button such as undo/redo selects the entire editor. domStyle.set(this.domNode, "KhtmlUserSelect", "none"); } this.toolbar.startup(); this.onNormalizedDisplayChanged(); //update toolbar button status }, destroy: function(){ array.forEach(this._plugins, function(p){ if(p && p.destroy){ p.destroy(); } }); this._plugins=[]; this.toolbar.destroyRecursive(); delete this.toolbar; this.inherited(arguments); }, addPlugin: function(/*String||Object||Function*/plugin, /*Integer?*/index){ // summary: // takes a plugin name as a string or a plugin instance and // adds it to the toolbar and associates it with this editor // instance. The resulting plugin is added to the Editor's // plugins array. If index is passed, it's placed in the plugins // array at that index. No big magic, but a nice helper for // passing in plugin names via markup. // // plugin: String, args object, plugin instance, or plugin constructor // // args: // This object will be passed to the plugin constructor // // index: Integer // Used when creating an instance from // something already in this.plugins. Ensures that the new // instance is assigned to this.plugins at that index. var args=lang.isString(plugin)?{name:plugin}:lang.isFunction(plugin)?{ctor:plugin}:plugin; if(!args.setEditor){ var o={"args":args,"plugin":null,"editor":this}; if(args.name){ // search registry for a plugin factory matching args.name, if it's not there then // fallback to 1.0 API: // ask all loaded plugin modules to fill in o.plugin if they can (ie, if they implement args.name) // remove fallback for 2.0. if(_Plugin.registry[args.name]){ o.plugin = _Plugin.registry[args.name](args); }else{ topic.publish(dijit._scopeName + ".Editor.getPlugin", o); // publish } } if(!o.plugin){ var pc = args.ctor || lang.getObject(args.name); if(pc){ o.plugin=new pc(args); } } if(!o.plugin){ console.warn('Cannot find plugin',plugin); return; } plugin=o.plugin; } if(arguments.length > 1){ this._plugins[index] = plugin; }else{ this._plugins.push(plugin); } plugin.setEditor(this); if(lang.isFunction(plugin.setToolbar)){ plugin.setToolbar(this.toolbar); } }, //the following 2 functions are required to make the editor play nice under a layout widget, see #4070 resize: function(size){ // summary: // Resize the editor to the specified size, see `dijit.layout._LayoutWidget.resize` if(size){ // we've been given a height/width for the entire editor (toolbar + contents), calls layout() // to split the allocated size between the toolbar and the contents _LayoutWidget.prototype.resize.apply(this, arguments); } /* else{ // do nothing, the editor is already laid out correctly. The user has probably specified // the height parameter, which was used to set a size on the iframe } */ }, layout: function(){ // summary: // Called from `dijit.layout._LayoutWidget.resize`. This shouldn't be called directly // tags: // protected // Converts the iframe (or rather the
surrounding it) to take all the available space // except what's needed for the header (toolbars) and footer (breadcrumbs, etc). // A class was added to the iframe container and some themes style it, so we have to // calc off the added margins and padding too. See tracker: #10662 var areaHeight = (this._contentBox.h - (this.getHeaderHeight() + this.getFooterHeight() + domGeometry.getPadBorderExtents(this.iframe.parentNode).h + domGeometry.getMarginExtents(this.iframe.parentNode).h)); this.editingArea.style.height = areaHeight + "px"; if(this.iframe){ this.iframe.style.height="100%"; } this._layoutMode = true; }, _onIEMouseDown: function(/*Event*/ e){ // summary: // IE only to prevent 2 clicks to focus // tags: // private var outsideClientArea; // IE 8's componentFromPoint is broken, which is a shame since it // was smaller code, but oh well. We have to do this brute force // to detect if the click was scroller or not. var b = this.document.body; var clientWidth = b.clientWidth; var clientHeight = b.clientHeight; var clientLeft = b.clientLeft; var offsetWidth = b.offsetWidth; var offsetHeight = b.offsetHeight; var offsetLeft = b.offsetLeft; //Check for vertical scroller click. if(/^rtl$/i.test(b.dir || "")){ if(clientWidth < offsetWidth && e.x > clientWidth && e.x < offsetWidth){ // Check the click was between width and offset width, if so, scroller outsideClientArea = true; } }else{ // RTL mode, we have to go by the left offsets. if(e.x < clientLeft && e.x > offsetLeft){ // Check the click was between width and offset width, if so, scroller outsideClientArea = true; } } if(!outsideClientArea){ // Okay, might be horiz scroller, check that. if(clientHeight < offsetHeight && e.y > clientHeight && e.y < offsetHeight){ // Horizontal scroller. outsideClientArea = true; } } if(!outsideClientArea){ delete this._cursorToStart; // Remove the force to cursor to start position. delete this._savedSelection; // new mouse position overrides old selection if(e.target.tagName == "BODY"){ setTimeout(lang.hitch(this, "placeCursorAtEnd"), 0); } this.inherited(arguments); } }, onBeforeActivate: function(){ this._restoreSelection(); }, onBeforeDeactivate: function(e){ // summary: // Called on IE right before focus is lost. Saves the selected range. // tags: // private if(this.customUndo){ this.endEditing(true); } //in IE, the selection will be lost when other elements get focus, //let's save focus before the editor is deactivated if(e.target.tagName != "BODY"){ this._saveSelection(); } //console.log('onBeforeDeactivate',this); }, /* beginning of custom undo/redo support */ // customUndo: Boolean // Whether we shall use custom undo/redo support instead of the native // browser support. By default, we now use custom undo. It works better // than native browser support and provides a consistent behavior across // browsers with a minimal performance hit. We already had the hit on // the slowest browser, IE, anyway. customUndo: true, // editActionInterval: Integer // When using customUndo, not every keystroke will be saved as a step. // Instead typing (including delete) will be grouped together: after // a user stops typing for editActionInterval seconds, a step will be // saved; if a user resume typing within editActionInterval seconds, // the timeout will be restarted. By default, editActionInterval is 3 // seconds. editActionInterval: 3, beginEditing: function(cmd){ // summary: // Called to note that the user has started typing alphanumeric characters, if it's not already noted. // Deals with saving undo; see editActionInterval parameter. // tags: // private if(!this._inEditing){ this._inEditing=true; this._beginEditing(cmd); } if(this.editActionInterval>0){ if(this._editTimer){ clearTimeout(this._editTimer); } this._editTimer = setTimeout(lang.hitch(this, this.endEditing), this._editInterval); } }, // TODO: declaring these in the prototype is meaningless, just create in the constructor/postCreate _steps:[], _undoedSteps:[], execCommand: function(cmd){ // summary: // Main handler for executing any commands to the editor, like paste, bold, etc. // Called by plugins, but not meant to be called by end users. // tags: // protected if(this.customUndo && (cmd == 'undo' || cmd == 'redo')){ return this[cmd](); }else{ if(this.customUndo){ this.endEditing(); this._beginEditing(); } var r = this.inherited(arguments); if(this.customUndo){ this._endEditing(); } return r; } }, _pasteImpl: function(){ // summary: // Over-ride of paste command control to make execCommand cleaner // tags: // Protected return this._clipboardCommand("paste"); }, _cutImpl: function(){ // summary: // Over-ride of cut command control to make execCommand cleaner // tags: // Protected return this._clipboardCommand("cut"); }, _copyImpl: function(){ // summary: // Over-ride of copy command control to make execCommand cleaner // tags: // Protected return this._clipboardCommand("copy"); }, _clipboardCommand: function(cmd){ // summary: // Function to handle processing clipboard commands (or at least try to). // tags: // Private var r; try{ // Try to exec the superclass exec-command and see if it works. r = this.document.execCommand(cmd, false, null); if(has("webkit") && !r){ //see #4598: webkit does not guarantee clipboard support from js throw { code: 1011 }; // throw an object like Mozilla's error } }catch(e){ //TODO: when else might we get an exception? Do we need the Mozilla test below? if(e.code == 1011 /* Mozilla: service denied */){ // Warn user of platform limitation. Cannot programmatically access clipboard. See ticket #4136 var sub = string.substitute, accel = {cut:'X', copy:'C', paste:'V'}; alert(sub(this.commands.systemShortcut, [this.commands[cmd], sub(this.commands[has("mac") ? 'appleKey' : 'ctrlKey'], [accel[cmd]])])); } r = false; } return r; }, queryCommandEnabled: function(cmd){ // summary: // Returns true if specified editor command is enabled. // Used by the plugins to know when to highlight/not highlight buttons. // tags: // protected if(this.customUndo && (cmd == 'undo' || cmd == 'redo')){ return cmd == 'undo' ? (this._steps.length > 1) : (this._undoedSteps.length > 0); }else{ return this.inherited(arguments); } }, _moveToBookmark: function(b){ // summary: // Selects the text specified in bookmark b // tags: // private var bookmark = b.mark; var mark = b.mark; var col = b.isCollapsed; var r, sNode, eNode, sel; if(mark){ if(has("ie") < 9){ if(lang.isArray(mark)){ //IE CONTROL, have to use the native bookmark. bookmark = []; array.forEach(mark,function(n){ bookmark.push(rangeapi.getNode(n,this.editNode)); },this); win.withGlobal(this.window,'moveToBookmark',dijit,[{mark: bookmark, isCollapsed: col}]); }else{ if(mark.startContainer && mark.endContainer){ // Use the pseudo WC3 range API. This works better for positions // than the IE native bookmark code. sel = rangeapi.getSelection(this.window); if(sel && sel.removeAllRanges){ sel.removeAllRanges(); r = rangeapi.create(this.window); sNode = rangeapi.getNode(mark.startContainer,this.editNode); eNode = rangeapi.getNode(mark.endContainer,this.editNode); if(sNode && eNode){ // Okay, we believe we found the position, so add it into the selection // There are cases where it may not be found, particularly in undo/redo, when // IE changes the underlying DOM on us (wraps text in a

tag or similar. // So, in those cases, don't bother restoring selection. r.setStart(sNode,mark.startOffset); r.setEnd(eNode,mark.endOffset); sel.addRange(r); } } } } }else{//w3c range sel = rangeapi.getSelection(this.window); if(sel && sel.removeAllRanges){ sel.removeAllRanges(); r = rangeapi.create(this.window); sNode = rangeapi.getNode(mark.startContainer,this.editNode); eNode = rangeapi.getNode(mark.endContainer,this.editNode); if(sNode && eNode){ // Okay, we believe we found the position, so add it into the selection // There are cases where it may not be found, particularly in undo/redo, when // formatting as been done and so on, so don't restore selection then. r.setStart(sNode,mark.startOffset); r.setEnd(eNode,mark.endOffset); sel.addRange(r); } } } } }, _changeToStep: function(from, to){ // summary: // Reverts editor to "to" setting, from the undo stack. // tags: // private this.setValue(to.text); var b=to.bookmark; if(!b){ return; } this._moveToBookmark(b); }, undo: function(){ // summary: // Handler for editor undo (ex: ctrl-z) operation // tags: // private //console.log('undo'); var ret = false; if(!this._undoRedoActive){ this._undoRedoActive = true; this.endEditing(true); var s=this._steps.pop(); if(s && this._steps.length>0){ this.focus(); this._changeToStep(s,this._steps[this._steps.length-1]); this._undoedSteps.push(s); this.onDisplayChanged(); delete this._undoRedoActive; ret = true; } delete this._undoRedoActive; } return ret; }, redo: function(){ // summary: // Handler for editor redo (ex: ctrl-y) operation // tags: // private //console.log('redo'); var ret = false; if(!this._undoRedoActive){ this._undoRedoActive = true; this.endEditing(true); var s=this._undoedSteps.pop(); if(s && this._steps.length>0){ this.focus(); this._changeToStep(this._steps[this._steps.length-1],s); this._steps.push(s); this.onDisplayChanged(); ret = true; } delete this._undoRedoActive; } return ret; }, endEditing: function(ignore_caret){ // summary: // Called to note that the user has stopped typing alphanumeric characters, if it's not already noted. // Deals with saving undo; see editActionInterval parameter. // tags: // private if(this._editTimer){ clearTimeout(this._editTimer); } if(this._inEditing){ this._endEditing(ignore_caret); this._inEditing=false; } }, _getBookmark: function(){ // summary: // Get the currently selected text // tags: // protected var b=win.withGlobal(this.window,focusBase.getBookmark); var tmp=[]; if(b && b.mark){ var mark = b.mark; if(has("ie") < 9){ // Try to use the pseudo range API on IE for better accuracy. var sel = rangeapi.getSelection(this.window); if(!lang.isArray(mark)){ if(sel){ var range; if(sel.rangeCount){ range = sel.getRangeAt(0); } if(range){ b.mark = range.cloneRange(); }else{ b.mark = win.withGlobal(this.window,focusBase.getBookmark); } } }else{ // Control ranges (img, table, etc), handle differently. array.forEach(b.mark,function(n){ tmp.push(rangeapi.getIndex(n,this.editNode).o); },this); b.mark = tmp; } } try{ if(b.mark && b.mark.startContainer){ tmp=rangeapi.getIndex(b.mark.startContainer,this.editNode).o; b.mark={startContainer:tmp, startOffset:b.mark.startOffset, endContainer:b.mark.endContainer===b.mark.startContainer?tmp:rangeapi.getIndex(b.mark.endContainer,this.editNode).o, endOffset:b.mark.endOffset}; } }catch(e){ b.mark = null; } } return b; }, _beginEditing: function(){ // summary: // Called when the user starts typing alphanumeric characters. // Deals with saving undo; see editActionInterval parameter. // tags: // private if(this._steps.length === 0){ // You want to use the editor content without post filtering // to make sure selection restores right for the 'initial' state. // and undo is called. So not using this.value, as it was 'processed' // and the line-up for selections may have been altered. this._steps.push({'text':html.getChildrenHtml(this.editNode),'bookmark':this._getBookmark()}); } }, _endEditing: function(){ // summary: // Called when the user stops typing alphanumeric characters. // Deals with saving undo; see editActionInterval parameter. // tags: // private // Avoid filtering to make sure selections restore. var v = html.getChildrenHtml(this.editNode); this._undoedSteps=[];//clear undoed steps this._steps.push({text: v, bookmark: this._getBookmark()}); }, onKeyDown: function(e){ // summary: // Handler for onkeydown event. // tags: // private //We need to save selection if the user TAB away from this editor //no need to call _saveSelection for IE, as that will be taken care of in onBeforeDeactivate if(!has("ie") && !this.iframe && e.keyCode == keys.TAB && !this.tabIndent){ this._saveSelection(); } if(!this.customUndo){ this.inherited(arguments); return; } var k = e.keyCode; if(e.ctrlKey && !e.altKey){//undo and redo only if the special right Alt + z/y are not pressed #5892 if(k == 90 || k == 122){ //z event.stop(e); this.undo(); return; }else if(k == 89 || k == 121){ //y event.stop(e); this.redo(); return; } } this.inherited(arguments); switch(k){ case keys.ENTER: case keys.BACKSPACE: case keys.DELETE: this.beginEditing(); break; case 88: //x case 86: //v if(e.ctrlKey && !e.altKey && !e.metaKey){ this.endEditing();//end current typing step if any if(e.keyCode == 88){ this.beginEditing('cut'); //use timeout to trigger after the cut is complete setTimeout(lang.hitch(this, this.endEditing), 1); }else{ this.beginEditing('paste'); //use timeout to trigger after the paste is complete setTimeout(lang.hitch(this, this.endEditing), 1); } break; } //pass through default: if(!e.ctrlKey && !e.altKey && !e.metaKey && (e.keyCodekeys.F15)){ this.beginEditing(); break; } //pass through case keys.ALT: this.endEditing(); break; case keys.UP_ARROW: case keys.DOWN_ARROW: case keys.LEFT_ARROW: case keys.RIGHT_ARROW: case keys.HOME: case keys.END: case keys.PAGE_UP: case keys.PAGE_DOWN: this.endEditing(true); break; //maybe ctrl+backspace/delete, so don't endEditing when ctrl is pressed case keys.CTRL: case keys.SHIFT: case keys.TAB: break; } }, _onBlur: function(){ // summary: // Called from focus manager when focus has moved away from this editor // tags: // protected //this._saveSelection(); this.inherited(arguments); this.endEditing(true); }, _saveSelection: function(){ // summary: // Save the currently selected text in _savedSelection attribute // tags: // private try{ this._savedSelection=this._getBookmark(); }catch(e){ /* Squelch any errors that occur if selection save occurs due to being hidden simultaneously. */} }, _restoreSelection: function(){ // summary: // Re-select the text specified in _savedSelection attribute; // see _saveSelection(). // tags: // private if(this._savedSelection){ // Clear off cursor to start, we're deliberately going to a selection. delete this._cursorToStart; // only restore the selection if the current range is collapsed // if not collapsed, then it means the editor does not lose // selection and there is no need to restore it if(win.withGlobal(this.window,'isCollapsed',dijit)){ this._moveToBookmark(this._savedSelection); } delete this._savedSelection; } }, onClick: function(){ // summary: // Handler for when editor is clicked // tags: // protected this.endEditing(true); this.inherited(arguments); }, replaceValue: function(/*String*/ html){ // summary: // over-ride of replaceValue to support custom undo and stack maintenance. // tags: // protected if(!this.customUndo){ this.inherited(arguments); }else{ if(this.isClosed){ this.setValue(html); }else{ this.beginEditing(); if(!html){ html = " "; //   } this.setValue(html); this.endEditing(); } } }, _setDisabledAttr: function(/*Boolean*/ value){ var disableFunc = lang.hitch(this, function(){ if((!this.disabled && value) || (!this._buttonEnabledPlugins && value)){ // Disable editor: disable all enabled buttons and remember that list array.forEach(this._plugins, function(p){ p.set("disabled", true); }); }else if(this.disabled && !value){ // Restore plugins to being active. array.forEach(this._plugins, function(p){ p.set("disabled", false); }); } }); this.setValueDeferred.addCallback(disableFunc); this.inherited(arguments); }, _setStateClass: function(){ try{ this.inherited(arguments); // Let theme set the editor's text color based on editor enabled/disabled state. // We need to jump through hoops because the main document (where the theme CSS is) // is separate from the iframe's document. if(this.document && this.document.body){ domStyle.set(this.document.body, "color", domStyle.get(this.iframe, "color")); } }catch(e){ /* Squelch any errors caused by focus change if hidden during a state change */} } }); // Register the "default plugins", ie, the built-in editor commands function simplePluginFactory(args){ return new _Plugin({ command: args.name }); } function togglePluginFactory(args){ return new _Plugin({ buttonClass: ToggleButton, command: args.name }); } lang.mixin(_Plugin.registry, { "undo": simplePluginFactory, "redo": simplePluginFactory, "cut": simplePluginFactory, "copy": simplePluginFactory, "paste": simplePluginFactory, "insertOrderedList": simplePluginFactory, "insertUnorderedList": simplePluginFactory, "indent": simplePluginFactory, "outdent": simplePluginFactory, "justifyCenter": simplePluginFactory, "justifyFull": simplePluginFactory, "justifyLeft": simplePluginFactory, "justifyRight": simplePluginFactory, "delete": simplePluginFactory, "selectAll": simplePluginFactory, "removeFormat": simplePluginFactory, "unlink": simplePluginFactory, "insertHorizontalRule": simplePluginFactory, "bold": togglePluginFactory, "italic": togglePluginFactory, "underline": togglePluginFactory, "strikethrough": togglePluginFactory, "subscript": togglePluginFactory, "superscript": togglePluginFactory, "|": function(){ return new _Plugin({ button: new ToolbarSeparator(), setEditor: function(editor){this.editor = editor;}}); } }); return Editor; }); }, 'dijit/_editor/_Plugin':function(){ define("dijit/_editor/_Plugin", [ "dojo/_base/connect", // connect.connect "dojo/_base/declare", // declare "dojo/_base/lang", // lang.mixin, lang.hitch "../form/Button" ], function(connect, declare, lang, Button){ // module: // dijit/_editor/_Plugin // summary: // Base class for a "plugin" to the editor, which is usually // a single button on the Toolbar and some associated code var _Plugin = declare("dijit._editor._Plugin", null, { // summary: // Base class for a "plugin" to the editor, which is usually // a single button on the Toolbar and some associated code constructor: function(/*Object?*/args){ this.params = args || {}; lang.mixin(this, this.params); this._connects=[]; this._attrPairNames = {}; }, // editor: [const] dijit.Editor // Points to the parent editor editor: null, // iconClassPrefix: [const] String // The CSS class name for the button node is formed from `iconClassPrefix` and `command` iconClassPrefix: "dijitEditorIcon", // button: dijit._Widget? // Pointer to `dijit.form.Button` or other widget (ex: `dijit.form.FilteringSelect`) // that is added to the toolbar to control this plugin. // If not specified, will be created on initialization according to `buttonClass` button: null, // command: String // String like "insertUnorderedList", "outdent", "justifyCenter", etc. that represents an editor command. // Passed to editor.execCommand() if `useDefaultCommand` is true. command: "", // useDefaultCommand: Boolean // If true, this plugin executes by calling Editor.execCommand() with the argument specified in `command`. useDefaultCommand: true, // buttonClass: Widget Class // Class of widget (ex: dijit.form.Button or dijit.form.FilteringSelect) // that is added to the toolbar to control this plugin. // This is used to instantiate the button, unless `button` itself is specified directly. buttonClass: Button, // disabled: Boolean // Flag to indicate if this plugin has been disabled and should do nothing // helps control button state, among other things. Set via the setter api. disabled: false, getLabel: function(/*String*/key){ // summary: // Returns the label to use for the button // tags: // private return this.editor.commands[key]; // String }, _initButton: function(){ // summary: // Initialize the button or other widget that will control this plugin. // This code only works for plugins controlling built-in commands in the editor. // tags: // protected extension if(this.command.length){ var label = this.getLabel(this.command), editor = this.editor, className = this.iconClassPrefix+" "+this.iconClassPrefix + this.command.charAt(0).toUpperCase() + this.command.substr(1); if(!this.button){ var props = lang.mixin({ label: label, dir: editor.dir, lang: editor.lang, showLabel: false, iconClass: className, dropDown: this.dropDown, tabIndex: "-1" }, this.params || {}); this.button = new this.buttonClass(props); } } if(this.get("disabled") && this.button){ this.button.set("disabled", this.get("disabled")); } }, destroy: function(){ // summary: // Destroy this plugin var h; while(h = this._connects.pop()){ h.remove(); } if(this.dropDown){ this.dropDown.destroyRecursive(); } }, connect: function(o, f, tf){ // summary: // Make a connect.connect() that is automatically disconnected when this plugin is destroyed. // Similar to `dijit._Widget.connect`. // tags: // protected this._connects.push(connect.connect(o, f, this, tf)); }, updateState: function(){ // summary: // Change state of the plugin to respond to events in the editor. // description: // This is called on meaningful events in the editor, such as change of selection // or caret position (but not simple typing of alphanumeric keys). It gives the // plugin a chance to update the CSS of its button. // // For example, the "bold" plugin will highlight/unhighlight the bold button depending on whether the // characters next to the caret are bold or not. // // Only makes sense when `useDefaultCommand` is true, as it calls Editor.queryCommandEnabled(`command`). var e = this.editor, c = this.command, checked, enabled; if(!e || !e.isLoaded || !c.length){ return; } var disabled = this.get("disabled"); if(this.button){ try{ enabled = !disabled && e.queryCommandEnabled(c); if(this.enabled !== enabled){ this.enabled = enabled; this.button.set('disabled', !enabled); } if(typeof this.button.checked == 'boolean'){ checked = e.queryCommandState(c); if(this.checked !== checked){ this.checked = checked; this.button.set('checked', e.queryCommandState(c)); } } }catch(e){ console.log(e); // FIXME: we shouldn't have debug statements in our code. Log as an error? } } }, setEditor: function(/*dijit.Editor*/ editor){ // summary: // Tell the plugin which Editor it is associated with. // TODO: refactor code to just pass editor to constructor. // FIXME: detach from previous editor!! this.editor = editor; // FIXME: prevent creating this if we don't need to (i.e., editor can't handle our command) this._initButton(); // Processing for buttons that execute by calling editor.execCommand() if(this.button && this.useDefaultCommand){ if(this.editor.queryCommandAvailable(this.command)){ this.connect(this.button, "onClick", lang.hitch(this.editor, "execCommand", this.command, this.commandArg) ); }else{ // hide button because editor doesn't support command (due to browser limitations) this.button.domNode.style.display = "none"; } } this.connect(this.editor, "onNormalizedDisplayChanged", "updateState"); }, setToolbar: function(/*dijit.Toolbar*/ toolbar){ // summary: // Tell the plugin to add it's controller widget (often a button) // to the toolbar. Does nothing if there is no controller widget. // TODO: refactor code to just pass toolbar to constructor. if(this.button){ toolbar.addChild(this.button); } // console.debug("adding", this.button, "to:", toolbar); }, set: function(/* attribute */ name, /* anything */ value){ // summary: // Set a property on a plugin // name: // The property to set. // value: // The value to set in the property. // description: // Sets named properties on a plugin which may potentially be handled by a // setter in the plugin. // For example, if the plugin has a properties "foo" // and "bar" and a method named "_setFooAttr", calling: // | plugin.set("foo", "Howdy!"); // would be equivalent to writing: // | plugin._setFooAttr("Howdy!"); // and: // | plugin.set("bar", 3); // would be equivalent to writing: // | plugin.bar = 3; // // set() may also be called with a hash of name/value pairs, ex: // | plugin.set({ // | foo: "Howdy", // | bar: 3 // | }) // This is equivalent to calling set(foo, "Howdy") and set(bar, 3) if(typeof name === "object"){ for(var x in name){ this.set(x, name[x]); } return this; } var names = this._getAttrNames(name); if(this[names.s]){ // use the explicit setter var result = this[names.s].apply(this, Array.prototype.slice.call(arguments, 1)); }else{ this._set(name, value); } return result || this; }, get: function(name){ // summary: // Get a property from a plugin. // name: // The property to get. // description: // Get a named property from a plugin. The property may // potentially be retrieved via a getter method. If no getter is defined, this // just retrieves the object's property. // For example, if the plugin has a properties "foo" // and "bar" and a method named "_getFooAttr", calling: // | plugin.get("foo"); // would be equivalent to writing: // | plugin._getFooAttr(); // and: // | plugin.get("bar"); // would be equivalent to writing: // | plugin.bar; var names = this._getAttrNames(name); return this[names.g] ? this[names.g]() : this[name]; }, _setDisabledAttr: function(disabled){ // summary: // Function to set the plugin state and call updateState to make sure the // button is updated appropriately. this.disabled = disabled; this.updateState(); }, _getAttrNames: function(name){ // summary: // Helper function for get() and set(). // Caches attribute name values so we don't do the string ops every time. // tags: // private var apn = this._attrPairNames; if(apn[name]){ return apn[name]; } var uc = name.charAt(0).toUpperCase() + name.substr(1); return (apn[name] = { s: "_set"+uc+"Attr", g: "_get"+uc+"Attr" }); }, _set: function(/*String*/ name, /*anything*/ value){ // summary: // Helper function to set new value for specified attribute this[name] = value; } }); // Hash mapping plugin name to factory, used for registering plugins _Plugin.registry = {}; return _Plugin; }); }, 'dijit/_editor/plugins/FontChoice':function(){ 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: "" + "" + "" + "", 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: "xx-small" } 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 "

" + name + "
"; } }, _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 "" + name + ""; } }, _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 + ""; } }, _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. if(this.editor.focused){ // put focus back in the iframe, unless focus has somehow been shifted out of the editor completely 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(/'([^']*)'/) || 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 }); }; }); }); }, 'dijit/_editor/html':function(){ define("dijit/_editor/html", [ "dojo/_base/array", "dojo/_base/lang", // lang.getObject "dojo/_base/sniff", // has("ie") ".." // for exporting symbols to dijit._editor (remove for 2.0) ], function(array, lang, has, dijit){ // module: // dijit/_editor/html // summary: // Utility functions used by editor // Tests for DOMNode.attributes[] behavior: // - dom-attributes-explicit - attributes[] only lists explicitly user specified attributes // - dom-attributes-specified-flag (IE8) - need to check attr.specified flag to skip attributes user didn't specify // - Otherwise, in IE6-7. attributes[] will list hundreds of values, so need to do outerHTML to get attrs instead. var form = document.createElement("form"); has.add("dom-attributes-explicit", form.attributes.length == 0); // W3C has.add("dom-attributes-specified-flag", form.attributes.length > 0 && form.attributes.length < 40); // IE8 lang.getObject("_editor", true, dijit); dijit._editor.escapeXml=function(/*String*/str, /*Boolean?*/noSingleQuotes){ // summary: // Adds escape sequences for special characters in XML: &<>"' // Optionally skips escapes for single quotes str = str.replace(/&/gm, "&").replace(//gm, ">").replace(/"/gm, """); if(!noSingleQuotes){ str = str.replace(/'/gm, "'"); } return str; // string }; dijit._editor.getNodeHtml=function(/* DomNode */node){ var output; switch(node.nodeType){ case 1: //element node var lName = node.nodeName.toLowerCase(); if(!lName || lName.charAt(0) == "/"){ // IE does some strange things with malformed HTML input, like // treating a close tag without an open tag , as // a new tag with tagName of /span. Corrupts output HTML, remove // them. Other browsers don't prefix tags that way, so will // never show up. return ""; } output = '<' + lName; //store the list of attributes and sort it to have the //attributes appear in the dictionary order var attrarray = [], attrhash = {}; var attr; if(has("dom-attributes-explicit") || has("dom-attributes-specified-flag")){ // IE8+ and all other browsers. var i = 0; while((attr = node.attributes[i++])){ // ignore all attributes starting with _dj which are // internal temporary attributes used by the editor var n = attr.name; if(n.substr(0,3) !== '_dj' && (!has("dom-attributes-specified-flag") || attr.specified) && !(n in attrhash)){ // workaround repeated attributes bug in IE8 (LinkDialog test) var v = attr.value; if(n == 'src' || n == 'href'){ if(node.getAttribute('_djrealurl')){ v = node.getAttribute('_djrealurl'); } } if(has("ie") === 8 && n === "style"){ v = v.replace("HEIGHT:", "height:").replace("WIDTH:", "width:"); } attrarray.push([n,v]); attrhash[n] = v; } } }else{ // IE6-7 code path var clone = /^input$|^img$/i.test(node.nodeName) ? node : node.cloneNode(false); var s = clone.outerHTML; // Split up and manage the attrs via regexp // similar to prettyPrint attr logic. var rgxp_attrsMatch = /[\w-]+=("[^"]*"|'[^']*'|\S*)/gi var attrSplit = s.match(rgxp_attrsMatch); s = s.substr(0, s.indexOf('>')); array.forEach(attrSplit, function(attr){ if(attr){ var idx = attr.indexOf("="); if(idx > 0){ var key = attr.substring(0,idx); if(key.substr(0,3) != '_dj'){ if(key == 'src' || key == 'href'){ if(node.getAttribute('_djrealurl')){ attrarray.push([key,node.getAttribute('_djrealurl')]); return; } } var val, match; switch(key){ case 'style': val = node.style.cssText.toLowerCase(); break; case 'class': val = node.className; break; case 'width': if(lName === "img"){ // This somehow gets lost on IE for IMG tags and the like // and we have to find it in outerHTML, known IE oddity. match=/width=(\S+)/i.exec(s); if(match){ val = match[1]; } break; } case 'height': if(lName === "img"){ // This somehow gets lost on IE for IMG tags and the like // and we have to find it in outerHTML, known IE oddity. match=/height=(\S+)/i.exec(s); if(match){ val = match[1]; } break; } default: val = node.getAttribute(key); } if(val != null){ attrarray.push([key, val.toString()]); } } } } }, this); } attrarray.sort(function(a,b){ return a[0] < b[0] ? -1 : (a[0] == b[0] ? 0 : 1); }); var j = 0; while((attr = attrarray[j++])){ output += ' ' + attr[0] + '="' + (lang.isString(attr[1]) ? dijit._editor.escapeXml(attr[1], true) : attr[1]) + '"'; } if(lName === "script"){ // Browsers handle script tags differently in how you get content, // but innerHTML always seems to work, so insert its content that way // Yes, it's bad to allow script tags in the editor code, but some people // seem to want to do it, so we need to at least return them right. // other plugins/filters can strip them. output += '>' + node.innerHTML +''; }else{ if(node.childNodes.length){ output += '>' + dijit._editor.getChildrenHtml(node)+''; }else{ switch(lName){ case 'br': case 'hr': case 'img': case 'input': case 'base': case 'meta': case 'area': case 'basefont': // These should all be singly closed output += ' />'; break; default: // Assume XML style separate closure for everything else. output += '>'; } } } break; case 4: // cdata case 3: // text // FIXME: output = dijit._editor.escapeXml(node.nodeValue, true); break; case 8: //comment // FIXME: output = ''; break; default: output = ""; } return output; }; dijit._editor.getChildrenHtml = function(/* DomNode */dom){ // summary: // Returns the html content of a DomNode and children var out = ""; if(!dom){ return out; } var nodes = dom["childNodes"] || dom; //IE issue. //If we have an actual node we can check parent relationships on for IE, //We should check, as IE sometimes builds invalid DOMS. If no parent, we can't check //And should just process it and hope for the best. var checkParent = !has("ie") || nodes !== dom; var node, i = 0; while((node = nodes[i++])){ //IE is broken. DOMs are supposed to be a tree. But in the case of malformed HTML, IE generates a graph //meaning one node ends up with multiple references (multiple parents). This is totally wrong and invalid, but //such is what it is. We have to keep track and check for this because otherise the source output HTML will have dups. //No other browser generates a graph. Leave it to IE to break a fundamental DOM rule. So, we check the parent if we can //If we can't, nothing more we can do other than walk it. if(!checkParent || node.parentNode == dom){ out += dijit._editor.getNodeHtml(node); } } return out; // String }; return dijit._editor; }); }, 'dijit/_editor/BuxRichText':function(){ define("dijit/_editor/BuxRichText", [ "dojo/_base/array", // array.forEach array.indexOf array.some "dojo/_base/config", // config "dojo/_base/declare", // declare "dojo/_base/Deferred", // Deferred "dojo/dom", // dom.byId "dojo/dom-attr", // domAttr.set or get "dojo/dom-class", // domClass.add domClass.remove "dojo/dom-construct", // domConstruct.create domConstruct.destroy domConstruct.place "dojo/dom-geometry", // domGeometry.position "dojo/dom-style", // domStyle.getComputedStyle domStyle.set "dojo/_base/event", // event.stop "dojo/_base/kernel", // kernel.deprecated "dojo/keys", // keys.BACKSPACE keys.TAB "dojo/_base/lang", // lang.clone lang.hitch lang.isArray lang.isFunction lang.isString lang.trim "dojo/on", // on() "dojo/query", // query "dojo/ready", // ready "dojo/_base/sniff", // has("ie") has("mozilla") has("opera") has("safari") has("webkit") "dojo/topic", // topic.publish() (publish) "dojo/_base/unload", // unload "dojo/_base/url", // url "dojo/_base/window", // win.body win.doc.body.focus win.doc.createElement win.global.location win.withGlobal "../_Widget", "../_CssStateMixin", "./selection", "./range", "./html", "../focus", ".." // dijit._scopeName ], function(array, config, declare, Deferred, dom, domAttr, domClass, domConstruct, domGeometry, domStyle, event, kernel, keys, lang, on, query, ready, has, topic, unload, _Url, win, _Widget, _CssStateMixin, selectionapi, rangeapi, htmlapi, focus, dijit){ /*===== var _Widget = dijit._Widget; var _CssStateMixin = dijit._CssStateMixin; =====*/ // module: // dijit/_editor/BuxRichText // summary: // dijit._editor.BuxRichText is the core of dijit.Editor, which provides basic // WYSIWYG editing features. // if you want to allow for rich text saving with back/forward actions, you must add a text area to your page with // the id==dijit._scopeName + "._editor.BuxRichText.value" (typically "dijit._editor.BuxRichText.value). For example, // something like this will work: // // // var BuxRichText = declare("dijit._editor.BuxRichText", [_Widget, _CssStateMixin], { // summary: // dijit._editor.BuxRichText is the core of dijit.Editor, which provides basic // WYSIWYG editing features. // // description: // dijit._editor.BuxRichText is the core of dijit.Editor, which provides basic // WYSIWYG editing features. It also encapsulates the differences // of different js engines for various browsers. Do not use this widget // with an HTML <TEXTAREA> tag, since the browser unescapes XML escape characters, // like <. This can have unexpected behavior and lead to security issues // such as scripting attacks. // // tags: // private constructor: function(params){ // contentPreFilters: Function(String)[] // Pre content filter function register array. // these filters will be executed before the actual // editing area gets the html content. this.contentPreFilters = []; // contentPostFilters: Function(String)[] // post content filter function register array. // These will be used on the resulting html // from contentDomPostFilters. The resulting // content is the final html (returned by getValue()). this.contentPostFilters = []; // contentDomPreFilters: Function(DomNode)[] // Pre content dom filter function register array. // These filters are applied after the result from // contentPreFilters are set to the editing area. this.contentDomPreFilters = []; // contentDomPostFilters: Function(DomNode)[] // Post content dom filter function register array. // These filters are executed on the editing area dom. // The result from these will be passed to contentPostFilters. this.contentDomPostFilters = []; // editingAreaStyleSheets: dojo._URL[] // array to store all the stylesheets applied to the editing area this.editingAreaStyleSheets = []; // Make a copy of this.events before we start writing into it, otherwise we // will modify the prototype which leads to bad things on pages w/multiple editors this.events = [].concat(this.events); this._keyHandlers = {}; if(params && lang.isString(params.value)){ this.value = params.value; } this.onLoadDeferred = new Deferred(); }, baseClass: "dijitEditor", // inheritWidth: Boolean // whether to inherit the parent's width or simply use 100% inheritWidth: false, // focusOnLoad: [deprecated] Boolean // Focus into this widget when the page is loaded focusOnLoad: false, // name: String? // Specifies the name of a (hidden)