bootstrap_pagedown 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+