bootstrap_pagedown 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,719 @@
1
+ (function () {
2
+ // A quick way to make sure we're only keeping span-level tags when we need to.
3
+ // This isn't supposed to be foolproof. It's just a quick way to make sure we
4
+ // keep all span-level tags returned by a pagedown converter. It should allow
5
+ // all span-level tags through, with or without attributes.
6
+ var inlineTags = new RegExp(['^(<\\/?(a|abbr|acronym|applet|area|b|basefont|',
7
+ 'bdo|big|button|cite|code|del|dfn|em|figcaption|',
8
+ 'font|i|iframe|img|input|ins|kbd|label|map|',
9
+ 'mark|meter|object|param|progress|q|ruby|rp|rt|s|',
10
+ 'samp|script|select|small|span|strike|strong|',
11
+ 'sub|sup|textarea|time|tt|u|var|wbr)[^>]*>|',
12
+ '<(br)\\s?\\/?>)$'].join(''), 'i');
13
+
14
+ /******************************************************************
15
+ * Utility Functions *
16
+ *****************************************************************/
17
+
18
+ // patch for ie7
19
+ if (!Array.indexOf) {
20
+ Array.prototype.indexOf = function(obj) {
21
+ for (var i = 0; i < this.length; i++) {
22
+ if (this[i] == obj) {
23
+ return i;
24
+ }
25
+ }
26
+ return -1;
27
+ };
28
+ }
29
+
30
+ function trim(str) {
31
+ return str.replace(/^\s+|\s+$/g, '');
32
+ }
33
+
34
+ function rtrim(str) {
35
+ return str.replace(/\s+$/g, '');
36
+ }
37
+
38
+ // Remove one level of indentation from text. Indent is 4 spaces.
39
+ function outdent(text) {
40
+ return text.replace(new RegExp('^(\\t|[ ]{1,4})', 'gm'), '');
41
+ }
42
+
43
+ function contains(str, substr) {
44
+ return str.indexOf(substr) != -1;
45
+ }
46
+
47
+ // Sanitize html, removing tags that aren't in the whitelist
48
+ function sanitizeHtml(html, whitelist) {
49
+ return html.replace(/<[^>]*>?/gi, function(tag) {
50
+ return tag.match(whitelist) ? tag : '';
51
+ });
52
+ }
53
+
54
+ // Merge two arrays, keeping only unique elements.
55
+ function union(x, y) {
56
+ var obj = {};
57
+ for (var i = 0; i < x.length; i++)
58
+ obj[x[i]] = x[i];
59
+ for (i = 0; i < y.length; i++)
60
+ obj[y[i]] = y[i];
61
+ var res = [];
62
+ for (var k in obj) {
63
+ if (obj.hasOwnProperty(k))
64
+ res.push(obj[k]);
65
+ }
66
+ return res;
67
+ }
68
+
69
+ // JS regexes don't support \A or \Z, so we add sentinels, as Pagedown
70
+ // does. In this case, we add the ascii codes for start of text (STX) and
71
+ // end of text (ETX), an idea borrowed from:
72
+ // https://github.com/tanakahisateru/js-markdown-extra
73
+ function addAnchors(text) {
74
+ if(text.charAt(0) != '\x02')
75
+ text = '\x02' + text;
76
+ if(text.charAt(text.length - 1) != '\x03')
77
+ text = text + '\x03';
78
+ return text;
79
+ }
80
+
81
+ // Remove STX and ETX sentinels.
82
+ function removeAnchors(text) {
83
+ if(text.charAt(0) == '\x02')
84
+ text = text.substr(1);
85
+ if(text.charAt(text.length - 1) == '\x03')
86
+ text = text.substr(0, text.length - 1);
87
+ return text;
88
+ }
89
+
90
+ // Convert markdown within an element, retaining only span-level tags
91
+ function convertSpans(text, extra) {
92
+ return sanitizeHtml(convertAll(text, extra), inlineTags);
93
+ }
94
+
95
+ // Convert internal markdown using the stock pagedown converter
96
+ function convertAll(text, extra) {
97
+ var result = extra.blockGamutHookCallback(text);
98
+ // We need to perform these operations since we skip the steps in the converter
99
+ result = unescapeSpecialChars(result);
100
+ result = result.replace(/~D/g, "$$").replace(/~T/g, "~");
101
+ result = extra.previousPostConversion(result);
102
+ return result;
103
+ }
104
+
105
+ // Convert escaped special characters to HTML decimal entity codes.
106
+ function processEscapes(text) {
107
+ // Markdown extra adds two escapable characters, `:` and `|`
108
+ // If escaped, we convert them to html entities so our
109
+ // regexes don't recognize them. Markdown doesn't support escaping
110
+ // the escape character, e.g. `\\`, which make this even simpler.
111
+ return text.replace(/\\\|/g, '&#124;').replace(/\\:/g, '&#58;');
112
+ }
113
+
114
+ // Duplicated from PageDown converter
115
+ function unescapeSpecialChars(text) {
116
+ // Swap back in all the special characters we've hidden.
117
+ text = text.replace(/~E(\d+)E/g, function(wholeMatch, m1) {
118
+ var charCodeToReplace = parseInt(m1);
119
+ return String.fromCharCode(charCodeToReplace);
120
+ });
121
+ return text;
122
+ }
123
+
124
+ function slugify(text) {
125
+ return text.toLowerCase()
126
+ .replace(/\s+/g, '-') // Replace spaces with -
127
+ .replace(/[^\w\-]+/g, '') // Remove all non-word chars
128
+ .replace(/\-\-+/g, '-') // Replace multiple - with single -
129
+ .replace(/^-+/, '') // Trim - from start of text
130
+ .replace(/-+$/, ''); // Trim - from end of text
131
+ }
132
+
133
+ /*****************************************************************************
134
+ * Markdown.Extra *
135
+ ****************************************************************************/
136
+
137
+ Markdown.Extra = function() {
138
+ // For converting internal markdown (in tables for instance).
139
+ // This is necessary since these methods are meant to be called as
140
+ // preConversion hooks, and the Markdown converter passed to init()
141
+ // won't convert any markdown contained in the html tags we return.
142
+ this.converter = null;
143
+
144
+ // Stores html blocks we generate in hooks so that
145
+ // they're not destroyed if the user is using a sanitizing converter
146
+ this.hashBlocks = [];
147
+
148
+ // Stores footnotes
149
+ this.footnotes = {};
150
+ this.usedFootnotes = [];
151
+
152
+ // Special attribute blocks for fenced code blocks and headers enabled.
153
+ this.attributeBlocks = false;
154
+
155
+ // Fenced code block options
156
+ this.googleCodePrettify = false;
157
+ this.highlightJs = false;
158
+
159
+ // Table options
160
+ this.tableClass = '';
161
+
162
+ this.tabWidth = 4;
163
+ };
164
+
165
+ Markdown.Extra.init = function(converter, options) {
166
+ // Each call to init creates a new instance of Markdown.Extra so it's
167
+ // safe to have multiple converters, with different options, on a single page
168
+ var extra = new Markdown.Extra();
169
+ var postNormalizationTransformations = [];
170
+ var preBlockGamutTransformations = [];
171
+ var postConversionTransformations = ["unHashExtraBlocks"];
172
+
173
+ options = options || {};
174
+ options.extensions = options.extensions || ["all"];
175
+ if (contains(options.extensions, "all")) {
176
+ options.extensions = ["tables", "fenced_code_gfm", "def_list", "attr_list", "footnotes"];
177
+ }
178
+ preBlockGamutTransformations.push("wrapHeaders");
179
+ if (contains(options.extensions, "attr_list")) {
180
+ postNormalizationTransformations.push("hashFcbAttributeBlocks");
181
+ preBlockGamutTransformations.push("hashHeaderAttributeBlocks");
182
+ postConversionTransformations.push("applyAttributeBlocks");
183
+ extra.attributeBlocks = true;
184
+ }
185
+ if (contains(options.extensions, "fenced_code_gfm")) {
186
+ postNormalizationTransformations.push("fencedCodeBlocks");
187
+ }
188
+ if (contains(options.extensions, "tables")) {
189
+ preBlockGamutTransformations.push("tables");
190
+ }
191
+ if (contains(options.extensions, "def_list")) {
192
+ preBlockGamutTransformations.push("definitionLists");
193
+ }
194
+ if (contains(options.extensions, "footnotes")) {
195
+ postNormalizationTransformations.push("stripFootnoteDefinitions");
196
+ preBlockGamutTransformations.push("doFootnotes");
197
+ postConversionTransformations.push("printFootnotes");
198
+ }
199
+
200
+ converter.hooks.chain("postNormalization", function(text) {
201
+ return extra.doTransform(postNormalizationTransformations, text) + '\n';
202
+ });
203
+
204
+ converter.hooks.chain("preBlockGamut", function(text, blockGamutHookCallback) {
205
+ // Keep a reference to the block gamut callback to run recursively
206
+ extra.blockGamutHookCallback = blockGamutHookCallback;
207
+ text = processEscapes(text);
208
+ return extra.doTransform(preBlockGamutTransformations, text) + '\n';
209
+ });
210
+
211
+ // Keep a reference to the hook chain running before doPostConversion to apply on hashed extra blocks
212
+ extra.previousPostConversion = converter.hooks.postConversion;
213
+ converter.hooks.chain("postConversion", function(text) {
214
+ text = extra.doTransform(postConversionTransformations, text);
215
+ // Clear state vars that may use unnecessary memory
216
+ extra.hashBlocks = [];
217
+ extra.footnotes = {};
218
+ extra.usedFootnotes = [];
219
+ return text;
220
+ });
221
+
222
+ if ("highlighter" in options) {
223
+ extra.googleCodePrettify = options.highlighter === 'prettify';
224
+ extra.highlightJs = options.highlighter === 'highlight';
225
+ }
226
+
227
+ if ("table_class" in options) {
228
+ extra.tableClass = options.table_class;
229
+ }
230
+
231
+ extra.converter = converter;
232
+
233
+ // Caller usually won't need this, but it's handy for testing.
234
+ return extra;
235
+ };
236
+
237
+ // Do transformations
238
+ Markdown.Extra.prototype.doTransform = function(transformations, text) {
239
+ for(var i = 0; i < transformations.length; i++)
240
+ text = this[transformations[i]](text);
241
+ return text;
242
+ };
243
+
244
+ // Return a placeholder containing a key, which is the block's index in the
245
+ // hashBlocks array. We wrap our output in a <p> tag here so Pagedown won't.
246
+ Markdown.Extra.prototype.hashExtraBlock = function(block) {
247
+ return '\n<p>~X' + (this.hashBlocks.push(block) - 1) + 'X</p>\n';
248
+ };
249
+ Markdown.Extra.prototype.hashExtraInline = function(block) {
250
+ return '~X' + (this.hashBlocks.push(block) - 1) + 'X';
251
+ };
252
+
253
+ // Replace placeholder blocks in `text` with their corresponding
254
+ // html blocks in the hashBlocks array.
255
+ Markdown.Extra.prototype.unHashExtraBlocks = function(text) {
256
+ var self = this;
257
+ function recursiveUnHash() {
258
+ var hasHash = false;
259
+ text = text.replace(/(?:<p>)?~X(\d+)X(?:<\/p>)?/g, function(wholeMatch, m1) {
260
+ hasHash = true;
261
+ var key = parseInt(m1, 10);
262
+ return self.hashBlocks[key];
263
+ });
264
+ if(hasHash === true) {
265
+ recursiveUnHash();
266
+ }
267
+ }
268
+ recursiveUnHash();
269
+ return text;
270
+ };
271
+
272
+ // Wrap headers to make sure they won't be in def lists
273
+ Markdown.Extra.prototype.wrapHeaders = function(text) {
274
+ function wrap(text) {
275
+ return '\n' + text + '\n';
276
+ }
277
+ text = text.replace(/^.+[ \t]*\n=+[ \t]*\n+/gm, wrap);
278
+ text = text.replace(/^.+[ \t]*\n-+[ \t]*\n+/gm, wrap);
279
+ text = text.replace(/^\#{1,6}[ \t]*.+?[ \t]*\#*\n+/gm, wrap);
280
+ return text;
281
+ };
282
+
283
+
284
+ /******************************************************************
285
+ * Attribute Blocks *
286
+ *****************************************************************/
287
+
288
+ // Extract headers attribute blocks, move them above the element they will be
289
+ // applied to, and hash them for later.
290
+ Markdown.Extra.prototype.hashHeaderAttributeBlocks = function(text) {
291
+ // TODO: use sentinels. Should we just add/remove them in doConversion?
292
+ // TODO: better matches for id / class attributes
293
+ var attrBlock = "\\{\\s*[.|#][^}]+\\}";
294
+ var hdrAttributesA = new RegExp("^(#{1,6}.*#{0,6})\\s+(" + attrBlock + ")[ \\t]*(\\n|0x03)", "gm");
295
+ var hdrAttributesB = new RegExp("^(.*)\\s+(" + attrBlock + ")[ \\t]*\\n" +
296
+ "(?=[\\-|=]+\\s*(\\n|0x03))", "gm"); // underline lookahead
297
+
298
+ var self = this;
299
+ function attributeCallback(wholeMatch, pre, attr) {
300
+ return '<p>~XX' + (self.hashBlocks.push(attr) - 1) + 'XX</p>\n' + pre + "\n";
301
+ }
302
+
303
+ text = text.replace(hdrAttributesA, attributeCallback); // ## headers
304
+ text = text.replace(hdrAttributesB, attributeCallback); // underline headers
305
+ return text;
306
+ };
307
+
308
+ // Extract FCB attribute blocks, move them above the element they will be
309
+ // applied to, and hash them for later.
310
+ Markdown.Extra.prototype.hashFcbAttributeBlocks = function(text) {
311
+ // TODO: use sentinels. Should we just add/remove them in doConversion?
312
+ // TODO: better matches for id / class attributes
313
+ var attrBlock = "\\{\\s*[.|#][^}]+\\}";
314
+ var fcbAttributes = new RegExp("^(```[^{\\n]*)\\s+(" + attrBlock + ")[ \\t]*\\n" +
315
+ "(?=([\\s\\S]*?)\\n```\\s*(\\n|0x03))", "gm");
316
+
317
+ var self = this;
318
+ function attributeCallback(wholeMatch, pre, attr) {
319
+ return '<p>~XX' + (self.hashBlocks.push(attr) - 1) + 'XX</p>\n' + pre + "\n";
320
+ }
321
+
322
+ return text.replace(fcbAttributes, attributeCallback);
323
+ };
324
+
325
+ Markdown.Extra.prototype.applyAttributeBlocks = function(text) {
326
+ var self = this;
327
+ var blockRe = new RegExp('<p>~XX(\\d+)XX</p>[\\s]*' +
328
+ '(?:<(h[1-6]|pre)(?: +class="(\\S+)")?(>[\\s\\S]*?</\\2>))', "gm");
329
+ text = text.replace(blockRe, function(wholeMatch, k, tag, cls, rest) {
330
+ if (!tag) // no following header or fenced code block.
331
+ return '';
332
+
333
+ // get attributes list from hash
334
+ var key = parseInt(k, 10);
335
+ var attributes = self.hashBlocks[key];
336
+
337
+ // get id
338
+ var id = attributes.match(/#[^\s{}]+/g) || [];
339
+ var idStr = id[0] ? ' id="' + id[0].substr(1, id[0].length - 1) + '"' : '';
340
+
341
+ // get classes and merge with existing classes
342
+ var classes = attributes.match(/\.[^\s{}]+/g) || [];
343
+ for (var i = 0; i < classes.length; i++) // Remove leading dot
344
+ classes[i] = classes[i].substr(1, classes[i].length - 1);
345
+
346
+ var classStr = '';
347
+ if (cls)
348
+ classes = union(classes, [cls]);
349
+
350
+ if (classes.length > 0)
351
+ classStr = ' class="' + classes.join(' ') + '"';
352
+
353
+ return "<" + tag + idStr + classStr + rest;
354
+ });
355
+
356
+ return text;
357
+ };
358
+
359
+ /******************************************************************
360
+ * Tables *
361
+ *****************************************************************/
362
+
363
+ // Find and convert Markdown Extra tables into html.
364
+ Markdown.Extra.prototype.tables = function(text) {
365
+ var self = this;
366
+
367
+ var leadingPipe = new RegExp(
368
+ ['^' ,
369
+ '[ ]{0,3}' , // Allowed whitespace
370
+ '[|]' , // Initial pipe
371
+ '(.+)\\n' , // $1: Header Row
372
+
373
+ '[ ]{0,3}' , // Allowed whitespace
374
+ '[|]([ ]*[-:]+[-| :]*)\\n' , // $2: Separator
375
+
376
+ '(' , // $3: Table Body
377
+ '(?:[ ]*[|].*\\n?)*' , // Table rows
378
+ ')',
379
+ '(?:\\n|$)' // Stop at final newline
380
+ ].join(''),
381
+ 'gm'
382
+ );
383
+
384
+ var noLeadingPipe = new RegExp(
385
+ ['^' ,
386
+ '[ ]{0,3}' , // Allowed whitespace
387
+ '(\\S.*[|].*)\\n' , // $1: Header Row
388
+
389
+ '[ ]{0,3}' , // Allowed whitespace
390
+ '([-:]+[ ]*[|][-| :]*)\\n' , // $2: Separator
391
+
392
+ '(' , // $3: Table Body
393
+ '(?:.*[|].*\\n?)*' , // Table rows
394
+ ')' ,
395
+ '(?:\\n|$)' // Stop at final newline
396
+ ].join(''),
397
+ 'gm'
398
+ );
399
+
400
+ text = text.replace(leadingPipe, doTable);
401
+ text = text.replace(noLeadingPipe, doTable);
402
+
403
+ // $1 = header, $2 = separator, $3 = body
404
+ function doTable(match, header, separator, body, offset, string) {
405
+ // remove any leading pipes and whitespace
406
+ header = header.replace(/^ *[|]/m, '');
407
+ separator = separator.replace(/^ *[|]/m, '');
408
+ body = body.replace(/^ *[|]/gm, '');
409
+
410
+ // remove trailing pipes and whitespace
411
+ header = header.replace(/[|] *$/m, '');
412
+ separator = separator.replace(/[|] *$/m, '');
413
+ body = body.replace(/[|] *$/gm, '');
414
+
415
+ // determine column alignments
416
+ alignspecs = separator.split(/ *[|] */);
417
+ align = [];
418
+ for (var i = 0; i < alignspecs.length; i++) {
419
+ var spec = alignspecs[i];
420
+ if (spec.match(/^ *-+: *$/m))
421
+ align[i] = ' style="text-align:right;"';
422
+ else if (spec.match(/^ *:-+: *$/m))
423
+ align[i] = ' style="text-align:center;"';
424
+ else if (spec.match(/^ *:-+ *$/m))
425
+ align[i] = ' style="text-align:left;"';
426
+ else align[i] = '';
427
+ }
428
+
429
+ // TODO: parse spans in header and rows before splitting, so that pipes
430
+ // inside of tags are not interpreted as separators
431
+ var headers = header.split(/ *[|] */);
432
+ var colCount = headers.length;
433
+
434
+ // build html
435
+ var cls = self.tableClass ? ' class="' + self.tableClass + '"' : '';
436
+ var html = ['<table', cls, '>\n', '<thead>\n', '<tr>\n'].join('');
437
+
438
+ // build column headers.
439
+ for (i = 0; i < colCount; i++) {
440
+ var headerHtml = convertSpans(trim(headers[i]), self);
441
+ html += [" <th", align[i], ">", headerHtml, "</th>\n"].join('');
442
+ }
443
+ html += "</tr>\n</thead>\n";
444
+
445
+ // build rows
446
+ var rows = body.split('\n');
447
+ for (i = 0; i < rows.length; i++) {
448
+ if (rows[i].match(/^\s*$/)) // can apply to final row
449
+ continue;
450
+
451
+ // ensure number of rowCells matches colCount
452
+ var rowCells = rows[i].split(/ *[|] */);
453
+ var lenDiff = colCount - rowCells.length;
454
+ for (var j = 0; j < lenDiff; j++)
455
+ rowCells.push('');
456
+
457
+ html += "<tr>\n";
458
+ for (j = 0; j < colCount; j++) {
459
+ var colHtml = convertSpans(trim(rowCells[j]), self);
460
+ html += [" <td", align[j], ">", colHtml, "</td>\n"].join('');
461
+ }
462
+ html += "</tr>\n";
463
+ }
464
+
465
+ html += "</table>\n";
466
+
467
+ // replace html with placeholder until postConversion step
468
+ return self.hashExtraBlock(html);
469
+ }
470
+
471
+ return text;
472
+ };
473
+
474
+
475
+ /******************************************************************
476
+ * Footnotes *
477
+ *****************************************************************/
478
+
479
+ // Strip footnote, store in hashes.
480
+ Markdown.Extra.prototype.stripFootnoteDefinitions = function(text) {
481
+ var self = this;
482
+
483
+ text = text.replace(
484
+ /\n[ ]{0,3}\[\^(.+?)\]\:[ \t]*\n?([\s\S]*?)\n{1,2}((?=\n[ ]{0,3}\S)|$)/g,
485
+ function(wholeMatch, m1, m2) {
486
+ m1 = slugify(m1);
487
+ m2 += "\n";
488
+ m2 = m2.replace(/^[ ]{0,3}/g, "");
489
+ self.footnotes[m1] = m2;
490
+ return "\n";
491
+ });
492
+
493
+ return text;
494
+ };
495
+
496
+
497
+ // Find and convert footnotes references.
498
+ Markdown.Extra.prototype.doFootnotes = function(text) {
499
+ var self = this;
500
+ if(self.isConvertingFootnote === true) {
501
+ return text;
502
+ }
503
+
504
+ var footnoteCounter = 0;
505
+ text = text.replace(/\[\^(.+?)\]/g, function(wholeMatch, m1) {
506
+ var id = slugify(m1);
507
+ var footnote = self.footnotes[id];
508
+ if (footnote === undefined) {
509
+ return wholeMatch;
510
+ }
511
+ footnoteCounter++;
512
+ self.usedFootnotes.push(id);
513
+ var html = '<a href="#fn:' + id + '" id="fnref:' + id
514
+ + '" title="See footnote" class="footnote">' + footnoteCounter
515
+ + '</a>';
516
+ return self.hashExtraInline(html);
517
+ });
518
+
519
+ return text;
520
+ };
521
+
522
+ // Print footnotes at the end of the document
523
+ Markdown.Extra.prototype.printFootnotes = function(text) {
524
+ var self = this;
525
+
526
+ if (self.usedFootnotes.length === 0) {
527
+ return text;
528
+ }
529
+
530
+ text += '\n\n<div class="footnotes">\n<hr>\n<ol>\n\n';
531
+ for(var i=0; i<self.usedFootnotes.length; i++) {
532
+ var id = self.usedFootnotes[i];
533
+ var footnote = self.footnotes[id];
534
+ self.isConvertingFootnote = true;
535
+ var formattedfootnote = convertSpans(footnote, self);
536
+ delete self.isConvertingFootnote;
537
+ text += '<li id="fn:'
538
+ + id
539
+ + '">'
540
+ + formattedfootnote
541
+ + ' <a href="#fnref:'
542
+ + id
543
+ + '" title="Return to article" class="reversefootnote">&#8617;</a></li>\n\n';
544
+ }
545
+ text += '</ol>\n</div>';
546
+ return text;
547
+ };
548
+
549
+
550
+ /******************************************************************
551
+ * Fenced Code Blocks (gfm) *
552
+ ******************************************************************/
553
+
554
+ // Find and convert gfm-inspired fenced code blocks into html.
555
+ Markdown.Extra.prototype.fencedCodeBlocks = function(text) {
556
+ function encodeCode(code) {
557
+ code = code.replace(/&/g, "&amp;");
558
+ code = code.replace(/</g, "&lt;");
559
+ code = code.replace(/>/g, "&gt;");
560
+ // These were escaped by PageDown before postNormalization
561
+ code = code.replace(/~D/g, "$$");
562
+ code = code.replace(/~T/g, "~");
563
+ return code;
564
+ }
565
+
566
+ var self = this;
567
+ text = text.replace(/(?:^|\n)```(.*)\n([\s\S]*?)\n```/g, function(match, m1, m2) {
568
+ var language = m1, codeblock = m2;
569
+
570
+ // adhere to specified options
571
+ var preclass = self.googleCodePrettify ? ' class="prettyprint"' : '';
572
+ var codeclass = '';
573
+ if (language) {
574
+ if (self.googleCodePrettify || self.highlightJs) {
575
+ // use html5 language- class names. supported by both prettify and highlight.js
576
+ codeclass = ' class="language-' + language + '"';
577
+ } else {
578
+ codeclass = ' class="' + language + '"';
579
+ }
580
+ }
581
+
582
+ var html = ['<pre', preclass, '><code', codeclass, '>',
583
+ encodeCode(codeblock), '</code></pre>'].join('');
584
+
585
+ // replace codeblock with placeholder until postConversion step
586
+ return self.hashExtraBlock(html);
587
+ });
588
+
589
+ return text;
590
+ };
591
+
592
+
593
+ /******************************************************************
594
+ * Definition Lists *
595
+ ******************************************************************/
596
+
597
+ // Find and convert markdown extra definition lists into html.
598
+ Markdown.Extra.prototype.definitionLists = function(text) {
599
+ var wholeList = new RegExp(
600
+ ['(\\x02\\n?|\\n\\n)' ,
601
+ '(?:' ,
602
+ '(' , // $1 = whole list
603
+ '(' , // $2
604
+ '[ ]{0,3}' ,
605
+ '((?:[ \\t]*\\S.*\\n)+)', // $3 = defined term
606
+ '\\n?' ,
607
+ '[ ]{0,3}:[ ]+' , // colon starting definition
608
+ ')' ,
609
+ '([\\s\\S]+?)' ,
610
+ '(' , // $4
611
+ '(?=\\0x03)' , // \z
612
+ '|' ,
613
+ '(?=' ,
614
+ '\\n{2,}' ,
615
+ '(?=\\S)' ,
616
+ '(?!' , // Negative lookahead for another term
617
+ '[ ]{0,3}' ,
618
+ '(?:\\S.*\\n)+?' , // defined term
619
+ '\\n?' ,
620
+ '[ ]{0,3}:[ ]+' , // colon starting definition
621
+ ')' ,
622
+ '(?!' , // Negative lookahead for another definition
623
+ '[ ]{0,3}:[ ]+' , // colon starting definition
624
+ ')' ,
625
+ ')' ,
626
+ ')' ,
627
+ ')' ,
628
+ ')'
629
+ ].join(''),
630
+ 'gm'
631
+ );
632
+
633
+ var self = this;
634
+ text = addAnchors(text);
635
+
636
+ text = text.replace(wholeList, function(match, pre, list) {
637
+ var result = trim(self.processDefListItems(list));
638
+ result = "<dl>\n" + result + "\n</dl>";
639
+ return pre + self.hashExtraBlock(result) + "\n\n";
640
+ });
641
+
642
+ return removeAnchors(text);
643
+ };
644
+
645
+ // Process the contents of a single definition list, splitting it
646
+ // into individual term and definition list items.
647
+ Markdown.Extra.prototype.processDefListItems = function(listStr) {
648
+ var self = this;
649
+
650
+ var dt = new RegExp(
651
+ ['(\\x02\\n?|\\n\\n+)' , // leading line
652
+ '(' , // definition terms = $1
653
+ '[ ]{0,3}' , // leading whitespace
654
+ '(?![:][ ]|[ ])' , // negative lookahead for a definition
655
+ // mark (colon) or more whitespace
656
+ '(?:\\S.*\\n)+?' , // actual term (not whitespace)
657
+ ')' ,
658
+ '(?=\\n?[ ]{0,3}:[ ])' // lookahead for following line feed
659
+ ].join(''), // with a definition mark
660
+ 'gm'
661
+ );
662
+
663
+ var dd = new RegExp(
664
+ ['\\n(\\n+)?' , // leading line = $1
665
+ '(' , // marker space = $2
666
+ '[ ]{0,3}' , // whitespace before colon
667
+ '[:][ ]+' , // definition mark (colon)
668
+ ')' ,
669
+ '([\\s\\S]+?)' , // definition text = $3
670
+ '(?=\\n*' , // stop at next definition mark,
671
+ '(?:' , // next term or end of text
672
+ '\\n[ ]{0,3}[:][ ]|' ,
673
+ '<dt>|\\x03' , // \z
674
+ ')' ,
675
+ ')'
676
+ ].join(''),
677
+ 'gm'
678
+ );
679
+
680
+ listStr = addAnchors(listStr);
681
+ // trim trailing blank lines:
682
+ listStr = listStr.replace(/\n{2,}(?=\\x03)/, "\n");
683
+
684
+ // Process definition terms.
685
+ listStr = listStr.replace(dt, function(match, pre, termsStr) {
686
+ var terms = trim(termsStr).split("\n");
687
+ var text = '';
688
+ for (var i = 0; i < terms.length; i++) {
689
+ var term = terms[i];
690
+ // process spans inside dt
691
+ term = convertSpans(trim(term), self);
692
+ text += "\n<dt>" + term + "</dt>";
693
+ }
694
+ return text + "\n";
695
+ });
696
+
697
+ // Process actual definitions.
698
+ listStr = listStr.replace(dd, function(match, leadingLine, markerSpace, def) {
699
+ if (leadingLine || def.match(/\n{2,}/)) {
700
+ // replace marker with the appropriate whitespace indentation
701
+ def = Array(markerSpace.length + 1).join(' ') + def;
702
+ // process markdown inside definition
703
+ // TODO?: currently doesn't apply extensions
704
+ def = outdent(def) + "\n\n";
705
+ def = "\n" + convertAll(def, self) + "\n";
706
+ } else {
707
+ // convert span-level markdown inside definition
708
+ def = rtrim(def);
709
+ def = convertSpans(outdent(def), self);
710
+ }
711
+
712
+ return "\n<dd>" + def + "</dd>\n";
713
+ });
714
+
715
+ return removeAnchors(listStr);
716
+ };
717
+
718
+ })();
719
+