diff options
author | Mike Chen <chagel@gmail.com> | 2009-07-05 10:23:06 +0800 |
---|---|---|
committer | Mike Chen <chagel@gmail.com> | 2009-07-05 10:23:06 +0800 |
commit | 2acb62174ab2cac9c7a88faafcb5f0a22a54db6f (patch) | |
tree | 93e9d22d6fb8a6c882e3915a8511a3ac542a1e67 /templates/content/js/wmd | |
download | askbot-2acb62174ab2cac9c7a88faafcb5f0a22a54db6f.tar.gz askbot-2acb62174ab2cac9c7a88faafcb5f0a22a54db6f.tar.bz2 askbot-2acb62174ab2cac9c7a88faafcb5f0a22a54db6f.zip |
initiliaze git rep
Diffstat (limited to 'templates/content/js/wmd')
-rw-r--r-- | templates/content/js/wmd/images/wmd-buttons.png | bin | 0 -> 7465 bytes | |||
-rw-r--r-- | templates/content/js/wmd/showdown-min.js | 1 | ||||
-rw-r--r-- | templates/content/js/wmd/showdown.js | 1309 | ||||
-rw-r--r-- | templates/content/js/wmd/wmd-min.js | 1 | ||||
-rw-r--r-- | templates/content/js/wmd/wmd-test.html | 158 | ||||
-rw-r--r-- | templates/content/js/wmd/wmd.css | 129 | ||||
-rw-r--r-- | templates/content/js/wmd/wmd.js | 2390 |
7 files changed, 3988 insertions, 0 deletions
diff --git a/templates/content/js/wmd/images/wmd-buttons.png b/templates/content/js/wmd/images/wmd-buttons.png Binary files differnew file mode 100644 index 00000000..50b37090 --- /dev/null +++ b/templates/content/js/wmd/images/wmd-buttons.png diff --git a/templates/content/js/wmd/showdown-min.js b/templates/content/js/wmd/showdown-min.js new file mode 100644 index 00000000..073613b1 --- /dev/null +++ b/templates/content/js/wmd/showdown-min.js @@ -0,0 +1 @@ +var Attacklab=Attacklab||{};Attacklab.showdown=Attacklab.showdown||{};Attacklab.showdown.converter=function(){var a;var j;var A;var i=0;this.makeHtml=function(H){a=new Array();j=new Array();A=new Array();H=H.replace(/~/g,"~T");H=H.replace(/\$/g,"~D");H=H.replace(/\r\n/g,"\n");H=H.replace(/\r/g,"\n");H="\n\n"+H+"\n\n";H=z(H);H=H.replace(/^[ \t]+$/mg,"");H=m(H);H=d(H);H=G(H);H=q(H);H=H.replace(/~D/g,"$$");H=H.replace(/~T/g,"~");return H};var d=function(H){var H=H.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,function(K,M,L,J,I){M=M.toLowerCase();a[M]=h(L);if(J){return J+I}else{if(I){j[M]=I.replace(/"/g,""")}}return""});return H};var m=function(J){J=J.replace(/\n/g,"\n\n");var I="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del";var H="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math";J=J.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,x);J=J.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,x);J=J.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,x);J=J.replace(/(\n\n[ ]{0,3}<!(--[^\r]*?--\s*)+>[ \t]*(?=\n{2,}))/g,x);J=J.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,x);J=J.replace(/\n\n/g,"\n");return J};var x=function(H,I){var J=I;J=J.replace(/\n\n/g,"\n");J=J.replace(/^\n/,"");J=J.replace(/\n+$/g,"");J="\n\n~K"+(A.push(J)-1)+"K\n\n";return J};var G=function(I){I=f(I);var H=o("<hr />");I=I.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,H);I=I.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm,H);I=I.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm,H);I=E(I);I=b(I);I=u(I);I=m(I);I=g(I);return I};var r=function(H){H=C(H);H=l(H);H=e(H);H=F(H);H=y(H);H=n(H);H=h(H);H=c(H);H=H.replace(/ +\n/g," <br />\n");return H};var l=function(I){var H=/(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi;I=I.replace(H,function(K){var J=K.replace(/(.)<\/?code>(?=.)/g,"$1`");J=w(J,"\\`*_");return J});return I};var y=function(H){H=H.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,D);H=H.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,D);H=H.replace(/(\[([^\[\]]+)\])()()()()()/g,D);return H};var D=function(N,T,S,R,Q,P,M,L){if(L==undefined){L=""}var K=T;var I=S;var J=R.toLowerCase();var H=Q;var O=L;if(H==""){if(J==""){J=I.toLowerCase().replace(/ ?\n/g," ")}H="#"+J;if(a[J]!=undefined){H=a[J];if(j[J]!=undefined){O=j[J]}}else{if(K.search(/\(\s*\)$/m)>-1){H=""}else{return K}}}H=w(H,"*_");var U='<a href="'+H+'"';if(O!=""){O=O.replace(/"/g,""");O=w(O,"*_");U+=' title="'+O+'"'}U+=">"+I+"</a>";return U};var F=function(H){H=H.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,t);H=H.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,t);return H};var t=function(N,T,S,R,Q,P,M,L){var K=T;var J=S;var I=R.toLowerCase();var H=Q;var O=L;if(!O){O=""}if(H==""){if(I==""){I=J.toLowerCase().replace(/ ?\n/g," ")}H="#"+I;if(a[I]!=undefined){H=a[I];if(j[I]!=undefined){O=j[I]}}else{return K}}J=J.replace(/"/g,""");H=w(H,"*_");var U='<img src="'+H+'" alt="'+J+'"';O=O.replace(/"/g,""");O=w(O,"*_");U+=' title="'+O+'"';U+=" />";return U};var f=function(H){H=H.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,function(I,J){return o("<h1>"+r(J)+"</h1>")});H=H.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,function(J,I){return o("<h2>"+r(I)+"</h2>")});H=H.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,function(I,L,K){var J=L.length;return o("<h"+J+">"+r(K)+"</h"+J+">")});return H};var p;var E=function(I){I+="~0";var H=/^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;if(i){I=I.replace(H,function(K,N,M){var O=N;var L=(M.search(/[*+-]/g)>-1)?"ul":"ol";O=O.replace(/\n{2,}/g,"\n\n\n");var J=p(O);J=J.replace(/\s+$/,"");J="<"+L+">"+J+"</"+L+">\n";return J})}else{H=/(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;I=I.replace(H,function(L,P,N,K){var O=P;var Q=N;var M=(K.search(/[*+-]/g)>-1)?"ul":"ol";var Q=Q.replace(/\n{2,}/g,"\n\n\n");var J=p(Q);J=O+"<"+M+">\n"+J+"</"+M+">\n";return J})}I=I.replace(/~0/,"");return I};p=function(H){i++;H=H.replace(/\n{2,}$/,"\n");H+="~0";H=H.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,function(K,M,L,J,I){var O=I;var N=M;var P=L;if(N||(O.search(/\n{2,}/)>-1)){O=G(s(O))}else{O=E(s(O));O=O.replace(/\n$/,"");O=r(O)}return"<li>"+O+"</li>\n"});H=H.replace(/~0/g,"");i--;return H};var b=function(H){H+="~0";H=H.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,function(I,K,J){var L=K;var M=J;L=v(s(L));L=z(L);L=L.replace(/^\n+/g,"");L=L.replace(/\n+$/g,"");L="<pre><code>"+L+"\n</code></pre>";return o(L)+M});H=H.replace(/~0/,"");return H};var o=function(H){H=H.replace(/(^\n+|\n+$)/g,"");return"\n\n~K"+(A.push(H)-1)+"K\n\n"};var C=function(H){H=H.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,function(K,M,L,J,I){var N=J;N=N.replace(/^([ \t]*)/g,"");N=N.replace(/[ \t]*$/g,"");N=v(N);return M+"<code>"+N+"</code>"});return H};var v=function(H){H=H.replace(/&/g,"&");H=H.replace(/</g,"<");H=H.replace(/>/g,">");H=w(H,"*_{}[]\\",false);return H};var c=function(H){H=H.replace(/(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\1/g,"<strong>$2</strong>");H=H.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,"<em>$2</em>");return H};var u=function(H){H=H.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,function(I,J){var K=J;K=K.replace(/^[ \t]*>[ \t]?/gm,"~0");K=K.replace(/~0/g,"");K=K.replace(/^[ \t]+$/gm,"");K=G(K);K=K.replace(/(^|\n)/g,"$1 ");K=K.replace(/(\s*<pre>[^\r]+?<\/pre>)/gm,function(L,M){var N=M;N=N.replace(/^ /mg,"~0");N=N.replace(/~0/g,"");return N});return o("<blockquote>\n"+K+"\n</blockquote>")});return H};var g=function(N){N=N.replace(/^\n+/g,"");N=N.replace(/\n+$/g,"");var M=N.split(/\n{2,}/g);var J=new Array();var H=M.length;for(var I=0;I<H;I++){var L=M[I];if(L.search(/~K(\d+)K/g)>=0){J.push(L)}else{if(L.search(/\S/)>=0){L=r(L);L=L.replace(/^([ \t]*)/g,"<p>");L+="</p>";J.push(L)}}}H=J.length;for(var I=0;I<H;I++){while(J[I].search(/~K(\d+)K/)>=0){var K=A[RegExp.$1];K=K.replace(/\$/g,"$$$$");J[I]=J[I].replace(/~K\d+K/,K)}}return J.join("\n\n")};var h=function(H){H=H.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&");H=H.replace(/<(?![a-z\/?\$!])/gi,"<");return H};var e=function(H){H=H.replace(/\\(\\)/g,k);H=H.replace(/\\([`*_{}\[\]()>#+-.!])/g,k);return H};var n=function(H){H=H.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,'<a href="$1">$1</a>');H=H.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,function(I,J){return B(q(J))});return H};var B=function(J){function I(L){var K="0123456789ABCDEF";var M=L.charCodeAt(0);return(K.charAt(M>>4)+K.charAt(M&15))}var H=[function(K){return"&#"+K.charCodeAt(0)+";"},function(K){return"&#x"+I(K)+";"},function(K){return K}];J="mailto:"+J;J=J.replace(/./g,function(K){if(K=="@"){K=H[Math.floor(Math.random()*2)](K)}else{if(K!=":"){var L=Math.random();K=(L>0.9?H[2](K):L>0.45?H[1](K):H[0](K))}}return K});J='<a href="'+J+'">'+J+"</a>";J=J.replace(/">.+:/g,'">');return J};var q=function(H){H=H.replace(/~E(\d+)E/g,function(I,K){var J=parseInt(K);return String.fromCharCode(J)});return H};var s=function(H){H=H.replace(/^(\t|[ ]{1,4})/gm,"~0");H=H.replace(/~0/g,"");return H};var z=function(H){H=H.replace(/\t(?=\t)/g," ");H=H.replace(/\t/g,"~A~B");H=H.replace(/~B(.+?)~A/g,function(I,L,K){var N=L;var J=4-N.length%4;for(var M=0;M<J;M++){N+=" "}return N});H=H.replace(/~A/g," ");H=H.replace(/~B/g,"");return H};var w=function(L,I,J){var H="(["+I.replace(/([\[\]\\])/g,"\\$1")+"])";if(J){H="\\\\"+H}var K=new RegExp(H,"g");L=L.replace(K,k);return L};var k=function(H,J){var I=J.charCodeAt(0);return"~E"+I+"E"}};var Showdown=Attacklab.showdown;if(Attacklab.fileLoaded){Attacklab.fileLoaded("showdown.js")};
\ No newline at end of file diff --git a/templates/content/js/wmd/showdown.js b/templates/content/js/wmd/showdown.js new file mode 100644 index 00000000..3f4b9947 --- /dev/null +++ b/templates/content/js/wmd/showdown.js @@ -0,0 +1,1309 @@ +// +// showdown.js -- A javascript port of Markdown. +// +// Copyright (c) 2007 John Fraser. +// +// Original Markdown Copyright (c) 2004-2005 John Gruber +// <http://daringfireball.net/projects/markdown/> +// +// Redistributable under a BSD-style open source license. +// See license.txt for more information. +// +// The full source distribution is at: +// +// A A L +// T C A +// T K B +// +// <http://www.attacklab.net/> +// + +// +// Wherever possible, Showdown is a straight, line-by-line port +// of the Perl version of Markdown. +// +// This is not a normal parser design; it's basically just a +// series of string substitutions. It's hard to read and +// maintain this way, but keeping Showdown close to the original +// design makes it easier to port new features. +// +// More importantly, Showdown behaves like markdown.pl in most +// edge cases. So web applications can do client-side preview +// in Javascript, and then build identical HTML on the server. +// +// This port needs the new RegExp functionality of ECMA 262, +// 3rd Edition (i.e. Javascript 1.5). Most modern web browsers +// should do fine. Even with the new regular expression features, +// We do a lot of work to emulate Perl's regex functionality. +// The tricky changes in this file mostly have the "attacklab:" +// label. Major or self-explanatory changes don't. +// +// Smart diff tools like Araxis Merge will be able to match up +// this file with markdown.pl in a useful way. A little tweaking +// helps: in a copy of markdown.pl, replace "#" with "//" and +// replace "$text" with "text". Be sure to ignore whitespace +// and line endings. +// + + +// +// Showdown usage: +// +// var text = "Markdown *rocks*."; +// +// var converter = new Attacklab.showdown.converter(); +// var html = converter.makeHtml(text); +// +// alert(html); +// +// Note: move the sample code to the bottom of this +// file before uncommenting it. +// + + +// +// Attacklab namespace +// +var Attacklab = Attacklab || {} + +// +// Showdown namespace +// +Attacklab.showdown = Attacklab.showdown || {} + +// +// converter +// +// Wraps all "globals" so that the only thing +// exposed is makeHtml(). +// +Attacklab.showdown.converter = function() { + +// +// Globals: +// + +// Global hashes, used by various utility routines +var g_urls; +var g_titles; +var g_html_blocks; + +// Used to track when we're inside an ordered or unordered list +// (see _ProcessListItems() for details): +var g_list_level = 0; + + +this.makeHtml = function(text) { +// +// Main function. The order in which other subs are called here is +// essential. Link and image substitutions need to happen before +// _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a> +// and <img> tags get encoded. +// + + // Clear the global hashes. If we don't clear these, you get conflicts + // from other articles when generating a page which contains more than + // one article (e.g. an index page that shows the N most recent + // articles): + g_urls = new Array(); + g_titles = new Array(); + g_html_blocks = new Array(); + + // attacklab: Replace ~ with ~T + // This lets us use tilde as an escape char to avoid md5 hashes + // The choice of character is arbitray; anything that isn't + // magic in Markdown will work. + text = text.replace(/~/g,"~T"); + + // attacklab: Replace $ with ~D + // RegExp interprets $ as a special character + // when it's in a replacement string + text = text.replace(/\$/g,"~D"); + + // Standardize line endings + text = text.replace(/\r\n/g,"\n"); // DOS to Unix + text = text.replace(/\r/g,"\n"); // Mac to Unix + + // Make sure text begins and ends with a couple of newlines: + text = "\n\n" + text + "\n\n"; + + // Convert all tabs to spaces. + text = _Detab(text); + + // Strip any lines consisting only of spaces and tabs. + // This makes subsequent regexen easier to write, because we can + // match consecutive blank lines with /\n+/ instead of something + // contorted like /[ \t]*\n+/ . + text = text.replace(/^[ \t]+$/mg,""); + + // Turn block-level HTML blocks into hash entries + text = _HashHTMLBlocks(text); + + // Strip link definitions, store in hashes. + text = _StripLinkDefinitions(text); + + text = _RunBlockGamut(text); + + text = _UnescapeSpecialChars(text); + + // attacklab: Restore dollar signs + text = text.replace(/~D/g,"$$"); + + // attacklab: Restore tildes + text = text.replace(/~T/g,"~"); + + return text; +} + +var _StripLinkDefinitions = function(text) { +// +// Strips link definitions from text, stores the URLs and titles in +// hash references. +// + + // Link defs are in the form: ^[id]: url "optional title" + + /* + var text = text.replace(/ + ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1 + [ \t]* + \n? // maybe *one* newline + [ \t]* + <?(\S+?)>? // url = $2 + [ \t]* + \n? // maybe one newline + [ \t]* + (?: + (\n*) // any lines skipped = $3 attacklab: lookbehind removed + ["(] + (.+?) // title = $4 + [")] + [ \t]* + )? // title is optional + (?:\n+|$) + /gm, + function(){...}); + */ + var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm, + function (wholeMatch,m1,m2,m3,m4) { + m1 = m1.toLowerCase(); + g_urls[m1] = _EncodeAmpsAndAngles(m2); // Link IDs are case-insensitive + if (m3) { + // Oops, found blank lines, so it's not a title. + // Put back the parenthetical statement we stole. + return m3+m4; + } else if (m4) { + g_titles[m1] = m4.replace(/"/g,"""); + } + + // Completely remove the definition from the text + return ""; + } + ); + + return text; +} + +var _HashHTMLBlocks = function(text) { + // attacklab: Double up blank lines to reduce lookaround + text = text.replace(/\n/g,"\n\n"); + + // Hashify HTML blocks: + // We only want to do this for block-level HTML tags, such as headers, + // lists, and tables. That's because we still want to wrap <p>s around + // "paragraphs" that are wrapped in non-block-level tags, such as anchors, + // phrase emphasis, and spans. The list of tags we're looking for is + // hard-coded: + var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del" + var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math" + + // First, look for nested blocks, e.g.: + // <div> + // <div> + // tags for inner block must be indented. + // </div> + // </div> + // + // The outermost tags must start at the left margin for this to match, and + // the inner nested divs must be indented. + // We need to do this before the next, more liberal match, because the next + // match will start at the first `<div>` and stop at the first `</div>`. + + // attacklab: This regex can be expensive when it fails. + /* + var text = text.replace(/ + ( // save in $1 + ^ // start of line (with /m) + <($block_tags_a) // start tag = $2 + \b // word break + // attacklab: hack around khtml/pcre bug... + [^\r]*?\n // any number of lines, minimally matching + </\2> // the matching end tag + [ \t]* // trailing spaces/tabs + (?=\n+) // followed by a newline + ) // attacklab: there are sentinel newlines at end of document + /gm,function(){...}}; + */ + text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,hashElement); + + // + // Now match more liberally, simply from `\n<tag>` to `</tag>\n` + // + + /* + var text = text.replace(/ + ( // save in $1 + ^ // start of line (with /m) + <($block_tags_b) // start tag = $2 + \b // word break + // attacklab: hack around khtml/pcre bug... + [^\r]*? // any number of lines, minimally matching + .*</\2> // the matching end tag + [ \t]* // trailing spaces/tabs + (?=\n+) // followed by a newline + ) // attacklab: there are sentinel newlines at end of document + /gm,function(){...}}; + */ + text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,hashElement); + + // Special case just for <hr />. It was easier to make a special case than + // to make the other regex more complicated. + + /* + text = text.replace(/ + ( // save in $1 + \n\n // Starting after a blank line + [ ]{0,3} + (<(hr) // start tag = $2 + \b // word break + ([^<>])*? // + \/?>) // the matching end tag + [ \t]* + (?=\n{2,}) // followed by a blank line + ) + /g,hashElement); + */ + text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement); + + // Special case for standalone HTML comments: + + /* + text = text.replace(/ + ( // save in $1 + \n\n // Starting after a blank line + [ ]{0,3} // attacklab: g_tab_width - 1 + <! + (--[^\r]*?--\s*)+ + > + [ \t]* + (?=\n{2,}) // followed by a blank line + ) + /g,hashElement); + */ + text = text.replace(/(\n\n[ ]{0,3}<!(--[^\r]*?--\s*)+>[ \t]*(?=\n{2,}))/g,hashElement); + + // PHP and ASP-style processor instructions (<?...?> and <%...%>) + + /* + text = text.replace(/ + (?: + \n\n // Starting after a blank line + ) + ( // save in $1 + [ ]{0,3} // attacklab: g_tab_width - 1 + (?: + <([?%]) // $2 + [^\r]*? + \2> + ) + [ \t]* + (?=\n{2,}) // followed by a blank line + ) + /g,hashElement); + */ + text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement); + + // attacklab: Undo double lines (see comment at top of this function) + text = text.replace(/\n\n/g,"\n"); + return text; +} + +var hashElement = function(wholeMatch,m1) { + var blockText = m1; + + // Undo double lines + blockText = blockText.replace(/\n\n/g,"\n"); + blockText = blockText.replace(/^\n/,""); + + // strip trailing blank lines + blockText = blockText.replace(/\n+$/g,""); + + // Replace the element text with a marker ("~KxK" where x is its key) + blockText = "\n\n~K" + (g_html_blocks.push(blockText)-1) + "K\n\n"; + + return blockText; +}; + +var _RunBlockGamut = function(text) { +// +// These are all the transformations that form block-level +// tags like paragraphs, headers, and list items. +// + text = _DoHeaders(text); + + // Do Horizontal Rules: + var key = hashBlock("<hr />"); + text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key); + text = text.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm,key); + text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm,key); + + text = _DoLists(text); + text = _DoCodeBlocks(text); + text = _DoBlockQuotes(text); + + // We already ran _HashHTMLBlocks() before, in Markdown(), but that + // was to escape raw HTML in the original Markdown source. This time, + // we're escaping the markup we've just created, so that we don't wrap + // <p> tags around block-level tags. + text = _HashHTMLBlocks(text); + text = _FormParagraphs(text); + + return text; +} + + +var _RunSpanGamut = function(text) { +// +// These are all the transformations that occur *within* block-level +// tags like paragraphs, headers, and list items. +// + + text = _DoCodeSpans(text); + text = _EscapeSpecialCharsWithinTagAttributes(text); + text = _EncodeBackslashEscapes(text); + + // Process anchor and image tags. Images must come first, + // because ![foo][f] looks like an anchor. + text = _DoImages(text); + text = _DoAnchors(text); + + // Make links out of things like `<http://example.com/>` + // Must come after _DoAnchors(), because you can use < and > + // delimiters in inline links like [this](<url>). + text = _DoAutoLinks(text); + text = _EncodeAmpsAndAngles(text); + text = _DoItalicsAndBold(text); + + // Do hard breaks: + text = text.replace(/ +\n/g," <br />\n"); + + return text; +} + +var _EscapeSpecialCharsWithinTagAttributes = function(text) { +// +// Within tags -- meaning between < and > -- encode [\ ` * _] so they +// don't conflict with their use in Markdown for code, italics and strong. +// + + // Build a regex to find HTML tags and comments. See Friedl's + // "Mastering Regular Expressions", 2nd Ed., pp. 200-201. + var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi; + + text = text.replace(regex, function(wholeMatch) { + var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`"); + tag = escapeCharacters(tag,"\\`*_"); + return tag; + }); + + return text; +} + +var _DoAnchors = function(text) { +// +// Turn Markdown link shortcuts into XHTML <a> tags. +// + // + // First, handle reference-style links: [link text] [id] + // + + /* + text = text.replace(/ + ( // wrap whole match in $1 + \[ + ( + (?: + \[[^\]]*\] // allow brackets nested one level + | + [^\[] // or anything else + )* + ) + \] + + [ ]? // one optional space + (?:\n[ ]*)? // one optional newline followed by spaces + + \[ + (.*?) // id = $3 + \] + )()()()() // pad remaining backreferences + /g,_DoAnchors_callback); + */ + text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag); + + // + // Next, inline-style links: [link text](url "optional title") + // + + /* + text = text.replace(/ + ( // wrap whole match in $1 + \[ + ( + (?: + \[[^\]]*\] // allow brackets nested one level + | + [^\[\]] // or anything else + ) + ) + \] + \( // literal paren + [ \t]* + () // no id, so leave $3 empty + <?(.*?)>? // href = $4 + [ \t]* + ( // $5 + (['"]) // quote char = $6 + (.*?) // Title = $7 + \6 // matching quote + [ \t]* // ignore any spaces/tabs between closing quote and ) + )? // title is optional + \) + ) + /g,writeAnchorTag); + */ + text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag); + + // + // Last, handle reference-style shortcuts: [link text] + // These must come last in case you've also got [link test][1] + // or [link test](/foo) + // + + /* + text = text.replace(/ + ( // wrap whole match in $1 + \[ + ([^\[\]]+) // link text = $2; can't contain '[' or ']' + \] + )()()()()() // pad rest of backreferences + /g, writeAnchorTag); + */ + text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag); + + return text; +} + +var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) { + if (m7 == undefined) m7 = ""; + var whole_match = m1; + var link_text = m2; + var link_id = m3.toLowerCase(); + var url = m4; + var title = m7; + + if (url == "") { + if (link_id == "") { + // lower-case and turn embedded newlines into spaces + link_id = link_text.toLowerCase().replace(/ ?\n/g," "); + } + url = "#"+link_id; + + if (g_urls[link_id] != undefined) { + url = g_urls[link_id]; + if (g_titles[link_id] != undefined) { + title = g_titles[link_id]; + } + } + else { + if (whole_match.search(/\(\s*\)$/m)>-1) { + // Special case for explicit empty url + url = ""; + } else { + return whole_match; + } + } + } + + url = escapeCharacters(url,"*_"); + var result = "<a href=\"" + url + "\""; + + if (title != "") { + title = title.replace(/"/g,"""); + title = escapeCharacters(title,"*_"); + result += " title=\"" + title + "\""; + } + + result += ">" + link_text + "</a>"; + + return result; +} + + +var _DoImages = function(text) { +// +// Turn Markdown image shortcuts into <img> tags. +// + + // + // First, handle reference-style labeled images: ![alt text][id] + // + + /* + text = text.replace(/ + ( // wrap whole match in $1 + !\[ + (.*?) // alt text = $2 + \] + + [ ]? // one optional space + (?:\n[ ]*)? // one optional newline followed by spaces + + \[ + (.*?) // id = $3 + \] + )()()()() // pad rest of backreferences + /g,writeImageTag); + */ + text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag); + + // + // Next, handle inline images: ![alt text](url "optional title") + // Don't forget: encode * and _ + + /* + text = text.replace(/ + ( // wrap whole match in $1 + !\[ + (.*?) // alt text = $2 + \] + \s? // One optional whitespace character + \( // literal paren + [ \t]* + () // no id, so leave $3 empty + <?(\S+?)>? // src url = $4 + [ \t]* + ( // $5 + (['"]) // quote char = $6 + (.*?) // title = $7 + \6 // matching quote + [ \t]* + )? // title is optional + \) + ) + /g,writeImageTag); + */ + text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag); + + return text; +} + +var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) { + var whole_match = m1; + var alt_text = m2; + var link_id = m3.toLowerCase(); + var url = m4; + var title = m7; + + if (!title) title = ""; + + if (url == "") { + if (link_id == "") { + // lower-case and turn embedded newlines into spaces + link_id = alt_text.toLowerCase().replace(/ ?\n/g," "); + } + url = "#"+link_id; + + if (g_urls[link_id] != undefined) { + url = g_urls[link_id]; + if (g_titles[link_id] != undefined) { + title = g_titles[link_id]; + } + } + else { + return whole_match; + } + } + + alt_text = alt_text.replace(/"/g,"""); + url = escapeCharacters(url,"*_"); + var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\""; + + // attacklab: Markdown.pl adds empty title attributes to images. + // Replicate this bug. + + //if (title != "") { + title = title.replace(/"/g,"""); + title = escapeCharacters(title,"*_"); + result += " title=\"" + title + "\""; + //} + + result += " />"; + + return result; +} + + +var _DoHeaders = function(text) { + + // Setext-style headers: + // Header 1 + // ======== + // + // Header 2 + // -------- + // + text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm, + function(wholeMatch,m1){return hashBlock("<h1>" + _RunSpanGamut(m1) + "</h1>");}); + + text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, + function(matchFound,m1){return hashBlock("<h2>" + _RunSpanGamut(m1) + "</h2>");}); + + // atx-style headers: + // # Header 1 + // ## Header 2 + // ## Header 2 with closing hashes ## + // ... + // ###### Header 6 + // + + /* + text = text.replace(/ + ^(\#{1,6}) // $1 = string of #'s + [ \t]* + (.+?) // $2 = Header text + [ \t]* + \#* // optional closing #'s (not counted) + \n+ + /gm, function() {...}); + */ + + text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm, + function(wholeMatch,m1,m2) { + var h_level = m1.length; + return hashBlock("<h" + h_level + ">" + _RunSpanGamut(m2) + "</h" + h_level + ">"); + }); + + return text; +} + +// This declaration keeps Dojo compressor from outputting garbage: +var _ProcessListItems; + +var _DoLists = function(text) { +// +// Form HTML ordered (numbered) and unordered (bulleted) lists. +// + + // attacklab: add sentinel to hack around khtml/safari bug: + // http://bugs.webkit.org/show_bug.cgi?id=11231 + text += "~0"; + + // Re-usable pattern to match any entirel ul or ol list: + + /* + var whole_list = / + ( // $1 = whole list + ( // $2 + [ ]{0,3} // attacklab: g_tab_width - 1 + ([*+-]|\d+[.]) // $3 = first list item marker + [ \t]+ + ) + [^\r]+? + ( // $4 + ~0 // sentinel for workaround; should be $ + | + \n{2,} + (?=\S) + (?! // Negative lookahead for another list item marker + [ \t]* + (?:[*+-]|\d+[.])[ \t]+ + ) + ) + )/g + */ + var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm; + + if (g_list_level) { + text = text.replace(whole_list,function(wholeMatch,m1,m2) { + var list = m1; + var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol"; + + // Turn double returns into triple returns, so that we can make a + // paragraph for the last item in a list, if necessary: + list = list.replace(/\n{2,}/g,"\n\n\n");; + var result = _ProcessListItems(list); + + // Trim any trailing whitespace, to put the closing `</$list_type>` + // up on the preceding line, to get it past the current stupid + // HTML block parser. This is a hack to work around the terrible + // hack that is the HTML block parser. + result = result.replace(/\s+$/,""); + result = "<"+list_type+">" + result + "</"+list_type+">\n"; + return result; + }); + } else { + whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g; + text = text.replace(whole_list,function(wholeMatch,m1,m2,m3) { + var runup = m1; + var list = m2; + + var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol"; + // Turn double returns into triple returns, so that we can make a + // paragraph for the last item in a list, if necessary: + var list = list.replace(/\n{2,}/g,"\n\n\n");; + var result = _ProcessListItems(list); + result = runup + "<"+list_type+">\n" + result + "</"+list_type+">\n"; + return result; + }); + } + + // attacklab: strip sentinel + text = text.replace(/~0/,""); + + return text; +} + +_ProcessListItems = function(list_str) { +// +// Process the contents of a single ordered or unordered list, splitting it +// into individual list items. +// + // The $g_list_level global keeps track of when we're inside a list. + // Each time we enter a list, we increment it; when we leave a list, + // we decrement. If it's zero, we're not in a list anymore. + // + // We do this because when we're not inside a list, we want to treat + // something like this: + // + // I recommend upgrading to version + // 8. Oops, now this line is treated + // as a sub-list. + // + // As a single paragraph, despite the fact that the second line starts + // with a digit-period-space sequence. + // + // Whereas when we're inside a list (or sub-list), that line will be + // treated as the start of a sub-list. What a kludge, huh? This is + // an aspect of Markdown's syntax that's hard to parse perfectly + // without resorting to mind-reading. Perhaps the solution is to + // change the syntax rules such that sub-lists must start with a + // starting cardinal number; e.g. "1." or "a.". + + g_list_level++; + + // trim trailing blank lines: + list_str = list_str.replace(/\n{2,}$/,"\n"); + + // attacklab: add sentinel to emulate \z + list_str += "~0"; + + /* + list_str = list_str.replace(/ + (\n)? // leading line = $1 + (^[ \t]*) // leading whitespace = $2 + ([*+-]|\d+[.]) [ \t]+ // list marker = $3 + ([^\r]+? // list item text = $4 + (\n{1,2})) + (?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+)) + /gm, function(){...}); + */ + list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm, + function(wholeMatch,m1,m2,m3,m4){ + var item = m4; + var leading_line = m1; + var leading_space = m2; + + if (leading_line || (item.search(/\n{2,}/)>-1)) { + item = _RunBlockGamut(_Outdent(item)); + } + else { + // Recursion for sub-lists: + item = _DoLists(_Outdent(item)); + item = item.replace(/\n$/,""); // chomp(item) + item = _RunSpanGamut(item); + } + + return "<li>" + item + "</li>\n"; + } + ); + + // attacklab: strip sentinel + list_str = list_str.replace(/~0/g,""); + + g_list_level--; + return list_str; +} + + +var _DoCodeBlocks = function(text) { +// +// Process Markdown `<pre><code>` blocks. +// + + /* + text = text.replace(text, + /(?:\n\n|^) + ( // $1 = the code block -- one or more lines, starting with a space/tab + (?: + (?:[ ]{4}|\t) // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width + .*\n+ + )+ + ) + (\n*[ ]{0,3}[^ \t\n]|(?=~0)) // attacklab: g_tab_width + /g,function(){...}); + */ + + // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug + text += "~0"; + + text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g, + function(wholeMatch,m1,m2) { + var codeblock = m1; + var nextChar = m2; + + codeblock = _EncodeCode( _Outdent(codeblock)); + codeblock = _Detab(codeblock); + codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines + codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace + + codeblock = "<pre><code>" + codeblock + "\n</code></pre>"; + + return hashBlock(codeblock) + nextChar; + } + ); + + // attacklab: strip sentinel + text = text.replace(/~0/,""); + + return text; +} + +var hashBlock = function(text) { + text = text.replace(/(^\n+|\n+$)/g,""); + return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n"; +} + + +var _DoCodeSpans = function(text) { +// +// * Backtick quotes are used for <code></code> spans. +// +// * You can use multiple backticks as the delimiters if you want to +// include literal backticks in the code span. So, this input: +// +// Just type ``foo `bar` baz`` at the prompt. +// +// Will translate to: +// +// <p>Just type <code>foo `bar` baz</code> at the prompt.</p> +// +// There's no arbitrary limit to the number of backticks you +// can use as delimters. If you need three consecutive backticks +// in your code, use four for delimiters, etc. +// +// * You can use spaces to get literal backticks at the edges: +// +// ... type `` `bar` `` ... +// +// Turns to: +// +// ... type <code>`bar`</code> ... +// + + /* + text = text.replace(/ + (^|[^\\]) // Character before opening ` can't be a backslash + (`+) // $2 = Opening run of ` + ( // $3 = The code block + [^\r]*? + [^`] // attacklab: work around lack of lookbehind + ) + \2 // Matching closer + (?!`) + /gm, function(){...}); + */ + + text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm, + function(wholeMatch,m1,m2,m3,m4) { + var c = m3; + c = c.replace(/^([ \t]*)/g,""); // leading whitespace + c = c.replace(/[ \t]*$/g,""); // trailing whitespace + c = _EncodeCode(c); + return m1+"<code>"+c+"</code>"; + }); + + return text; +} + + +var _EncodeCode = function(text) { +// +// Encode/escape certain characters inside Markdown code runs. +// The point is that in code, these characters are literals, +// and lose their special Markdown meanings. +// + // Encode all ampersands; HTML entities are not + // entities within a Markdown code span. + text = text.replace(/&/g,"&"); + + // Do the angle bracket song and dance: + text = text.replace(/</g,"<"); + text = text.replace(/>/g,">"); + + // Now, escape characters that are magic in Markdown: + text = escapeCharacters(text,"\*_{}[]\\",false); + +// jj the line above breaks this: +//--- + +//* Item + +// 1. Subitem + +// special char: * +//--- + + return text; +} + + +var _DoItalicsAndBold = function(text) { + + // <strong> must go first: + text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\1/g, + "<strong>$2</strong>"); + + text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g, + "<em>$2</em>"); + + return text; +} + + +var _DoBlockQuotes = function(text) { + + /* + text = text.replace(/ + ( // Wrap whole match in $1 + ( + ^[ \t]*>[ \t]? // '>' at the start of a line + .+\n // rest of the first line + (.+\n)* // subsequent consecutive lines + \n* // blanks + )+ + ) + /gm, function(){...}); + */ + + text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm, + function(wholeMatch,m1) { + var bq = m1; + + // attacklab: hack around Konqueror 3.5.4 bug: + // "----------bug".replace(/^-/g,"") == "bug" + + bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0"); // trim one level of quoting + + // attacklab: clean up hack + bq = bq.replace(/~0/g,""); + + bq = bq.replace(/^[ \t]+$/gm,""); // trim whitespace-only lines + bq = _RunBlockGamut(bq); // recurse + + bq = bq.replace(/(^|\n)/g,"$1 "); + // These leading spaces screw with <pre> content, so we need to fix that: + bq = bq.replace( + /(\s*<pre>[^\r]+?<\/pre>)/gm, + function(wholeMatch,m1) { + var pre = m1; + // attacklab: hack around Konqueror 3.5.4 bug: + pre = pre.replace(/^ /mg,"~0"); + pre = pre.replace(/~0/g,""); + return pre; + }); + + return hashBlock("<blockquote>\n" + bq + "\n</blockquote>"); + }); + return text; +} + + +var _FormParagraphs = function(text) { +// +// Params: +// $text - string to process with html <p> tags +// + + // Strip leading and trailing lines: + text = text.replace(/^\n+/g,""); + text = text.replace(/\n+$/g,""); + + var grafs = text.split(/\n{2,}/g); + var grafsOut = new Array(); + + // + // Wrap <p> tags. + // + var end = grafs.length; + for (var i=0; i<end; i++) { + var str = grafs[i]; + + // if this is an HTML marker, copy it + if (str.search(/~K(\d+)K/g) >= 0) { + grafsOut.push(str); + } + else if (str.search(/\S/) >= 0) { + str = _RunSpanGamut(str); + str = str.replace(/^([ \t]*)/g,"<p>"); + str += "</p>" + grafsOut.push(str); + } + + } + + // + // Unhashify HTML blocks + // + end = grafsOut.length; + for (var i=0; i<end; i++) { + // if this is a marker for an html block... + while (grafsOut[i].search(/~K(\d+)K/) >= 0) { + var blockText = g_html_blocks[RegExp.$1]; + blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs + grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText); + } + } + + return grafsOut.join("\n\n"); +} + + +var _EncodeAmpsAndAngles = function(text) { +// Smart processing for ampersands and angle brackets that need to be encoded. + + // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: + // http://bumppo.net/projects/amputator/ + text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&"); + + // Encode naked <'s + text = text.replace(/<(?![a-z\/?\$!])/gi,"<"); + + return text; +} + + +var _EncodeBackslashEscapes = function(text) { +// +// Parameter: String. +// Returns: The string, with after processing the following backslash +// escape sequences. +// + + // attacklab: The polite way to do this is with the new + // escapeCharacters() function: + // + // text = escapeCharacters(text,"\\",true); + // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true); + // + // ...but we're sidestepping its use of the (slow) RegExp constructor + // as an optimization for Firefox. This function gets called a LOT. + + text = text.replace(/\\(\\)/g,escapeCharacters_callback); + text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback); + return text; +} + + +var _DoAutoLinks = function(text) { + + text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"<a href=\"$1\">$1</a>"); + + // Email addresses: <address@domain.foo> + + /* + text = text.replace(/ + < + (?:mailto:)? + ( + [-.\w]+ + \@ + [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ + ) + > + /gi, _DoAutoLinks_callback()); + */ + text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi, + function(wholeMatch,m1) { + return _EncodeEmailAddress( _UnescapeSpecialChars(m1) ); + } + ); + + return text; +} + + +var _EncodeEmailAddress = function(addr) { +// +// Input: an email address, e.g. "foo@example.com" +// +// Output: the email address as a mailto link, with each character +// of the address encoded as either a decimal or hex entity, in +// the hopes of foiling most address harvesting spam bots. E.g.: +// +// <a href="mailto:foo@e +// xample.com">foo +// @example.com</a> +// +// Based on a filter by Matthew Wickline, posted to the BBEdit-Talk +// mailing list: <http://tinyurl.com/yu7ue> +// + + // attacklab: why can't javascript speak hex? + function char2hex(ch) { + var hexDigits = '0123456789ABCDEF'; + var dec = ch.charCodeAt(0); + return(hexDigits.charAt(dec>>4) + hexDigits.charAt(dec&15)); + } + + var encode = [ + function(ch){return "&#"+ch.charCodeAt(0)+";";}, + function(ch){return "&#x"+char2hex(ch)+";";}, + function(ch){return ch;} + ]; + + addr = "mailto:" + addr; + + addr = addr.replace(/./g, function(ch) { + if (ch == "@") { + // this *must* be encoded. I insist. + ch = encode[Math.floor(Math.random()*2)](ch); + } else if (ch !=":") { + // leave ':' alone (to spot mailto: later) + var r = Math.random(); + // roughly 10% raw, 45% hex, 45% dec + ch = ( + r > .9 ? encode[2](ch) : + r > .45 ? encode[1](ch) : + encode[0](ch) + ); + } + return ch; + }); + + addr = "<a href=\"" + addr + "\">" + addr + "</a>"; + addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part + + return addr; +} + + +var _UnescapeSpecialChars = function(text) { +// +// Swap back in all the special characters we've hidden. +// + text = text.replace(/~E(\d+)E/g, + function(wholeMatch,m1) { + var charCodeToReplace = parseInt(m1); + return String.fromCharCode(charCodeToReplace); + } + ); + return text; +} + + +var _Outdent = function(text) { +// +// Remove one level of line-leading tabs or spaces +// + + // attacklab: hack around Konqueror 3.5.4 bug: + // "----------bug".replace(/^-/g,"") == "bug" + + text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width + + // attacklab: clean up hack + text = text.replace(/~0/g,"") + + return text; +} + +var _Detab = function(text) { +// attacklab: Detab's completely rewritten for speed. +// In perl we could fix it by anchoring the regexp with \G. +// In javascript we're less fortunate. + + // expand first n-1 tabs + text = text.replace(/\t(?=\t)/g," "); // attacklab: g_tab_width + + // replace the nth with two sentinels + text = text.replace(/\t/g,"~A~B"); + + // use the sentinel to anchor our regex so it doesn't explode + text = text.replace(/~B(.+?)~A/g, + function(wholeMatch,m1,m2) { + var leadingText = m1; + var numSpaces = 4 - leadingText.length % 4; // attacklab: g_tab_width + + // there *must* be a better way to do this: + for (var i=0; i<numSpaces; i++) leadingText+=" "; + + return leadingText; + } + ); + + // clean up sentinels + text = text.replace(/~A/g," "); // attacklab: g_tab_width + text = text.replace(/~B/g,""); + + return text; +} + + +// +// attacklab: Utility functions +// + + +var escapeCharacters = function(text, charsToEscape, afterBackslash) { + // First we have to escape the escape characters so that + // we can build a character class out of them + var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g,"\\$1") + "])"; + + if (afterBackslash) { + regexString = "\\\\" + regexString; + } + + var regex = new RegExp(regexString,"g"); + text = text.replace(regex,escapeCharacters_callback); + + return text; +} + + +var escapeCharacters_callback = function(wholeMatch,m1) { + var charCodeToEscape = m1.charCodeAt(0); + return "~E"+charCodeToEscape+"E"; +} + +} // end of Attacklab.showdown.converter + + +// Version 0.9 used the Showdown namespace instead of Attacklab.showdown +// The old namespace is deprecated, but we'll support it for now: +var Showdown = Attacklab.showdown; + +// If anyone's interested, tell the world that this file's been loaded +if (Attacklab.fileLoaded) { + Attacklab.fileLoaded("showdown.js"); +}
\ No newline at end of file diff --git a/templates/content/js/wmd/wmd-min.js b/templates/content/js/wmd/wmd-min.js new file mode 100644 index 00000000..85cbd907 --- /dev/null +++ b/templates/content/js/wmd/wmd-min.js @@ -0,0 +1 @@ +var Attacklab=Attacklab||{};Attacklab.wmdBase=function(){var y=top.Attacklab;var E=top.document;var s=top.RegExp;var l=top.navigator;y.Util={};y.Position={};y.Command={};y.Global={};var a=y.Util;var C=y.Position;var h=y.Command;var v=y.Global;v.isIE=/msie/.test(l.userAgent.toLowerCase());v.isIE_5or6=/msie 6/.test(l.userAgent.toLowerCase())||/msie 5/.test(l.userAgent.toLowerCase());v.isIE_7plus=v.isIE&&!v.isIE_5or6;v.isOpera=/opera/.test(l.userAgent.toLowerCase());v.isKonqueror=/konqueror/.test(l.userAgent.toLowerCase());var c="粗体 <strong> Ctrl-B";var f="斜体 <em> Ctrl-I";var z="超链接 <a> Ctrl-L";var u="引用 <blockquote> Ctrl-.";var e="代码 <pre><code> Ctrl-K";var d="图片 <img> Ctrl-G";var q="数字编号列表 <ol> Ctrl-O";var t="项目符号列表 <ul> Ctrl-U";var i="标题 <h1>/<h2> Ctrl-H";var p="水平线 <hr> Ctrl-R";var m="撤销 Ctrl-Z";var j="重做 Ctrl-Y";var B="<p style='margin-top: 0px'><b>输入图片地址</b></p><p>示例:<br />http://www.cnprog.com/images/temp.jpg \"我的截图\"</p>";var D="<p style='margin-top: 0px'><b>输入Web地址</b></p><p>示例:<br />http://www.cnprog.com/ \"我的网站\"</p>";var n='<div>或者上传本地图片:</div><input type="file" name="file-upload" id="file-upload" size="26" onchange="return ajaxFileUpload($(\'#image-url\'));"/><br><img id="loading" src="/content/images/indicator.gif" style="display:none;"/>';var b="http://";var g="http://";var o="images/";var A=500;var x=100;var k="http://wmd-editor.com/";var r="WMD website";var w="_blank";y.PanelCollection=function(){this.buttonBar=E.getElementById("wmd-button-bar");this.preview=E.getElementById("previewer");this.output=E.getElementById("wmd-output");this.input=E.getElementById("editor")};y.panels=undefined;y.ieCachedRange=null;y.ieRetardedClick=false;a.isVisible=function(F){if(window.getComputedStyle){return window.getComputedStyle(F,null).getPropertyValue("display")!=="none"}else{if(F.currentStyle){return F.currentStyle.display!=="none"}}};a.addEvent=function(G,F,H){if(G.attachEvent){G.attachEvent("on"+F,H)}else{G.addEventListener(F,H,false)}};a.removeEvent=function(G,F,H){if(G.detachEvent){G.detachEvent("on"+F,H)}else{G.removeEventListener(F,H,false)}};a.fixEolChars=function(F){F=F.replace(/\r\n/g,"\n");F=F.replace(/\r/g,"\n");return F};a.extendRegExp=function(H,J,G){if(J===null||J===undefined){J=""}if(G===null||G===undefined){G=""}var I=H.toString();var F;I=I.replace(/\/([gim]*)$/,"");F=s.$1;I=I.replace(/(^\/|\/$)/g,"");I=J+I+G;return new s(I,F)};a.createImage=function(F){var H=o+F;var G=E.createElement("img");G.className="wmd-button";G.src=H;return G};a.prompt=function(M,P,H){var I;var F;var K;var J=0;if(arguments.length==4){J=arguments[3]}if(P===undefined){P=""}var L=function(Q){var R=(Q.charCode||Q.keyCode);if(R===27){N(true)}};var N=function(Q){a.removeEvent(E.body,"keydown",L);var R=K.value;if(Q){R=null}else{R=R.replace("http://http://","http://");R=R.replace("http://https://","https://");R=R.replace("http://ftp://","ftp://");if(R.indexOf("http://")===-1&&R.indexOf("ftp://")===-1){R="http://"+R}}I.parentNode.removeChild(I);F.parentNode.removeChild(F);H(R);return false};var G=function(){F=E.createElement("div");F.className="wmd-prompt-background";style=F.style;style.position="absolute";style.top="0";style.zIndex="1000";if(v.isKonqueror){style.backgroundColor="transparent"}else{if(v.isIE){style.filter="alpha(opacity=50)"}else{style.opacity="0.5"}}var Q=C.getPageSize();style.height=Q[1]+"px";if(v.isIE){style.left=E.documentElement.scrollLeft;style.width=E.documentElement.clientWidth}else{style.left="0";style.width="100%"}E.body.appendChild(F)};var O=function(){I=E.createElement("div");I.className="wmd-prompt-dialog";I.style.padding="10px;";I.style.position="fixed";I.style.width="400px";I.style.zIndex="1001";var Q=E.createElement("div");Q.innerHTML=M;Q.style.padding="5px";I.appendChild(Q);var S=E.createElement("form");S.onsubmit=function(){return N(false)};style=S.style;style.padding="0";style.margin="0";style.cssFloat="left";style.width="100%";style.textAlign="center";style.position="relative";I.appendChild(S);K=E.createElement("input");if(J==1){K.id="image-url"}K.type="text";K.value=P;style=K.style;style.display="block";style.width="80%";style.marginLeft=style.marginRight="auto";S.appendChild(K);if(J==1){var R=E.createElement("div");R.innerHTML=n;R.style.padding="5px";S.appendChild(R)}var U=E.createElement("input");U.type="button";U.onclick=function(){return N(false)};U.value="OK";style=U.style;style.margin="10px";style.display="inline";style.width="7em";var T=E.createElement("input");T.type="button";T.onclick=function(){return N(true)};T.value="Cancel";style=T.style;style.margin="10px";style.display="inline";style.width="7em";if(/mac/.test(l.platform.toLowerCase())){S.appendChild(T);S.appendChild(U)}else{S.appendChild(U);S.appendChild(T)}a.addEvent(E.body,"keydown",L);I.style.top="50%";I.style.left="50%";I.style.display="block";if(v.isIE_5or6){I.style.position="absolute";I.style.top=E.documentElement.scrollTop+200+"px";I.style.left="50%"}E.body.appendChild(I);I.style.marginTop=-(C.getHeight(I)/2)+"px";I.style.marginLeft=-(C.getWidth(I)/2)+"px"};G();top.setTimeout(function(){O();var R=P.length;if(K.selectionStart!==undefined){K.selectionStart=0;K.selectionEnd=R}else{if(K.createTextRange){var Q=K.createTextRange();Q.collapse(false);Q.moveStart("character",-R);Q.moveEnd("character",R);Q.select()}}K.focus()},0)};C.getTop=function(H,G){var F=H.offsetTop;if(!G){while(H=H.offsetParent){F+=H.offsetTop}}return F};C.getHeight=function(F){return F.offsetHeight||F.scrollHeight};C.getWidth=function(F){return F.offsetWidth||F.scrollWidth};C.getPageSize=function(){var G,H;var F,K;if(self.innerHeight&&self.scrollMaxY){G=E.body.scrollWidth;H=self.innerHeight+self.scrollMaxY}else{if(E.body.scrollHeight>E.body.offsetHeight){G=E.body.scrollWidth;H=E.body.scrollHeight}else{G=E.body.offsetWidth;H=E.body.offsetHeight}}if(self.innerHeight){F=self.innerWidth;K=self.innerHeight}else{if(E.documentElement&&E.documentElement.clientHeight){F=E.documentElement.clientWidth;K=E.documentElement.clientHeight}else{if(E.body){F=E.body.clientWidth;K=E.body.clientHeight}}}var J=Math.max(G,F);var I=Math.max(H,K);return[J,I,F,K]};y.inputPoller=function(O,H){var F=this;var K=y.panels.input;var G;var I;var L;var J;this.tick=function(){if(!a.isVisible(K)){return}if(K.selectionStart||K.selectionStart===0){var Q=K.selectionStart;var P=K.selectionEnd;if(Q!=G||P!=I){G=Q;I=P;if(L!=K.value){L=K.value;return true}}}return false};var N=function(){if(!a.isVisible(K)){return}if(F.tick()){O()}};var M=function(){J=top.setInterval(N,H)};this.destroy=function(){top.clearInterval(J)};M()};y.undoManager=function(Q){var U=this;var O=[];var M=0;var L="none";var G;var R;var H;var K;var F=function(W,V){if(L!=W){L=W;if(!V){I()}}if(!v.isIE||L!="moving"){H=top.setTimeout(N,1)}else{K=null}};var N=function(){K=new y.TextareaState();R.tick();H=undefined};this.setCommandMode=function(){L="command";I();H=top.setTimeout(N,0)};this.canUndo=function(){return M>1};this.canRedo=function(){if(O[M+1]){return true}return false};this.undo=function(){if(U.canUndo()){if(G){G.restore();G=null}else{O[M]=new y.TextareaState();O[--M].restore();if(Q){Q()}}}L="none";y.panels.input.focus();N()};this.redo=function(){if(U.canRedo()){O[++M].restore();if(Q){Q()}}L="none";y.panels.input.focus();N()};var I=function(){var V=K||new y.TextareaState();if(!V){return false}if(L=="moving"){if(!G){G=V}return}if(G){if(O[M-1].text!=G.text){O[M++]=G}G=null}O[M++]=V;O[M+1]=null;if(Q){Q()}};var P=function(V){var X=false;if(V.ctrlKey||V.metaKey){var W=V.charCode||V.keyCode;var Y=String.fromCharCode(W);switch(Y){case"y":U.redo();X=true;break;case"z":if(!V.shiftKey){U.undo()}else{U.redo()}X=true;break}}if(X){if(V.preventDefault){V.preventDefault()}if(top.event){top.event.returnValue=false}return}};var T=function(V){if(!V.ctrlKey&&!V.metaKey){var W=V.keyCode;if((W>=33&&W<=40)||(W>=63232&&W<=63235)){F("moving")}else{if(W==8||W==46||W==127){F("deleting")}else{if(W==13){F("newlines")}else{if(W==27){F("escape")}else{if((W<16||W>20)&&W!=91){F("typing")}}}}}}};var J=function(){a.addEvent(y.panels.input,"keypress",function(W){if((W.ctrlKey||W.metaKey)&&(W.keyCode==89||W.keyCode==90)){W.preventDefault()}});var V=function(){if(v.isIE||(K&&K.text!=y.panels.input.value)){if(H==undefined){L="paste";I();N()}}};R=new y.inputPoller(V,x);a.addEvent(y.panels.input,"keydown",P);a.addEvent(y.panels.input,"keydown",T);a.addEvent(y.panels.input,"mousedown",function(){F("moving")});y.panels.input.onpaste=V;y.panels.input.ondrop=V};var S=function(){J();N();I()};this.destroy=function(){if(R){R.destroy()}};S()};y.editor=function(O){if(!O){O=function(){}}var L=y.panels.input;var I=0;var P=this;var K;var R;var G;var M;var N;var U=function(W){L.focus();if(W.textOp){if(N){N.setCommandMode()}var Y=new y.TextareaState();if(!Y){return}var Z=Y.getChunks();var V=function(){L.focus();if(Z){Y.setChunks(Z)}Y.restore();O()};var X=W.textOp(Z,V);if(!X){V()}}if(W.execute){W.execute(P)}};var S=function(){if(N){F(document.getElementById("wmd-undo-button"),N.canUndo());F(document.getElementById("wmd-redo-button"),N.canRedo())}};var F=function(V,X){var Y="0px";var Z="-20px";var W="-40px";if(X){V.style.backgroundPosition=V.XShift+" "+Y;V.onmouseover=function(){this.style.backgroundPosition=this.XShift+" "+W};V.onmouseout=function(){this.style.backgroundPosition=this.XShift+" "+Y};if(v.isIE){V.onmousedown=function(){y.ieRetardedClick=true;y.ieCachedRange=document.selection.createRange()}}if(!V.isHelp){V.onclick=function(){if(this.onmouseout){this.onmouseout()}U(this);return false}}}else{V.style.backgroundPosition=V.XShift+" "+Z;V.onmouseover=V.onmouseout=V.onclick=function(){}}};var J=function(){var Z=document.getElementById("wmd-button-bar");var W="0px";var Y="-20px";var ae="-40px";var ak=document.createElement("ul");ak.id="wmd-button-row";ak=Z.appendChild(ak);var ad=document.createElement("li");ad.className="wmd-button";ad.id="wmd-bold-button";ad.title=c;ad.XShift="0px";ad.textOp=h.doBold;F(ad,true);ak.appendChild(ad);var ac=document.createElement("li");ac.className="wmd-button";ac.id="wmd-italic-button";ac.title=f;ac.XShift="-20px";ac.textOp=h.doItalic;F(ac,true);ak.appendChild(ac);var ah=document.createElement("li");ah.className="wmd-spacer";ah.id="wmd-spacer1";ak.appendChild(ah);var ai=document.createElement("li");ai.className="wmd-button";ai.id="wmd-link-button";ai.title=z;ai.XShift="-40px";ai.textOp=function(ap,aq){return h.doLinkOrImage(ap,aq,false)};F(ai,true);ak.appendChild(ai);var al=document.createElement("li");al.className="wmd-button";al.id="wmd-quote-button";al.title=u;al.XShift="-60px";al.textOp=h.doBlockquote;F(al,true);ak.appendChild(al);var am=document.createElement("li");am.className="wmd-button";am.id="wmd-code-button";am.title=e;am.XShift="-80px";am.textOp=h.doCode;F(am,true);ak.appendChild(am);var aa=document.createElement("li");aa.className="wmd-button";aa.id="wmd-image-button";aa.title=d;aa.XShift="-100px";aa.textOp=function(ap,aq){return h.doLinkOrImage(ap,aq,true)};F(aa,true);ak.appendChild(aa);var ag=document.createElement("li");ag.className="wmd-spacer";ag.id="wmd-spacer2";ak.appendChild(ag);var ab=document.createElement("li");ab.className="wmd-button";ab.id="wmd-olist-button";ab.title=q;ab.XShift="-120px";ab.textOp=function(ap,aq){h.doList(ap,aq,true)};F(ab,true);ak.appendChild(ab);var ao=document.createElement("li");ao.className="wmd-button";ao.id="wmd-ulist-button";ao.title=t;ao.XShift="-140px";ao.textOp=function(ap,aq){h.doList(ap,aq,false)};F(ao,true);ak.appendChild(ao);var aj=document.createElement("li");aj.className="wmd-button";aj.id="wmd-heading-button";aj.title=i;aj.XShift="-160px";aj.textOp=h.doHeading;F(aj,true);ak.appendChild(aj);var X=document.createElement("li");X.className="wmd-button";X.id="wmd-hr-button";X.title=p;X.XShift="-180px";X.textOp=h.doHorizontalRule;F(X,true);ak.appendChild(X);var af=document.createElement("li");af.className="wmd-spacer";af.id="wmd-spacer3";ak.appendChild(af);var V=document.createElement("li");V.className="wmd-button";V.id="wmd-undo-button";V.title=m;V.XShift="-200px";V.execute=function(ap){ap.undo()};F(V,true);ak.appendChild(V);var an=document.createElement("li");an.className="wmd-button";an.id="wmd-redo-button";an.title=j;if(/win/.test(l.platform.toLowerCase())){an.title=j}else{an.title="重做 - Ctrl+Shift+Z"}an.XShift="-220px";an.execute=function(ap){ap.redo()};F(an,true);ak.appendChild(an);S()};var H=function(){if(/\?noundo/.test(E.location.href)){y.nativeUndo=true}if(!y.nativeUndo){N=new y.undoManager(function(){O();S()})}J();var W="keydown";if(v.isOpera){W="keypress"}a.addEvent(L,W,function(Y){if(Y.ctrlKey||Y.metaKey){var Z=Y.charCode||Y.keyCode;var X=String.fromCharCode(Z).toLowerCase();if(Z===46){X=""}if(Z===190){X="."}switch(X){case"b":U(document.getElementById("wmd-bold-button"));break;case"i":U(document.getElementById("wmd-italic-button"));break;case"l":U(document.getElementById("wmd-link-button"));break;case".":U(document.getElementById("wmd-quote-button"));break;case"k":U(document.getElementById("wmd-code-button"));break;case"g":U(document.getElementById("wmd-image-button"));break;case"o":U(document.getElementById("wmd-olist-button"));break;case"u":U(document.getElementById("wmd-ulist-button"));break;case"h":U(document.getElementById("wmd-heading-button"));break;case"r":U(document.getElementById("wmd-hr-button"));break;case"y":U(document.getElementById("wmd-redo-button"));break;case"z":if(Y.shiftKey){U(document.getElementById("wmd-redo-button"))}else{U(document.getElementById("wmd-undo-button"))}break;default:return}if(Y.preventDefault){Y.preventDefault()}if(top.event){top.event.returnValue=false}}});a.addEvent(L,"keyup",function(X){if(X.shiftKey&&!X.ctrlKey&&!X.metaKey){var Y=X.charCode||X.keyCode;if(Y===13){fakeButton={};fakeButton.textOp=h.doAutoindent;U(fakeButton)}}});if(L.form){var V=L.form.onsubmit;L.form.onsubmit=function(){Q();if(V){return V.apply(this,arguments)}}}};var Q=function(){if(y.showdown){var V=new y.showdown.converter()}var W=L.value;var X=function(){L.value=W};if(!/markdown/.test(y.wmd_env.output.toLowerCase())){if(V){L.value=V.makeHtml(W);top.setTimeout(X,0)}}return true};this.undo=function(){if(N){N.undo()}};this.redo=function(){if(N){N.redo()}};var T=function(){H()};this.destroy=function(){if(N){N.destroy()}if(G.parentNode){G.parentNode.removeChild(G)}if(L){L.style.marginTop=""}top.clearInterval(M)};T()};y.TextareaState=function(){var F=this;var G=y.panels.input;this.init=function(){if(!a.isVisible(G)){return}this.setInputAreaSelectionStartEnd();this.scrollTop=G.scrollTop;if(!this.text&&G.selectionStart||G.selectionStart===0){this.text=G.value}};this.setInputAreaSelection=function(){if(!a.isVisible(G)){return}if(G.selectionStart!==undefined&&!v.isOpera){G.focus();G.selectionStart=F.start;G.selectionEnd=F.end;G.scrollTop=F.scrollTop}else{if(E.selection){if(E.activeElement&&E.activeElement!==G){return}G.focus();var H=G.createTextRange();H.moveStart("character",-G.value.length);H.moveEnd("character",-G.value.length);H.moveEnd("character",F.end);H.moveStart("character",F.start);H.select()}}};this.setInputAreaSelectionStartEnd=function(){if(G.selectionStart||G.selectionStart===0){F.start=G.selectionStart;F.end=G.selectionEnd}else{if(E.selection){F.text=a.fixEolChars(G.value);var K;if(y.ieRetardedClick&&y.ieCachedRange){K=y.ieCachedRange;y.ieRetardedClick=false}else{K=E.selection.createRange()}var L=a.fixEolChars(K.text);var J="\x07";var I=J+L+J;K.text=I;var M=a.fixEolChars(G.value);K.moveStart("character",-I.length);K.text=L;F.start=M.indexOf(J);F.end=M.lastIndexOf(J)-J.length;var H=F.text.length-a.fixEolChars(G.value).length;if(H){K.moveStart("character",-L.length);while(H--){L+="\n";F.end+=1}K.text=L}this.setInputAreaSelection()}}};this.restore=function(){if(F.text!=undefined&&F.text!=G.value){G.value=F.text}this.setInputAreaSelection();G.scrollTop=F.scrollTop};this.getChunks=function(){var H=new y.Chunks();H.before=a.fixEolChars(F.text.substring(0,F.start));H.startTag="";H.selection=a.fixEolChars(F.text.substring(F.start,F.end));H.endTag="";H.after=a.fixEolChars(F.text.substring(F.end));H.scrollTop=F.scrollTop;return H};this.setChunks=function(H){H.before=H.before+H.startTag;H.after=H.endTag+H.after;if(v.isOpera){H.before=H.before.replace(/\n/g,"\r\n");H.selection=H.selection.replace(/\n/g,"\r\n");H.after=H.after.replace(/\n/g,"\r\n")}this.start=H.before.length;this.end=H.before.length+H.selection.length;this.text=H.before+H.selection+H.after;this.scrollTop=H.scrollTop};this.init()};y.Chunks=function(){};y.Chunks.prototype.findTags=function(G,I){var F=this;var H;if(G){H=a.extendRegExp(G,"","$");this.before=this.before.replace(H,function(J){F.startTag=F.startTag+J;return""});H=a.extendRegExp(G,"^","");this.selection=this.selection.replace(H,function(J){F.startTag=F.startTag+J;return""})}if(I){H=a.extendRegExp(I,"","$");this.selection=this.selection.replace(H,function(J){F.endTag=J+F.endTag;return""});H=a.extendRegExp(I,"^","");this.after=this.after.replace(H,function(J){F.endTag=J+F.endTag;return""})}};y.Chunks.prototype.trimWhitespace=function(F){this.selection=this.selection.replace(/^(\s*)/,"");if(!F){this.before+=s.$1}this.selection=this.selection.replace(/(\s*)$/,"");if(!F){this.after=s.$1+this.after}};y.Chunks.prototype.skipLines=function(H,G,F){if(H===undefined){H=1}if(G===undefined){G=1}H++;G++;var I;var J;this.selection=this.selection.replace(/(^\n*)/,"");this.startTag=this.startTag+s.$1;this.selection=this.selection.replace(/(\n*$)/,"");this.endTag=this.endTag+s.$1;this.startTag=this.startTag.replace(/(^\n*)/,"");this.before=this.before+s.$1;this.endTag=this.endTag.replace(/(\n*$)/,"");this.after=this.after+s.$1;if(this.before){I=J="";while(H--){I+="\\n?";J+="\n"}if(F){I="\\n*"}this.before=this.before.replace(new s(I+"$",""),J)}if(this.after){I=J="";while(G--){I+="\\n?";J+="\n"}if(F){I="\\n*"}this.after=this.after.replace(new s(I,""),J)}};h.prefixes="(?:\\s{4,}|\\s*>|\\s*-\\s+|\\s*\\d+\\.|=|\\+|-|_|\\*|#|\\s*\\[[^\n]]+\\]:)";h.unwrap=function(G){var F=new s("([^\\n])\\n(?!(\\n|"+h.prefixes+"))","g");G.selection=G.selection.replace(F,"$1 $2")};h.wrap=function(G,F){h.unwrap(G);var H=new s("(.{1,"+F+"})( +|$\\n?)","gm");G.selection=G.selection.replace(H,function(I,J){if(new s("^"+h.prefixes,"").test(I)){return I}return J+"\n"});G.selection=G.selection.replace(/\s+$/,"")};h.doBold=function(F,G){return h.doBorI(F,G,2,"strong text")};h.doItalic=function(F,G){return h.doBorI(F,G,1,"emphasized text")};h.doBorI=function(L,J,K,F){L.trimWhitespace();L.selection=L.selection.replace(/\n{2,}/g,"\n");L.before.search(/(\**$)/);var I=s.$1;L.after.search(/(^\**)/);var G=s.$1;var M=Math.min(I.length,G.length);if((M>=K)&&(M!=2||K!=1)){L.before=L.before.replace(s("[*]{"+K+"}$",""),"");L.after=L.after.replace(s("^[*]{"+K+"}",""),"")}else{if(!L.selection&&G){L.after=L.after.replace(/^([*_]*)/,"");L.before=L.before.replace(/(\s?)$/,"");var H=s.$1;L.before=L.before+G+H}else{if(!L.selection&&!G){L.selection=F}var N=K<=1?"*":"**";L.before=L.before+N;L.after=N+L.after}}return};h.stripLinkDefs=function(G,F){G=G.replace(/^[ ]{0,3}\[(\d+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|$)/gm,function(K,L,H,I,J){F[L]=K.replace(/\s*$/,"");if(I){F[L]=K.replace(/["(](.+?)[")]$/,"");return I+J}return""});return G};h.addLinkDef=function(M,I){var F=0;var H={};M.before=h.stripLinkDefs(M.before,H);M.selection=h.stripLinkDefs(M.selection,H);M.after=h.stripLinkDefs(M.after,H);var G="";var L=/(\[(?:\[[^\]]*\]|[^\[\]])*\][ ]?(?:\n[ ]*)?\[)(\d+)(\])/g;var K=function(O){F++;O=O.replace(/^[ ]{0,3}\[(\d+)\]:/," ["+F+"]:");G+="\n"+O};var J=function(P,Q,R,O){if(H[R]){K(H[R]);return Q+F+O}return P};M.before=M.before.replace(L,J);if(I){K(I)}else{M.selection=M.selection.replace(L,J)}var N=F;M.after=M.after.replace(L,J);if(M.after){M.after=M.after.replace(/\n*$/,"")}if(!M.after){M.selection=M.selection.replace(/\n*$/,"")}M.after+="\n\n"+G;return N};h.doLinkOrImage=function(F,G,I){F.trimWhitespace();F.findTags(/\s*!?\[/,/\][ ]?(?:\n[ ]*)?(\[.*?\])?/);if(F.endTag.length>1){F.startTag=F.startTag.replace(/!?\[/,"");F.endTag="";h.addLinkDef(F,null)}else{if(/\n\n/.test(F.selection)){h.addLinkDef(F,null);return}var H=function(L){if(L!==null){F.startTag=F.endTag="";var K=" [999]: "+L;var J=h.addLinkDef(F,K);F.startTag=I?"![":"[";F.endTag="]["+J+"]";if(!F.selection){if(I){F.selection="alt text"}else{F.selection="link text"}}}G()};if(I){a.prompt(B,b,H,1)}else{a.prompt(D,g,H)}return true}};a.makeAPI=function(){y.wmd={};y.wmd.editor=y.editor;y.wmd.previewManager=y.previewManager};a.startEditor=function(){if(y.wmd_env.autostart===false){a.makeAPI();return}var G;var F;var H=function(){y.panels=new y.PanelCollection();F=new y.previewManager();var I=F.refresh;G=new y.editor(I);F.refresh(true)};a.addEvent(top,"load",H)};y.previewManager=function(){var H=this;var V;var F;var N;var M;var S;var O;var I=3000;var P="delayed";var K=function(X,Y){a.addEvent(X,"input",Y);X.onpaste=Y;X.ondrop=Y;a.addEvent(X,"keypress",Y);a.addEvent(X,"keydown",Y);F=new y.inputPoller(Y,A)};var R=function(){var X=0;if(top.innerHeight){X=top.pageYOffset}else{if(E.documentElement&&E.documentElement.scrollTop){X=E.documentElement.scrollTop}else{if(E.body){X=E.body.scrollTop}}}return X};var L=function(){if(!y.panels.preview&&!y.panels.output){return}var Z=y.panels.input.value;if(Z&&Z==S){return}else{S=Z}var Y=new Date().getTime();if(!V&&y.showdown){V=new y.showdown.converter()}if(V){Z=V.makeHtml(Z)}var X=new Date().getTime();M=X-Y;G(Z);O=Z};var U=function(){if(N){top.clearTimeout(N);N=undefined}if(P!=="manual"){var X=0;if(P==="delayed"){X=M}if(X>I){X=I}N=top.setTimeout(L,X)}};var J=function(X){if(X.scrollHeight<=X.clientHeight){return 1}return X.scrollTop/(X.scrollHeight-X.clientHeight)};var W=function(){if(y.panels.preview){y.panels.preview.scrollTop=(y.panels.preview.scrollHeight-y.panels.preview.clientHeight)*J(y.panels.preview)}if(y.panels.output){y.panels.output.scrollTop=(y.panels.output.scrollHeight-y.panels.output.clientHeight)*J(y.panels.output)}};this.refresh=function(X){if(X){S="";L()}else{U()}};this.processingTime=function(){return M};this.output=function(){return O};this.setUpdateMode=function(X){P=X;H.refresh()};var Q=true;var G=function(aa){var X=C.getTop(y.panels.input)-R();if(y.panels.output){if(y.panels.output.value!==undefined){y.panels.output.value=aa;y.panels.output.readOnly=true}else{var Z=aa.replace(/&/g,"&");Z=Z.replace(/</g,"<");y.panels.output.innerHTML="<pre><code>"+Z+"</code></pre>"}}if(y.panels.preview){y.panels.preview.innerHTML=aa}W();if(Q){Q=false;return}var Y=C.getTop(y.panels.input)-R();if(v.isIE){top.setTimeout(function(){top.scrollBy(0,Y-X)},0)}else{top.scrollBy(0,Y-X)}};var T=function(){K(y.panels.input,U);L();if(y.panels.preview){y.panels.preview.scrollTop=0}if(y.panels.output){y.panels.output.scrollTop=0}};this.destroy=function(){if(F){F.destroy()}};T()};h.doAutoindent=function(F,G){F.before=F.before.replace(/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]*\n$/,"\n\n");F.before=F.before.replace(/(\n|^)[ ]{0,3}>[ \t]*\n$/,"\n\n");F.before=F.before.replace(/(\n|^)[ \t]+\n$/,"\n\n");if(/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]+.*\n$/.test(F.before)){if(h.doList){h.doList(F)}}if(/(\n|^)[ ]{0,3}>[ \t]+.*\n$/.test(F.before)){if(h.doBlockquote){h.doBlockquote(F)}}if(/(\n|^)(\t|[ ]{4,}).*\n$/.test(F.before)){if(h.doCode){h.doCode(F)}}};h.doBlockquote=function(F,G){F.selection=F.selection.replace(/^(\n*)([^\r]+?)(\n*)$/,function(L,K,J,I){F.before+=K;F.after=I+F.after;return J});F.before=F.before.replace(/(>[ \t]*)$/,function(J,I){F.selection=I+F.selection;return""});F.selection=F.selection.replace(/^(\s|>)+$/,"");F.selection=F.selection||"Blockquote";if(F.before){F.before=F.before.replace(/\n?$/,"\n")}if(F.after){F.after=F.after.replace(/^\n?/,"\n")}F.before=F.before.replace(/(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*$)/,function(I){F.startTag=I;return""});F.after=F.after.replace(/^(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*)/,function(I){F.endTag=I;return""});var H=function(J){var I=J?"> ":"";if(F.startTag){F.startTag=F.startTag.replace(/\n((>|\s)*)\n$/,function(L,K){return"\n"+K.replace(/^[ ]{0,3}>?[ \t]*$/gm,I)+"\n"})}if(F.endTag){F.endTag=F.endTag.replace(/^\n((>|\s)*)\n/,function(L,K){return"\n"+K.replace(/^[ ]{0,3}>?[ \t]*$/gm,I)+"\n"})}};if(/^(?![ ]{0,3}>)/m.test(F.selection)){h.wrap(F,y.wmd_env.lineLength-2);F.selection=F.selection.replace(/^/gm,"> ");H(true);F.skipLines()}else{F.selection=F.selection.replace(/^[ ]{0,3}> ?/gm,"");h.unwrap(F);H(false);if(!/^(\n|^)[ ]{0,3}>/.test(F.selection)&&F.startTag){F.startTag=F.startTag.replace(/\n{0,2}$/,"\n\n")}if(!/(\n|^)[ ]{0,3}>.*$/.test(F.selection)&&F.endTag){F.endTag=F.endTag.replace(/^\n{0,2}/,"\n\n")}}if(!/\n/.test(F.selection)){F.selection=F.selection.replace(/^(> *)/,function(I,J){F.startTag+=J;return""})}};h.doCode=function(F,G){var I=/\S[ ]*$/.test(F.before);var K=/^[ ]*\S/.test(F.after);if((!K&&!I)||/\n/.test(F.selection)){F.before=F.before.replace(/[ ]{4}$/,function(L){F.selection=L+F.selection;return""});var J=1;var H=1;if(/\n(\t|[ ]{4,}).*\n$/.test(F.before)){J=0}if(/^\n(\t|[ ]{4,})/.test(F.after)){H=0}F.skipLines(J,H);if(!F.selection){F.startTag=" ";F.selection="enter code here"}else{if(/^[ ]{0,3}\S/m.test(F.selection)){F.selection=F.selection.replace(/^/gm," ")}else{F.selection=F.selection.replace(/^[ ]{4}/gm,"")}}}else{F.trimWhitespace();F.findTags(/`/,/`/);if(!F.startTag&&!F.endTag){F.startTag=F.endTag="`";if(!F.selection){F.selection="enter code here"}}else{if(F.endTag&&!F.startTag){F.before+=F.endTag;F.endTag=""}else{F.startTag=F.endTag=""}}}};h.doList=function(Q,J,I){var S=/(\n|^)(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*$/;var R=/^\n*(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*/;var F="-";var N=1;var L=function(){var T;if(I){T=" "+N+". ";N++}else{T=" "+F+" "}return T};var M=function(T){if(I===undefined){I=/^\s*\d/.test(T)}T=T.replace(/^[ ]{0,3}([*+-]|\d+[.])\s/gm,function(U){return L()});return T};Q.findTags(/(\n|^)*[ ]{0,3}([*+-]|\d+[.])\s+/,null);if(Q.before&&!/\n$/.test(Q.before)&&!/^\n/.test(Q.startTag)){Q.before+=Q.startTag;Q.startTag=""}if(Q.startTag){var H=/\d+[.]/.test(Q.startTag);Q.startTag="";Q.selection=Q.selection.replace(/\n[ ]{4}/g,"\n");h.unwrap(Q);Q.skipLines();if(H){Q.after=Q.after.replace(R,M)}if(I==H){return}}var K=1;Q.before=Q.before.replace(S,function(T){if(/^\s*([*+-])/.test(T)){F=s.$1}K=/[^\n]\n\n[^\n]/.test(T)?1:0;return M(T)});if(!Q.selection){Q.selection="List item"}var O=L();var G=1;Q.after=Q.after.replace(R,function(T){G=/[^\n]\n\n[^\n]/.test(T)?1:0;return M(T)});Q.trimWhitespace(true);Q.skipLines(K,G,true);Q.startTag=O;var P=O.replace(/./g," ");h.wrap(Q,y.wmd_env.lineLength-P.length);Q.selection=Q.selection.replace(/\n/g,"\n"+P)};h.doHeading=function(H,I){H.selection=H.selection.replace(/\s+/g," ");H.selection=H.selection.replace(/(^\s+|\s+$)/g,"");if(!H.selection){H.startTag="## ";H.selection="Heading";H.endTag=" ##";return}var J=0;H.findTags(/#+[ ]*/,/[ ]*#+/);if(/#+/.test(H.startTag)){J=s.lastMatch.length}H.startTag=H.endTag="";H.findTags(null,/\s?(-+|=+)/);if(/=+/.test(H.endTag)){J=1}if(/-+/.test(H.endTag)){J=2}H.startTag=H.endTag="";H.skipLines(1,1);var K=J==0?2:J-1;if(K>0){var G=K>=2?"-":"=";var F=H.selection.length;if(F>y.wmd_env.lineLength){F=y.wmd_env.lineLength}H.endTag="\n";while(F--){H.endTag+=G}}};h.doHorizontalRule=function(F,G){F.startTag="----------\n";F.selection="";F.skipLines(2,1,true)}};Attacklab.wmd_env={};Attacklab.account_options={};Attacklab.wmd_defaults={version:1,output:"Markdown",lineLength:40,delayLoad:false};if(!Attacklab.wmd){Attacklab.wmd=function(){Attacklab.loadEnv=function(){var b=function(d){if(!d){return}for(var c in d){Attacklab.wmd_env[c]=d[c]}};b(Attacklab.wmd_defaults);b(Attacklab.account_options);b(top.wmd_options);Attacklab.full=true;var a="bold italic link blockquote code image ol ul heading hr";Attacklab.wmd_env.buttons=Attacklab.wmd_env.buttons||a};Attacklab.loadEnv()};Attacklab.wmd();Attacklab.wmdBase();Attacklab.Util.startEditor()};
\ No newline at end of file diff --git a/templates/content/js/wmd/wmd-test.html b/templates/content/js/wmd/wmd-test.html new file mode 100644 index 00000000..d748501a --- /dev/null +++ b/templates/content/js/wmd/wmd-test.html @@ -0,0 +1,158 @@ +<!DOCTYPE html> +<html> + + <head> + <title>Test WMD Page</title> + + <link rel="stylesheet" type="text/css" href="wmd.css" /> + + <meta http-equiv="pragma" content="no-cache"> + <meta http-equiv="cache-control" content="no-cache"> + <meta http-equiv="pragma-directive" content="no-cache"> + <meta http-equiv="cache-directive" content="no-cache"> + <meta http-equiv="expires" content="0"> + + <script type="text/javascript" src="jQuery/jquery-1.2.6.js"></script> + <script type="text/javascript" src="showdown.js"></script> + </head> + + <body> + <div id="wmd-button-bar" class="wmd-panel"></div> + <br/> + <textarea id="editor" class="wmd-panel"></textarea> + <br/> + <div id="previewer" class="wmd-panel"></div> + <br/> + <div id="wmd-output" class="wmd-panel"></div> + + <p>To test that page up/down and arrow keys work, copy this above the WMD + control.</p> + + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + Scroll Down!<br/> + + <script type="text/javascript" src="wmd.js"></script> + </body> +</html>
\ No newline at end of file diff --git a/templates/content/js/wmd/wmd.css b/templates/content/js/wmd/wmd.css new file mode 100644 index 00000000..80c226c8 --- /dev/null +++ b/templates/content/js/wmd/wmd.css @@ -0,0 +1,129 @@ +/*body +{ + background-color: White +} +*/ +.wmd-panel +{ +} + +#wmd-button-bar +{ + background-color: White; +} + +#wmd-input +{ + height: 500px; + background-color: Gainsboro; + border: 1px solid DarkGray; + margin-top: -20px; +} + +#wmd-preview +{ + background-color: LightSkyBlue; +} + +#wmd-output +{ + background-color: Pink; +} + +#wmd-button-row +{ + position: relative; + margin-left: 5px; + margin-right: 5px; + margin-bottom: 5px; + margin-top: 10px; + padding: 0px; + height: 20px; +} + +.wmd-spacer +{ + width: 1px; + height: 20px; + margin-left: 14px; + + position: absolute; + background-color: Silver; + display: inline-block; + list-style: none; +} + +.wmd-button +{ + width: 20px; + height: 20px; + margin-left: 5px; + margin-right: 5px; + + position: absolute; + background-image: url(images/wmd-buttons.png); + background-repeat: no-repeat; + background-position: 0px 0px; + display: inline-block; + list-style: none; +} + +.wmd-button > a +{ + width: 20px; + height: 20px; + margin-left: 5px; + margin-right: 5px; + + position: absolute; + display: inline-block; +} + + +/* sprite button slicing style information */ +#wmd-button-bar #wmd-bold-button {left: 0px; background-position: 0px 0;} +#wmd-button-bar #wmd-italic-button {left: 25px; background-position: -20px 0;} +#wmd-button-bar #wmd-spacer1 {left: 50px;} +#wmd-button-bar #wmd-link-button {left: 75px; background-position: -40px 0;} +#wmd-button-bar #wmd-quote-button {left: 100px; background-position: -60px 0;} +#wmd-button-bar #wmd-code-button {left: 125px; background-position: -80px 0;} +#wmd-button-bar #wmd-image-button {left: 150px; background-position: -100px 0;} +#wmd-button-bar #wmd-spacer2 {left: 175px;} +#wmd-button-bar #wmd-olist-button {left: 200px; background-position: -120px 0;} +#wmd-button-bar #wmd-ulist-button {left: 225px; background-position: -140px 0;} +#wmd-button-bar #wmd-heading-button {left: 250px; background-position: -160px 0;} +#wmd-button-bar #wmd-hr-button {left: 275px; background-position: -180px 0;} +#wmd-button-bar #wmd-spacer3 {left: 300px;} +#wmd-button-bar #wmd-undo-button {left: 325px; background-position: -200px 0;} +#wmd-button-bar #wmd-redo-button {left: 350px; background-position: -220px 0;} +#wmd-button-bar #wmd-help-button {right: 0px; background-position: -240px 0;} + + +.wmd-prompt-background +{ + background-color: Black; +} + +.wmd-prompt-dialog +{ + border: 1px solid #999999; + background-color: #F5F5F5; +} + +.wmd-prompt-dialog > div { + font-size: 1em; + font-family: arial, helvetica, sans-serif; +} + + +.wmd-prompt-dialog > form > input[type="text"] { + border: 1px solid #999999; + color: black; +} + +.wmd-prompt-dialog > form > input[type="button"]{ + border: 1px solid #888888; + font-family: trebuchet MS, helvetica, sans-serif; + font-size: 1em; + font-weight: bold; +} diff --git a/templates/content/js/wmd/wmd.js b/templates/content/js/wmd/wmd.js new file mode 100644 index 00000000..d83780d6 --- /dev/null +++ b/templates/content/js/wmd/wmd.js @@ -0,0 +1,2390 @@ +var Attacklab = Attacklab || {}; + +Attacklab.wmdBase = function(){ + + // A few handy aliases for readability. + var wmd = top.Attacklab; + var doc = top.document; + var re = top.RegExp; + var nav = top.navigator; + + // Some namespaces. + wmd.Util = {}; + wmd.Position = {}; + wmd.Command = {}; + wmd.Global = {}; + + var util = wmd.Util; + var position = wmd.Position; + var command = wmd.Command; + var global = wmd.Global; + + + // Used to work around some browser bugs where we can't use feature testing. + global.isIE = /msie/.test(nav.userAgent.toLowerCase()); + global.isIE_5or6 = /msie 6/.test(nav.userAgent.toLowerCase()) || /msie 5/.test(nav.userAgent.toLowerCase()); + global.isIE_7plus = global.isIE && !global.isIE_5or6; + global.isOpera = /opera/.test(nav.userAgent.toLowerCase()); + global.isKonqueror = /konqueror/.test(nav.userAgent.toLowerCase()); + + var toolbar_strong_label = "粗体 <strong> Ctrl-B"; + var toolbar_emphasis_label = "斜体 <em> Ctrl-I"; + var toolbar_hyperlink_label = "超链接 <a> Ctrl-L"; + var toolbar_blockquote_label = "引用 <blockquote> Ctrl-."; + var toolbar_code_label = "代码 <pre><code> Ctrl-K"; + var toolbar_image_label = "图片 <img> Ctrl-G"; + var toolbar_numbered_label = "数字编号列表 <ol> Ctrl-O"; + var toolbar_bulleted_label = "项目符号列表 <ul> Ctrl-U"; + var toolbar_heading_label = "标题 <h1>/<h2> Ctrl-H"; + var toolbar_horizontal_label = "水平线 <hr> Ctrl-R"; + var toolbar_undo_label = "撤销 Ctrl-Z"; + var toolbar_redo_label = "重做 Ctrl-Y"; + + // ------------------------------------------------------------------- + // YOUR CHANGES GO HERE + // + // I've tried to localize the things you are likely to change to + // this area. + // ------------------------------------------------------------------- + + // The text that appears on the upper part of the dialog box when + // entering links. + var imageDialogText = "<p style='margin-top: 0px'><b>输入图片地址</b></p><p>示例:<br />"+ + "http://www.cnprog.com/images/temp.jpg \"我的截图\"</p>"; + var linkDialogText = "<p style='margin-top: 0px'><b>输入Web地址</b></p><p>示例:<br />"+ + "http://www.cnprog.com/ \"我的网站\"</p>"; + var uploadImageHTML ="<div>或者上传本地图片:</div>" + + "<input type=\"file\" name=\"file-upload\" id=\"file-upload\" size=\"26\" "+ + "onchange=\"return ajaxFileUpload($('#image-url'));\"/><br>" + + "<img id=\"loading\" src=\"/content/images/indicator.gif\" style=\"display:none;\"/>"; + + // The default text that appears in the dialog input box when entering + // links. + var imageDefaultText = "http://"; + var linkDefaultText = "http://"; + + // The location of your button images relative to the base directory. + var imageDirectory = "images/"; + + // Some intervals in ms. These can be adjusted to reduce the control's load. + var previewPollInterval = 500; + var pastePollInterval = 100; + + // The link and title for the help button + var helpLink = "http://wmd-editor.com/"; + var helpHoverTitle = "WMD website"; + var helpTarget = "_blank"; + + // ------------------------------------------------------------------- + // END OF YOUR CHANGES + // ------------------------------------------------------------------- + + // A collection of the important regions on the page. + // Cached so we don't have to keep traversing the DOM. + wmd.PanelCollection = function(){ + this.buttonBar = doc.getElementById("wmd-button-bar"); + this.preview = doc.getElementById("previewer"); + this.output = doc.getElementById("wmd-output"); + this.input = doc.getElementById("editor"); + }; + + // This PanelCollection object can't be filled until after the page + // has loaded. + wmd.panels = undefined; + + // Internet explorer has problems with CSS sprite buttons that use HTML + // lists. When you click on the background image "button", IE will + // select the non-existent link text and discard the selection in the + // textarea. The solution to this is to cache the textarea selection + // on the button's mousedown event and set a flag. In the part of the + // code where we need to grab the selection, we check for the flag + // and, if it's set, use the cached area instead of querying the + // textarea. + // + // This ONLY affects Internet Explorer (tested on versions 6, 7 + // and 8) and ONLY on button clicks. Keyboard shortcuts work + // normally since the focus never leaves the textarea. + wmd.ieCachedRange = null; // cached textarea selection + wmd.ieRetardedClick = false; // flag + + // Returns true if the DOM element is visible, false if it's hidden. + // Checks if display is anything other than none. + util.isVisible = function (elem) { + + if (window.getComputedStyle) { + // Most browsers + return window.getComputedStyle(elem, null).getPropertyValue("display") !== "none"; + } + else if (elem.currentStyle) { + // IE + return elem.currentStyle["display"] !== "none"; + } + }; + + + // Adds a listener callback to a DOM element which is fired on a specified + // event. + util.addEvent = function(elem, event, listener){ + if (elem.attachEvent) { + // IE only. The "on" is mandatory. + elem.attachEvent("on" + event, listener); + } + else { + // Other browsers. + elem.addEventListener(event, listener, false); + } + }; + + + // Removes a listener callback from a DOM element which is fired on a specified + // event. + util.removeEvent = function(elem, event, listener){ + if (elem.detachEvent) { + // IE only. The "on" is mandatory. + elem.detachEvent("on" + event, listener); + } + else { + // Other browsers. + elem.removeEventListener(event, listener, false); + } + }; + + // Converts \r\n and \r to \n. + util.fixEolChars = function(text){ + text = text.replace(/\r\n/g, "\n"); + text = text.replace(/\r/g, "\n"); + return text; + }; + + // Extends a regular expression. Returns a new RegExp + // using pre + regex + post as the expression. + // Used in a few functions where we have a base + // expression and we want to pre- or append some + // conditions to it (e.g. adding "$" to the end). + // The flags are unchanged. + // + // regex is a RegExp, pre and post are strings. + util.extendRegExp = function(regex, pre, post){ + + if (pre === null || pre === undefined) + { + pre = ""; + } + if(post === null || post === undefined) + { + post = ""; + } + + var pattern = regex.toString(); + var flags; + + // Replace the flags with empty space and store them. + pattern = pattern.replace(/\/([gim]*)$/, ""); + flags = re.$1; + + // Remove the slash delimiters on the regular expression. + pattern = pattern.replace(/(^\/|\/$)/g, ""); + pattern = pre + pattern + post; + + return new re(pattern, flags); + } + + + // Sets the image for a button passed to the WMD editor. + // Returns a new element with the image attached. + // Adds several style properties to the image. + util.createImage = function(img){ + + var imgPath = imageDirectory + img; + + var elem = doc.createElement("img"); + elem.className = "wmd-button"; + elem.src = imgPath; + + return elem; + }; + + + // This simulates a modal dialog box and asks for the URL when you + // click the hyperlink or image buttons. + // + // text: The html for the input box. + // defaultInputText: The default value that appears in the input box. + // makeLinkMarkdown: The function which is executed when the prompt is dismissed, either via OK or Cancel + util.prompt = function(text, defaultInputText, makeLinkMarkdown){ + + // These variables need to be declared at this level since they are used + // in multiple functions. + var dialog; // The dialog box. + var background; // The background beind the dialog box. + var input; // The text box where you enter the hyperlink. + var type = 0; + // The dialog box type(0: Link, 1: Image) + if(arguments.length == 4) + type = arguments[3]; + + if (defaultInputText === undefined) { + defaultInputText = ""; + } + + // Used as a keydown event handler. Esc dismisses the prompt. + // Key code 27 is ESC. + var checkEscape = function(key){ + var code = (key.charCode || key.keyCode); + if (code === 27) { + close(true); + } + }; + + // Dismisses the hyperlink input box. + // isCancel is true if we don't care about the input text. + // isCancel is false if we are going to keep the text. + var close = function(isCancel){ + util.removeEvent(doc.body, "keydown", checkEscape); + var text = input.value; + + if (isCancel){ + text = null; + } + else{ + // Fixes common pasting errors. + text = text.replace('http://http://', 'http://'); + text = text.replace('http://https://', 'https://'); + text = text.replace('http://ftp://', 'ftp://'); + + if (text.indexOf('http://') === -1 && text.indexOf('ftp://') === -1) { + text = 'http://' + text; + } + } + + dialog.parentNode.removeChild(dialog); + background.parentNode.removeChild(background); + makeLinkMarkdown(text); + return false; + }; + + // Creates the background behind the hyperlink text entry box. + // Most of this has been moved to CSS but the div creation and + // browser-specific hacks remain here. + var createBackground = function(){ + + background = doc.createElement("div"); + background.className = "wmd-prompt-background"; + style = background.style; + style.position = "absolute"; + style.top = "0"; + + style.zIndex = "1000"; + + // Some versions of Konqueror don't support transparent colors + // so we make the whole window transparent. + // + // Is this necessary on modern konqueror browsers? + if (global.isKonqueror){ + style.backgroundColor = "transparent"; + } + else if (global.isIE){ + style.filter = "alpha(opacity=50)"; + } + else { + style.opacity = "0.5"; + } + + var pageSize = position.getPageSize(); + style.height = pageSize[1] + "px"; + + if(global.isIE){ + style.left = doc.documentElement.scrollLeft; + style.width = doc.documentElement.clientWidth; + } + else { + style.left = "0"; + style.width = "100%"; + } + + doc.body.appendChild(background); + }; + + // Create the text input box form/window. + var createDialog = function(){ + + // The main dialog box. + dialog = doc.createElement("div"); + dialog.className = "wmd-prompt-dialog"; + dialog.style.padding = "10px;"; + dialog.style.position = "fixed"; + dialog.style.width = "400px"; + dialog.style.zIndex = "1001"; + + // The dialog text. + var question = doc.createElement("div"); + question.innerHTML = text; + question.style.padding = "5px"; + dialog.appendChild(question); + + // The web form container for the text box and buttons. + var form = doc.createElement("form"); + form.onsubmit = function(){ return close(false); }; + style = form.style; + style.padding = "0"; + style.margin = "0"; + style.cssFloat = "left"; + style.width = "100%"; + style.textAlign = "center"; + style.position = "relative"; + dialog.appendChild(form); + + // The input text box + input = doc.createElement("input"); + if(type == 1) + input.id = "image-url"; + input.type = "text"; + input.value = defaultInputText; + style = input.style; + style.display = "block"; + style.width = "80%"; + style.marginLeft = style.marginRight = "auto"; + form.appendChild(input); + + // The upload file input + if(type == 1){ + var upload = doc.createElement("div"); + upload.innerHTML = uploadImageHTML; + upload.style.padding = "5px"; + form.appendChild(upload); + } + + // The ok button + var okButton = doc.createElement("input"); + okButton.type = "button"; + okButton.onclick = function(){ return close(false); }; + okButton.value = "OK"; + style = okButton.style; + style.margin = "10px"; + style.display = "inline"; + style.width = "7em"; + + + // The cancel button + var cancelButton = doc.createElement("input"); + cancelButton.type = "button"; + cancelButton.onclick = function(){ return close(true); }; + cancelButton.value = "Cancel"; + style = cancelButton.style; + style.margin = "10px"; + style.display = "inline"; + style.width = "7em"; + + // The order of these buttons is different on macs. + if (/mac/.test(nav.platform.toLowerCase())) { + form.appendChild(cancelButton); + form.appendChild(okButton); + } + else { + form.appendChild(okButton); + form.appendChild(cancelButton); + } + + util.addEvent(doc.body, "keydown", checkEscape); + dialog.style.top = "50%"; + dialog.style.left = "50%"; + dialog.style.display = "block"; + if(global.isIE_5or6){ + dialog.style.position = "absolute"; + dialog.style.top = doc.documentElement.scrollTop + 200 + "px"; + dialog.style.left = "50%"; + } + doc.body.appendChild(dialog); + + // This has to be done AFTER adding the dialog to the form if you + // want it to be centered. + dialog.style.marginTop = -(position.getHeight(dialog) / 2) + "px"; + dialog.style.marginLeft = -(position.getWidth(dialog) / 2) + "px"; + + }; + + createBackground(); + + // Why is this in a zero-length timeout? + // Is it working around a browser bug? + top.setTimeout(function(){ + + createDialog(); + + var defTextLen = defaultInputText.length; + if (input.selectionStart !== undefined) { + input.selectionStart = 0; + input.selectionEnd = defTextLen; + } + else if (input.createTextRange) { + var range = input.createTextRange(); + range.collapse(false); + range.moveStart("character", -defTextLen); + range.moveEnd("character", defTextLen); + range.select(); + } + + input.focus(); + }, 0); + }; + + + // UNFINISHED + // The assignment in the while loop makes jslint cranky. + // I'll change it to a better loop later. + position.getTop = function(elem, isInner){ + var result = elem.offsetTop; + if (!isInner) { + while (elem = elem.offsetParent) { + result += elem.offsetTop; + } + } + return result; + }; + + position.getHeight = function (elem) { + return elem.offsetHeight || elem.scrollHeight; + }; + + position.getWidth = function (elem) { + return elem.offsetWidth || elem.scrollWidth; + }; + + position.getPageSize = function(){ + + var scrollWidth, scrollHeight; + var innerWidth, innerHeight; + + // It's not very clear which blocks work with which browsers. + if(self.innerHeight && self.scrollMaxY){ + scrollWidth = doc.body.scrollWidth; + scrollHeight = self.innerHeight + self.scrollMaxY; + } + else if(doc.body.scrollHeight > doc.body.offsetHeight){ + scrollWidth = doc.body.scrollWidth; + scrollHeight = doc.body.scrollHeight; + } + else{ + scrollWidth = doc.body.offsetWidth; + scrollHeight = doc.body.offsetHeight; + } + + if(self.innerHeight){ + // Non-IE browser + innerWidth = self.innerWidth; + innerHeight = self.innerHeight; + } + else if(doc.documentElement && doc.documentElement.clientHeight){ + // Some versions of IE (IE 6 w/ a DOCTYPE declaration) + innerWidth = doc.documentElement.clientWidth; + innerHeight = doc.documentElement.clientHeight; + } + else if(doc.body){ + // Other versions of IE + innerWidth = doc.body.clientWidth; + innerHeight = doc.body.clientHeight; + } + + var maxWidth = Math.max(scrollWidth, innerWidth); + var maxHeight = Math.max(scrollHeight, innerHeight); + return [maxWidth, maxHeight, innerWidth, innerHeight]; + }; + + // Watches the input textarea, polling at an interval and runs + // a callback function if anything has changed. + wmd.inputPoller = function(callback, interval){ + + var pollerObj = this; + var inputArea = wmd.panels.input; + + // Stored start, end and text. Used to see if there are changes to the input. + var lastStart; + var lastEnd; + var markdown; + + var killHandle; // Used to cancel monitoring on destruction. + // Checks to see if anything has changed in the textarea. + // If so, it runs the callback. + this.tick = function(){ + + if (!util.isVisible(inputArea)) { + return; + } + + // Update the selection start and end, text. + if (inputArea.selectionStart || inputArea.selectionStart === 0) { + var start = inputArea.selectionStart; + var end = inputArea.selectionEnd; + if (start != lastStart || end != lastEnd) { + lastStart = start; + lastEnd = end; + + if (markdown != inputArea.value) { + markdown = inputArea.value; + return true; + } + } + } + return false; + }; + + + var doTickCallback = function(){ + + if (!util.isVisible(inputArea)) { + return; + } + + // If anything has changed, call the function. + if (pollerObj.tick()) { + callback(); + } + }; + + // Set how often we poll the textarea for changes. + var assignInterval = function(){ + // previewPollInterval is set at the top of the namespace. + killHandle = top.setInterval(doTickCallback, interval); + }; + + this.destroy = function(){ + top.clearInterval(killHandle); + }; + + assignInterval(); + }; + + // Handles pushing and popping TextareaStates for undo/redo commands. + // I should rename the stack variables to list. + wmd.undoManager = function(callback){ + + var undoObj = this; + var undoStack = []; // A stack of undo states + var stackPtr = 0; // The index of the current state + var mode = "none"; + var lastState; // The last state + var poller; + var timer; // The setTimeout handle for cancelling the timer + var inputStateObj; + + // Set the mode for later logic steps. + var setMode = function(newMode, noSave){ + + if (mode != newMode) { + mode = newMode; + if (!noSave) { + saveState(); + } + } + + if (!global.isIE || mode != "moving") { + timer = top.setTimeout(refreshState, 1); + } + else { + inputStateObj = null; + } + }; + + var refreshState = function(){ + inputStateObj = new wmd.TextareaState(); + poller.tick(); + timer = undefined; + }; + + this.setCommandMode = function(){ + mode = "command"; + saveState(); + timer = top.setTimeout(refreshState, 0); + }; + + this.canUndo = function(){ + return stackPtr > 1; + }; + + this.canRedo = function(){ + if (undoStack[stackPtr + 1]) { + return true; + } + return false; + }; + + // Removes the last state and restores it. + this.undo = function(){ + + if (undoObj.canUndo()) { + if (lastState) { + // What about setting state -1 to null or checking for undefined? + lastState.restore(); + lastState = null; + } + else { + undoStack[stackPtr] = new wmd.TextareaState(); + undoStack[--stackPtr].restore(); + + if (callback) { + callback(); + } + } + } + + mode = "none"; + wmd.panels.input.focus(); + refreshState(); + }; + + // Redo an action. + this.redo = function(){ + + if (undoObj.canRedo()) { + + undoStack[++stackPtr].restore(); + + if (callback) { + callback(); + } + } + + mode = "none"; + wmd.panels.input.focus(); + refreshState(); + }; + + // Push the input area state to the stack. + var saveState = function(){ + + var currState = inputStateObj || new wmd.TextareaState(); + + if (!currState) { + return false; + } + if (mode == "moving") { + if (!lastState) { + lastState = currState; + } + return; + } + if (lastState) { + if (undoStack[stackPtr - 1].text != lastState.text) { + undoStack[stackPtr++] = lastState; + } + lastState = null; + } + undoStack[stackPtr++] = currState; + undoStack[stackPtr + 1] = null; + if (callback) { + callback(); + } + }; + + var handleCtrlYZ = function(event){ + + var handled = false; + + if (event.ctrlKey || event.metaKey) { + + // IE and Opera do not support charCode. + var keyCode = event.charCode || event.keyCode; + var keyCodeChar = String.fromCharCode(keyCode); + + switch (keyCodeChar) { + + case "y": + undoObj.redo(); + handled = true; + break; + + case "z": + if (!event.shiftKey) { + undoObj.undo(); + } + else { + undoObj.redo(); + } + handled = true; + break; + } + } + + if (handled) { + if (event.preventDefault) { + event.preventDefault(); + } + if (top.event) { + top.event.returnValue = false; + } + return; + } + }; + + // Set the mode depending on what is going on in the input area. + var handleModeChange = function(event){ + + if (!event.ctrlKey && !event.metaKey) { + + var keyCode = event.keyCode; + + if ((keyCode >= 33 && keyCode <= 40) || (keyCode >= 63232 && keyCode <= 63235)) { + // 33 - 40: page up/dn and arrow keys + // 63232 - 63235: page up/dn and arrow keys on safari + setMode("moving"); + } + else if (keyCode == 8 || keyCode == 46 || keyCode == 127) { + // 8: backspace + // 46: delete + // 127: delete + setMode("deleting"); + } + else if (keyCode == 13) { + // 13: Enter + setMode("newlines"); + } + else if (keyCode == 27) { + // 27: escape + setMode("escape"); + } + else if ((keyCode < 16 || keyCode > 20) && keyCode != 91) { + // 16-20 are shift, etc. + // 91: left window key + // I think this might be a little messed up since there are + // a lot of nonprinting keys above 20. + setMode("typing"); + } + } + }; + + var setEventHandlers = function(){ + + util.addEvent(wmd.panels.input, "keypress", function(event){ + // keyCode 89: y + // keyCode 90: z + if ((event.ctrlKey || event.metaKey) && (event.keyCode == 89 || event.keyCode == 90)) { + event.preventDefault(); + } + }); + + var handlePaste = function(){ + if (global.isIE || (inputStateObj && inputStateObj.text != wmd.panels.input.value)) { + if (timer == undefined) { + mode = "paste"; + saveState(); + refreshState(); + } + } + }; + + // pastePollInterval is specified at the beginning of this namespace. + poller = new wmd.inputPoller(handlePaste, pastePollInterval); + + util.addEvent(wmd.panels.input, "keydown", handleCtrlYZ); + util.addEvent(wmd.panels.input, "keydown", handleModeChange); + + util.addEvent(wmd.panels.input, "mousedown", function(){ + setMode("moving"); + }); + wmd.panels.input.onpaste = handlePaste; + wmd.panels.input.ondrop = handlePaste; + }; + + var init = function(){ + setEventHandlers(); + refreshState(); + saveState(); + }; + + this.destroy = function(){ + if (poller) { + poller.destroy(); + } + }; + + init(); + }; + + // I think my understanding of how the buttons and callbacks are stored in the array is incomplete. + wmd.editor = function(previewRefreshCallback){ + + if (!previewRefreshCallback) { + previewRefreshCallback = function(){}; + } + + var inputBox = wmd.panels.input; + + var offsetHeight = 0; + + var editObj = this; + + var mainDiv; + var mainSpan; + + var div; // This name is pretty ambiguous. I should rename this. + + // Used to cancel recurring events from setInterval. + var creationHandle; + + var undoMgr; // The undo manager + + // Perform the button's action. + var doClick = function(button){ + + inputBox.focus(); + + if (button.textOp) { + + if (undoMgr) { + undoMgr.setCommandMode(); + } + + var state = new wmd.TextareaState(); + + if (!state) { + return; + } + + var chunks = state.getChunks(); + + // Some commands launch a "modal" prompt dialog. Javascript + // can't really make a modal dialog box and the WMD code + // will continue to execute while the dialog is displayed. + // This prevents the dialog pattern I'm used to and means + // I can't do something like this: + // + // var link = CreateLinkDialog(); + // makeMarkdownLink(link); + // + // Instead of this straightforward method of handling a + // dialog I have to pass any code which would execute + // after the dialog is dismissed (e.g. link creation) + // in a function parameter. + // + // Yes this is awkward and I think it sucks, but there's + // no real workaround. Only the image and link code + // create dialogs and require the function pointers. + var fixupInputArea = function(){ + + inputBox.focus(); + + if (chunks) { + state.setChunks(chunks); + } + + state.restore(); + previewRefreshCallback(); + }; + + var noCleanup = button.textOp(chunks, fixupInputArea); + + if(!noCleanup) { + fixupInputArea(); + } + + } + + if (button.execute) { + button.execute(editObj); + } + }; + + var setUndoRedoButtonStates = function(){ + if(undoMgr){ + setupButton(document.getElementById("wmd-undo-button"), undoMgr.canUndo()); + setupButton(document.getElementById("wmd-redo-button"), undoMgr.canRedo()); + } + }; + + var setupButton = function(button, isEnabled) { + + var normalYShift = "0px"; + var disabledYShift = "-20px"; + var highlightYShift = "-40px"; + + if(isEnabled) { + button.style.backgroundPosition = button.XShift + " " + normalYShift; + button.onmouseover = function(){ + this.style.backgroundPosition = this.XShift + " " + highlightYShift; + }; + + button.onmouseout = function(){ + this.style.backgroundPosition = this.XShift + " " + normalYShift; + }; + + // IE tries to select the background image "button" text (it's + // implemented in a list item) so we have to cache the selection + // on mousedown. + if(global.isIE) { + button.onmousedown = function() { + wmd.ieRetardedClick = true; + wmd.ieCachedRange = document.selection.createRange(); + }; + } + + if (!button.isHelp) + { + button.onclick = function() { + if (this.onmouseout) { + this.onmouseout(); + } + doClick(this); + return false; + } + } + } + else { + button.style.backgroundPosition = button.XShift + " " + disabledYShift; + button.onmouseover = button.onmouseout = button.onclick = function(){}; + } + } + + var makeSpritedButtonRow = function(){ + + var buttonBar = document.getElementById("wmd-button-bar"); + var normalYShift = "0px"; + var disabledYShift = "-20px"; + var highlightYShift = "-40px"; + + var buttonRow = document.createElement("ul"); + buttonRow.id = "wmd-button-row"; + buttonRow = buttonBar.appendChild(buttonRow); + + + var boldButton = document.createElement("li"); + boldButton.className = "wmd-button"; + boldButton.id = "wmd-bold-button"; + boldButton.title = toolbar_strong_label; + boldButton.XShift = "0px"; + boldButton.textOp = command.doBold; + setupButton(boldButton, true); + buttonRow.appendChild(boldButton); + + var italicButton = document.createElement("li"); + italicButton.className = "wmd-button"; + italicButton.id = "wmd-italic-button"; + italicButton.title = toolbar_emphasis_label; + italicButton.XShift = "-20px"; + italicButton.textOp = command.doItalic; + setupButton(italicButton, true); + buttonRow.appendChild(italicButton); + + var spacer1 = document.createElement("li"); + spacer1.className = "wmd-spacer"; + spacer1.id = "wmd-spacer1"; + buttonRow.appendChild(spacer1); + + var linkButton = document.createElement("li"); + linkButton.className = "wmd-button"; + linkButton.id = "wmd-link-button"; + linkButton.title = toolbar_hyperlink_label; + linkButton.XShift = "-40px"; + linkButton.textOp = function(chunk, postProcessing){ + return command.doLinkOrImage(chunk, postProcessing, false); + }; + setupButton(linkButton, true); + buttonRow.appendChild(linkButton); + + var quoteButton = document.createElement("li"); + quoteButton.className = "wmd-button"; + quoteButton.id = "wmd-quote-button"; + quoteButton.title = toolbar_blockquote_label; + quoteButton.XShift = "-60px"; + quoteButton.textOp = command.doBlockquote; + setupButton(quoteButton, true); + buttonRow.appendChild(quoteButton); + + var codeButton = document.createElement("li"); + codeButton.className = "wmd-button"; + codeButton.id = "wmd-code-button"; + codeButton.title = toolbar_code_label; + codeButton.XShift = "-80px"; + codeButton.textOp = command.doCode; + setupButton(codeButton, true); + buttonRow.appendChild(codeButton); + + var imageButton = document.createElement("li"); + imageButton.className = "wmd-button"; + imageButton.id = "wmd-image-button"; + imageButton.title = toolbar_image_label; + imageButton.XShift = "-100px"; + imageButton.textOp = function(chunk, postProcessing){ + return command.doLinkOrImage(chunk, postProcessing, true); + }; + setupButton(imageButton, true); + buttonRow.appendChild(imageButton); + + var spacer2 = document.createElement("li"); + spacer2.className = "wmd-spacer"; + spacer2.id = "wmd-spacer2"; + buttonRow.appendChild(spacer2); + + var olistButton = document.createElement("li"); + olistButton.className = "wmd-button"; + olistButton.id = "wmd-olist-button"; + olistButton.title = toolbar_numbered_label; + olistButton.XShift = "-120px"; + olistButton.textOp = function(chunk, postProcessing){ + command.doList(chunk, postProcessing, true); + }; + setupButton(olistButton, true); + buttonRow.appendChild(olistButton); + + var ulistButton = document.createElement("li"); + ulistButton.className = "wmd-button"; + ulistButton.id = "wmd-ulist-button"; + ulistButton.title = toolbar_bulleted_label; + ulistButton.XShift = "-140px"; + ulistButton.textOp = function(chunk, postProcessing){ + command.doList(chunk, postProcessing, false); + }; + setupButton(ulistButton, true); + buttonRow.appendChild(ulistButton); + + var headingButton = document.createElement("li"); + headingButton.className = "wmd-button"; + headingButton.id = "wmd-heading-button"; + headingButton.title = toolbar_heading_label; + headingButton.XShift = "-160px"; + headingButton.textOp = command.doHeading; + setupButton(headingButton, true); + buttonRow.appendChild(headingButton); + + var hrButton = document.createElement("li"); + hrButton.className = "wmd-button"; + hrButton.id = "wmd-hr-button"; + hrButton.title = toolbar_horizontal_label; + hrButton.XShift = "-180px"; + hrButton.textOp = command.doHorizontalRule; + setupButton(hrButton, true); + buttonRow.appendChild(hrButton); + + var spacer3 = document.createElement("li"); + spacer3.className = "wmd-spacer"; + spacer3.id = "wmd-spacer3"; + buttonRow.appendChild(spacer3); + + var undoButton = document.createElement("li"); + undoButton.className = "wmd-button"; + undoButton.id = "wmd-undo-button"; + undoButton.title = toolbar_undo_label; + undoButton.XShift = "-200px"; + undoButton.execute = function(manager){ + manager.undo(); + }; + setupButton(undoButton, true); + buttonRow.appendChild(undoButton); + + var redoButton = document.createElement("li"); + redoButton.className = "wmd-button"; + redoButton.id = "wmd-redo-button"; + redoButton.title = toolbar_redo_label; + if (/win/.test(nav.platform.toLowerCase())) { + redoButton.title = toolbar_redo_label; + } + else { + // mac and other non-Windows platforms + redoButton.title = "重做 - Ctrl+Shift+Z"; + } + redoButton.XShift = "-220px"; + redoButton.execute = function(manager){ + manager.redo(); + }; + setupButton(redoButton, true); + buttonRow.appendChild(redoButton); + /* + var helpButton = document.createElement("li"); + helpButton.className = "wmd-button"; + helpButton.id = "wmd-help-button"; + helpButton.XShift = "-240px"; + helpButton.isHelp = true; + + var helpAnchor = document.createElement("a"); + helpAnchor.href = helpLink; + helpAnchor.target = helpTarget + helpAnchor.title = helpHoverTitle; + helpButton.appendChild(helpAnchor); + + setupButton(helpButton, true); + buttonRow.appendChild(helpButton); + */ + setUndoRedoButtonStates(); + } + + var setupEditor = function(){ + + if (/\?noundo/.test(doc.location.href)) { + wmd.nativeUndo = true; + } + + if (!wmd.nativeUndo) { + undoMgr = new wmd.undoManager(function(){ + previewRefreshCallback(); + setUndoRedoButtonStates(); + }); + } + + makeSpritedButtonRow(); + + + var keyEvent = "keydown"; + if (global.isOpera) { + keyEvent = "keypress"; + } + + util.addEvent(inputBox, keyEvent, function(key){ + + // Check to see if we have a button key and, if so execute the callback. + if (key.ctrlKey || key.metaKey) { + + var keyCode = key.charCode || key.keyCode; + var keyCodeStr = String.fromCharCode(keyCode).toLowerCase(); + + // Bugfix for messed up DEL and . + if (keyCode === 46) { + keyCodeStr = ""; + } + if (keyCode === 190) { + keyCodeStr = "."; + } + + switch(keyCodeStr) { + case "b": + doClick(document.getElementById("wmd-bold-button")); + break; + case "i": + doClick(document.getElementById("wmd-italic-button")); + break; + case "l": + doClick(document.getElementById("wmd-link-button")); + break; + case ".": + doClick(document.getElementById("wmd-quote-button")); + break; + case "k": + doClick(document.getElementById("wmd-code-button")); + break; + case "g": + doClick(document.getElementById("wmd-image-button")); + break; + case "o": + doClick(document.getElementById("wmd-olist-button")); + break; + case "u": + doClick(document.getElementById("wmd-ulist-button")); + break; + case "h": + doClick(document.getElementById("wmd-heading-button")); + break; + case "r": + doClick(document.getElementById("wmd-hr-button")); + break; + case "y": + doClick(document.getElementById("wmd-redo-button")); + break; + case "z": + if(key.shiftKey) { + doClick(document.getElementById("wmd-redo-button")); + } + else { + doClick(document.getElementById("wmd-undo-button")); + } + break; + default: + return; + } + + + if (key.preventDefault) { + key.preventDefault(); + } + + if (top.event) { + top.event.returnValue = false; + } + } + }); + + // Auto-indent on shift-enter + util.addEvent(inputBox, "keyup", function(key){ + if (key.shiftKey && !key.ctrlKey && !key.metaKey) { + var keyCode = key.charCode || key.keyCode; + // Character 13 is Enter + if (keyCode === 13) { + fakeButton = {}; + fakeButton.textOp = command.doAutoindent; + doClick(fakeButton); + } + } + }); + + if (inputBox.form) { + var submitCallback = inputBox.form.onsubmit; + inputBox.form.onsubmit = function(){ + convertToHtml(); + if (submitCallback) { + return submitCallback.apply(this, arguments); + } + }; + } + }; + + // Convert the contents of the input textarea to HTML in the output/preview panels. + var convertToHtml = function(){ + + if (wmd.showdown) { + var markdownConverter = new wmd.showdown.converter(); + } + var text = inputBox.value; + + var callback = function(){ + inputBox.value = text; + }; + + if (!/markdown/.test(wmd.wmd_env.output.toLowerCase())) { + if (markdownConverter) { + inputBox.value = markdownConverter.makeHtml(text); + top.setTimeout(callback, 0); + } + } + return true; + }; + + + this.undo = function(){ + if (undoMgr) { + undoMgr.undo(); + } + }; + + this.redo = function(){ + if (undoMgr) { + undoMgr.redo(); + } + }; + + // This is pretty useless. The setupEditor function contents + // should just be copied here. + var init = function(){ + setupEditor(); + }; + + this.destroy = function(){ + if (undoMgr) { + undoMgr.destroy(); + } + if (div.parentNode) { + div.parentNode.removeChild(div); + } + if (inputBox) { + inputBox.style.marginTop = ""; + } + top.clearInterval(creationHandle); + }; + + init(); + }; + + // The input textarea state/contents. + // This is used to implement undo/redo by the undo manager. + wmd.TextareaState = function(){ + + // Aliases + var stateObj = this; + var inputArea = wmd.panels.input; + + this.init = function() { + + if (!util.isVisible(inputArea)) { + return; + } + + this.setInputAreaSelectionStartEnd(); + this.scrollTop = inputArea.scrollTop; + if (!this.text && inputArea.selectionStart || inputArea.selectionStart === 0) { + this.text = inputArea.value; + } + + } + + // Sets the selected text in the input box after we've performed an + // operation. + this.setInputAreaSelection = function(){ + + if (!util.isVisible(inputArea)) { + return; + } + + if (inputArea.selectionStart !== undefined && !global.isOpera) { + + inputArea.focus(); + inputArea.selectionStart = stateObj.start; + inputArea.selectionEnd = stateObj.end; + inputArea.scrollTop = stateObj.scrollTop; + } + else if (doc.selection) { + + if (doc.activeElement && doc.activeElement !== inputArea) { + return; + } + + inputArea.focus(); + var range = inputArea.createTextRange(); + range.moveStart("character", -inputArea.value.length); + range.moveEnd("character", -inputArea.value.length); + range.moveEnd("character", stateObj.end); + range.moveStart("character", stateObj.start); + range.select(); + } + }; + + this.setInputAreaSelectionStartEnd = function(){ + + if (inputArea.selectionStart || inputArea.selectionStart === 0) { + + stateObj.start = inputArea.selectionStart; + stateObj.end = inputArea.selectionEnd; + } + else if (doc.selection) { + + stateObj.text = util.fixEolChars(inputArea.value); + + // IE loses the selection in the textarea when buttons are + // clicked. On IE we cache the selection and set a flag + // which we check for here. + var range; + if(wmd.ieRetardedClick && wmd.ieCachedRange) { + range = wmd.ieCachedRange; + wmd.ieRetardedClick = false; + } + else { + range = doc.selection.createRange(); + } + + var fixedRange = util.fixEolChars(range.text); + var marker = "\x07"; + var markedRange = marker + fixedRange + marker; + range.text = markedRange; + var inputText = util.fixEolChars(inputArea.value); + + range.moveStart("character", -markedRange.length); + range.text = fixedRange; + + stateObj.start = inputText.indexOf(marker); + stateObj.end = inputText.lastIndexOf(marker) - marker.length; + + var len = stateObj.text.length - util.fixEolChars(inputArea.value).length; + + if (len) { + range.moveStart("character", -fixedRange.length); + while (len--) { + fixedRange += "\n"; + stateObj.end += 1; + } + range.text = fixedRange; + } + + this.setInputAreaSelection(); + } + }; + + // Restore this state into the input area. + this.restore = function(){ + + if (stateObj.text != undefined && stateObj.text != inputArea.value) { + inputArea.value = stateObj.text; + } + this.setInputAreaSelection(); + inputArea.scrollTop = stateObj.scrollTop; + }; + + // Gets a collection of HTML chunks from the inptut textarea. + this.getChunks = function(){ + + var chunk = new wmd.Chunks(); + + chunk.before = util.fixEolChars(stateObj.text.substring(0, stateObj.start)); + chunk.startTag = ""; + chunk.selection = util.fixEolChars(stateObj.text.substring(stateObj.start, stateObj.end)); + chunk.endTag = ""; + chunk.after = util.fixEolChars(stateObj.text.substring(stateObj.end)); + chunk.scrollTop = stateObj.scrollTop; + + return chunk; + }; + + // Sets the TextareaState properties given a chunk of markdown. + this.setChunks = function(chunk){ + + chunk.before = chunk.before + chunk.startTag; + chunk.after = chunk.endTag + chunk.after; + + if (global.isOpera) { + chunk.before = chunk.before.replace(/\n/g, "\r\n"); + chunk.selection = chunk.selection.replace(/\n/g, "\r\n"); + chunk.after = chunk.after.replace(/\n/g, "\r\n"); + } + + this.start = chunk.before.length; + this.end = chunk.before.length + chunk.selection.length; + this.text = chunk.before + chunk.selection + chunk.after; + this.scrollTop = chunk.scrollTop; + }; + + this.init(); + }; + + // before: contains all the text in the input box BEFORE the selection. + // after: contains all the text in the input box AFTER the selection. + wmd.Chunks = function(){ + }; + + // startRegex: a regular expression to find the start tag + // endRegex: a regular expresssion to find the end tag + wmd.Chunks.prototype.findTags = function(startRegex, endRegex){ + + var chunkObj = this; + var regex; + + if (startRegex) { + + regex = util.extendRegExp(startRegex, "", "$"); + + this.before = this.before.replace(regex, + function(match){ + chunkObj.startTag = chunkObj.startTag + match; + return ""; + }); + + regex = util.extendRegExp(startRegex, "^", ""); + + this.selection = this.selection.replace(regex, + function(match){ + chunkObj.startTag = chunkObj.startTag + match; + return ""; + }); + } + + if (endRegex) { + + regex = util.extendRegExp(endRegex, "", "$"); + + this.selection = this.selection.replace(regex, + function(match){ + chunkObj.endTag = match + chunkObj.endTag; + return ""; + }); + + regex = util.extendRegExp(endRegex, "^", ""); + + this.after = this.after.replace(regex, + function(match){ + chunkObj.endTag = match + chunkObj.endTag; + return ""; + }); + } + }; + + // If remove is false, the whitespace is transferred + // to the before/after regions. + // + // If remove is true, the whitespace disappears. + wmd.Chunks.prototype.trimWhitespace = function(remove){ + + this.selection = this.selection.replace(/^(\s*)/, ""); + + if (!remove) { + this.before += re.$1; + } + + this.selection = this.selection.replace(/(\s*)$/, ""); + + if (!remove) { + this.after = re.$1 + this.after; + } + }; + + + wmd.Chunks.prototype.skipLines = function(nLinesBefore, nLinesAfter, findExtraNewlines){ + + if (nLinesBefore === undefined) { + nLinesBefore = 1; + } + + if (nLinesAfter === undefined) { + nLinesAfter = 1; + } + + nLinesBefore++; + nLinesAfter++; + + var regexText; + var replacementText; + + this.selection = this.selection.replace(/(^\n*)/, ""); + this.startTag = this.startTag + re.$1; + this.selection = this.selection.replace(/(\n*$)/, ""); + this.endTag = this.endTag + re.$1; + this.startTag = this.startTag.replace(/(^\n*)/, ""); + this.before = this.before + re.$1; + this.endTag = this.endTag.replace(/(\n*$)/, ""); + this.after = this.after + re.$1; + + if (this.before) { + + regexText = replacementText = ""; + + while (nLinesBefore--) { + regexText += "\\n?"; + replacementText += "\n"; + } + + if (findExtraNewlines) { + regexText = "\\n*"; + } + this.before = this.before.replace(new re(regexText + "$", ""), replacementText); + } + + if (this.after) { + + regexText = replacementText = ""; + + while (nLinesAfter--) { + regexText += "\\n?"; + replacementText += "\n"; + } + if (findExtraNewlines) { + regexText = "\\n*"; + } + + this.after = this.after.replace(new re(regexText, ""), replacementText); + } + }; + + // The markdown symbols - 4 spaces = code, > = blockquote, etc. + command.prefixes = "(?:\\s{4,}|\\s*>|\\s*-\\s+|\\s*\\d+\\.|=|\\+|-|_|\\*|#|\\s*\\[[^\n]]+\\]:)"; + + // Remove markdown symbols from the chunk selection. + command.unwrap = function(chunk){ + var txt = new re("([^\\n])\\n(?!(\\n|" + command.prefixes + "))", "g"); + chunk.selection = chunk.selection.replace(txt, "$1 $2"); + }; + + command.wrap = function(chunk, len){ + command.unwrap(chunk); + var regex = new re("(.{1," + len + "})( +|$\\n?)", "gm"); + + chunk.selection = chunk.selection.replace(regex, function(line, marked){ + if (new re("^" + command.prefixes, "").test(line)) { + return line; + } + return marked + "\n"; + }); + + chunk.selection = chunk.selection.replace(/\s+$/, ""); + }; + + command.doBold = function(chunk, postProcessing){ + return command.doBorI(chunk, postProcessing, 2, "strong text"); + }; + + command.doItalic = function(chunk, postProcessing){ + return command.doBorI(chunk, postProcessing, 1, "emphasized text"); + }; + + // chunk: The selected region that will be enclosed with */** + // nStars: 1 for italics, 2 for bold + // insertText: If you just click the button without highlighting text, this gets inserted + command.doBorI = function(chunk, postProcessing, nStars, insertText){ + + // Get rid of whitespace and fixup newlines. + chunk.trimWhitespace(); + chunk.selection = chunk.selection.replace(/\n{2,}/g, "\n"); + + // Look for stars before and after. Is the chunk already marked up? + chunk.before.search(/(\**$)/); + var starsBefore = re.$1; + + chunk.after.search(/(^\**)/); + var starsAfter = re.$1; + + var prevStars = Math.min(starsBefore.length, starsAfter.length); + + // Remove stars if we have to since the button acts as a toggle. + if ((prevStars >= nStars) && (prevStars != 2 || nStars != 1)) { + chunk.before = chunk.before.replace(re("[*]{" + nStars + "}$", ""), ""); + chunk.after = chunk.after.replace(re("^[*]{" + nStars + "}", ""), ""); + } + else if (!chunk.selection && starsAfter) { + // It's not really clear why this code is necessary. It just moves + // some arbitrary stuff around. + chunk.after = chunk.after.replace(/^([*_]*)/, ""); + chunk.before = chunk.before.replace(/(\s?)$/, ""); + var whitespace = re.$1; + chunk.before = chunk.before + starsAfter + whitespace; + } + else { + + // In most cases, if you don't have any selected text and click the button + // you'll get a selected, marked up region with the default text inserted. + if (!chunk.selection && !starsAfter) { + chunk.selection = insertText; + } + + // Add the true markup. + var markup = nStars <= 1 ? "*" : "**"; // shouldn't the test be = ? + chunk.before = chunk.before + markup; + chunk.after = markup + chunk.after; + } + + return; + }; + + command.stripLinkDefs = function(text, defsToAdd){ + + text = text.replace(/^[ ]{0,3}\[(\d+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|$)/gm, + function(totalMatch, id, link, newlines, title){ + defsToAdd[id] = totalMatch.replace(/\s*$/, ""); + if (newlines) { + // Strip the title and return that separately. + defsToAdd[id] = totalMatch.replace(/["(](.+?)[")]$/, ""); + return newlines + title; + } + return ""; + }); + + return text; + }; + + command.addLinkDef = function(chunk, linkDef){ + + var refNumber = 0; // The current reference number + var defsToAdd = {}; // + // Start with a clean slate by removing all previous link definitions. + chunk.before = command.stripLinkDefs(chunk.before, defsToAdd); + chunk.selection = command.stripLinkDefs(chunk.selection, defsToAdd); + chunk.after = command.stripLinkDefs(chunk.after, defsToAdd); + + var defs = ""; + var regex = /(\[(?:\[[^\]]*\]|[^\[\]])*\][ ]?(?:\n[ ]*)?\[)(\d+)(\])/g; + + var addDefNumber = function(def){ + refNumber++; + def = def.replace(/^[ ]{0,3}\[(\d+)\]:/, " [" + refNumber + "]:"); + defs += "\n" + def; + }; + + var getLink = function(wholeMatch, link, id, end){ + + if (defsToAdd[id]) { + addDefNumber(defsToAdd[id]); + return link + refNumber + end; + + } + return wholeMatch; + }; + + chunk.before = chunk.before.replace(regex, getLink); + + if (linkDef) { + addDefNumber(linkDef); + } + else { + chunk.selection = chunk.selection.replace(regex, getLink); + } + + var refOut = refNumber; + + chunk.after = chunk.after.replace(regex, getLink); + + if (chunk.after) { + chunk.after = chunk.after.replace(/\n*$/, ""); + } + if (!chunk.after) { + chunk.selection = chunk.selection.replace(/\n*$/, ""); + } + + chunk.after += "\n\n" + defs; + + return refOut; + }; + + command.doLinkOrImage = function(chunk, postProcessing, isImage){ + + chunk.trimWhitespace(); + chunk.findTags(/\s*!?\[/, /\][ ]?(?:\n[ ]*)?(\[.*?\])?/); + + if (chunk.endTag.length > 1) { + + chunk.startTag = chunk.startTag.replace(/!?\[/, ""); + chunk.endTag = ""; + command.addLinkDef(chunk, null); + + } + else { + + if (/\n\n/.test(chunk.selection)) { + command.addLinkDef(chunk, null); + return; + } + + // The function to be executed when you enter a link and press OK or Cancel. + // Marks up the link and adds the ref. + var makeLinkMarkdown = function(link){ + + if (link !== null) { + + chunk.startTag = chunk.endTag = ""; + var linkDef = " [999]: " + link; + + var num = command.addLinkDef(chunk, linkDef); + chunk.startTag = isImage ? "![" : "["; + chunk.endTag = "][" + num + "]"; + + if (!chunk.selection) { + if (isImage) { + chunk.selection = "alt text"; + } + else { + chunk.selection = "link text"; + } + } + } + postProcessing(); + }; + + if (isImage) { + // add forth param to identify image window + util.prompt(imageDialogText, imageDefaultText, makeLinkMarkdown, 1); + } + else { + util.prompt(linkDialogText, linkDefaultText, makeLinkMarkdown); + } + return true; + } + }; + + util.makeAPI = function(){ + wmd.wmd = {}; + wmd.wmd.editor = wmd.editor; + wmd.wmd.previewManager = wmd.previewManager; + }; + + util.startEditor = function(){ + + if (wmd.wmd_env.autostart === false) { + util.makeAPI(); + return; + } + + var edit; // The editor (buttons + input + outputs) - the main object. + var previewMgr; // The preview manager. + + // Fired after the page has fully loaded. + var loadListener = function(){ + + wmd.panels = new wmd.PanelCollection(); + + previewMgr = new wmd.previewManager(); + var previewRefreshCallback = previewMgr.refresh; + + edit = new wmd.editor(previewRefreshCallback); + + previewMgr.refresh(true); + + }; + + util.addEvent(top, "load", loadListener); + }; + + wmd.previewManager = function(){ + + var managerObj = this; + var converter; + var poller; + var timeout; + var elapsedTime; + var oldInputText; + var htmlOut; + var maxDelay = 3000; + var startType = "delayed"; // The other legal value is "manual" + + // Adds event listeners to elements and creates the input poller. + var setupEvents = function(inputElem, listener){ + + util.addEvent(inputElem, "input", listener); + inputElem.onpaste = listener; + inputElem.ondrop = listener; + + util.addEvent(inputElem, "keypress", listener); + util.addEvent(inputElem, "keydown", listener); + // previewPollInterval is set at the top of this file. + poller = new wmd.inputPoller(listener, previewPollInterval); + }; + + var getDocScrollTop = function(){ + + var result = 0; + + if (top.innerHeight) { + result = top.pageYOffset; + } + else + if (doc.documentElement && doc.documentElement.scrollTop) { + result = doc.documentElement.scrollTop; + } + else + if (doc.body) { + result = doc.body.scrollTop; + } + + return result; + }; + + var makePreviewHtml = function(){ + + // If there are no registered preview and output panels + // there is nothing to do. + if (!wmd.panels.preview && !wmd.panels.output) { + return; + } + + var text = wmd.panels.input.value; + if (text && text == oldInputText) { + return; // Input text hasn't changed. + } + else { + oldInputText = text; + } + + var prevTime = new Date().getTime(); + + if (!converter && wmd.showdown) { + converter = new wmd.showdown.converter(); + } + + if (converter) { + text = converter.makeHtml(text); + } + + // Calculate the processing time of the HTML creation. + // It's used as the delay time in the event listener. + var currTime = new Date().getTime(); + elapsedTime = currTime - prevTime; + + pushPreviewHtml(text); + htmlOut = text; + }; + + // setTimeout is already used. Used as an event listener. + var applyTimeout = function(){ + + if (timeout) { + top.clearTimeout(timeout); + timeout = undefined; + } + + if (startType !== "manual") { + + var delay = 0; + + if (startType === "delayed") { + delay = elapsedTime; + } + + if (delay > maxDelay) { + delay = maxDelay; + } + timeout = top.setTimeout(makePreviewHtml, delay); + } + }; + + var getScaleFactor = function(panel){ + if (panel.scrollHeight <= panel.clientHeight) { + return 1; + } + return panel.scrollTop / (panel.scrollHeight - panel.clientHeight); + }; + + var setPanelScrollTops = function(){ + + if (wmd.panels.preview) { + wmd.panels.preview.scrollTop = (wmd.panels.preview.scrollHeight - wmd.panels.preview.clientHeight) * getScaleFactor(wmd.panels.preview); + ; + } + + if (wmd.panels.output) { + wmd.panels.output.scrollTop = (wmd.panels.output.scrollHeight - wmd.panels.output.clientHeight) * getScaleFactor(wmd.panels.output); + ; + } + }; + + this.refresh = function(requiresRefresh){ + + if (requiresRefresh) { + oldInputText = ""; + makePreviewHtml(); + } + else { + applyTimeout(); + } + }; + + this.processingTime = function(){ + return elapsedTime; + }; + + // The output HTML + this.output = function(){ + return htmlOut; + }; + + // The mode can be "manual" or "delayed" + this.setUpdateMode = function(mode){ + startType = mode; + managerObj.refresh(); + }; + + var isFirstTimeFilled = true; + + var pushPreviewHtml = function(text){ + + var emptyTop = position.getTop(wmd.panels.input) - getDocScrollTop(); + + // Send the encoded HTML to the output textarea/div. + if (wmd.panels.output) { + // The value property is only defined if the output is a textarea. + if (wmd.panels.output.value !== undefined) { + wmd.panels.output.value = text; + wmd.panels.output.readOnly = true; + } + // Otherwise we are just replacing the text in a div. + // Send the HTML wrapped in <pre><code> + else { + var newText = text.replace(/&/g, "&"); + newText = newText.replace(/</g, "<"); + wmd.panels.output.innerHTML = "<pre><code>" + newText + "</code></pre>"; + } + } + + if (wmd.panels.preview) { + wmd.panels.preview.innerHTML = text; + } + + setPanelScrollTops(); + + if (isFirstTimeFilled) { + isFirstTimeFilled = false; + return; + } + + var fullTop = position.getTop(wmd.panels.input) - getDocScrollTop(); + + if (global.isIE) { + top.setTimeout(function(){ + top.scrollBy(0, fullTop - emptyTop); + }, 0); + } + else { + top.scrollBy(0, fullTop - emptyTop); + } + }; + + var init = function(){ + + setupEvents(wmd.panels.input, applyTimeout); + makePreviewHtml(); + + if (wmd.panels.preview) { + wmd.panels.preview.scrollTop = 0; + } + if (wmd.panels.output) { + wmd.panels.output.scrollTop = 0; + } + }; + + this.destroy = function(){ + if (poller) { + poller.destroy(); + } + }; + + init(); + }; + + // When making a list, hitting shift-enter will put your cursor on the next line + // at the current indent level. + command.doAutoindent = function(chunk, postProcessing){ + + chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]*\n$/, "\n\n"); + chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}>[ \t]*\n$/, "\n\n"); + chunk.before = chunk.before.replace(/(\n|^)[ \t]+\n$/, "\n\n"); + + if(/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]+.*\n$/.test(chunk.before)){ + if(command.doList){ + command.doList(chunk); + } + } + if(/(\n|^)[ ]{0,3}>[ \t]+.*\n$/.test(chunk.before)){ + if(command.doBlockquote){ + command.doBlockquote(chunk); + } + } + if(/(\n|^)(\t|[ ]{4,}).*\n$/.test(chunk.before)){ + if(command.doCode){ + command.doCode(chunk); + } + } + }; + + command.doBlockquote = function(chunk, postProcessing){ + + chunk.selection = chunk.selection.replace(/^(\n*)([^\r]+?)(\n*)$/, + function(totalMatch, newlinesBefore, text, newlinesAfter){ + chunk.before += newlinesBefore; + chunk.after = newlinesAfter + chunk.after; + return text; + }); + + chunk.before = chunk.before.replace(/(>[ \t]*)$/, + function(totalMatch, blankLine){ + chunk.selection = blankLine + chunk.selection; + return ""; + }); + + chunk.selection = chunk.selection.replace(/^(\s|>)+$/ ,""); + chunk.selection = chunk.selection || "Blockquote"; + + if(chunk.before){ + chunk.before = chunk.before.replace(/\n?$/,"\n"); + } + if(chunk.after){ + chunk.after = chunk.after.replace(/^\n?/,"\n"); + } + + chunk.before = chunk.before.replace(/(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*$)/, + function(totalMatch){ + chunk.startTag = totalMatch; + return ""; + }); + + chunk.after = chunk.after.replace(/^(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*)/, + function(totalMatch){ + chunk.endTag = totalMatch; + return ""; + }); + + var replaceBlanksInTags = function(useBracket){ + + var replacement = useBracket ? "> " : ""; + + if(chunk.startTag){ + chunk.startTag = chunk.startTag.replace(/\n((>|\s)*)\n$/, + function(totalMatch, markdown){ + return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n"; + }); + } + if(chunk.endTag){ + chunk.endTag = chunk.endTag.replace(/^\n((>|\s)*)\n/, + function(totalMatch, markdown){ + return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n"; + }); + } + }; + + if(/^(?![ ]{0,3}>)/m.test(chunk.selection)){ + command.wrap(chunk, wmd.wmd_env.lineLength - 2); + chunk.selection = chunk.selection.replace(/^/gm, "> "); + replaceBlanksInTags(true); + chunk.skipLines(); + } + else{ + chunk.selection = chunk.selection.replace(/^[ ]{0,3}> ?/gm, ""); + command.unwrap(chunk); + replaceBlanksInTags(false); + + if(!/^(\n|^)[ ]{0,3}>/.test(chunk.selection) && chunk.startTag){ + chunk.startTag = chunk.startTag.replace(/\n{0,2}$/, "\n\n"); + } + + if(!/(\n|^)[ ]{0,3}>.*$/.test(chunk.selection) && chunk.endTag){ + chunk.endTag=chunk.endTag.replace(/^\n{0,2}/, "\n\n"); + } + } + + if(!/\n/.test(chunk.selection)){ + chunk.selection = chunk.selection.replace(/^(> *)/, + function(wholeMatch, blanks){ + chunk.startTag += blanks; + return ""; + }); + } + }; + + command.doCode = function(chunk, postProcessing){ + + var hasTextBefore = /\S[ ]*$/.test(chunk.before); + var hasTextAfter = /^[ ]*\S/.test(chunk.after); + + // Use 'four space' markdown if the selection is on its own + // line or is multiline. + if((!hasTextAfter && !hasTextBefore) || /\n/.test(chunk.selection)){ + + chunk.before = chunk.before.replace(/[ ]{4}$/, + function(totalMatch){ + chunk.selection = totalMatch + chunk.selection; + return ""; + }); + + var nLinesBack = 1; + var nLinesForward = 1; + + if(/\n(\t|[ ]{4,}).*\n$/.test(chunk.before)){ + nLinesBack = 0; + } + if(/^\n(\t|[ ]{4,})/.test(chunk.after)){ + nLinesForward = 0; + } + + chunk.skipLines(nLinesBack, nLinesForward); + + if(!chunk.selection){ + chunk.startTag = " "; + chunk.selection = "enter code here"; + } + else { + if(/^[ ]{0,3}\S/m.test(chunk.selection)){ + chunk.selection = chunk.selection.replace(/^/gm, " "); + } + else{ + chunk.selection = chunk.selection.replace(/^[ ]{4}/gm, ""); + } + } + } + else{ + // Use backticks (`) to delimit the code block. + + chunk.trimWhitespace(); + chunk.findTags(/`/, /`/); + + if(!chunk.startTag && !chunk.endTag){ + chunk.startTag = chunk.endTag="`"; + if(!chunk.selection){ + chunk.selection = "enter code here"; + } + } + else if(chunk.endTag && !chunk.startTag){ + chunk.before += chunk.endTag; + chunk.endTag = ""; + } + else{ + chunk.startTag = chunk.endTag=""; + } + } + }; + + command.doList = function(chunk, postProcessing, isNumberedList){ + + // These are identical except at the very beginning and end. + // Should probably use the regex extension function to make this clearer. + var previousItemsRegex = /(\n|^)(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*$/; + var nextItemsRegex = /^\n*(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*/; + + // The default bullet is a dash but others are possible. + // This has nothing to do with the particular HTML bullet, + // it's just a markdown bullet. + var bullet = "-"; + + // The number in a numbered list. + var num = 1; + + // Get the item prefix - e.g. " 1. " for a numbered list, " - " for a bulleted list. + var getItemPrefix = function(){ + var prefix; + if(isNumberedList){ + prefix = " " + num + ". "; + num++; + } + else{ + prefix = " " + bullet + " "; + } + return prefix; + }; + + // Fixes the prefixes of the other list items. + var getPrefixedItem = function(itemText){ + + // The numbering flag is unset when called by autoindent. + if(isNumberedList === undefined){ + isNumberedList = /^\s*\d/.test(itemText); + } + + // Renumber/bullet the list element. + itemText = itemText.replace(/^[ ]{0,3}([*+-]|\d+[.])\s/gm, + function( _ ){ + return getItemPrefix(); + }); + + return itemText; + }; + + chunk.findTags(/(\n|^)*[ ]{0,3}([*+-]|\d+[.])\s+/, null); + + if(chunk.before && !/\n$/.test(chunk.before) && !/^\n/.test(chunk.startTag)){ + chunk.before += chunk.startTag; + chunk.startTag = ""; + } + + if(chunk.startTag){ + + var hasDigits = /\d+[.]/.test(chunk.startTag); + chunk.startTag = ""; + chunk.selection = chunk.selection.replace(/\n[ ]{4}/g, "\n"); + command.unwrap(chunk); + chunk.skipLines(); + + if(hasDigits){ + // Have to renumber the bullet points if this is a numbered list. + chunk.after = chunk.after.replace(nextItemsRegex, getPrefixedItem); + } + if(isNumberedList == hasDigits){ + return; + } + } + + var nLinesUp = 1; + + chunk.before = chunk.before.replace(previousItemsRegex, + function(itemText){ + if(/^\s*([*+-])/.test(itemText)){ + bullet = re.$1; + } + nLinesUp = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0; + return getPrefixedItem(itemText); + }); + + if(!chunk.selection){ + chunk.selection = "List item"; + } + + var prefix = getItemPrefix(); + + var nLinesDown = 1; + + chunk.after = chunk.after.replace(nextItemsRegex, + function(itemText){ + nLinesDown = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0; + return getPrefixedItem(itemText); + }); + + chunk.trimWhitespace(true); + chunk.skipLines(nLinesUp, nLinesDown, true); + chunk.startTag = prefix; + var spaces = prefix.replace(/./g, " "); + command.wrap(chunk, wmd.wmd_env.lineLength - spaces.length); + chunk.selection = chunk.selection.replace(/\n/g, "\n" + spaces); + + }; + + command.doHeading = function(chunk, postProcessing){ + + // Remove leading/trailing whitespace and reduce internal spaces to single spaces. + chunk.selection = chunk.selection.replace(/\s+/g, " "); + chunk.selection = chunk.selection.replace(/(^\s+|\s+$)/g, ""); + + // If we clicked the button with no selected text, we just + // make a level 2 hash header around some default text. + if(!chunk.selection){ + chunk.startTag = "## "; + chunk.selection = "Heading"; + chunk.endTag = " ##"; + return; + } + + var headerLevel = 0; // The existing header level of the selected text. + + // Remove any existing hash heading markdown and save the header level. + chunk.findTags(/#+[ ]*/, /[ ]*#+/); + if(/#+/.test(chunk.startTag)){ + headerLevel = re.lastMatch.length; + } + chunk.startTag = chunk.endTag = ""; + + // Try to get the current header level by looking for - and = in the line + // below the selection. + chunk.findTags(null, /\s?(-+|=+)/); + if(/=+/.test(chunk.endTag)){ + headerLevel = 1; + } + if(/-+/.test(chunk.endTag)){ + headerLevel = 2; + } + + // Skip to the next line so we can create the header markdown. + chunk.startTag = chunk.endTag = ""; + chunk.skipLines(1, 1); + + // We make a level 2 header if there is no current header. + // If there is a header level, we substract one from the header level. + // If it's already a level 1 header, it's removed. + var headerLevelToCreate = headerLevel == 0 ? 2 : headerLevel - 1; + + if(headerLevelToCreate > 0){ + + // The button only creates level 1 and 2 underline headers. + // Why not have it iterate over hash header levels? Wouldn't that be easier and cleaner? + var headerChar = headerLevelToCreate >= 2 ? "-" : "="; + var len = chunk.selection.length; + if(len > wmd.wmd_env.lineLength){ + len = wmd.wmd_env.lineLength; + } + chunk.endTag = "\n"; + while(len--){ + chunk.endTag += headerChar; + } + } + }; + + command.doHorizontalRule = function(chunk, postProcessing){ + chunk.startTag = "----------\n"; + chunk.selection = ""; + chunk.skipLines(2, 1, true); + } +}; + + +Attacklab.wmd_env = {}; +Attacklab.account_options = {}; +Attacklab.wmd_defaults = {version:1, output:"Markdown", lineLength:40, delayLoad:false}; + +if(!Attacklab.wmd) +{ + Attacklab.wmd = function() + { + Attacklab.loadEnv = function() + { + var mergeEnv = function(env) + { + if(!env) + { + return; + } + + for(var key in env) + { + Attacklab.wmd_env[key] = env[key]; + } + }; + + mergeEnv(Attacklab.wmd_defaults); + mergeEnv(Attacklab.account_options); + mergeEnv(top["wmd_options"]); + Attacklab.full = true; + + var defaultButtons = "bold italic link blockquote code image ol ul heading hr"; + Attacklab.wmd_env.buttons = Attacklab.wmd_env.buttons || defaultButtons; + }; + Attacklab.loadEnv(); + + }; + + Attacklab.wmd(); + Attacklab.wmdBase(); + Attacklab.Util.startEditor(); +}; + |