X-Git-Url: https://git.openstreetmap.org./osqa.git/blobdiff_plain/a54367cd91fc9a232c86a328b3ea5b48a3376112..1cc8105e990487370773f8442f76d8793652cd36:/forum/skins/default/media/js/wmd/showdown.js diff --git a/forum/skins/default/media/js/wmd/showdown.js b/forum/skins/default/media/js/wmd/showdown.js index 0efa6c5..c87fd45 100644 --- a/forum/skins/default/media/js/wmd/showdown.js +++ b/forum/skins/default/media/js/wmd/showdown.js @@ -76,6 +76,23 @@ Attacklab.showdown = Attacklab.showdown || {} // Attacklab.showdown.converter = function() { + +// g_urls and g_titles allow arbitrary user-entered strings as keys. This +// caused an exception (and hence stopped the rendering) when the user entered +// e.g. [push] or [__proto__]. Adding a prefix to the actual key prevents this +// (since no builtin property starts with "s_"). See +// http://meta.stackoverflow.com/questions/64655/strange-wmd-bug +// (granted, switching from Array() to Object() alone would have left only __proto__ +// to be a problem) +var SaveHash = function () { + this.set = function (key, value) { + this["s_" + key] = value; + } + this.get = function (key) { + return this["s_" + key]; + } +} + // // Globals: // @@ -97,13 +114,14 @@ this.makeHtml = function(text) { // _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the // and tags get encoded. // + text = html_sanitize(text, function(url) {return url;}, function(id) {return id;}); // 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_urls = new SaveHash(); + g_titles = new SaveHash(); g_html_blocks = new Array(); // attacklab: Replace ~ with ~T @@ -167,13 +185,15 @@ var _StripLinkDefinitions = function(text) { \n? // maybe *one* newline [ \t]* ? // url = $2 + (?=\s|$) // lookahead for whitespace instead of the lookbehind removed below [ \t]* \n? // maybe one newline [ \t]* - (?: - (\n*) // any lines skipped = $3 attacklab: lookbehind removed + ( // (potential) title = $3 + (\n*) // any lines skipped = $4 attacklab: lookbehind removed + [ \t]+ ["(] - (.+?) // title = $4 + (.+?) // title = $5 [")] [ \t]* )? // title is optional @@ -181,16 +201,16 @@ var _StripLinkDefinitions = function(text) { /gm, function(){...}); */ - var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm, - function (wholeMatch,m1,m2,m3,m4) { + var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm, + function (wholeMatch,m1,m2,m3,m4,m5) { m1 = m1.toLowerCase(); - g_urls[m1] = _EncodeAmpsAndAngles(m2); // Link IDs are case-insensitive - if (m3) { + g_urls.set(m1, _EncodeAmpsAndAngles(m2)); // Link IDs are case-insensitive + if (m4) { // 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,"""); + return m3; + } else if (m5) { + g_titles.set(m1, m5.replace(/"/g,""")); } // Completely remove the definition from the text @@ -202,8 +222,6 @@ var _StripLinkDefinitions = function(text) { } var _HashHTMLBlocks = function(text) { - // attacklab: Double up blank lines to reduce lookaround - text = text.replace(/\n/g,"\n\n"); // Hashify HTML blocks: // We only want to do this for block-level HTML tags, such as headers, @@ -268,9 +286,9 @@ var _HashHTMLBlocks = function(text) { /* text = text.replace(/ + \n // Starting after a blank line + [ ]{0,3} ( // save in $1 - \n\n // Starting after a blank line - [ ]{0,3} (<(hr) // start tag = $2 \b // word break ([^<>])*? // @@ -280,24 +298,24 @@ var _HashHTMLBlocks = function(text) { ) /g,hashElement); */ - text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement); + text = text.replace(/\n[ ]{0,3}((<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement); // Special case for standalone HTML comments: /* text = text.replace(/ + \n\n // Starting after a blank line + [ ]{0,3} // attacklab: g_tab_width - 1 ( // save in $1 - \n\n // Starting after a blank line - [ ]{0,3} // attacklab: g_tab_width - 1 -]|-[^>])(?:[^-]|-[^-])*)--) // see http://www.w3.org/TR/html-markup/syntax.html#comments > [ \t]* (?=\n{2,}) // followed by a blank line ) /g,hashElement); */ - text = text.replace(/(\n\n[ ]{0,3}[ \t]*(?=\n{2,}))/g,hashElement); + text = text.replace(/\n\n[ ]{0,3}(-]|-[^>])(?:[^-]|-[^-])*)--)>[ \t]*(?=\n{2,}))/g, hashElement); // PHP and ASP-style processor instructions ( and <%...%>) @@ -320,8 +338,6 @@ var _HashHTMLBlocks = function(text) { */ 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; } @@ -329,8 +345,7 @@ var hashElement = function(wholeMatch,m1) { var blockText = m1; // Undo double lines - blockText = blockText.replace(/\n\n/g,"\n"); - blockText = blockText.replace(/^\n/,""); + blockText = blockText.replace(/^\n+/,""); // strip trailing blank lines blockText = blockText.replace(/\n+$/g,""); @@ -341,7 +356,7 @@ var hashElement = function(wholeMatch,m1) { return blockText; }; -var _RunBlockGamut = function(text) { +var _RunBlockGamut = function(text, doNotUnhash) { // // These are all the transformations that form block-level // tags like paragraphs, headers, and list items. @@ -363,7 +378,7 @@ var _RunBlockGamut = function(text) { // we're escaping the markup we've just created, so that we don't wrap //

