feather_cms 0.0.5.1 → 0.0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
- = FeatherCms
1
+ FeatherCms
2
+ ------------
2
3
 
3
4
  # Motivation
4
5
  We have felt the pain of integrating static pages into a Rails application. There are plenty of gems available for this (Radiant, Locomotiv, etc.) but they are very heavy for what we need.
@@ -29,22 +30,7 @@ If you want to add devise authentication set
29
30
  or
30
31
  c.authentication = :authenticate_admin!
31
32
 
32
- # Features
33
33
 
34
- * Creates a route for each static page.
35
- * Caching the static pages.
36
- * CodeMirror Editor for syntax highlighting. (Default: HTML)
37
- * Storage is file (default) or database.
38
-
39
- # Caveats
40
- You may need to ensure that the public/system folder exists (for file storage)
41
-
42
- # TODO
43
-
44
- * Move the basic authentication to the config/initializers/feather_cms.rb
45
- * Override the default authentication strategy from the controller
46
- * Move the code from generated controller to the gem (if required)
47
-
48
34
  # Contribute
49
35
  Fork away and send me pull requests!
50
36
 
@@ -1,12 +1,13 @@
1
1
  $(document).ready(function(){
2
+
2
3
  var opts = {
3
4
  container: 'epiceditor',
4
5
  basePath: '/assets/feather_cms',
5
6
  focusOnLoad: true,
6
- file: { defaultContent: "" }
7
+ file: { defaultContent: '' }
7
8
  };
8
9
 
9
- window.editor = new EpicEditor(opts).load();
10
+ window.editor = new EpicEditor(opts); //.load();
10
11
 
11
12
  window.editor.load(function(){
12
13
  window.editor.importFile(null, $('#page_content').val());
@@ -1,4 +1,2017 @@
1
1
  /**
2
2
  * EpicEditor - An Embeddable JavaScript Markdown Editor (https://github.com/OscarGodson/EpicEditor)
3
3
  * Copyright (c) 2011-2012, Oscar Godson. (MIT Licensed)
4
- */(function(a,b){function c(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c])}function d(a,b){for(var c in b)b.hasOwnProperty(c)&&(a.style[c]=b[c])}function e(b,c){var d=b,e=null;return a.getComputedStyle?e=document.defaultView.getComputedStyle(d,null).getPropertyValue(c):d.currentStyle&&(e=d.currentStyle[c]),e}function f(a,b,c){var f={},g;if(b==="save"){for(g in c)c.hasOwnProperty(g)&&(f[g]=e(a,g));d(a,c)}else b==="apply"&&d(a,c);return f}function g(a){var b=parseInt(e(a,"border-left-width"),10)+parseInt(e(a,"border-right-width"),10),c=parseInt(e(a,"padding-left"),10)+parseInt(e(a,"padding-right"),10),d=a.offsetWidth,f;return isNaN(b)&&(b=0),f=b+c+d,f}function h(a){var b=parseInt(e(a,"border-top-width"),10)+parseInt(e(a,"border-bottom-width"),10),c=parseInt(e(a,"padding-top"),10)+parseInt(e(a,"padding-bottom"),10),d=a.offsetHeight,f;return isNaN(b)&&(b=0),f=b+c+d,f}function i(a,b,d){d=d||"";var e=b.getElementsByTagName("head")[0],f=b.createElement("link");c(f,{type:"text/css",id:d,rel:"stylesheet",href:a,name:a,media:"screen"}),e.appendChild(f)}function j(a,b,c){a.className=a.className.replace(b,c)}function k(a){return a.contentDocument||a.contentWindow.document}function l(a){var b;return document.body.innerText?b=a.innerText:(b=a.innerHTML.replace(/<br>/gi,"\n"),b=b.replace(/<(?:.|\n)*?>/gm,""),b=b.replace(/&lt;/gi,"<"),b=b.replace(/&gt;/gi,">")),b}function m(a,b){return document.body.innerText?a.innerText=b:a.innerHTML=b.replace(/\n/g,"<br>"),!0}function n(){var a=-1,b=navigator.userAgent,c;return navigator.appName=="Microsoft Internet Explorer"&&(c=/MSIE ([0-9]{1,}[\.0-9]{0,})/,c.exec(b)!=null&&(a=parseFloat(RegExp.$1,10))),a}function o(a){var b={};return a&&b.toString.call(a)==="[object Function]"}function p(){var a=arguments[0]||{},c=1,d=arguments.length,e=!1,f,g,h,i;typeof a=="boolean"&&(e=a,a=arguments[1]||{},c=2),typeof a!="object"&&!o(a)&&(a={}),d===c&&(a=this,--c);for(;c<d;c++)if((f=arguments[c])!=null)for(g in f)if(f.hasOwnProperty(g)){h=a[g],i=f[g];if(a===i)continue;e&&i&&typeof i=="object"&&!i.nodeType?a[g]=p(e,h||(i.length!=null?[]:{}),i):i!==b&&(a[g]=i)}return a}function q(a){var c=this,d=a||{},e,f,g={container:"epiceditor",basePath:"epiceditor",clientSideStorage:!0,localStorageName:"epiceditor",file:{name:null,defaultContent:"",autoSave:100},theme:{base:"/themes/base/epiceditor.css",preview:"/themes/preview/github.css",editor:"/themes/editor/epic-dark.css"},focusOnLoad:!1,shortcut:{modifier:18,fullscreen:70,preview:80,edit:79},parser:typeof marked=="function"?marked:null},h;c.settings=p(!0,g,d);if(typeof c.settings.parser!="function"||typeof c.settings.parser("TEST")!="string")c.settings.parser=function(a){return a};return typeof c.settings.container=="string"?c.element=document.getElementById(c.settings.container):typeof c.settings.container=="object"&&(c.element=c.settings.container),c.settings.file.name||(typeof c.settings.container=="string"?c.settings.file.name=c.settings.container:typeof c.settings.container=="object"&&(c.element.id?c.settings.file.name=c.element.id:(q._data.unnamedEditors||(q._data.unnamedEditors=[]),q._data.unnamedEditors.push(c),c.settings.file.name="__epiceditor-untitled-"+q._data.unnamedEditors.length))),c._instanceId="epiceditor-"+Math.round(Math.random()*1e5),c._storage={},c._canSave=!0,c._defaultFileSchema=function(){return{content:c.settings.file.defaultContent,created:new Date,modified:new Date}},localStorage&&c.settings.clientSideStorage&&(this._storage=localStorage,this._storage[c.settings.localStorageName]&&c.getFiles(c.settings.file.name)===b&&(f=c.getFiles(c.settings.file.name),f=c._defaultFileSchema(),f.content=c.settings.file.defaultContent)),this._storage[c.settings.localStorageName]||(h={},h[c.settings.file.name]=c._defaultFileSchema(),h=JSON.stringify(h),this._storage[c.settings.localStorageName]=h),c.events||(c.events={}),this}q.prototype.load=function(b){function G(a){for(var b=0;b<a.length;b++)a[b].style.width=c.element.offsetWidth-o+"px",a[b].style.height=c.element.offsetHeight-p+"px"}function H(a){o=g(c.element)-c.element.offsetWidth;for(var b=0;b<a.length;b++)a[b].style.width=c.element.offsetWidth-o+"px"}function I(b){if(Math.abs(u.y-b.pageY)>=5||Math.abs(u.x-b.pageX)>=5)r.style.display="block",s&&clearTimeout(s),s=a.setTimeout(function(){r.style.display="none"},1e3);u={y:b.pageY,x:b.pageX}}function J(a){a.keyCode==c.settings.shortcut.modifier&&(C=!0),a.keyCode==17&&(D=!0),C===!0&&a.keyCode==c.settings.shortcut.preview&&!c.eeState.fullscreen&&(a.preventDefault(),c.preview()),C===!0&&a.keyCode==c.settings.shortcut.edit&&(a.preventDefault(),c.eeState.fullscreen||c.edit()),C===!0&&a.keyCode==c.settings.shortcut.fullscreen&&(a.preventDefault(),y(B)),C===!0&&a.keyCode!==c.settings.shortcut.modifier&&(C=!1),a.keyCode==27&&c.eeState.fullscreen&&(document.body.webkitRequestFullScreen||z(B)),D===!0&&a.keyCode==83&&(c.save(),a.preventDefault(),D=!1),a.metaKey&&a.keyCode==83&&(c.save(),a.preventDefault())}function K(a){a.keyCode==c.settings.shortcut.modifier&&(C=!1),a.keyCode==17&&(D=!1)}var c=this,j,l,m,o,p,q,r,s,t,u={y:-1,x:-1},v,w,x=document.body.webkitRequestFullScreen?!0:!1,y,z,A,B,C=!1,D=!1,E,F;b=b||function(){},c.eeState={fullscreen:!1,preview:!1,edit:!0,loaded:!1,unloaded:!1},j={chrome:'<div id="epiceditor-wrapper" class="epiceditor-edit-mode"><iframe frameborder="0" id="epiceditor-editor-frame"></iframe><iframe frameborder="0" id="epiceditor-previewer-frame"></iframe><div id="epiceditor-utilbar"><img width="30" src="'+this.settings.basePath+'/images/preview.png" title="Toggle Preview Mode" class="epiceditor-toggle-btn epiceditor-toggle-preview-btn"> '+'<img width="30" src="'+this.settings.basePath+'/images/edit.png" title="Toggle Edit Mode" class="epiceditor-toggle-btn epiceditor-toggle-edit-btn"> '+'<img width="30" src="'+this.settings.basePath+'/images/fullscreen.png" title="Enter Fullscreen" class="epiceditor-fullscreen-btn">'+"</div>"+"</div>",previewer:'<div id="epiceditor-preview"></div>'},c.element.innerHTML='<iframe scrolling="no" frameborder="0" id= "'+c._instanceId+'"></iframe>',l=document.getElementById(c._instanceId),c.iframeElement=l,c.iframe=k(l),c.iframe.open(),c.iframe.write(j.chrome),c.editorIframe=c.iframe.getElementById("epiceditor-editor-frame"),c.previewerIframe=c.iframe.getElementById("epiceditor-previewer-frame"),c.editorIframeDocument=k(c.editorIframe),c.editorIframeDocument.open(),c.editorIframeDocument.write(""),c.editorIframeDocument.close(),c.previewerIframeDocument=k(c.previewerIframe),c.previewerIframeDocument.open(),c.previewerIframeDocument.write(j.previewer),m=c.previewerIframeDocument.createElement("base"),m.target="_blank",c.previewerIframeDocument.getElementsByTagName("head")[0].appendChild(m),c.previewerIframeDocument.close(),o=g(c.element)-c.element.offsetWidth,p=h(c.element)-c.element.offsetHeight,A=[c.iframeElement,c.editorIframe,c.previewerIframe],G(A),i(c.settings.basePath+c.settings.theme.base,c.iframe,"theme"),i(c.settings.basePath+c.settings.theme.editor,c.editorIframeDocument,"theme"),i(c.settings.basePath+c.settings.theme.preview,c.previewerIframeDocument,"theme"),c.iframe.getElementById("epiceditor-wrapper").style.position="relative",c.editor=c.editorIframeDocument.body,c.previewer=c.previewerIframeDocument.getElementById("epiceditor-preview"),c.editor.contentEditable=!0,c.iframe.body.style.height=this.element.offsetHeight+"px",this.previewerIframe.style.display="none",n()>-1&&(this.previewer.style.height=parseInt(e(this.previewer,"height"),10)+2),this.open(c.settings.file.name),c.settings.focusOnLoad&&c.iframe.addEventListener("readystatechange",function(){c.iframe.readyState=="complete"&&c.editorIframeDocument.body.focus()}),q=c.iframe.getElementById("epiceditor-utilbar"),v={},y=function(b){if(c.eeState.fullscreen){z(b);return}x&&b.webkitRequestFullScreen(),w=c.eeState.edit,c.eeState.fullscreen=!0,c.eeState.edit=!0,c.eeState.preview=!0;var d=a.innerWidth,g=a.innerHeight,h=a.outerWidth,i=a.outerHeight;x||(i=a.innerHeight),v.editorIframe=f(c.editorIframe,"save",{width:h/2+"px",height:i+"px","float":"left",cssFloat:"left",styleFloat:"left",display:"block"}),v.previewerIframe=f(c.previewerIframe,"save",{width:h/2+"px",height:i+"px","float":"right",cssFloat:"right",styleFloat:"right",display:"block"}),v.element=f(c.element,"save",{position:"fixed",top:"0",left:"0",width:"100%","z-index":"9999",zIndex:"9999",border:"none",margin:"0",background:e(c.editor,"background-color"),height:g+"px"}),v.iframeElement=f(c.iframeElement,"save",{width:h+"px",height:g+"px"}),q.style.visibility="hidden",x||(document.body.style.overflow="hidden"),c.preview(),c.editorIframeDocument.body.focus()},z=function(a){f(c.element,"apply",v.element),f(c.iframeElement,"apply",v.iframeElement),f(c.editorIframe,"apply",v.editorIframe),f(c.previewerIframe,"apply",v.previewerIframe),c.element.style.width="",c.element.style.height="",q.style.visibility="visible",x?document.webkitCancelFullScreen():document.body.style.overflow="auto",c.eeState.fullscreen=!1,w?c.edit():c.preview(),H(A)},c.editor.addEventListener("keyup",function(){t&&a.clearTimeout(t),t=a.setTimeout(function(){c.eeState.fullscreen&&c.preview()},250)}),B=c.iframeElement,q.addEventListener("click",function(a){var b=a.target.className;b.indexOf("epiceditor-toggle-preview-btn")>-1?c.preview():b.indexOf("epiceditor-toggle-edit-btn")>-1?c.edit():b.indexOf("epiceditor-fullscreen-btn")>-1&&y(B)}),document.body.webkitRequestFullScreen&&B.addEventListener("webkitfullscreenchange",function(){document.webkitIsFullScreen||z(B)},!1),r=c.iframe.getElementById("epiceditor-utilbar"),r.style.display="none",r.addEventListener("mouseover",function(){s&&clearTimeout(s)}),E=[c.previewerIframeDocument,c.editorIframeDocument];for(F=0;F<E.length;F++)E[F].addEventListener("mousemove",function(a){I(a)}),E[F].addEventListener("scroll",function(a){I(a)}),E[F].addEventListener("keyup",function(a){K(a)}),E[F].addEventListener("keydown",function(a){J(a)});return c.settings.file.autoSave&&(c.saveInterval=a.setInterval(function(){if(!c._canSave)return;c.save()},c.settings.file.autoSave)),a.addEventListener("resize",function(){!c.iframe.webkitRequestFullScreen&&c.eeState.fullscreen?(d(c.iframeElement,{width:a.outerWidth+"px",height:a.innerHeight+"px"}),d(c.element,{height:a.innerHeight+"px"}),d(c.previewerIframe,{width:a.outerWidth/2+"px",height:a.innerHeight+"px"}),d(c.editorIframe,{width:a.outerWidth/2+"px",height:a.innerHeight+"px"})):c.eeState.fullscreen||H(A)}),c.iframe.close(),c.eeState.loaded=!0,c.eeState.unloaded=!1,b.call(this),this.emit("load"),this},q.prototype.unload=function(b){if(this.eeState.unloaded)throw new Error("Editor isn't loaded");var c=this,d=a.parent.document.getElementById(c._instanceId);return d.parentNode.removeChild(d),c.eeState.loaded=!1,c.eeState.unloaded=!0,b=b||function(){},c.saveInterval&&a.clearInterval(c.saveInterval),b.call(this),c.emit("unload"),c},q.prototype.preview=function(a){var b=this;return a=a||b.settings.basePath+b.settings.theme.preview,j(b.getElement("wrapper"),"epiceditor-edit-mode","epiceditor-preview-mode"),b.previewerIframeDocument.getElementById("theme")?b.previewerIframeDocument.getElementById("theme").name!==a&&(b.previewerIframeDocument.getElementById("theme").href=a):i(a,b.previewerIframeDocument,"theme"),b.previewer.innerHTML=b.exportFile(null,"html"),b.eeState.fullscreen||(b.editorIframe.style.display="none",b.previewerIframe.style.display="block",b.eeState.preview=!0,b.eeState.edit=!1,b.previewerIframe.focus()),b.emit("preview"),b},q.prototype.edit=function(){var a=this;return j(a.getElement("wrapper"),"epiceditor-preview-mode","epiceditor-edit-mode"),a.eeState.preview=!1,a.eeState.edit=!0,a.editorIframe.style.display="block",a.previewerIframe.style.display="none",a.editorIframe.focus(),a.emit("edit"),this},q.prototype.getElement=function(a){var b={container:this.element,wrapper:this.iframe.getElementById("epiceditor-wrapper"),wrapperIframe:this.iframeElement,editor:this.editorIframeDocument,editorIframe:this.editorIframe,previewer:this.previewerIframeDocument,previewerIframe:this.previewerIframe};return!b[a]||this.eeState.unloaded?null:b[a]},q.prototype.open=function(a){var c=this,d=c.settings.file.defaultContent,e;return a=a||c.settings.file.name,c.settings.file.name=a,this._storage[c.settings.localStorageName]&&(e=c.getFiles(),e[a]!==b?(m(c.editor,e[a].content),c.emit("read")):(m(c.editor,d),c.save(),c.emit("create")),c.previewer.innerHTML=c.exportFile(null,"html"),c.emit("open")),this},q.prototype.save=function(){var a=this,c,d=!1,e=a.settings.file.name,f=l(this.editor);return this._canSave=!0,c=JSON.parse(this._storage[a.settings.localStorageName]),c[e]===b?c[e]=a._defaultFileSchema():f!==c[e].content&&(c[e].modified=new Date,d=!0),c[e].content=f,this._storage[a.settings.localStorageName]=JSON.stringify(c),d&&a.emit("update"),this.emit("save"),this},q.prototype.remove=function(a){var b=this,c;return a=a||b.settings.file.name,a==b.settings.file.name&&(b._canSave=!1),c=JSON.parse(this._storage[b.settings.localStorageName]),delete c[a],this._storage[b.settings.localStorageName]=JSON.stringify(c),this.emit("remove"),this},q.prototype.rename=function(a,b){var c=this,d=JSON.parse(this._storage[c.settings.localStorageName]);return d[b]=d[a],delete d[a],this._storage[c.settings.localStorageName]=JSON.stringify(d),c.open(b),this},q.prototype.importFile=function(a,c,d,e){var f=this,g=!1;return a=a||f.settings.file.name,c=c||"",d=d||"md",e=e||{},JSON.parse(this._storage[f.settings.localStorageName])[a]===b&&(g=!0),f.settings.file.name=a,m(f.editor,c),g&&f.emit("create"),f.save(),f.eeState.fullscreen&&f.preview(),this},q.prototype.exportFile=function(a,c){var d=this,e,f;a=a||d.settings.file.name,c=c||"text",e=d.getFiles(a);if(e===b)return;f=e.content;switch(c){case"html":return f=f.replace(/\u00a0/g," ").replace(/&nbsp;/g," "),d.settings.parser(f);case"text":return f=f.replace(/&nbsp;/g," "),f;default:return f}},q.prototype.getFiles=function(a){var b=JSON.parse(this._storage[this.settings.localStorageName]);return a?b[a]:b},q.prototype.on=function(a,b){var c=this;return this.events[a]||(this.events[a]=[]),this.events[a].push(b),c},q.prototype.emit=function(a,b){function e(a){a.call(c,b)}var c=this,d;b=b||c.getFiles(c.settings.file.name);if(!this.events[a])return;for(d=0;d<c.events[a].length;d++)e(c.events[a][d]);return c},q.prototype.removeListener=function(a,b){var c=this;return b?this.events[a]?(this.events[a].splice(this.events[a].indexOf(b),1),c):c:(this.events[a]=[],c)},q.version="0.1.1",q._data={},a.EpicEditor=q})(window),function(){function c(a,c){return a[0][0]!=="!"?'<a href="'+j(c.href)+'"'+(c.title?' title="'+j(c.title)+'"':"")+">"+b.lexer(a[1])+"</a>":'<img src="'+j(c.href)+'" alt="'+j(a[1])+'"'+(c.title?' title="'+j(c.title)+'"':"")+">"}function f(){return e=d.pop()}function g(){switch(e.type){case"space":return"";case"hr":return"<hr>\n";case"heading":return"<h"+e.depth+">"+b.lexer(e.text)+"</h"+e.depth+">\n";case"code":return p.highlight&&(e.code=p.highlight(e.text,e.lang),e.code!=null&&e.code!==e.text&&(e.escaped=!0,e.text=e.code)),e.escaped||(e.text=j(e.text,!0)),"<pre><code"+(e.lang?' class="lang-'+e.lang+'"':"")+">"+e.text+"</code></pre>\n";case"blockquote_start":var a="";while(f().type!=="blockquote_end")a+=g();return"<blockquote>\n"+a+"</blockquote>\n";case"list_start":var c=e.ordered?"ol":"ul",a="";while(f().type!=="list_end")a+=g();return"<"+c+">\n"+a+"</"+c+">\n";case"list_item_start":var a="";while(f().type!=="list_item_end")a+=e.type==="text"?h():g();return"<li>"+a+"</li>\n";case"loose_item_start":var a="";while(f().type!=="list_item_end")a+=g();return"<li>"+a+"</li>\n";case"html":return p.sanitize?b.lexer(e.text):!e.pre&&!p.pedantic?b.lexer(e.text):e.text;case"paragraph":return"<p>"+b.lexer(e.text)+"</p>\n";case"text":return"<p>"+h()+"</p>\n"}}function h(){var a=e.text,c;while((c=d[d.length-1])&&c.type==="text")a+="\n"+f().text;return b.lexer(a)}function i(a){d=a.reverse();var b="";while(f())b+=g();return d=null,e=null,b}function j(a,b){return a.replace(b?/&/g:/&(?!#?\w+;)/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function k(a){var b="",c=a.length,d=0,e;for(;d<c;d++)e=a.charCodeAt(d),Math.random()>.5&&(e="x"+e.toString(16)),b+="&#"+e+";";return b}function l(){var a="(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+";return a}function m(a,b){return a=a.source,b=b||"",function c(d,e){return d?(a=a.replace(d,e.source||e),c):new RegExp(a,b)}}function n(){}function o(b,c){return r(c),i(a.lexer(b))}function r(c){c||(c=q);if(p===c)return;p=c,p.gfm?(a.fences=a.gfm.fences,a.paragraph=a.gfm.paragraph,b.text=b.gfm.text,b.url=b.gfm.url):(a.fences=a.normal.fences,a.paragraph=a.normal.paragraph,b.text=b.normal.text,b.url=b.normal.url),p.pedantic?(b.em=b.pedantic.em,b.strong=b.pedantic.strong):(b.em=b.normal.em,b.strong=b.normal.strong)}var a={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:n,hr:/^( *[-*_]){3,} *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,lheading:/^([^\n]+)\n *(=|-){3,} *\n*/,blockquote:/^( *>[^\n]+(\n[^\n]+)*\n*)+/,list:/^( *)(bull) [^\0]+?(?:hr|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:/^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/,def:/^ *\[([^\]]+)\]: *([^\s]+)(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,paragraph:/^([^\n]+\n?(?!body))+\n*/,text:/^[^\n]+/};a.bullet=/(?:[*+-]|\d+\.)/,a.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/,a.item=m(a.item,"gm")(/bull/g,a.bullet)(),a.list=m(a.list)(/bull/g,a.bullet)("hr",/\n+(?=(?: *[-*_]){3,} *(?:\n+|$))/)(),a.html=m(a.html)("comment",/<!--[^\0]*?-->/)("closed",/<(tag)[^\0]+?<\/\1>/)("closing",/<tag(?!:\/|@)\b(?:"[^"]*"|'[^']*'|[^'">])*?>/)(/tag/g,l())(),a.paragraph=function(){var b=a.paragraph.source,c=[];return function d(b){return b=a[b]?a[b].source:b,c.push(b.replace(/(^|[^\[])\^/g,"$1")),d}("hr")("heading")("lheading")("blockquote")("<"+l())("def"),new RegExp(b.replace("body",c.join("|")))}(),a.normal={fences:a.fences,paragraph:a.paragraph},a.gfm={fences:/^ *``` *(\w+)? *\n([^\0]+?)\s*``` *(?:\n+|$)/,paragraph:/^/},a.gfm.paragraph=m(a.paragraph)("(?!","(?!"+a.gfm.fences.source.replace(/(^|[^\[])\^/g,"$1")+"|")(),a.lexer=function(b){var c=[];return c.links={},b=b.replace(/\r\n|\r/g,"\n").replace(/\t/g," "),a.token(b,c,!0)},a.token=function(b,c,d){var b=b.replace(/^ +$/gm,""),e,f,g,h,i,j,k;while(b){if(g=a.newline.exec(b))b=b.substring(g[0].length),g[0].length>1&&c.push({type:"space"});if(g=a.code.exec(b)){b=b.substring(g[0].length),g=g[0].replace(/^ {4}/gm,""),c.push({type:"code",text:p.pedantic?g:g.replace(/\n+$/,"")});continue}if(g=a.fences.exec(b)){b=b.substring(g[0].length),c.push({type:"code",lang:g[1],text:g[2]});continue}if(g=a.heading.exec(b)){b=b.substring(g[0].length),c.push({type:"heading",depth:g[1].length,text:g[2]});continue}if(g=a.lheading.exec(b)){b=b.substring(g[0].length),c.push({type:"heading",depth:g[2]==="="?1:2,text:g[1]});continue}if(g=a.hr.exec(b)){b=b.substring(g[0].length),c.push({type:"hr"});continue}if(g=a.blockquote.exec(b)){b=b.substring(g[0].length),c.push({type:"blockquote_start"}),g=g[0].replace(/^ *> ?/gm,""),a.token(g,c,d),c.push({type:"blockquote_end"});continue}if(g=a.list.exec(b)){b=b.substring(g[0].length),c.push({type:"list_start",ordered:isFinite(g[2])}),g=g[0].match(a.item),e=!1,k=g.length,j=0;for(;j<k;j++)h=g[j],i=h.length,h=h.replace(/^ *([*+-]|\d+\.) +/,""),~h.indexOf("\n ")&&(i-=h.length,h=p.pedantic?h.replace(/^ {1,4}/gm,""):h.replace(new RegExp("^ {1,"+i+"}","gm"),"")),f=e||/\n\n(?!\s*$)/.test(h),j!==k-1&&(e=h[h.length-1]==="\n",f||(f=e)),c.push({type:f?"loose_item_start":"list_item_start"}),a.token(h,c),c.push({type:"list_item_end"});c.push({type:"list_end"});continue}if(g=a.html.exec(b)){b=b.substring(g[0].length),c.push({type:"html",pre:g[1]==="pre",text:g[0]});continue}if(d&&(g=a.def.exec(b))){b=b.substring(g[0].length),c.links[g[1].toLowerCase()]={href:g[2],title:g[3]};continue}if(d&&(g=a.paragraph.exec(b))){b=b.substring(g[0].length),c.push({type:"paragraph",text:g[0]});continue}if(g=a.text.exec(b)){b=b.substring(g[0].length),c.push({type:"text",text:g[0]});continue}}return c};var b={escape:/^\\([\\`*{}\[\]()#+\-.!_>])/,autolink:/^<([^ >]+(@|:\/)[^ >]+)>/,url:n,tag:/^<!--[^\0]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,link:/^!?\[(inside)\]\(href\)/,reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,strong:/^__([^\0]+?)__(?!_)|^\*\*([^\0]+?)\*\*(?!\*)/,em:/^\b_((?:__|[^\0])+?)_\b|^\*((?:\*\*|[^\0])+?)\*(?!\*)/,code:/^(`+)([^\0]*?[^`])\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,text:/^[^\0]+?(?=[\\<!\[_*`]| {2,}\n|$)/};b._linkInside=/(?:\[[^\]]*\]|[^\]]|\](?=[^\[]*\]))*/,b._linkHref=/\s*<?([^\s]*?)>?(?:\s+['"]([^\0]*?)['"])?\s*/,b.link=m(b.link)("inside",b._linkInside)("href",b._linkHref)(),b.reflink=m(b.reflink)("inside",b._linkInside)(),b.normal={url:b.url,strong:b.strong,em:b.em,text:b.text},b.pedantic={strong:/^__(?=\S)([^\0]*?\S)__(?!_)|^\*\*(?=\S)([^\0]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([^\0]*?\S)_(?!_)|^\*(?=\S)([^\0]*?\S)\*(?!\*)/},b.gfm={url:/^(https?:\/\/[^\s]+[^.,:;"')\]\s])/,text:/^[^\0]+?(?=[\\<!\[_*`]|https?:\/\/| {2,}\n|$)/},b.lexer=function(a){var e="",f=d.links,g,h,i,l;while(a){if(l=b.escape.exec(a)){a=a.substring(l[0].length),e+=l[1];continue}if(l=b.autolink.exec(a)){a=a.substring(l[0].length),l[2]==="@"?(h=l[1][6]===":"?k(l[1].substring(7)):k(l[1]),i=k("mailto:")+h):(h=j(l[1]),i=h),e+='<a href="'+i+'">'+h+"</a>";continue}if(l=b.url.exec(a)){a=a.substring(l[0].length),h=j(l[1]),i=h,e+='<a href="'+i+'">'+h+"</a>";continue}if(l=b.tag.exec(a)){a=a.substring(l[0].length),e+=p.sanitize?j(l[0]):l[0];continue}if(l=b.link.exec(a)){a=a.substring(l[0].length),e+=c(l,{href:l[2],title:l[3]});continue}if((l=b.reflink.exec(a))||(l=b.nolink.exec(a))){a=a.substring(l[0].length),g=(l[2]||l[1]).replace(/\s+/g," "),g=f[g.toLowerCase()];if(!g||!g.href){e+=l[0][0],a=l[0].substring(1)+a;continue}e+=c(l,g);continue}if(l=b.strong.exec(a)){a=a.substring(l[0].length),e+="<strong>"+b.lexer(l[2]||l[1])+"</strong>";continue}if(l=b.em.exec(a)){a=a.substring(l[0].length),e+="<em>"+b.lexer(l[2]||l[1])+"</em>";continue}if(l=b.code.exec(a)){a=a.substring(l[0].length),e+="<code>"+j(l[2],!0)+"</code>";continue}if(l=b.br.exec(a)){a=a.substring(l[0].length),e+="<br>";continue}if(l=b.text.exec(a)){a=a.substring(l[0].length),e+=j(l[0]);continue}}return e};var d,e;n.exec=n;var p,q;o.options=o.setOptions=function(a){return q=a,r(a),o},o.setOptions({gfm:!0,pedantic:!1,sanitize:!1,highlight:null}),o.parser=function(a,b){return r(b),i(a)},o.lexer=function(b,c){return r(c),a.lexer(b)},o.parse=o,typeof module!="undefined"?module.exports=o:this.marked=o}.call(function(){return this||(typeof window!="undefined"?window:global)}());
4
+ */
5
+
6
+ (function (window, undefined) {
7
+ /**
8
+ * Applies attributes to a DOM object
9
+ * @param {object} context The DOM obj you want to apply the attributes to
10
+ * @param {object} attrs A key/value pair of attributes you want to apply
11
+ * @returns {undefined}
12
+ */
13
+ function _applyAttrs(context, attrs) {
14
+ for (var attr in attrs) {
15
+ if (attrs.hasOwnProperty(attr)) {
16
+ context[attr] = attrs[attr];
17
+ }
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Applies styles to a DOM object
23
+ * @param {object} context The DOM obj you want to apply the attributes to
24
+ * @param {object} attrs A key/value pair of attributes you want to apply
25
+ * @returns {undefined}
26
+ */
27
+ function _applyStyles(context, attrs) {
28
+ for (var attr in attrs) {
29
+ if (attrs.hasOwnProperty(attr)) {
30
+ context.style[attr] = attrs[attr];
31
+ }
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Returns a DOM objects computed style
37
+ * @param {object} el The element you want to get the style from
38
+ * @param {string} styleProp The property you want to get from the element
39
+ * @returns {string} Returns a string of the value. If property is not set it will return a blank string
40
+ */
41
+ function _getStyle(el, styleProp) {
42
+ var x = el
43
+ , y = null;
44
+ if (window.getComputedStyle) {
45
+ y = document.defaultView.getComputedStyle(x, null).getPropertyValue(styleProp);
46
+ }
47
+ else if (x.currentStyle) {
48
+ y = x.currentStyle[styleProp];
49
+ }
50
+ return y;
51
+ }
52
+
53
+ /**
54
+ * Saves the current style state for the styles requested, then applys styles
55
+ * to overwrite the existing one. The old styles are returned as an object so
56
+ * you can pass it back in when you want to revert back to the old style
57
+ * @param {object} el The element to get the styles of
58
+ * @param {string} type Can be "save" or "apply". apply will just apply styles you give it. Save will write styles
59
+ * @param {object} styles Key/value style/property pairs
60
+ * @returns {object}
61
+ */
62
+ function _saveStyleState(el, type, styles) {
63
+ var returnState = {}
64
+ , style;
65
+ if (type === 'save') {
66
+ for (style in styles) {
67
+ if (styles.hasOwnProperty(style)) {
68
+ returnState[style] = _getStyle(el, style);
69
+ }
70
+ }
71
+ // After it's all done saving all the previous states, change the styles
72
+ _applyStyles(el, styles);
73
+ }
74
+ else if (type === 'apply') {
75
+ _applyStyles(el, styles);
76
+ }
77
+ return returnState;
78
+ }
79
+
80
+ /**
81
+ * Gets an elements total width including it's borders and padding
82
+ * @param {object} el The element to get the total width of
83
+ * @returns {int}
84
+ */
85
+ function _outerWidth(el) {
86
+ var b = parseInt(_getStyle(el, 'border-left-width'), 10) + parseInt(_getStyle(el, 'border-right-width'), 10)
87
+ , p = parseInt(_getStyle(el, 'padding-left'), 10) + parseInt(_getStyle(el, 'padding-right'), 10)
88
+ , w = el.offsetWidth
89
+ , t;
90
+ // For IE in case no border is set and it defaults to "medium"
91
+ if (isNaN(b)) { b = 0; }
92
+ t = b + p + w;
93
+ return t;
94
+ }
95
+
96
+ /**
97
+ * Gets an elements total height including it's borders and padding
98
+ * @param {object} el The element to get the total width of
99
+ * @returns {int}
100
+ */
101
+ function _outerHeight(el) {
102
+ var b = parseInt(_getStyle(el, 'border-top-width'), 10) + parseInt(_getStyle(el, 'border-bottom-width'), 10)
103
+ , p = parseInt(_getStyle(el, 'padding-top'), 10) + parseInt(_getStyle(el, 'padding-bottom'), 10)
104
+ , w = el.offsetHeight
105
+ , t;
106
+ // For IE in case no border is set and it defaults to "medium"
107
+ if (isNaN(b)) { b = 0; }
108
+ t = b + p + w;
109
+ return t;
110
+ }
111
+
112
+ /**
113
+ * Inserts a <link> tag specifically for CSS
114
+ * @param {string} path The path to the CSS file
115
+ * @param {object} context In what context you want to apply this to (document, iframe, etc)
116
+ * @param {string} id An id for you to reference later for changing properties of the <link>
117
+ * @returns {undefined}
118
+ */
119
+ function _insertCSSLink(path, context, id) {
120
+ id = id || '';
121
+ var headID = context.getElementsByTagName("head")[0]
122
+ , cssNode = context.createElement('link');
123
+
124
+ _applyAttrs(cssNode, {
125
+ type: 'text/css'
126
+ , id: id
127
+ , rel: 'stylesheet'
128
+ , href: path
129
+ , name: path
130
+ , media: 'screen'
131
+ });
132
+
133
+ headID.appendChild(cssNode);
134
+ }
135
+
136
+ // Simply replaces a class (o), to a new class (n) on an element provided (e)
137
+ function _replaceClass(e, o, n) {
138
+ e.className = e.className.replace(o, n);
139
+ }
140
+
141
+ // Feature detects an iframe to get the inner document for writing to
142
+ function _getIframeInnards(el) {
143
+ return el.contentDocument || el.contentWindow.document;
144
+ }
145
+
146
+ // Grabs the text from an element and preserves whitespace
147
+ function _getText(el) {
148
+ var theText;
149
+ if (document.body.innerText) {
150
+ theText = el.innerText;
151
+ }
152
+ else {
153
+ // First replace <br>s before replacing the rest of the HTML
154
+ theText = el.innerHTML.replace(/<br>/gi, "\n");
155
+ // Now we can clean the HTML
156
+ theText = theText.replace(/<(?:.|\n)*?>/gm, '');
157
+ // Now fix HTML entities
158
+ theText = theText.replace(/&lt;/gi, '<');
159
+ theText = theText.replace(/&gt;/gi, '>');
160
+ }
161
+ return theText;
162
+ }
163
+
164
+ function _setText(el, content) {
165
+ if (document.body.innerText) {
166
+ el.innerText = content;
167
+ }
168
+ else {
169
+ el.innerHTML = content.replace(/\n/g, "<br>");
170
+ }
171
+ return true;
172
+ }
173
+
174
+ /**
175
+ * Will return the version number if the browser is IE. If not will return -1
176
+ * TRY NEVER TO USE THIS AND USE FEATURE DETECTION IF POSSIBLE
177
+ * @returns {Number} -1 if false or the version number if true
178
+ */
179
+ function _isIE() {
180
+ var rv = -1 // Return value assumes failure.
181
+ , ua = navigator.userAgent
182
+ , re;
183
+ if (navigator.appName == 'Microsoft Internet Explorer') {
184
+ re = /MSIE ([0-9]{1,}[\.0-9]{0,})/;
185
+ if (re.exec(ua) != null) {
186
+ rv = parseFloat(RegExp.$1, 10);
187
+ }
188
+ }
189
+ return rv;
190
+ }
191
+
192
+ /**
193
+ * Determines if supplied value is a function
194
+ * @param {object} object to determine type
195
+ */
196
+ function _isFunction(functionToCheck) {
197
+ var getType = {};
198
+ return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
199
+ }
200
+
201
+ /**
202
+ * Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1
203
+ * @param {boolean} [deepMerge=false] If true, will deep merge meaning it will merge sub-objects like {obj:obj2{foo:'bar'}}
204
+ * @param {object} first object
205
+ * @param {object} second object
206
+ * @returnss {object} a new object based on obj1 and obj2
207
+ */
208
+ function _mergeObjs() {
209
+ // copy reference to target object
210
+ var target = arguments[0] || {}
211
+ , i = 1
212
+ , length = arguments.length
213
+ , deep = false
214
+ , options
215
+ , name
216
+ , src
217
+ , copy
218
+
219
+ // Handle a deep copy situation
220
+ if (typeof target === "boolean") {
221
+ deep = target;
222
+ target = arguments[1] || {};
223
+ // skip the boolean and the target
224
+ i = 2;
225
+ }
226
+
227
+ // Handle case when target is a string or something (possible in deep copy)
228
+ if (typeof target !== "object" && !_isFunction(target)) {
229
+ target = {};
230
+ }
231
+ // extend jQuery itself if only one argument is passed
232
+ if (length === i) {
233
+ target = this;
234
+ --i;
235
+ }
236
+
237
+ for (; i < length; i++) {
238
+ // Only deal with non-null/undefined values
239
+ if ((options = arguments[i]) != null) {
240
+ // Extend the base object
241
+ for (name in options) {
242
+ // @NOTE: added hasOwnProperty check
243
+ if (options.hasOwnProperty(name)) {
244
+ src = target[name];
245
+ copy = options[name];
246
+ // Prevent never-ending loop
247
+ if (target === copy) {
248
+ continue;
249
+ }
250
+ // Recurse if we're merging object values
251
+ if (deep && copy && typeof copy === "object" && !copy.nodeType) {
252
+ target[name] = _mergeObjs(deep,
253
+ // Never move original objects, clone them
254
+ src || (copy.length != null ? [] : {})
255
+ , copy);
256
+ } else if (copy !== undefined) { // Don't bring in undefined values
257
+ target[name] = copy;
258
+ }
259
+ }
260
+ }
261
+ }
262
+ }
263
+
264
+ // Return the modified object
265
+ return target;
266
+ }
267
+
268
+ /**
269
+ * Initiates the EpicEditor object and sets up offline storage as well
270
+ * @class Represents an EpicEditor instance
271
+ * @param {object} options An optional customization object
272
+ * @returns {object} EpicEditor will be returned
273
+ */
274
+ function EpicEditor(options) {
275
+ // Default settings will be overwritten/extended by options arg
276
+ var self = this
277
+ , opts = options || {}
278
+ , _defaultFileSchema
279
+ , _defaultFile
280
+ , defaults = { container: 'epiceditor'
281
+ , basePath: 'epiceditor'
282
+ , clientSideStorage: true
283
+ , localStorageName: 'epiceditor'
284
+ , file: { name: null
285
+ , defaultContent: ''
286
+ , autoSave: 100 // Set to false for no auto saving
287
+ }
288
+ , theme: { base: '/themes/base/epiceditor.css'
289
+ , preview: '/themes/preview/github.css'
290
+ , editor: '/themes/editor/epic-dark.css'
291
+ }
292
+ , focusOnLoad: false
293
+ , shortcut: { modifier: 18 // alt keycode
294
+ , fullscreen: 70 // f keycode
295
+ , preview: 80 // p keycode
296
+ , edit: 79 // o keycode
297
+ }
298
+ , parser: typeof marked == 'function' ? marked : null
299
+ }
300
+ , defaultStorage;
301
+
302
+ self.settings = _mergeObjs(true, defaults, opts);
303
+
304
+ if (!(typeof self.settings.parser == 'function' && typeof self.settings.parser('TEST') == 'string')) {
305
+ self.settings.parser = function (str) {
306
+ return str;
307
+ }
308
+ }
309
+
310
+
311
+ // Grab the container element and save it to self.element
312
+ // if it's a string assume it's an ID and if it's an object
313
+ // assume it's a DOM element
314
+ if (typeof self.settings.container == 'string') {
315
+ self.element = document.getElementById(self.settings.container);
316
+ }
317
+ else if (typeof self.settings.container == 'object') {
318
+ self.element = self.settings.container;
319
+ }
320
+
321
+ // Figure out the file name. If no file name is given we'll use the ID.
322
+ // If there's no ID either we'll use a namespaced file name that's incremented
323
+ // based on the calling order. As long as it doesn't change, drafts will be saved.
324
+ if (!self.settings.file.name) {
325
+ if (typeof self.settings.container == 'string') {
326
+ self.settings.file.name = self.settings.container;
327
+ }
328
+ else if (typeof self.settings.container == 'object') {
329
+ if (self.element.id) {
330
+ self.settings.file.name = self.element.id;
331
+ }
332
+ else {
333
+ if (!EpicEditor._data.unnamedEditors) {
334
+ EpicEditor._data.unnamedEditors = [];
335
+ }
336
+ EpicEditor._data.unnamedEditors.push(self);
337
+ self.settings.file.name = '__epiceditor-untitled-' + EpicEditor._data.unnamedEditors.length;
338
+ }
339
+ }
340
+ }
341
+
342
+ // Protect the id and overwrite if passed in as an option
343
+ // TODO: Put underscrore to denote that this is private
344
+ self._instanceId = 'epiceditor-' + Math.round(Math.random() * 100000);
345
+ self._storage = {};
346
+ self._canSave = true;
347
+
348
+ // Setup local storage of files
349
+ self._defaultFileSchema = function () {
350
+ return {
351
+ content: self.settings.file.defaultContent
352
+ , created: new Date()
353
+ , modified: new Date()
354
+ }
355
+ }
356
+
357
+ if (localStorage && self.settings.clientSideStorage) {
358
+ this._storage = localStorage;
359
+ if (this._storage[self.settings.localStorageName] && self.getFiles(self.settings.file.name) === undefined) {
360
+ _defaultFile = self.getFiles(self.settings.file.name);
361
+ _defaultFile = self._defaultFileSchema();
362
+ _defaultFile.content = self.settings.file.defaultContent;
363
+ }
364
+ }
365
+
366
+ if (!this._storage[self.settings.localStorageName]) {
367
+ defaultStorage = {};
368
+ defaultStorage[self.settings.file.name] = self._defaultFileSchema();
369
+ defaultStorage = JSON.stringify(defaultStorage);
370
+ this._storage[self.settings.localStorageName] = defaultStorage;
371
+ }
372
+
373
+ // Now that it exists, allow binding of events if it doesn't exist yet
374
+ if (!self.events) {
375
+ self.events = {};
376
+ }
377
+
378
+ return this;
379
+ }
380
+
381
+ /**
382
+ * Inserts the EpicEditor into the DOM via an iframe and gets it ready for editing and previewing
383
+ * @returns {object} EpicEditor will be returned
384
+ */
385
+ EpicEditor.prototype.load = function (callback) {
386
+ // TODO: Gotta get the privates with underscores!
387
+ // TODO: Gotta document what these are for...
388
+ var self = this
389
+ , _HtmlTemplates
390
+ , iframeElement
391
+ , baseTag
392
+ , widthDiff
393
+ , heightDiff
394
+ , utilBtns
395
+ , utilBar
396
+ , utilBarTimer
397
+ , keypressTimer
398
+ , mousePos = { y: -1, x: -1 }
399
+ , _elementStates
400
+ , _isInEdit
401
+ , nativeFs = document.body.webkitRequestFullScreen ? true : false
402
+ , _goFullscreen
403
+ , _exitFullscreen
404
+ , elementsToResize
405
+ , fsElement
406
+ , isMod = false
407
+ , isCtrl = false
408
+ , eventableIframes
409
+ , i; // i is reused for loops
410
+
411
+ callback = callback || function () {};
412
+
413
+ // This needs to replace the use of classes to check the state of EE
414
+ self.eeState = {
415
+ fullscreen: false
416
+ , preview: false
417
+ , edit: true
418
+ , loaded: false
419
+ , unloaded: false
420
+ }
421
+
422
+ // The editor HTML
423
+ // TODO: edit-mode class should be dynamically added
424
+ _HtmlTemplates = {
425
+ // This is wrapping iframe element. It contains the other two iframes and the utilbar
426
+ chrome: '<div id="epiceditor-wrapper" class="epiceditor-edit-mode">' +
427
+ '<iframe frameborder="0" id="epiceditor-editor-frame"></iframe>' +
428
+ '<iframe frameborder="0" id="epiceditor-previewer-frame"></iframe>' +
429
+ '<div id="epiceditor-utilbar">' +
430
+ '<img width="30" src="' + this.settings.basePath + '/images/preview.png" title="Toggle Preview Mode" class="epiceditor-toggle-btn epiceditor-toggle-preview-btn"> ' +
431
+ '<img width="30" src="' + this.settings.basePath + '/images/edit.png" title="Toggle Edit Mode" class="epiceditor-toggle-btn epiceditor-toggle-edit-btn"> ' +
432
+ '<img width="30" src="' + this.settings.basePath + '/images/fullscreen.png" title="Enter Fullscreen" class="epiceditor-fullscreen-btn">' +
433
+ '</div>' +
434
+ '</div>'
435
+
436
+ // The previewer is just an empty box for the generated HTML to go into
437
+ , previewer: '<div id="epiceditor-preview"></div>'
438
+ };
439
+
440
+ // Used to setup the initial size of the iframes
441
+ function setupIframeStyles(el) {
442
+ for (var x = 0; x < el.length; x++) {
443
+ el[x].style.width = self.element.offsetWidth - widthDiff + 'px';
444
+ el[x].style.height = self.element.offsetHeight - heightDiff + 'px';
445
+ }
446
+ }
447
+
448
+ // Used for resetting the width of EE mainly for fluid width containers
449
+ function resetWidth(el) {
450
+ widthDiff = _outerWidth(self.element) - self.element.offsetWidth;
451
+ for (var x = 0; x < el.length; x++) {
452
+ el[x].style.width = self.element.offsetWidth - widthDiff + 'px';
453
+ }
454
+ }
455
+ // Write an iframe and then select it for the editor
456
+ self.element.innerHTML = '<iframe scrolling="no" frameborder="0" id= "' + self._instanceId + '"></iframe>';
457
+ iframeElement = document.getElementById(self._instanceId);
458
+
459
+ // Store a reference to the iframeElement itself
460
+ self.iframeElement = iframeElement;
461
+
462
+ // Grab the innards of the iframe (returns the document.body)
463
+ // TODO: Change self.iframe to self.iframeDocument
464
+ self.iframe = _getIframeInnards(iframeElement);
465
+ self.iframe.open();
466
+ self.iframe.write(_HtmlTemplates.chrome);
467
+
468
+ // Now that we got the innards of the iframe, we can grab the other iframes
469
+ self.editorIframe = self.iframe.getElementById('epiceditor-editor-frame')
470
+ self.previewerIframe = self.iframe.getElementById('epiceditor-previewer-frame');
471
+
472
+ // Setup the editor iframe
473
+ self.editorIframeDocument = _getIframeInnards(self.editorIframe);
474
+ self.editorIframeDocument.open();
475
+ // Need something for... you guessed it, Firefox
476
+ self.editorIframeDocument.write('');
477
+ self.editorIframeDocument.close();
478
+
479
+ // Setup the previewer iframe
480
+ self.previewerIframeDocument = _getIframeInnards(self.previewerIframe);
481
+ self.previewerIframeDocument.open();
482
+ self.previewerIframeDocument.write(_HtmlTemplates.previewer);
483
+
484
+ // Base tag is added so that links will open a new tab and not inside of the iframes
485
+ baseTag = self.previewerIframeDocument.createElement('base');
486
+ baseTag.target = '_blank';
487
+ self.previewerIframeDocument.getElementsByTagName('head')[0].appendChild(baseTag);
488
+
489
+ self.previewerIframeDocument.close();
490
+
491
+ // Set the default styles for the iframe
492
+ widthDiff = _outerWidth(self.element) - self.element.offsetWidth;
493
+ heightDiff = _outerHeight(self.element) - self.element.offsetHeight;
494
+ elementsToResize = [self.iframeElement, self.editorIframe, self.previewerIframe];
495
+
496
+ setupIframeStyles(elementsToResize);
497
+
498
+ // Insert Base Stylesheet
499
+ _insertCSSLink(self.settings.basePath + self.settings.theme.base, self.iframe, 'theme');
500
+
501
+ // Insert Editor Stylesheet
502
+ _insertCSSLink(self.settings.basePath + self.settings.theme.editor, self.editorIframeDocument, 'theme');
503
+
504
+ // Insert Previewer Stylesheet
505
+ _insertCSSLink(self.settings.basePath + self.settings.theme.preview, self.previewerIframeDocument, 'theme');
506
+
507
+ // Add a relative style to the overall wrapper to keep CSS relative to the editor
508
+ self.iframe.getElementById('epiceditor-wrapper').style.position = 'relative';
509
+
510
+ // Now grab the editor and previewer for later use
511
+ self.editor = self.editorIframeDocument.body;
512
+ self.previewer = self.previewerIframeDocument.getElementById('epiceditor-preview');
513
+
514
+ self.editor.contentEditable = true;
515
+
516
+ // Firefox's <body> gets all fucked up so, to be sure, we need to hardcode it
517
+ self.iframe.body.style.height = this.element.offsetHeight + 'px';
518
+
519
+ // Should actually check what mode it's in!
520
+ this.previewerIframe.style.display = 'none';
521
+
522
+ // FIXME figure out why it needs +2 px
523
+ if (_isIE() > -1) {
524
+ this.previewer.style.height = parseInt(_getStyle(this.previewer, 'height'), 10) + 2;
525
+ }
526
+
527
+ // If there is a file to be opened with that filename and it has content...
528
+ this.open(self.settings.file.name);
529
+
530
+ if (self.settings.focusOnLoad) {
531
+ // We need to wait until all three iframes are done loading by waiting until the parent
532
+ // iframe's ready state == complete, then we can focus on the contenteditable
533
+ self.iframe.addEventListener('readystatechange', function () {
534
+ if (self.iframe.readyState == 'complete') {
535
+ self.editorIframeDocument.body.focus();
536
+ }
537
+ });
538
+ }
539
+
540
+ // TODO: Should probably have an ID since we only select one
541
+ // TODO: Should probably have an underscore?
542
+ utilBtns = self.iframe.getElementById('epiceditor-utilbar');
543
+
544
+ _elementStates = {}
545
+ _goFullscreen = function (el) {
546
+
547
+ if (self.eeState.fullscreen) {
548
+ _exitFullscreen(el);
549
+ return;
550
+ }
551
+
552
+ if (nativeFs) {
553
+ el.webkitRequestFullScreen();
554
+ }
555
+
556
+ _isInEdit = self.eeState.edit;
557
+
558
+ // Set the state of EE in fullscreen
559
+ // We set edit and preview to true also because they're visible
560
+ // we might want to allow fullscreen edit mode without preview (like a "zen" mode)
561
+ self.eeState.fullscreen = true;
562
+ self.eeState.edit = true;
563
+ self.eeState.preview = true;
564
+
565
+ // Cache calculations
566
+ var windowInnerWidth = window.innerWidth
567
+ , windowInnerHeight = window.innerHeight
568
+ , windowOuterWidth = window.outerWidth
569
+ , windowOuterHeight = window.outerHeight;
570
+
571
+ // Without this the scrollbars will get hidden when scrolled to the bottom in faux fullscreen (see #66)
572
+ if (!nativeFs) {
573
+ windowOuterHeight = window.innerHeight;
574
+ }
575
+
576
+ // This MUST come first because the editor is 100% width so if we change the width of the iframe or wrapper
577
+ // the editor's width wont be the same as before
578
+ _elementStates.editorIframe = _saveStyleState(self.editorIframe, 'save', {
579
+ 'width': windowOuterWidth / 2 + 'px'
580
+ , 'height': windowOuterHeight + 'px'
581
+ , 'float': 'left' // Most browsers
582
+ , 'cssFloat': 'left' // FF
583
+ , 'styleFloat': 'left' // Older IEs
584
+ , 'display': 'block'
585
+ });
586
+
587
+ // the previewer
588
+ _elementStates.previewerIframe = _saveStyleState(self.previewerIframe, 'save', {
589
+ 'width': windowOuterWidth / 2 + 'px'
590
+ , 'height': windowOuterHeight + 'px'
591
+ , 'float': 'right' // Most browsers
592
+ , 'cssFloat': 'right' // FF
593
+ , 'styleFloat': 'right' // Older IEs
594
+ , 'display': 'block'
595
+ });
596
+
597
+ // Setup the containing element CSS for fullscreen
598
+ _elementStates.element = _saveStyleState(self.element, 'save', {
599
+ 'position': 'fixed'
600
+ , 'top': '0'
601
+ , 'left': '0'
602
+ , 'width': '100%'
603
+ , 'z-index': '9999' // Most browsers
604
+ , 'zIndex': '9999' // Firefox
605
+ , 'border': 'none'
606
+ , 'margin': '0'
607
+ // Should use the base styles background!
608
+ , 'background': _getStyle(self.editor, 'background-color') // Try to hide the site below
609
+ , 'height': windowInnerHeight + 'px'
610
+ });
611
+
612
+ // The iframe element
613
+ _elementStates.iframeElement = _saveStyleState(self.iframeElement, 'save', {
614
+ 'width': windowOuterWidth + 'px'
615
+ , 'height': windowInnerHeight + 'px'
616
+ });
617
+
618
+ // ...Oh, and hide the buttons and prevent scrolling
619
+ utilBtns.style.visibility = 'hidden';
620
+
621
+ if (!nativeFs) {
622
+ document.body.style.overflow = 'hidden';
623
+ }
624
+
625
+ self.preview();
626
+
627
+ self.editorIframeDocument.body.focus();
628
+ };
629
+
630
+ _exitFullscreen = function (el) {
631
+ _saveStyleState(self.element, 'apply', _elementStates.element);
632
+ _saveStyleState(self.iframeElement, 'apply', _elementStates.iframeElement);
633
+ _saveStyleState(self.editorIframe, 'apply', _elementStates.editorIframe);
634
+ _saveStyleState(self.previewerIframe, 'apply', _elementStates.previewerIframe);
635
+
636
+ // We want to always revert back to the original styles in the CSS so,
637
+ // if it's a fluid width container it will expand on resize and not get
638
+ // stuck at a specific width after closing fullscreen.
639
+ self.element.style.width = '';
640
+ self.element.style.height = '';
641
+
642
+ utilBtns.style.visibility = 'visible';
643
+
644
+ if (!nativeFs) {
645
+ document.body.style.overflow = 'auto';
646
+ }
647
+ else {
648
+ document.webkitCancelFullScreen();
649
+ }
650
+ // Put the editor back in the right state
651
+ // TODO: This is ugly... how do we make this nicer?
652
+ self.eeState.fullscreen = false;
653
+
654
+ if (_isInEdit) {
655
+ self.edit();
656
+ }
657
+ else {
658
+ self.preview();
659
+ }
660
+
661
+ resetWidth(elementsToResize);
662
+ };
663
+
664
+ // This setups up live previews by triggering preview() IF in fullscreen on keyup
665
+ self.editor.addEventListener('keyup', function () {
666
+ if (keypressTimer) {
667
+ window.clearTimeout(keypressTimer);
668
+ }
669
+ keypressTimer = window.setTimeout(function () {
670
+ if (self.eeState.fullscreen) {
671
+ self.preview();
672
+ }
673
+ }, 250);
674
+ });
675
+
676
+ fsElement = self.iframeElement;
677
+
678
+ // Sets up the onclick event on utility buttons
679
+ utilBtns.addEventListener('click', function (e) {
680
+ var targetClass = e.target.className;
681
+ if (targetClass.indexOf('epiceditor-toggle-preview-btn') > -1) {
682
+ self.preview();
683
+ }
684
+ else if (targetClass.indexOf('epiceditor-toggle-edit-btn') > -1) {
685
+ self.edit();
686
+ }
687
+ else if (targetClass.indexOf('epiceditor-fullscreen-btn') > -1) {
688
+ _goFullscreen(fsElement);
689
+ }
690
+ });
691
+
692
+ // Sets up the NATIVE fullscreen editor/previewer for WebKit
693
+ if (document.body.webkitRequestFullScreen) {
694
+ fsElement.addEventListener('webkitfullscreenchange', function () {
695
+ if (!document.webkitIsFullScreen) {
696
+ _exitFullscreen(fsElement);
697
+ }
698
+ }, false);
699
+ }
700
+
701
+ utilBar = self.iframe.getElementById('epiceditor-utilbar');
702
+
703
+ // Hide it at first until they move their mouse
704
+ utilBar.style.display = 'none';
705
+
706
+ utilBar.addEventListener('mouseover', function () {
707
+ if (utilBarTimer) {
708
+ clearTimeout(utilBarTimer);
709
+ }
710
+ });
711
+
712
+ function utilBarHandler(e) {
713
+ // Here we check if the mouse has moves more than 5px in any direction before triggering the mousemove code
714
+ // we do this for 2 reasons:
715
+ // 1. On Mac OS X lion when you scroll and it does the iOS like "jump" when it hits the top/bottom of the page itll fire off
716
+ // a mousemove of a few pixels depending on how hard you scroll
717
+ // 2. We give a slight buffer to the user in case he barely touches his touchpad or mouse and not trigger the UI
718
+ if (Math.abs(mousePos.y - e.pageY) >= 5 || Math.abs(mousePos.x - e.pageX) >= 5) {
719
+ utilBar.style.display = 'block';
720
+ // if we have a timer already running, kill it out
721
+ if (utilBarTimer) {
722
+ clearTimeout(utilBarTimer);
723
+ }
724
+
725
+ // begin a new timer that hides our object after 1000 ms
726
+ utilBarTimer = window.setTimeout(function () {
727
+ utilBar.style.display = 'none';
728
+ }, 1000);
729
+ }
730
+ mousePos = { y: e.pageY, x: e.pageX };
731
+ }
732
+
733
+ // Add keyboard shortcuts for convenience.
734
+ function shortcutHandler(e) {
735
+ if (e.keyCode == self.settings.shortcut.modifier) { isMod = true } // check for modifier press(default is alt key), save to var
736
+ if (e.keyCode == 17) { isCtrl = true } // check for ctrl/cmnd press, in order to catch ctrl/cmnd + s
737
+
738
+ // Check for alt+p and make sure were not in fullscreen - default shortcut to switch to preview
739
+ if (isMod === true && e.keyCode == self.settings.shortcut.preview && !self.eeState.fullscreen) {
740
+ e.preventDefault();
741
+ self.preview();
742
+ }
743
+ // Check for alt+o - default shortcut to switch back to the editor
744
+ if (isMod === true && e.keyCode == self.settings.shortcut.edit) {
745
+ e.preventDefault();
746
+ if (!self.eeState.fullscreen) {
747
+ self.edit();
748
+ }
749
+ }
750
+ // Check for alt+f - default shortcut to make editor fullscreen
751
+ if (isMod === true && e.keyCode == self.settings.shortcut.fullscreen) {
752
+ e.preventDefault();
753
+ _goFullscreen(fsElement);
754
+ }
755
+
756
+ // Set the modifier key to false once *any* key combo is completed
757
+ // or else, on Windows, hitting the alt key will lock the isMod state to true (ticket #133)
758
+ if (isMod === true && e.keyCode !== self.settings.shortcut.modifier) {
759
+ isMod = false;
760
+ }
761
+
762
+ // When a user presses "esc", revert everything!
763
+ if (e.keyCode == 27 && self.eeState.fullscreen) {
764
+ if (!document.body.webkitRequestFullScreen) {
765
+ _exitFullscreen(fsElement);
766
+ }
767
+ }
768
+
769
+ // Check for ctrl + s (since a lot of people do it out of habit) and make it do nothing
770
+ if (isCtrl === true && e.keyCode == 83) {
771
+ self.save();
772
+ e.preventDefault();
773
+ isCtrl = false;
774
+ }
775
+
776
+ // Do the same for Mac now (metaKey == cmd).
777
+ if (e.metaKey && e.keyCode == 83) {
778
+ self.save();
779
+ e.preventDefault();
780
+ }
781
+
782
+ }
783
+
784
+ function shortcutUpHandler(e) {
785
+ if (e.keyCode == self.settings.shortcut.modifier) { isMod = false }
786
+ if (e.keyCode == 17) { isCtrl = false }
787
+ }
788
+
789
+ // Hide and show the util bar based on mouse movements
790
+ eventableIframes = [self.previewerIframeDocument, self.editorIframeDocument];
791
+
792
+ for (i = 0; i < eventableIframes.length; i++) {
793
+ eventableIframes[i].addEventListener('mousemove', function (e) {
794
+ utilBarHandler(e);
795
+ });
796
+ eventableIframes[i].addEventListener('scroll', function (e) {
797
+ utilBarHandler(e);
798
+ });
799
+ eventableIframes[i].addEventListener('keyup', function (e) {
800
+ shortcutUpHandler(e);
801
+ });
802
+ eventableIframes[i].addEventListener('keydown', function (e) {
803
+ shortcutHandler(e);
804
+ });
805
+ }
806
+
807
+ // Save the document every 100ms by default
808
+ if (self.settings.file.autoSave) {
809
+ self.saveInterval = window.setInterval(function () {
810
+ if (!self._canSave) {
811
+ return;
812
+ }
813
+ self.save();
814
+ }, self.settings.file.autoSave);
815
+ }
816
+
817
+ window.addEventListener('resize', function () {
818
+ // If NOT webkit, and in fullscreen, we need to account for browser resizing
819
+ // we don't care about webkit because you can't resize in webkit's fullscreen
820
+ if (!self.iframe.webkitRequestFullScreen && self.eeState.fullscreen) {
821
+ _applyStyles(self.iframeElement, {
822
+ 'width': window.outerWidth + 'px'
823
+ , 'height': window.innerHeight + 'px'
824
+ });
825
+
826
+ _applyStyles(self.element, {
827
+ 'height': window.innerHeight + 'px'
828
+ });
829
+
830
+ _applyStyles(self.previewerIframe, {
831
+ 'width': window.outerWidth / 2 + 'px'
832
+ , 'height': window.innerHeight + 'px'
833
+ });
834
+
835
+ _applyStyles(self.editorIframe, {
836
+ 'width': window.outerWidth / 2 + 'px'
837
+ , 'height': window.innerHeight + 'px'
838
+ });
839
+ }
840
+ // Makes the editor support fluid width when not in fullscreen mode
841
+ else if (!self.eeState.fullscreen) {
842
+ resetWidth(elementsToResize);
843
+ }
844
+ });
845
+
846
+ self.iframe.close();
847
+ self.eeState.loaded = true;
848
+ self.eeState.unloaded = false;
849
+ // The callback and call are the same thing, but different ways to access them
850
+ callback.call(this);
851
+ this.emit('load');
852
+ return this;
853
+ }
854
+
855
+ /**
856
+ * Will remove the editor, but not offline files
857
+ * @returns {object} EpicEditor will be returned
858
+ */
859
+ EpicEditor.prototype.unload = function (callback) {
860
+
861
+ // Make sure the editor isn't already unloaded.
862
+ if (this.eeState.unloaded) {
863
+ throw new Error('Editor isn\'t loaded');
864
+ }
865
+
866
+ var self = this
867
+ , editor = window.parent.document.getElementById(self._instanceId);
868
+
869
+ editor.parentNode.removeChild(editor);
870
+ self.eeState.loaded = false;
871
+ self.eeState.unloaded = true;
872
+ callback = callback || function () {};
873
+
874
+ if (self.saveInterval) {
875
+ window.clearInterval(self.saveInterval);
876
+ }
877
+
878
+ callback.call(this);
879
+ self.emit('unload');
880
+ return self;
881
+ }
882
+
883
+ /**
884
+ * Will take the markdown and generate a preview view based on the theme
885
+ * @param {string} theme The path to the theme you want to preview in
886
+ * @returns {object} EpicEditor will be returned
887
+ */
888
+ EpicEditor.prototype.preview = function (theme) {
889
+ var self = this;
890
+
891
+ theme = theme || self.settings.basePath + self.settings.theme.preview;
892
+
893
+ _replaceClass(self.getElement('wrapper'), 'epiceditor-edit-mode', 'epiceditor-preview-mode');
894
+
895
+ // Check if no CSS theme link exists
896
+ if (!self.previewerIframeDocument.getElementById('theme')) {
897
+ _insertCSSLink(theme, self.previewerIframeDocument, 'theme');
898
+ }
899
+ else if (self.previewerIframeDocument.getElementById('theme').name !== theme) {
900
+ self.previewerIframeDocument.getElementById('theme').href = theme;
901
+ }
902
+
903
+ // Add the generated HTML into the previewer
904
+ self.previewer.innerHTML = self.exportFile(null, 'html');
905
+
906
+ // Hide the editor and display the previewer
907
+ if (!self.eeState.fullscreen) {
908
+ self.editorIframe.style.display = 'none';
909
+ self.previewerIframe.style.display = 'block';
910
+ self.eeState.preview = true;
911
+ self.eeState.edit = false;
912
+ self.previewerIframe.focus();
913
+ }
914
+
915
+ self.emit('preview');
916
+ return self;
917
+ }
918
+
919
+ /**
920
+ * Hides the preview and shows the editor again
921
+ * @returns {object} EpicEditor will be returned
922
+ */
923
+ EpicEditor.prototype.edit = function () {
924
+ var self = this;
925
+ _replaceClass(self.getElement('wrapper'), 'epiceditor-preview-mode', 'epiceditor-edit-mode');
926
+ self.eeState.preview = false;
927
+ self.eeState.edit = true;
928
+ self.editorIframe.style.display = 'block';
929
+ self.previewerIframe.style.display = 'none';
930
+ self.editorIframe.focus();
931
+ self.emit('edit');
932
+ return this;
933
+ }
934
+
935
+ /**
936
+ * Grabs a specificed HTML node. Use it as a shortcut to getting the iframe contents
937
+ * @param {String} name The name of the node (can be document, body, editor, previewer, or wrapper)
938
+ * @returns {Object|Null}
939
+ */
940
+ EpicEditor.prototype.getElement = function (name) {
941
+ var available = {
942
+ "container": this.element
943
+ , "wrapper": this.iframe.getElementById('epiceditor-wrapper')
944
+ , "wrapperIframe": this.iframeElement
945
+ , "editor": this.editorIframeDocument
946
+ , "editorIframe": this.editorIframe
947
+ , "previewer": this.previewerIframeDocument
948
+ , "previewerIframe": this.previewerIframe
949
+ }
950
+
951
+ // Check that the given string is a possible option and verify the editor isn't unloaded
952
+ // without this, you'd be given a reference to an object that no longer exists in the DOM
953
+ if (!available[name] || this.eeState.unloaded) {
954
+ return null;
955
+ }
956
+ else {
957
+ return available[name];
958
+ }
959
+ }
960
+
961
+ /**
962
+ * Opens a file
963
+ * @param {string} name The name of the file you want to open
964
+ * @returns {object} EpicEditor will be returned
965
+ */
966
+ EpicEditor.prototype.open = function (name) {
967
+ var self = this
968
+ , defaultContent = self.settings.file.defaultContent
969
+ , fileObj;
970
+ name = name || self.settings.file.name;
971
+ self.settings.file.name = name;
972
+ if (this._storage[self.settings.localStorageName]) {
973
+ fileObj = self.getFiles();
974
+ if (fileObj[name] !== undefined) {
975
+ _setText(self.editor, fileObj[name].content);
976
+ self.emit('read');
977
+ }
978
+ else {
979
+ _setText(self.editor, defaultContent);
980
+ self.save(); // ensure a save
981
+ self.emit('create');
982
+ }
983
+ self.previewer.innerHTML = self.exportFile(null, 'html');
984
+ self.emit('open');
985
+ }
986
+ return this;
987
+ }
988
+
989
+ /**
990
+ * Saves content for offline use
991
+ * @returns {object} EpicEditor will be returned
992
+ */
993
+ EpicEditor.prototype.save = function () {
994
+ var self = this
995
+ , storage
996
+ , isUpdate = false
997
+ , file = self.settings.file.name
998
+ , content = _getText(this.editor);
999
+
1000
+ // This could have been false but since we're manually saving
1001
+ // we know it's save to start autoSaving again
1002
+ this._canSave = true;
1003
+
1004
+ storage = JSON.parse(this._storage[self.settings.localStorageName]);
1005
+
1006
+ // If the file doesn't exist we need to create it
1007
+ if (storage[file] === undefined) {
1008
+ storage[file] = self._defaultFileSchema();
1009
+ }
1010
+
1011
+ // If it does, we need to check if the content is different and
1012
+ // if it is, send the update event and update the timestamp
1013
+ else if (content !== storage[file].content) {
1014
+ storage[file].modified = new Date();
1015
+ isUpdate = true;
1016
+ }
1017
+
1018
+ storage[file].content = content;
1019
+ this._storage[self.settings.localStorageName] = JSON.stringify(storage);
1020
+
1021
+ // After the content is actually changed, emit update so it emits the updated content
1022
+ if (isUpdate) {
1023
+ self.emit('update');
1024
+ }
1025
+
1026
+ this.emit('save');
1027
+ return this;
1028
+ }
1029
+
1030
+ /**
1031
+ * Removes a page
1032
+ * @param {string} name The name of the file you want to remove from localStorage
1033
+ * @returns {object} EpicEditor will be returned
1034
+ */
1035
+ EpicEditor.prototype.remove = function (name) {
1036
+ var self = this
1037
+ , s;
1038
+ name = name || self.settings.file.name;
1039
+
1040
+ // If you're trying to delete a page you have open, block saving
1041
+ if (name == self.settings.file.name) {
1042
+ self._canSave = false;
1043
+ }
1044
+
1045
+ s = JSON.parse(this._storage[self.settings.localStorageName]);
1046
+ delete s[name];
1047
+ this._storage[self.settings.localStorageName] = JSON.stringify(s);
1048
+ this.emit('remove');
1049
+ return this;
1050
+ };
1051
+
1052
+ /**
1053
+ * Renames a file
1054
+ * @param {string} oldName The old file name
1055
+ * @param {string} newName The new file name
1056
+ * @returns {object} EpicEditor will be returned
1057
+ */
1058
+ EpicEditor.prototype.rename = function (oldName, newName) {
1059
+ var self = this
1060
+ , s = JSON.parse(this._storage[self.settings.localStorageName]);
1061
+ s[newName] = s[oldName];
1062
+ delete s[oldName];
1063
+ this._storage[self.settings.localStorageName] = JSON.stringify(s);
1064
+ self.open(newName);
1065
+ return this;
1066
+ };
1067
+
1068
+ /**
1069
+ * Imports a file and it's contents and opens it
1070
+ * @param {string} name The name of the file you want to import (will overwrite existing files!)
1071
+ * @param {string} content Content of the file you want to import
1072
+ * @param {string} kind The kind of file you want to import (TBI)
1073
+ * @param {object} meta Meta data you want to save with your file.
1074
+ * @returns {object} EpicEditor will be returned
1075
+ */
1076
+ EpicEditor.prototype.importFile = function (name, content, kind, meta) {
1077
+ var self = this
1078
+ , isNew = false;
1079
+
1080
+ name = name || self.settings.file.name;
1081
+ content = content || '';
1082
+ kind = kind || 'md';
1083
+ meta = meta || {};
1084
+
1085
+ if (JSON.parse(this._storage[self.settings.localStorageName])[name] === undefined) {
1086
+ isNew = true;
1087
+ }
1088
+
1089
+ // Set our current file to the new file and update the content
1090
+ self.settings.file.name = name;
1091
+ _setText(self.editor, content);
1092
+
1093
+ if (isNew) {
1094
+ self.emit('create');
1095
+ }
1096
+
1097
+ self.save();
1098
+
1099
+ if (self.eeState.fullscreen) {
1100
+ self.preview();
1101
+ }
1102
+
1103
+ return this;
1104
+ };
1105
+
1106
+ /**
1107
+ * Exports a file as a string in a supported format
1108
+ * @param {string} name Name of the file you want to export (case sensitive)
1109
+ * @param {string} kind Kind of file you want the content in (currently supports html and text)
1110
+ * @returns {string|undefined} The content of the file in the content given or undefined if it doesn't exist
1111
+ */
1112
+ EpicEditor.prototype.exportFile = function (name, kind) {
1113
+ var self = this
1114
+ , file
1115
+ , content;
1116
+
1117
+ name = name || self.settings.file.name;
1118
+ kind = kind || 'text';
1119
+
1120
+ file = self.getFiles(name);
1121
+
1122
+ // If the file doesn't exist just return early with undefined
1123
+ if (file === undefined) {
1124
+ return;
1125
+ }
1126
+
1127
+ content = file.content;
1128
+
1129
+ switch (kind) {
1130
+ case 'html':
1131
+ // Get this, 2 spaces in a content editable actually converts to:
1132
+ // 0020 00a0, meaning, "space no-break space". So, manually convert
1133
+ // no-break spaces to spaces again before handing to marked.
1134
+ // Also, WebKit converts no-break to unicode equivalent and FF HTML.
1135
+ content = content.replace(/\u00a0/g, ' ').replace(/&nbsp;/g, ' ');
1136
+ return self.settings.parser(content);
1137
+ case 'text':
1138
+ content = content.replace(/&nbsp;/g, ' ');
1139
+ return content;
1140
+ default:
1141
+ return content;
1142
+ }
1143
+ }
1144
+
1145
+ EpicEditor.prototype.getFiles = function (name) {
1146
+ var files = JSON.parse(this._storage[this.settings.localStorageName]);
1147
+ if (name) {
1148
+ return files[name];
1149
+ }
1150
+ else {
1151
+ return files;
1152
+ }
1153
+ }
1154
+
1155
+ // EVENTS
1156
+ // TODO: Support for namespacing events like "preview.foo"
1157
+ /**
1158
+ * Sets up an event handler for a specified event
1159
+ * @param {string} ev The event name
1160
+ * @param {function} handler The callback to run when the event fires
1161
+ * @returns {object} EpicEditor will be returned
1162
+ */
1163
+ EpicEditor.prototype.on = function (ev, handler) {
1164
+ var self = this;
1165
+ if (!this.events[ev]) {
1166
+ this.events[ev] = [];
1167
+ }
1168
+ this.events[ev].push(handler);
1169
+ return self;
1170
+ };
1171
+
1172
+ /**
1173
+ * This will emit or "trigger" an event specified
1174
+ * @param {string} ev The event name
1175
+ * @param {any} data Any data you want to pass into the callback
1176
+ * @returns {object} EpicEditor will be returned
1177
+ */
1178
+ EpicEditor.prototype.emit = function (ev, data) {
1179
+ var self = this
1180
+ , x;
1181
+
1182
+ data = data || self.getFiles(self.settings.file.name);
1183
+
1184
+ if (!this.events[ev]) {
1185
+ return;
1186
+ }
1187
+
1188
+ function invokeHandler(handler) {
1189
+ handler.call(self, data);
1190
+ }
1191
+
1192
+ for (x = 0; x < self.events[ev].length; x++) {
1193
+ invokeHandler(self.events[ev][x]);
1194
+ }
1195
+
1196
+ return self;
1197
+ };
1198
+
1199
+ /**
1200
+ * Will remove any listeners added from EpicEditor.on()
1201
+ * @param {string} ev The event name
1202
+ * @param {function} handler Handler to remove
1203
+ * @returns {object} EpicEditor will be returned
1204
+ */
1205
+ EpicEditor.prototype.removeListener = function (ev, handler) {
1206
+ var self = this;
1207
+ if (!handler) {
1208
+ this.events[ev] = [];
1209
+ return self;
1210
+ }
1211
+ if (!this.events[ev]) {
1212
+ return self;
1213
+ }
1214
+ // Otherwise a handler and event exist, so take care of it
1215
+ this.events[ev].splice(this.events[ev].indexOf(handler), 1);
1216
+ return self;
1217
+ }
1218
+
1219
+ EpicEditor.version = '0.1.1';
1220
+
1221
+ // Used to store information to be shared acrossed editors
1222
+ EpicEditor._data = {};
1223
+
1224
+ window.EpicEditor = EpicEditor;
1225
+ })(window);
1226
+
1227
+ /**
1228
+ * marked - A markdown parser (https://github.com/chjj/marked)
1229
+ * Copyright (c) 2011-2012, Christopher Jeffrey. (MIT Licensed)
1230
+ */
1231
+
1232
+ ;(function() {
1233
+
1234
+ /**
1235
+ * Block-Level Grammar
1236
+ */
1237
+
1238
+ var block = {
1239
+ newline: /^\n+/,
1240
+ code: /^( {4}[^\n]+\n*)+/,
1241
+ fences: noop,
1242
+ hr: /^( *[-*_]){3,} *(?:\n+|$)/,
1243
+ heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
1244
+ lheading: /^([^\n]+)\n *(=|-){3,} *\n*/,
1245
+ blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/,
1246
+ list: /^( *)(bull) [^\0]+?(?:hr|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
1247
+ html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/,
1248
+ def: /^ *\[([^\]]+)\]: *([^\s]+)(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
1249
+ paragraph: /^([^\n]+\n?(?!body))+\n*/,
1250
+ text: /^[^\n]+/
1251
+ };
1252
+
1253
+ block.bullet = /(?:[*+-]|\d+\.)/;
1254
+ block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
1255
+ block.item = replace(block.item, 'gm')
1256
+ (/bull/g, block.bullet)
1257
+ ();
1258
+
1259
+ block.list = replace(block.list)
1260
+ (/bull/g, block.bullet)
1261
+ ('hr', /\n+(?=(?: *[-*_]){3,} *(?:\n+|$))/)
1262
+ ();
1263
+
1264
+ block.html = replace(block.html)
1265
+ ('comment', /<!--[^\0]*?-->/)
1266
+ ('closed', /<(tag)[^\0]+?<\/\1>/)
1267
+ ('closing', /<tag(?!:\/|@)\b(?:"[^"]*"|'[^']*'|[^'">])*?>/)
1268
+ (/tag/g, tag())
1269
+ ();
1270
+
1271
+ block.paragraph = (function() {
1272
+ var paragraph = block.paragraph.source
1273
+ , body = [];
1274
+
1275
+ (function push(rule) {
1276
+ rule = block[rule] ? block[rule].source : rule;
1277
+ body.push(rule.replace(/(^|[^\[])\^/g, '$1'));
1278
+ return push;
1279
+ })
1280
+ ('hr')
1281
+ ('heading')
1282
+ ('lheading')
1283
+ ('blockquote')
1284
+ ('<' + tag())
1285
+ ('def');
1286
+
1287
+ return new
1288
+ RegExp(paragraph.replace('body', body.join('|')));
1289
+ })();
1290
+
1291
+ block.normal = {
1292
+ fences: block.fences,
1293
+ paragraph: block.paragraph
1294
+ };
1295
+
1296
+ block.gfm = {
1297
+ fences: /^ *``` *(\w+)? *\n([^\0]+?)\s*``` *(?:\n+|$)/,
1298
+ paragraph: /^/
1299
+ };
1300
+
1301
+ block.gfm.paragraph = replace(block.paragraph)
1302
+ ('(?!', '(?!' + block.gfm.fences.source.replace(/(^|[^\[])\^/g, '$1') + '|')
1303
+ ();
1304
+
1305
+ /**
1306
+ * Block Lexer
1307
+ */
1308
+
1309
+ block.lexer = function(src) {
1310
+ var tokens = [];
1311
+
1312
+ tokens.links = {};
1313
+
1314
+ src = src
1315
+ .replace(/\r\n|\r/g, '\n')
1316
+ .replace(/\t/g, ' ');
1317
+
1318
+ return block.token(src, tokens, true);
1319
+ };
1320
+
1321
+ block.token = function(src, tokens, top) {
1322
+ var src = src.replace(/^ +$/gm, '')
1323
+ , next
1324
+ , loose
1325
+ , cap
1326
+ , item
1327
+ , space
1328
+ , i
1329
+ , l;
1330
+
1331
+ while (src) {
1332
+ // newline
1333
+ if (cap = block.newline.exec(src)) {
1334
+ src = src.substring(cap[0].length);
1335
+ if (cap[0].length > 1) {
1336
+ tokens.push({
1337
+ type: 'space'
1338
+ });
1339
+ }
1340
+ }
1341
+
1342
+ // code
1343
+ if (cap = block.code.exec(src)) {
1344
+ src = src.substring(cap[0].length);
1345
+ cap = cap[0].replace(/^ {4}/gm, '');
1346
+ tokens.push({
1347
+ type: 'code',
1348
+ text: !options.pedantic
1349
+ ? cap.replace(/\n+$/, '')
1350
+ : cap
1351
+ });
1352
+ continue;
1353
+ }
1354
+
1355
+ // fences (gfm)
1356
+ if (cap = block.fences.exec(src)) {
1357
+ src = src.substring(cap[0].length);
1358
+ tokens.push({
1359
+ type: 'code',
1360
+ lang: cap[1],
1361
+ text: cap[2]
1362
+ });
1363
+ continue;
1364
+ }
1365
+
1366
+ // heading
1367
+ if (cap = block.heading.exec(src)) {
1368
+ src = src.substring(cap[0].length);
1369
+ tokens.push({
1370
+ type: 'heading',
1371
+ depth: cap[1].length,
1372
+ text: cap[2]
1373
+ });
1374
+ continue;
1375
+ }
1376
+
1377
+ // lheading
1378
+ if (cap = block.lheading.exec(src)) {
1379
+ src = src.substring(cap[0].length);
1380
+ tokens.push({
1381
+ type: 'heading',
1382
+ depth: cap[2] === '=' ? 1 : 2,
1383
+ text: cap[1]
1384
+ });
1385
+ continue;
1386
+ }
1387
+
1388
+ // hr
1389
+ if (cap = block.hr.exec(src)) {
1390
+ src = src.substring(cap[0].length);
1391
+ tokens.push({
1392
+ type: 'hr'
1393
+ });
1394
+ continue;
1395
+ }
1396
+
1397
+ // blockquote
1398
+ if (cap = block.blockquote.exec(src)) {
1399
+ src = src.substring(cap[0].length);
1400
+
1401
+ tokens.push({
1402
+ type: 'blockquote_start'
1403
+ });
1404
+
1405
+ cap = cap[0].replace(/^ *> ?/gm, '');
1406
+
1407
+ // Pass `top` to keep the current
1408
+ // "toplevel" state. This is exactly
1409
+ // how markdown.pl works.
1410
+ block.token(cap, tokens, top);
1411
+
1412
+ tokens.push({
1413
+ type: 'blockquote_end'
1414
+ });
1415
+
1416
+ continue;
1417
+ }
1418
+
1419
+ // list
1420
+ if (cap = block.list.exec(src)) {
1421
+ src = src.substring(cap[0].length);
1422
+
1423
+ tokens.push({
1424
+ type: 'list_start',
1425
+ ordered: isFinite(cap[2])
1426
+ });
1427
+
1428
+ // Get each top-level item.
1429
+ cap = cap[0].match(block.item);
1430
+
1431
+ next = false;
1432
+ l = cap.length;
1433
+ i = 0;
1434
+
1435
+ for (; i < l; i++) {
1436
+ item = cap[i];
1437
+
1438
+ // Remove the list item's bullet
1439
+ // so it is seen as the next token.
1440
+ space = item.length;
1441
+ item = item.replace(/^ *([*+-]|\d+\.) +/, '');
1442
+
1443
+ // Outdent whatever the
1444
+ // list item contains. Hacky.
1445
+ if (~item.indexOf('\n ')) {
1446
+ space -= item.length;
1447
+ item = !options.pedantic
1448
+ ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')
1449
+ : item.replace(/^ {1,4}/gm, '');
1450
+ }
1451
+
1452
+ // Determine whether item is loose or not.
1453
+ // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
1454
+ // for discount behavior.
1455
+ loose = next || /\n\n(?!\s*$)/.test(item);
1456
+ if (i !== l - 1) {
1457
+ next = item[item.length-1] === '\n';
1458
+ if (!loose) loose = next;
1459
+ }
1460
+
1461
+ tokens.push({
1462
+ type: loose
1463
+ ? 'loose_item_start'
1464
+ : 'list_item_start'
1465
+ });
1466
+
1467
+ // Recurse.
1468
+ block.token(item, tokens);
1469
+
1470
+ tokens.push({
1471
+ type: 'list_item_end'
1472
+ });
1473
+ }
1474
+
1475
+ tokens.push({
1476
+ type: 'list_end'
1477
+ });
1478
+
1479
+ continue;
1480
+ }
1481
+
1482
+ // html
1483
+ if (cap = block.html.exec(src)) {
1484
+ src = src.substring(cap[0].length);
1485
+ tokens.push({
1486
+ type: 'html',
1487
+ pre: cap[1] === 'pre',
1488
+ text: cap[0]
1489
+ });
1490
+ continue;
1491
+ }
1492
+
1493
+ // def
1494
+ if (top && (cap = block.def.exec(src))) {
1495
+ src = src.substring(cap[0].length);
1496
+ tokens.links[cap[1].toLowerCase()] = {
1497
+ href: cap[2],
1498
+ title: cap[3]
1499
+ };
1500
+ continue;
1501
+ }
1502
+
1503
+ // top-level paragraph
1504
+ if (top && (cap = block.paragraph.exec(src))) {
1505
+ src = src.substring(cap[0].length);
1506
+ tokens.push({
1507
+ type: 'paragraph',
1508
+ text: cap[0]
1509
+ });
1510
+ continue;
1511
+ }
1512
+
1513
+ // text
1514
+ if (cap = block.text.exec(src)) {
1515
+ // Top-level should never reach here.
1516
+ src = src.substring(cap[0].length);
1517
+ tokens.push({
1518
+ type: 'text',
1519
+ text: cap[0]
1520
+ });
1521
+ continue;
1522
+ }
1523
+ }
1524
+
1525
+ return tokens;
1526
+ };
1527
+
1528
+ /**
1529
+ * Inline Processing
1530
+ */
1531
+
1532
+ var inline = {
1533
+ escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
1534
+ autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
1535
+ url: noop,
1536
+ tag: /^<!--[^\0]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
1537
+ link: /^!?\[(inside)\]\(href\)/,
1538
+ reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
1539
+ nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
1540
+ strong: /^__([^\0]+?)__(?!_)|^\*\*([^\0]+?)\*\*(?!\*)/,
1541
+ em: /^\b_((?:__|[^\0])+?)_\b|^\*((?:\*\*|[^\0])+?)\*(?!\*)/,
1542
+ code: /^(`+)([^\0]*?[^`])\1(?!`)/,
1543
+ br: /^ {2,}\n(?!\s*$)/,
1544
+ text: /^[^\0]+?(?=[\\<!\[_*`]| {2,}\n|$)/
1545
+ };
1546
+
1547
+ inline._linkInside = /(?:\[[^\]]*\]|[^\]]|\](?=[^\[]*\]))*/;
1548
+ inline._linkHref = /\s*<?([^\s]*?)>?(?:\s+['"]([^\0]*?)['"])?\s*/;
1549
+
1550
+ inline.link = replace(inline.link)
1551
+ ('inside', inline._linkInside)
1552
+ ('href', inline._linkHref)
1553
+ ();
1554
+
1555
+ inline.reflink = replace(inline.reflink)
1556
+ ('inside', inline._linkInside)
1557
+ ();
1558
+
1559
+ inline.normal = {
1560
+ url: inline.url,
1561
+ strong: inline.strong,
1562
+ em: inline.em,
1563
+ text: inline.text
1564
+ };
1565
+
1566
+ inline.pedantic = {
1567
+ strong: /^__(?=\S)([^\0]*?\S)__(?!_)|^\*\*(?=\S)([^\0]*?\S)\*\*(?!\*)/,
1568
+ em: /^_(?=\S)([^\0]*?\S)_(?!_)|^\*(?=\S)([^\0]*?\S)\*(?!\*)/
1569
+ };
1570
+
1571
+ inline.gfm = {
1572
+ url: /^(https?:\/\/[^\s]+[^.,:;"')\]\s])/,
1573
+ text: /^[^\0]+?(?=[\\<!\[_*`]|https?:\/\/| {2,}\n|$)/
1574
+ };
1575
+
1576
+ /**
1577
+ * Inline Lexer
1578
+ */
1579
+
1580
+ inline.lexer = function(src) {
1581
+ var out = ''
1582
+ , links = tokens.links
1583
+ , link
1584
+ , text
1585
+ , href
1586
+ , cap;
1587
+
1588
+ while (src) {
1589
+ // escape
1590
+ if (cap = inline.escape.exec(src)) {
1591
+ src = src.substring(cap[0].length);
1592
+ out += cap[1];
1593
+ continue;
1594
+ }
1595
+
1596
+ // autolink
1597
+ if (cap = inline.autolink.exec(src)) {
1598
+ src = src.substring(cap[0].length);
1599
+ if (cap[2] === '@') {
1600
+ text = cap[1][6] === ':'
1601
+ ? mangle(cap[1].substring(7))
1602
+ : mangle(cap[1]);
1603
+ href = mangle('mailto:') + text;
1604
+ } else {
1605
+ text = escape(cap[1]);
1606
+ href = text;
1607
+ }
1608
+ out += '<a href="'
1609
+ + href
1610
+ + '">'
1611
+ + text
1612
+ + '</a>';
1613
+ continue;
1614
+ }
1615
+
1616
+ // url (gfm)
1617
+ if (cap = inline.url.exec(src)) {
1618
+ src = src.substring(cap[0].length);
1619
+ text = escape(cap[1]);
1620
+ href = text;
1621
+ out += '<a href="'
1622
+ + href
1623
+ + '">'
1624
+ + text
1625
+ + '</a>';
1626
+ continue;
1627
+ }
1628
+
1629
+ // tag
1630
+ if (cap = inline.tag.exec(src)) {
1631
+ src = src.substring(cap[0].length);
1632
+ out += options.sanitize
1633
+ ? escape(cap[0])
1634
+ : cap[0];
1635
+ continue;
1636
+ }
1637
+
1638
+ // link
1639
+ if (cap = inline.link.exec(src)) {
1640
+ src = src.substring(cap[0].length);
1641
+ out += outputLink(cap, {
1642
+ href: cap[2],
1643
+ title: cap[3]
1644
+ });
1645
+ continue;
1646
+ }
1647
+
1648
+ // reflink, nolink
1649
+ if ((cap = inline.reflink.exec(src))
1650
+ || (cap = inline.nolink.exec(src))) {
1651
+ src = src.substring(cap[0].length);
1652
+ link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
1653
+ link = links[link.toLowerCase()];
1654
+ if (!link || !link.href) {
1655
+ out += cap[0][0];
1656
+ src = cap[0].substring(1) + src;
1657
+ continue;
1658
+ }
1659
+ out += outputLink(cap, link);
1660
+ continue;
1661
+ }
1662
+
1663
+ // strong
1664
+ if (cap = inline.strong.exec(src)) {
1665
+ src = src.substring(cap[0].length);
1666
+ out += '<strong>'
1667
+ + inline.lexer(cap[2] || cap[1])
1668
+ + '</strong>';
1669
+ continue;
1670
+ }
1671
+
1672
+ // em
1673
+ if (cap = inline.em.exec(src)) {
1674
+ src = src.substring(cap[0].length);
1675
+ out += '<em>'
1676
+ + inline.lexer(cap[2] || cap[1])
1677
+ + '</em>';
1678
+ continue;
1679
+ }
1680
+
1681
+ // code
1682
+ if (cap = inline.code.exec(src)) {
1683
+ src = src.substring(cap[0].length);
1684
+ out += '<code>'
1685
+ + escape(cap[2], true)
1686
+ + '</code>';
1687
+ continue;
1688
+ }
1689
+
1690
+ // br
1691
+ if (cap = inline.br.exec(src)) {
1692
+ src = src.substring(cap[0].length);
1693
+ out += '<br>';
1694
+ continue;
1695
+ }
1696
+
1697
+ // text
1698
+ if (cap = inline.text.exec(src)) {
1699
+ src = src.substring(cap[0].length);
1700
+ out += escape(cap[0]);
1701
+ continue;
1702
+ }
1703
+ }
1704
+
1705
+ return out;
1706
+ };
1707
+
1708
+ function outputLink(cap, link) {
1709
+ if (cap[0][0] !== '!') {
1710
+ return '<a href="'
1711
+ + escape(link.href)
1712
+ + '"'
1713
+ + (link.title
1714
+ ? ' title="'
1715
+ + escape(link.title)
1716
+ + '"'
1717
+ : '')
1718
+ + '>'
1719
+ + inline.lexer(cap[1])
1720
+ + '</a>';
1721
+ } else {
1722
+ return '<img src="'
1723
+ + escape(link.href)
1724
+ + '" alt="'
1725
+ + escape(cap[1])
1726
+ + '"'
1727
+ + (link.title
1728
+ ? ' title="'
1729
+ + escape(link.title)
1730
+ + '"'
1731
+ : '')
1732
+ + '>';
1733
+ }
1734
+ }
1735
+
1736
+ /**
1737
+ * Parsing
1738
+ */
1739
+
1740
+ var tokens
1741
+ , token;
1742
+
1743
+ function next() {
1744
+ return token = tokens.pop();
1745
+ }
1746
+
1747
+ function tok() {
1748
+ switch (token.type) {
1749
+ case 'space': {
1750
+ return '';
1751
+ }
1752
+ case 'hr': {
1753
+ return '<hr>\n';
1754
+ }
1755
+ case 'heading': {
1756
+ return '<h'
1757
+ + token.depth
1758
+ + '>'
1759
+ + inline.lexer(token.text)
1760
+ + '</h'
1761
+ + token.depth
1762
+ + '>\n';
1763
+ }
1764
+ case 'code': {
1765
+ if (options.highlight) {
1766
+ token.code = options.highlight(token.text, token.lang);
1767
+ if (token.code != null && token.code !== token.text) {
1768
+ token.escaped = true;
1769
+ token.text = token.code;
1770
+ }
1771
+ }
1772
+
1773
+ if (!token.escaped) {
1774
+ token.text = escape(token.text, true);
1775
+ }
1776
+
1777
+ return '<pre><code'
1778
+ + (token.lang
1779
+ ? ' class="lang-'
1780
+ + token.lang
1781
+ + '"'
1782
+ : '')
1783
+ + '>'
1784
+ + token.text
1785
+ + '</code></pre>\n';
1786
+ }
1787
+ case 'blockquote_start': {
1788
+ var body = '';
1789
+
1790
+ while (next().type !== 'blockquote_end') {
1791
+ body += tok();
1792
+ }
1793
+
1794
+ return '<blockquote>\n'
1795
+ + body
1796
+ + '</blockquote>\n';
1797
+ }
1798
+ case 'list_start': {
1799
+ var type = token.ordered ? 'ol' : 'ul'
1800
+ , body = '';
1801
+
1802
+ while (next().type !== 'list_end') {
1803
+ body += tok();
1804
+ }
1805
+
1806
+ return '<'
1807
+ + type
1808
+ + '>\n'
1809
+ + body
1810
+ + '</'
1811
+ + type
1812
+ + '>\n';
1813
+ }
1814
+ case 'list_item_start': {
1815
+ var body = '';
1816
+
1817
+ while (next().type !== 'list_item_end') {
1818
+ body += token.type === 'text'
1819
+ ? parseText()
1820
+ : tok();
1821
+ }
1822
+
1823
+ return '<li>'
1824
+ + body
1825
+ + '</li>\n';
1826
+ }
1827
+ case 'loose_item_start': {
1828
+ var body = '';
1829
+
1830
+ while (next().type !== 'list_item_end') {
1831
+ body += tok();
1832
+ }
1833
+
1834
+ return '<li>'
1835
+ + body
1836
+ + '</li>\n';
1837
+ }
1838
+ case 'html': {
1839
+ if (options.sanitize) {
1840
+ return inline.lexer(token.text);
1841
+ }
1842
+ return !token.pre && !options.pedantic
1843
+ ? inline.lexer(token.text)
1844
+ : token.text;
1845
+ }
1846
+ case 'paragraph': {
1847
+ return '<p>'
1848
+ + inline.lexer(token.text)
1849
+ + '</p>\n';
1850
+ }
1851
+ case 'text': {
1852
+ return '<p>'
1853
+ + parseText()
1854
+ + '</p>\n';
1855
+ }
1856
+ }
1857
+ }
1858
+
1859
+ function parseText() {
1860
+ var body = token.text
1861
+ , top;
1862
+
1863
+ while ((top = tokens[tokens.length-1])
1864
+ && top.type === 'text') {
1865
+ body += '\n' + next().text;
1866
+ }
1867
+
1868
+ return inline.lexer(body);
1869
+ }
1870
+
1871
+ function parse(src) {
1872
+ tokens = src.reverse();
1873
+
1874
+ var out = '';
1875
+ while (next()) {
1876
+ out += tok();
1877
+ }
1878
+
1879
+ tokens = null;
1880
+ token = null;
1881
+
1882
+ return out;
1883
+ }
1884
+
1885
+ /**
1886
+ * Helpers
1887
+ */
1888
+
1889
+ function escape(html, encode) {
1890
+ return html
1891
+ .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&amp;')
1892
+ .replace(/</g, '&lt;')
1893
+ .replace(/>/g, '&gt;')
1894
+ .replace(/"/g, '&quot;')
1895
+ .replace(/'/g, '&#39;');
1896
+ }
1897
+
1898
+ function mangle(text) {
1899
+ var out = ''
1900
+ , l = text.length
1901
+ , i = 0
1902
+ , ch;
1903
+
1904
+ for (; i < l; i++) {
1905
+ ch = text.charCodeAt(i);
1906
+ if (Math.random() > 0.5) {
1907
+ ch = 'x' + ch.toString(16);
1908
+ }
1909
+ out += '&#' + ch + ';';
1910
+ }
1911
+
1912
+ return out;
1913
+ }
1914
+
1915
+ function tag() {
1916
+ var tag = '(?!(?:'
1917
+ + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code'
1918
+ + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo'
1919
+ + '|span|br|wbr|ins|del|img)\\b)\\w+';
1920
+
1921
+ return tag;
1922
+ }
1923
+
1924
+ function replace(regex, opt) {
1925
+ regex = regex.source;
1926
+ opt = opt || '';
1927
+ return function self(name, val) {
1928
+ if (!name) return new RegExp(regex, opt);
1929
+ regex = regex.replace(name, val.source || val);
1930
+ return self;
1931
+ };
1932
+ }
1933
+
1934
+ function noop() {}
1935
+ noop.exec = noop;
1936
+
1937
+ /**
1938
+ * Marked
1939
+ */
1940
+
1941
+ function marked(src, opt) {
1942
+ setOptions(opt);
1943
+ return parse(block.lexer(src));
1944
+ }
1945
+
1946
+ /**
1947
+ * Options
1948
+ */
1949
+
1950
+ var options
1951
+ , defaults;
1952
+
1953
+ function setOptions(opt) {
1954
+ if (!opt) opt = defaults;
1955
+ if (options === opt) return;
1956
+ options = opt;
1957
+
1958
+ if (options.gfm) {
1959
+ block.fences = block.gfm.fences;
1960
+ block.paragraph = block.gfm.paragraph;
1961
+ inline.text = inline.gfm.text;
1962
+ inline.url = inline.gfm.url;
1963
+ } else {
1964
+ block.fences = block.normal.fences;
1965
+ block.paragraph = block.normal.paragraph;
1966
+ inline.text = inline.normal.text;
1967
+ inline.url = inline.normal.url;
1968
+ }
1969
+
1970
+ if (options.pedantic) {
1971
+ inline.em = inline.pedantic.em;
1972
+ inline.strong = inline.pedantic.strong;
1973
+ } else {
1974
+ inline.em = inline.normal.em;
1975
+ inline.strong = inline.normal.strong;
1976
+ }
1977
+ }
1978
+
1979
+ marked.options =
1980
+ marked.setOptions = function(opt) {
1981
+ defaults = opt;
1982
+ setOptions(opt);
1983
+ return marked;
1984
+ };
1985
+
1986
+ marked.setOptions({
1987
+ gfm: true,
1988
+ pedantic: false,
1989
+ sanitize: false,
1990
+ highlight: null
1991
+ });
1992
+
1993
+ /**
1994
+ * Expose
1995
+ */
1996
+
1997
+ marked.parser = function(src, opt) {
1998
+ setOptions(opt);
1999
+ return parse(src);
2000
+ };
2001
+
2002
+ marked.lexer = function(src, opt) {
2003
+ setOptions(opt);
2004
+ return block.lexer(src);
2005
+ };
2006
+
2007
+ marked.parse = marked;
2008
+
2009
+ if (typeof module !== 'undefined') {
2010
+ module.exports = marked;
2011
+ } else {
2012
+ this.marked = marked;
2013
+ }
2014
+
2015
+ }).call(function() {
2016
+ return this || (typeof window !== 'undefined' ? window : global);
2017
+ }());