rc-web@42: // rc-web@42: // showdown.js -- A javascript port of Markdown. rc-web@42: // rc-web@42: // Copyright (c) 2007 John Fraser. rc-web@42: // rc-web@42: // Original Markdown Copyright (c) 2004-2005 John Gruber rc-web@42: // rc-web@42: // rc-web@42: // Redistributable under a BSD-style open source license. rc-web@42: // See license.txt for more information. rc-web@42: // rc-web@42: // The full source distribution is at: rc-web@42: // rc-web@42: // A A L rc-web@42: // T C A rc-web@42: // T K B rc-web@42: // rc-web@42: // rc-web@42: // rc-web@42: rc-web@42: // rc-web@42: // Wherever possible, Showdown is a straight, line-by-line port rc-web@42: // of the Perl version of Markdown. rc-web@42: // rc-web@42: // This is not a normal parser design; it's basically just a rc-web@42: // series of string substitutions. It's hard to read and rc-web@42: // maintain this way, but keeping Showdown close to the original rc-web@42: // design makes it easier to port new features. rc-web@42: // rc-web@42: // More importantly, Showdown behaves like markdown.pl in most rc-web@42: // edge cases. So web applications can do client-side preview rc-web@42: // in Javascript, and then build identical HTML on the server. rc-web@42: // rc-web@42: // This port needs the new RegExp functionality of ECMA 262, rc-web@42: // 3rd Edition (i.e. Javascript 1.5). Most modern web browsers rc-web@42: // should do fine. Even with the new regular expression features, rc-web@42: // We do a lot of work to emulate Perl's regex functionality. rc-web@42: // The tricky changes in this file mostly have the "attacklab:" rc-web@42: // label. Major or self-explanatory changes don't. rc-web@42: // rc-web@42: // Smart diff tools like Araxis Merge will be able to match up rc-web@42: // this file with markdown.pl in a useful way. A little tweaking rc-web@42: // helps: in a copy of markdown.pl, replace "#" with "//" and rc-web@42: // replace "$text" with "text". Be sure to ignore whitespace rc-web@42: // and line endings. rc-web@42: // rc-web@42: rc-web@42: rc-web@42: // rc-web@42: // Showdown usage: rc-web@42: // rc-web@42: // var text = "Markdown *rocks*."; rc-web@42: // rc-web@42: // var converter = new Showdown.converter(); rc-web@42: // var html = converter.makeHtml(text); rc-web@42: // rc-web@42: // alert(html); rc-web@42: // rc-web@42: // Note: move the sample code to the bottom of this rc-web@42: // file before uncommenting it. rc-web@42: // rc-web@42: rc-web@42: rc-web@42: // rc-web@42: // Showdown namespace rc-web@42: // rc-web@42: var Showdown = {}; rc-web@42: rc-web@42: // rc-web@42: // converter rc-web@42: // rc-web@42: // Wraps all "globals" so that the only thing rc-web@42: // exposed is makeHtml(). rc-web@42: // rc-web@42: Showdown.converter = function() { rc-web@42: rc-web@42: // rc-web@42: // Globals: rc-web@42: // rc-web@42: rc-web@42: // Global hashes, used by various utility routines rc-web@42: var g_urls; rc-web@42: var g_titles; rc-web@42: var g_html_blocks; rc-web@42: rc-web@42: // Used to track when we're inside an ordered or unordered list rc-web@42: // (see _ProcessListItems() for details): rc-web@42: var g_list_level = 0; rc-web@42: rc-web@42: rc-web@42: this.makeHtml = function(text) { rc-web@42: // rc-web@42: // Main function. The order in which other subs are called here is rc-web@42: // essential. Link and image substitutions need to happen before rc-web@42: // _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the rc-web@42: // and tags get encoded. rc-web@42: // rc-web@42: rc-web@42: // Clear the global hashes. If we don't clear these, you get conflicts rc-web@42: // from other articles when generating a page which contains more than rc-web@42: // one article (e.g. an index page that shows the N most recent rc-web@42: // articles): rc-web@42: g_urls = new Array(); rc-web@42: g_titles = new Array(); rc-web@42: g_html_blocks = new Array(); rc-web@42: rc-web@42: // attacklab: Replace ~ with ~T rc-web@42: // This lets us use tilde as an escape char to avoid md5 hashes rc-web@42: // The choice of character is arbitray; anything that isn't rc-web@42: // magic in Markdown will work. rc-web@42: text = text.replace(/~/g,"~T"); rc-web@42: rc-web@42: // attacklab: Replace $ with ~D rc-web@42: // RegExp interprets $ as a special character rc-web@42: // when it's in a replacement string rc-web@42: text = text.replace(/\$/g,"~D"); rc-web@42: rc-web@42: // Standardize line endings rc-web@42: text = text.replace(/\r\n/g,"\n"); // DOS to Unix rc-web@42: text = text.replace(/\r/g,"\n"); // Mac to Unix rc-web@42: rc-web@42: // Make sure text begins and ends with a couple of newlines: rc-web@42: text = "\n\n" + text + "\n\n"; rc-web@42: rc-web@42: // Convert all tabs to spaces. rc-web@42: text = _Detab(text); rc-web@42: rc-web@42: // Strip any lines consisting only of spaces and tabs. rc-web@42: // This makes subsequent regexen easier to write, because we can rc-web@42: // match consecutive blank lines with /\n+/ instead of something rc-web@42: // contorted like /[ \t]*\n+/ . rc-web@42: text = text.replace(/^[ \t]+$/mg,""); rc-web@42: rc-web@42: // Handle github codeblocks prior to running HashHTML so that rc-web@42: // HTML contained within the codeblock gets escaped propertly rc-web@42: text = _DoGithubCodeBlocks(text); rc-web@42: rc-web@42: // Turn block-level HTML blocks into hash entries rc-web@42: text = _HashHTMLBlocks(text); rc-web@42: rc-web@42: // Strip link definitions, store in hashes. rc-web@42: text = _StripLinkDefinitions(text); rc-web@42: rc-web@42: text = _RunBlockGamut(text); rc-web@42: rc-web@42: text = _UnescapeSpecialChars(text); rc-web@42: rc-web@42: // attacklab: Restore dollar signs rc-web@42: text = text.replace(/~D/g,"$$"); rc-web@42: rc-web@42: // attacklab: Restore tildes rc-web@42: text = text.replace(/~T/g,"~"); rc-web@42: rc-web@42: return text; rc-web@42: }; rc-web@42: rc-web@42: rc-web@42: var _StripLinkDefinitions = function(text) { rc-web@42: // rc-web@42: // Strips link definitions from text, stores the URLs and titles in rc-web@42: // hash references. rc-web@42: // rc-web@42: rc-web@42: // Link defs are in the form: ^[id]: url "optional title" rc-web@42: rc-web@42: /* rc-web@42: var text = text.replace(/ rc-web@42: ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1 rc-web@42: [ \t]* rc-web@42: \n? // maybe *one* newline rc-web@42: [ \t]* rc-web@42: ? // url = $2 rc-web@42: [ \t]* rc-web@42: \n? // maybe one newline rc-web@42: [ \t]* rc-web@42: (?: rc-web@42: (\n*) // any lines skipped = $3 attacklab: lookbehind removed rc-web@42: ["(] rc-web@42: (.+?) // title = $4 rc-web@42: [")] rc-web@42: [ \t]* rc-web@42: )? // title is optional rc-web@42: (?:\n+|$) rc-web@42: /gm, rc-web@42: function(){...}); rc-web@42: */ rc-web@42: var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm, rc-web@42: function (wholeMatch,m1,m2,m3,m4) { rc-web@42: m1 = m1.toLowerCase(); rc-web@42: g_urls[m1] = _EncodeAmpsAndAngles(m2); // Link IDs are case-insensitive rc-web@42: if (m3) { rc-web@42: // Oops, found blank lines, so it's not a title. rc-web@42: // Put back the parenthetical statement we stole. rc-web@42: return m3+m4; rc-web@42: } else if (m4) { rc-web@42: g_titles[m1] = m4.replace(/"/g,"""); rc-web@42: } rc-web@42: rc-web@42: // Completely remove the definition from the text rc-web@42: return ""; rc-web@42: } rc-web@42: ); rc-web@42: rc-web@42: return text; rc-web@42: } rc-web@42: rc-web@42: rc-web@42: var _HashHTMLBlocks = function(text) { rc-web@42: // attacklab: Double up blank lines to reduce lookaround rc-web@42: text = text.replace(/\n/g,"\n\n"); rc-web@42: rc-web@42: // Hashify HTML blocks: rc-web@42: // We only want to do this for block-level HTML tags, such as headers, rc-web@42: // lists, and tables. That's because we still want to wrap