tags around block-level tags. text = _HashHTMLBlocks(text); - text = _FormParagraphs(text); + text = _FormParagraphs(text, doNotUnhash); return text; } @@ -404,8 +419,11 @@ var _EscapeSpecialCharsWithinTagAttributes = function(text) { // // Build a regex to find HTML tags and comments. See Friedl's - // "Mastering Regular Expressions", 2nd Ed., pp. 200-201. - var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi; + // "Mastering Regular Expressions", 2nd Ed., pp. 200-201. + + // SE: changed the comment part of the regex + + var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|-]|-[^>])(?:[^-]|-[^-])*)--)>)/gi; text = text.replace(regex, function(wholeMatch) { var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`"); @@ -454,20 +472,26 @@ var _DoAnchors = function(text) { /* text = text.replace(/ - ( // wrap whole match in $1 - \[ + ( // wrap whole match in $1 + \[ ( (?: \[[^\]]*\] // allow brackets nested one level - | - [^\[\]] // or anything else - ) - ) + | + [^\[\]] // or anything else + )* + ) \] \( // literal paren [ \t]* () // no id, so leave $3 empty - ? // href = $4 + ? [ \t]* ( // $5 (['"]) // quote char = $6 @@ -479,7 +503,8 @@ var _DoAnchors = function(text) { ) /g,writeAnchorTag); */ - text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag); + + text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag); // // Last, handle reference-style shortcuts: [link text] @@ -516,10 +541,10 @@ var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) { } 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]; + if (g_urls.get(link_id) != undefined) { + url = g_urls.get(link_id); + if (g_titles.get(link_id) != undefined) { + title = g_titles.get(link_id); } } else { @@ -621,10 +646,10 @@ var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) { } 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]; + if (g_urls.get(link_id) != undefined) { + url = g_urls.get(link_id); + if (g_titles.get(link_id) != undefined) { + title = g_titles.get(link_id); } } else { @@ -661,10 +686,10 @@ var _DoHeaders = function(text) { // -------- // text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm, - function(wholeMatch,m1){return hashBlock("

" + _RunSpanGamut(m1) + "

");}); + function(wholeMatch,m1){return "

" + _RunSpanGamut(m1) + "

\n\n";}); text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, - function(matchFound,m1){return hashBlock("

" + _RunSpanGamut(m1) + "

");}); + function(matchFound,m1){return "

" + _RunSpanGamut(m1) + "

\n\n";}); // atx-style headers: // # Header 1 @@ -688,7 +713,7 @@ var _DoHeaders = function(text) { text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm, function(wholeMatch,m1,m2) { var h_level = m1.length; - return hashBlock("" + _RunSpanGamut(m2) + ""); + return "" + _RunSpanGamut(m2) + "\n\n"; }); return text; @@ -736,10 +761,7 @@ var _DoLists = function(text) { var list = m1; var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol"; - // Turn double returns into triple returns, so that we can make a - // paragraph for the last item in a list, if necessary: - list = list.replace(/\n{2,}/g,"\n\n\n");; - var result = _ProcessListItems(list); + var result = _ProcessListItems(list, list_type); // Trim any trailing whitespace, to put the closing `` // up on the preceding line, to get it past the current stupid @@ -756,10 +778,7 @@ var _DoLists = function(text) { var list = m2; var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol"; - // Turn double returns into triple returns, so that we can make a - // paragraph for the last item in a list, if necessary: - var list = list.replace(/\n{2,}/g,"\n\n\n");; - var result = _ProcessListItems(list); + var result = _ProcessListItems(list, list_type); result = runup + "<"+list_type+">\n" + result + "\n"; return result; }); @@ -771,11 +790,15 @@ var _DoLists = function(text) { return text; } -_ProcessListItems = function(list_str) { +var _listItemMarkers = { ol: "\\d+[.]", ul: "[*+-]" }; + +_ProcessListItems = function(list_str, list_type) { // // Process the contents of a single ordered or unordered list, splitting it // into individual list items. // +// list_type is either "ul" or "ol". + // 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. @@ -805,32 +828,47 @@ _ProcessListItems = function(list_str) { // attacklab: add sentinel to emulate \z list_str += "~0"; + // In the original attacklab WMD, list_type was not given to this function, and anything + // that matched /[*+-]|\d+[.]/ would just create the next
  • , causing this mismatch: + // + // Markdown rendered by WMD rendered by MarkdownSharp + // ------------------------------------------------------------------ + // 1. first 1. first 1. first + // 2. second 2. second 2. second + // - third 3. third * third + // + // We changed this to behave identical to MarkdownSharp. This is the constructed RegEx, + // with {MARKER} being one of \d+[.] or [*+-], depending on list_type: /* 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]+)) + (^[ \t]*) // leading whitespace = $1 + ({MARKER}) [ \t]+ // list marker = $2 + ([^\r]+? // list item text = $3 + (\n+)) + (?= (~0 | \2 ({MARKER}) [ \t]+)) /gm, function(){...}); */ - list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm, - function(wholeMatch,m1,m2,m3,m4){ - var item = m4; - var leading_line = m1; - var leading_space = m2; - - if (leading_line || (item.search(/\n{2,}/)>-1)) { - item = _RunBlockGamut(_Outdent(item)); + + var marker = _listItemMarkers[list_type]; + var re = new RegExp("(^[ \\t]*)(" + marker + ")[ \\t]+([^\\r]+?(\\n+))(?=(~0|\\1(" + marker + ")[ \\t]+))", "gm"); + var last_item_had_a_double_newline = false; + list_str = list_str.replace(re, + function(wholeMatch,m1,m2,m3){ + var item = m3; + var leading_space = m1; + var ends_with_double_newline = /\n\n$/.test(item); + var contains_double_newline = ends_with_double_newline || item.search(/\n{2,}/)>-1; + + if (contains_double_newline || last_item_had_a_double_newline) { + item = _RunBlockGamut(_Outdent(item), /* doNotUnhash = */ true); } else { // Recursion for sub-lists: item = _DoLists(_Outdent(item)); item = item.replace(/\n$/,""); // chomp(item) item = _RunSpanGamut(item); - } - + } + last_item_had_a_double_newline = ends_with_double_newline; return "
  • " + item + "
  • \n"; } ); @@ -876,7 +914,7 @@ var _DoCodeBlocks = function(text) { codeblock = "
    " + codeblock + "\n
    "; - return hashBlock(codeblock) + nextChar; + return "\n\n" + codeblock + "\n\n" + nextChar; } ); @@ -1036,7 +1074,7 @@ var _DoBlockQuotes = function(text) { } -var _FormParagraphs = function(text) { +var _FormParagraphs = function(text, doNotUnhash) { // // Params: // $text - string to process with html

    tags @@ -1068,20 +1106,20 @@ var _FormParagraphs = function(text) { } } - // // Unhashify HTML blocks // - end = grafsOut.length; - for (var i=0; i= 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); - } - } - + if (!doNotUnhash) { + end = grafsOut.length; + for (var i=0; i= 0) { + var blockText = g_html_blocks[RegExp.$1]; + blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs + grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText); + } + } + } return grafsOut.join("\n\n"); } @@ -1235,38 +1273,25 @@ var _Outdent = function(text) { return text; } -var _Detab = function(text) { -// attacklab: Detab's completely rewritten for speed. -// In perl we could fix it by anchoring the regexp with \G. -// In javascript we're less fortunate. - - // expand first n-1 tabs - text = text.replace(/\t(?=\t)/g," "); // attacklab: g_tab_width - - // replace the nth with two sentinels - text = text.replace(/\t/g,"~A~B"); +var _Detab = function (text) { + if (!/\t/.test(text)) + return text; - // 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