Commit 9639780359f16d784f22af2a1dbbcd8322ffd6c3

Authored by Nihad Abbasov
1 parent 1ec3bd3f

render notes preview on server-side

app/assets/javascripts/application.js
... ... @@ -18,7 +18,6 @@
18 18 //= require chosen-jquery
19 19 //= require raphael
20 20 //= require branch-graph
21   -//= require Markdown.Converter
22 21 //= require_tree .
23 22  
24 23 $(document).ready(function(){
... ... @@ -76,15 +75,17 @@ $(document).ready(function(){
76 75 *
77 76 */
78 77 $('#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);
  78 + $('#preview-note').text('Loading...');
84 79  
85 80 var previewLinkText = ($(this).text() == 'Preview' ? 'Edit' : 'Preview');
86 81 $(this).text(previewLinkText);
87 82  
  83 + var note = $('#note_note').val();
  84 + if (note.trim().length === 0) { note = 'Nothing to preview'; }
  85 + $.post($(this).attr('href'), {note: note}, function(data) {
  86 + $('#preview-note').html(data);
  87 + });
  88 +
88 89 $('#preview-note, #note_note').toggle();
89 90 e.preventDefault();
90 91 });
... ...
app/controllers/notes_controller.rb
... ... @@ -33,7 +33,11 @@ class NotesController < ApplicationController
33 33 end
34 34 end
35 35  
36   - protected
  36 + def preview
  37 + render :text => view_context.markdown(params[:note])
  38 + end
  39 +
  40 + protected
37 41  
38 42 def notes
39 43 @notes = Notes::LoadContext.new(project, current_user, params).execute
... ...
app/views/notes/_form.html.haml
... ... @@ -12,7 +12,7 @@
12 12 %p.hint
13 13 = link_to "Gitlab Markdown", help_markdown_path, :target => '_blank'
14 14 is enabled.
15   - = link_to 'Preview', '#', :id => 'preview-link'
  15 + = link_to 'Preview', preview_project_notes_path(@project), :id => 'preview-link'
16 16  
17 17 .row.note_advanced_opts.hide
18 18 .span4
... ...
config/routes.rb
... ... @@ -199,7 +199,11 @@ Gitlab::Application.routes.draw do
199 199 get :search
200 200 end
201 201 end
202   - resources :notes, :only => [:index, :create, :destroy]
  202 + resources :notes, :only => [:index, :create, :destroy] do
  203 + collection do
  204 + post :preview
  205 + end
  206 + end
203 207 end
204 208 root :to => "dashboard#index"
205 209 end
... ...
vendor/assets/javascripts/Markdown.Converter.js
... ... @@ -1,1332 +0,0 @@
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   -})();