s around rc-web@42: // "paragraphs" that are wrapped in non-block-level tags, such as anchors, rc-web@42: // phrase emphasis, and spans. The list of tags we're looking for is rc-web@42: // hard-coded: rc-web@42: var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del|style|section|header|footer|nav|article|aside"; rc-web@42: var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|style|section|header|footer|nav|article|aside"; rc-web@42: rc-web@42: // First, look for nested blocks, e.g.: rc-web@42: //

rc-web@42: //
rc-web@42: // tags for inner block must be indented. rc-web@42: //
rc-web@42: //
rc-web@42: // rc-web@42: // The outermost tags must start at the left margin for this to match, and rc-web@42: // the inner nested divs must be indented. rc-web@42: // We need to do this before the next, more liberal match, because the next rc-web@42: // match will start at the first `
` and stop at the first `
`. rc-web@42: rc-web@42: // attacklab: This regex can be expensive when it fails. rc-web@42: /* rc-web@42: var text = text.replace(/ rc-web@42: ( // save in $1 rc-web@42: ^ // start of line (with /m) rc-web@42: <($block_tags_a) // start tag = $2 rc-web@42: \b // word break rc-web@42: // attacklab: hack around khtml/pcre bug... rc-web@42: [^\r]*?\n // any number of lines, minimally matching rc-web@42: // the matching end tag rc-web@42: [ \t]* // trailing spaces/tabs rc-web@42: (?=\n+) // followed by a newline rc-web@42: ) // attacklab: there are sentinel newlines at end of document rc-web@42: /gm,function(){...}}; rc-web@42: */ rc-web@42: 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); rc-web@42: rc-web@42: // rc-web@42: // Now match more liberally, simply from `\n` to `\n` rc-web@42: // rc-web@42: rc-web@42: /* rc-web@42: var text = text.replace(/ rc-web@42: ( // save in $1 rc-web@42: ^ // start of line (with /m) rc-web@42: <($block_tags_b) // start tag = $2 rc-web@42: \b // word break rc-web@42: // attacklab: hack around khtml/pcre bug... rc-web@42: [^\r]*? // any number of lines, minimally matching rc-web@42: .* // the matching end tag rc-web@42: [ \t]* // trailing spaces/tabs rc-web@42: (?=\n+) // followed by a newline rc-web@42: ) // attacklab: there are sentinel newlines at end of document rc-web@42: /gm,function(){...}}; rc-web@42: */ rc-web@42: text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|style|section|header|footer|nav|article|aside)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,hashElement); rc-web@42: rc-web@42: // Special case just for
. It was easier to make a special case than rc-web@42: // to make the other regex more complicated. rc-web@42: rc-web@42: /* rc-web@42: text = text.replace(/ rc-web@42: ( // save in $1 rc-web@42: \n\n // Starting after a blank line rc-web@42: [ ]{0,3} rc-web@42: (<(hr) // start tag = $2 rc-web@42: \b // word break rc-web@42: ([^<>])*? // rc-web@42: \/?>) // the matching end tag rc-web@42: [ \t]* rc-web@42: (?=\n{2,}) // followed by a blank line rc-web@42: ) rc-web@42: /g,hashElement); rc-web@42: */ rc-web@42: text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement); rc-web@42: rc-web@42: // Special case for standalone HTML comments: rc-web@42: rc-web@42: /* rc-web@42: text = text.replace(/ rc-web@42: ( // save in $1 rc-web@42: \n\n // Starting after a blank line rc-web@42: [ ]{0,3} // attacklab: g_tab_width - 1 rc-web@42: rc-web@42: [ \t]* rc-web@42: (?=\n{2,}) // followed by a blank line rc-web@42: ) rc-web@42: /g,hashElement); rc-web@42: */ rc-web@42: text = text.replace(/(\n\n[ ]{0,3}[ \t]*(?=\n{2,}))/g,hashElement); rc-web@42: rc-web@42: // PHP and ASP-style processor instructions ( and <%...%>) rc-web@42: rc-web@42: /* rc-web@42: text = text.replace(/ rc-web@42: (?: rc-web@42: \n\n // Starting after a blank line rc-web@42: ) rc-web@42: ( // save in $1 rc-web@42: [ ]{0,3} // attacklab: g_tab_width - 1 rc-web@42: (?: rc-web@42: <([?%]) // $2 rc-web@42: [^\r]*? rc-web@42: \2> rc-web@42: ) rc-web@42: [ \t]* rc-web@42: (?=\n{2,}) // followed by a blank line rc-web@42: ) rc-web@42: /g,hashElement); rc-web@42: */ rc-web@42: text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement); rc-web@42: rc-web@42: // attacklab: Undo double lines (see comment at top of this function) rc-web@42: text = text.replace(/\n\n/g,"\n"); rc-web@42: return text; rc-web@42: } rc-web@42: rc-web@42: var hashElement = function(wholeMatch,m1) { rc-web@42: var blockText = m1; rc-web@42: rc-web@42: // Undo double lines rc-web@42: blockText = blockText.replace(/\n\n/g,"\n"); rc-web@42: blockText = blockText.replace(/^\n/,""); rc-web@42: rc-web@42: // strip trailing blank lines rc-web@42: blockText = blockText.replace(/\n+$/g,""); rc-web@42: rc-web@42: // Replace the element text with a marker ("~KxK" where x is its key) rc-web@42: blockText = "\n\n~K" + (g_html_blocks.push(blockText)-1) + "K\n\n"; rc-web@42: rc-web@42: return blockText; rc-web@42: }; rc-web@42: rc-web@42: var _RunBlockGamut = function(text) { rc-web@42: // rc-web@42: // These are all the transformations that form block-level rc-web@42: // tags like paragraphs, headers, and list items. rc-web@42: // rc-web@42: text = _DoHeaders(text); rc-web@42: rc-web@42: // Do Horizontal Rules: rc-web@42: var key = hashBlock("
"); rc-web@42: text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key); rc-web@42: text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key); rc-web@42: text = text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key); rc-web@42: rc-web@42: text = _DoLists(text); rc-web@42: text = _DoCodeBlocks(text); rc-web@42: text = _DoBlockQuotes(text); rc-web@42: rc-web@42: // We already ran _HashHTMLBlocks() before, in Markdown(), but that rc-web@42: // was to escape raw HTML in the original Markdown source. This time, rc-web@42: // we're escaping the markup we've just created, so that we don't wrap rc-web@42: //

tags around block-level tags. rc-web@42: text = _HashHTMLBlocks(text); rc-web@42: text = _FormParagraphs(text); rc-web@42: rc-web@42: return text; rc-web@42: }; rc-web@42: rc-web@42: rc-web@42: var _RunSpanGamut = function(text) { rc-web@42: // rc-web@42: // These are all the transformations that occur *within* block-level rc-web@42: // tags like paragraphs, headers, and list items. rc-web@42: // rc-web@42: rc-web@42: text = _DoCodeSpans(text); rc-web@42: text = _EscapeSpecialCharsWithinTagAttributes(text); rc-web@42: text = _EncodeBackslashEscapes(text); rc-web@42: rc-web@42: // Process anchor and image tags. Images must come first, rc-web@42: // because ![foo][f] looks like an anchor. rc-web@42: text = _DoImages(text); rc-web@42: text = _DoAnchors(text); rc-web@42: rc-web@42: // Make links out of things like `` rc-web@42: // Must come after _DoAnchors(), because you can use < and > rc-web@42: // delimiters in inline links like [this](). rc-web@42: text = _DoAutoLinks(text); rc-web@42: text = _EncodeAmpsAndAngles(text); rc-web@42: text = _DoItalicsAndBold(text); rc-web@42: rc-web@42: // Do hard breaks: rc-web@42: text = text.replace(/ +\n/g,"
\n"); rc-web@42: rc-web@42: return text; rc-web@42: } rc-web@42: rc-web@42: var _EscapeSpecialCharsWithinTagAttributes = function(text) { rc-web@42: // rc-web@42: // Within tags -- meaning between < and > -- encode [\ ` * _] so they rc-web@42: // don't conflict with their use in Markdown for code, italics and strong. rc-web@42: // rc-web@42: rc-web@42: // Build a regex to find HTML tags and comments. See Friedl's rc-web@42: // "Mastering Regular Expressions", 2nd Ed., pp. 200-201. rc-web@42: var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi; rc-web@42: rc-web@42: text = text.replace(regex, function(wholeMatch) { rc-web@42: var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`"); rc-web@42: tag = escapeCharacters(tag,"\\`*_"); rc-web@42: return tag; rc-web@42: }); rc-web@42: rc-web@42: return text; rc-web@42: } rc-web@42: rc-web@42: var _DoAnchors = function(text) { rc-web@42: // rc-web@42: // Turn Markdown link shortcuts into XHTML
tags. rc-web@42: // rc-web@42: // rc-web@42: // First, handle reference-style links: [link text] [id] rc-web@42: // rc-web@42: rc-web@42: /* rc-web@42: text = text.replace(/ rc-web@42: ( // wrap whole match in $1 rc-web@42: \[ rc-web@42: ( rc-web@42: (?: rc-web@42: \[[^\]]*\] // allow brackets nested one level rc-web@42: | rc-web@42: [^\[] // or anything else rc-web@42: )* rc-web@42: ) rc-web@42: \] rc-web@42: rc-web@42: [ ]? // one optional space rc-web@42: (?:\n[ ]*)? // one optional newline followed by spaces rc-web@42: rc-web@42: \[ rc-web@42: (.*?) // id = $3 rc-web@42: \] rc-web@42: )()()()() // pad remaining backreferences rc-web@42: /g,_DoAnchors_callback); rc-web@42: */ rc-web@42: text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag); rc-web@42: rc-web@42: // rc-web@42: // Next, inline-style links: [link text](url "optional title") rc-web@42: // rc-web@42: rc-web@42: /* rc-web@42: text = text.replace(/ rc-web@42: ( // wrap whole match in $1 rc-web@42: \[ rc-web@42: ( rc-web@42: (?: rc-web@42: \[[^\]]*\] // allow brackets nested one level rc-web@42: | rc-web@42: [^\[\]] // or anything else rc-web@42: ) rc-web@42: ) rc-web@42: \] rc-web@42: \( // literal paren rc-web@42: [ \t]* rc-web@42: () // no id, so leave $3 empty rc-web@42: ? // href = $4 rc-web@42: [ \t]* rc-web@42: ( // $5 rc-web@42: (['"]) // quote char = $6 rc-web@42: (.*?) // Title = $7 rc-web@42: \6 // matching quote rc-web@42: [ \t]* // ignore any spaces/tabs between closing quote and ) rc-web@42: )? // title is optional rc-web@42: \) rc-web@42: ) rc-web@42: /g,writeAnchorTag); rc-web@42: */ rc-web@42: text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag); rc-web@42: rc-web@42: // rc-web@42: // Last, handle reference-style shortcuts: [link text] rc-web@42: // These must come last in case you've also got [link test][1] rc-web@42: // or [link test](/foo) rc-web@42: // rc-web@42: rc-web@42: /* rc-web@42: text = text.replace(/ rc-web@42: ( // wrap whole match in $1 rc-web@42: \[ rc-web@42: ([^\[\]]+) // link text = $2; can't contain '[' or ']' rc-web@42: \] rc-web@42: )()()()()() // pad rest of backreferences rc-web@42: /g, writeAnchorTag); rc-web@42: */ rc-web@42: text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag); rc-web@42: rc-web@42: return text; rc-web@42: } rc-web@42: rc-web@42: var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) { rc-web@42: if (m7 == undefined) m7 = ""; rc-web@42: var whole_match = m1; rc-web@42: var link_text = m2; rc-web@42: var link_id = m3.toLowerCase(); rc-web@42: var url = m4; rc-web@42: var title = m7; rc-web@42: rc-web@42: if (url == "") { rc-web@42: if (link_id == "") { rc-web@42: // lower-case and turn embedded newlines into spaces rc-web@42: link_id = link_text.toLowerCase().replace(/ ?\n/g," "); rc-web@42: } rc-web@42: url = "#"+link_id; rc-web@42: rc-web@42: if (g_urls[link_id] != undefined) { rc-web@42: url = g_urls[link_id]; rc-web@42: if (g_titles[link_id] != undefined) { rc-web@42: title = g_titles[link_id]; rc-web@42: } rc-web@42: } rc-web@42: else { rc-web@42: if (whole_match.search(/\(\s*\)$/m)>-1) { rc-web@42: // Special case for explicit empty url rc-web@42: url = ""; rc-web@42: } else { rc-web@42: return whole_match; rc-web@42: } rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: url = escapeCharacters(url,"*_"); rc-web@42: var result = ""; rc-web@42: rc-web@42: return result; rc-web@42: } rc-web@42: rc-web@42: rc-web@42: var _DoImages = function(text) { rc-web@42: // rc-web@42: // Turn Markdown image shortcuts into tags. rc-web@42: // rc-web@42: rc-web@42: // rc-web@42: // First, handle reference-style labeled images: ![alt text][id] rc-web@42: // rc-web@42: rc-web@42: /* rc-web@42: text = text.replace(/ rc-web@42: ( // wrap whole match in $1 rc-web@42: !\[ rc-web@42: (.*?) // alt text = $2 rc-web@42: \] rc-web@42: rc-web@42: [ ]? // one optional space rc-web@42: (?:\n[ ]*)? // one optional newline followed by spaces rc-web@42: rc-web@42: \[ rc-web@42: (.*?) // id = $3 rc-web@42: \] rc-web@42: )()()()() // pad rest of backreferences rc-web@42: /g,writeImageTag); rc-web@42: */ rc-web@42: text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag); rc-web@42: rc-web@42: // rc-web@42: // Next, handle inline images: ![alt text](url "optional title") rc-web@42: // Don't forget: encode * and _ rc-web@42: rc-web@42: /* rc-web@42: text = text.replace(/ rc-web@42: ( // wrap whole match in $1 rc-web@42: !\[ rc-web@42: (.*?) // alt text = $2 rc-web@42: \] rc-web@42: \s? // One optional whitespace character rc-web@42: \( // literal paren rc-web@42: [ \t]* rc-web@42: () // no id, so leave $3 empty rc-web@42: ? // src url = $4 rc-web@42: [ \t]* rc-web@42: ( // $5 rc-web@42: (['"]) // quote char = $6 rc-web@42: (.*?) // title = $7 rc-web@42: \6 // matching quote rc-web@42: [ \t]* rc-web@42: )? // title is optional rc-web@42: \) rc-web@42: ) rc-web@42: /g,writeImageTag); rc-web@42: */ rc-web@42: text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag); rc-web@42: rc-web@42: return text; rc-web@42: } rc-web@42: rc-web@42: var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) { rc-web@42: var whole_match = m1; rc-web@42: var alt_text = m2; rc-web@42: var link_id = m3.toLowerCase(); rc-web@42: var url = m4; rc-web@42: var title = m7; rc-web@42: rc-web@42: if (!title) title = ""; rc-web@42: rc-web@42: if (url == "") { rc-web@42: if (link_id == "") { rc-web@42: // lower-case and turn embedded newlines into spaces rc-web@42: link_id = alt_text.toLowerCase().replace(/ ?\n/g," "); rc-web@42: } rc-web@42: url = "#"+link_id; rc-web@42: rc-web@42: if (g_urls[link_id] != undefined) { rc-web@42: url = g_urls[link_id]; rc-web@42: if (g_titles[link_id] != undefined) { rc-web@42: title = g_titles[link_id]; rc-web@42: } rc-web@42: } rc-web@42: else { rc-web@42: return whole_match; rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: alt_text = alt_text.replace(/"/g,"""); rc-web@42: url = escapeCharacters(url,"*_"); rc-web@42: var result = "\""' + _RunSpanGamut(m1) + "");}); rc-web@42: rc-web@42: text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, rc-web@42: function(matchFound,m1){return hashBlock('

