showdown.js 45 KB

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