support for data-markdown (#15)
authorHakim El Hattab <hakim.elhattab@gmail.com>
Tue, 31 Jul 2012 05:13:33 +0000 (01:13 -0400)
committerHakim El Hattab <hakim.elhattab@gmail.com>
Tue, 31 Jul 2012 05:13:33 +0000 (01:13 -0400)
index.html
js/reveal.js
lib/js/data-markdown.js [new file with mode: 0644]
lib/js/showdown.js [new file with mode: 0644]

index 76631e9fc22ce5028e032622f16ed4e484497774..b66f4f19bdbbb820e8523eff377e0fe0f050efab 100644 (file)
@@ -259,9 +259,23 @@ function linkify( selector ) {
                <script src="lib/js/head.min.js"></script>
 
                <script>
-                       // Load reveal.js as well as a classList polyfill if needed
-                       head.js( !document.body.classList ? 'lib/js/classList.js' : null )
-                               .js( 'js/reveal.js', function() {
+                       // All scripts that should be loaded before initializing
+                       var scripts = [];
+
+                       // If the browser doesn't support classList, load a polyfill
+                       if( !document.body.classList ) {
+                               scripts.push( 'lib/js/classList.js' );
+                       }
+
+                       // Load markdown parser if there are slides defined using markdown
+                       if( document.querySelector( '[data-markdown]' ) ) {
+                               scripts.push( 'lib/js/showdown.js' );
+                               scripts.push( 'lib/js/data-markdown.js' );
+                       }
+
+                       scripts.push( 'js/reveal.js' );
+
+                       head.js.apply( null, scripts.concat([ function() {
 
                                // Parse the query string into a key/value object
                                var query = {};
@@ -290,7 +304,7 @@ function linkify( selector ) {
                                        transition: query.transition || 'default' // default/cube/page/concave/linear(2d)
                                });
 
-                       } );
+                       }]));
                        
                        // Load highlight.js for syntax highlighting of code samples
                        head.js( 'lib/js/highlight.js', function() { 
index 8ae46b60b30b9972259551640b8efc85aab47273..cbb859ab92431eb59f8f8038cc012ac63262b8df 100644 (file)
@@ -1,5 +1,5 @@
 /*!
- * reveal.js 1.5 r3
+ * reveal.js 1.5 r4
  * http://lab.hakim.se/reveal-js
  * MIT licensed
  * 
diff --git a/lib/js/data-markdown.js b/lib/js/data-markdown.js
new file mode 100644 (file)
index 0000000..d1b27c4
--- /dev/null
@@ -0,0 +1,19 @@
+// From https://gist.github.com/1343518, modified to not load showdown
+(function boom(){
+
+  [].forEach.call( document.querySelectorAll('[data-markdown]'), function  fn(elem){
+      
+    // strip leading whitespace so it isn't evaluated as code
+    var text      = elem.innerHTML.replace(/\n\s*\n/g,'\n'),
+        // set indentation level so your markdown can be indented within your HTML
+        leadingws = text.match(/^\n?(\s*)/)[1].length,
+        regex     = new RegExp('\\n?\\s{' + leadingws + '}','g'),
+        md        = text.replace(regex,'\n'),
+        html      = (new Showdown.converter()).makeHtml(md);
+
+    // here, have sum HTML
+    elem.innerHTML = html;
+
+  });
+
+}());
\ No newline at end of file
diff --git a/lib/js/showdown.js b/lib/js/showdown.js
new file mode 100644 (file)
index 0000000..a0da114
--- /dev/null
@@ -0,0 +1,1341 @@
+//
+// showdown.js -- A javascript port of Markdown.
+//
+// Copyright (c) 2007 John Fraser.
+//
+// Original Markdown Copyright (c) 2004-2005 John Gruber
+//   <http://daringfireball.net/projects/markdown/>
+//
+// Redistributable under a BSD-style open source license.
+// See license.txt for more information.
+//
+// The full source distribution is at:
+//
+//                             A A L
+//                             T C A
+//                             T K B
+//
+//   <http://www.attacklab.net/>
+//
+
+//
+// Wherever possible, Showdown is a straight, line-by-line port
+// of the Perl version of Markdown.
+//
+// This is not a normal parser design; it's basically just a
+// series of string substitutions.  It's hard to read and
+// maintain this way,  but keeping Showdown close to the original
+// design makes it easier to port new features.
+//
+// More importantly, Showdown behaves like markdown.pl in most
+// edge cases.  So web applications can do client-side preview
+// in Javascript, and then build identical HTML on the server.
+//
+// This port needs the new RegExp functionality of ECMA 262,
+// 3rd Edition (i.e. Javascript 1.5).  Most modern web browsers
+// should do fine.  Even with the new regular expression features,
+// We do a lot of work to emulate Perl's regex functionality.
+// The tricky changes in this file mostly have the "attacklab:"
+// label.  Major or self-explanatory changes don't.
+//
+// Smart diff tools like Araxis Merge will be able to match up
+// this file with markdown.pl in a useful way.  A little tweaking
+// helps: in a copy of markdown.pl, replace "#" with "//" and
+// replace "$text" with "text".  Be sure to ignore whitespace
+// and line endings.
+//
+
+
+//
+// Showdown usage:
+//
+//   var text = "Markdown *rocks*.";
+//
+//   var converter = new Showdown.converter();
+//   var html = converter.makeHtml(text);
+//
+//   alert(html);
+//
+// Note: move the sample code to the bottom of this
+// file before uncommenting it.
+//
+
+
+//
+// Showdown namespace
+//
+var Showdown = {};
+
+//
+// converter
+//
+// Wraps all "globals" so that the only thing
+// exposed is makeHtml().
+//
+Showdown.converter = function() {
+
+//
+// Globals:
+//
+
+// Global hashes, used by various utility routines
+var g_urls;
+var g_titles;
+var g_html_blocks;
+
+// Used to track when we're inside an ordered or unordered list
+// (see _ProcessListItems() for details):
+var g_list_level = 0;
+
+
+this.makeHtml = function(text) {
+//
+// Main function. The order in which other subs are called here is
+// essential. Link and image substitutions need to happen before
+// _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>
+// and <img> tags get encoded.
+//
+
+       // Clear the global hashes. If we don't clear these, you get conflicts
+       // from other articles when generating a page which contains more than
+       // one article (e.g. an index page that shows the N most recent
+       // articles):
+       g_urls = new Array();
+       g_titles = new Array();
+       g_html_blocks = new Array();
+
+       // attacklab: Replace ~ with ~T
+       // This lets us use tilde as an escape char to avoid md5 hashes
+       // The choice of character is arbitray; anything that isn't
+    // magic in Markdown will work.
+       text = text.replace(/~/g,"~T");
+
+       // attacklab: Replace $ with ~D
+       // RegExp interprets $ as a special character
+       // when it's in a replacement string
+       text = text.replace(/\$/g,"~D");
+
+       // Standardize line endings
+       text = text.replace(/\r\n/g,"\n"); // DOS to Unix
+       text = text.replace(/\r/g,"\n"); // Mac to Unix
+
+       // Make sure text begins and ends with a couple of newlines:
+       text = "\n\n" + text + "\n\n";
+
+       // Convert all tabs to spaces.
+       text = _Detab(text);
+
+       // Strip any lines consisting only of spaces and tabs.
+       // This makes subsequent regexen easier to write, because we can
+       // match consecutive blank lines with /\n+/ instead of something
+       // contorted like /[ \t]*\n+/ .
+       text = text.replace(/^[ \t]+$/mg,"");
+
+       // Handle github codeblocks prior to running HashHTML so that
+       // HTML contained within the codeblock gets escaped propertly
+       text = _DoGithubCodeBlocks(text);
+
+       // Turn block-level HTML blocks into hash entries
+       text = _HashHTMLBlocks(text);
+
+       // Strip link definitions, store in hashes.
+       text = _StripLinkDefinitions(text);
+
+       text = _RunBlockGamut(text);
+
+       text = _UnescapeSpecialChars(text);
+
+       // attacklab: Restore dollar signs
+       text = text.replace(/~D/g,"$$");
+
+       // attacklab: Restore tildes
+       text = text.replace(/~T/g,"~");
+
+       return text;
+};
+
+
+var _StripLinkDefinitions = function(text) {
+//
+// Strips link definitions from text, stores the URLs and titles in
+// hash references.
+//
+
+       // Link defs are in the form: ^[id]: url "optional title"
+
+       /*
+               var text = text.replace(/
+                               ^[ ]{0,3}\[(.+)\]:  // id = $1  attacklab: g_tab_width - 1
+                                 [ \t]*
+                                 \n?                           // maybe *one* newline
+                                 [ \t]*
+                               <?(\S+?)>?                      // url = $2
+                                 [ \t]*
+                                 \n?                           // maybe one newline
+                                 [ \t]*
+                               (?:
+                                 (\n*)                         // any lines skipped = $3 attacklab: lookbehind removed
+                                 ["(]
+                                 (.+?)                         // title = $4
+                                 [")]
+                                 [ \t]*
+                               )?                                      // title is optional
+                               (?:\n+|$)
+                         /gm,
+                         function(){...});
+       */
+       var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,
+               function (wholeMatch,m1,m2,m3,m4) {
+                       m1 = m1.toLowerCase();
+                       g_urls[m1] = _EncodeAmpsAndAngles(m2);  // Link IDs are case-insensitive
+                       if (m3) {
+                               // Oops, found blank lines, so it's not a title.
+                               // Put back the parenthetical statement we stole.
+                               return m3+m4;
+                       } else if (m4) {
+                               g_titles[m1] = m4.replace(/"/g,"&quot;");
+                       }
+
+                       // Completely remove the definition from the text
+                       return "";
+               }
+       );
+
+       return text;
+}
+
+
+var _HashHTMLBlocks = function(text) {
+       // attacklab: Double up blank lines to reduce lookaround
+       text = text.replace(/\n/g,"\n\n");
+
+       // Hashify HTML blocks:
+       // We only want to do this for block-level HTML tags, such as headers,
+       // lists, and tables. That's because we still want to wrap <p>s around
+       // "paragraphs" that are wrapped in non-block-level tags, such as anchors,
+       // phrase emphasis, and spans. The list of tags we're looking for is
+       // hard-coded:
+       var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del|style|section|header|footer|nav|article|aside";
+       var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|style|section|header|footer|nav|article|aside";
+
+       // First, look for nested blocks, e.g.:
+       //   <div>
+       //     <div>
+       //     tags for inner block must be indented.
+       //     </div>
+       //   </div>
+       //
+       // The outermost tags must start at the left margin for this to match, and
+       // the inner nested divs must be indented.
+       // We need to do this before the next, more liberal match, because the next
+       // match will start at the first `<div>` and stop at the first `</div>`.
+
+       // attacklab: This regex can be expensive when it fails.
+       /*
+               var text = text.replace(/
+               (                                               // save in $1
+                       ^                                       // start of line  (with /m)
+                       <($block_tags_a)        // start tag = $2
+                       \b                                      // word break
+                                                               // attacklab: hack around khtml/pcre bug...
+                       [^\r]*?\n                       // any number of lines, minimally matching
+                       </\2>                           // the matching end tag
+                       [ \t]*                          // trailing spaces/tabs
+                       (?=\n+)                         // followed by a newline
+               )                                               // attacklab: there are sentinel newlines at end of document
+               /gm,function(){...}};
+       */
+       text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,hashElement);
+
+       //
+       // Now match more liberally, simply from `\n<tag>` to `</tag>\n`
+       //
+
+       /*
+               var text = text.replace(/
+               (                                               // save in $1
+                       ^                                       // start of line  (with /m)
+                       <($block_tags_b)        // start tag = $2
+                       \b                                      // word break
+                                                               // attacklab: hack around khtml/pcre bug...
+                       [^\r]*?                         // any number of lines, minimally matching
+                       .*</\2>                         // the matching end tag
+                       [ \t]*                          // trailing spaces/tabs
+                       (?=\n+)                         // followed by a newline
+               )                                               // attacklab: there are sentinel newlines at end of document
+               /gm,function(){...}};
+       */
+       text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|style|section|header|footer|nav|article|aside)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,hashElement);
+
+       // Special case just for <hr />. It was easier to make a special case than
+       // to make the other regex more complicated.
+
+       /*
+               text = text.replace(/
+               (                                               // save in $1
+                       \n\n                            // Starting after a blank line
+                       [ ]{0,3}
+                       (<(hr)                          // start tag = $2
+                       \b                                      // word break
+                       ([^<>])*?                       //
+                       \/?>)                           // the matching end tag
+                       [ \t]*
+                       (?=\n{2,})                      // followed by a blank line
+               )
+               /g,hashElement);
+       */
+       text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement);
+
+       // Special case for standalone HTML comments:
+
+       /*
+               text = text.replace(/
+               (                                               // save in $1
+                       \n\n                            // Starting after a blank line
+                       [ ]{0,3}                        // attacklab: g_tab_width - 1
+                       <!
+                       (--[^\r]*?--\s*)+
+                       >
+                       [ \t]*
+                       (?=\n{2,})                      // followed by a blank line
+               )
+               /g,hashElement);
+       */
+       text = text.replace(/(\n\n[ ]{0,3}<!(--[^\r]*?--\s*)+>[ \t]*(?=\n{2,}))/g,hashElement);
+
+       // PHP and ASP-style processor instructions (<?...?> and <%...%>)
+
+       /*
+               text = text.replace(/
+               (?:
+                       \n\n                            // Starting after a blank line
+               )
+               (                                               // save in $1
+                       [ ]{0,3}                        // attacklab: g_tab_width - 1
+                       (?:
+                               <([?%])                 // $2
+                               [^\r]*?
+                               \2>
+                       )
+                       [ \t]*
+                       (?=\n{2,})                      // followed by a blank line
+               )
+               /g,hashElement);
+       */
+       text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement);
+
+       // attacklab: Undo double lines (see comment at top of this function)
+       text = text.replace(/\n\n/g,"\n");
+       return text;
+}
+
+var hashElement = function(wholeMatch,m1) {
+       var blockText = m1;
+
+       // Undo double lines
+       blockText = blockText.replace(/\n\n/g,"\n");
+       blockText = blockText.replace(/^\n/,"");
+
+       // strip trailing blank lines
+       blockText = blockText.replace(/\n+$/g,"");
+
+       // Replace the element text with a marker ("~KxK" where x is its key)
+       blockText = "\n\n~K" + (g_html_blocks.push(blockText)-1) + "K\n\n";
+
+       return blockText;
+};
+
+var _RunBlockGamut = function(text) {
+//
+// These are all the transformations that form block-level
+// tags like paragraphs, headers, and list items.
+//
+       text = _DoHeaders(text);
+
+       // Do Horizontal Rules:
+       var key = hashBlock("<hr />");
+       text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key);
+       text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key);
+       text = text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key);
+
+       text = _DoLists(text);
+       text = _DoCodeBlocks(text);
+       text = _DoBlockQuotes(text);
+
+       // We already ran _HashHTMLBlocks() before, in Markdown(), but that
+       // was to escape raw HTML in the original Markdown source. This time,
+       // we're escaping the markup we've just created, so that we don't wrap
+       // <p> tags around block-level tags.
+       text = _HashHTMLBlocks(text);
+       text = _FormParagraphs(text);
+
+       return text;
+};
+
+
+var _RunSpanGamut = function(text) {
+//
+// These are all the transformations that occur *within* block-level
+// tags like paragraphs, headers, and list items.
+//
+
+       text = _DoCodeSpans(text);
+       text = _EscapeSpecialCharsWithinTagAttributes(text);
+       text = _EncodeBackslashEscapes(text);
+
+       // Process anchor and image tags. Images must come first,
+       // because ![foo][f] looks like an anchor.
+       text = _DoImages(text);
+       text = _DoAnchors(text);
+
+       // Make links out of things like `<http://example.com/>`
+       // Must come after _DoAnchors(), because you can use < and >
+       // delimiters in inline links like [this](<url>).
+       text = _DoAutoLinks(text);
+       text = _EncodeAmpsAndAngles(text);
+       text = _DoItalicsAndBold(text);
+
+       // Do hard breaks:
+       text = text.replace(/  +\n/g," <br />\n");
+
+       return text;
+}
+
+var _EscapeSpecialCharsWithinTagAttributes = function(text) {
+//
+// Within tags -- meaning between < and > -- encode [\ ` * _] so they
+// don't conflict with their use in Markdown for code, italics and strong.
+//
+
+       // Build a regex to find HTML tags and comments.  See Friedl's
+       // "Mastering Regular Expressions", 2nd Ed., pp. 200-201.
+       var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi;
+
+       text = text.replace(regex, function(wholeMatch) {
+               var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`");
+               tag = escapeCharacters(tag,"\\`*_");
+               return tag;
+       });
+
+       return text;
+}
+
+var _DoAnchors = function(text) {
+//
+// Turn Markdown link shortcuts into XHTML <a> tags.
+//
+       //
+       // First, handle reference-style links: [link text] [id]
+       //
+
+       /*
+               text = text.replace(/
+               (                                                       // wrap whole match in $1
+                       \[
+                       (
+                               (?:
+                                       \[[^\]]*\]              // allow brackets nested one level
+                                       |
+                                       [^\[]                   // or anything else
+                               )*
+                       )
+                       \]
+
+                       [ ]?                                    // one optional space
+                       (?:\n[ ]*)?                             // one optional newline followed by spaces
+
+                       \[
+                       (.*?)                                   // id = $3
+                       \]
+               )()()()()                                       // pad remaining backreferences
+               /g,_DoAnchors_callback);
+       */
+       text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag);
+
+       //
+       // Next, inline-style links: [link text](url "optional title")
+       //
+
+       /*
+               text = text.replace(/
+                       (                                               // wrap whole match in $1
+                               \[
+                               (
+                                       (?:
+                                               \[[^\]]*\]      // allow brackets nested one level
+                                       |
+                                       [^\[\]]                 // or anything else
+                               )
+                       )
+                       \]
+                       \(                                              // literal paren
+                       [ \t]*
+                       ()                                              // no id, so leave $3 empty
+                       <?(.*?)>?                               // href = $4
+                       [ \t]*
+                       (                                               // $5
+                               (['"])                          // quote char = $6
+                               (.*?)                           // Title = $7
+                               \6                                      // matching quote
+                               [ \t]*                          // ignore any spaces/tabs between closing quote and )
+                       )?                                              // title is optional
+                       \)
+               )
+               /g,writeAnchorTag);
+       */
+       text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag);
+
+       //
+       // Last, handle reference-style shortcuts: [link text]
+       // These must come last in case you've also got [link test][1]
+       // or [link test](/foo)
+       //
+
+       /*
+               text = text.replace(/
+               (                                                       // wrap whole match in $1
+                       \[
+                       ([^\[\]]+)                              // link text = $2; can't contain '[' or ']'
+                       \]
+               )()()()()()                                     // pad rest of backreferences
+               /g, writeAnchorTag);
+       */
+       text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);
+
+       return text;
+}
+
+var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {
+       if (m7 == undefined) m7 = "";
+       var whole_match = m1;
+       var link_text   = m2;
+       var link_id      = m3.toLowerCase();
+       var url         = m4;
+       var title       = m7;
+
+       if (url == "") {
+               if (link_id == "") {
+                       // lower-case and turn embedded newlines into spaces
+                       link_id = link_text.toLowerCase().replace(/ ?\n/g," ");
+               }
+               url = "#"+link_id;
+
+               if (g_urls[link_id] != undefined) {
+                       url = g_urls[link_id];
+                       if (g_titles[link_id] != undefined) {
+                               title = g_titles[link_id];
+                       }
+               }
+               else {
+                       if (whole_match.search(/\(\s*\)$/m)>-1) {
+                               // Special case for explicit empty url
+                               url = "";
+                       } else {
+                               return whole_match;
+                       }
+               }
+       }
+
+       url = escapeCharacters(url,"*_");
+       var result = "<a href=\"" + url + "\"";
+
+       if (title != "") {
+               title = title.replace(/"/g,"&quot;");
+               title = escapeCharacters(title,"*_");
+               result +=  " title=\"" + title + "\"";
+       }
+
+       result += ">" + link_text + "</a>";
+
+       return result;
+}
+
+
+var _DoImages = function(text) {
+//
+// Turn Markdown image shortcuts into <img> tags.
+//
+
+       //
+       // First, handle reference-style labeled images: ![alt text][id]
+       //
+
+       /*
+               text = text.replace(/
+               (                                               // wrap whole match in $1
+                       !\[
+                       (.*?)                           // alt text = $2
+                       \]
+
+                       [ ]?                            // one optional space
+                       (?:\n[ ]*)?                     // one optional newline followed by spaces
+
+                       \[
+                       (.*?)                           // id = $3
+                       \]
+               )()()()()                               // pad rest of backreferences
+               /g,writeImageTag);
+       */
+       text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag);
+
+       //
+       // Next, handle inline images:  ![alt text](url "optional title")
+       // Don't forget: encode * and _
+
+       /*
+               text = text.replace(/
+               (                                               // wrap whole match in $1
+                       !\[
+                       (.*?)                           // alt text = $2
+                       \]
+                       \s?                                     // One optional whitespace character
+                       \(                                      // literal paren
+                       [ \t]*
+                       ()                                      // no id, so leave $3 empty
+                       <?(\S+?)>?                      // src url = $4
+                       [ \t]*
+                       (                                       // $5
+                               (['"])                  // quote char = $6
+                               (.*?)                   // title = $7
+                               \6                              // matching quote
+                               [ \t]*
+                       )?                                      // title is optional
+               \)
+               )
+               /g,writeImageTag);
+       */
+       text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag);
+
+       return text;
+}
+
+var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {
+       var whole_match = m1;
+       var alt_text   = m2;
+       var link_id      = m3.toLowerCase();
+       var url         = m4;
+       var title       = m7;
+
+       if (!title) title = "";
+
+       if (url == "") {
+               if (link_id == "") {
+                       // lower-case and turn embedded newlines into spaces
+                       link_id = alt_text.toLowerCase().replace(/ ?\n/g," ");
+               }
+               url = "#"+link_id;
+
+               if (g_urls[link_id] != undefined) {
+                       url = g_urls[link_id];
+                       if (g_titles[link_id] != undefined) {
+                               title = g_titles[link_id];
+                       }
+               }
+               else {
+                       return whole_match;
+               }
+       }
+
+       alt_text = alt_text.replace(/"/g,"&quot;");
+       url = escapeCharacters(url,"*_");
+       var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\"";
+
+       // attacklab: Markdown.pl adds empty title attributes to images.
+       // Replicate this bug.
+
+       //if (title != "") {
+               title = title.replace(/"/g,"&quot;");
+               title = escapeCharacters(title,"*_");
+               result +=  " title=\"" + title + "\"";
+       //}
+
+       result += " />";
+
+       return result;
+}
+
+
+var _DoHeaders = function(text) {
+
+       // Setext-style headers:
+       //      Header 1
+       //      ========
+       //
+       //      Header 2
+       //      --------
+       //
+       text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,
+               function(wholeMatch,m1){return hashBlock('<h1 id="' + headerId(m1) + '">' + _RunSpanGamut(m1) + "</h1>");});
+
+       text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,
+               function(matchFound,m1){return hashBlock('<h2 id="' + headerId(m1) + '">' + _RunSpanGamut(m1) + "</h2>");});
+
+       // atx-style headers:
+       //  # Header 1
+       //  ## Header 2
+       //  ## Header 2 with closing hashes ##
+       //  ...
+       //  ###### Header 6
+       //
+
+       /*
+               text = text.replace(/
+                       ^(\#{1,6})                              // $1 = string of #'s
+                       [ \t]*
+                       (.+?)                                   // $2 = Header text
+                       [ \t]*
+                       \#*                                             // optional closing #'s (not counted)
+                       \n+
+               /gm, function() {...});
+       */
+
+       text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,
+               function(wholeMatch,m1,m2) {
+                       var h_level = m1.length;
+                       return hashBlock("<h" + h_level + ' id="' + headerId(m2) + '">' + _RunSpanGamut(m2) + "</h" + h_level + ">");
+               });
+
+       function headerId(m) {
+               return m.replace(/[^\w]/g, '').toLowerCase();
+       }
+       return text;
+}
+
+// This declaration keeps Dojo compressor from outputting garbage:
+var _ProcessListItems;
+
+var _DoLists = function(text) {
+//
+// Form HTML ordered (numbered) and unordered (bulleted) lists.
+//
+
+       // attacklab: add sentinel to hack around khtml/safari bug:
+       // http://bugs.webkit.org/show_bug.cgi?id=11231
+       text += "~0";
+
+       // Re-usable pattern to match any entirel ul or ol list:
+
+       /*
+               var whole_list = /
+               (                                                                       // $1 = whole list
+                       (                                                               // $2
+                               [ ]{0,3}                                        // attacklab: g_tab_width - 1
+                               ([*+-]|\d+[.])                          // $3 = first list item marker
+                               [ \t]+
+                       )
+                       [^\r]+?
+                       (                                                               // $4
+                               ~0                                                      // sentinel for workaround; should be $
+                       |
+                               \n{2,}
+                               (?=\S)
+                               (?!                                                     // Negative lookahead for another list item marker
+                                       [ \t]*
+                                       (?:[*+-]|\d+[.])[ \t]+
+                               )
+                       )
+               )/g
+       */
+       var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
+
+       if (g_list_level) {
+               text = text.replace(whole_list,function(wholeMatch,m1,m2) {
+                       var list = m1;
+                       var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol";
+
+                       // Turn double returns into triple returns, so that we can make a
+                       // paragraph for the last item in a list, if necessary:
+                       list = list.replace(/\n{2,}/g,"\n\n\n");;
+                       var result = _ProcessListItems(list);
+
+                       // Trim any trailing whitespace, to put the closing `</$list_type>`
+                       // up on the preceding line, to get it past the current stupid
+                       // HTML block parser. This is a hack to work around the terrible
+                       // hack that is the HTML block parser.
+                       result = result.replace(/\s+$/,"");
+                       result = "<"+list_type+">" + result + "</"+list_type+">\n";
+                       return result;
+               });
+       } else {
+               whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;
+               text = text.replace(whole_list,function(wholeMatch,m1,m2,m3) {
+                       var runup = m1;
+                       var list = m2;
+
+                       var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol";
+                       // Turn double returns into triple returns, so that we can make a
+                       // paragraph for the last item in a list, if necessary:
+                       var list = list.replace(/\n{2,}/g,"\n\n\n");;
+                       var result = _ProcessListItems(list);
+                       result = runup + "<"+list_type+">\n" + result + "</"+list_type+">\n";
+                       return result;
+               });
+       }
+
+       // attacklab: strip sentinel
+       text = text.replace(/~0/,"");
+
+       return text;
+}
+
+_ProcessListItems = function(list_str) {
+//
+//  Process the contents of a single ordered or unordered list, splitting it
+//  into individual list items.
+//
+       // The $g_list_level global keeps track of when we're inside a list.
+       // Each time we enter a list, we increment it; when we leave a list,
+       // we decrement. If it's zero, we're not in a list anymore.
+       //
+       // We do this because when we're not inside a list, we want to treat
+       // something like this:
+       //
+       //    I recommend upgrading to version
+       //    8. Oops, now this line is treated
+       //    as a sub-list.
+       //
+       // As a single paragraph, despite the fact that the second line starts
+       // with a digit-period-space sequence.
+       //
+       // Whereas when we're inside a list (or sub-list), that line will be
+       // treated as the start of a sub-list. What a kludge, huh? This is
+       // an aspect of Markdown's syntax that's hard to parse perfectly
+       // without resorting to mind-reading. Perhaps the solution is to
+       // change the syntax rules such that sub-lists must start with a
+       // starting cardinal number; e.g. "1." or "a.".
+
+       g_list_level++;
+
+       // trim trailing blank lines:
+       list_str = list_str.replace(/\n{2,}$/,"\n");
+
+       // attacklab: add sentinel to emulate \z
+       list_str += "~0";
+
+       /*
+               list_str = list_str.replace(/
+                       (\n)?                                                   // leading line = $1
+                       (^[ \t]*)                                               // leading whitespace = $2
+                       ([*+-]|\d+[.]) [ \t]+                   // list marker = $3
+                       ([^\r]+?                                                // list item text   = $4
+                       (\n{1,2}))
+                       (?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+))
+               /gm, function(){...});
+       */
+       list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,
+               function(wholeMatch,m1,m2,m3,m4){
+                       var item = m4;
+                       var leading_line = m1;
+                       var leading_space = m2;
+
+                       if (leading_line || (item.search(/\n{2,}/)>-1)) {
+                               item = _RunBlockGamut(_Outdent(item));
+                       }
+                       else {
+                               // Recursion for sub-lists:
+                               item = _DoLists(_Outdent(item));
+                               item = item.replace(/\n$/,""); // chomp(item)
+                               item = _RunSpanGamut(item);
+                       }
+
+                       return  "<li>" + item + "</li>\n";
+               }
+       );
+
+       // attacklab: strip sentinel
+       list_str = list_str.replace(/~0/g,"");
+
+       g_list_level--;
+       return list_str;
+}
+
+
+var _DoCodeBlocks = function(text) {
+//
+//  Process Markdown `<pre><code>` blocks.
+//
+
+       /*
+               text = text.replace(text,
+                       /(?:\n\n|^)
+                       (                                                               // $1 = the code block -- one or more lines, starting with a space/tab
+                               (?:
+                                       (?:[ ]{4}|\t)                   // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
+                                       .*\n+
+                               )+
+                       )
+                       (\n*[ ]{0,3}[^ \t\n]|(?=~0))    // attacklab: g_tab_width
+               /g,function(){...});
+       */
+
+       // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
+       text += "~0";
+
+       text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
+               function(wholeMatch,m1,m2) {
+                       var codeblock = m1;
+                       var nextChar = m2;
+
+                       codeblock = _EncodeCode( _Outdent(codeblock));
+                       codeblock = _Detab(codeblock);
+                       codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines
+                       codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace
+
+                       codeblock = "<pre><code>" + codeblock + "\n</code></pre>";
+
+                       return hashBlock(codeblock) + nextChar;
+               }
+       );
+
+       // attacklab: strip sentinel
+       text = text.replace(/~0/,"");
+
+       return text;
+};
+
+var _DoGithubCodeBlocks = function(text) {
+//
+//  Process Github-style code blocks
+//  Example:
+//  ```ruby
+//  def hello_world(x)
+//    puts "Hello, #{x}"
+//  end
+//  ```
+//
+
+
+       // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
+       text += "~0";
+
+       text = text.replace(/(?:^|\n)```(.*)\n([\s\S]*?)\n```/g,
+               function(wholeMatch,m1,m2) {
+                       var language = m1;
+                       var codeblock = m2;
+
+                       codeblock = _EncodeCode(codeblock);
+                       codeblock = _Detab(codeblock);
+                       codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines
+                       codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace
+
+                       codeblock = "<pre><code" + (language ? " class=\"" + language + '"' : "") + ">" + codeblock + "\n</code></pre>";
+
+                       return hashBlock(codeblock);
+               }
+       );
+
+       // attacklab: strip sentinel
+       text = text.replace(/~0/,"");
+
+       return text;
+}
+
+var hashBlock = function(text) {
+       text = text.replace(/(^\n+|\n+$)/g,"");
+       return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n";
+}
+
+var _DoCodeSpans = function(text) {
+//
+//   *  Backtick quotes are used for <code></code> spans.
+//
+//   *  You can use multiple backticks as the delimiters if you want to
+//      include literal backticks in the code span. So, this input:
+//
+//              Just type ``foo `bar` baz`` at the prompt.
+//
+//        Will translate to:
+//
+//              <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
+//
+//     There's no arbitrary limit to the number of backticks you
+//     can use as delimters. If you need three consecutive backticks
+//     in your code, use four for delimiters, etc.
+//
+//  *  You can use spaces to get literal backticks at the edges:
+//
+//              ... type `` `bar` `` ...
+//
+//        Turns to:
+//
+//              ... type <code>`bar`</code> ...
+//
+
+       /*
+               text = text.replace(/
+                       (^|[^\\])                                       // Character before opening ` can't be a backslash
+                       (`+)                                            // $2 = Opening run of `
+                       (                                                       // $3 = The code block
+                               [^\r]*?
+                               [^`]                                    // attacklab: work around lack of lookbehind
+                       )
+                       \2                                                      // Matching closer
+                       (?!`)
+               /gm, function(){...});
+       */
+
+       text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
+               function(wholeMatch,m1,m2,m3,m4) {
+                       var c = m3;
+                       c = c.replace(/^([ \t]*)/g,""); // leading whitespace
+                       c = c.replace(/[ \t]*$/g,"");   // trailing whitespace
+                       c = _EncodeCode(c);
+                       return m1+"<code>"+c+"</code>";
+               });
+
+       return text;
+}
+
+var _EncodeCode = function(text) {
+//
+// Encode/escape certain characters inside Markdown code runs.
+// The point is that in code, these characters are literals,
+// and lose their special Markdown meanings.
+//
+       // Encode all ampersands; HTML entities are not
+       // entities within a Markdown code span.
+       text = text.replace(/&/g,"&amp;");
+
+       // Do the angle bracket song and dance:
+       text = text.replace(/</g,"&lt;");
+       text = text.replace(/>/g,"&gt;");
+
+       // Now, escape characters that are magic in Markdown:
+       text = escapeCharacters(text,"\*_{}[]\\",false);
+
+// jj the line above breaks this:
+//---
+
+//* Item
+
+//   1. Subitem
+
+//            special char: *
+//---
+
+       return text;
+}
+
+
+var _DoItalicsAndBold = function(text) {
+
+       // <strong> must go first:
+       text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,
+               "<strong>$2</strong>");
+
+       text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,
+               "<em>$2</em>");
+
+       return text;
+}
+
+
+var _DoBlockQuotes = function(text) {
+
+       /*
+               text = text.replace(/
+               (                                                               // Wrap whole match in $1
+                       (
+                               ^[ \t]*>[ \t]?                  // '>' at the start of a line
+                               .+\n                                    // rest of the first line
+                               (.+\n)*                                 // subsequent consecutive lines
+                               \n*                                             // blanks
+                       )+
+               )
+               /gm, function(){...});
+       */
+
+       text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,
+               function(wholeMatch,m1) {
+                       var bq = m1;
+
+                       // attacklab: hack around Konqueror 3.5.4 bug:
+                       // "----------bug".replace(/^-/g,"") == "bug"
+
+                       bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0");       // trim one level of quoting
+
+                       // attacklab: clean up hack
+                       bq = bq.replace(/~0/g,"");
+
+                       bq = bq.replace(/^[ \t]+$/gm,"");               // trim whitespace-only lines
+                       bq = _RunBlockGamut(bq);                                // recurse
+
+                       bq = bq.replace(/(^|\n)/g,"$1  ");
+                       // These leading spaces screw with <pre> content, so we need to fix that:
+                       bq = bq.replace(
+                                       /(\s*<pre>[^\r]+?<\/pre>)/gm,
+                               function(wholeMatch,m1) {
+                                       var pre = m1;
+                                       // attacklab: hack around Konqueror 3.5.4 bug:
+                                       pre = pre.replace(/^  /mg,"~0");
+                                       pre = pre.replace(/~0/g,"");
+                                       return pre;
+                               });
+
+                       return hashBlock("<blockquote>\n" + bq + "\n</blockquote>");
+               });
+       return text;
+}
+
+
+var _FormParagraphs = function(text) {
+//
+//  Params:
+//    $text - string to process with html <p> tags
+//
+
+       // Strip leading and trailing lines:
+       text = text.replace(/^\n+/g,"");
+       text = text.replace(/\n+$/g,"");
+
+       var grafs = text.split(/\n{2,}/g);
+       var grafsOut = new Array();
+
+       //
+       // Wrap <p> tags.
+       //
+       var end = grafs.length;
+       for (var i=0; i<end; i++) {
+               var str = grafs[i];
+
+               // if this is an HTML marker, copy it
+               if (str.search(/~K(\d+)K/g) >= 0) {
+                       grafsOut.push(str);
+               }
+               else if (str.search(/\S/) >= 0) {
+                       str = _RunSpanGamut(str);
+                       str = str.replace(/^([ \t]*)/g,"<p>");
+                       str += "</p>"
+                       grafsOut.push(str);
+               }
+
+       }
+
+       //
+       // Unhashify HTML blocks
+       //
+       end = grafsOut.length;
+       for (var i=0; i<end; i++) {
+               // if this is a marker for an html block...
+               while (grafsOut[i].search(/~K(\d+)K/) >= 0) {
+                       var blockText = g_html_blocks[RegExp.$1];
+                       blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs
+                       grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText);
+               }
+       }
+
+       return grafsOut.join("\n\n");
+}
+
+
+var _EncodeAmpsAndAngles = function(text) {
+// Smart processing for ampersands and angle brackets that need to be encoded.
+
+       // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
+       //   http://bumppo.net/projects/amputator/
+       text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&amp;");
+
+       // Encode naked <'s
+       text = text.replace(/<(?![a-z\/?\$!])/gi,"&lt;");
+
+       return text;
+}
+
+
+var _EncodeBackslashEscapes = function(text) {
+//
+//   Parameter:  String.
+//   Returns:  The string, with after processing the following backslash
+//                        escape sequences.
+//
+
+       // attacklab: The polite way to do this is with the new
+       // escapeCharacters() function:
+       //
+       //      text = escapeCharacters(text,"\\",true);
+       //      text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
+       //
+       // ...but we're sidestepping its use of the (slow) RegExp constructor
+       // as an optimization for Firefox.  This function gets called a LOT.
+
+       text = text.replace(/\\(\\)/g,escapeCharacters_callback);
+       text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback);
+       return text;
+}
+
+
+var _DoAutoLinks = function(text) {
+
+       text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"<a href=\"$1\">$1</a>");
+
+       // Email addresses: <address@domain.foo>
+
+       /*
+               text = text.replace(/
+                       <
+                       (?:mailto:)?
+                       (
+                               [-.\w]+
+                               \@
+                               [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
+                       )
+                       >
+               /gi, _DoAutoLinks_callback());
+       */
+       text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,
+               function(wholeMatch,m1) {
+                       return _EncodeEmailAddress( _UnescapeSpecialChars(m1) );
+               }
+       );
+
+       return text;
+}
+
+
+var _EncodeEmailAddress = function(addr) {
+//
+//  Input: an email address, e.g. "foo@example.com"
+//
+//  Output: the email address as a mailto link, with each character
+//     of the address encoded as either a decimal or hex entity, in
+//     the hopes of foiling most address harvesting spam bots. E.g.:
+//
+//     <a href="&#x6D;&#97;&#105;&#108;&#x74;&#111;:&#102;&#111;&#111;&#64;&#101;
+//        x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;">&#102;&#111;&#111;
+//        &#64;&#101;x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;</a>
+//
+//  Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
+//  mailing list: <http://tinyurl.com/yu7ue>
+//
+
+       // attacklab: why can't javascript speak hex?
+       function char2hex(ch) {
+               var hexDigits = '0123456789ABCDEF';
+               var dec = ch.charCodeAt(0);
+               return(hexDigits.charAt(dec>>4) + hexDigits.charAt(dec&15));
+       }
+
+       var encode = [
+               function(ch){return "&#"+ch.charCodeAt(0)+";";},
+               function(ch){return "&#x"+char2hex(ch)+";";},
+               function(ch){return ch;}
+       ];
+
+       addr = "mailto:" + addr;
+
+       addr = addr.replace(/./g, function(ch) {
+               if (ch == "@") {
+                       // this *must* be encoded. I insist.
+                       ch = encode[Math.floor(Math.random()*2)](ch);
+               } else if (ch !=":") {
+                       // leave ':' alone (to spot mailto: later)
+                       var r = Math.random();
+                       // roughly 10% raw, 45% hex, 45% dec
+                       ch =  (
+                                       r > .9  ?       encode[2](ch)   :
+                                       r > .45 ?       encode[1](ch)   :
+                                                               encode[0](ch)
+                               );
+               }
+               return ch;
+       });
+
+       addr = "<a href=\"" + addr + "\">" + addr + "</a>";
+       addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part
+
+       return addr;
+}
+
+
+var _UnescapeSpecialChars = function(text) {
+//
+// Swap back in all the special characters we've hidden.
+//
+       text = text.replace(/~E(\d+)E/g,
+               function(wholeMatch,m1) {
+                       var charCodeToReplace = parseInt(m1);
+                       return String.fromCharCode(charCodeToReplace);
+               }
+       );
+       return text;
+}
+
+
+var _Outdent = function(text) {
+//
+// Remove one level of line-leading tabs or spaces
+//
+
+       // attacklab: hack around Konqueror 3.5.4 bug:
+       // "----------bug".replace(/^-/g,"") == "bug"
+
+       text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width
+
+       // attacklab: clean up hack
+       text = text.replace(/~0/g,"")
+
+       return text;
+}
+
+var _Detab = function(text) {
+// attacklab: Detab's completely rewritten for speed.
+// In perl we could fix it by anchoring the regexp with \G.
+// In javascript we're less fortunate.
+
+       // expand first n-1 tabs
+       text = text.replace(/\t(?=\t)/g,"    "); // attacklab: g_tab_width
+
+       // replace the nth with two sentinels
+       text = text.replace(/\t/g,"~A~B");
+
+       // use the sentinel to anchor our regex so it doesn't explode
+       text = text.replace(/~B(.+?)~A/g,
+               function(wholeMatch,m1,m2) {
+                       var leadingText = m1;
+                       var numSpaces = 4 - leadingText.length % 4;  // attacklab: g_tab_width
+
+                       // there *must* be a better way to do this:
+                       for (var i=0; i<numSpaces; i++) leadingText+=" ";
+
+                       return leadingText;
+               }
+       );
+
+       // clean up sentinels
+       text = text.replace(/~A/g,"    ");  // attacklab: g_tab_width
+       text = text.replace(/~B/g,"");
+
+       return text;
+}
+
+
+//
+//  attacklab: Utility functions
+//
+
+
+var escapeCharacters = function(text, charsToEscape, afterBackslash) {
+       // First we have to escape the escape characters so that
+       // we can build a character class out of them
+       var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g,"\\$1") + "])";
+
+       if (afterBackslash) {
+               regexString = "\\\\" + regexString;
+       }
+
+       var regex = new RegExp(regexString,"g");
+       text = text.replace(regex,escapeCharacters_callback);
+
+       return text;
+}
+
+
+var escapeCharacters_callback = function(wholeMatch,m1) {
+       var charCodeToEscape = m1.charCodeAt(0);
+       return "~E"+charCodeToEscape+"E";
+}
+
+} // end of Showdown.converter
+
+// export
+if (typeof module !== 'undefined') module.exports = Showdown;
\ No newline at end of file