417 lines
14 KiB
JavaScript
417 lines
14 KiB
JavaScript
|
if(!dojo._hasResource["dijit._editor.plugins.EnterKeyHandling"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
|
||
|
dojo._hasResource["dijit._editor.plugins.EnterKeyHandling"] = true;
|
||
|
dojo.provide("dijit._editor.plugins.EnterKeyHandling");
|
||
|
|
||
|
dojo.declare("dijit._editor.plugins.EnterKeyHandling", dijit._editor._Plugin, {
|
||
|
// summary: this plugin tries to handle enter key events to make all
|
||
|
// browsers have identical behaviors.
|
||
|
|
||
|
// 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.
|
||
|
blockNodeForEnter: 'P',
|
||
|
constructor: function(args){
|
||
|
if(args){
|
||
|
dojo.mixin(this,args);
|
||
|
}
|
||
|
},
|
||
|
setEditor: function(editor){
|
||
|
this.editor = editor;
|
||
|
if(this.blockNodeForEnter == 'BR'){
|
||
|
if(dojo.isIE){
|
||
|
editor.contentDomPreFilters.push(dojo.hitch(this, "regularPsToSingleLinePs"));
|
||
|
editor.contentDomPostFilters.push(dojo.hitch(this, "singleLinePsToRegularPs"));
|
||
|
editor.onLoadDeferred.addCallback(dojo.hitch(this, "_fixNewLineBehaviorForIE"));
|
||
|
}else{
|
||
|
editor.onLoadDeferred.addCallback(dojo.hitch(this,function(d){
|
||
|
try{
|
||
|
this.editor.document.execCommand("insertBrOnReturn", false, true);
|
||
|
}catch(e){}
|
||
|
return d;
|
||
|
}));
|
||
|
}
|
||
|
}else if(this.blockNodeForEnter){
|
||
|
//add enter key handler
|
||
|
// FIXME: need to port to the new event code!!
|
||
|
dojo['require']('dijit._editor.range');
|
||
|
var h = dojo.hitch(this,this.handleEnterKey);
|
||
|
editor.addKeyHandler(13, 0, h); //enter
|
||
|
editor.addKeyHandler(13, 2, h); //shift+enter
|
||
|
this.connect(this.editor,'onKeyPressed','onKeyPressed');
|
||
|
}
|
||
|
},
|
||
|
connect: function(o,f,tf){
|
||
|
if(!this._connects){
|
||
|
this._connects=[];
|
||
|
}
|
||
|
this._connects.push(dojo.connect(o,f,this,tf));
|
||
|
},
|
||
|
destroy: function(){
|
||
|
dojo.forEach(this._connects,dojo.disconnect);
|
||
|
this._connects=[];
|
||
|
},
|
||
|
onKeyPressed: function(e){
|
||
|
if(this._checkListLater){
|
||
|
if(dojo.withGlobal(this.editor.window, 'isCollapsed', dijit)){
|
||
|
if(!dojo.withGlobal(this.editor.window, 'hasAncestorElement', dijit._editor.selection, ['LI'])){
|
||
|
//circulate the undo detection code by calling RichText::execCommand directly
|
||
|
dijit._editor.RichText.prototype.execCommand.apply(this.editor, ['formatblock',this.blockNodeForEnter]);
|
||
|
//set the innerHTML of the new block node
|
||
|
var block = dojo.withGlobal(this.editor.window, 'getAncestorElement', dijit._editor.selection, [this.blockNodeForEnter]);
|
||
|
if(block){
|
||
|
block.innerHTML=this.bogusHtmlContent;
|
||
|
if(dojo.isIE){
|
||
|
//the following won't work, it will move the caret to the last list item in the previous list
|
||
|
// var newrange = dijit.range.create();
|
||
|
// newrange.setStart(block.firstChild,0);
|
||
|
// var selection = dijit.range.getSelection(this.editor.window)
|
||
|
// selection.removeAllRanges();
|
||
|
// selection.addRange(newrange);
|
||
|
//move to the start by move backward one char
|
||
|
var r = this.editor.document.selection.createRange();
|
||
|
r.move('character',-1);
|
||
|
r.select();
|
||
|
}
|
||
|
}else{
|
||
|
alert('onKeyPressed: Can not find the new block node'); //FIXME
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
this._checkListLater = false;
|
||
|
}else if(this._pressedEnterInBlock){
|
||
|
//the new created is the original current P, so we have previousSibling below
|
||
|
this.removeTrailingBr(this._pressedEnterInBlock.previousSibling);
|
||
|
delete this._pressedEnterInBlock;
|
||
|
}
|
||
|
},
|
||
|
bogusHtmlContent: ' ',
|
||
|
blockNodes: /^(?:H1|H2|H3|H4|H5|H6|LI)$/,
|
||
|
handleEnterKey: function(e){
|
||
|
// summary: manually handle enter key event to make the behavior consistant across
|
||
|
// all supported browsers. See property blockNodeForEnter for available options
|
||
|
if(!this.blockNodeForEnter){ return true; } //let browser handle this
|
||
|
var selection, range, newrange;
|
||
|
if(e.shiftKey //shift+enter always generates <br>
|
||
|
|| this.blockNodeForEnter=='BR'){
|
||
|
var parent = dojo.withGlobal(this.editor.window, "getParentElement", dijit._editor.selection);
|
||
|
var header = dijit.range.getAncestor(parent,this.editor.blockNodes);
|
||
|
if(header){
|
||
|
if(header.tagName=='LI'){
|
||
|
return true; //let brower handle
|
||
|
}
|
||
|
selection = dijit.range.getSelection(this.editor.window);
|
||
|
range = selection.getRangeAt(0);
|
||
|
if(!range.collapsed){
|
||
|
range.deleteContents();
|
||
|
}
|
||
|
if(dijit.range.atBeginningOfContainer(header, range.startContainer, range.startOffset)){
|
||
|
dojo.place(this.editor.document.createElement('br'), header, "before");
|
||
|
}else if(dijit.range.atEndOfContainer(header, range.startContainer, range.startOffset)){
|
||
|
dojo.place(this.editor.document.createElement('br'), header, "after");
|
||
|
newrange = dijit.range.create();
|
||
|
newrange.setStartAfter(header);
|
||
|
|
||
|
selection.removeAllRanges();
|
||
|
selection.addRange(newrange);
|
||
|
}else{
|
||
|
return true; //let brower handle
|
||
|
}
|
||
|
}else{
|
||
|
//don't change this: do not call this.execCommand, as that may have other logic in subclass
|
||
|
// FIXME
|
||
|
dijit._editor.RichText.prototype.execCommand.call(this.editor, 'inserthtml', '<br>');
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
var _letBrowserHandle = true;
|
||
|
//blockNodeForEnter is either P or DIV
|
||
|
//first remove selection
|
||
|
selection = dijit.range.getSelection(this.editor.window);
|
||
|
range = selection.getRangeAt(0);
|
||
|
if(!range.collapsed){
|
||
|
range.deleteContents();
|
||
|
}
|
||
|
|
||
|
var block = dijit.range.getBlockAncestor(range.endContainer, null, this.editor.editNode);
|
||
|
|
||
|
if((this._checkListLater = (block.blockNode && block.blockNode.tagName == 'LI'))){
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//text node directly under body, let's wrap them in a node
|
||
|
if(!block.blockNode){
|
||
|
this.editor.document.execCommand('formatblock', false, this.blockNodeForEnter);
|
||
|
//get the newly created block node
|
||
|
// FIXME
|
||
|
block = {blockNode:dojo.withGlobal(this.editor.window, "getAncestorElement", dijit._editor.selection, [this.blockNodeForEnter]),
|
||
|
blockContainer: this.editor.editNode};
|
||
|
if(block.blockNode){
|
||
|
if(!(block.blockNode.textContent || block.blockNode.innerHTML).replace(/^\s+|\s+$/g, "").length){
|
||
|
this.removeTrailingBr(block.blockNode);
|
||
|
return false;
|
||
|
}
|
||
|
}else{
|
||
|
block.blockNode = this.editor.editNode;
|
||
|
}
|
||
|
selection = dijit.range.getSelection(this.editor.window);
|
||
|
range = selection.getRangeAt(0);
|
||
|
}
|
||
|
var newblock = this.editor.document.createElement(this.blockNodeForEnter);
|
||
|
newblock.innerHTML=this.bogusHtmlContent;
|
||
|
this.removeTrailingBr(block.blockNode);
|
||
|
if(dijit.range.atEndOfContainer(block.blockNode, range.endContainer, range.endOffset)){
|
||
|
if(block.blockNode === block.blockContainer){
|
||
|
block.blockNode.appendChild(newblock);
|
||
|
}else{
|
||
|
dojo.place(newblock, block.blockNode, "after");
|
||
|
}
|
||
|
_letBrowserHandle = false;
|
||
|
//lets move caret to the newly created block
|
||
|
newrange = dijit.range.create();
|
||
|
newrange.setStart(newblock,0);
|
||
|
selection.removeAllRanges();
|
||
|
selection.addRange(newrange);
|
||
|
if(this.editor.height){
|
||
|
newblock.scrollIntoView(false);
|
||
|
}
|
||
|
}else if(dijit.range.atBeginningOfContainer(block.blockNode,
|
||
|
range.startContainer, range.startOffset)){
|
||
|
dojo.place(newblock, block.blockNode, block.blockNode === block.blockContainer ? "first" : "before");
|
||
|
if(newblock.nextSibling && this.editor.height){
|
||
|
//browser does not scroll the caret position into view, do it manually
|
||
|
newblock.nextSibling.scrollIntoView(false);
|
||
|
}
|
||
|
_letBrowserHandle = false;
|
||
|
}else{ //press enter in the middle of P
|
||
|
if(dojo.isMoz){
|
||
|
//press enter in middle of P may leave a trailing <br/>, let's remove it later
|
||
|
this._pressedEnterInBlock = block.blockNode;
|
||
|
}
|
||
|
}
|
||
|
return _letBrowserHandle;
|
||
|
},
|
||
|
removeTrailingBr: function(container){
|
||
|
var para = /P|DIV|LI/i.test(container.tagName) ?
|
||
|
container : dijit._editor.selection.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 && para.lastChild.tagName=='BR')){
|
||
|
|
||
|
dojo._destroyElement(para.lastChild);
|
||
|
}
|
||
|
}
|
||
|
if(!para.childNodes.length){
|
||
|
para.innerHTML=this.bogusHtmlContent;
|
||
|
}
|
||
|
},
|
||
|
_fixNewLineBehaviorForIE: function(d){
|
||
|
if(this.editor.document.__INSERTED_EDITIOR_NEWLINE_CSS === undefined){
|
||
|
var lineFixingStyles = "p{margin:0 !important;}";
|
||
|
var insertCssText = function(
|
||
|
/*String*/ cssStr,
|
||
|
/*Document*/ doc,
|
||
|
/*String*/ URI)
|
||
|
{
|
||
|
// summary:
|
||
|
// Attempt to insert CSS rules into the document through inserting a
|
||
|
// style element
|
||
|
|
||
|
// DomNode Style = insertCssText(String ".dojoMenu {color: green;}"[, DomDoc document, dojo.uri.Uri Url ])
|
||
|
if(!cssStr){
|
||
|
return null; // HTMLStyleElement
|
||
|
}
|
||
|
if(!doc){ doc = document; }
|
||
|
// if(URI){// fix paths in cssStr
|
||
|
// cssStr = dojo.html.fixPathsInCssText(cssStr, URI);
|
||
|
// }
|
||
|
var style = doc.createElement("style");
|
||
|
style.setAttribute("type", "text/css");
|
||
|
// IE is b0rken enough to require that we add the element to the doc
|
||
|
// before changing it's properties
|
||
|
var head = doc.getElementsByTagName("head")[0];
|
||
|
if(!head){ // must have a head tag
|
||
|
console.debug("No head tag in document, aborting styles");
|
||
|
return null; // HTMLStyleElement
|
||
|
}else{
|
||
|
head.appendChild(style);
|
||
|
}
|
||
|
if(style.styleSheet){// IE
|
||
|
var setFunc = function(){
|
||
|
try{
|
||
|
style.styleSheet.cssText = cssStr;
|
||
|
}catch(e){ console.debug(e); }
|
||
|
};
|
||
|
if(style.styleSheet.disabled){
|
||
|
setTimeout(setFunc, 10);
|
||
|
}else{
|
||
|
setFunc();
|
||
|
}
|
||
|
}else{ // w3c
|
||
|
var cssText = doc.createTextNode(cssStr);
|
||
|
style.appendChild(cssText);
|
||
|
}
|
||
|
return style; // HTMLStyleElement
|
||
|
}
|
||
|
insertCssText(lineFixingStyles, this.editor.document);
|
||
|
this.editor.document.__INSERTED_EDITIOR_NEWLINE_CSS = true;
|
||
|
// this.regularPsToSingleLinePs(this.editNode);
|
||
|
return d;
|
||
|
}
|
||
|
return null;
|
||
|
},
|
||
|
regularPsToSingleLinePs: function(element, noWhiteSpaceInEmptyP){
|
||
|
function wrapLinesInPs(el){
|
||
|
// move "lines" of top-level text nodes into ps
|
||
|
function wrapNodes(nodes){
|
||
|
// nodes are assumed to all be siblings
|
||
|
var newP = nodes[0].ownerDocument.createElement('p'); // FIXME: not very idiomatic
|
||
|
nodes[0].parentNode.insertBefore(newP, nodes[0]);
|
||
|
dojo.forEach(nodes, function(node){
|
||
|
newP.appendChild(node);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
var currentNodeIndex = 0;
|
||
|
var nodesInLine = [];
|
||
|
var currentNode;
|
||
|
while(currentNodeIndex < el.childNodes.length){
|
||
|
currentNode = el.childNodes[currentNodeIndex];
|
||
|
if( (currentNode.nodeName!='BR') &&
|
||
|
(currentNode.nodeType==1) &&
|
||
|
(dojo.style(currentNode, "display")!="block")
|
||
|
){
|
||
|
nodesInLine.push(currentNode);
|
||
|
}else{
|
||
|
// hit line delimiter; process nodesInLine if there are any
|
||
|
var nextCurrentNode = currentNode.nextSibling;
|
||
|
if(nodesInLine.length){
|
||
|
wrapNodes(nodesInLine);
|
||
|
currentNodeIndex = (currentNodeIndex+1)-nodesInLine.length;
|
||
|
if(currentNode.nodeName=="BR"){
|
||
|
dojo._destroyElement(currentNode);
|
||
|
}
|
||
|
}
|
||
|
nodesInLine = [];
|
||
|
}
|
||
|
currentNodeIndex++;
|
||
|
}
|
||
|
if(nodesInLine.length){ wrapNodes(nodesInLine); }
|
||
|
}
|
||
|
|
||
|
function splitP(el){
|
||
|
// split a paragraph into seperate paragraphs at BRs
|
||
|
var currentNode = null;
|
||
|
var trailingNodes = [];
|
||
|
var lastNodeIndex = el.childNodes.length-1;
|
||
|
for(var i=lastNodeIndex; i>=0; i--){
|
||
|
currentNode = el.childNodes[i];
|
||
|
if(currentNode.nodeName=="BR"){
|
||
|
var newP = currentNode.ownerDocument.createElement('p');
|
||
|
dojo.place(newP, el, "after");
|
||
|
if (trailingNodes.length==0 && i != lastNodeIndex) {
|
||
|
newP.innerHTML = " "
|
||
|
}
|
||
|
dojo.forEach(trailingNodes, function(node){
|
||
|
newP.appendChild(node);
|
||
|
});
|
||
|
dojo._destroyElement(currentNode);
|
||
|
trailingNodes = [];
|
||
|
}else{
|
||
|
trailingNodes.unshift(currentNode);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var pList = [];
|
||
|
var ps = element.getElementsByTagName('p');
|
||
|
dojo.forEach(ps, function(p){ pList.push(p); });
|
||
|
dojo.forEach(pList, function(p){
|
||
|
if( (p.previousSibling) &&
|
||
|
(p.previousSibling.nodeName == 'P' || dojo.style(p.previousSibling, 'display') != 'block')
|
||
|
){
|
||
|
var newP = p.parentNode.insertBefore(this.document.createElement('p'), p);
|
||
|
// this is essential to prevent IE from losing the P.
|
||
|
// if it's going to be innerHTML'd later we need
|
||
|
// to add the to _really_ force the issue
|
||
|
newP.innerHTML = noWhiteSpaceInEmptyP ? "" : " ";
|
||
|
}
|
||
|
splitP(p);
|
||
|
},this.editor);
|
||
|
wrapLinesInPs(element);
|
||
|
return element;
|
||
|
},
|
||
|
|
||
|
singleLinePsToRegularPs: function(element){
|
||
|
function getParagraphParents(node){
|
||
|
var ps = node.getElementsByTagName('p');
|
||
|
var parents = [];
|
||
|
for(var i=0; i<ps.length; i++){
|
||
|
var p = ps[i];
|
||
|
var knownParent = false;
|
||
|
for(var k=0; k < parents.length; k++){
|
||
|
if(parents[k] === p.parentNode){
|
||
|
knownParent = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if(!knownParent){
|
||
|
parents.push(p.parentNode);
|
||
|
}
|
||
|
}
|
||
|
return parents;
|
||
|
}
|
||
|
|
||
|
function isParagraphDelimiter(node){
|
||
|
if(node.nodeType != 1 || node.tagName != 'P'){
|
||
|
return dojo.style(node, 'display') == 'block';
|
||
|
}else{
|
||
|
if(!node.childNodes.length || node.innerHTML==" "){ return true; }
|
||
|
//return node.innerHTML.match(/^(<br\ ?\/?>| |\ \;)$/i);
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
var paragraphContainers = getParagraphParents(element);
|
||
|
for(var i=0; i<paragraphContainers.length; i++){
|
||
|
var container = paragraphContainers[i];
|
||
|
var firstPInBlock = null;
|
||
|
var node = container.firstChild;
|
||
|
var deleteNode = null;
|
||
|
while(node){
|
||
|
if(node.nodeType != "1" || node.tagName != 'P'){
|
||
|
firstPInBlock = null;
|
||
|
}else if (isParagraphDelimiter(node)){
|
||
|
deleteNode = node;
|
||
|
firstPInBlock = null;
|
||
|
}else{
|
||
|
if(firstPInBlock == null){
|
||
|
firstPInBlock = node;
|
||
|
}else{
|
||
|
if( (!firstPInBlock.lastChild || firstPInBlock.lastChild.nodeName != 'BR') &&
|
||
|
(node.firstChild) &&
|
||
|
(node.firstChild.nodeName != 'BR')
|
||
|
){
|
||
|
firstPInBlock.appendChild(this.editor.document.createElement('br'));
|
||
|
}
|
||
|
while(node.firstChild){
|
||
|
firstPInBlock.appendChild(node.firstChild);
|
||
|
}
|
||
|
deleteNode = node;
|
||
|
}
|
||
|
}
|
||
|
node = node.nextSibling;
|
||
|
if(deleteNode){
|
||
|
dojo._destroyElement(deleteNode);
|
||
|
deleteNode = null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return element;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
}
|