diff --git a/Demo.html b/Demo.html index 17a44b2..5d0d339 100644 --- a/Demo.html +++ b/Demo.html @@ -58,8 +58,8 @@

Text colour - Text highlight - Link + Text highlight + Link

Quote diff --git a/build/document.html b/build/document.html index 734a865..280b317 100644 --- a/build/document.html +++ b/build/document.html @@ -36,12 +36,12 @@ } h4,h5,h6 { margin: 0; - } + } ul, ol { margin: 0 1em; padding: 0 1em; } - + blockquote { border-left: 2px solid blue; margin: 0; diff --git a/build/squire.js b/build/squire.js index 261f776..b776692 100644 --- a/build/squire.js +++ b/build/squire.js @@ -1 +1 @@ -/* Copyright © 2011 by Neil Jenkins. Licensed under the MIT license. */(function(a){"use strict";var b=!a.createTreeWalker;window.ie===9&&(b=!0),b||function(){var c=a.createElement("div"),d=a.createTextNode("");c.appendChild(d);var e=c.cloneNode(!0),f=c.cloneNode(!0),g=c.cloneNode(!0),h=a.createTreeWalker(c,1,function(a){return 1},!1);c.appendChild(e),c.appendChild(f),c.appendChild(g),h.currentNode=g,h.previousNode()!==f&&(b=!0)}();if(!b)return;var c={1:1,2:2,3:4,8:128,9:256,11:1024},d=1,e=function(a,b,c){this.root=this.currentNode=a,this.nodeType=b,this.filter=c};e.prototype.nextNode=function(){var a=this.currentNode,b=this.root,e=this.nodeType,f=this.filter,g;for(;;){g=a.firstChild;while(!g&&a){if(a===b)break;g=a.nextSibling,g||(a=a.parentNode)}if(!g)return null;if(c[g.nodeType]&e&&f(g)===d)return this.currentNode=g,g;a=g}},e.prototype.previousNode=function(){var a=this.currentNode,b=this.root,e=this.nodeType,f=this.filter,g;for(;;){if(a===b)return null;g=a.previousSibling;if(g)while(a=g.lastChild)g=a;else g=a.parentNode;if(!g)return null;if(c[g.nodeType]&e&&f(g)===d)return this.currentNode=g,g;a=g}},a.createTreeWalker=function(a,b,c){return new e(a,b,c)}})(document),function(){"use strict";var a=function(a,b){var c=a.prototype,d;for(d in b)c[d]=b[d]},b=function(a,b){var c=a.length;while(c--)if(!b(a[c]))return!1;return!0},c=function(){return!1},d=function(){return!0},e=/^(?:A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:FN|EL)|EM|HR|I(?:NPUT|MG|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:U[BP]|PAN|TRONG|AMP)|U)$/,f={BR:1,IMG:1,INPUT:1},g=function(a,b){var c=b.parentNode;return c&&c.replaceChild(a,b),a},h=1,i=3,j=1,k=1,l=3,m=function(a){return a.isBlock()?k:l},n=!!window.opera||!!window.ie;a(Node,{isInline:c,isBlock:c,isContainer:c,getPath:function(){var a=this.parentNode;return a?a.getPath():""},detach:function(){var a=this.parentNode;return a&&a.removeChild(this),this},replaceWith:function(a){return g(a,this),this},replaces:function(a){return g(this,a),this},nearest:function(a,b){var c=this.parentNode;return c?c.nearest(a,b):null},getPreviousBlock:function(){var a=this.ownerDocument,b=a.createTreeWalker(a.body,j,m,!1);return b.currentNode=this,b.previousNode()},getNextBlock:function(){var a=this.ownerDocument,b=a.createTreeWalker(a.body,j,m,!1);return b.currentNode=this,b.nextNode()},split:function(a,b){return a},mergeContainers:function(){}}),a(Text,{isLeaf:d,isInline:d,getLength:function(){return this.length},isLike:function(a){return a.nodeType===i},split:function(a,b){var c=this;return c===b?a:c.parentNode.split(c.splitText(a),b)}}),a(Element,{isLeaf:function(){return!!f[this.nodeName]},isInline:function(){return e.test(this.nodeName)},isBlock:function(){return!this.isInline()&&b(this.childNodes,function(a){return a.isInline()})},isContainer:function(){return!this.isInline()&&!this.isBlock()},getLength:function(){return this.childNodes.length},getPath:function(){var a=this.nodeName;if(a==="BODY")return a;var b=this.parentNode.getPath(),c=this.id,d=this.className.trim();return b+=">"+a,c&&(b+="#"+c),d&&(d=d.split(/\s\s*/),d.sort(),b+=".",b+=d.join(".")),b},wraps:function(a){return g(this,a).appendChild(a),this},empty:function(){var a=this.ownerDocument.createDocumentFragment(),b=this.childNodes.length;while(b--)a.appendChild(this.firstChild);return a},is:function(a,b){if(this.nodeName!==a)return!1;var c;for(c in b)if(this.getAttribute(c)!==b[c])return!1;return!0},nearest:function(a,b){var c=this;do if(c.is(a,b))return c;while((c=c.parentNode)&&c.nodeType===h);return null},isLike:function(a){return a.nodeType===h&&a.nodeName===this.nodeName&&a.className===this.className&&a.style.cssText===this.style.cssText},mergeInlines:function(a){var b=this.childNodes,c=b.length,d=[],e,g,j;while(c--){e=b[c],g=c&&b[c-1];if(c&&e.isInline()&&e.isLike(g)&&!f[e.nodeName])a.startContainer===e&&(a.startContainer=g,a.startOffset+=g.getLength()),a.endContainer===e&&(a.endContainer=g,a.endOffset+=g.getLength()),a.startContainer===this&&(a.startOffset>c?a.startOffset-=1:a.startOffset===c&&(a.startContainer=g,a.startOffset=g.getLength())),a.endContainer===this&&(a.endOffset>c?a.endOffset-=1:a.endOffset===c&&(a.endContainer=g,a.endOffset=g.getLength())),e.detach(),e.nodeType===i?g.appendData(e.data):d.push(e.empty());else if(e.nodeType===h){j=d.length;while(j--)e.appendChild(d.pop());e.mergeInlines(a)}}},mergeWithBlock:function(a,b){var c=this,d=a,e,f,g;while(d.parentNode.childNodes.length===1)d=d.parentNode;d.detach(),f=c.childNodes.length,e=c.lastChild,e&&e.nodeName==="BR"&&(c.removeChild(e),f-=1),g={startContainer:c,startOffset:f,endContainer:c,endOffset:f},c.appendChild(a.empty()),c.mergeInlines(g),b.setStart(g.startContainer,g.startOffset),b.collapse(!0),window.opera&&(e=c.lastChild)&&e.nodeName==="BR"&&c.removeChild(e)},mergeContainers:function(){var a=this.previousSibling,b=this.firstChild;a&&a.isLike(this)&&a.isContainer()&&(a.appendChild(this.detach().empty()),b&&b.mergeContainers())},split:function(a,b){var c=this;typeof a=="number"&&(a=a-1,f=c.compareBoundaryPoints(h,d)<1;return!e&&!f}var k=c.compareBoundaryPoints(g,d)<1,l=c.compareBoundaryPoints(i,d)>-1;return k&&l},moveBoundariesDownTree:function(){var a=this.startContainer,b=this.startOffset,c=this.endContainer,e=this.endOffset,f;while(a.nodeType!==d){f=a.childNodes[b];if(!f||f.nodeName==="BR")break;a=f,b=0}if(e)while(c.nodeType!==d){f=c.childNodes[e-1];if(!f||f.nodeName==="BR")break;c=f,e=c.getLength()}else while(c.nodeType!==d){f=c.firstChild;if(!f||f.nodeName==="BR")break;c=f}return this.collapsed?(this.setStart(c,e),this.setEnd(a,b)):(this.setStart(a,b),this.setEnd(c,e)),this},moveBoundariesUpTree:function(a){var c=this.startContainer,d=this.startOffset,e=this.endContainer,f=this.endOffset,g;a||(a=this.commonAncestorContainer);while(c!==a&&!d)g=c.parentNode,d=b.call(g.childNodes,c),c=g;while(e!==a&&f===e.getLength())g=e.parentNode,f=b.call(g.childNodes,e)+1,e=g;return this.setStart(c,d),this.setEnd(e,f),this},getStartBlock:function(){var a=this.startContainer,b;return a.isInline()?b=a.getPreviousBlock():a.isBlock()?b=a:(b=k(a,this.startOffset),b=b.getNextBlock()),b&&this.containsNode(b,!0)?b:null},getEndBlock:function(){var a=this.endContainer,b,c;if(a.isInline())b=a.getPreviousBlock();else if(a.isBlock())b=a;else{b=l(a,this.endOffset);if(!b){b=a.ownerDocument.body;while(c=b.lastChild)b=c}b=b.getPreviousBlock()}return b&&this.containsNode(b,!0)?b:null},startsAtBlockBoundary:function(){var a=this.startContainer,c=this.startOffset,d,e;while(a.isInline()){if(c)return!1;d=a.parentNode,c=b.call(d.childNodes,a),a=d}while(c&&(e=a.childNodes[c-1])&&(e.data===""||e.nodeName==="BR"))c-=1;return!c},endsAtBlockBoundary:function(){var a=this.endContainer,c=this.endOffset,d=a.getLength(),e,f;while(a.isInline()){if(c!==d)return!1;e=a.parentNode,c=b.call(e.childNodes,a)+1,a=e,d=a.childNodes.length}while(c20)&&(b<33||b>45)&&R()});var S=function(a){Q||(N+=1,Nf.endOffset?p.splitText(f.endOffset):o=f.endOffset),p===f.startContainer&&(q&&f.startOffset?p=p.splitText(f.startOffset):m=f.startOffset),q&&(n(b,c).wraps(p),o=p.length),l=p,k||(k=l);while(p=j.nextNode());f=u(k,m,l,o)}return f},X=function(b,c,e,f){K(e),e.collapsed&&e._insertNode(a.createTextNode(""));var g=e.commonAncestorContainer;while(g.isInline())g=g.parentNode;var h=e.startContainer,i=e.startOffset,j=e.endContainer,k=e.endOffset,l=[],m=function p(a,b){if(e.containsNode(a,!1))return;var c=a.nodeType===d,f,g;if(!e.containsNode(a,!0)){a.nodeName!=="INPUT"&&l.push([b,a]);return}if(c)a===j&&k!==a.length&&l.push([b,a.splitText(k)]),a===h&&i&&(a.splitText(i),l.push([b,a]));else for(f=a.firstChild;f;f=g)g=f.nextSibling,p(f,b)},n=Array.prototype.filter.call(g.getElementsByTagName(b),function(a){return e.containsNode(a,!0)&&a.is(b,c)});f||n.forEach(function(a){m(a,a)}),l.forEach(function(a){a[0].cloneNode(!1).wraps(a[1])}),n.forEach(function(a){a.replaceWith(a.empty())}),e=M();var o={startContainer:e.startContainer,startOffset:e.startOffset,endContainer:e.endContainer,endOffset:e.endOffset};return g.mergeInlines(o),e.setStart(o.startContainer,o.startOffset),e.setEnd(o.endContainer,o.endOffset),e},Y=function(a,b,c,d){if(!c&&!(c=x()))return;S(c),M(c),b&&(c=X(b.tag.toUpperCase(),b.attributes||{},c,d)),a&&(c=W(a.tag.toUpperCase(),a.attributes||{},c)),C(c),B(0,!0),R()},Z=function(a,b,c){if(!c&&!(c=x()))return;b&&(S(c),M(c));var d=c.getStartBlock(),e=c.getEndBlock();if(d&&e)for(;;){if(a(d)||d===e)break;d=d.getNextBlock()}b&&(C(c),B(0,!0),R())},$=function(a,b){if(!b&&!(b=x()))return;k||j.setAttribute("contenteditable","false"),Q?K(b):S(b),b.expandToBlockBoundaries(),b.moveBoundariesUpTree(j);var c=b._extractContents(j);b._insertNode(a(c)),b.endOffset]*>([\s\S]*?)<\/style>/gi,bv=function(a){return function(){return a.apply(null,arguments),this}},bw=function(a,b,c){return function(){return a(b,c),D(),this}};i.editor={addEventListener:bv(s),removeEventListener:bv(t),focus:bv(D),blur:bv(E),getDocument:function(){return a},addStyles:function(b){if(b){var c=n("STYLE",{type:"text/css"});c.appendChild(a.createTextNode(b)),a.documentElement.firstChild.appendChild(c)}return this},getHTML:function(){var a=[],b,c,d,e;if(m){b=j;while(b=b.getNextBlock())!b.textContent&&!b.querySelector("BR")&&(c=n("BR"),b.appendChild(c),a.push(c))}d=F();if(m){e=a.length;while(e--)a[e].detach()}return d},setHTML:function(b){var c=a.createDocumentFragment(),d=n("DIV"),e;d.innerHTML=b,c.appendChild(d.empty()),bl(c,!0),bn(c),bm(c,"DIV");var f=c;while(f=f.getNextBlock())f.fixCursor();while(e=j.lastChild)j.removeChild(e);j.appendChild(c),j.fixCursor(),N=-1,O=[],P=0,Q=!1;var g=u(j.firstChild,0);return S(g),C(M(g)),B(0,!0),this},getSelectedText:function(){return x().getTextContent()},insertImage:function(a){var b=n("IMG",{src:a});return H(b),b},getPath:function(){return A},getSelection:x,setSelection:bv(C),undo:bv(T),redo:bv(U),hasFormat:V,changeFormat:bv(Y),bold:bw(Y,{tag:"B"}),italic:bw(Y,{tag:"I"}),underline:bw(Y,{tag:"U"}),removeBold:bw(Y,null,{tag:"B"}),removeItalic:bw(Y,null,{tag:"I"}),removeUnderline:bw(Y,null,{tag:"U"}),makeLink:function(b){b=encodeURI(b);var c=x();if(c.collapsed){var d=b.indexOf(":")+1;if(d)while(b[d]==="/")d+=1;c._insertNode(a.createTextNode(b.slice(d)))}return Y({tag:"A",attributes:{href:b}},{tag:"A"},c),D(),this},removeLink:function(){return Y(null,{tag:"A"},x(),!0),D(),this},setFontFace:function(a){return Y({tag:"SPAN",attributes:{"class":"font",style:"font-family: "+a+", sans-serif;"}},{tag:"SPAN",attributes:{"class":"font"}}),D(),this},setFontSize:function(a){return Y({tag:"SPAN",attributes:{"class":"size",style:"font-size: "+(typeof a=="number"?a+"px":a)}},{tag:"SPAN",attributes:{"class":"size"}}),D(),this},setTextColour:function(a){return Y({tag:"SPAN",attributes:{"class":"colour",style:"color: "+a}},{tag:"SPAN",attributes:{"class":"colour"}}),D(),this},setHighlightColour:function(a){return Y({tag:"SPAN",attributes:{"class":"highlight",style:"background-color: "+a}},{tag:"SPAN",attributes:{"class":"highlight"}}),D(),this},setTextAlignment:function(a){return Z(function(b){b.className="align-"+a,b.style.textAlign=a},!0),D(),this},forEachBlock:bv(Z),modifyBlocks:bv($),incQuoteLevel:bw($,_),decQuoteLevel:bw($,ba),makeUnorderedList:bw($,bd),makeOrderedList:bw($,be),removeList:bw($,bf)},j.setAttribute("contenteditable","true"),i.editor.setHTML(""),i.onEditorLoad&&(i.onEditorLoad(i.editor),delete i.onEditorLoad)}(document); \ No newline at end of file +/* Copyright © 2011 by Neil Jenkins. Licensed under the MIT license. */(function(a){"use strict";var b=!a.createTreeWalker;window.ie===9&&(b=!0),b||function(){var c=a.createElement("div"),d=a.createTextNode("");c.appendChild(d);var e=c.cloneNode(!0),f=c.cloneNode(!0),g=c.cloneNode(!0),h=a.createTreeWalker(c,1,function(a){return 1},!1);c.appendChild(e),c.appendChild(f),c.appendChild(g),h.currentNode=g,h.previousNode()!==f&&(b=!0)}();if(!b)return;var c={1:1,2:2,3:4,8:128,9:256,11:1024},d=1,e=function(a,b,c){this.root=this.currentNode=a,this.nodeType=b,this.filter=c};e.prototype.nextNode=function(){var a=this.currentNode,b=this.root,e=this.nodeType,f=this.filter,g;for(;;){g=a.firstChild;while(!g&&a){if(a===b)break;g=a.nextSibling,g||(a=a.parentNode)}if(!g)return null;if(c[g.nodeType]&e&&f(g)===d)return this.currentNode=g,g;a=g}},e.prototype.previousNode=function(){var a=this.currentNode,b=this.root,e=this.nodeType,f=this.filter,g;for(;;){if(a===b)return null;g=a.previousSibling;if(g)while(a=g.lastChild)g=a;else g=a.parentNode;if(!g)return null;if(c[g.nodeType]&e&&f(g)===d)return this.currentNode=g,g;a=g}},a.createTreeWalker=function(a,b,c){return new e(a,b,c)}})(document),function(){"use strict";var a=function(a,b){var c=a.prototype,d;for(d in b)c[d]=b[d]},b=function(a,b){var c=a.length;while(c--)if(!b(a[c]))return!1;return!0},c=function(){return!1},d=function(){return!0},e=/^(?:A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:FN|EL)|EM|HR|I(?:NPUT|MG|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:U[BP]|PAN|TRONG|AMP)|U)$/,f={BR:1,IMG:1,INPUT:1},g=function(a,b){var c=b.parentNode;return c&&c.replaceChild(a,b),a},h=1,i=3,j=1,k=1,l=3,m=function(a){return a.isBlock()?k:l},n=!!window.opera||!!window.ie;a(Node,{isInline:c,isBlock:c,isContainer:c,getPath:function(){var a=this.parentNode;return a?a.getPath():""},detach:function(){var a=this.parentNode;return a&&a.removeChild(this),this},replaceWith:function(a){return g(a,this),this},replaces:function(a){return g(this,a),this},nearest:function(a,b){var c=this.parentNode;return c?c.nearest(a,b):null},getPreviousBlock:function(){var a=this.ownerDocument,b=a.createTreeWalker(a.body,j,m,!1);return b.currentNode=this,b.previousNode()},getNextBlock:function(){var a=this.ownerDocument,b=a.createTreeWalker(a.body,j,m,!1);return b.currentNode=this,b.nextNode()},split:function(a,b){return a},mergeContainers:function(){}}),a(Text,{isLeaf:d,isInline:d,getLength:function(){return this.length},isLike:function(a){return a.nodeType===i},split:function(a,b){var c=this;return c===b?a:c.parentNode.split(c.splitText(a),b)}}),a(Element,{isLeaf:function(){return!!f[this.nodeName]},isInline:function(){return e.test(this.nodeName)},isBlock:function(){return!this.isInline()&&b(this.childNodes,function(a){return a.isInline()})},isContainer:function(){return!this.isInline()&&!this.isBlock()},getLength:function(){return this.childNodes.length},getPath:function(){var a=this.nodeName;if(a==="BODY")return a;var b=this.parentNode.getPath(),c=this.id,d=this.className.trim();return b+=">"+a,c&&(b+="#"+c),d&&(d=d.split(/\s\s*/),d.sort(),b+=".",b+=d.join(".")),b},wraps:function(a){return g(this,a).appendChild(a),this},empty:function(){var a=this.ownerDocument.createDocumentFragment(),b=this.childNodes.length;while(b--)a.appendChild(this.firstChild);return a},is:function(a,b){if(this.nodeName!==a)return!1;var c;for(c in b)if(this.getAttribute(c)!==b[c])return!1;return!0},nearest:function(a,b){var c=this;do if(c.is(a,b))return c;while((c=c.parentNode)&&c.nodeType===h);return null},isLike:function(a){return a.nodeType===h&&a.nodeName===this.nodeName&&a.className===this.className&&a.style.cssText===this.style.cssText},mergeInlines:function(a){var b=this.childNodes,c=b.length,d=[],e,g,j;while(c--){e=b[c],g=c&&b[c-1];if(c&&e.isInline()&&e.isLike(g)&&!f[e.nodeName])a.startContainer===e&&(a.startContainer=g,a.startOffset+=g.getLength()),a.endContainer===e&&(a.endContainer=g,a.endOffset+=g.getLength()),a.startContainer===this&&(a.startOffset>c?a.startOffset-=1:a.startOffset===c&&(a.startContainer=g,a.startOffset=g.getLength())),a.endContainer===this&&(a.endOffset>c?a.endOffset-=1:a.endOffset===c&&(a.endContainer=g,a.endOffset=g.getLength())),e.detach(),e.nodeType===i?g.appendData(e.data):d.push(e.empty());else if(e.nodeType===h){j=d.length;while(j--)e.appendChild(d.pop());e.mergeInlines(a)}}},mergeWithBlock:function(a,b){var c=this,d=a,e,f,g;while(d.parentNode.childNodes.length===1)d=d.parentNode;d.detach(),f=c.childNodes.length,e=c.lastChild,e&&e.nodeName==="BR"&&(c.removeChild(e),f-=1),g={startContainer:c,startOffset:f,endContainer:c,endOffset:f},c.appendChild(a.empty()),c.mergeInlines(g),b.setStart(g.startContainer,g.startOffset),b.collapse(!0),window.opera&&(e=c.lastChild)&&e.nodeName==="BR"&&c.removeChild(e)},mergeContainers:function(){var a=this.previousSibling,b=this.firstChild;a&&a.isLike(this)&&a.isContainer()&&(a.appendChild(this.detach().empty()),b&&b.mergeContainers())},split:function(a,b){var c=this;typeof a=="number"&&(a=a-1,f=c.compareBoundaryPoints(h,d)<1;return!e&&!f}var k=c.compareBoundaryPoints(g,d)<1,l=c.compareBoundaryPoints(i,d)>-1;return k&&l},moveBoundariesDownTree:function(){var a=this.startContainer,b=this.startOffset,c=this.endContainer,e=this.endOffset,f;while(a.nodeType!==d){f=a.childNodes[b];if(!f||f.nodeName==="BR")break;a=f,b=0}if(e)while(c.nodeType!==d){f=c.childNodes[e-1];if(!f||f.nodeName==="BR")break;c=f,e=c.getLength()}else while(c.nodeType!==d){f=c.firstChild;if(!f||f.nodeName==="BR")break;c=f}return this.collapsed?(this.setStart(c,e),this.setEnd(a,b)):(this.setStart(a,b),this.setEnd(c,e)),this},moveBoundariesUpTree:function(a){var c=this.startContainer,d=this.startOffset,e=this.endContainer,f=this.endOffset,g;a||(a=this.commonAncestorContainer);while(c!==a&&!d)g=c.parentNode,d=b.call(g.childNodes,c),c=g;while(e!==a&&f===e.getLength())g=e.parentNode,f=b.call(g.childNodes,e)+1,e=g;return this.setStart(c,d),this.setEnd(e,f),this},getStartBlock:function(){var a=this.startContainer,b;return a.isInline()?b=a.getPreviousBlock():a.isBlock()?b=a:(b=k(a,this.startOffset),b=b.getNextBlock()),b&&this.containsNode(b,!0)?b:null},getEndBlock:function(){var a=this.endContainer,b,c;if(a.isInline())b=a.getPreviousBlock();else if(a.isBlock())b=a;else{b=l(a,this.endOffset);if(!b){b=a.ownerDocument.body;while(c=b.lastChild)b=c}b=b.getPreviousBlock()}return b&&this.containsNode(b,!0)?b:null},startsAtBlockBoundary:function(){var a=this.startContainer,c=this.startOffset,d,e;while(a.isInline()){if(c)return!1;d=a.parentNode,c=b.call(d.childNodes,a),a=d}while(c&&(e=a.childNodes[c-1])&&(e.data===""||e.nodeName==="BR"))c-=1;return!c},endsAtBlockBoundary:function(){var a=this.endContainer,c=this.endOffset,d=a.getLength(),e,f;while(a.isInline()){if(c!==d)return!1;e=a.parentNode,c=b.call(e.childNodes,a)+1,a=e,d=a.childNodes.length}while(c20)&&(b<33||b>45)&&S()});var T=function(a){R||(O+=1,Of.endOffset?p.splitText(f.endOffset):n=f.endOffset),p===f.startContainer&&(q&&f.startOffset?p=p.splitText(f.startOffset):m=f.startOffset),q&&(o(b,c).wraps(p),n=p.length),l=p,k||(k=l);while(p=j.nextNode());f=v(k,m,l,n)}return f},Y=function(b,c,e,f){L(e),e.collapsed&&e._insertNode(a.createTextNode(""));var g=e.commonAncestorContainer;while(g.isInline())g=g.parentNode;var h=e.startContainer,i=e.startOffset,j=e.endContainer,k=e.endOffset,l=[],m=function p(a,b){if(e.containsNode(a,!1))return;var c=a.nodeType===d,f,g;if(!e.containsNode(a,!0)){a.nodeName!=="INPUT"&&(!c||a.data)&&l.push([b,a]);return}if(c)a===j&&k!==a.length&&l.push([b,a.splitText(k)]),a===h&&i&&(a.splitText(i),l.push([b,a]));else for(f=a.firstChild;f;f=g)g=f.nextSibling,p(f,b)},n=Array.prototype.filter.call(g.getElementsByTagName(b),function(a){return e.containsNode(a,!0)&&a.is(b,c)});f||n.forEach(function(a){m(a,a)}),l.forEach(function(a){a[0].cloneNode(!1).wraps(a[1])}),n.forEach(function(a){a.replaceWith(a.empty())}),e=N();var o={startContainer:e.startContainer,startOffset:e.startOffset,endContainer:e.endContainer,endOffset:e.endOffset};return g.mergeInlines(o),e.setStart(o.startContainer,o.startOffset),e.setEnd(o.endContainer,o.endOffset),e},Z=function(a,b,c,d){if(!c&&!(c=y()))return;T(c),N(c),b&&(c=Y(b.tag.toUpperCase(),b.attributes||{},c,d)),a&&(c=X(a.tag.toUpperCase(),a.attributes||{},c)),D(c),C(0,!0),S()},$=function(a,b,c){if(!c&&!(c=y()))return;b&&(T(c),N(c));var d=c.getStartBlock(),e=c.getEndBlock();if(d&&e)for(;;){if(a(d)||d===e)break;d=d.getNextBlock()}b&&(D(c),C(0,!0),S())},_=function(a,b){if(!b&&!(b=y()))return;k||j.setAttribute("contenteditable","false"),R?L(b):T(b),b.expandToBlockBoundaries(),b.moveBoundariesUpTree(j);var c=b._extractContents(j);b._insertNode(a(c)),b.endOffset]+|\([^\s()<>]+\))+(?:\((?:[^\s()<>]+|(?:\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/i,bk=function(a){var b=a.ownerDocument,c=b.createTreeWalker(a,e,function(a){return a.nearest("A")?h:g},!1),d,f,i,j,k,l,m;while(d=c.nextNode()){f=d.data.split(bj),j=f.length;if(j>1){l=d.parentNode,m=d.nextSibling;for(i=0;i(a.documentElement.scrollTop||j.scrollTop)+j.offsetHeight&&i.scrollIntoView(!1),S()},backspace:function(a){var b=y();if(!b.collapsed)a.preventDefault(),b._deleteContents(),D(b),C(0,!0);else if(b.startsAtBlockBoundary()){a.preventDefault();var c=b.getStartBlock(),d=c.getPreviousBlock();if(d){d.mergeWithBlock(c,b),c=d.parentNode;while(c&&!c.nextSibling)c=c.parentNode;c&&(c=c.nextSibling)&&c.mergeContainers(),D(b)}else{if(c.nearest("UL")||c.nearest("OL"))return _(bg,b);if(c.nearest("BLOCKQUOTE"))return _(bb,b);D(b),C(0,!0)}}},"delete":function(a){var b=y();if(!b.collapsed)a.preventDefault(),b._deleteContents(),D(b),C(0,!0);else if(b.endsAtBlockBoundary()){a.preventDefault();var c=b.getStartBlock(),d=c.getNextBlock();if(d){c.mergeWithBlock(d,b),d=c.parentNode;while(d&&!d.nextSibling)d=d.parentNode;d&&(d=d.nextSibling)&&d.mergeContainers(),D(b),C(0,!0)}}},space:function(){var a=y();T(a),N(a),D(a)},"ctrl-b":bv("B"),"ctrl-i":bv("I"),"ctrl-u":bv("U"),"ctrl-y":bu(V),"ctrl-z":bu(U),"ctrl-shift-z":bu(V)};t("keydown",function(a){var b=a.keyCode||a.which,c=bt[b]||String.fromCharCode(b).toLowerCase(),d="";111]*>([\s\S]*?)<\/style>/gi,by=function(a){return function(){return a.apply(null,arguments),this}},bz=function(a,b,c){return function(){return a(b,c),E(),this}};i.editor={addEventListener:by(t),removeEventListener:by(u),focus:by(E),blur:by(F),getDocument:function(){return a},addStyles:function(b){if(b){var c=o("STYLE",{type:"text/css"});c.appendChild(a.createTextNode(b)),a.documentElement.firstChild.appendChild(c)}return this},getHTML:function(){var a=[],b,c,d,e;if(n){b=j;while(b=b.getNextBlock())!b.textContent&&!b.querySelector("BR")&&(c=o("BR"),b.appendChild(c),a.push(c))}d=G();if(n){e=a.length;while(e--)a[e].detach()}return d},setHTML:function(b){var c=a.createDocumentFragment(),d=o("DIV"),e;d.innerHTML=b,c.appendChild(d.empty()),bo(c,!0),bq(c),bp(c,"DIV");var f=c;while(f=f.getNextBlock())f.fixCursor();while(e=j.lastChild)j.removeChild(e);j.appendChild(c),j.fixCursor(),O=-1,P=[],Q=0,R=!1;var g=v(j.firstChild,0);return T(g),D(N(g)),C(0,!0),this},getSelectedText:function(){return y().getTextContent()},insertImage:function(a){var b=o("IMG",{src:a});return I(b),b},getPath:function(){return B},getSelection:y,setSelection:by(D),undo:by(U),redo:by(V),hasFormat:W,changeFormat:by(Z),bold:bz(Z,{tag:"B"}),italic:bz(Z,{tag:"I"}),underline:bz(Z,{tag:"U"}),removeBold:bz(Z,null,{tag:"B"}),removeItalic:bz(Z,null,{tag:"I"}),removeUnderline:bz(Z,null,{tag:"U"}),makeLink:function(b){b=encodeURI(b);var c=y();if(c.collapsed){var d=b.indexOf(":")+1;if(d)while(b[d]==="/")d+=1;c._insertNode(a.createTextNode(b.slice(d)))}return Z({tag:"A",attributes:{href:b}},{tag:"A"},c),E(),this},removeLink:function(){return Z(null,{tag:"A"},y(),!0),E(),this},setFontFace:function(a){return Z({tag:"SPAN",attributes:{"class":"font",style:"font-family: "+a+", sans-serif;"}},{tag:"SPAN",attributes:{"class":"font"}}),E(),this},setFontSize:function(a){return Z({tag:"SPAN",attributes:{"class":"size",style:"font-size: "+(typeof a=="number"?a+"px":a)}},{tag:"SPAN",attributes:{"class":"size"}}),E(),this},setTextColour:function(a){return Z({tag:"SPAN",attributes:{"class":"colour",style:"color: "+a}},{tag:"SPAN",attributes:{"class":"colour"}}),E(),this},setHighlightColour:function(a){return Z({tag:"SPAN",attributes:{"class":"highlight",style:"background-color: "+a}},{tag:"SPAN",attributes:{"class":"highlight"}}),E(),this},setTextAlignment:function(a){return $(function(b){b.className="align-"+a,b.style.textAlign=a},!0),E(),this},forEachBlock:by($),modifyBlocks:by(_),incQuoteLevel:bz(_,ba),decQuoteLevel:bz(_,bb),makeUnorderedList:bz(_,be),makeOrderedList:bz(_,bf),removeList:bz(_,bg)},j.setAttribute("contenteditable","true"),i.editor.setHTML(""),i.onEditorLoad&&(i.onEditorLoad(i.editor),delete i.onEditorLoad)}(document); \ No newline at end of file diff --git a/source/Editor.js b/source/Editor.js index 9e30c46..2df3a79 100644 --- a/source/Editor.js +++ b/source/Editor.js @@ -3,11 +3,11 @@ /*global Range, navigator, window, document, setTimeout */ ( function ( doc ) { - + "use strict"; - + // --- Constants --- - + var DOCUMENT_POSITION_PRECEDING = 2, // Node.DOCUMENT_POSITION_PRECEDING ELEMENT_NODE = 1, // Node.ELEMENT_NODE, TEXT_NODE = 3, // Node.TEXT_NODE, @@ -15,17 +15,17 @@ SHOW_ELEMENT = 1, // NodeFilter.SHOW_ELEMENT, FILTER_ACCEPT = 1, // NodeFilter.FILTER_ACCEPT, FILTER_SKIP = 3; // NodeFilter.FILTER_SKIP; - + var win = doc.defaultView, body = doc.body; - + var isOpera = !!win.opera; var isIE = !!win.ie; var isIOS = /iP(?:ad|hone|od)/.test( navigator.userAgent ); var useTextFixer = isIE || isOpera; // --- DOM Sugar --- - + var createElement = function ( tag, props, children ) { var el = doc.createElement( tag ), attr, i, l; @@ -45,15 +45,15 @@ } return el; }; - + // --- Events --- - + var events = {}, customEvents = { cut: 1, paste: 1, focus: 1, blur: 1, pathChange: 1, select: 1, input: 1, undoStateChange: 1 }; - + var fireEvent = function ( type, event ) { var handlers = events[ type ], l, obj; @@ -77,11 +77,11 @@ } } }; - + var propagateEvent = function ( event ) { fireEvent( event.type, event ); }; - + var addEventListener = function ( type, fn ) { var handlers = events[ type ]; if ( !handlers ) { @@ -92,7 +92,7 @@ } handlers.push( fn ); }; - + var removeEventListener = function ( type, fn ) { var handlers = events[ type ], l; @@ -111,7 +111,7 @@ } } }; - + // --- Selection and Path --- var createRange = function ( range, startOffset, endContainer, endOffset ) { @@ -127,9 +127,9 @@ } return domRange; }; - + var sel = win.getSelection(); - + var lastSelection = null; var getSelection = function () { if ( sel.rangeCount ) { @@ -138,17 +138,17 @@ } return lastSelection; }; - + // IE9 loses selection state of iframe on blur, so make sure we // cache it just before it loses focus. if ( win.ie ) { win.addEventListener( 'beforedeactivate', getSelection, true ); } - + var lastAnchorNode; var lastFocusNode; var path = ''; - + var updatePath = function ( _, force ) { var anchor = sel.anchorNode, focus = sel.focusNode, @@ -169,7 +169,7 @@ }; addEventListener( 'keyup', updatePath ); addEventListener( 'mouseup', updatePath ); - + var setSelection = function ( range ) { if ( range ) { // iOS bug: if you don't focus the iframe before setting the @@ -183,26 +183,26 @@ sel.addRange( range ); } }; - + // --- Focus --- - + var focus = function () { win.focus(); }; - + var blur = function () { win.blur(); }; - + win.addEventListener( 'focus', propagateEvent, false ); win.addEventListener( 'blur', propagateEvent, false ); - + // --- Get/Set data --- - + var getHTML = function () { return body.innerHTML; }; - + var setHTML = function ( html ) { var node = body; node.innerHTML = html; @@ -210,7 +210,7 @@ node.fixCursor(); } while ( node = node.getNextBlock() ); }; - + var insertElement = function ( el, range ) { if ( !range ) { range = getSelection(); } range.collapse( true ); @@ -219,12 +219,12 @@ setSelection( range ); updatePath(); }; - + // --- Bookmarking --- - + var startSelectionId = 'ss-' + Date.now() + '-' + Math.random(); var endSelectionId = 'es-' + Date.now() + '-' + Math.random(); - + var saveRangeToBookmark = function ( range ) { var startNode = createElement( 'INPUT', { id: startSelectionId, @@ -235,11 +235,11 @@ type: 'hidden' }), temp; - + range._insertNode( startNode ); range.collapse( false ); range._insertNode( endNode ); - + // In a collapsed range, the start is sometimes inserted after the end! if ( startNode.compareDocumentPosition( endNode ) & DOCUMENT_POSITION_PRECEDING ) { @@ -249,56 +249,56 @@ startNode = endNode; endNode = temp; } - + range.setStartAfter( startNode ); range.setEndBefore( endNode ); }; - + var indexOf = Array.prototype.indexOf; - + var getRangeAndRemoveBookmark = function ( range ) { var start = doc.getElementById( startSelectionId ), end = doc.getElementById( endSelectionId ); - + if ( start && end ) { var startContainer = start.parentNode, endContainer = end.parentNode; - + var _range = { startContainer: startContainer, endContainer: endContainer, startOffset: indexOf.call( startContainer.childNodes, start ), endOffset: indexOf.call( endContainer.childNodes, end ) }; - + if ( startContainer === endContainer ) { _range.endOffset -= 1; } - + start.detach(); end.detach(); - + // Merge any text nodes we split startContainer.mergeInlines( _range ); if ( startContainer !== endContainer ) { endContainer.mergeInlines( _range ); } - + if ( !range ) { range = doc.createRange(); } range.setStart( _range.startContainer, _range.startOffset ); range.setEnd( _range.endContainer, _range.endOffset ); - + if ( !range.collapsed ) { range.moveBoundariesDownTree(); } } return range; }; - + // --- Undo --- - + var undoIndex, // = -1, undoStack, // = [], undoStackLength, // = 0, @@ -313,7 +313,7 @@ } fireEvent( 'input' ); }; - + addEventListener( 'keyup', function ( event ) { var code = event.keyCode; // Presume document was changed if: @@ -326,7 +326,7 @@ docWasChanged(); } }); - + // Leaves bookmark var recordUndoState = function ( range ) { // Don't record if we're already in an undo state @@ -348,7 +348,7 @@ isInUndoState = true; } }; - + var undo = function () { // Sanity check: must not be at beginning of the history stack if ( undoIndex !== 0 || !isInUndoState ) { @@ -369,7 +369,7 @@ fireEvent( 'input' ); } }; - + var redo = function () { // Sanity check: must not be at end of stack and must be in an undo // state. @@ -387,9 +387,9 @@ fireEvent( 'input' ); } }; - + // --- Inline formatting --- - + // Looks for matching tag and attributes, so won't work // if instead of etc. var hasFormat = function ( tag, attributes, range ) { @@ -399,7 +399,7 @@ if ( !range && !( range = getSelection() ) ) { return false; } - + // If the common ancestor is inside the tag we require, we definitely // have the format. var root = range.commonAncestorContainer, @@ -407,20 +407,20 @@ if ( root.nearest( tag, attributes ) ) { return true; } - + // If common ancestor is a text node and doesn't have the format, we // definitely don't have it. if ( root.nodeType === TEXT_NODE ) { return false; } - + // Otherwise, check each text node at least partially contained within // the selection and make sure all of them have the format we want. walker = doc.createTreeWalker( root, SHOW_TEXT, function ( node ) { return range.containsNode( node, true ) ? FILTER_ACCEPT : FILTER_SKIP; }, false ); - + var seenNode = false; while ( node = walker.nextNode() ) { if ( !node.nearest( tag, attributes ) ) { @@ -428,10 +428,10 @@ } seenNode = true; } - + return seenNode; }; - + var addFormat = function ( tag, attributes, range ) { // If the range is collapsed we simply insert the node by wrapping // it round the range and focus it. @@ -465,11 +465,11 @@ endOffset = 0, textnode = walker.currentNode = range.startContainer, needsFormat; - + if ( textnode.nodeType !== TEXT_NODE ) { textnode = walker.nextNode(); } - + do { needsFormat = !textnode.nearest( tag, attributes ); if ( textnode === range.endContainer ) { @@ -493,30 +493,30 @@ endContainer = textnode; if ( !startContainer ) { startContainer = endContainer; } } while ( textnode = walker.nextNode() ); - + // Now set the selection to as it was before range = createRange( startContainer, startOffset, endContainer, endOffset ); } return range; }; - + var removeFormat = function ( tag, attributes, range, partial ) { // Add bookmark saveRangeToBookmark( range ); - + // We need a node in the selection to break the surrounding // formatted text. if ( range.collapsed ) { range._insertNode( doc.createTextNode( '' ) ); } - + // Find block-level ancestor of selection var root = range.commonAncestorContainer; while ( root.isInline() ) { root = root.parentNode; } - + // Find text nodes inside formatTags that are not in selection and // add an extra tag with the same formatting. var startContainer = range.startContainer, @@ -530,10 +530,10 @@ if ( range.containsNode( node, false ) ) { return; } - + var isText = node.nodeType === TEXT_NODE, child, next; - + // If not at least partially contained, wrap entire contents // in a clone of the tag we're removing and we're done. if ( !range.containsNode( node, true ) ) { @@ -544,7 +544,7 @@ } return; } - + // Split any partially selected text nodes. if ( isText ) { if ( node === endContainer && endOffset !== node.length ) { @@ -571,13 +571,13 @@ el.is( tag, attributes ); } ); - + if ( !partial ) { formatTags.forEach( function ( node ) { examineNode( node, node ); }); } - + // Now wrap unselected nodes in the tag toWrap.forEach( function ( item ) { // [ exemplar, node ] tuple @@ -587,7 +587,7 @@ formatTags.forEach( function ( el ) { el.replaceWith( el.empty() ); }); - + // Merge adjacent inlines: range = getRangeAndRemoveBookmark(); var _range = { @@ -599,20 +599,20 @@ root.mergeInlines( _range ); range.setStart( _range.startContainer, _range.startOffset ); range.setEnd( _range.endContainer, _range.endOffset ); - + return range; }; - + var changeFormat = function ( add, remove, range, partial ) { // Normalise the arguments and get selection if ( !range && !( range = getSelection() ) ) { return; } - + // Save undo checkpoint recordUndoState( range ); getRangeAndRemoveBookmark( range ); - + if ( remove ) { range = removeFormat( remove.tag.toUpperCase(), remove.attributes || {}, range, partial ); @@ -621,27 +621,27 @@ range = addFormat( add.tag.toUpperCase(), add.attributes || {}, range ); } - + setSelection( range ); updatePath( 0, true ); - + // We're not still in an undo state docWasChanged(); }; - + // --- Block formatting --- - + var forEachBlock = function ( fn, mutates, range ) { if ( !range && !( range = getSelection() ) ) { return; } - + // Save undo checkpoint if ( mutates ) { recordUndoState( range ); getRangeAndRemoveBookmark( range ); } - + var start = range.getStartBlock(), end = range.getEndBlock(); if ( start && end ) { @@ -650,18 +650,18 @@ start = start.getNextBlock(); } } - + if ( mutates ) { setSelection( range ); - + // Path may have changed updatePath( 0, true ); - + // We're not still in an undo state docWasChanged(); } }; - + var modifyBlocks = function ( modify, range ) { if ( !range && !( range = getSelection() ) ) { return; @@ -672,49 +672,49 @@ if ( !isOpera ) { body.setAttribute( 'contenteditable', 'false' ); } - + // 2. Save undo checkpoint and bookmark selection if ( isInUndoState ) { saveRangeToBookmark( range ); } else { recordUndoState( range ); } - + // 3. Expand range to block boundaries range.expandToBlockBoundaries(); - + // 4. Remove range. range.moveBoundariesUpTree( body ); var frag = range._extractContents( body ); - + // 5. Modify tree of fragment and reinsert. range._insertNode( modify( frag ) ); - + // 6. Merge containers at edges if ( range.endOffset < range.endContainer.childNodes.length ) { range.endContainer.childNodes[ range.endOffset ].mergeContainers(); } range.startContainer.childNodes[ range.startOffset ].mergeContainers(); - + // 7. Make it editable again if ( !isOpera ) { body.setAttribute( 'contenteditable', 'true' ); } - + // 8. Restore selection setSelection( getRangeAndRemoveBookmark() ); updatePath( 0, true ); - + // 9. We're not still in an undo state docWasChanged(); }; - + var increaseBlockQuoteLevel = function ( frag ) { return createElement( 'BLOCKQUOTE', [ frag ]); }; - + var decreaseBlockQuoteLevel = function ( frag ) { var blockquotes = frag.querySelectorAll( 'blockquote' ); Array.prototype.filter.call( blockquotes, function ( el ) { @@ -724,7 +724,7 @@ }); return frag; }; - + var removeBlockQuote = function ( frag ) { var blockquotes = frag.querySelectorAll( 'blockquote' ), l = blockquotes.length, @@ -735,7 +735,7 @@ } return frag; }; - + var makeList = function makeList ( nodes, type ) { var i, l, node, tag, prev, replacement; for ( i = 0, l = nodes.length; i < l; i += 1 ) { @@ -775,17 +775,17 @@ } } }; - + var makeUnorderedList = function ( frag ) { makeList( frag.childNodes, 'UL' ); return frag; }; - + var makeOrderedList = function ( frag ) { makeList( frag.childNodes, 'OL' ); return frag; }; - + var decreaseListLevel = function ( frag ) { var lists = frag.querySelectorAll( 'UL, OL' ); Array.prototype.filter.call( lists, function ( el ) { @@ -808,7 +808,7 @@ }); return frag; }; - + var tagAfterSplit = { DIV: 'DIV', PRE: 'DIV', @@ -823,11 +823,11 @@ DD: 'DT', LI: 'LI' }; - + var splitBlock = function ( block, node, offset ) { var splitTag = tagAfterSplit[ block.nodeName ], nodeAfterSplit = node.split( offset, block.parentNode ); - + // Make sure the new node is the correct type. if ( nodeAfterSplit.nodeName !== splitTag ) { block = createElement( splitTag ); @@ -837,9 +837,9 @@ } return nodeAfterSplit; }; - + // --- Clean --- - + var urlRegExp = /\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\([^\s()<>]+\))+(?:\((?:[^\s()<>]+|(?:\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/i; var addLinks = function ( frag ) { var doc = frag.ownerDocument, @@ -878,9 +878,9 @@ } } }; - + var allowedBlock = /^A(?:DDRESS|RTICLE|SIDE)|BLOCKQUOTE|CAPTION|D(?:[DLT]|IV)|F(?:IGURE|OOTER)|H[1-6]|HEADER|L(?:ABEL|EGEND|I)|O(?:L|UTPUT)|P(?:RE)?|SECTION|T(?:ABLE|BODY|D|FOOT|H|HEAD|R)|UL$/; - + var spanToSemantic = { color: { regexp: /\S/, @@ -922,12 +922,12 @@ } } }; - + var stylesRewriters = { SPAN: function ( span, parent ) { var style = span.style, attr, converter, css, newTreeBottom, newTreeTop, el; - + for ( attr in spanToSemantic ) { converter = spanToSemantic[ attr ]; css = style[ attr ]; @@ -942,12 +942,12 @@ } } } - + if ( newTreeTop ) { newTreeBottom.appendChild( span.empty() ); parent.replaceChild( newTreeTop, span ); } - + return newTreeBottom || span; }, STRONG: function ( node, parent ) { @@ -972,10 +972,10 @@ return el; } }; - + /* Two purposes: - + 1. Remove nodes we don't want, such as weird tags, comment nodes and whitespace nodes. 2. Convert inline tags into our preferred format. @@ -1016,7 +1016,7 @@ } return node; }; - + var wrapTopLevelInline = function ( root, tag ) { var children = root.childNodes, wrapper = null, @@ -1047,12 +1047,12 @@ } return root; }; - + var cleanupBRs = function ( root ) { var brs = root.querySelectorAll( 'BR' ), l = brs.length, br, block, nodeAfterSplit, div, next; - + while ( l-- ) { br = brs[l]; // Cleanup may have removed it @@ -1078,14 +1078,14 @@ } } }; - + // --- Cut and Paste --- - + var afterCut = function () { // If all content removed, ensure div at start of body. body.fixCursor(); }; - + doc.addEventListener( isIE ? 'beforecut' : 'cut', function () { // Save undo checkpoint var range = getSelection(); @@ -1093,21 +1093,21 @@ getRangeAndRemoveBookmark( range ); setTimeout( afterCut, 0 ); }, false ); - + // IE sometimes fires the beforepaste event twice; make sure it is not run // again before our after paste function is called. var awaitingPaste = false; - + doc.addEventListener( isIE ? 'beforepaste' : 'paste', function () { if ( awaitingPaste ) { return; } awaitingPaste = true; - + var range = getSelection(), startContainer = range.startContainer, startOffset = range.startOffset, endContainer = range.endContainer, endOffset = range.endOffset; - + var pasteArea = createElement( 'DIV', { style: 'position: absolute; overflow: hidden;' + 'top: -100px; left: -100px; width: 1px; height: 1px;' @@ -1115,7 +1115,7 @@ body.appendChild( pasteArea ); range.selectNodeContents( pasteArea ); setSelection( range ); - + // A setTimeout of 0 means this is added to the back of the // single javascript thread, so it will be executed after the // paste event. @@ -1125,7 +1125,7 @@ first = frag.firstChild, range = createRange( startContainer, startOffset, endContainer, endOffset ); - + // Was anything actually pasted? if ( first ) { // Safari likes putting extra divs around things. @@ -1142,23 +1142,23 @@ while ( node = node.getNextBlock() ) { node.fixCursor(); } - + // Insert pasted data range.insertTreeFragment( frag ); docWasChanged(); - + range.collapse( false ); } - + setSelection( range ); updatePath( 0, true ); - + awaitingPaste = false; }, 0 ); }, false ); - + // --- Keyboard interaction --- - + var keys = { 8: 'backspace', 9: 'tab', @@ -1166,14 +1166,14 @@ 32: 'space', 46: 'delete' }; - + var mapKeyTo = function ( fn ) { return function ( event ) { event.preventDefault(); fn(); }; }; - + var mapKeyToFormat = function ( tag ) { return function ( event ) { event.preventDefault(); @@ -1185,31 +1185,31 @@ } }; }; - + var keyHandlers = { enter: function ( event ) { // We handle this ourselves event.preventDefault(); - + // Must have some form of selection var range = getSelection(); if ( !range ) { return; } - + // Save undo checkpoint recordUndoState( range ); getRangeAndRemoveBookmark( range ); - + // Selected text is overwritten, therefore delete the contents // to collapse selection. if ( !range.collapsed ) { range._deleteContents(); } - + var block = range.getStartBlock(), tag = block ? block.nodeName : 'DIV', splitTag = tagAfterSplit[ tag ], nodeAfterSplit; - + // If this is a malformed bit of document, just play it safe // and insert a
. if ( !block ) { @@ -1220,7 +1220,7 @@ docWasChanged(); return; } - + // We need to wrap the contents in divs. var splitNode = range.startContainer, splitOffset = range.startOffset, @@ -1259,7 +1259,7 @@ range.setEnd( splitNode, splitOffset ); block = range.getStartBlock(); } - + if ( !block.textContent ) { // Break list if ( block.nearest( 'UL' ) || block.nearest( 'OL' ) ) { @@ -1270,17 +1270,17 @@ return modifyBlocks( removeBlockQuote, range ); } } - + // Otherwise, split at cursor point. nodeAfterSplit = splitBlock( block, splitNode, splitOffset ); - + // Focus cursor // If there's a / etc. at the beginning of the split // make sure we focus inside it. while ( nodeAfterSplit.nodeType === ELEMENT_NODE) { var child = nodeAfterSplit.firstChild, next; - + // Don't continue links over a block break; unlikely to be the // desired outcome. if ( nodeAfterSplit.nodeName === 'A' ) { @@ -1288,7 +1288,7 @@ nodeAfterSplit = child; continue; } - + while ( child && child.nodeType === TEXT_NODE && !child.data ) { next = child.nextSibling; if ( !next || next.nodeName === 'BR' ) { @@ -1297,7 +1297,7 @@ child.detach(); child = next; } - + // 'BR's essentially don't count; they're a browser hack. // If you try to select the contents of a 'BR', FF will not let // you type anything! @@ -1309,7 +1309,7 @@ } setSelection( createRange( nodeAfterSplit, 0 ) ); updatePath( 0, true ); - + // Scroll into view if ( nodeAfterSplit.nodeType === TEXT_NODE ) { nodeAfterSplit = nodeAfterSplit.parentNode; @@ -1319,7 +1319,7 @@ body.offsetHeight ) { nodeAfterSplit.scrollIntoView( false ); } - + // We're not still in an undo state docWasChanged(); }, @@ -1413,40 +1413,40 @@ 'ctrl-z': mapKeyTo( undo ), 'ctrl-shift-z': mapKeyTo( redo ) }; - + addEventListener( 'keydown', function ( event ) { // Ref: http://unixpapa.com/js/key.html var code = event.keyCode || event.which, key = keys[ code ] || String.fromCharCode( code ).toLowerCase(), modifiers = ''; - + // Function keys if ( 111 < code && code < 124 ) { key = 'f' + ( code - 111 ); } - + if ( event.altKey ) { modifiers += 'alt-'; } if ( event.ctrlKey || event.metaKey ) { modifiers += 'ctrl-'; } if ( event.shiftKey ) { modifiers += 'shift-'; } - + key = modifiers + key; - + if ( keyHandlers[ key ] ) { keyHandlers[ key ]( event ); } }); - + // --- Export --- - + var styleExtractor = /]*>([\s\S]*?)<\/style>/gi; - + var chain = function ( fn ) { return function () { fn.apply( null, arguments ); return this; }; }; - + var command = function ( fn, arg, arg2 ) { return function () { fn( arg, arg2 ); @@ -1454,19 +1454,19 @@ return this; }; }; - + win.editor = { - + addEventListener: chain( addEventListener ), removeEventListener: chain( removeEventListener ), - + focus: chain( focus ), blur: chain( blur ), - + getDocument: function () { return doc; }, - + addStyles: function ( styles ) { if ( styles ) { var style = createElement( 'STYLE', { @@ -1477,7 +1477,7 @@ } return this; }, - + getHTML: function () { var brs = [], node, fixer, html, l; @@ -1504,50 +1504,50 @@ var frag = doc.createDocumentFragment(), div = createElement( 'DIV' ), child; - + // Parse HTML into DOM tree div.innerHTML = html; frag.appendChild( div.empty() ); - + cleanTree( frag, true ); cleanupBRs( frag ); - + wrapTopLevelInline( frag, 'DIV' ); - + // Fix cursor var node = frag; while ( node = node.getNextBlock() ) { node.fixCursor(); } - + // Remove existing body children while ( child = body.lastChild ) { body.removeChild( child ); } - + // And insert new content body.appendChild( frag ); body.fixCursor(); - + // Reset the undo stack undoIndex = -1; undoStack = []; undoStackLength = 0; isInUndoState = false; - + // Record undo state var range = createRange( body.firstChild, 0 ); recordUndoState( range ); setSelection( getRangeAndRemoveBookmark( range ) ); updatePath( 0, true ); - + return this; }, - + getSelectedText: function () { return getSelection().getTextContent(); }, - + insertImage: function ( src ) { var img = createElement( 'IMG', { src: src @@ -1555,27 +1555,27 @@ insertElement( img ); return img; }, - + getPath: function () { return path; }, getSelection: getSelection, setSelection: chain( setSelection ), - + undo: chain( undo ), redo: chain( redo ), - + hasFormat: hasFormat, changeFormat: chain( changeFormat ), - + bold: command( changeFormat, { tag: 'B' } ), italic: command( changeFormat, { tag: 'I' } ), underline: command( changeFormat, { tag: 'U' } ), - + removeBold: command( changeFormat, null, { tag: 'B' } ), removeItalic: command( changeFormat, null, { tag: 'I' } ), removeUnderline: command( changeFormat, null, { tag: 'U' } ), - + makeLink: function ( url ) { url = encodeURI( url ); var range = getSelection(); @@ -1599,7 +1599,7 @@ focus(); return this; }, - + removeLink: function () { changeFormat( null, { tag: 'A' @@ -1607,7 +1607,7 @@ focus(); return this; }, - + setFontFace: function ( name ) { changeFormat({ tag: 'SPAN', @@ -1637,7 +1637,7 @@ focus(); return this; }, - + setTextColour: function ( colour ) { changeFormat({ tag: 'SPAN', @@ -1652,7 +1652,7 @@ focus(); return this; }, - + setHighlightColour: function ( colour ) { changeFormat({ tag: 'SPAN', @@ -1667,7 +1667,7 @@ focus(); return this; }, - + setTextAlignment: function ( dir ) { forEachBlock( function ( block ) { block.className = 'align-' + dir; @@ -1676,26 +1676,26 @@ focus(); return this; }, - + forEachBlock: chain( forEachBlock ), modifyBlocks: chain( modifyBlocks ), - + incQuoteLevel: command( modifyBlocks, increaseBlockQuoteLevel ), decQuoteLevel: command( modifyBlocks, decreaseBlockQuoteLevel ), - + makeUnorderedList: command( modifyBlocks, makeUnorderedList ), makeOrderedList: command( modifyBlocks, makeOrderedList ), removeList: command( modifyBlocks, decreaseListLevel ) }; - + // --- Initialise --- - + body.setAttribute( 'contenteditable', 'true' ); win.editor.setHTML( '' ); - + if ( win.onEditorLoad ) { win.onEditorLoad( win.editor ); delete win.onEditorLoad; } - + }( document ) ); \ No newline at end of file diff --git a/source/Node.js b/source/Node.js index ae224e1..ca1ed93 100644 --- a/source/Node.js +++ b/source/Node.js @@ -1,7 +1,7 @@ /* Copyright © 2011 by Neil Jenkins. Licensed under the MIT license. */ ( function () { - + /*global Node, Text, Element, window, document */ "use strict"; @@ -145,7 +145,7 @@ implement( Element, { var path = this.parentNode.getPath(), id = this.id, className = this.className.trim(); - + path += '>' + tag; if ( id ) { path += '#' + id; @@ -259,30 +259,30 @@ implement( Element, { container = container.parentNode; } container.detach(); - + offset = block.childNodes.length; - + // Remove extra
fixer if present. last = block.lastChild; if ( last && last.nodeName === 'BR' ) { block.removeChild( last ); offset -= 1; } - + _range = { startContainer: block, startOffset: offset, endContainer: block, endOffset: offset }; - + block.appendChild( next.empty() ); block.mergeInlines( _range ); - + range.setStart( _range.startContainer, _range.startOffset ); range.collapse( true ); - + // Opera inserts a BR if you delete the last piece of text // in a block-level element. Unfortunately, it then gets // confused when setting the selection subsequently and @@ -308,43 +308,43 @@ implement( Element, { }, split: function ( childNodeToSplitBefore, stopNode ) { var node = this; - + if ( typeof( childNodeToSplitBefore ) === 'number' ) { childNodeToSplitBefore = childNodeToSplitBefore < node.childNodes.length ? node.childNodes[ childNodeToSplitBefore ] : null; } - + if ( node === stopNode ) { return childNodeToSplitBefore; } - + // Clone node without children var parent = node.parentNode, clone = node.cloneNode( false ), next; - + // Add right-hand siblings to the clone while ( childNodeToSplitBefore ) { next = childNodeToSplitBefore.nextSibling; clone.appendChild( childNodeToSplitBefore ); childNodeToSplitBefore = next; } - + // DO NOT NORMALISE. This may undo the fixCursor() call // of a node lower down the tree! - + // We need something in the element in order for the cursor to appear. node.fixCursor(); clone.fixCursor(); - + // Inject clone after original node if ( next = node.nextSibling ) { parent.insertBefore( clone, next ); } else { parent.appendChild( clone ); } - + // Keep on splitting up the tree return parent.split( clone, stopNode ); }, @@ -356,7 +356,7 @@ implement( Element, { var el = this, doc = el.ownerDocument, fixer, child; - + if ( el.nodeName === 'BODY' ) { if ( !( child = el.firstChild ) || child.nodeName === 'BR' ) { fixer = doc.createElement( 'DIV' ); @@ -370,7 +370,7 @@ implement( Element, { fixer = null; } } - + if ( el.isInline() ) { if ( !el.firstChild ) { fixer = doc.createTextNode( /* isWebkit ? '\u200B' :*/ '' ); @@ -407,7 +407,7 @@ implement( Element, { if ( fixer ) { el.appendChild( fixer ); } - + return this; } }); diff --git a/source/Range.js b/source/Range.js index d004f22..6b37d3d 100644 --- a/source/Range.js +++ b/source/Range.js @@ -51,11 +51,11 @@ var getNodeAfter = function ( node, offset ) { }; implement( Range, { - + forEachTextNode: function ( fn ) { var range = this.cloneRange(); range.moveBoundariesDownTree(); - + var startContainer = range.startContainer, endContainer = range.endContainer, root = range.commonAncestorContainer, @@ -64,12 +64,12 @@ implement( Range, { return FILTER_ACCEPT; }, false ), textnode = walker.currentNode = startContainer; - + while ( !fn( textnode, range ) && textnode !== endContainer && ( textnode = walker.nextNode() ) ) {} }, - + getTextContent: function () { var textContent = ''; this.forEachTextNode( function ( textnode, range ) { @@ -86,9 +86,9 @@ implement( Range, { }); return textContent; }, - + // --- - + _insertNode: function ( node ) { // Insert at start. var startContainer = this.startContainer, @@ -169,26 +169,26 @@ implement( Range, { return frag; }, - + _deleteContents: function () { // Move boundaries up as much as possible to reduce need to split. this.moveBoundariesUpTree(); - + // Remove selected range this._extractContents(); - + // If we split into two different blocks, merge the blocks. var startBlock = this.getStartBlock(), endBlock = this.getEndBlock(); if ( startBlock && endBlock && startBlock !== endBlock ) { startBlock.mergeWithBlock( endBlock, this ); } - + // Ensure block has necessary children if ( startBlock ) { startBlock.fixCursor(); } - + // Ensure body has a block-level element in it. var body = this.endContainer.ownerDocument.body, child = body.firstChild; @@ -196,7 +196,7 @@ implement( Range, { body.fixCursor(); this.selectNodeContents( body.firstChild ); } - + // Ensure valid range (must have only block or inline containers) var isCollapsed = this.collapsed; this.moveBoundariesDownTree(); @@ -205,12 +205,12 @@ implement( Range, { // Make that the focus point. this.collapse( this.startContainer.nodeType === TEXT_NODE ); } - + return this; }, - + // --- - + insertTreeFragment: function ( frag ) { // Check if it's all inline content var isInline = true, @@ -222,7 +222,7 @@ implement( Range, { break; } } - + // Delete any selected content if ( !this.collapsed ) { this._deleteContents(); @@ -230,7 +230,7 @@ implement( Range, { // Move range down into text ndoes this.moveBoundariesDownTree(); - + // If inline, just insert at the current position. if ( isInline ) { this._insertNode( frag ); @@ -248,7 +248,7 @@ implement( Range, { endOffset = 0, parent = nodeAfterSplit.parentNode, child, node; - + while ( ( child = startContainer.lastChild ) && child.nodeType === ELEMENT_NODE && child.nodeName !== 'BR' ) { @@ -267,19 +267,19 @@ implement( Range, { endContainer.insertBefore( child, endContainer.firstChild ); endOffset += 1; } - + // Fix cursor before inserting block: node = frag; while ( node = node.getNextBlock() ) { node.fixCursor(); } - + parent.insertBefore( frag, nodeAfterSplit ); - + // Merge containers at edges nodeAfterSplit.mergeContainers(); nodeBeforeSplit.nextSibling.mergeContainers(); - + // Remove empty nodes created by split. if ( nodeAfterSplit === endContainer && !endContainer.textContent ) { @@ -293,7 +293,7 @@ implement( Range, { startOffset = 0; parent.removeChild( nodeBeforeSplit ); } - + this.setStart( startContainer, startOffset ); this.setEnd( endContainer, endOffset ); this.moveBoundariesDownTree(); @@ -301,7 +301,7 @@ implement( Range, { }, // --- - + containsNode: function ( node, partial ) { var range = this, nodeRange = node.ownerDocument.createRange(); @@ -327,7 +327,7 @@ implement( Range, { return ( nodeStartAfterStart && nodeEndBeforeEnd ); } }, - + moveBoundariesDownTree: function () { var startContainer = this.startContainer, startOffset = this.startOffset, @@ -361,7 +361,7 @@ implement( Range, { endContainer = child; } } - + // If collapsed, this algorithm finds the nearest text node positions // *outside* the range rather than inside, but also it flips which is // assigned to which. @@ -372,7 +372,7 @@ implement( Range, { this.setStart( startContainer, startOffset ); this.setEnd( endContainer, endOffset ); } - + return this; }, @@ -411,7 +411,7 @@ implement( Range, { getStartBlock: function () { var container = this.startContainer, block; - + // If inline, get the containing block. if ( container.isInline() ) { block = container.getPreviousBlock(); @@ -424,13 +424,13 @@ implement( Range, { // Check the block actually intersects the range return block && this.containsNode( block, true ) ? block : null; }, - + // Returns the last block at least partially contained by the range, // or null if no block is contained by the range. getEndBlock: function () { var container = this.endContainer, block, child; - + // If inline, get the containing block. if ( container.isInline() ) { block = container.getPreviousBlock(); @@ -445,7 +445,7 @@ implement( Range, { } } block = block.getPreviousBlock(); - + } // Check the block actually intersects the range return block && this.containsNode( block, true ) ? block : null; @@ -501,14 +501,14 @@ implement( Range, { var start = this.getStartBlock(), end = this.getEndBlock(), parent; - + if ( start && end ) { parent = start.parentNode; this.setStart( parent, indexOf.call( parent.childNodes, start ) ); parent = end.parentNode; this.setEnd( parent, indexOf.call( parent.childNodes, end ) + 1 ); } - + return this; } }); diff --git a/source/TreeWalker.js b/source/TreeWalker.js index 92cadba..c410dbf 100644 --- a/source/TreeWalker.js +++ b/source/TreeWalker.js @@ -21,9 +21,9 @@ if ( !needsReplacement ) { ( function () { var div = doc.createElement( 'div' ), text = doc.createTextNode( '' ); - + div.appendChild( text ); - + var div1 = div.cloneNode( true ), div2 = div.cloneNode( true ), div3 = div.cloneNode( true ), diff --git a/source/document.html b/source/document.html index 734a865..280b317 100644 --- a/source/document.html +++ b/source/document.html @@ -36,12 +36,12 @@ } h4,h5,h6 { margin: 0; - } + } ul, ol { margin: 0 1em; padding: 0 1em; } - + blockquote { border-left: 2px solid blue; margin: 0;