Commit 1ec3bd3f8d237c1afb73d4ad1b7a0c24f9a6887c

Authored by Nihad Abbasov
1 parent 34ba4e13

ability to preview notes

app/assets/javascripts/application.js
... ... @@ -18,6 +18,7 @@
18 18 //= require chosen-jquery
19 19 //= require raphael
20 20 //= require branch-graph
  21 +//= require Markdown.Converter
21 22 //= require_tree .
22 23  
23 24 $(document).ready(function(){
... ... @@ -26,7 +27,6 @@ $(document).ready(function(){
26 27 $(this).select();
27 28 });
28 29  
29   -
30 30 $('body').on('ajax:complete, ajax:beforeSend, submit', 'form', function(e){
31 31 var buttons = $('[type="submit"]', this);
32 32 switch( e.type ){
... ... @@ -70,6 +70,24 @@ $(document).ready(function(){
70 70 $(".supp_diff_link").bind("click", function() {
71 71 showDiff(this);
72 72 });
  73 +
  74 + /**
  75 + * Note markdown preview
  76 + *
  77 + */
  78 + $('#preview-link').on('click', function(e) {
  79 + var note = $('#note_note').val();
  80 + if (note.trim().length === 0) { note = 'Nothing to preview'; }
  81 + var converter = new Markdown.Converter();
  82 + var md_preview = converter.makeHtml(note);
  83 + $('#preview-note').html(md_preview);
  84 +
  85 + var previewLinkText = ($(this).text() == 'Preview' ? 'Edit' : 'Preview');
  86 + $(this).text(previewLinkText);
  87 +
  88 + $('#preview-note, #note_note').toggle();
  89 + e.preventDefault();
  90 + });
73 91 });
74 92  
75 93 function focusSearch() {
... ... @@ -108,6 +126,6 @@ function showDiff(link) {
108 126 })(jQuery);
109 127  
110 128  
111   -function ajaxGet(url) {
112   - $.ajax({type: "GET", url: url, dataType: "script"});
  129 +function ajaxGet(url) {
  130 + $.ajax({type: "GET", url: url, dataType: "script"});
113 131 }
... ...
app/views/notes/_form.html.haml
... ... @@ -7,10 +7,12 @@
7 7  
8 8 = f.hidden_field :noteable_id
9 9 = f.hidden_field :noteable_type
10   - = f.text_area :note, :size => 255
  10 + = f.text_area :note, :size => 255
  11 + #preview-note.well.hide
11 12 %p.hint
12 13 = link_to "Gitlab Markdown", help_markdown_path, :target => '_blank'
13 14 is enabled.
  15 + = link_to 'Preview', '#', :id => 'preview-link'
14 16  
15 17 .row.note_advanced_opts.hide
16 18 .span4
... ...
vendor/assets/javascripts/Markdown.Converter.js 0 → 100644
... ... @@ -0,0 +1,1332 @@
  1 +var Markdown;
  2 +
  3 +if (typeof exports === "object" && typeof require === "function") // we're in a CommonJS (e.g. Node.js) module
  4 + Markdown = exports;
  5 +else
  6 + Markdown = {};
  7 +
  8 +// The following text is included for historical reasons, but should
  9 +// be taken with a pinch of salt; it's not all true anymore.
  10 +
  11 +//
  12 +// Wherever possible, Showdown is a straight, line-by-line port
  13 +// of the Perl version of Markdown.
  14 +//
  15 +// This is not a normal parser design; it's basically just a
  16 +// series of string substitutions. It's hard to read and
  17 +// maintain this way, but keeping Showdown close to the original
  18 +// design makes it easier to port new features.
  19 +//
  20 +// More importantly, Showdown behaves like markdown.pl in most
  21 +// edge cases. So web applications can do client-side preview
  22 +// in Javascript, and then build identical HTML on the server.
  23 +//
  24 +// This port needs the new RegExp functionality of ECMA 262,
  25 +// 3rd Edition (i.e. Javascript 1.5). Most modern web browsers
  26 +// should do fine. Even with the new regular expression features,
  27 +// We do a lot of work to emulate Perl's regex functionality.
  28 +// The tricky changes in this file mostly have the "attacklab:"
  29 +// label. Major or self-explanatory changes don't.
  30 +//
  31 +// Smart diff tools like Araxis Merge will be able to match up
  32 +// this file with markdown.pl in a useful way. A little tweaking
  33 +// helps: in a copy of markdown.pl, replace "#" with "//" and
  34 +// replace "$text" with "text". Be sure to ignore whitespace
  35 +// and line endings.
  36 +//
  37 +
  38 +
  39 +//
  40 +// Usage:
  41 +//
  42 +// var text = "Markdown *rocks*.";
  43 +//
  44 +// var converter = new Markdown.Converter();
  45 +// var html = converter.makeHtml(text);
  46 +//
  47 +// alert(html);
  48 +//
  49 +// Note: move the sample code to the bottom of this
  50 +// file before uncommenting it.
  51 +//
  52 +
  53 +(function () {
  54 +
  55 + function identity(x) { return x; }
  56 + function returnFalse(x) { return false; }
  57 +
  58 + function HookCollection() { }
  59 +
  60 + HookCollection.prototype = {
  61 +
  62 + chain: function (hookname, func) {
  63 + var original = this[hookname];
  64 + if (!original)
  65 + throw new Error("unknown hook " + hookname);
  66 +
  67 + if (original === identity)
  68 + this[hookname] = func;
  69 + else
  70 + this[hookname] = function (x) { return func(original(x)); }
  71 + },
  72 + set: function (hookname, func) {
  73 + if (!this[hookname])
  74 + throw new Error("unknown hook " + hookname);
  75 + this[hookname] = func;
  76 + },
  77 + addNoop: function (hookname) {
  78 + this[hookname] = identity;
  79 + },
  80 + addFalse: function (hookname) {
  81 + this[hookname] = returnFalse;
  82 + }
  83 + };
  84 +
  85 + Markdown.HookCollection = HookCollection;
  86 +
  87 + // g_urls and g_titles allow arbitrary user-entered strings as keys. This
  88 + // caused an exception (and hence stopped the rendering) when the user entered
  89 + // e.g. [push] or [__proto__]. Adding a prefix to the actual key prevents this
  90 + // (since no builtin property starts with "s_"). See
  91 + // http://meta.stackoverflow.com/questions/64655/strange-wmd-bug
  92 + // (granted, switching from Array() to Object() alone would have left only __proto__
  93 + // to be a problem)
  94 + function SaveHash() { }
  95 + SaveHash.prototype = {
  96 + set: function (key, value) {
  97 + this["s_" + key] = value;
  98 + },
  99 + get: function (key) {
  100 + return this["s_" + key];
  101 + }
  102 + };
  103 +
  104 + Markdown.Converter = function () {
  105 + var pluginHooks = this.hooks = new HookCollection();
  106 + pluginHooks.addNoop("plainLinkText"); // given a URL that was encountered by itself (without markup), should return the link text that's to be given to this link
  107 + pluginHooks.addNoop("preConversion"); // called with the orignal text as given to makeHtml. The result of this plugin hook is the actual markdown source that will be cooked
  108 + pluginHooks.addNoop("postConversion"); // called with the final cooked HTML code. The result of this plugin hook is the actual output of makeHtml
  109 +
  110 + //
  111 + // Private state of the converter instance:
  112 + //
  113 +
  114 + // Global hashes, used by various utility routines
  115 + var g_urls;
  116 + var g_titles;
  117 + var g_html_blocks;
  118 +
  119 + // Used to track when we're inside an ordered or unordered list
  120 + // (see _ProcessListItems() for details):
  121 + var g_list_level;
  122 +
  123 + this.makeHtml = function (text) {
  124 +
  125 + //
  126 + // Main function. The order in which other subs are called here is
  127 + // essential. Link and image substitutions need to happen before
  128 + // _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>
  129 + // and <img> tags get encoded.
  130 + //
  131 +
  132 + // This will only happen if makeHtml on the same converter instance is called from a plugin hook.
  133 + // Don't do that.
  134 + if (g_urls)
  135 + throw new Error("Recursive call to converter.makeHtml");
  136 +
  137 + // Create the private state objects.
  138 + g_urls = new SaveHash();
  139 + g_titles = new SaveHash();
  140 + g_html_blocks = [];
  141 + g_list_level = 0;
  142 +
  143 + text = pluginHooks.preConversion(text);
  144 +
  145 + // attacklab: Replace ~ with ~T
  146 + // This lets us use tilde as an escape char to avoid md5 hashes
  147 + // The choice of character is arbitray; anything that isn't
  148 + // magic in Markdown will work.
  149 + text = text.replace(/~/g, "~T");
  150 +
  151 + // attacklab: Replace $ with ~D
  152 + // RegExp interprets $ as a special character
  153 + // when it's in a replacement string
  154 + text = text.replace(/\$/g, "~D");
  155 +
  156 + // Standardize line endings
  157 + text = text.replace(/\r\n/g, "\n"); // DOS to Unix
  158 + text = text.replace(/\r/g, "\n"); // Mac to Unix
  159 +
  160 + // Make sure text begins and ends with a couple of newlines:
  161 + text = "\n\n" + text + "\n\n";
  162 +
  163 + // Convert all tabs to spaces.
  164 + text = _Detab(text);
  165 +
  166 + // Strip any lines consisting only of spaces and tabs.
  167 + // This makes subsequent regexen easier to write, because we can
  168 + // match consecutive blank lines with /\n+/ instead of something
  169 + // contorted like /[ \t]*\n+/ .
  170 + text = text.replace(/^[ \t]+$/mg, "");
  171 +
  172 + // Turn block-level HTML blocks into hash entries
  173 + text = _HashHTMLBlocks(text);
  174 +
  175 + // Strip link definitions, store in hashes.
  176 + text = _StripLinkDefinitions(text);
  177 +
  178 + text = _RunBlockGamut(text);
  179 +
  180 + text = _UnescapeSpecialChars(text);
  181 +
  182 + // attacklab: Restore dollar signs
  183 + text = text.replace(/~D/g, "$$");
  184 +
  185 + // attacklab: Restore tildes
  186 + text = text.replace(/~T/g, "~");
  187 +
  188 + text = pluginHooks.postConversion(text);
  189 +
  190 + g_html_blocks = g_titles = g_urls = null;
  191 +
  192 + return text;
  193 + };
  194 +
  195 + function _StripLinkDefinitions(text) {
  196 + //
  197 + // Strips link definitions from text, stores the URLs and titles in
  198 + // hash references.
  199 + //
  200 +
  201 + // Link defs are in the form: ^[id]: url "optional title"
  202 +
  203 + /*
  204 + text = text.replace(/
  205 + ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1
  206 + [ \t]*
  207 + \n? // maybe *one* newline
  208 + [ \t]*
  209 + <?(\S+?)>? // url = $2
  210 + (?=\s|$) // lookahead for whitespace instead of the lookbehind removed below
  211 + [ \t]*
  212 + \n? // maybe one newline
  213 + [ \t]*
  214 + ( // (potential) title = $3
  215 + (\n*) // any lines skipped = $4 attacklab: lookbehind removed
  216 + [ \t]+
  217 + ["(]
  218 + (.+?) // title = $5
  219 + [")]
  220 + [ \t]*
  221 + )? // title is optional
  222 + (?:\n+|$)
  223 + /gm, function(){...});
  224 + */
  225 +
  226 + text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm,
  227 + function (wholeMatch, m1, m2, m3, m4, m5) {
  228 + m1 = m1.toLowerCase();
  229 + g_urls.set(m1, _EncodeAmpsAndAngles(m2)); // Link IDs are case-insensitive
  230 + if (m4) {
  231 + // Oops, found blank lines, so it's not a title.
  232 + // Put back the parenthetical statement we stole.
  233 + return m3;
  234 + } else if (m5) {
  235 + g_titles.set(m1, m5.replace(/"/g, "&quot;"));
  236 + }
  237 +
  238 + // Completely remove the definition from the text
  239 + return "";
  240 + }
  241 + );
  242 +
  243 + return text;
  244 + }
  245 +
  246 + function _HashHTMLBlocks(text) {
  247 +
  248 + // Hashify HTML blocks:
  249 + // We only want to do this for block-level HTML tags, such as headers,
  250 + // lists, and tables. That's because we still want to wrap <p>s around
  251 + // "paragraphs" that are wrapped in non-block-level tags, such as anchors,
  252 + // phrase emphasis, and spans. The list of tags we're looking for is
  253 + // hard-coded:
  254 + var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"
  255 + var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"
  256 +
  257 + // First, look for nested blocks, e.g.:
  258 + // <div>
  259 + // <div>
  260 + // tags for inner block must be indented.
  261 + // </div>
  262 + // </div>
  263 + //
  264 + // The outermost tags must start at the left margin for this to match, and
  265 + // the inner nested divs must be indented.
  266 + // We need to do this before the next, more liberal match, because the next
  267 + // match will start at the first `<div>` and stop at the first `</div>`.
  268 +
  269 + // attacklab: This regex can be expensive when it fails.
  270 +
  271 + /*
  272 + text = text.replace(/
  273 + ( // save in $1
  274 + ^ // start of line (with /m)
  275 + <($block_tags_a) // start tag = $2
  276 + \b // word break
  277 + // attacklab: hack around khtml/pcre bug...
  278 + [^\r]*?\n // any number of lines, minimally matching
  279 + </\2> // the matching end tag
  280 + [ \t]* // trailing spaces/tabs
  281 + (?=\n+) // followed by a newline
  282 + ) // attacklab: there are sentinel newlines at end of document
  283 + /gm,function(){...}};
  284 + */
  285 + 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);
  286 +
  287 + //
  288 + // Now match more liberally, simply from `\n<tag>` to `</tag>\n`
  289 + //
  290 +
  291 + /*
  292 + text = text.replace(/
  293 + ( // save in $1
  294 + ^ // start of line (with /m)
  295 + <($block_tags_b) // start tag = $2
  296 + \b // word break
  297 + // attacklab: hack around khtml/pcre bug...
  298 + [^\r]*? // any number of lines, minimally matching
  299 + .*</\2> // the matching end tag
  300 + [ \t]* // trailing spaces/tabs
  301 + (?=\n+) // followed by a newline
  302 + ) // attacklab: there are sentinel newlines at end of document
  303 + /gm,function(){...}};
  304 + */
  305 + 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);
  306 +
  307 + // Special case just for <hr />. It was easier to make a special case than
  308 + // to make the other regex more complicated.
  309 +
  310 + /*
  311 + text = text.replace(/
  312 + \n // Starting after a blank line
  313 + [ ]{0,3}
  314 + ( // save in $1
  315 + (<(hr) // start tag = $2
  316 + \b // word break
  317 + ([^<>])*?
  318 + \/?>) // the matching end tag
  319 + [ \t]*
  320 + (?=\n{2,}) // followed by a blank line
  321 + )
  322 + /g,hashElement);
  323 + */
  324 + text = text.replace(/\n[ ]{0,3}((<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g, hashElement);
  325 +
  326 + // Special case for standalone HTML comments:
  327 +
  328 + /*
  329 + text = text.replace(/
  330 + \n\n // Starting after a blank line
  331 + [ ]{0,3} // attacklab: g_tab_width - 1
  332 + ( // save in $1
  333 + <!
  334 + (--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--) // see http://www.w3.org/TR/html-markup/syntax.html#comments and http://meta.stackoverflow.com/q/95256
  335 + >
  336 + [ \t]*
  337 + (?=\n{2,}) // followed by a blank line
  338 + )
  339 + /g,hashElement);
  340 + */
  341 + text = text.replace(/\n\n[ ]{0,3}(<!(--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)>[ \t]*(?=\n{2,}))/g, hashElement);
  342 +
  343 + // PHP and ASP-style processor instructions (<?...?> and <%...%>)
  344 +
  345 + /*
  346 + text = text.replace(/
  347 + (?:
  348 + \n\n // Starting after a blank line
  349 + )
  350 + ( // save in $1
  351 + [ ]{0,3} // attacklab: g_tab_width - 1
  352 + (?:
  353 + <([?%]) // $2
  354 + [^\r]*?
  355 + \2>
  356 + )
  357 + [ \t]*
  358 + (?=\n{2,}) // followed by a blank line
  359 + )
  360 + /g,hashElement);
  361 + */
  362 + text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g, hashElement);
  363 +
  364 + return text;
  365 + }
  366 +
  367 + function hashElement(wholeMatch, m1) {
  368 + var blockText = m1;
  369 +
  370 + // Undo double lines
  371 + blockText = blockText.replace(/^\n+/, "");
  372 +
  373 + // strip trailing blank lines
  374 + blockText = blockText.replace(/\n+$/g, "");
  375 +
  376 + // Replace the element text with a marker ("~KxK" where x is its key)
  377 + blockText = "\n\n~K" + (g_html_blocks.push(blockText) - 1) + "K\n\n";
  378 +
  379 + return blockText;
  380 + }
  381 +
  382 + function _RunBlockGamut(text, doNotUnhash) {
  383 + //
  384 + // These are all the transformations that form block-level
  385 + // tags like paragraphs, headers, and list items.
  386 + //
  387 + text = _DoHeaders(text);
  388 +
  389 + // Do Horizontal Rules:
  390 + var replacement = "<hr />\n";
  391 + text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, replacement);
  392 + text = text.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm, replacement);
  393 + text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm, replacement);
  394 +
  395 + text = _DoLists(text);
  396 + text = _DoCodeBlocks(text);
  397 + text = _DoBlockQuotes(text);
  398 +
  399 + // We already ran _HashHTMLBlocks() before, in Markdown(), but that
  400 + // was to escape raw HTML in the original Markdown source. This time,
  401 + // we're escaping the markup we've just created, so that we don't wrap
  402 + // <p> tags around block-level tags.
  403 + text = _HashHTMLBlocks(text);
  404 + text = _FormParagraphs(text, doNotUnhash);
  405 +
  406 + return text;
  407 + }
  408 +
  409 + function _RunSpanGamut(text) {
  410 + //
  411 + // These are all the transformations that occur *within* block-level
  412 + // tags like paragraphs, headers, and list items.
  413 + //
  414 +
  415 + text = _DoCodeSpans(text);
  416 + text = _EscapeSpecialCharsWithinTagAttributes(text);
  417 + text = _EncodeBackslashEscapes(text);
  418 +
  419 + // Process anchor and image tags. Images must come first,
  420 + // because ![foo][f] looks like an anchor.
  421 + text = _DoImages(text);
  422 + text = _DoAnchors(text);
  423 +
  424 + // Make links out of things like `<http://example.com/>`
  425 + // Must come after _DoAnchors(), because you can use < and >
  426 + // delimiters in inline links like [this](<url>).
  427 + text = _DoAutoLinks(text);
  428 +
  429 + text = text.replace(/~P/g, "://"); // put in place to prevent autolinking; reset now
  430 +
  431 + text = _EncodeAmpsAndAngles(text);
  432 + text = _DoItalicsAndBold(text);
  433 +
  434 + // Do hard breaks:
  435 + text = text.replace(/ +\n/g, " <br>\n");
  436 +
  437 + return text;
  438 + }
  439 +
  440 + function _EscapeSpecialCharsWithinTagAttributes(text) {
  441 + //
  442 + // Within tags -- meaning between < and > -- encode [\ ` * _] so they
  443 + // don't conflict with their use in Markdown for code, italics and strong.
  444 + //
  445 +
  446 + // Build a regex to find HTML tags and comments. See Friedl's
  447 + // "Mastering Regular Expressions", 2nd Ed., pp. 200-201.
  448 +
  449 + // SE: changed the comment part of the regex
  450 +
  451 + var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)>)/gi;
  452 +
  453 + text = text.replace(regex, function (wholeMatch) {
  454 + var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, "$1`");
  455 + tag = escapeCharacters(tag, wholeMatch.charAt(1) == "!" ? "\\`*_/" : "\\`*_"); // also escape slashes in comments to prevent autolinking there -- http://meta.stackoverflow.com/questions/95987
  456 + return tag;
  457 + });
  458 +
  459 + return text;
  460 + }
  461 +
  462 + function _DoAnchors(text) {
  463 + //
  464 + // Turn Markdown link shortcuts into XHTML <a> tags.
  465 + //
  466 + //
  467 + // First, handle reference-style links: [link text] [id]
  468 + //
  469 +
  470 + /*
  471 + text = text.replace(/
  472 + ( // wrap whole match in $1
  473 + \[
  474 + (
  475 + (?:
  476 + \[[^\]]*\] // allow brackets nested one level
  477 + |
  478 + [^\[] // or anything else
  479 + )*
  480 + )
  481 + \]
  482 +
  483 + [ ]? // one optional space
  484 + (?:\n[ ]*)? // one optional newline followed by spaces
  485 +
  486 + \[
  487 + (.*?) // id = $3
  488 + \]
  489 + )
  490 + ()()()() // pad remaining backreferences
  491 + /g, writeAnchorTag);
  492 + */
  493 + text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeAnchorTag);
  494 +
  495 + //
  496 + // Next, inline-style links: [link text](url "optional title")
  497 + //
  498 +
  499 + /*
  500 + text = text.replace(/
  501 + ( // wrap whole match in $1
  502 + \[
  503 + (
  504 + (?:
  505 + \[[^\]]*\] // allow brackets nested one level
  506 + |
  507 + [^\[\]] // or anything else
  508 + )*
  509 + )
  510 + \]
  511 + \( // literal paren
  512 + [ \t]*
  513 + () // no id, so leave $3 empty
  514 + <?( // href = $4
  515 + (?:
  516 + \([^)]*\) // allow one level of (correctly nested) parens (think MSDN)
  517 + |
  518 + [^()\s]
  519 + )*?
  520 + )>?
  521 + [ \t]*
  522 + ( // $5
  523 + (['"]) // quote char = $6
  524 + (.*?) // Title = $7
  525 + \6 // matching quote
  526 + [ \t]* // ignore any spaces/tabs between closing quote and )
  527 + )? // title is optional
  528 + \)
  529 + )
  530 + /g, writeAnchorTag);
  531 + */
  532 +
  533 + text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?((?:\([^)]*\)|[^()\s])*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeAnchorTag);
  534 +
  535 + //
  536 + // Last, handle reference-style shortcuts: [link text]
  537 + // These must come last in case you've also got [link test][1]
  538 + // or [link test](/foo)
  539 + //
  540 +
  541 + /*
  542 + text = text.replace(/
  543 + ( // wrap whole match in $1
  544 + \[
  545 + ([^\[\]]+) // link text = $2; can't contain '[' or ']'
  546 + \]
  547 + )
  548 + ()()()()() // pad rest of backreferences
  549 + /g, writeAnchorTag);
  550 + */
  551 + text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);
  552 +
  553 + return text;
  554 + }
  555 +
  556 + function writeAnchorTag(wholeMatch, m1, m2, m3, m4, m5, m6, m7) {
  557 + if (m7 == undefined) m7 = "";
  558 + var whole_match = m1;
  559 + var link_text = m2.replace(/:\/\//g, "~P"); // to prevent auto-linking withing the link. will be converted back after the auto-linker runs
  560 + var link_id = m3.toLowerCase();
  561 + var url = m4;
  562 + var title = m7;
  563 +
  564 + if (url == "") {
  565 + if (link_id == "") {
  566 + // lower-case and turn embedded newlines into spaces
  567 + link_id = link_text.toLowerCase().replace(/ ?\n/g, " ");
  568 + }
  569 + url = "#" + link_id;
  570 +
  571 + if (g_urls.get(link_id) != undefined) {
  572 + url = g_urls.get(link_id);
  573 + if (g_titles.get(link_id) != undefined) {
  574 + title = g_titles.get(link_id);
  575 + }
  576 + }
  577 + else {
  578 + if (whole_match.search(/\(\s*\)$/m) > -1) {
  579 + // Special case for explicit empty url
  580 + url = "";
  581 + } else {
  582 + return whole_match;
  583 + }
  584 + }
  585 + }
  586 + url = encodeProblemUrlChars(url);
  587 + url = escapeCharacters(url, "*_");
  588 + var result = "<a href=\"" + url + "\"";
  589 +
  590 + if (title != "") {
  591 + title = attributeEncode(title);
  592 + title = escapeCharacters(title, "*_");
  593 + result += " title=\"" + title + "\"";
  594 + }
  595 +
  596 + result += ">" + link_text + "</a>";
  597 +
  598 + return result;
  599 + }
  600 +
  601 + function _DoImages(text) {
  602 + //
  603 + // Turn Markdown image shortcuts into <img> tags.
  604 + //
  605 +
  606 + //
  607 + // First, handle reference-style labeled images: ![alt text][id]
  608 + //
  609 +
  610 + /*
  611 + text = text.replace(/
  612 + ( // wrap whole match in $1
  613 + !\[
  614 + (.*?) // alt text = $2
  615 + \]
  616 +
  617 + [ ]? // one optional space
  618 + (?:\n[ ]*)? // one optional newline followed by spaces
  619 +
  620 + \[
  621 + (.*?) // id = $3
  622 + \]
  623 + )
  624 + ()()()() // pad rest of backreferences
  625 + /g, writeImageTag);
  626 + */
  627 + text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeImageTag);
  628 +
  629 + //
  630 + // Next, handle inline images: ![alt text](url "optional title")
  631 + // Don't forget: encode * and _
  632 +
  633 + /*
  634 + text = text.replace(/
  635 + ( // wrap whole match in $1
  636 + !\[
  637 + (.*?) // alt text = $2
  638 + \]
  639 + \s? // One optional whitespace character
  640 + \( // literal paren
  641 + [ \t]*
  642 + () // no id, so leave $3 empty
  643 + <?(\S+?)>? // src url = $4
  644 + [ \t]*
  645 + ( // $5
  646 + (['"]) // quote char = $6
  647 + (.*?) // title = $7
  648 + \6 // matching quote
  649 + [ \t]*
  650 + )? // title is optional
  651 + \)
  652 + )
  653 + /g, writeImageTag);
  654 + */
  655 + text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeImageTag);
  656 +
  657 + return text;
  658 + }
  659 +
  660 + function attributeEncode(text) {
  661 + // unconditionally replace angle brackets here -- what ends up in an attribute (e.g. alt or title)
  662 + // never makes sense to have verbatim HTML in it (and the sanitizer would totally break it)
  663 + return text.replace(/>/g, "&gt;").replace(/</g, "&lt;").replace(/"/g, "&quot;");
  664 + }
  665 +
  666 + function writeImageTag(wholeMatch, m1, m2, m3, m4, m5, m6, m7) {
  667 + var whole_match = m1;
  668 + var alt_text = m2;
  669 + var link_id = m3.toLowerCase();
  670 + var url = m4;
  671 + var title = m7;
  672 +
  673 + if (!title) title = "";
  674 +
  675 + if (url == "") {
  676 + if (link_id == "") {
  677 + // lower-case and turn embedded newlines into spaces
  678 + link_id = alt_text.toLowerCase().replace(/ ?\n/g, " ");
  679 + }
  680 + url = "#" + link_id;
  681 +
  682 + if (g_urls.get(link_id) != undefined) {
  683 + url = g_urls.get(link_id);
  684 + if (g_titles.get(link_id) != undefined) {
  685 + title = g_titles.get(link_id);
  686 + }
  687 + }
  688 + else {
  689 + return whole_match;
  690 + }
  691 + }
  692 +
  693 + alt_text = escapeCharacters(attributeEncode(alt_text), "*_[]()");
  694 + url = escapeCharacters(url, "*_");
  695 + var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\"";
  696 +
  697 + // attacklab: Markdown.pl adds empty title attributes to images.
  698 + // Replicate this bug.
  699 +
  700 + //if (title != "") {
  701 + title = attributeEncode(title);
  702 + title = escapeCharacters(title, "*_");
  703 + result += " title=\"" + title + "\"";
  704 + //}
  705 +
  706 + result += " />";
  707 +
  708 + return result;
  709 + }
  710 +
  711 + function _DoHeaders(text) {
  712 +
  713 + // Setext-style headers:
  714 + // Header 1
  715 + // ========
  716 + //
  717 + // Header 2
  718 + // --------
  719 + //
  720 + text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,
  721 + function (wholeMatch, m1) { return "<h1>" + _RunSpanGamut(m1) + "</h1>\n\n"; }
  722 + );
  723 +
  724 + text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,
  725 + function (matchFound, m1) { return "<h2>" + _RunSpanGamut(m1) + "</h2>\n\n"; }
  726 + );
  727 +
  728 + // atx-style headers:
  729 + // # Header 1
  730 + // ## Header 2
  731 + // ## Header 2 with closing hashes ##
  732 + // ...
  733 + // ###### Header 6
  734 + //
  735 +
  736 + /*
  737 + text = text.replace(/
  738 + ^(\#{1,6}) // $1 = string of #'s
  739 + [ \t]*
  740 + (.+?) // $2 = Header text
  741 + [ \t]*
  742 + \#* // optional closing #'s (not counted)
  743 + \n+
  744 + /gm, function() {...});
  745 + */
  746 +
  747 + text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,
  748 + function (wholeMatch, m1, m2) {
  749 + var h_level = m1.length;
  750 + return "<h" + h_level + ">" + _RunSpanGamut(m2) + "</h" + h_level + ">\n\n";
  751 + }
  752 + );
  753 +
  754 + return text;
  755 + }
  756 +
  757 + function _DoLists(text) {
  758 + //
  759 + // Form HTML ordered (numbered) and unordered (bulleted) lists.
  760 + //
  761 +
  762 + // attacklab: add sentinel to hack around khtml/safari bug:
  763 + // http://bugs.webkit.org/show_bug.cgi?id=11231
  764 + text += "~0";
  765 +
  766 + // Re-usable pattern to match any entirel ul or ol list:
  767 +
  768 + /*
  769 + var whole_list = /
  770 + ( // $1 = whole list
  771 + ( // $2
  772 + [ ]{0,3} // attacklab: g_tab_width - 1
  773 + ([*+-]|\d+[.]) // $3 = first list item marker
  774 + [ \t]+
  775 + )
  776 + [^\r]+?
  777 + ( // $4
  778 + ~0 // sentinel for workaround; should be $
  779 + |
  780 + \n{2,}
  781 + (?=\S)
  782 + (?! // Negative lookahead for another list item marker
  783 + [ \t]*
  784 + (?:[*+-]|\d+[.])[ \t]+
  785 + )
  786 + )
  787 + )
  788 + /g
  789 + */
  790 + var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
  791 +
  792 + if (g_list_level) {
  793 + text = text.replace(whole_list, function (wholeMatch, m1, m2) {
  794 + var list = m1;
  795 + var list_type = (m2.search(/[*+-]/g) > -1) ? "ul" : "ol";
  796 +
  797 + var result = _ProcessListItems(list, list_type);
  798 +
  799 + // Trim any trailing whitespace, to put the closing `</$list_type>`
  800 + // up on the preceding line, to get it past the current stupid
  801 + // HTML block parser. This is a hack to work around the terrible
  802 + // hack that is the HTML block parser.
  803 + result = result.replace(/\s+$/, "");
  804 + result = "<" + list_type + ">" + result + "</" + list_type + ">\n";
  805 + return result;
  806 + });
  807 + } else {
  808 + whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;
  809 + text = text.replace(whole_list, function (wholeMatch, m1, m2, m3) {
  810 + var runup = m1;
  811 + var list = m2;
  812 +
  813 + var list_type = (m3.search(/[*+-]/g) > -1) ? "ul" : "ol";
  814 + var result = _ProcessListItems(list, list_type);
  815 + result = runup + "<" + list_type + ">\n" + result + "</" + list_type + ">\n";
  816 + return result;
  817 + });
  818 + }
  819 +
  820 + // attacklab: strip sentinel
  821 + text = text.replace(/~0/, "");
  822 +
  823 + return text;
  824 + }
  825 +
  826 + var _listItemMarkers = { ol: "\\d+[.]", ul: "[*+-]" };
  827 +
  828 + function _ProcessListItems(list_str, list_type) {
  829 + //
  830 + // Process the contents of a single ordered or unordered list, splitting it
  831 + // into individual list items.
  832 + //
  833 + // list_type is either "ul" or "ol".
  834 +
  835 + // The $g_list_level global keeps track of when we're inside a list.
  836 + // Each time we enter a list, we increment it; when we leave a list,
  837 + // we decrement. If it's zero, we're not in a list anymore.
  838 + //
  839 + // We do this because when we're not inside a list, we want to treat
  840 + // something like this:
  841 + //
  842 + // I recommend upgrading to version
  843 + // 8. Oops, now this line is treated
  844 + // as a sub-list.
  845 + //
  846 + // As a single paragraph, despite the fact that the second line starts
  847 + // with a digit-period-space sequence.
  848 + //
  849 + // Whereas when we're inside a list (or sub-list), that line will be
  850 + // treated as the start of a sub-list. What a kludge, huh? This is
  851 + // an aspect of Markdown's syntax that's hard to parse perfectly
  852 + // without resorting to mind-reading. Perhaps the solution is to
  853 + // change the syntax rules such that sub-lists must start with a
  854 + // starting cardinal number; e.g. "1." or "a.".
  855 +
  856 + g_list_level++;
  857 +
  858 + // trim trailing blank lines:
  859 + list_str = list_str.replace(/\n{2,}$/, "\n");
  860 +
  861 + // attacklab: add sentinel to emulate \z
  862 + list_str += "~0";
  863 +
  864 + // In the original attacklab showdown, list_type was not given to this function, and anything
  865 + // that matched /[*+-]|\d+[.]/ would just create the next <li>, causing this mismatch:
  866 + //
  867 + // Markdown rendered by WMD rendered by MarkdownSharp
  868 + // ------------------------------------------------------------------
  869 + // 1. first 1. first 1. first
  870 + // 2. second 2. second 2. second
  871 + // - third 3. third * third
  872 + //
  873 + // We changed this to behave identical to MarkdownSharp. This is the constructed RegEx,
  874 + // with {MARKER} being one of \d+[.] or [*+-], depending on list_type:
  875 +
  876 + /*
  877 + list_str = list_str.replace(/
  878 + (^[ \t]*) // leading whitespace = $1
  879 + ({MARKER}) [ \t]+ // list marker = $2
  880 + ([^\r]+? // list item text = $3
  881 + (\n+)
  882 + )
  883 + (?=
  884 + (~0 | \2 ({MARKER}) [ \t]+)
  885 + )
  886 + /gm, function(){...});
  887 + */
  888 +
  889 + var marker = _listItemMarkers[list_type];
  890 + var re = new RegExp("(^[ \\t]*)(" + marker + ")[ \\t]+([^\\r]+?(\\n+))(?=(~0|\\1(" + marker + ")[ \\t]+))", "gm");
  891 + var last_item_had_a_double_newline = false;
  892 + list_str = list_str.replace(re,
  893 + function (wholeMatch, m1, m2, m3) {
  894 + var item = m3;
  895 + var leading_space = m1;
  896 + var ends_with_double_newline = /\n\n$/.test(item);
  897 + var contains_double_newline = ends_with_double_newline || item.search(/\n{2,}/) > -1;
  898 +
  899 + if (contains_double_newline || last_item_had_a_double_newline) {
  900 + item = _RunBlockGamut(_Outdent(item), /* doNotUnhash = */true);
  901 + }
  902 + else {
  903 + // Recursion for sub-lists:
  904 + item = _DoLists(_Outdent(item));
  905 + item = item.replace(/\n$/, ""); // chomp(item)
  906 + item = _RunSpanGamut(item);
  907 + }
  908 + last_item_had_a_double_newline = ends_with_double_newline;
  909 + return "<li>" + item + "</li>\n";
  910 + }
  911 + );
  912 +
  913 + // attacklab: strip sentinel
  914 + list_str = list_str.replace(/~0/g, "");
  915 +
  916 + g_list_level--;
  917 + return list_str;
  918 + }
  919 +
  920 + function _DoCodeBlocks(text) {
  921 + //
  922 + // Process Markdown `<pre><code>` blocks.
  923 + //
  924 +
  925 + /*
  926 + text = text.replace(/
  927 + (?:\n\n|^)
  928 + ( // $1 = the code block -- one or more lines, starting with a space/tab
  929 + (?:
  930 + (?:[ ]{4}|\t) // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
  931 + .*\n+
  932 + )+
  933 + )
  934 + (\n*[ ]{0,3}[^ \t\n]|(?=~0)) // attacklab: g_tab_width
  935 + /g ,function(){...});
  936 + */
  937 +
  938 + // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
  939 + text += "~0";
  940 +
  941 + text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
  942 + function (wholeMatch, m1, m2) {
  943 + var codeblock = m1;
  944 + var nextChar = m2;
  945 +
  946 + codeblock = _EncodeCode(_Outdent(codeblock));
  947 + codeblock = _Detab(codeblock);
  948 + codeblock = codeblock.replace(/^\n+/g, ""); // trim leading newlines
  949 + codeblock = codeblock.replace(/\n+$/g, ""); // trim trailing whitespace
  950 +
  951 + codeblock = "<pre><code>" + codeblock + "\n</code></pre>";
  952 +
  953 + return "\n\n" + codeblock + "\n\n" + nextChar;
  954 + }
  955 + );
  956 +
  957 + // attacklab: strip sentinel
  958 + text = text.replace(/~0/, "");
  959 +
  960 + return text;
  961 + }
  962 +
  963 + function hashBlock(text) {
  964 + text = text.replace(/(^\n+|\n+$)/g, "");
  965 + return "\n\n~K" + (g_html_blocks.push(text) - 1) + "K\n\n";
  966 + }
  967 +
  968 + function _DoCodeSpans(text) {
  969 + //
  970 + // * Backtick quotes are used for <code></code> spans.
  971 + //
  972 + // * You can use multiple backticks as the delimiters if you want to
  973 + // include literal backticks in the code span. So, this input:
  974 + //
  975 + // Just type ``foo `bar` baz`` at the prompt.
  976 + //
  977 + // Will translate to:
  978 + //
  979 + // <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
  980 + //
  981 + // There's no arbitrary limit to the number of backticks you
  982 + // can use as delimters. If you need three consecutive backticks
  983 + // in your code, use four for delimiters, etc.
  984 + //
  985 + // * You can use spaces to get literal backticks at the edges:
  986 + //
  987 + // ... type `` `bar` `` ...
  988 + //
  989 + // Turns to:
  990 + //
  991 + // ... type <code>`bar`</code> ...
  992 + //
  993 +
  994 + /*
  995 + text = text.replace(/
  996 + (^|[^\\]) // Character before opening ` can't be a backslash
  997 + (`+) // $2 = Opening run of `
  998 + ( // $3 = The code block
  999 + [^\r]*?
  1000 + [^`] // attacklab: work around lack of lookbehind
  1001 + )
  1002 + \2 // Matching closer
  1003 + (?!`)
  1004 + /gm, function(){...});
  1005 + */
  1006 +
  1007 + text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
  1008 + function (wholeMatch, m1, m2, m3, m4) {
  1009 + var c = m3;
  1010 + c = c.replace(/^([ \t]*)/g, ""); // leading whitespace
  1011 + c = c.replace(/[ \t]*$/g, ""); // trailing whitespace
  1012 + c = _EncodeCode(c);
  1013 + c = c.replace(/:\/\//g, "~P"); // to prevent auto-linking. Not necessary in code *blocks*, but in code spans. Will be converted back after the auto-linker runs.
  1014 + return m1 + "<code>" + c + "</code>";
  1015 + }
  1016 + );
  1017 +
  1018 + return text;
  1019 + }
  1020 +
  1021 + function _EncodeCode(text) {
  1022 + //
  1023 + // Encode/escape certain characters inside Markdown code runs.
  1024 + // The point is that in code, these characters are literals,
  1025 + // and lose their special Markdown meanings.
  1026 + //
  1027 + // Encode all ampersands; HTML entities are not
  1028 + // entities within a Markdown code span.
  1029 + text = text.replace(/&/g, "&amp;");
  1030 +
  1031 + // Do the angle bracket song and dance:
  1032 + text = text.replace(/</g, "&lt;");
  1033 + text = text.replace(/>/g, "&gt;");
  1034 +
  1035 + // Now, escape characters that are magic in Markdown:
  1036 + text = escapeCharacters(text, "\*_{}[]\\", false);
  1037 +
  1038 + // jj the line above breaks this:
  1039 + //---
  1040 +
  1041 + //* Item
  1042 +
  1043 + // 1. Subitem
  1044 +
  1045 + // special char: *
  1046 + //---
  1047 +
  1048 + return text;
  1049 + }
  1050 +
  1051 + function _DoItalicsAndBold(text) {
  1052 +
  1053 + // <strong> must go first:
  1054 + text = text.replace(/([\W_]|^)(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\2([\W_]|$)/g,
  1055 + "$1<strong>$3</strong>$4");
  1056 +
  1057 + text = text.replace(/([\W_]|^)(\*|_)(?=\S)([^\r\*_]*?\S)\2([\W_]|$)/g,
  1058 + "$1<em>$3</em>$4");
  1059 +
  1060 + return text;
  1061 + }
  1062 +
  1063 + function _DoBlockQuotes(text) {
  1064 +
  1065 + /*
  1066 + text = text.replace(/
  1067 + ( // Wrap whole match in $1
  1068 + (
  1069 + ^[ \t]*>[ \t]? // '>' at the start of a line
  1070 + .+\n // rest of the first line
  1071 + (.+\n)* // subsequent consecutive lines
  1072 + \n* // blanks
  1073 + )+
  1074 + )
  1075 + /gm, function(){...});
  1076 + */
  1077 +
  1078 + text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,
  1079 + function (wholeMatch, m1) {
  1080 + var bq = m1;
  1081 +
  1082 + // attacklab: hack around Konqueror 3.5.4 bug:
  1083 + // "----------bug".replace(/^-/g,"") == "bug"
  1084 +
  1085 + bq = bq.replace(/^[ \t]*>[ \t]?/gm, "~0"); // trim one level of quoting
  1086 +
  1087 + // attacklab: clean up hack
  1088 + bq = bq.replace(/~0/g, "");
  1089 +
  1090 + bq = bq.replace(/^[ \t]+$/gm, ""); // trim whitespace-only lines
  1091 + bq = _RunBlockGamut(bq); // recurse
  1092 +
  1093 + bq = bq.replace(/(^|\n)/g, "$1 ");
  1094 + // These leading spaces screw with <pre> content, so we need to fix that:
  1095 + bq = bq.replace(
  1096 + /(\s*<pre>[^\r]+?<\/pre>)/gm,
  1097 + function (wholeMatch, m1) {
  1098 + var pre = m1;
  1099 + // attacklab: hack around Konqueror 3.5.4 bug:
  1100 + pre = pre.replace(/^ /mg, "~0");
  1101 + pre = pre.replace(/~0/g, "");
  1102 + return pre;
  1103 + });
  1104 +
  1105 + return hashBlock("<blockquote>\n" + bq + "\n</blockquote>");
  1106 + }
  1107 + );
  1108 + return text;
  1109 + }
  1110 +
  1111 + function _FormParagraphs(text, doNotUnhash) {
  1112 + //
  1113 + // Params:
  1114 + // $text - string to process with html <p> tags
  1115 + //
  1116 +
  1117 + // Strip leading and trailing lines:
  1118 + text = text.replace(/^\n+/g, "");
  1119 + text = text.replace(/\n+$/g, "");
  1120 +
  1121 + var grafs = text.split(/\n{2,}/g);
  1122 + var grafsOut = [];
  1123 +
  1124 + var markerRe = /~K(\d+)K/;
  1125 +
  1126 + //
  1127 + // Wrap <p> tags.
  1128 + //
  1129 + var end = grafs.length;
  1130 + for (var i = 0; i < end; i++) {
  1131 + var str = grafs[i];
  1132 +
  1133 + // if this is an HTML marker, copy it
  1134 + if (markerRe.test(str)) {
  1135 + grafsOut.push(str);
  1136 + }
  1137 + else if (/\S/.test(str)) {
  1138 + str = _RunSpanGamut(str);
  1139 + str = str.replace(/^([ \t]*)/g, "<p>");
  1140 + str += "</p>"
  1141 + grafsOut.push(str);
  1142 + }
  1143 +
  1144 + }
  1145 + //
  1146 + // Unhashify HTML blocks
  1147 + //
  1148 + if (!doNotUnhash) {
  1149 + end = grafsOut.length;
  1150 + for (var i = 0; i < end; i++) {
  1151 + var foundAny = true;
  1152 + while (foundAny) { // we may need several runs, since the data may be nested
  1153 + foundAny = false;
  1154 + grafsOut[i] = grafsOut[i].replace(/~K(\d+)K/g, function (wholeMatch, id) {
  1155 + foundAny = true;
  1156 + return g_html_blocks[id];
  1157 + });
  1158 + }
  1159 + }
  1160 + }
  1161 + return grafsOut.join("\n\n");
  1162 + }
  1163 +
  1164 + function _EncodeAmpsAndAngles(text) {
  1165 + // Smart processing for ampersands and angle brackets that need to be encoded.
  1166 +
  1167 + // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
  1168 + // http://bumppo.net/projects/amputator/
  1169 + text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, "&amp;");
  1170 +
  1171 + // Encode naked <'s
  1172 + text = text.replace(/<(?![a-z\/?\$!])/gi, "&lt;");
  1173 +
  1174 + return text;
  1175 + }
  1176 +
  1177 + function _EncodeBackslashEscapes(text) {
  1178 + //
  1179 + // Parameter: String.
  1180 + // Returns: The string, with after processing the following backslash
  1181 + // escape sequences.
  1182 + //
  1183 +
  1184 + // attacklab: The polite way to do this is with the new
  1185 + // escapeCharacters() function:
  1186 + //
  1187 + // text = escapeCharacters(text,"\\",true);
  1188 + // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
  1189 + //
  1190 + // ...but we're sidestepping its use of the (slow) RegExp constructor
  1191 + // as an optimization for Firefox. This function gets called a LOT.
  1192 +
  1193 + text = text.replace(/\\(\\)/g, escapeCharacters_callback);
  1194 + text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, escapeCharacters_callback);
  1195 + return text;
  1196 + }
  1197 +
  1198 + function _DoAutoLinks(text) {
  1199 +
  1200 + // note that at this point, all other URL in the text are already hyperlinked as <a href=""></a>
  1201 + // *except* for the <http://www.foo.com> case
  1202 +
  1203 + // automatically add < and > around unadorned raw hyperlinks
  1204 + // must be preceded by space/BOF and followed by non-word/EOF character
  1205 + text = text.replace(/(^|\s)(https?|ftp)(:\/\/[-A-Z0-9+&@#\/%?=~_|\[\]\(\)!:,\.;]*[-A-Z0-9+&@#\/%=~_|\[\]])($|\W)/gi, "$1<$2$3>$4");
  1206 +
  1207 + // autolink anything like <http://example.com>
  1208 +
  1209 + var replacer = function (wholematch, m1) { return "<a href=\"" + m1 + "\">" + pluginHooks.plainLinkText(m1) + "</a>"; }
  1210 + text = text.replace(/<((https?|ftp):[^'">\s]+)>/gi, replacer);
  1211 +
  1212 + // Email addresses: <address@domain.foo>
  1213 + /*
  1214 + text = text.replace(/
  1215 + <
  1216 + (?:mailto:)?
  1217 + (
  1218 + [-.\w]+
  1219 + \@
  1220 + [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
  1221 + )
  1222 + >
  1223 + /gi, _DoAutoLinks_callback());
  1224 + */
  1225 +
  1226 + /* disabling email autolinking, since we don't do that on the server, either
  1227 + text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,
  1228 + function(wholeMatch,m1) {
  1229 + return _EncodeEmailAddress( _UnescapeSpecialChars(m1) );
  1230 + }
  1231 + );
  1232 + */
  1233 + return text;
  1234 + }
  1235 +
  1236 + function _UnescapeSpecialChars(text) {
  1237 + //
  1238 + // Swap back in all the special characters we've hidden.
  1239 + //
  1240 + text = text.replace(/~E(\d+)E/g,
  1241 + function (wholeMatch, m1) {
  1242 + var charCodeToReplace = parseInt(m1);
  1243 + return String.fromCharCode(charCodeToReplace);
  1244 + }
  1245 + );
  1246 + return text;
  1247 + }
  1248 +
  1249 + function _Outdent(text) {
  1250 + //
  1251 + // Remove one level of line-leading tabs or spaces
  1252 + //
  1253 +
  1254 + // attacklab: hack around Konqueror 3.5.4 bug:
  1255 + // "----------bug".replace(/^-/g,"") == "bug"
  1256 +
  1257 + text = text.replace(/^(\t|[ ]{1,4})/gm, "~0"); // attacklab: g_tab_width
  1258 +
  1259 + // attacklab: clean up hack
  1260 + text = text.replace(/~0/g, "")
  1261 +
  1262 + return text;
  1263 + }
  1264 +
  1265 + function _Detab(text) {
  1266 + if (!/\t/.test(text))
  1267 + return text;
  1268 +
  1269 + var spaces = [" ", " ", " ", " "],
  1270 + skew = 0,
  1271 + v;
  1272 +
  1273 + return text.replace(/[\n\t]/g, function (match, offset) {
  1274 + if (match === "\n") {
  1275 + skew = offset + 1;
  1276 + return match;
  1277 + }
  1278 + v = (offset - skew) % 4;
  1279 + skew = offset + 1;
  1280 + return spaces[v];
  1281 + });
  1282 + }
  1283 +
  1284 + //
  1285 + // attacklab: Utility functions
  1286 + //
  1287 +
  1288 + var _problemUrlChars = /(?:["'*()[\]:]|~D)/g;
  1289 +
  1290 + // hex-encodes some unusual "problem" chars in URLs to avoid URL detection problems
  1291 + function encodeProblemUrlChars(url) {
  1292 + if (!url)
  1293 + return "";
  1294 +
  1295 + var len = url.length;
  1296 +
  1297 + return url.replace(_problemUrlChars, function (match, offset) {
  1298 + if (match == "~D") // escape for dollar
  1299 + return "%24";
  1300 + if (match == ":") {
  1301 + if (offset == len - 1 || /[0-9\/]/.test(url.charAt(offset + 1)))
  1302 + return ":"
  1303 + }
  1304 + return "%" + match.charCodeAt(0).toString(16);
  1305 + });
  1306 + }
  1307 +
  1308 +
  1309 + function escapeCharacters(text, charsToEscape, afterBackslash) {
  1310 + // First we have to escape the escape characters so that
  1311 + // we can build a character class out of them
  1312 + var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g, "\\$1") + "])";
  1313 +
  1314 + if (afterBackslash) {
  1315 + regexString = "\\\\" + regexString;
  1316 + }
  1317 +
  1318 + var regex = new RegExp(regexString, "g");
  1319 + text = text.replace(regex, escapeCharacters_callback);
  1320 +
  1321 + return text;
  1322 + }
  1323 +
  1324 +
  1325 + function escapeCharacters_callback(wholeMatch, m1) {
  1326 + var charCodeToEscape = m1.charCodeAt(0);
  1327 + return "~E" + charCodeToEscape + "E";
  1328 + }
  1329 +
  1330 + }; // end of the Markdown.Converter constructor
  1331 +
  1332 +})();
... ...