1 if(!dojo._hasResource["dijit._editor.plugins.EnterKeyHandling"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2 dojo._hasResource["dijit._editor.plugins.EnterKeyHandling"] = true;
3 dojo.provide("dijit._editor.plugins.EnterKeyHandling");
5 dojo.declare("dijit._editor.plugins.EnterKeyHandling", dijit._editor._Plugin, {
6 // summary: this plugin tries to handle enter key events to make all
7 // browsers have identical behaviors.
9 // blockNodeForEnter: String
10 // this property decides the behavior of Enter key. It can be either P,
11 // DIV, BR, or empty (which means disable this feature). Anything else
12 // will trigger errors.
13 blockNodeForEnter: 'P',
14 constructor: function(args){
16 dojo.mixin(this,args);
19 setEditor: function(editor){
21 if(this.blockNodeForEnter == 'BR'){
23 editor.contentDomPreFilters.push(dojo.hitch(this, "regularPsToSingleLinePs"));
24 editor.contentDomPostFilters.push(dojo.hitch(this, "singleLinePsToRegularPs"));
25 editor.onLoadDeferred.addCallback(dojo.hitch(this, "_fixNewLineBehaviorForIE"));
27 editor.onLoadDeferred.addCallback(dojo.hitch(this,function(d){
29 this.editor.document.execCommand("insertBrOnReturn", false, true);
34 }else if(this.blockNodeForEnter){
35 //add enter key handler
36 // FIXME: need to port to the new event code!!
37 dojo['require']('dijit._editor.range');
38 var h = dojo.hitch(this,this.handleEnterKey);
39 editor.addKeyHandler(13, 0, h); //enter
40 editor.addKeyHandler(13, 2, h); //shift+enter
41 this.connect(this.editor,'onKeyPressed','onKeyPressed');
44 connect: function(o,f,tf){
48 this._connects.push(dojo.connect(o,f,this,tf));
51 dojo.forEach(this._connects,dojo.disconnect);
54 onKeyPressed: function(e){
55 if(this._checkListLater){
56 if(dojo.withGlobal(this.editor.window, 'isCollapsed', dijit)){
57 if(!dojo.withGlobal(this.editor.window, 'hasAncestorElement', dijit._editor.selection, ['LI'])){
58 //circulate the undo detection code by calling RichText::execCommand directly
59 dijit._editor.RichText.prototype.execCommand.apply(this.editor, ['formatblock',this.blockNodeForEnter]);
60 //set the innerHTML of the new block node
61 var block = dojo.withGlobal(this.editor.window, 'getAncestorElement', dijit._editor.selection, [this.blockNodeForEnter]);
63 block.innerHTML=this.bogusHtmlContent;
65 //the following won't work, it will move the caret to the last list item in the previous list
66 // var newrange = dijit.range.create();
67 // newrange.setStart(block.firstChild,0);
68 // var selection = dijit.range.getSelection(this.editor.window)
69 // selection.removeAllRanges();
70 // selection.addRange(newrange);
71 //move to the start by move backward one char
72 var r = this.editor.document.selection.createRange();
73 r.move('character',-1);
77 alert('onKeyPressed: Can not find the new block node'); //FIXME
81 this._checkListLater = false;
82 }else if(this._pressedEnterInBlock){
83 //the new created is the original current P, so we have previousSibling below
84 this.removeTrailingBr(this._pressedEnterInBlock.previousSibling);
85 delete this._pressedEnterInBlock;
88 bogusHtmlContent: ' ',
89 blockNodes: /^(?:H1|H2|H3|H4|H5|H6|LI)$/,
90 handleEnterKey: function(e){
91 // summary: manually handle enter key event to make the behavior consistant across
92 // all supported browsers. See property blockNodeForEnter for available options
93 if(!this.blockNodeForEnter){ return true; } //let browser handle this
94 var selection, range, newrange;
95 if(e.shiftKey //shift+enter always generates <br>
96 || this.blockNodeForEnter=='BR'){
97 var parent = dojo.withGlobal(this.editor.window, "getParentElement", dijit._editor.selection);
98 var header = dijit.range.getAncestor(parent,this.editor.blockNodes);
100 if(header.tagName=='LI'){
101 return true; //let brower handle
103 selection = dijit.range.getSelection(this.editor.window);
104 range = selection.getRangeAt(0);
105 if(!range.collapsed){
106 range.deleteContents();
108 if(dijit.range.atBeginningOfContainer(header, range.startContainer, range.startOffset)){
109 dojo.place(this.editor.document.createElement('br'), header, "before");
110 }else if(dijit.range.atEndOfContainer(header, range.startContainer, range.startOffset)){
111 dojo.place(this.editor.document.createElement('br'), header, "after");
112 newrange = dijit.range.create();
113 newrange.setStartAfter(header);
115 selection.removeAllRanges();
116 selection.addRange(newrange);
118 return true; //let brower handle
121 //don't change this: do not call this.execCommand, as that may have other logic in subclass
123 dijit._editor.RichText.prototype.execCommand.call(this.editor, 'inserthtml', '<br>');
127 var _letBrowserHandle = true;
128 //blockNodeForEnter is either P or DIV
129 //first remove selection
130 selection = dijit.range.getSelection(this.editor.window);
131 range = selection.getRangeAt(0);
132 if(!range.collapsed){
133 range.deleteContents();
136 var block = dijit.range.getBlockAncestor(range.endContainer, null, this.editor.editNode);
138 if((this._checkListLater = (block.blockNode && block.blockNode.tagName == 'LI'))){
142 //text node directly under body, let's wrap them in a node
143 if(!block.blockNode){
144 this.editor.document.execCommand('formatblock', false, this.blockNodeForEnter);
145 //get the newly created block node
147 block = {blockNode:dojo.withGlobal(this.editor.window, "getAncestorElement", dijit._editor.selection, [this.blockNodeForEnter]),
148 blockContainer: this.editor.editNode};
150 if(!(block.blockNode.textContent || block.blockNode.innerHTML).replace(/^\s+|\s+$/g, "").length){
151 this.removeTrailingBr(block.blockNode);
155 block.blockNode = this.editor.editNode;
157 selection = dijit.range.getSelection(this.editor.window);
158 range = selection.getRangeAt(0);
160 var newblock = this.editor.document.createElement(this.blockNodeForEnter);
161 newblock.innerHTML=this.bogusHtmlContent;
162 this.removeTrailingBr(block.blockNode);
163 if(dijit.range.atEndOfContainer(block.blockNode, range.endContainer, range.endOffset)){
164 if(block.blockNode === block.blockContainer){
165 block.blockNode.appendChild(newblock);
167 dojo.place(newblock, block.blockNode, "after");
169 _letBrowserHandle = false;
170 //lets move caret to the newly created block
171 newrange = dijit.range.create();
172 newrange.setStart(newblock,0);
173 selection.removeAllRanges();
174 selection.addRange(newrange);
175 if(this.editor.height){
176 newblock.scrollIntoView(false);
178 }else if(dijit.range.atBeginningOfContainer(block.blockNode,
179 range.startContainer, range.startOffset)){
180 dojo.place(newblock, block.blockNode, block.blockNode === block.blockContainer ? "first" : "before");
181 if(newblock.nextSibling && this.editor.height){
182 //browser does not scroll the caret position into view, do it manually
183 newblock.nextSibling.scrollIntoView(false);
185 _letBrowserHandle = false;
186 }else{ //press enter in the middle of P
188 //press enter in middle of P may leave a trailing <br/>, let's remove it later
189 this._pressedEnterInBlock = block.blockNode;
192 return _letBrowserHandle;
194 removeTrailingBr: function(container){
195 var para = /P|DIV|LI/i.test(container.tagName) ?
196 container : dijit._editor.selection.getParentOfType(container,['P','DIV','LI']);
200 if((para.childNodes.length > 1 && para.lastChild.nodeType == 3 && /^[\s\xAD]*$/.test(para.lastChild.nodeValue)) ||
201 (para.lastChild && para.lastChild.tagName=='BR')){
203 dojo._destroyElement(para.lastChild);
206 if(!para.childNodes.length){
207 para.innerHTML=this.bogusHtmlContent;
210 _fixNewLineBehaviorForIE: function(d){
211 if(this.editor.document.__INSERTED_EDITIOR_NEWLINE_CSS === undefined){
212 var lineFixingStyles = "p{margin:0 !important;}";
213 var insertCssText = function(
219 // Attempt to insert CSS rules into the document through inserting a
222 // DomNode Style = insertCssText(String ".dojoMenu {color: green;}"[, DomDoc document, dojo.uri.Uri Url ])
224 return null; // HTMLStyleElement
226 if(!doc){ doc = document; }
227 // if(URI){// fix paths in cssStr
228 // cssStr = dojo.html.fixPathsInCssText(cssStr, URI);
230 var style = doc.createElement("style");
231 style.setAttribute("type", "text/css");
232 // IE is b0rken enough to require that we add the element to the doc
233 // before changing it's properties
234 var head = doc.getElementsByTagName("head")[0];
235 if(!head){ // must have a head tag
236 console.debug("No head tag in document, aborting styles");
237 return null; // HTMLStyleElement
239 head.appendChild(style);
241 if(style.styleSheet){// IE
242 var setFunc = function(){
244 style.styleSheet.cssText = cssStr;
245 }catch(e){ console.debug(e); }
247 if(style.styleSheet.disabled){
248 setTimeout(setFunc, 10);
253 var cssText = doc.createTextNode(cssStr);
254 style.appendChild(cssText);
256 return style; // HTMLStyleElement
258 insertCssText(lineFixingStyles, this.editor.document);
259 this.editor.document.__INSERTED_EDITIOR_NEWLINE_CSS = true;
260 // this.regularPsToSingleLinePs(this.editNode);
265 regularPsToSingleLinePs: function(element, noWhiteSpaceInEmptyP){
266 function wrapLinesInPs(el){
267 // move "lines" of top-level text nodes into ps
268 function wrapNodes(nodes){
269 // nodes are assumed to all be siblings
270 var newP = nodes[0].ownerDocument.createElement('p'); // FIXME: not very idiomatic
271 nodes[0].parentNode.insertBefore(newP, nodes[0]);
272 dojo.forEach(nodes, function(node){
273 newP.appendChild(node);
277 var currentNodeIndex = 0;
278 var nodesInLine = [];
280 while(currentNodeIndex < el.childNodes.length){
281 currentNode = el.childNodes[currentNodeIndex];
282 if( (currentNode.nodeName!='BR') &&
283 (currentNode.nodeType==1) &&
284 (dojo.style(currentNode, "display")!="block")
286 nodesInLine.push(currentNode);
288 // hit line delimiter; process nodesInLine if there are any
289 var nextCurrentNode = currentNode.nextSibling;
290 if(nodesInLine.length){
291 wrapNodes(nodesInLine);
292 currentNodeIndex = (currentNodeIndex+1)-nodesInLine.length;
293 if(currentNode.nodeName=="BR"){
294 dojo._destroyElement(currentNode);
301 if(nodesInLine.length){ wrapNodes(nodesInLine); }
305 // split a paragraph into seperate paragraphs at BRs
306 var currentNode = null;
307 var trailingNodes = [];
308 var lastNodeIndex = el.childNodes.length-1;
309 for(var i=lastNodeIndex; i>=0; i--){
310 currentNode = el.childNodes[i];
311 if(currentNode.nodeName=="BR"){
312 var newP = currentNode.ownerDocument.createElement('p');
313 dojo.place(newP, el, "after");
314 if (trailingNodes.length==0 && i != lastNodeIndex) {
315 newP.innerHTML = " "
317 dojo.forEach(trailingNodes, function(node){
318 newP.appendChild(node);
320 dojo._destroyElement(currentNode);
323 trailingNodes.unshift(currentNode);
329 var ps = element.getElementsByTagName('p');
330 dojo.forEach(ps, function(p){ pList.push(p); });
331 dojo.forEach(pList, function(p){
332 if( (p.previousSibling) &&
333 (p.previousSibling.nodeName == 'P' || dojo.style(p.previousSibling, 'display') != 'block')
335 var newP = p.parentNode.insertBefore(this.document.createElement('p'), p);
336 // this is essential to prevent IE from losing the P.
337 // if it's going to be innerHTML'd later we need
338 // to add the to _really_ force the issue
339 newP.innerHTML = noWhiteSpaceInEmptyP ? "" : " ";
343 wrapLinesInPs(element);
347 singleLinePsToRegularPs: function(element){
348 function getParagraphParents(node){
349 var ps = node.getElementsByTagName('p');
351 for(var i=0; i<ps.length; i++){
353 var knownParent = false;
354 for(var k=0; k < parents.length; k++){
355 if(parents[k] === p.parentNode){
361 parents.push(p.parentNode);
367 function isParagraphDelimiter(node){
368 if(node.nodeType != 1 || node.tagName != 'P'){
369 return dojo.style(node, 'display') == 'block';
371 if(!node.childNodes.length || node.innerHTML==" "){ return true; }
372 //return node.innerHTML.match(/^(<br\ ?\/?>| |\ \;)$/i);
377 var paragraphContainers = getParagraphParents(element);
378 for(var i=0; i<paragraphContainers.length; i++){
379 var container = paragraphContainers[i];
380 var firstPInBlock = null;
381 var node = container.firstChild;
382 var deleteNode = null;
384 if(node.nodeType != "1" || node.tagName != 'P'){
385 firstPInBlock = null;
386 }else if (isParagraphDelimiter(node)){
388 firstPInBlock = null;
390 if(firstPInBlock == null){
391 firstPInBlock = node;
393 if( (!firstPInBlock.lastChild || firstPInBlock.lastChild.nodeName != 'BR') &&
395 (node.firstChild.nodeName != 'BR')
397 firstPInBlock.appendChild(this.editor.document.createElement('br'));
399 while(node.firstChild){
400 firstPInBlock.appendChild(node.firstChild);
405 node = node.nextSibling;
407 dojo._destroyElement(deleteNode);