vendor/assets/javascripts/showdown.js in mercury-rails-0.1.1 vs vendor/assets/javascripts/showdown.js in mercury-rails-0.1.2

- old
+ new

@@ -79,1284 +79,1262 @@ // Wraps all "globals" so that the only thing // exposed is makeHtml(). // Showdown.converter = function() { -// -// Globals: -// + // Global hashes, used by various utility routines + var g_urls; + var g_titles; + var g_html_blocks; -// 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; -// Used to track when we're inside an ordered or unordered list -// (see _ProcessListItems() for details): -var g_list_level = 0; + // 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. + this.makeHtml = function(text) { + // 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(); -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 + // 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"); + 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"); + // 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 + // 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"; + // 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); + // 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,""); + // 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); + // Turn block-level HTML blocks into hash entries + text = _HashHTMLBlocks(text); - // Strip link definitions, store in hashes. - text = _StripLinkDefinitions(text); + // Strip link definitions, store in hashes. + text = _StripLinkDefinitions(text); - text = _RunBlockGamut(text); + text = _RunBlockGamut(text); - text = _UnescapeSpecialChars(text); + text = _UnescapeSpecialChars(text); - // attacklab: Restore dollar signs - text = text.replace(/~D/g,"$$"); + // attacklab: Restore dollar signs + text = text.replace(/~D/g, "$$"); - // attacklab: Restore tildes - text = text.replace(/~T/g,"~"); + // attacklab: Restore tildes + text = text.replace(/~T/g, "~"); - // ** GFM ** Auto-link URLs and emails - text = text.replace(/https?\:\/\/[^"\s\<\>]*[^.,;'">\:\s\<\>\)\]\!]/g, function(wholeMatch,matchIndex){ - var left = text.slice(0, matchIndex), right = text.slice(matchIndex) - if (left.match(/<[^>]+$/) && right.match(/^[^>]*>/)) {return wholeMatch} - href = wholeMatch.replace(/^http:\/\/github.com\//, "https://github.com/") - return "<a href='" + href + "'>" + wholeMatch + "</a>"; - }); - text = text.replace(/[a-z0-9_\-+=.]+@[a-z0-9\-]+(\.[a-z0-9-]+)+/ig, function(wholeMatch){return "<a href='mailto:" + wholeMatch + "'>" + wholeMatch + "</a>";}); + // ** GFM ** Auto-link URLs and emails + text = text.replace(/https?\:\/\/[^"\s\<\>]*[^.,;'">\:\s\<\>\)\]\!]/g, function(wholeMatch, matchIndex) { + var left = text.slice(0, matchIndex), right = text.slice(matchIndex); + if (left.match(/<[^>]+$/) && right.match(/^[^>]*>/)) { + return wholeMatch + } + var href = wholeMatch.replace(/^http:\/\/github.com\//, "https://github.com/"); + return "<a href='" + href + "'>" + wholeMatch + "</a>"; + }); + text = text.replace(/[a-z0-9_\-+=.]+@[a-z0-9\-]+(\.[a-z0-9-]+)+/ig, function(wholeMatch) { + return "<a href='mailto:" + wholeMatch + "'>" + wholeMatch + "</a>"; + }); - // ** GFM ** Auto-link sha1 if GitHub.nameWithOwner is defined - text = text.replace(/[a-f0-9]{40}/ig, function(wholeMatch,matchIndex){ - if (typeof(GitHub) == "undefined" || typeof(GitHub.nameWithOwner) == "undefined") {return wholeMatch;} - var left = text.slice(0, matchIndex), right = text.slice(matchIndex) - if (left.match(/@$/) || (left.match(/<[^>]+$/) && right.match(/^[^>]*>/))) {return wholeMatch;} - return "<a href='http://github.com/" + GitHub.nameWithOwner + "/commit/" + wholeMatch + "'>" + wholeMatch.substring(0,7) + "</a>"; - }); + // ** GFM ** Auto-link sha1 if GitHub.nameWithOwner is defined + text = text.replace(/[a-f0-9]{40}/ig, function(wholeMatch, matchIndex) { + if (typeof(GitHub) == "undefined" || typeof(GitHub.nameWithOwner) == "undefined") { + return wholeMatch; + } + var left = text.slice(0, matchIndex), right = text.slice(matchIndex); + if (left.match(/@$/) || (left.match(/<[^>]+$/) && right.match(/^[^>]*>/))) { + return wholeMatch; + } + return "<a href='http://github.com/" + GitHub.nameWithOwner + "/commit/" + wholeMatch + "'>" + wholeMatch.substring(0, 7) + "</a>"; + }); - // ** GFM ** Auto-link user@sha1 if GitHub.nameWithOwner is defined - text = text.replace(/([a-z0-9_\-+=.]+)@([a-f0-9]{40})/ig, function(wholeMatch,username,sha,matchIndex){ - if (typeof(GitHub) == "undefined" || typeof(GitHub.nameWithOwner) == "undefined") {return wholeMatch;} - GitHub.repoName = GitHub.repoName || _GetRepoName() - var left = text.slice(0, matchIndex), right = text.slice(matchIndex) - if (left.match(/\/$/) || (left.match(/<[^>]+$/) && right.match(/^[^>]*>/))) {return wholeMatch;} - return "<a href='http://github.com/" + username + "/" + GitHub.repoName + "/commit/" + sha + "'>" + username + "@" + sha.substring(0,7) + "</a>"; - }); + // ** GFM ** Auto-link user@sha1 if GitHub.nameWithOwner is defined + text = text.replace(/([a-z0-9_\-+=.]+)@([a-f0-9]{40})/ig, function(wholeMatch, username, sha, matchIndex) { + if (typeof(GitHub) == "undefined" || typeof(GitHub.nameWithOwner) == "undefined") { + return wholeMatch; + } + GitHub.repoName = GitHub.repoName || _GetRepoName(); + var left = text.slice(0, matchIndex), right = text.slice(matchIndex); + if (left.match(/\/$/) || (left.match(/<[^>]+$/) && right.match(/^[^>]*>/))) { + return wholeMatch; + } + return "<a href='http://github.com/" + username + "/" + GitHub.repoName + "/commit/" + sha + "'>" + username + "@" + sha.substring(0, 7) + "</a>"; + }); - // ** GFM ** Auto-link user/repo@sha1 - text = text.replace(/([a-z0-9_\-+=.]+\/[a-z0-9_\-+=.]+)@([a-f0-9]{40})/ig, function(wholeMatch,repo,sha){ - return "<a href='http://github.com/" + repo + "/commit/" + sha + "'>" + repo + "@" + sha.substring(0,7) + "</a>"; - }); + // ** GFM ** Auto-link user/repo@sha1 + text = text.replace(/([a-z0-9_\-+=.]+\/[a-z0-9_\-+=.]+)@([a-f0-9]{40})/ig, function(wholeMatch, repo, sha) { + return "<a href='http://github.com/" + repo + "/commit/" + sha + "'>" + repo + "@" + sha.substring(0, 7) + "</a>"; + }); - // ** GFM ** Auto-link #issue if GitHub.nameWithOwner is defined - text = text.replace(/#([0-9]+)/ig, function(wholeMatch,issue,matchIndex){ - if (typeof(GitHub) == "undefined" || typeof(GitHub.nameWithOwner) == "undefined") {return wholeMatch;} - var left = text.slice(0, matchIndex), right = text.slice(matchIndex) - if (left == "" || left.match(/[a-z0-9_\-+=.]$/) || (left.match(/<[^>]+$/) && right.match(/^[^>]*>/))) {return wholeMatch;} - return "<a href='http://github.com/" + GitHub.nameWithOwner + "/issues/#issue/" + issue + "'>" + wholeMatch + "</a>"; - }); + // ** GFM ** Auto-link #issue if GitHub.nameWithOwner is defined + text = text.replace(/#([0-9]+)/ig, function(wholeMatch, issue, matchIndex) { + if (typeof(GitHub) == "undefined" || typeof(GitHub.nameWithOwner) == "undefined") { + return wholeMatch; + } + var left = text.slice(0, matchIndex), right = text.slice(matchIndex); + if (left == "" || left.match(/[a-z0-9_\-+=.]$/) || (left.match(/<[^>]+$/) && right.match(/^[^>]*>/))) { + return wholeMatch; + } + return "<a href='http://github.com/" + GitHub.nameWithOwner + "/issues/#issue/" + issue + "'>" + wholeMatch + "</a>"; + }); - // ** GFM ** Auto-link user#issue if GitHub.nameWithOwner is defined - text = text.replace(/([a-z0-9_\-+=.]+)#([0-9]+)/ig, function(wholeMatch,username,issue,matchIndex){ - if (typeof(GitHub) == "undefined" || typeof(GitHub.nameWithOwner) == "undefined") {return wholeMatch;} - GitHub.repoName = GitHub.repoName || _GetRepoName() - var left = text.slice(0, matchIndex), right = text.slice(matchIndex) - if (left.match(/\/$/) || (left.match(/<[^>]+$/) && right.match(/^[^>]*>/))) {return wholeMatch;} - return "<a href='http://github.com/" + username + "/" + GitHub.repoName + "/issues/#issue/" + issue + "'>" + wholeMatch + "</a>"; - }); + // ** GFM ** Auto-link user#issue if GitHub.nameWithOwner is defined + text = text.replace(/([a-z0-9_\-+=.]+)#([0-9]+)/ig, function(wholeMatch, username, issue, matchIndex) { + if (typeof(GitHub) == "undefined" || typeof(GitHub.nameWithOwner) == "undefined") { + return wholeMatch; + } + GitHub.repoName = GitHub.repoName || _GetRepoName(); + var left = text.slice(0, matchIndex), right = text.slice(matchIndex); + if (left.match(/\/$/) || (left.match(/<[^>]+$/) && right.match(/^[^>]*>/))) { + return wholeMatch; + } + return "<a href='http://github.com/" + username + "/" + GitHub.repoName + "/issues/#issue/" + issue + "'>" + wholeMatch + "</a>"; + }); - // ** GFM ** Auto-link user/repo#issue - text = text.replace(/([a-z0-9_\-+=.]+\/[a-z0-9_\-+=.]+)#([0-9]+)/ig, function(wholeMatch,repo,issue){ - return "<a href='http://github.com/" + repo + "/issues/#issue/" + issue + "'>" + wholeMatch + "</a>"; - }); + // ** GFM ** Auto-link user/repo#issue + text = text.replace(/([a-z0-9_\-+=.]+\/[a-z0-9_\-+=.]+)#([0-9]+)/ig, function(wholeMatch, repo, issue) { + return "<a href='http://github.com/" + repo + "/issues/#issue/" + issue + "'>" + wholeMatch + "</a>"; + }); - return text; -} + return text; + }; -var _GetRepoName = function() { - return GitHub.nameWithOwner.match(/^.+\/(.+)$/)[1] -} + var _GetRepoName = function() { + return GitHub.nameWithOwner.match(/^.+\/(.+)$/)[1] + }; -var _StripLinkDefinitions = function(text) { -// -// Strips link definitions from text, stores the URLs and titles in -// hash references. -// + // + // Strips link definitions from text, stores the URLs and titles in + // hash references. + // + var _StripLinkDefinitions = function(text) { - // Link defs are in the form: ^[id]: url "optional title" + // 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,"&quot;"); - } + /* + 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, "&quot;"); + } - // Completely remove the definition from the text - return ""; - } - ); + // Completely remove the definition from the text + return ""; + }); - return text; -} + return text; + }; -var _HashHTMLBlocks = function(text) { - // attacklab: Double up blank lines to reduce lookaround - text = text.replace(/\n/g,"\n\n"); + 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" + // 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>`. + // 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); + // 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` - // + // + // 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); + /* + 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. + // 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); + /* + 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: + // 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); + /* + 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 <%...%>) + // 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); + /* + 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; -} + // 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; + var hashElement = function(wholeMatch, m1) { + var blockText = m1; - // Undo double lines - blockText = blockText.replace(/\n\n/g,"\n"); - blockText = blockText.replace(/^\n/,""); + // Undo double lines + blockText = blockText.replace(/\n\n/g, "\n"); + blockText = blockText.replace(/^\n/, ""); - // strip trailing blank lines - blockText = blockText.replace(/\n+$/g,""); + // 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"; + // 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; -}; + 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); + // + // These are all the transformations that form block-level + // tags like paragraphs, headers, and list items. + // + var _RunBlockGamut = function(text) { + 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); + // 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); + 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); + // 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; -} + return text; + }; + // + // These are all the transformations that occur *within* block-level + // tags like paragraphs, headers, and list items. + // + var _RunSpanGamut = function(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); - 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); - // 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); - // 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"); - // Do hard breaks: - text = text.replace(/ +\n/g," <br />\n"); + return text; + }; - return text; -} + // + // Within tags -- meaning between < and > -- encode [\ ` * _] so they + // don't conflict with their use in Markdown for code, italics and strong. + // + var _EscapeSpecialCharsWithinTagAttributes = function(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; - // 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; + }); - text = text.replace(regex, function(wholeMatch) { - var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`"); - tag = escapeCharacters(tag,"\\`*_"); - return tag; - }); + return text; + }; - return text; -} + // + // Turn Markdown link shortcuts into XHTML <a> tags. + // + var _DoAnchors = function(text) { + // + // First, handle reference-style links: [link text] [id] + // -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 + )* + ) + \] - /* - 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 - [ ]? // one optional space - (?:\n[ ]*)? // one optional newline followed by spaces + \[ + (.*?) // id = $3 + \] + )()()()() // pad remaining backreferences + /g,_DoAnchors_callback); + */ + text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeAnchorTag); - \[ - (.*?) // id = $3 - \] - )()()()() // pad remaining backreferences - /g,_DoAnchors_callback); - */ - text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag); + // + // Next, inline-style links: [link text](url "optional title") + // - // - // 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); - /* - 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) + // - // - // 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); - /* - 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; + }; - 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; -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 (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; + } + } + } - 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 + "\""; - url = escapeCharacters(url,"*_"); - var result = "<a href=\"" + url + "\""; + if (title != "") { + title = title.replace(/"/g, "&quot;"); + title = escapeCharacters(title, "*_"); + result += " title=\"" + title + "\""; + } - if (title != "") { - title = title.replace(/"/g,"&quot;"); - title = escapeCharacters(title,"*_"); - result += " title=\"" + title + "\""; - } + result += ">" + link_text + "</a>"; - result += ">" + link_text + "</a>"; + return result; + }; - return result; -} + // + // Turn Markdown image shortcuts into <img> tags. + // + var _DoImages = function(text) { + // + // First, handle reference-style labeled images: ![alt text][id] + // -var _DoImages = function(text) { -// -// Turn Markdown image shortcuts into <img> tags. -// + /* + text = text.replace(/ + ( // wrap whole match in $1 + !\[ + (.*?) // alt text = $2 + \] - // - // First, handle reference-style labeled images: ![alt text][id] - // + [ ]? // one optional space + (?:\n[ ]*)? // one optional newline followed by spaces - /* - text = text.replace(/ - ( // wrap whole match in $1 - !\[ - (.*?) // alt text = $2 - \] + \[ + (.*?) // id = $3 + \] + )()()()() // pad rest of backreferences + /g,writeImageTag); + */ + text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeImageTag); - [ ]? // one optional space - (?:\n[ ]*)? // one optional newline followed by spaces + // + // Next, handle inline images: ![alt text](url "optional title") + // Don't forget: encode * and _ - \[ - (.*?) // id = $3 - \] - )()()()() // pad rest of backreferences - /g,writeImageTag); - */ - text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag); + /* + 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); - // - // Next, handle inline images: ![alt text](url "optional title") - // Don't forget: encode * and _ + return text; + }; - /* - 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); + 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; - return text; -} + if (!title) title = ""; -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 (url == "") { + if (link_id == "") { + // lower-case and turn embedded newlines into spaces + link_id = alt_text.toLowerCase().replace(/ ?\n/g, " "); + } + url = "#" + link_id; - if (!title) title = ""; + 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; + } + } - if (url == "") { - if (link_id == "") { - // lower-case and turn embedded newlines into spaces - link_id = alt_text.toLowerCase().replace(/ ?\n/g," "); - } - url = "#"+link_id; + alt_text = alt_text.replace(/"/g, "&quot;"); + url = escapeCharacters(url, "*_"); + var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\""; - 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; - } - } + // attacklab: Markdown.pl adds empty title attributes to images. + // Replicate this bug. - alt_text = alt_text.replace(/"/g,"&quot;"); - url = escapeCharacters(url,"*_"); - var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\""; + //if (title != "") { + title = title.replace(/"/g, "&quot;"); + title = escapeCharacters(title, "*_"); + result += " title=\"" + title + "\""; + //} - // attacklab: Markdown.pl adds empty title attributes to images. - // Replicate this bug. + result += " />"; - //if (title != "") { - title = title.replace(/"/g,"&quot;"); - title = escapeCharacters(title,"*_"); - result += " title=\"" + title + "\""; - //} + return result; + }; - 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>"); + }); -var _DoHeaders = function(text) { + text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, + function(matchFound, m1) { + return hashBlock("<h2>" + _RunSpanGamut(m1) + "</h2>"); + }); - // Setext-style headers: - // Header 1 - // ======== - // - // Header 2 - // -------- - // - text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm, - function(wholeMatch,m1){return hashBlock("<h1>" + _RunSpanGamut(m1) + "</h1>");}); + // atx-style headers: + // # Header 1 + // ## Header 2 + // ## Header 2 with closing hashes ## + // ... + // ###### Header 6 + // - text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, - function(matchFound,m1){return hashBlock("<h2>" + _RunSpanGamut(m1) + "</h2>");}); + /* + text = text.replace(/ + ^(\#{1,6}) // $1 = string of #'s + [ \t]* + (.+?) // $2 = Header text + [ \t]* + \#* // optional closing #'s (not counted) + \n+ + /gm, function() {...}); + */ - // atx-style headers: - // # Header 1 - // ## Header 2 - // ## Header 2 with closing hashes ## - // ... - // ###### Header 6 - // + 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 + ">"); + }); - /* - text = text.replace(/ - ^(\#{1,6}) // $1 = string of #'s - [ \t]* - (.+?) // $2 = Header text - [ \t]* - \#* // optional closing #'s (not counted) - \n+ - /gm, function() {...}); - */ + return text; + }; - 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 + ">"); - }); + // This declaration keeps Dojo compressor from outputting garbage: + var _ProcessListItems; - return text; -} + // + // Form HTML ordered (numbered) and unordered (bulleted) lists. + // + var _DoLists = function(text) { -// This declaration keeps Dojo compressor from outputting garbage: -var _ProcessListItems; + // attacklab: add sentinel to hack around khtml/safari bug: + // http://bugs.webkit.org/show_bug.cgi?id=11231 + text += "~0"; -var _DoLists = function(text) { -// -// Form HTML ordered (numbered) and unordered (bulleted) lists. -// + // Re-usable pattern to match any entirel ul or ol list: - // attacklab: add sentinel to hack around khtml/safari bug: - // http://bugs.webkit.org/show_bug.cgi?id=11231 - text += "~0"; + /* + 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; - // Re-usable pattern to match any entirel ul or ol list: + 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"; - /* - 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; + // 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); - 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"; + // 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; - // 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); + 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; + }); + } - // 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; + // attacklab: strip sentinel + text = text.replace(/~0/, ""); - 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; - }); - } + return text; + }; - // attacklab: strip sentinel - text = text.replace(/~0/,""); + // + // Process the contents of a single ordered or unordered list, splitting it + // into individual list items. + // + _ProcessListItems = function(list_str) { + // 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.". - return text; -} + g_list_level++; -_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.". + // trim trailing blank lines: + list_str = list_str.replace(/\n{2,}$/, "\n"); - g_list_level++; + // attacklab: add sentinel to emulate \z + list_str += "~0"; - // trim trailing blank lines: - list_str = list_str.replace(/\n{2,}$/,"\n"); + /* + 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; - // attacklab: add sentinel to emulate \z - list_str += "~0"; + 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); + } - /* - 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; + return "<li>" + item + "</li>\n"; + } + ); - 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); - } + // attacklab: strip sentinel + list_str = list_str.replace(/~0/g, ""); - return "<li>" + item + "</li>\n"; - } - ); + g_list_level--; + return list_str; + }; - // attacklab: strip sentinel - list_str = list_str.replace(/~0/g,""); - g_list_level--; - return list_str; -} + // + // Process Markdown `<pre><code>` blocks. + // + var _DoCodeBlocks = function(text) { + /* + 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(){...}); + */ -var _DoCodeBlocks = function(text) { -// -// Process Markdown `<pre><code>` blocks. -// + // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug + text += "~0"; - /* - 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(){...}); - */ + 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; - // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug - text += "~0"; + codeblock = _EncodeCode(_Outdent(codeblock)); + codeblock = _Detab(codeblock); + codeblock = codeblock.replace(/^\n+/g, ""); // trim leading newlines + codeblock = codeblock.replace(/\n+$/g, ""); // trim trailing whitespace - 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 = "<pre><code>" + codeblock + "\n</code></pre>"; - codeblock = _EncodeCode( _Outdent(codeblock)); - codeblock = _Detab(codeblock); - codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines - codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace + return hashBlock(codeblock) + nextChar; + }); - codeblock = "<pre><code>" + codeblock + "\n</code></pre>"; + // attacklab: strip sentinel + text = text.replace(/~0/, ""); - return hashBlock(codeblock) + nextChar; - } - ); + return text; + }; - // attacklab: strip sentinel - text = text.replace(/~0/,""); + var hashBlock = function(text) { + text = text.replace(/(^\n+|\n+$)/g, ""); + return "\n\n~K" + (g_html_blocks.push(text) - 1) + "K\n\n"; + }; - 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"; -} + // + // * 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> ... + // + var _DoCodeSpans = function(text) { + /* + 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>"; + }); -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> ... -// + return text; + }; - /* - 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>"; - }); + // + // Encode/escape certain characters inside Markdown code runs. + // The point is that in code, these characters are literals, + // and lose their special Markdown meanings. + // + var _EncodeCode = function(text) { + // Encode all ampersands; HTML entities are not + // entities within a Markdown code span. + text = text.replace(/&/g, "&amp;"); - return text; -} + // Do the angle bracket song and dance: + text = text.replace(/</g, "&lt;"); + text = text.replace(/>/g, "&gt;"); + // Now, escape characters that are magic in Markdown: + text = escapeCharacters(text, "\*_{}[]\\", false); -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,"&amp;"); + return text; + }; - // Do the angle bracket song and dance: - text = text.replace(/</g,"&lt;"); - text = text.replace(/>/g,"&gt;"); - // Now, escape characters that are magic in Markdown: - text = escapeCharacters(text,"\*_{}[]\\",false); + var _DoItalicsAndBold = function(text) { -// jj the line above breaks this: -//--- + // <strong> must go first: + text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g, + "<strong>$2</strong>"); -//* Item + text = text.replace(/(\w)_(\w)/g, "$1~E95E$2"); // ** GFM ** "~E95E" == escaped "_" + text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g, + "<em>$2</em>"); -// 1. Subitem + return text; + }; -// special char: * -//--- - 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(){...}); + */ -var _DoItalicsAndBold = function(text) { + text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm, + function(wholeMatch, m1) { + var bq = m1; - // <strong> must go first: - text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g, - "<strong>$2</strong>"); + // attacklab: hack around Konqueror 3.5.4 bug: + // "----------bug".replace(/^-/g,"") == "bug" - text = text.replace(/(\w)_(\w)/g, "$1~E95E$2") // ** GFM ** "~E95E" == escaped "_" - text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g, - "<em>$2</em>"); + bq = bq.replace(/^[ \t]*>[ \t]?/gm, "~0"); // trim one level of quoting - return text; -} + // attacklab: clean up hack + bq = bq.replace(/~0/g, ""); + bq = bq.replace(/^[ \t]+$/gm, ""); // trim whitespace-only lines + bq = _RunBlockGamut(bq); // recurse -var _DoBlockQuotes = function(text) { + 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; + }); - /* - 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(){...}); - */ + return hashBlock("<blockquote>\n" + bq + "\n</blockquote>"); + }); + return text; + }; - 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" + // + // Params: + // $text - string to process with html <p> tags + // + var _FormParagraphs = function(text) { - bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0"); // trim one level of quoting + // Strip leading and trailing lines: + text = text.replace(/^\n+/g, ""); + text = text.replace(/\n+$/g, ""); - // attacklab: clean up hack - bq = bq.replace(/~0/g,""); + var grafs = text.split(/\n{2,}/g); + var grafsOut = new Array(); - bq = bq.replace(/^[ \t]+$/gm,""); // trim whitespace-only lines - bq = _RunBlockGamut(bq); // recurse + // + // Wrap <p> tags. + // + var end = grafs.length; + for (var i = 0; i < end; i++) { + var str = grafs[i]; - 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; - }); + // 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(/\n/g, "<br />"); // ** GFM ** + str = str.replace(/^([ \t]*)/g, "<p>"); + str += "</p>"; + grafsOut.push(str); + } - return hashBlock("<blockquote>\n" + bq + "\n</blockquote>"); - }); - return text; -} + } + // + // 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); + } + } -var _FormParagraphs = function(text) { -// -// Params: -// $text - string to process with html <p> tags -// + return grafsOut.join("\n\n"); + }; - // Strip leading and trailing lines: - text = text.replace(/^\n+/g,""); - text = text.replace(/\n+$/g,""); + // Smart processing for ampersands and angle brackets that need to be encoded. + var _EncodeAmpsAndAngles = function(text) { - var grafs = text.split(/\n{2,}/g); - var grafsOut = new Array(); + // 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, "&amp;"); - // - // Wrap <p> tags. - // - var end = grafs.length; - for (var i=0; i<end; i++) { - var str = grafs[i]; + // Encode naked <'s + text = text.replace(/<(?![a-z\/?\$!])/gi, "&lt;"); - // 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(/\n/g,"<br />"); // ** GFM ** - str = str.replace(/^([ \t]*)/g,"<p>"); - str += "</p>" - grafsOut.push(str); - } + return text; + }; - } + // + // Parameter: String. + // Returns: The string, with after processing the following backslash + // escape sequences. + // + var _EncodeBackslashEscapes = function(text) { + // 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. - // - // 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); - } - } + text = text.replace(/\\(\\)/g, escapeCharacters_callback); + text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, escapeCharacters_callback); + return text; + }; - return grafsOut.join("\n\n"); -} + var _DoAutoLinks = function(text) { + text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi, "<a href=\"$1\">$1</a>"); -var _EncodeAmpsAndAngles = function(text) { -// Smart processing for ampersands and angle brackets that need to be encoded. + // Email addresses: <address@domain.foo> - // 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,"&amp;"); + /* + 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)); + }); - // Encode naked <'s - text = text.replace(/<(?![a-z\/?\$!])/gi,"&lt;"); + return text; + }; - return text; -} + // + // 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="&#x6D;&#97;&#105;&#108;&#x74;&#111;:&#102;&#111;&#111;&#64;&#101; + // x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;">&#102;&#111;&#111; + // &#64;&#101;x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;</a> + // + // Based on a filter by Matthew Wickline, posted to the BBEdit-Talk + // mailing list: <http://tinyurl.com/yu7ue> + // + var _EncodeEmailAddress = function(addr) { + // 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 _EncodeBackslashEscapes = function(text) { -// -// Parameter: String. -// Returns: The string, with after processing the following backslash -// escape sequences. -// + var encode = [ + function(ch) { return "&#" + ch.charCodeAt(0) + ";" }, + function(ch) { return "&#x" + char2hex(ch) + ";" }, + function(ch) { return ch } + ]; - // 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. + addr = "mailto:" + addr; - text = text.replace(/\\(\\)/g,escapeCharacters_callback); - text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback); - return text; -} + 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 -var _DoAutoLinks = function(text) { + return addr; + }; - text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"<a href=\"$1\">$1</a>"); + // + // Swap back in all the special characters we've hidden. + // + var _UnescapeSpecialChars = function(text) { + text = text.replace(/~E(\d+)E/g, function(wholeMatch, m1) { + var charCodeToReplace = parseInt(m1); + return String.fromCharCode(charCodeToReplace); + }); + return text; + }; - // Email addresses: <address@domain.foo> + // + // Remove one level of line-leading tabs or spaces + // + var _Outdent = function(text) { - /* - 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) ); - } - ); + // attacklab: hack around Konqueror 3.5.4 bug: + // "----------bug".replace(/^-/g,"") == "bug" - return text; -} + text = text.replace(/^(\t|[ ]{1,4})/gm, "~0"); // attacklab: g_tab_width + // attacklab: clean up hack + text = text.replace(/~0/g, ""); -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="&#x6D;&#97;&#105;&#108;&#x74;&#111;:&#102;&#111;&#111;&#64;&#101; -// x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;">&#102;&#111;&#111; -// &#64;&#101;x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;</a> -// -// Based on a filter by Matthew Wickline, posted to the BBEdit-Talk -// mailing list: <http://tinyurl.com/yu7ue> -// + return text; + }; - // 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)); - } + // 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. + var _Detab = function(text) { + // expand first n-1 tabs + text = text.replace(/\t(?=\t)/g, " "); // attacklab: g_tab_width - var encode = [ - function(ch){return "&#"+ch.charCodeAt(0)+";";}, - function(ch){return "&#x"+char2hex(ch)+";";}, - function(ch){return ch;} - ]; + // replace the nth with two sentinels + text = text.replace(/\t/g, "~A~B"); - addr = "mailto:" + addr; + // 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 - 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; - }); + // there *must* be a better way to do this: + for (var i = 0; i < numSpaces; i++) leadingText += " "; - addr = "<a href=\"" + addr + "\">" + addr + "</a>"; - addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part + return leadingText; + }); - return addr; -} + // clean up sentinels + text = text.replace(/~A/g, " "); // attacklab: g_tab_width + text = text.replace(/~B/g, ""); + return text; + }; -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; -} + // 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") + "])"; -var _Outdent = function(text) { -// -// Remove one level of line-leading tabs or spaces -// + if (afterBackslash) { + regexString = "\\\\" + regexString; + } - // attacklab: hack around Konqueror 3.5.4 bug: - // "----------bug".replace(/^-/g,"") == "bug" + var regex = new RegExp(regexString, "g"); + text = text.replace(regex, escapeCharacters_callback); - text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width + return text; + }; - // attacklab: clean up hack - text = text.replace(/~0/g,"") - return text; -} + var escapeCharacters_callback = function(wholeMatch, m1) { + var charCodeToEscape = m1.charCodeAt(0); + return "~E" + charCodeToEscape + "E"; + }; -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 Showdown.converter +}; \ No newline at end of file