showdown.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338
  1. //
  2. // showdown.js -- A javascript port of Markdown.
  3. //
  4. // Copyright (c) 2007 John Fraser.
  5. //
  6. // Original Markdown Copyright (c) 2004-2005 John Gruber
  7. // <http://daringfireball.net/projects/markdown/>
  8. //
  9. // Redistributable under a BSD-style open source license.
  10. // See license.txt for more information.
  11. //
  12. // The full source distribution is at:
  13. //
  14. // A A L
  15. // T C A
  16. // T K B
  17. //
  18. // <http://www.attacklab.net/>
  19. //
  20. //
  21. // Wherever possible, Showdown is a straight, line-by-line port
  22. // of the Perl version of Markdown.
  23. //
  24. // This is not a normal parser design; it's basically just a
  25. // series of string substitutions. It's hard to read and
  26. // maintain this way, but keeping Showdown close to the original
  27. // design makes it easier to port new features.
  28. //
  29. // More importantly, Showdown behaves like markdown.pl in most
  30. // edge cases. So web applications can do client-side preview
  31. // in Javascript, and then build identical HTML on the server.
  32. //
  33. // This port needs the new RegExp functionality of ECMA 262,
  34. // 3rd Edition (i.e. Javascript 1.5). Most modern web browsers
  35. // should do fine. Even with the new regular expression features,
  36. // We do a lot of work to emulate Perl's regex functionality.
  37. // The tricky changes in this file mostly have the "attacklab:"
  38. // label. Major or self-explanatory changes don't.
  39. //
  40. // Smart diff tools like Araxis Merge will be able to match up
  41. // this file with markdown.pl in a useful way. A little tweaking
  42. // helps: in a copy of markdown.pl, replace "#" with "//" and
  43. // replace "$text" with "text". Be sure to ignore whitespace
  44. // and line endings.
  45. //
  46. //
  47. // Showdown usage:
  48. //
  49. // var text = "Markdown *rocks*.";
  50. //
  51. // var converter = new Showdown.converter();
  52. // var html = converter.makeHtml(text);
  53. //
  54. // alert(html);
  55. //
  56. // Note: move the sample code to the bottom of this
  57. // file before uncommenting it.
  58. //
  59. //
  60. // Showdown namespace
  61. //
  62. var Showdown = {};
  63. //
  64. // converter
  65. //
  66. // Wraps all "globals" so that the only thing
  67. // exposed is makeHtml().
  68. //
  69. Showdown.converter = function() {
  70. //
  71. // Globals:
  72. //
  73. // Global hashes, used by various utility routines
  74. var g_urls;
  75. var g_titles;
  76. var g_html_blocks;
  77. // Used to track when we're inside an ordered or unordered list
  78. // (see _ProcessListItems() for details):
  79. var g_list_level = 0;
  80. this.makeHtml = function(text) {
  81. //
  82. // Main function. The order in which other subs are called here is
  83. // essential. Link and image substitutions need to happen before
  84. // _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>
  85. // and <img> tags get encoded.
  86. //
  87. // Clear the global hashes. If we don't clear these, you get conflicts
  88. // from other articles when generating a page which contains more than
  89. // one article (e.g. an index page that shows the N most recent
  90. // articles):
  91. g_urls = new Array();
  92. g_titles = new Array();
  93. g_html_blocks = new Array();
  94. // attacklab: Replace ~ with ~T
  95. // This lets us use tilde as an escape char to avoid md5 hashes
  96. // The choice of character is arbitray; anything that isn't
  97. // magic in Markdown will work.
  98. text = text.replace(/~/g,"~T");
  99. // attacklab: Replace $ with ~D
  100. // RegExp interprets $ as a special character
  101. // when it's in a replacement string
  102. text = text.replace(/\$/g,"~D");
  103. // Standardize line endings
  104. text = text.replace(/\r\n/g,"\n"); // DOS to Unix
  105. text = text.replace(/\r/g,"\n"); // Mac to Unix
  106. // Make sure text begins and ends with a couple of newlines:
  107. text = "\n\n" + text + "\n\n";
  108. // Convert all tabs to spaces.
  109. text = _Detab(text);
  110. // Strip any lines consisting only of spaces and tabs.
  111. // This makes subsequent regexen easier to write, because we can
  112. // match consecutive blank lines with /\n+/ instead of something
  113. // contorted like /[ \t]*\n+/ .
  114. text = text.replace(/^[ \t]+$/mg,"");
  115. // Turn block-level HTML blocks into hash entries
  116. text = _HashHTMLBlocks(text);
  117. // Strip link definitions, store in hashes.
  118. text = _StripLinkDefinitions(text);
  119. text = _RunBlockGamut(text);
  120. text = _UnescapeSpecialChars(text);
  121. // attacklab: Restore dollar signs
  122. text = text.replace(/~D/g,"$$");
  123. // attacklab: Restore tildes
  124. text = text.replace(/~T/g,"~");
  125. return text;
  126. }
  127. var _StripLinkDefinitions = function(text) {
  128. //
  129. // Strips link definitions from text, stores the URLs and titles in
  130. // hash references.
  131. //
  132. // Link defs are in the form: ^[id]: url "optional title"
  133. /*
  134. var text = text.replace(/
  135. ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1
  136. [ \t]*
  137. \n? // maybe *one* newline
  138. [ \t]*
  139. <?(\S+?)>? // url = $2
  140. [ \t]*
  141. \n? // maybe one newline
  142. [ \t]*
  143. (?:
  144. (\n*) // any lines skipped = $3 attacklab: lookbehind removed
  145. ["(]
  146. (.+?) // title = $4
  147. [")]
  148. [ \t]*
  149. )? // title is optional
  150. (?:\n+|$)
  151. /gm,
  152. function(){...});
  153. */
  154. var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,
  155. function (wholeMatch,m1,m2,m3,m4) {
  156. m1 = m1.toLowerCase();
  157. g_urls[m1] = _EncodeAmpsAndAngles(m2); // Link IDs are case-insensitive
  158. if (m3) {
  159. // Oops, found blank lines, so it's not a title.
  160. // Put back the parenthetical statement we stole.
  161. return m3+m4;
  162. } else if (m4) {
  163. g_titles[m1] = m4.replace(/"/g,"&quot;");
  164. }
  165. // Completely remove the definition from the text
  166. return "";
  167. }
  168. );
  169. return text;
  170. }
  171. var _HashHTMLBlocks = function(text) {
  172. // attacklab: Double up blank lines to reduce lookaround
  173. text = text.replace(/\n/g,"\n\n");
  174. // Hashify HTML blocks:
  175. // We only want to do this for block-level HTML tags, such as headers,
  176. // lists, and tables. That's because we still want to wrap <p>s around
  177. // "paragraphs" that are wrapped in non-block-level tags, such as anchors,
  178. // phrase emphasis, and spans. The list of tags we're looking for is
  179. // hard-coded:
  180. var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"
  181. var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"
  182. // First, look for nested blocks, e.g.:
  183. // <div>
  184. // <div>
  185. // tags for inner block must be indented.
  186. // </div>
  187. // </div>
  188. //
  189. // The outermost tags must start at the left margin for this to match, and
  190. // the inner nested divs must be indented.
  191. // We need to do this before the next, more liberal match, because the next
  192. // match will start at the first `<div>` and stop at the first `</div>`.
  193. // attacklab: This regex can be expensive when it fails.
  194. /*
  195. var text = text.replace(/
  196. ( // save in $1
  197. ^ // start of line (with /m)
  198. <($block_tags_a) // start tag = $2
  199. \b // word break
  200. // attacklab: hack around khtml/pcre bug...
  201. [^\r]*?\n // any number of lines, minimally matching
  202. </\2> // the matching end tag
  203. [ \t]* // trailing spaces/tabs
  204. (?=\n+) // followed by a newline
  205. ) // attacklab: there are sentinel newlines at end of document
  206. /gm,function(){...}};
  207. */
  208. 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);
  209. //
  210. // Now match more liberally, simply from `\n<tag>` to `</tag>\n`
  211. //
  212. /*
  213. var text = text.replace(/
  214. ( // save in $1
  215. ^ // start of line (with /m)
  216. <($block_tags_b) // start tag = $2
  217. \b // word break
  218. // attacklab: hack around khtml/pcre bug...
  219. [^\r]*? // any number of lines, minimally matching
  220. .*</\2> // the matching end tag
  221. [ \t]* // trailing spaces/tabs
  222. (?=\n+) // followed by a newline
  223. ) // attacklab: there are sentinel newlines at end of document
  224. /gm,function(){...}};
  225. */
  226. 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);
  227. // Special case just for <hr />. It was easier to make a special case than
  228. // to make the other regex more complicated.
  229. /*
  230. text = text.replace(/
  231. ( // save in $1
  232. \n\n // Starting after a blank line
  233. [ ]{0,3}
  234. (<(hr) // start tag = $2
  235. \b // word break
  236. ([^<>])*? //
  237. \/?>) // the matching end tag
  238. [ \t]*
  239. (?=\n{2,}) // followed by a blank line
  240. )
  241. /g,hashElement);
  242. */
  243. text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement);
  244. // Special case for standalone HTML comments:
  245. /*
  246. text = text.replace(/
  247. ( // save in $1
  248. \n\n // Starting after a blank line
  249. [ ]{0,3} // attacklab: g_tab_width - 1
  250. <!
  251. (--[^\r]*?--\s*)+
  252. >
  253. [ \t]*
  254. (?=\n{2,}) // followed by a blank line
  255. )
  256. /g,hashElement);
  257. */
  258. text = text.replace(/(\n\n[ ]{0,3}<!(--[^\r]*?--\s*)+>[ \t]*(?=\n{2,}))/g,hashElement);
  259. // PHP and ASP-style processor instructions (<?...?> and <%...%>)
  260. /*
  261. text = text.replace(/
  262. (?:
  263. \n\n // Starting after a blank line
  264. )
  265. ( // save in $1
  266. [ ]{0,3} // attacklab: g_tab_width - 1
  267. (?:
  268. <([?%]) // $2
  269. [^\r]*?
  270. \2>
  271. )
  272. [ \t]*
  273. (?=\n{2,}) // followed by a blank line
  274. )
  275. /g,hashElement);
  276. */
  277. text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement);
  278. // attacklab: Undo double lines (see comment at top of this function)
  279. text = text.replace(/\n\n/g,"\n");
  280. return text;
  281. }
  282. var hashElement = function(wholeMatch,m1) {
  283. var blockText = m1;
  284. // Undo double lines
  285. blockText = blockText.replace(/\n\n/g,"\n");
  286. blockText = blockText.replace(/^\n/,"");
  287. // strip trailing blank lines
  288. blockText = blockText.replace(/\n+$/g,"");
  289. // Replace the element text with a marker ("~KxK" where x is its key)
  290. blockText = "\n\n~K" + (g_html_blocks.push(blockText)-1) + "K\n\n";
  291. return blockText;
  292. };
  293. var _RunBlockGamut = function(text) {
  294. //
  295. // These are all the transformations that form block-level
  296. // tags like paragraphs, headers, and list items.
  297. //
  298. text = _DoHeaders(text);
  299. // Do Horizontal Rules:
  300. var key = hashBlock("<hr />");
  301. text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key);
  302. text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key);
  303. text = text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key);
  304. text = _DoLists(text);
  305. text = _DoGithubCodeBlocks(text);
  306. text = _DoCodeBlocks(text);
  307. text = _DoBlockQuotes(text);
  308. // We already ran _HashHTMLBlocks() before, in Markdown(), but that
  309. // was to escape raw HTML in the original Markdown source. This time,
  310. // we're escaping the markup we've just created, so that we don't wrap
  311. // <p> tags around block-level tags.
  312. text = _HashHTMLBlocks(text);
  313. text = _FormParagraphs(text);
  314. return text;
  315. }
  316. var _RunSpanGamut = function(text) {
  317. //
  318. // These are all the transformations that occur *within* block-level
  319. // tags like paragraphs, headers, and list items.
  320. //
  321. text = _DoCodeSpans(text);
  322. text = _EscapeSpecialCharsWithinTagAttributes(text);
  323. text = _EncodeBackslashEscapes(text);
  324. // Process anchor and image tags. Images must come first,
  325. // because ![foo][f] looks like an anchor.
  326. text = _DoImages(text);
  327. text = _DoAnchors(text);
  328. // Make links out of things like `<http://example.com/>`
  329. // Must come after _DoAnchors(), because you can use < and >
  330. // delimiters in inline links like [this](<url>).
  331. text = _DoAutoLinks(text);
  332. text = _EncodeAmpsAndAngles(text);
  333. text = _DoItalicsAndBold(text);
  334. // Do hard breaks:
  335. text = text.replace(/ +\n/g," <br />\n");
  336. return text;
  337. }
  338. var _EscapeSpecialCharsWithinTagAttributes = function(text) {
  339. //
  340. // Within tags -- meaning between < and > -- encode [\ ` * _] so they
  341. // don't conflict with their use in Markdown for code, italics and strong.
  342. //
  343. // Build a regex to find HTML tags and comments. See Friedl's
  344. // "Mastering Regular Expressions", 2nd Ed., pp. 200-201.
  345. var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi;
  346. text = text.replace(regex, function(wholeMatch) {
  347. var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`");
  348. tag = escapeCharacters(tag,"\\`*_");
  349. return tag;
  350. });
  351. return text;
  352. }
  353. var _DoAnchors = function(text) {
  354. //
  355. // Turn Markdown link shortcuts into XHTML <a> tags.
  356. //
  357. //
  358. // First, handle reference-style links: [link text] [id]
  359. //
  360. /*
  361. text = text.replace(/
  362. ( // wrap whole match in $1
  363. \[
  364. (
  365. (?:
  366. \[[^\]]*\] // allow brackets nested one level
  367. |
  368. [^\[] // or anything else
  369. )*
  370. )
  371. \]
  372. [ ]? // one optional space
  373. (?:\n[ ]*)? // one optional newline followed by spaces
  374. \[
  375. (.*?) // id = $3
  376. \]
  377. )()()()() // pad remaining backreferences
  378. /g,_DoAnchors_callback);
  379. */
  380. text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag);
  381. //
  382. // Next, inline-style links: [link text](url "optional title")
  383. //
  384. /*
  385. text = text.replace(/
  386. ( // wrap whole match in $1
  387. \[
  388. (
  389. (?:
  390. \[[^\]]*\] // allow brackets nested one level
  391. |
  392. [^\[\]] // or anything else
  393. )
  394. )
  395. \]
  396. \( // literal paren
  397. [ \t]*
  398. () // no id, so leave $3 empty
  399. <?(.*?)>? // href = $4
  400. [ \t]*
  401. ( // $5
  402. (['"]) // quote char = $6
  403. (.*?) // Title = $7
  404. \6 // matching quote
  405. [ \t]* // ignore any spaces/tabs between closing quote and )
  406. )? // title is optional
  407. \)
  408. )
  409. /g,writeAnchorTag);
  410. */
  411. text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag);
  412. //
  413. // Last, handle reference-style shortcuts: [link text]
  414. // These must come last in case you've also got [link test][1]
  415. // or [link test](/foo)
  416. //
  417. /*
  418. text = text.replace(/
  419. ( // wrap whole match in $1
  420. \[
  421. ([^\[\]]+) // link text = $2; can't contain '[' or ']'
  422. \]
  423. )()()()()() // pad rest of backreferences
  424. /g, writeAnchorTag);
  425. */
  426. text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);
  427. return text;
  428. }
  429. var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {
  430. if (m7 == undefined) m7 = "";
  431. var whole_match = m1;
  432. var link_text = m2;
  433. var link_id = m3.toLowerCase();
  434. var url = m4;
  435. var title = m7;
  436. if (url == "") {
  437. if (link_id == "") {
  438. // lower-case and turn embedded newlines into spaces
  439. link_id = link_text.toLowerCase().replace(/ ?\n/g," ");
  440. }
  441. url = "#"+link_id;
  442. if (g_urls[link_id] != undefined) {
  443. url = g_urls[link_id];
  444. if (g_titles[link_id] != undefined) {
  445. title = g_titles[link_id];
  446. }
  447. }
  448. else {
  449. if (whole_match.search(/\(\s*\)$/m)>-1) {
  450. // Special case for explicit empty url
  451. url = "";
  452. } else {
  453. return whole_match;
  454. }
  455. }
  456. }
  457. url = escapeCharacters(url,"*_");
  458. var result = "<a href=\"" + url + "\"";
  459. if (title != "") {
  460. title = title.replace(/"/g,"&quot;");
  461. title = escapeCharacters(title,"*_");
  462. result += " title=\"" + title + "\"";
  463. }
  464. result += ">" + link_text + "</a>";
  465. return result;
  466. }
  467. var _DoImages = function(text) {
  468. //
  469. // Turn Markdown image shortcuts into <img> tags.
  470. //
  471. //
  472. // First, handle reference-style labeled images: ![alt text][id]
  473. //
  474. /*
  475. text = text.replace(/
  476. ( // wrap whole match in $1
  477. !\[
  478. (.*?) // alt text = $2
  479. \]
  480. [ ]? // one optional space
  481. (?:\n[ ]*)? // one optional newline followed by spaces
  482. \[
  483. (.*?) // id = $3
  484. \]
  485. )()()()() // pad rest of backreferences
  486. /g,writeImageTag);
  487. */
  488. text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag);
  489. //
  490. // Next, handle inline images: ![alt text](url "optional title")
  491. // Don't forget: encode * and _
  492. /*
  493. text = text.replace(/
  494. ( // wrap whole match in $1
  495. !\[
  496. (.*?) // alt text = $2
  497. \]
  498. \s? // One optional whitespace character
  499. \( // literal paren
  500. [ \t]*
  501. () // no id, so leave $3 empty
  502. <?(\S+?)>? // src url = $4
  503. [ \t]*
  504. ( // $5
  505. (['"]) // quote char = $6
  506. (.*?) // title = $7
  507. \6 // matching quote
  508. [ \t]*
  509. )? // title is optional
  510. \)
  511. )
  512. /g,writeImageTag);
  513. */
  514. text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag);
  515. return text;
  516. }
  517. var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {
  518. var whole_match = m1;
  519. var alt_text = m2;
  520. var link_id = m3.toLowerCase();
  521. var url = m4;
  522. var title = m7;
  523. if (!title) title = "";
  524. if (url == "") {
  525. if (link_id == "") {
  526. // lower-case and turn embedded newlines into spaces
  527. link_id = alt_text.toLowerCase().replace(/ ?\n/g," ");
  528. }
  529. url = "#"+link_id;
  530. if (g_urls[link_id] != undefined) {
  531. url = g_urls[link_id];
  532. if (g_titles[link_id] != undefined) {
  533. title = g_titles[link_id];
  534. }
  535. }
  536. else {
  537. return whole_match;
  538. }
  539. }
  540. alt_text = alt_text.replace(/"/g,"&quot;");
  541. url = escapeCharacters(url,"*_");
  542. var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\"";
  543. // attacklab: Markdown.pl adds empty title attributes to images.
  544. // Replicate this bug.
  545. //if (title != "") {
  546. title = title.replace(/"/g,"&quot;");
  547. title = escapeCharacters(title,"*_");
  548. result += " title=\"" + title + "\"";
  549. //}
  550. result += " />";
  551. return result;
  552. }
  553. var _DoHeaders = function(text) {
  554. // Setext-style headers:
  555. // Header 1
  556. // ========
  557. //
  558. // Header 2
  559. // --------
  560. //
  561. text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,
  562. function(wholeMatch,m1){return hashBlock('<h1 id="' + headerId(m1) + '">' + _RunSpanGamut(m1) + "</h1>");});
  563. text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,
  564. function(matchFound,m1){return hashBlock('<h2 id="' + headerId(m1) + '">' + _RunSpanGamut(m1) + "</h2>");});
  565. // atx-style headers:
  566. // # Header 1
  567. // ## Header 2
  568. // ## Header 2 with closing hashes ##
  569. // ...
  570. // ###### Header 6
  571. //
  572. /*
  573. text = text.replace(/
  574. ^(\#{1,6}) // $1 = string of #'s
  575. [ \t]*
  576. (.+?) // $2 = Header text
  577. [ \t]*
  578. \#* // optional closing #'s (not counted)
  579. \n+
  580. /gm, function() {...});
  581. */
  582. text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,
  583. function(wholeMatch,m1,m2) {
  584. var h_level = m1.length;
  585. return hashBlock("<h" + h_level + ' id="' + headerId(m2) + '">' + _RunSpanGamut(m2) + "</h" + h_level + ">");
  586. });
  587. function headerId(m) {
  588. return m.replace(/[^\w]/g, '').toLowerCase();
  589. }
  590. return text;
  591. }
  592. // This declaration keeps Dojo compressor from outputting garbage:
  593. var _ProcessListItems;
  594. var _DoLists = function(text) {
  595. //
  596. // Form HTML ordered (numbered) and unordered (bulleted) lists.
  597. //
  598. // attacklab: add sentinel to hack around khtml/safari bug:
  599. // http://bugs.webkit.org/show_bug.cgi?id=11231
  600. text += "~0";
  601. // Re-usable pattern to match any entirel ul or ol list:
  602. /*
  603. var whole_list = /
  604. ( // $1 = whole list
  605. ( // $2
  606. [ ]{0,3} // attacklab: g_tab_width - 1
  607. ([*+-]|\d+[.]) // $3 = first list item marker
  608. [ \t]+
  609. )
  610. [^\r]+?
  611. ( // $4
  612. ~0 // sentinel for workaround; should be $
  613. |
  614. \n{2,}
  615. (?=\S)
  616. (?! // Negative lookahead for another list item marker
  617. [ \t]*
  618. (?:[*+-]|\d+[.])[ \t]+
  619. )
  620. )
  621. )/g
  622. */
  623. var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
  624. if (g_list_level) {
  625. text = text.replace(whole_list,function(wholeMatch,m1,m2) {
  626. var list = m1;
  627. var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol";
  628. // Turn double returns into triple returns, so that we can make a
  629. // paragraph for the last item in a list, if necessary:
  630. list = list.replace(/\n{2,}/g,"\n\n\n");;
  631. var result = _ProcessListItems(list);
  632. // Trim any trailing whitespace, to put the closing `</$list_type>`
  633. // up on the preceding line, to get it past the current stupid
  634. // HTML block parser. This is a hack to work around the terrible
  635. // hack that is the HTML block parser.
  636. result = result.replace(/\s+$/,"");
  637. result = "<"+list_type+">" + result + "</"+list_type+">\n";
  638. return result;
  639. });
  640. } else {
  641. whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;
  642. text = text.replace(whole_list,function(wholeMatch,m1,m2,m3) {
  643. var runup = m1;
  644. var list = m2;
  645. var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol";
  646. // Turn double returns into triple returns, so that we can make a
  647. // paragraph for the last item in a list, if necessary:
  648. var list = list.replace(/\n{2,}/g,"\n\n\n");;
  649. var result = _ProcessListItems(list);
  650. result = runup + "<"+list_type+">\n" + result + "</"+list_type+">\n";
  651. return result;
  652. });
  653. }
  654. // attacklab: strip sentinel
  655. text = text.replace(/~0/,"");
  656. return text;
  657. }
  658. _ProcessListItems = function(list_str) {
  659. //
  660. // Process the contents of a single ordered or unordered list, splitting it
  661. // into individual list items.
  662. //
  663. // The $g_list_level global keeps track of when we're inside a list.
  664. // Each time we enter a list, we increment it; when we leave a list,
  665. // we decrement. If it's zero, we're not in a list anymore.
  666. //
  667. // We do this because when we're not inside a list, we want to treat
  668. // something like this:
  669. //
  670. // I recommend upgrading to version
  671. // 8. Oops, now this line is treated
  672. // as a sub-list.
  673. //
  674. // As a single paragraph, despite the fact that the second line starts
  675. // with a digit-period-space sequence.
  676. //
  677. // Whereas when we're inside a list (or sub-list), that line will be
  678. // treated as the start of a sub-list. What a kludge, huh? This is
  679. // an aspect of Markdown's syntax that's hard to parse perfectly
  680. // without resorting to mind-reading. Perhaps the solution is to
  681. // change the syntax rules such that sub-lists must start with a
  682. // starting cardinal number; e.g. "1." or "a.".
  683. g_list_level++;
  684. // trim trailing blank lines:
  685. list_str = list_str.replace(/\n{2,}$/,"\n");
  686. // attacklab: add sentinel to emulate \z
  687. list_str += "~0";
  688. /*
  689. list_str = list_str.replace(/
  690. (\n)? // leading line = $1
  691. (^[ \t]*) // leading whitespace = $2
  692. ([*+-]|\d+[.]) [ \t]+ // list marker = $3
  693. ([^\r]+? // list item text = $4
  694. (\n{1,2}))
  695. (?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+))
  696. /gm, function(){...});
  697. */
  698. list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,
  699. function(wholeMatch,m1,m2,m3,m4){
  700. var item = m4;
  701. var leading_line = m1;
  702. var leading_space = m2;
  703. if (leading_line || (item.search(/\n{2,}/)>-1)) {
  704. item = _RunBlockGamut(_Outdent(item));
  705. }
  706. else {
  707. // Recursion for sub-lists:
  708. item = _DoLists(_Outdent(item));
  709. item = item.replace(/\n$/,""); // chomp(item)
  710. item = _RunSpanGamut(item);
  711. }
  712. return "<li>" + item + "</li>\n";
  713. }
  714. );
  715. // attacklab: strip sentinel
  716. list_str = list_str.replace(/~0/g,"");
  717. g_list_level--;
  718. return list_str;
  719. }
  720. var _DoCodeBlocks = function(text) {
  721. //
  722. // Process Markdown `<pre><code>` blocks.
  723. //
  724. /*
  725. text = text.replace(text,
  726. /(?:\n\n|^)
  727. ( // $1 = the code block -- one or more lines, starting with a space/tab
  728. (?:
  729. (?:[ ]{4}|\t) // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
  730. .*\n+
  731. )+
  732. )
  733. (\n*[ ]{0,3}[^ \t\n]|(?=~0)) // attacklab: g_tab_width
  734. /g,function(){...});
  735. */
  736. // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
  737. text += "~0";
  738. text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
  739. function(wholeMatch,m1,m2) {
  740. var codeblock = m1;
  741. var nextChar = m2;
  742. codeblock = _EncodeCode( _Outdent(codeblock));
  743. codeblock = _Detab(codeblock);
  744. codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines
  745. codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace
  746. codeblock = "<pre><code>" + codeblock + "\n</code></pre>";
  747. return hashBlock(codeblock) + nextChar;
  748. }
  749. );
  750. // attacklab: strip sentinel
  751. text = text.replace(/~0/,"");
  752. return text;
  753. }
  754. var _DoGithubCodeBlocks = function(text) {
  755. //
  756. // Process Github-style code blocks
  757. // Example:
  758. // ```ruby
  759. // def hello_world(x)
  760. // puts "Hello, #{x}"
  761. // end
  762. // ```
  763. //
  764. // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
  765. text += "~0";
  766. text = text.replace(/\n```(.*)\n([^`]+)\n```/g,
  767. function(wholeMatch,m1,m2) {
  768. var language = m1;
  769. var codeblock = m2;
  770. codeblock = _EncodeCode(codeblock);
  771. codeblock = _Detab(codeblock);
  772. codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines
  773. codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace
  774. codeblock = "<pre><code class=" + language + ">" + codeblock + "\n</code></pre>";
  775. return hashBlock(codeblock);
  776. }
  777. );
  778. // attacklab: strip sentinel
  779. text = text.replace(/~0/,"");
  780. return text;
  781. }
  782. var hashBlock = function(text) {
  783. text = text.replace(/(^\n+|\n+$)/g,"");
  784. return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n";
  785. }
  786. var _DoCodeSpans = function(text) {
  787. //
  788. // * Backtick quotes are used for <code></code> spans.
  789. //
  790. // * You can use multiple backticks as the delimiters if you want to
  791. // include literal backticks in the code span. So, this input:
  792. //
  793. // Just type ``foo `bar` baz`` at the prompt.
  794. //
  795. // Will translate to:
  796. //
  797. // <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
  798. //
  799. // There's no arbitrary limit to the number of backticks you
  800. // can use as delimters. If you need three consecutive backticks
  801. // in your code, use four for delimiters, etc.
  802. //
  803. // * You can use spaces to get literal backticks at the edges:
  804. //
  805. // ... type `` `bar` `` ...
  806. //
  807. // Turns to:
  808. //
  809. // ... type <code>`bar`</code> ...
  810. //
  811. /*
  812. text = text.replace(/
  813. (^|[^\\]) // Character before opening ` can't be a backslash
  814. (`+) // $2 = Opening run of `
  815. ( // $3 = The code block
  816. [^\r]*?
  817. [^`] // attacklab: work around lack of lookbehind
  818. )
  819. \2 // Matching closer
  820. (?!`)
  821. /gm, function(){...});
  822. */
  823. text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
  824. function(wholeMatch,m1,m2,m3,m4) {
  825. var c = m3;
  826. c = c.replace(/^([ \t]*)/g,""); // leading whitespace
  827. c = c.replace(/[ \t]*$/g,""); // trailing whitespace
  828. c = _EncodeCode(c);
  829. return m1+"<code>"+c+"</code>";
  830. });
  831. return text;
  832. }
  833. var _EncodeCode = function(text) {
  834. //
  835. // Encode/escape certain characters inside Markdown code runs.
  836. // The point is that in code, these characters are literals,
  837. // and lose their special Markdown meanings.
  838. //
  839. // Encode all ampersands; HTML entities are not
  840. // entities within a Markdown code span.
  841. text = text.replace(/&/g,"&amp;");
  842. // Do the angle bracket song and dance:
  843. text = text.replace(/</g,"&lt;");
  844. text = text.replace(/>/g,"&gt;");
  845. // Now, escape characters that are magic in Markdown:
  846. text = escapeCharacters(text,"\*_{}[]\\",false);
  847. // jj the line above breaks this:
  848. //---
  849. //* Item
  850. // 1. Subitem
  851. // special char: *
  852. //---
  853. return text;
  854. }
  855. var _DoItalicsAndBold = function(text) {
  856. // <strong> must go first:
  857. text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,
  858. "<strong>$2</strong>");
  859. text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,
  860. "<em>$2</em>");
  861. return text;
  862. }
  863. var _DoBlockQuotes = function(text) {
  864. /*
  865. text = text.replace(/
  866. ( // Wrap whole match in $1
  867. (
  868. ^[ \t]*>[ \t]? // '>' at the start of a line
  869. .+\n // rest of the first line
  870. (.+\n)* // subsequent consecutive lines
  871. \n* // blanks
  872. )+
  873. )
  874. /gm, function(){...});
  875. */
  876. text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,
  877. function(wholeMatch,m1) {
  878. var bq = m1;
  879. // attacklab: hack around Konqueror 3.5.4 bug:
  880. // "----------bug".replace(/^-/g,"") == "bug"
  881. bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0"); // trim one level of quoting
  882. // attacklab: clean up hack
  883. bq = bq.replace(/~0/g,"");
  884. bq = bq.replace(/^[ \t]+$/gm,""); // trim whitespace-only lines
  885. bq = _RunBlockGamut(bq); // recurse
  886. bq = bq.replace(/(^|\n)/g,"$1 ");
  887. // These leading spaces screw with <pre> content, so we need to fix that:
  888. bq = bq.replace(
  889. /(\s*<pre>[^\r]+?<\/pre>)/gm,
  890. function(wholeMatch,m1) {
  891. var pre = m1;
  892. // attacklab: hack around Konqueror 3.5.4 bug:
  893. pre = pre.replace(/^ /mg,"~0");
  894. pre = pre.replace(/~0/g,"");
  895. return pre;
  896. });
  897. return hashBlock("<blockquote>\n" + bq + "\n</blockquote>");
  898. });
  899. return text;
  900. }
  901. var _FormParagraphs = function(text) {
  902. //
  903. // Params:
  904. // $text - string to process with html <p> tags
  905. //
  906. // Strip leading and trailing lines:
  907. text = text.replace(/^\n+/g,"");
  908. text = text.replace(/\n+$/g,"");
  909. var grafs = text.split(/\n{2,}/g);
  910. var grafsOut = new Array();
  911. //
  912. // Wrap <p> tags.
  913. //
  914. var end = grafs.length;
  915. for (var i=0; i<end; i++) {
  916. var str = grafs[i];
  917. // if this is an HTML marker, copy it
  918. if (str.search(/~K(\d+)K/g) >= 0) {
  919. grafsOut.push(str);
  920. }
  921. else if (str.search(/\S/) >= 0) {
  922. str = _RunSpanGamut(str);
  923. str = str.replace(/^([ \t]*)/g,"<p>");
  924. str += "</p>"
  925. grafsOut.push(str);
  926. }
  927. }
  928. //
  929. // Unhashify HTML blocks
  930. //
  931. end = grafsOut.length;
  932. for (var i=0; i<end; i++) {
  933. // if this is a marker for an html block...
  934. while (grafsOut[i].search(/~K(\d+)K/) >= 0) {
  935. var blockText = g_html_blocks[RegExp.$1];
  936. blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs
  937. grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText);
  938. }
  939. }
  940. return grafsOut.join("\n\n");
  941. }
  942. var _EncodeAmpsAndAngles = function(text) {
  943. // Smart processing for ampersands and angle brackets that need to be encoded.
  944. // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
  945. // http://bumppo.net/projects/amputator/
  946. text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&amp;");
  947. // Encode naked <'s
  948. text = text.replace(/<(?![a-z\/?\$!])/gi,"&lt;");
  949. return text;
  950. }
  951. var _EncodeBackslashEscapes = function(text) {
  952. //
  953. // Parameter: String.
  954. // Returns: The string, with after processing the following backslash
  955. // escape sequences.
  956. //
  957. // attacklab: The polite way to do this is with the new
  958. // escapeCharacters() function:
  959. //
  960. // text = escapeCharacters(text,"\\",true);
  961. // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
  962. //
  963. // ...but we're sidestepping its use of the (slow) RegExp constructor
  964. // as an optimization for Firefox. This function gets called a LOT.
  965. text = text.replace(/\\(\\)/g,escapeCharacters_callback);
  966. text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback);
  967. return text;
  968. }
  969. var _DoAutoLinks = function(text) {
  970. text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"<a href=\"$1\">$1</a>");
  971. // Email addresses: <address@domain.foo>
  972. /*
  973. text = text.replace(/
  974. <
  975. (?:mailto:)?
  976. (
  977. [-.\w]+
  978. \@
  979. [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
  980. )
  981. >
  982. /gi, _DoAutoLinks_callback());
  983. */
  984. text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,
  985. function(wholeMatch,m1) {
  986. return _EncodeEmailAddress( _UnescapeSpecialChars(m1) );
  987. }
  988. );
  989. return text;
  990. }
  991. var _EncodeEmailAddress = function(addr) {
  992. //
  993. // Input: an email address, e.g. "foo@example.com"
  994. //
  995. // Output: the email address as a mailto link, with each character
  996. // of the address encoded as either a decimal or hex entity, in
  997. // the hopes of foiling most address harvesting spam bots. E.g.:
  998. //
  999. // <a href="&#x6D;&#97;&#105;&#108;&#x74;&#111;:&#102;&#111;&#111;&#64;&#101;
  1000. // x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;">&#102;&#111;&#111;
  1001. // &#64;&#101;x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;</a>
  1002. //
  1003. // Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
  1004. // mailing list: <http://tinyurl.com/yu7ue>
  1005. //
  1006. // attacklab: why can't javascript speak hex?
  1007. function char2hex(ch) {
  1008. var hexDigits = '0123456789ABCDEF';
  1009. var dec = ch.charCodeAt(0);
  1010. return(hexDigits.charAt(dec>>4) + hexDigits.charAt(dec&15));
  1011. }
  1012. var encode = [
  1013. function(ch){return "&#"+ch.charCodeAt(0)+";";},
  1014. function(ch){return "&#x"+char2hex(ch)+";";},
  1015. function(ch){return ch;}
  1016. ];
  1017. addr = "mailto:" + addr;
  1018. addr = addr.replace(/./g, function(ch) {
  1019. if (ch == "@") {
  1020. // this *must* be encoded. I insist.
  1021. ch = encode[Math.floor(Math.random()*2)](ch);
  1022. } else if (ch !=":") {
  1023. // leave ':' alone (to spot mailto: later)
  1024. var r = Math.random();
  1025. // roughly 10% raw, 45% hex, 45% dec
  1026. ch = (
  1027. r > .9 ? encode[2](ch) :
  1028. r > .45 ? encode[1](ch) :
  1029. encode[0](ch)
  1030. );
  1031. }
  1032. return ch;
  1033. });
  1034. addr = "<a href=\"" + addr + "\">" + addr + "</a>";
  1035. addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part
  1036. return addr;
  1037. }
  1038. var _UnescapeSpecialChars = function(text) {
  1039. //
  1040. // Swap back in all the special characters we've hidden.
  1041. //
  1042. text = text.replace(/~E(\d+)E/g,
  1043. function(wholeMatch,m1) {
  1044. var charCodeToReplace = parseInt(m1);
  1045. return String.fromCharCode(charCodeToReplace);
  1046. }
  1047. );
  1048. return text;
  1049. }
  1050. var _Outdent = function(text) {
  1051. //
  1052. // Remove one level of line-leading tabs or spaces
  1053. //
  1054. // attacklab: hack around Konqueror 3.5.4 bug:
  1055. // "----------bug".replace(/^-/g,"") == "bug"
  1056. text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width
  1057. // attacklab: clean up hack
  1058. text = text.replace(/~0/g,"")
  1059. return text;
  1060. }
  1061. var _Detab = function(text) {
  1062. // attacklab: Detab's completely rewritten for speed.
  1063. // In perl we could fix it by anchoring the regexp with \G.
  1064. // In javascript we're less fortunate.
  1065. // expand first n-1 tabs
  1066. text = text.replace(/\t(?=\t)/g," "); // attacklab: g_tab_width
  1067. // replace the nth with two sentinels
  1068. text = text.replace(/\t/g,"~A~B");
  1069. // use the sentinel to anchor our regex so it doesn't explode
  1070. text = text.replace(/~B(.+?)~A/g,
  1071. function(wholeMatch,m1,m2) {
  1072. var leadingText = m1;
  1073. var numSpaces = 4 - leadingText.length % 4; // attacklab: g_tab_width
  1074. // there *must* be a better way to do this:
  1075. for (var i=0; i<numSpaces; i++) leadingText+=" ";
  1076. return leadingText;
  1077. }
  1078. );
  1079. // clean up sentinels
  1080. text = text.replace(/~A/g," "); // attacklab: g_tab_width
  1081. text = text.replace(/~B/g,"");
  1082. return text;
  1083. }
  1084. //
  1085. // attacklab: Utility functions
  1086. //
  1087. var escapeCharacters = function(text, charsToEscape, afterBackslash) {
  1088. // First we have to escape the escape characters so that
  1089. // we can build a character class out of them
  1090. var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g,"\\$1") + "])";
  1091. if (afterBackslash) {
  1092. regexString = "\\\\" + regexString;
  1093. }
  1094. var regex = new RegExp(regexString,"g");
  1095. text = text.replace(regex,escapeCharacters_callback);
  1096. return text;
  1097. }
  1098. var escapeCharacters_callback = function(wholeMatch,m1) {
  1099. var charCodeToEscape = m1.charCodeAt(0);
  1100. return "~E"+charCodeToEscape+"E";
  1101. }
  1102. } // end of Showdown.converter
  1103. // export
  1104. if (typeof exports != 'undefined') exports = Showdown;