' + _RunSpanGamut(m1) + "

");}); rc-web@42: rc-web@42: // atx-style headers: rc-web@42: // # Header 1 rc-web@42: // ## Header 2 rc-web@42: // ## Header 2 with closing hashes ## rc-web@42: // ... rc-web@42: // ###### Header 6 rc-web@42: // rc-web@42: rc-web@42: /* rc-web@42: text = text.replace(/ rc-web@42: ^(\#{1,6}) // $1 = string of #'s rc-web@42: [ \t]* rc-web@42: (.+?) // $2 = Header text rc-web@42: [ \t]* rc-web@42: \#* // optional closing #'s (not counted) rc-web@42: \n+ rc-web@42: /gm, function() {...}); rc-web@42: */ rc-web@42: rc-web@42: text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm, rc-web@42: function(wholeMatch,m1,m2) { rc-web@42: var h_level = m1.length; rc-web@42: return hashBlock("' + _RunSpanGamut(m2) + ""); rc-web@42: }); rc-web@42: rc-web@42: function headerId(m) { rc-web@42: return m.replace(/[^\w]/g, '').toLowerCase(); rc-web@42: } rc-web@42: return text; rc-web@42: } rc-web@42: rc-web@42: // This declaration keeps Dojo compressor from outputting garbage: rc-web@42: var _ProcessListItems; rc-web@42: rc-web@42: var _DoLists = function(text) { rc-web@42: // rc-web@42: // Form HTML ordered (numbered) and unordered (bulleted) lists. rc-web@42: // rc-web@42: rc-web@42: // attacklab: add sentinel to hack around khtml/safari bug: rc-web@42: // http://bugs.webkit.org/show_bug.cgi?id=11231 rc-web@42: text += "~0"; rc-web@42: rc-web@42: // Re-usable pattern to match any entirel ul or ol list: rc-web@42: rc-web@42: /* rc-web@42: var whole_list = / rc-web@42: ( // $1 = whole list rc-web@42: ( // $2 rc-web@42: [ ]{0,3} // attacklab: g_tab_width - 1 rc-web@42: ([*+-]|\d+[.]) // $3 = first list item marker rc-web@42: [ \t]+ rc-web@42: ) rc-web@42: [^\r]+? rc-web@42: ( // $4 rc-web@42: ~0 // sentinel for workaround; should be $ rc-web@42: | rc-web@42: \n{2,} rc-web@42: (?=\S) rc-web@42: (?! // Negative lookahead for another list item marker rc-web@42: [ \t]* rc-web@42: (?:[*+-]|\d+[.])[ \t]+ rc-web@42: ) rc-web@42: ) rc-web@42: )/g rc-web@42: */ rc-web@42: var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm; rc-web@42: rc-web@42: if (g_list_level) { rc-web@42: text = text.replace(whole_list,function(wholeMatch,m1,m2) { rc-web@42: var list = m1; rc-web@42: var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol"; rc-web@42: rc-web@42: // Turn double returns into triple returns, so that we can make a rc-web@42: // paragraph for the last item in a list, if necessary: rc-web@42: list = list.replace(/\n{2,}/g,"\n\n\n");; rc-web@42: var result = _ProcessListItems(list); rc-web@42: rc-web@42: // Trim any trailing whitespace, to put the closing `` rc-web@42: // up on the preceding line, to get it past the current stupid rc-web@42: // HTML block parser. This is a hack to work around the terrible rc-web@42: // hack that is the HTML block parser. rc-web@42: result = result.replace(/\s+$/,""); rc-web@42: result = "<"+list_type+">" + result + "\n"; rc-web@42: return result; rc-web@42: }); rc-web@42: } else { rc-web@42: whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g; rc-web@42: text = text.replace(whole_list,function(wholeMatch,m1,m2,m3) { rc-web@42: var runup = m1; rc-web@42: var list = m2; rc-web@42: rc-web@42: var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol"; rc-web@42: // Turn double returns into triple returns, so that we can make a rc-web@42: // paragraph for the last item in a list, if necessary: rc-web@42: var list = list.replace(/\n{2,}/g,"\n\n\n");; rc-web@42: var result = _ProcessListItems(list); rc-web@42: result = runup + "<"+list_type+">\n" + result + "\n"; rc-web@42: return result; rc-web@42: }); rc-web@42: } rc-web@42: rc-web@42: // attacklab: strip sentinel rc-web@42: text = text.replace(/~0/,""); rc-web@42: rc-web@42: return text; rc-web@42: } rc-web@42: rc-web@42: _ProcessListItems = function(list_str) { rc-web@42: // rc-web@42: // Process the contents of a single ordered or unordered list, splitting it rc-web@42: // into individual list items. rc-web@42: // rc-web@42: // The $g_list_level global keeps track of when we're inside a list. rc-web@42: // Each time we enter a list, we increment it; when we leave a list, rc-web@42: // we decrement. If it's zero, we're not in a list anymore. rc-web@42: // rc-web@42: // We do this because when we're not inside a list, we want to treat rc-web@42: // something like this: rc-web@42: // rc-web@42: // I recommend upgrading to version rc-web@42: // 8. Oops, now this line is treated rc-web@42: // as a sub-list. rc-web@42: // rc-web@42: // As a single paragraph, despite the fact that the second line starts rc-web@42: // with a digit-period-space sequence. rc-web@42: // rc-web@42: // Whereas when we're inside a list (or sub-list), that line will be rc-web@42: // treated as the start of a sub-list. What a kludge, huh? This is rc-web@42: // an aspect of Markdown's syntax that's hard to parse perfectly rc-web@42: // without resorting to mind-reading. Perhaps the solution is to rc-web@42: // change the syntax rules such that sub-lists must start with a rc-web@42: // starting cardinal number; e.g. "1." or "a.". rc-web@42: rc-web@42: g_list_level++; rc-web@42: rc-web@42: // trim trailing blank lines: rc-web@42: list_str = list_str.replace(/\n{2,}$/,"\n"); rc-web@42: rc-web@42: // attacklab: add sentinel to emulate \z rc-web@42: list_str += "~0"; rc-web@42: rc-web@42: /* rc-web@42: list_str = list_str.replace(/ rc-web@42: (\n)? // leading line = $1 rc-web@42: (^[ \t]*) // leading whitespace = $2 rc-web@42: ([*+-]|\d+[.]) [ \t]+ // list marker = $3 rc-web@42: ([^\r]+? // list item text = $4 rc-web@42: (\n{1,2})) rc-web@42: (?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+)) rc-web@42: /gm, function(){...}); rc-web@42: */ rc-web@42: list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm, rc-web@42: function(wholeMatch,m1,m2,m3,m4){ rc-web@42: var item = m4; rc-web@42: var leading_line = m1; rc-web@42: var leading_space = m2; rc-web@42: rc-web@42: if (leading_line || (item.search(/\n{2,}/)>-1)) { rc-web@42: item = _RunBlockGamut(_Outdent(item)); rc-web@42: } rc-web@42: else { rc-web@42: // Recursion for sub-lists: rc-web@42: item = _DoLists(_Outdent(item)); rc-web@42: item = item.replace(/\n$/,""); // chomp(item) rc-web@42: item = _RunSpanGamut(item); rc-web@42: } rc-web@42: rc-web@42: return "
  • " + item + "
  • \n"; rc-web@42: } rc-web@42: ); rc-web@42: rc-web@42: // attacklab: strip sentinel rc-web@42: list_str = list_str.replace(/~0/g,""); rc-web@42: rc-web@42: g_list_level--; rc-web@42: return list_str; rc-web@42: } rc-web@42: rc-web@42: rc-web@42: var _DoCodeBlocks = function(text) { rc-web@42: // rc-web@42: // Process Markdown `
    ` blocks.
    rc-web@42: //
    rc-web@42: 
    rc-web@42: 	/*
    rc-web@42: 		text = text.replace(text,
    rc-web@42: 			/(?:\n\n|^)
    rc-web@42: 			(								// $1 = the code block -- one or more lines, starting with a space/tab
    rc-web@42: 				(?:
    rc-web@42: 					(?:[ ]{4}|\t)			// Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
    rc-web@42: 					.*\n+
    rc-web@42: 				)+
    rc-web@42: 			)
    rc-web@42: 			(\n*[ ]{0,3}[^ \t\n]|(?=~0))	// attacklab: g_tab_width
    rc-web@42: 		/g,function(){...});
    rc-web@42: 	*/
    rc-web@42: 
    rc-web@42: 	// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
    rc-web@42: 	text += "~0";
    rc-web@42: 
    rc-web@42: 	text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
    rc-web@42: 		function(wholeMatch,m1,m2) {
    rc-web@42: 			var codeblock = m1;
    rc-web@42: 			var nextChar = m2;
    rc-web@42: 
    rc-web@42: 			codeblock = _EncodeCode( _Outdent(codeblock));
    rc-web@42: 			codeblock = _Detab(codeblock);
    rc-web@42: 			codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines
    rc-web@42: 			codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace
    rc-web@42: 
    rc-web@42: 			codeblock = "
    " + codeblock + "\n
    "; rc-web@42: rc-web@42: return hashBlock(codeblock) + nextChar; rc-web@42: } rc-web@42: ); rc-web@42: rc-web@42: // attacklab: strip sentinel rc-web@42: text = text.replace(/~0/,""); rc-web@42: rc-web@42: return text; rc-web@42: }; rc-web@42: rc-web@42: var _DoGithubCodeBlocks = function(text) { rc-web@42: // rc-web@42: // Process Github-style code blocks rc-web@42: // Example: rc-web@42: // ```ruby rc-web@42: // def hello_world(x) rc-web@42: // puts "Hello, #{x}" rc-web@42: // end rc-web@42: // ``` rc-web@42: // rc-web@42: rc-web@42: rc-web@42: // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug rc-web@42: text += "~0"; rc-web@42: rc-web@42: text = text.replace(/(?:^|\n)```(.*)\n([\s\S]*?)\n```/g, rc-web@42: function(wholeMatch,m1,m2) { rc-web@42: var language = m1; rc-web@42: var codeblock = m2; rc-web@42: rc-web@42: codeblock = _EncodeCode(codeblock); rc-web@42: codeblock = _Detab(codeblock); rc-web@42: codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines rc-web@42: codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace rc-web@42: rc-web@42: codeblock = "
    " + codeblock + "\n
    "; rc-web@42: rc-web@42: return hashBlock(codeblock); rc-web@42: } rc-web@42: ); rc-web@42: rc-web@42: // attacklab: strip sentinel rc-web@42: text = text.replace(/~0/,""); rc-web@42: rc-web@42: return text; rc-web@42: } rc-web@42: rc-web@42: var hashBlock = function(text) { rc-web@42: text = text.replace(/(^\n+|\n+$)/g,""); rc-web@42: return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n"; rc-web@42: } rc-web@42: rc-web@42: var _DoCodeSpans = function(text) { rc-web@42: // rc-web@42: // * Backtick quotes are used for spans. rc-web@42: // rc-web@42: // * You can use multiple backticks as the delimiters if you want to rc-web@42: // include literal backticks in the code span. So, this input: rc-web@42: // rc-web@42: // Just type ``foo `bar` baz`` at the prompt. rc-web@42: // rc-web@42: // Will translate to: rc-web@42: // rc-web@42: //

    Just type foo `bar` baz at the prompt.

    rc-web@42: // rc-web@42: // There's no arbitrary limit to the number of backticks you rc-web@42: // can use as delimters. If you need three consecutive backticks rc-web@42: // in your code, use four for delimiters, etc. rc-web@42: // rc-web@42: // * You can use spaces to get literal backticks at the edges: rc-web@42: // rc-web@42: // ... type `` `bar` `` ... rc-web@42: // rc-web@42: // Turns to: rc-web@42: // rc-web@42: // ... type `bar` ... rc-web@42: // rc-web@42: rc-web@42: /* rc-web@42: text = text.replace(/ rc-web@42: (^|[^\\]) // Character before opening ` can't be a backslash rc-web@42: (`+) // $2 = Opening run of ` rc-web@42: ( // $3 = The code block rc-web@42: [^\r]*? rc-web@42: [^`] // attacklab: work around lack of lookbehind rc-web@42: ) rc-web@42: \2 // Matching closer rc-web@42: (?!`) rc-web@42: /gm, function(){...}); rc-web@42: */ rc-web@42: rc-web@42: text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm, rc-web@42: function(wholeMatch,m1,m2,m3,m4) { rc-web@42: var c = m3; rc-web@42: c = c.replace(/^([ \t]*)/g,""); // leading whitespace rc-web@42: c = c.replace(/[ \t]*$/g,""); // trailing whitespace rc-web@42: c = _EncodeCode(c); rc-web@42: return m1+""+c+""; rc-web@42: }); rc-web@42: rc-web@42: return text; rc-web@42: } rc-web@42: rc-web@42: var _EncodeCode = function(text) { rc-web@42: // rc-web@42: // Encode/escape certain characters inside Markdown code runs. rc-web@42: // The point is that in code, these characters are literals, rc-web@42: // and lose their special Markdown meanings. rc-web@42: // rc-web@42: // Encode all ampersands; HTML entities are not rc-web@42: // entities within a Markdown code span. rc-web@42: text = text.replace(/&/g,"&"); rc-web@42: rc-web@42: // Do the angle bracket song and dance: rc-web@42: text = text.replace(//g,">"); rc-web@42: rc-web@42: // Now, escape characters that are magic in Markdown: rc-web@42: text = escapeCharacters(text,"\*_{}[]\\",false); rc-web@42: rc-web@42: // jj the line above breaks this: rc-web@42: //--- rc-web@42: rc-web@42: //* Item rc-web@42: rc-web@42: // 1. Subitem rc-web@42: rc-web@42: // special char: * rc-web@42: //--- rc-web@42: rc-web@42: return text; rc-web@42: } rc-web@42: rc-web@42: rc-web@42: var _DoItalicsAndBold = function(text) { rc-web@42: rc-web@42: // must go first: rc-web@42: text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g, rc-web@42: "$2"); rc-web@42: rc-web@42: text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g, rc-web@42: "$2"); rc-web@42: rc-web@42: return text; rc-web@42: } rc-web@42: rc-web@42: rc-web@42: var _DoBlockQuotes = function(text) { rc-web@42: rc-web@42: /* rc-web@42: text = text.replace(/ rc-web@42: ( // Wrap whole match in $1 rc-web@42: ( rc-web@42: ^[ \t]*>[ \t]? // '>' at the start of a line rc-web@42: .+\n // rest of the first line rc-web@42: (.+\n)* // subsequent consecutive lines rc-web@42: \n* // blanks rc-web@42: )+ rc-web@42: ) rc-web@42: /gm, function(){...}); rc-web@42: */ rc-web@42: rc-web@42: text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm, rc-web@42: function(wholeMatch,m1) { rc-web@42: var bq = m1; rc-web@42: rc-web@42: // attacklab: hack around Konqueror 3.5.4 bug: rc-web@42: // "----------bug".replace(/^-/g,"") == "bug" rc-web@42: rc-web@42: bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0"); // trim one level of quoting rc-web@42: rc-web@42: // attacklab: clean up hack rc-web@42: bq = bq.replace(/~0/g,""); rc-web@42: rc-web@42: bq = bq.replace(/^[ \t]+$/gm,""); // trim whitespace-only lines rc-web@42: bq = _RunBlockGamut(bq); // recurse rc-web@42: rc-web@42: bq = bq.replace(/(^|\n)/g,"$1 "); rc-web@42: // These leading spaces screw with
     content, so we need to fix that:
    rc-web@42: 			bq = bq.replace(
    rc-web@42: 					/(\s*
    [^\r]+?<\/pre>)/gm,
    rc-web@42: 				function(wholeMatch,m1) {
    rc-web@42: 					var pre = m1;
    rc-web@42: 					// attacklab: hack around Konqueror 3.5.4 bug:
    rc-web@42: 					pre = pre.replace(/^  /mg,"~0");
    rc-web@42: 					pre = pre.replace(/~0/g,"");
    rc-web@42: 					return pre;
    rc-web@42: 				});
    rc-web@42: 
    rc-web@42: 			return hashBlock("
    \n" + bq + "\n
    "); rc-web@42: }); rc-web@42: return text; rc-web@42: } rc-web@42: rc-web@42: rc-web@42: var _FormParagraphs = function(text) { rc-web@42: // rc-web@42: // Params: rc-web@42: // $text - string to process with html

    tags rc-web@42: // rc-web@42: rc-web@42: // Strip leading and trailing lines: rc-web@42: text = text.replace(/^\n+/g,""); rc-web@42: text = text.replace(/\n+$/g,""); rc-web@42: rc-web@42: var grafs = text.split(/\n{2,}/g); rc-web@42: var grafsOut = new Array(); rc-web@42: rc-web@42: // rc-web@42: // Wrap

    tags. rc-web@42: // rc-web@42: var end = grafs.length; rc-web@42: for (var i=0; i= 0) { rc-web@42: grafsOut.push(str); rc-web@42: } rc-web@42: else if (str.search(/\S/) >= 0) { rc-web@42: str = _RunSpanGamut(str); rc-web@42: str = str.replace(/^([ \t]*)/g,"

    "); rc-web@42: str += "

    " rc-web@42: grafsOut.push(str); rc-web@42: } rc-web@42: rc-web@42: } rc-web@42: rc-web@42: // rc-web@42: // Unhashify HTML blocks rc-web@42: // rc-web@42: end = grafsOut.length; rc-web@42: for (var i=0; i= 0) { rc-web@42: var blockText = g_html_blocks[RegExp.$1]; rc-web@42: blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs rc-web@42: grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText); rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: return grafsOut.join("\n\n"); rc-web@42: } rc-web@42: rc-web@42: rc-web@42: var _EncodeAmpsAndAngles = function(text) { rc-web@42: // Smart processing for ampersands and angle brackets that need to be encoded. rc-web@42: rc-web@42: // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: rc-web@42: // http://bumppo.net/projects/amputator/ rc-web@42: text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&"); rc-web@42: rc-web@42: // Encode naked <'s rc-web@42: text = text.replace(/<(?![a-z\/?\$!])/gi,"<"); rc-web@42: rc-web@42: return text; rc-web@42: } rc-web@42: rc-web@42: rc-web@42: var _EncodeBackslashEscapes = function(text) { rc-web@42: // rc-web@42: // Parameter: String. rc-web@42: // Returns: The string, with after processing the following backslash rc-web@42: // escape sequences. rc-web@42: // rc-web@42: rc-web@42: // attacklab: The polite way to do this is with the new rc-web@42: // escapeCharacters() function: rc-web@42: // rc-web@42: // text = escapeCharacters(text,"\\",true); rc-web@42: // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true); rc-web@42: // rc-web@42: // ...but we're sidestepping its use of the (slow) RegExp constructor rc-web@42: // as an optimization for Firefox. This function gets called a LOT. rc-web@42: rc-web@42: text = text.replace(/\\(\\)/g,escapeCharacters_callback); rc-web@42: text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback); rc-web@42: return text; rc-web@42: } rc-web@42: rc-web@42: rc-web@42: var _DoAutoLinks = function(text) { rc-web@42: rc-web@42: text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"
    $1"); rc-web@42: rc-web@42: // Email addresses: rc-web@42: rc-web@42: /* rc-web@42: text = text.replace(/ rc-web@42: < rc-web@42: (?:mailto:)? rc-web@42: ( rc-web@42: [-.\w]+ rc-web@42: \@ rc-web@42: [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ rc-web@42: ) rc-web@42: > rc-web@42: /gi, _DoAutoLinks_callback()); rc-web@42: */ rc-web@42: text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi, rc-web@42: function(wholeMatch,m1) { rc-web@42: return _EncodeEmailAddress( _UnescapeSpecialChars(m1) ); rc-web@42: } rc-web@42: ); rc-web@42: rc-web@42: return text; rc-web@42: } rc-web@42: rc-web@42: rc-web@42: var _EncodeEmailAddress = function(addr) { rc-web@42: // rc-web@42: // Input: an email address, e.g. "foo@example.com" rc-web@42: // rc-web@42: // Output: the email address as a mailto link, with each character rc-web@42: // of the address encoded as either a decimal or hex entity, in rc-web@42: // the hopes of foiling most address harvesting spam bots. E.g.: rc-web@42: // rc-web@42: // foo rc-web@42: // @example.com rc-web@42: // rc-web@42: // Based on a filter by Matthew Wickline, posted to the BBEdit-Talk rc-web@42: // mailing list: rc-web@42: // rc-web@42: rc-web@42: // attacklab: why can't javascript speak hex? rc-web@42: function char2hex(ch) { rc-web@42: var hexDigits = '0123456789ABCDEF'; rc-web@42: var dec = ch.charCodeAt(0); rc-web@42: return(hexDigits.charAt(dec>>4) + hexDigits.charAt(dec&15)); rc-web@42: } rc-web@42: rc-web@42: var encode = [ rc-web@42: function(ch){return "&#"+ch.charCodeAt(0)+";";}, rc-web@42: function(ch){return "&#x"+char2hex(ch)+";";}, rc-web@42: function(ch){return ch;} rc-web@42: ]; rc-web@42: rc-web@42: addr = "mailto:" + addr; rc-web@42: rc-web@42: addr = addr.replace(/./g, function(ch) { rc-web@42: if (ch == "@") { rc-web@42: // this *must* be encoded. I insist. rc-web@42: ch = encode[Math.floor(Math.random()*2)](ch); rc-web@42: } else if (ch !=":") { rc-web@42: // leave ':' alone (to spot mailto: later) rc-web@42: var r = Math.random(); rc-web@42: // roughly 10% raw, 45% hex, 45% dec rc-web@42: ch = ( rc-web@42: r > .9 ? encode[2](ch) : rc-web@42: r > .45 ? encode[1](ch) : rc-web@42: encode[0](ch) rc-web@42: ); rc-web@42: } rc-web@42: return ch; rc-web@42: }); rc-web@42: rc-web@42: addr = "" + addr + ""; rc-web@42: addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part rc-web@42: rc-web@42: return addr; rc-web@42: } rc-web@42: rc-web@42: rc-web@42: var _UnescapeSpecialChars = function(text) { rc-web@42: // rc-web@42: // Swap back in all the special characters we've hidden. rc-web@42: // rc-web@42: text = text.replace(/~E(\d+)E/g, rc-web@42: function(wholeMatch,m1) { rc-web@42: var charCodeToReplace = parseInt(m1); rc-web@42: return String.fromCharCode(charCodeToReplace); rc-web@42: } rc-web@42: ); rc-web@42: return text; rc-web@42: } rc-web@42: rc-web@42: rc-web@42: var _Outdent = function(text) { rc-web@42: // rc-web@42: // Remove one level of line-leading tabs or spaces rc-web@42: // rc-web@42: rc-web@42: // attacklab: hack around Konqueror 3.5.4 bug: rc-web@42: // "----------bug".replace(/^-/g,"") == "bug" rc-web@42: rc-web@42: text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width rc-web@42: rc-web@42: // attacklab: clean up hack rc-web@42: text = text.replace(/~0/g,"") rc-web@42: rc-web@42: return text; rc-web@42: } rc-web@42: rc-web@42: var _Detab = function(text) { rc-web@42: // attacklab: Detab's completely rewritten for speed. rc-web@42: // In perl we could fix it by anchoring the regexp with \G. rc-web@42: // In javascript we're less fortunate. rc-web@42: rc-web@42: // expand first n-1 tabs rc-web@42: text = text.replace(/\t(?=\t)/g," "); // attacklab: g_tab_width rc-web@42: rc-web@42: // replace the nth with two sentinels rc-web@42: text = text.replace(/\t/g,"~A~B"); rc-web@42: rc-web@42: // use the sentinel to anchor our regex so it doesn't explode rc-web@42: text = text.replace(/~B(.+?)~A/g, rc-web@42: function(wholeMatch,m1,m2) { rc-web@42: var leadingText = m1; rc-web@42: var numSpaces = 4 - leadingText.length % 4; // attacklab: g_tab_width rc-web@42: rc-web@42: // there *must* be a better way to do this: rc-web@42: for (var i=0; i