pagedown-bootstrap-rails 1.0.0 → 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.
data/Readme.md
CHANGED
@@ -36,6 +36,7 @@ You will need to instantiate PageDown. I recommend a `lib/assets/javascripts/pag
|
|
36
36
|
$('textarea.wmd-input').each (i, input) ->
|
37
37
|
attr = $(input).attr('id').split('wmd-input')[1]
|
38
38
|
converter = new Markdown.Converter()
|
39
|
+
Markdown.Extra.init(converter)
|
39
40
|
editor = new Markdown.Editor(converter, attr)
|
40
41
|
editor.run()
|
41
42
|
|
@@ -41,7 +41,7 @@ else
|
|
41
41
|
//
|
42
42
|
// var text = "Markdown *rocks*.";
|
43
43
|
//
|
44
|
-
// var converter = new Markdown.Converter(
|
44
|
+
// var converter = new Markdown.Converter();
|
45
45
|
// var html = converter.makeHtml(text);
|
46
46
|
//
|
47
47
|
// alert(html);
|
@@ -67,7 +67,11 @@ else
|
|
67
67
|
if (original === identity)
|
68
68
|
this[hookname] = func;
|
69
69
|
else
|
70
|
-
this[hookname] = function (
|
70
|
+
this[hookname] = function (text) {
|
71
|
+
var args = Array.prototype.slice.call(arguments, 0);
|
72
|
+
args[0] = original.apply(null, args);
|
73
|
+
return func.apply(null, args);
|
74
|
+
};
|
71
75
|
},
|
72
76
|
set: function (hookname, func) {
|
73
77
|
if (!this[hookname])
|
@@ -101,19 +105,35 @@ else
|
|
101
105
|
}
|
102
106
|
};
|
103
107
|
|
104
|
-
Markdown.Converter = function (
|
108
|
+
Markdown.Converter = function () {
|
105
109
|
var pluginHooks = this.hooks = new HookCollection();
|
106
|
-
|
107
|
-
|
108
|
-
pluginHooks.addNoop("
|
110
|
+
|
111
|
+
// given a URL that was encountered by itself (without markup), should return the link text that's to be given to this link
|
112
|
+
pluginHooks.addNoop("plainLinkText");
|
113
|
+
|
114
|
+
// called with the orignal text as given to makeHtml. The result of this plugin hook is the actual markdown source that will be cooked
|
115
|
+
pluginHooks.addNoop("preConversion");
|
116
|
+
|
117
|
+
// called with the text once all normalizations have been completed (tabs to spaces, line endings, etc.), but before any conversions have
|
118
|
+
pluginHooks.addNoop("postNormalization");
|
119
|
+
|
120
|
+
// Called with the text before / after creating block elements like code blocks and lists. Note that this is called recursively
|
121
|
+
// with inner content, e.g. it's called with the full text, and then only with the content of a blockquote. The inner
|
122
|
+
// call will receive outdented text.
|
123
|
+
pluginHooks.addNoop("preBlockGamut");
|
124
|
+
pluginHooks.addNoop("postBlockGamut");
|
125
|
+
|
126
|
+
// called with the text of a single block element before / after the span-level conversions (bold, code spans, etc.) have been made
|
127
|
+
pluginHooks.addNoop("preSpanGamut");
|
128
|
+
pluginHooks.addNoop("postSpanGamut");
|
129
|
+
|
130
|
+
// called with the final cooked HTML code. The result of this plugin hook is the actual output of makeHtml
|
131
|
+
pluginHooks.addNoop("postConversion");
|
109
132
|
|
110
133
|
//
|
111
134
|
// Private state of the converter instance:
|
112
135
|
//
|
113
136
|
|
114
|
-
// Aloow for automatic linking
|
115
|
-
var g_autolink = (autolink === true);
|
116
|
-
|
117
137
|
// Global hashes, used by various utility routines
|
118
138
|
var g_urls;
|
119
139
|
var g_titles;
|
@@ -172,6 +192,8 @@ else
|
|
172
192
|
// contorted like /[ \t]*\n+/ .
|
173
193
|
text = text.replace(/^[ \t]+$/mg, "");
|
174
194
|
|
195
|
+
text = pluginHooks.postNormalization(text);
|
196
|
+
|
175
197
|
// Turn block-level HTML blocks into hash entries
|
176
198
|
text = _HashHTMLBlocks(text);
|
177
199
|
|
@@ -382,11 +404,16 @@ else
|
|
382
404
|
return blockText;
|
383
405
|
}
|
384
406
|
|
407
|
+
var blockGamutHookCallback = function (t) { return _RunBlockGamut(t); }
|
408
|
+
|
385
409
|
function _RunBlockGamut(text, doNotUnhash) {
|
386
410
|
//
|
387
411
|
// These are all the transformations that form block-level
|
388
412
|
// tags like paragraphs, headers, and list items.
|
389
413
|
//
|
414
|
+
|
415
|
+
text = pluginHooks.preBlockGamut(text, blockGamutHookCallback);
|
416
|
+
|
390
417
|
text = _DoHeaders(text);
|
391
418
|
|
392
419
|
// Do Horizontal Rules:
|
@@ -399,6 +426,8 @@ else
|
|
399
426
|
text = _DoCodeBlocks(text);
|
400
427
|
text = _DoBlockQuotes(text);
|
401
428
|
|
429
|
+
text = pluginHooks.postBlockGamut(text, blockGamutHookCallback);
|
430
|
+
|
402
431
|
// We already ran _HashHTMLBlocks() before, in Markdown(), but that
|
403
432
|
// was to escape raw HTML in the original Markdown source. This time,
|
404
433
|
// we're escaping the markup we've just created, so that we don't wrap
|
@@ -415,6 +444,8 @@ else
|
|
415
444
|
// tags like paragraphs, headers, and list items.
|
416
445
|
//
|
417
446
|
|
447
|
+
text = pluginHooks.preSpanGamut(text);
|
448
|
+
|
418
449
|
text = _DoCodeSpans(text);
|
419
450
|
text = _EscapeSpecialCharsWithinTagAttributes(text);
|
420
451
|
text = _EncodeBackslashEscapes(text);
|
@@ -427,9 +458,7 @@ else
|
|
427
458
|
// Make links out of things like `<http://example.com/>`
|
428
459
|
// Must come after _DoAnchors(), because you can use < and >
|
429
460
|
// delimiters in inline links like [this](<url>).
|
430
|
-
|
431
|
-
text = _DoAutoLinks(text);
|
432
|
-
}
|
461
|
+
text = _DoAutoLinks(text);
|
433
462
|
|
434
463
|
text = text.replace(/~P/g, "://"); // put in place to prevent autolinking; reset now
|
435
464
|
|
@@ -439,6 +468,8 @@ else
|
|
439
468
|
// Do hard breaks:
|
440
469
|
text = text.replace(/ +\n/g, " <br>\n");
|
441
470
|
|
471
|
+
text = pluginHooks.postSpanGamut(text);
|
472
|
+
|
442
473
|
return text;
|
443
474
|
}
|
444
475
|
|
@@ -520,7 +551,7 @@ else
|
|
520
551
|
(?:
|
521
552
|
\([^)]*\) // allow one level of (correctly nested) parens (think MSDN)
|
522
553
|
|
|
523
|
-
[^()]
|
554
|
+
[^()\s]
|
524
555
|
)*?
|
525
556
|
)>?
|
526
557
|
[ \t]*
|
@@ -535,7 +566,7 @@ else
|
|
535
566
|
/g, writeAnchorTag);
|
536
567
|
*/
|
537
568
|
|
538
|
-
text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?((?:\([^)]*\)|[^()])*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeAnchorTag);
|
569
|
+
text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?((?:\([^)]*\)|[^()\s])*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeAnchorTag);
|
539
570
|
|
540
571
|
//
|
541
572
|
// Last, handle reference-style shortcuts: [link text]
|
@@ -759,7 +790,7 @@ else
|
|
759
790
|
return text;
|
760
791
|
}
|
761
792
|
|
762
|
-
function _DoLists(text) {
|
793
|
+
function _DoLists(text, isInsideParagraphlessListItem) {
|
763
794
|
//
|
764
795
|
// Form HTML ordered (numbered) and unordered (bulleted) lists.
|
765
796
|
//
|
@@ -799,7 +830,7 @@ else
|
|
799
830
|
var list = m1;
|
800
831
|
var list_type = (m2.search(/[*+-]/g) > -1) ? "ul" : "ol";
|
801
832
|
|
802
|
-
var result = _ProcessListItems(list, list_type);
|
833
|
+
var result = _ProcessListItems(list, list_type, isInsideParagraphlessListItem);
|
803
834
|
|
804
835
|
// Trim any trailing whitespace, to put the closing `</$list_type>`
|
805
836
|
// up on the preceding line, to get it past the current stupid
|
@@ -830,7 +861,7 @@ else
|
|
830
861
|
|
831
862
|
var _listItemMarkers = { ol: "\\d+[.]", ul: "[*+-]" };
|
832
863
|
|
833
|
-
function _ProcessListItems(list_str, list_type) {
|
864
|
+
function _ProcessListItems(list_str, list_type, isInsideParagraphlessListItem) {
|
834
865
|
//
|
835
866
|
// Process the contents of a single ordered or unordered list, splitting it
|
836
867
|
// into individual list items.
|
@@ -906,9 +937,10 @@ else
|
|
906
937
|
}
|
907
938
|
else {
|
908
939
|
// Recursion for sub-lists:
|
909
|
-
item = _DoLists(_Outdent(item));
|
940
|
+
item = _DoLists(_Outdent(item), /* isInsideParagraphlessListItem= */ true);
|
910
941
|
item = item.replace(/\n$/, ""); // chomp(item)
|
911
|
-
item
|
942
|
+
if (!isInsideParagraphlessListItem) // only the outer-most item should run this, otherwise it's run multiple times for the inner ones
|
943
|
+
item = _RunSpanGamut(item);
|
912
944
|
}
|
913
945
|
last_item_had_a_double_newline = ends_with_double_newline;
|
914
946
|
return "<li>" + item + "</li>\n";
|
@@ -943,7 +975,7 @@ else
|
|
943
975
|
// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
|
944
976
|
text += "~0";
|
945
977
|
|
946
|
-
text = text.replace(/(?:\n\n
|
978
|
+
text = text.replace(/(?:\n\n|^\n?)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
|
947
979
|
function (wholeMatch, m1, m2) {
|
948
980
|
var codeblock = m1;
|
949
981
|
var nextChar = m2;
|
@@ -953,7 +985,7 @@ else
|
|
953
985
|
codeblock = codeblock.replace(/^\n+/g, ""); // trim leading newlines
|
954
986
|
codeblock = codeblock.replace(/\n+$/g, ""); // trim trailing whitespace
|
955
987
|
|
956
|
-
codeblock =
|
988
|
+
codeblock = "<pre><code>" + codeblock + "\n</code></pre>";
|
957
989
|
|
958
990
|
return "\n\n" + codeblock + "\n\n" + nextChar;
|
959
991
|
}
|
@@ -1174,7 +1206,7 @@ else
|
|
1174
1206
|
text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, "&");
|
1175
1207
|
|
1176
1208
|
// Encode naked <'s
|
1177
|
-
text = text.replace(/<(?![a-z
|
1209
|
+
text = text.replace(/<(?![a-z\/?!]|~D)/gi, "<");
|
1178
1210
|
|
1179
1211
|
return text;
|
1180
1212
|
}
|
@@ -1200,14 +1232,57 @@ else
|
|
1200
1232
|
return text;
|
1201
1233
|
}
|
1202
1234
|
|
1235
|
+
var charInsideUrl = "[-A-Z0-9+&@#/%?=~_|[\\]()!:,.;]",
|
1236
|
+
charEndingUrl = "[-A-Z0-9+&@#/%=~_|[\\])]",
|
1237
|
+
autoLinkRegex = new RegExp("(=\"|<)?\\b(https?|ftp)(://" + charInsideUrl + "*" + charEndingUrl + ")(?=$|\\W)", "gi"),
|
1238
|
+
endCharRegex = new RegExp(charEndingUrl, "i");
|
1239
|
+
|
1240
|
+
function handleTrailingParens(wholeMatch, lookbehind, protocol, link) {
|
1241
|
+
if (lookbehind)
|
1242
|
+
return wholeMatch;
|
1243
|
+
if (link.charAt(link.length - 1) !== ")")
|
1244
|
+
return "<" + protocol + link + ">";
|
1245
|
+
var parens = link.match(/[()]/g);
|
1246
|
+
var level = 0;
|
1247
|
+
for (var i = 0; i < parens.length; i++) {
|
1248
|
+
if (parens[i] === "(") {
|
1249
|
+
if (level <= 0)
|
1250
|
+
level = 1;
|
1251
|
+
else
|
1252
|
+
level++;
|
1253
|
+
}
|
1254
|
+
else {
|
1255
|
+
level--;
|
1256
|
+
}
|
1257
|
+
}
|
1258
|
+
var tail = "";
|
1259
|
+
if (level < 0) {
|
1260
|
+
var re = new RegExp("\\){1," + (-level) + "}$");
|
1261
|
+
link = link.replace(re, function (trailingParens) {
|
1262
|
+
tail = trailingParens;
|
1263
|
+
return "";
|
1264
|
+
});
|
1265
|
+
}
|
1266
|
+
if (tail) {
|
1267
|
+
var lastChar = link.charAt(link.length - 1);
|
1268
|
+
if (!endCharRegex.test(lastChar)) {
|
1269
|
+
tail = lastChar + tail;
|
1270
|
+
link = link.substr(0, link.length - 1);
|
1271
|
+
}
|
1272
|
+
}
|
1273
|
+
return "<" + protocol + link + ">" + tail;
|
1274
|
+
}
|
1275
|
+
|
1203
1276
|
function _DoAutoLinks(text) {
|
1204
1277
|
|
1205
1278
|
// note that at this point, all other URL in the text are already hyperlinked as <a href=""></a>
|
1206
1279
|
// *except* for the <http://www.foo.com> case
|
1207
1280
|
|
1208
1281
|
// automatically add < and > around unadorned raw hyperlinks
|
1209
|
-
// must be preceded by
|
1210
|
-
|
1282
|
+
// must be preceded by a non-word character (and not by =" or <) and followed by non-word/EOF character
|
1283
|
+
// simulating the lookbehind in a consuming way is okay here, since a URL can neither and with a " nor
|
1284
|
+
// with a <, so there is no risk of overlapping matches.
|
1285
|
+
text = text.replace(autoLinkRegex, handleTrailingParens);
|
1211
1286
|
|
1212
1287
|
// autolink anything like <http://example.com>
|
1213
1288
|
|
@@ -1228,21 +1303,13 @@ else
|
|
1228
1303
|
/gi, _DoAutoLinks_callback());
|
1229
1304
|
*/
|
1230
1305
|
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
if (m1.substring(0, mailto.length) != mailto){
|
1236
|
-
link = mailto + m1;
|
1237
|
-
email = m1;
|
1238
|
-
} else {
|
1239
|
-
link = m1;
|
1240
|
-
email = m1.substring(mailto.length, m1.length);
|
1306
|
+
/* disabling email autolinking, since we don't do that on the server, either
|
1307
|
+
text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,
|
1308
|
+
function(wholeMatch,m1) {
|
1309
|
+
return _EncodeEmailAddress( _UnescapeSpecialChars(m1) );
|
1241
1310
|
}
|
1242
|
-
|
1243
|
-
|
1244
|
-
text = text.replace(/<((?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+))>/gi, email_replacer);
|
1245
|
-
|
1311
|
+
);
|
1312
|
+
*/
|
1246
1313
|
return text;
|
1247
1314
|
}
|
1248
1315
|
|
@@ -1312,11 +1379,7 @@ else
|
|
1312
1379
|
return "%24";
|
1313
1380
|
if (match == ":") {
|
1314
1381
|
if (offset == len - 1 || /[0-9\/]/.test(url.charAt(offset + 1)))
|
1315
|
-
return ":"
|
1316
|
-
if (url.substring(0, 'mailto:'.length) === 'mailto:')
|
1317
|
-
return ":";
|
1318
|
-
if (url.substring(0, 'magnet:'.length) === 'magnet:')
|
1319
|
-
return ":";
|
1382
|
+
return ":"
|
1320
1383
|
}
|
1321
1384
|
return "%" + match.charCodeAt(0).toString(16);
|
1322
1385
|
});
|
@@ -0,0 +1,706 @@
|
|
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, '|').replace(/\\:/g, ':');
|
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
|
+
if (contains(options.extensions, "attr_list")) {
|
179
|
+
postNormalizationTransformations.push("hashFcbAttributeBlocks");
|
180
|
+
preBlockGamutTransformations.push("hashHeaderAttributeBlocks");
|
181
|
+
postConversionTransformations.push("applyAttributeBlocks");
|
182
|
+
extra.attributeBlocks = true;
|
183
|
+
}
|
184
|
+
if (contains(options.extensions, "tables")) {
|
185
|
+
preBlockGamutTransformations.push("tables");
|
186
|
+
}
|
187
|
+
if (contains(options.extensions, "fenced_code_gfm")) {
|
188
|
+
postNormalizationTransformations.push("fencedCodeBlocks");
|
189
|
+
}
|
190
|
+
if (contains(options.extensions, "def_list")) {
|
191
|
+
preBlockGamutTransformations.push("definitionLists");
|
192
|
+
}
|
193
|
+
if (contains(options.extensions, "footnotes")) {
|
194
|
+
postNormalizationTransformations.push("stripFootnoteDefinitions");
|
195
|
+
preBlockGamutTransformations.push("doFootnotes");
|
196
|
+
postConversionTransformations.push("printFootnotes");
|
197
|
+
}
|
198
|
+
|
199
|
+
converter.hooks.chain("postNormalization", function(text) {
|
200
|
+
return extra.doTransform(postNormalizationTransformations, text) + '\n';
|
201
|
+
});
|
202
|
+
|
203
|
+
converter.hooks.chain("preBlockGamut", function(text, blockGamutHookCallback) {
|
204
|
+
// Keep a reference to the block gamut callback to run recursively
|
205
|
+
extra.blockGamutHookCallback = blockGamutHookCallback;
|
206
|
+
text = processEscapes(text);
|
207
|
+
return extra.doTransform(preBlockGamutTransformations, text) + '\n';
|
208
|
+
});
|
209
|
+
|
210
|
+
// Keep a reference to the hook chain running before doPostConversion to apply on hashed extra blocks
|
211
|
+
extra.previousPostConversion = converter.hooks.postConversion;
|
212
|
+
converter.hooks.chain("postConversion", function(text) {
|
213
|
+
text = extra.doTransform(postConversionTransformations, text);
|
214
|
+
// Clear state vars that may use unnecessary memory
|
215
|
+
extra.hashBlocks = [];
|
216
|
+
extra.footnotes = {};
|
217
|
+
extra.usedFootnotes = [];
|
218
|
+
return text;
|
219
|
+
});
|
220
|
+
|
221
|
+
if ("highlighter" in options) {
|
222
|
+
extra.googleCodePrettify = options.highlighter === 'prettify';
|
223
|
+
extra.highlightJs = options.highlighter === 'highlight';
|
224
|
+
}
|
225
|
+
|
226
|
+
if ("table_class" in options) {
|
227
|
+
extra.tableClass = options.table_class;
|
228
|
+
}
|
229
|
+
|
230
|
+
extra.converter = converter;
|
231
|
+
|
232
|
+
// Caller usually won't need this, but it's handy for testing.
|
233
|
+
return extra;
|
234
|
+
};
|
235
|
+
|
236
|
+
// Do transformations
|
237
|
+
Markdown.Extra.prototype.doTransform = function(transformations, text) {
|
238
|
+
for(var i = 0; i < transformations.length; i++)
|
239
|
+
text = this[transformations[i]](text);
|
240
|
+
return text;
|
241
|
+
};
|
242
|
+
|
243
|
+
// Return a placeholder containing a key, which is the block's index in the
|
244
|
+
// hashBlocks array. We wrap our output in a <p> tag here so Pagedown won't.
|
245
|
+
Markdown.Extra.prototype.hashExtraBlock = function(block) {
|
246
|
+
return '\n<p>~X' + (this.hashBlocks.push(block) - 1) + 'X</p>\n';
|
247
|
+
};
|
248
|
+
Markdown.Extra.prototype.hashExtraInline = function(block) {
|
249
|
+
return '~X' + (this.hashBlocks.push(block) - 1) + 'X';
|
250
|
+
};
|
251
|
+
|
252
|
+
// Replace placeholder blocks in `text` with their corresponding
|
253
|
+
// html blocks in the hashBlocks array.
|
254
|
+
Markdown.Extra.prototype.unHashExtraBlocks = function(text) {
|
255
|
+
var self = this;
|
256
|
+
function recursiveUnHash() {
|
257
|
+
var hasHash = false;
|
258
|
+
text = text.replace(/(?:<p>)?~X(\d+)X(?:<\/p>)?/g, function(wholeMatch, m1) {
|
259
|
+
hasHash = true;
|
260
|
+
var key = parseInt(m1, 10);
|
261
|
+
return self.hashBlocks[key];
|
262
|
+
});
|
263
|
+
if(hasHash === true) {
|
264
|
+
recursiveUnHash();
|
265
|
+
}
|
266
|
+
}
|
267
|
+
recursiveUnHash();
|
268
|
+
return text;
|
269
|
+
};
|
270
|
+
|
271
|
+
|
272
|
+
/******************************************************************
|
273
|
+
* Attribute Blocks *
|
274
|
+
*****************************************************************/
|
275
|
+
|
276
|
+
// Extract headers attribute blocks, move them above the element they will be
|
277
|
+
// applied to, and hash them for later.
|
278
|
+
Markdown.Extra.prototype.hashHeaderAttributeBlocks = function(text) {
|
279
|
+
// TODO: use sentinels. Should we just add/remove them in doConversion?
|
280
|
+
// TODO: better matches for id / class attributes
|
281
|
+
var attrBlock = "\\{\\s*[.|#][^}]+\\}";
|
282
|
+
var hdrAttributesA = new RegExp("^(#{1,6}.*#{0,6})\\s+(" + attrBlock + ")[ \\t]*(\\n|0x03)", "gm");
|
283
|
+
var hdrAttributesB = new RegExp("^(.*)\\s+(" + attrBlock + ")[ \\t]*\\n" +
|
284
|
+
"(?=[\\-|=]+\\s*(\\n|0x03))", "gm"); // underline lookahead
|
285
|
+
|
286
|
+
var self = this;
|
287
|
+
function attributeCallback(wholeMatch, pre, attr) {
|
288
|
+
return '<p>~XX' + (self.hashBlocks.push(attr) - 1) + 'XX</p>\n' + pre + "\n";
|
289
|
+
}
|
290
|
+
|
291
|
+
text = text.replace(hdrAttributesA, attributeCallback); // ## headers
|
292
|
+
text = text.replace(hdrAttributesB, attributeCallback); // underline headers
|
293
|
+
return text;
|
294
|
+
};
|
295
|
+
|
296
|
+
// Extract FCB attribute blocks, move them above the element they will be
|
297
|
+
// applied to, and hash them for later.
|
298
|
+
Markdown.Extra.prototype.hashFcbAttributeBlocks = function(text) {
|
299
|
+
// TODO: use sentinels. Should we just add/remove them in doConversion?
|
300
|
+
// TODO: better matches for id / class attributes
|
301
|
+
var attrBlock = "\\{\\s*[.|#][^}]+\\}";
|
302
|
+
var fcbAttributes = new RegExp("^(```[^{\\n]*)\\s+(" + attrBlock + ")[ \\t]*\\n" +
|
303
|
+
"(?=([\\s\\S]*?)\\n```\\s*(\\n|0x03))", "gm");
|
304
|
+
|
305
|
+
var self = this;
|
306
|
+
function attributeCallback(wholeMatch, pre, attr) {
|
307
|
+
return '<p>~XX' + (self.hashBlocks.push(attr) - 1) + 'XX</p>\n' + pre + "\n";
|
308
|
+
}
|
309
|
+
|
310
|
+
return text.replace(fcbAttributes, attributeCallback);
|
311
|
+
};
|
312
|
+
|
313
|
+
Markdown.Extra.prototype.applyAttributeBlocks = function(text) {
|
314
|
+
var self = this;
|
315
|
+
var blockRe = new RegExp('<p>~XX(\\d+)XX</p>[\\s]*' +
|
316
|
+
'(?:<(h[1-6]|pre)(?: +class="(\\S+)")?(>[\\s\\S]*?</\\2>))', "gm");
|
317
|
+
text = text.replace(blockRe, function(wholeMatch, k, tag, cls, rest) {
|
318
|
+
if (!tag) // no following header or fenced code block.
|
319
|
+
return '';
|
320
|
+
|
321
|
+
// get attributes list from hash
|
322
|
+
var key = parseInt(k, 10);
|
323
|
+
var attributes = self.hashBlocks[key];
|
324
|
+
|
325
|
+
// get id
|
326
|
+
var id = attributes.match(/#[^\s{}]+/g) || [];
|
327
|
+
var idStr = id[0] ? ' id="' + id[0].substr(1, id[0].length - 1) + '"' : '';
|
328
|
+
|
329
|
+
// get classes and merge with existing classes
|
330
|
+
var classes = attributes.match(/\.[^\s{}]+/g) || [];
|
331
|
+
for (var i = 0; i < classes.length; i++) // Remove leading dot
|
332
|
+
classes[i] = classes[i].substr(1, classes[i].length - 1);
|
333
|
+
|
334
|
+
var classStr = '';
|
335
|
+
if (cls)
|
336
|
+
classes = union(classes, [cls]);
|
337
|
+
|
338
|
+
if (classes.length > 0)
|
339
|
+
classStr = ' class="' + classes.join(' ') + '"';
|
340
|
+
|
341
|
+
return "<" + tag + idStr + classStr + rest;
|
342
|
+
});
|
343
|
+
|
344
|
+
return text;
|
345
|
+
};
|
346
|
+
|
347
|
+
/******************************************************************
|
348
|
+
* Tables *
|
349
|
+
*****************************************************************/
|
350
|
+
|
351
|
+
// Find and convert Markdown Extra tables into html.
|
352
|
+
Markdown.Extra.prototype.tables = function(text) {
|
353
|
+
var self = this;
|
354
|
+
|
355
|
+
var leadingPipe = new RegExp(
|
356
|
+
['^' ,
|
357
|
+
'[ ]{0,3}' , // Allowed whitespace
|
358
|
+
'[|]' , // Initial pipe
|
359
|
+
'(.+)\\n' , // $1: Header Row
|
360
|
+
|
361
|
+
'[ ]{0,3}' , // Allowed whitespace
|
362
|
+
'[|]([ ]*[-:]+[-| :]*)\\n' , // $2: Separator
|
363
|
+
|
364
|
+
'(' , // $3: Table Body
|
365
|
+
'(?:[ ]*[|].*\\n?)*' , // Table rows
|
366
|
+
')',
|
367
|
+
'(?:\\n|$)' // Stop at final newline
|
368
|
+
].join(''),
|
369
|
+
'gm'
|
370
|
+
);
|
371
|
+
|
372
|
+
var noLeadingPipe = new RegExp(
|
373
|
+
['^' ,
|
374
|
+
'[ ]{0,3}' , // Allowed whitespace
|
375
|
+
'(\\S.*[|].*)\\n' , // $1: Header Row
|
376
|
+
|
377
|
+
'[ ]{0,3}' , // Allowed whitespace
|
378
|
+
'([-:]+[ ]*[|][-| :]*)\\n' , // $2: Separator
|
379
|
+
|
380
|
+
'(' , // $3: Table Body
|
381
|
+
'(?:.*[|].*\\n?)*' , // Table rows
|
382
|
+
')' ,
|
383
|
+
'(?:\\n|$)' // Stop at final newline
|
384
|
+
].join(''),
|
385
|
+
'gm'
|
386
|
+
);
|
387
|
+
|
388
|
+
text = text.replace(leadingPipe, doTable);
|
389
|
+
text = text.replace(noLeadingPipe, doTable);
|
390
|
+
|
391
|
+
// $1 = header, $2 = separator, $3 = body
|
392
|
+
function doTable(match, header, separator, body, offset, string) {
|
393
|
+
// remove any leading pipes and whitespace
|
394
|
+
header = header.replace(/^ *[|]/m, '');
|
395
|
+
separator = separator.replace(/^ *[|]/m, '');
|
396
|
+
body = body.replace(/^ *[|]/gm, '');
|
397
|
+
|
398
|
+
// remove trailing pipes and whitespace
|
399
|
+
header = header.replace(/[|] *$/m, '');
|
400
|
+
separator = separator.replace(/[|] *$/m, '');
|
401
|
+
body = body.replace(/[|] *$/gm, '');
|
402
|
+
|
403
|
+
// determine column alignments
|
404
|
+
alignspecs = separator.split(/ *[|] */);
|
405
|
+
align = [];
|
406
|
+
for (var i = 0; i < alignspecs.length; i++) {
|
407
|
+
var spec = alignspecs[i];
|
408
|
+
if (spec.match(/^ *-+: *$/m))
|
409
|
+
align[i] = ' style="text-align:right;"';
|
410
|
+
else if (spec.match(/^ *:-+: *$/m))
|
411
|
+
align[i] = ' style="text-align:center;"';
|
412
|
+
else if (spec.match(/^ *:-+ *$/m))
|
413
|
+
align[i] = ' style="text-align:left;"';
|
414
|
+
else align[i] = '';
|
415
|
+
}
|
416
|
+
|
417
|
+
// TODO: parse spans in header and rows before splitting, so that pipes
|
418
|
+
// inside of tags are not interpreted as separators
|
419
|
+
var headers = header.split(/ *[|] */);
|
420
|
+
var colCount = headers.length;
|
421
|
+
|
422
|
+
// build html
|
423
|
+
var cls = self.tableClass ? ' class="' + self.tableClass + '"' : '';
|
424
|
+
var html = ['<table', cls, '>\n', '<thead>\n', '<tr>\n'].join('');
|
425
|
+
|
426
|
+
// build column headers.
|
427
|
+
for (i = 0; i < colCount; i++) {
|
428
|
+
var headerHtml = convertSpans(trim(headers[i]), self);
|
429
|
+
html += [" <th", align[i], ">", headerHtml, "</th>\n"].join('');
|
430
|
+
}
|
431
|
+
html += "</tr>\n</thead>\n";
|
432
|
+
|
433
|
+
// build rows
|
434
|
+
var rows = body.split('\n');
|
435
|
+
for (i = 0; i < rows.length; i++) {
|
436
|
+
if (rows[i].match(/^\s*$/)) // can apply to final row
|
437
|
+
continue;
|
438
|
+
|
439
|
+
// ensure number of rowCells matches colCount
|
440
|
+
var rowCells = rows[i].split(/ *[|] */);
|
441
|
+
var lenDiff = colCount - rowCells.length;
|
442
|
+
for (var j = 0; j < lenDiff; j++)
|
443
|
+
rowCells.push('');
|
444
|
+
|
445
|
+
html += "<tr>\n";
|
446
|
+
for (j = 0; j < colCount; j++) {
|
447
|
+
var colHtml = convertSpans(trim(rowCells[j]), self);
|
448
|
+
html += [" <td", align[j], ">", colHtml, "</td>\n"].join('');
|
449
|
+
}
|
450
|
+
html += "</tr>\n";
|
451
|
+
}
|
452
|
+
|
453
|
+
html += "</table>\n";
|
454
|
+
|
455
|
+
// replace html with placeholder until postConversion step
|
456
|
+
return self.hashExtraBlock(html);
|
457
|
+
}
|
458
|
+
|
459
|
+
return text;
|
460
|
+
};
|
461
|
+
|
462
|
+
|
463
|
+
/******************************************************************
|
464
|
+
* Footnotes *
|
465
|
+
*****************************************************************/
|
466
|
+
|
467
|
+
// Strip footnote, store in hashes.
|
468
|
+
Markdown.Extra.prototype.stripFootnoteDefinitions = function(text) {
|
469
|
+
var self = this;
|
470
|
+
|
471
|
+
text = text.replace(
|
472
|
+
/\n[ ]{0,3}\[\^(.+?)\]\:[ \t]*\n?([\s\S]*?)\n{1,2}((?=\n[ ]{0,3}\S)|$)/g,
|
473
|
+
function(wholeMatch, m1, m2) {
|
474
|
+
m1 = slugify(m1);
|
475
|
+
m2 += "\n";
|
476
|
+
m2 = m2.replace(/^[ ]{0,3}/g, "");
|
477
|
+
self.footnotes[m1] = m2;
|
478
|
+
return "\n";
|
479
|
+
});
|
480
|
+
|
481
|
+
return text;
|
482
|
+
};
|
483
|
+
|
484
|
+
|
485
|
+
// Find and convert footnotes references.
|
486
|
+
Markdown.Extra.prototype.doFootnotes = function(text) {
|
487
|
+
var self = this;
|
488
|
+
if(self.isConvertingFootnote === true) {
|
489
|
+
return text;
|
490
|
+
}
|
491
|
+
|
492
|
+
var footnoteCounter = 0;
|
493
|
+
text = text.replace(/\[\^(.+?)\]/g, function(wholeMatch, m1) {
|
494
|
+
var id = slugify(m1);
|
495
|
+
var footnote = self.footnotes[id];
|
496
|
+
if (footnote === undefined) {
|
497
|
+
return "";
|
498
|
+
}
|
499
|
+
footnoteCounter++;
|
500
|
+
self.usedFootnotes.push(id);
|
501
|
+
var html = '<a href="#fn:' + id + '" id="fnref:' + id
|
502
|
+
+ '" title="See footnote" class="footnote">' + footnoteCounter
|
503
|
+
+ '</a>';
|
504
|
+
return self.hashExtraInline(html);
|
505
|
+
});
|
506
|
+
|
507
|
+
return text;
|
508
|
+
};
|
509
|
+
|
510
|
+
// Print footnotes at the end of the document
|
511
|
+
Markdown.Extra.prototype.printFootnotes = function(text) {
|
512
|
+
var self = this;
|
513
|
+
|
514
|
+
if (self.usedFootnotes.length === 0) {
|
515
|
+
return text;
|
516
|
+
}
|
517
|
+
|
518
|
+
text += '\n\n<div class="footnotes">\n<hr>\n<ol>\n\n';
|
519
|
+
for(var i=0; i<self.usedFootnotes.length; i++) {
|
520
|
+
var id = self.usedFootnotes[i];
|
521
|
+
var footnote = self.footnotes[id];
|
522
|
+
self.isConvertingFootnote = true;
|
523
|
+
var formattedfootnote = convertSpans(footnote, self);
|
524
|
+
delete self.isConvertingFootnote;
|
525
|
+
text += '<li id="fn:'
|
526
|
+
+ id
|
527
|
+
+ '">'
|
528
|
+
+ formattedfootnote
|
529
|
+
+ ' <a href="#fnref:'
|
530
|
+
+ id
|
531
|
+
+ '" title="Return to article" class="reversefootnote">↩</a></li>\n\n';
|
532
|
+
}
|
533
|
+
text += '</ol>\n</div>';
|
534
|
+
return text;
|
535
|
+
};
|
536
|
+
|
537
|
+
|
538
|
+
/******************************************************************
|
539
|
+
* Fenced Code Blocks (gfm) *
|
540
|
+
******************************************************************/
|
541
|
+
|
542
|
+
// Find and convert gfm-inspired fenced code blocks into html.
|
543
|
+
Markdown.Extra.prototype.fencedCodeBlocks = function(text) {
|
544
|
+
function encodeCode(code) {
|
545
|
+
code = code.replace(/&/g, "&");
|
546
|
+
code = code.replace(/</g, "<");
|
547
|
+
code = code.replace(/>/g, ">");
|
548
|
+
// These were escaped by PageDown before postNormalization
|
549
|
+
code = code.replace(/~D/g, "$$");
|
550
|
+
code = code.replace(/~T/g, "~");
|
551
|
+
return code;
|
552
|
+
}
|
553
|
+
|
554
|
+
var self = this;
|
555
|
+
text = text.replace(/(?:^|\n)```(.*)\n([\s\S]*?)\n```/g, function(match, m1, m2) {
|
556
|
+
var language = m1, codeblock = m2;
|
557
|
+
|
558
|
+
// adhere to specified options
|
559
|
+
var preclass = self.googleCodePrettify ? ' class="prettyprint"' : '';
|
560
|
+
var codeclass = '';
|
561
|
+
if (language) {
|
562
|
+
if (self.googleCodePrettify || self.highlightJs) {
|
563
|
+
// use html5 language- class names. supported by both prettify and highlight.js
|
564
|
+
codeclass = ' class="language-' + language + '"';
|
565
|
+
} else {
|
566
|
+
codeclass = ' class="' + language + '"';
|
567
|
+
}
|
568
|
+
}
|
569
|
+
|
570
|
+
var html = ['<pre', preclass, '><code', codeclass, '>',
|
571
|
+
encodeCode(codeblock), '</code></pre>'].join('');
|
572
|
+
|
573
|
+
// replace codeblock with placeholder until postConversion step
|
574
|
+
return self.hashExtraBlock(html);
|
575
|
+
});
|
576
|
+
|
577
|
+
return text;
|
578
|
+
};
|
579
|
+
|
580
|
+
|
581
|
+
/******************************************************************
|
582
|
+
* Definition Lists *
|
583
|
+
******************************************************************/
|
584
|
+
|
585
|
+
// Find and convert markdown extra definition lists into html.
|
586
|
+
Markdown.Extra.prototype.definitionLists = function(text) {
|
587
|
+
var wholeList = new RegExp(
|
588
|
+
['(\\x02\\n?|\\n\\n)' ,
|
589
|
+
'(?:' ,
|
590
|
+
'(' , // $1 = whole list
|
591
|
+
'(' , // $2
|
592
|
+
'[ ]{0,3}' ,
|
593
|
+
'((?:[ \\t]*\\S.*\\n)+)', // $3 = defined term
|
594
|
+
'\\n?' ,
|
595
|
+
'[ ]{0,3}:[ ]+' , // colon starting definition
|
596
|
+
')' ,
|
597
|
+
'([\\s\\S]+?)' ,
|
598
|
+
'(' , // $4
|
599
|
+
'(?=\\0x03)' , // \z
|
600
|
+
'|' ,
|
601
|
+
'(?=' ,
|
602
|
+
'\\n{2,}' ,
|
603
|
+
'(?=\\S)' ,
|
604
|
+
'(?!' , // Negative lookahead for another term
|
605
|
+
'[ ]{0,3}' ,
|
606
|
+
'(?:\\S.*\\n)+?' , // defined term
|
607
|
+
'\\n?' ,
|
608
|
+
'[ ]{0,3}:[ ]+' , // colon starting definition
|
609
|
+
')' ,
|
610
|
+
'(?!' , // Negative lookahead for another definition
|
611
|
+
'[ ]{0,3}:[ ]+' , // colon starting definition
|
612
|
+
')' ,
|
613
|
+
')' ,
|
614
|
+
')' ,
|
615
|
+
')' ,
|
616
|
+
')'
|
617
|
+
].join(''),
|
618
|
+
'gm'
|
619
|
+
);
|
620
|
+
|
621
|
+
var self = this;
|
622
|
+
text = addAnchors(text);
|
623
|
+
|
624
|
+
text = text.replace(wholeList, function(match, pre, list) {
|
625
|
+
var result = trim(self.processDefListItems(list));
|
626
|
+
result = "<dl>\n" + result + "\n</dl>";
|
627
|
+
return pre + self.hashExtraBlock(result) + "\n\n";
|
628
|
+
});
|
629
|
+
|
630
|
+
return removeAnchors(text);
|
631
|
+
};
|
632
|
+
|
633
|
+
// Process the contents of a single definition list, splitting it
|
634
|
+
// into individual term and definition list items.
|
635
|
+
Markdown.Extra.prototype.processDefListItems = function(listStr) {
|
636
|
+
var self = this;
|
637
|
+
|
638
|
+
var dt = new RegExp(
|
639
|
+
['(\\x02\\n?|\\n\\n+)' , // leading line
|
640
|
+
'(' , // definition terms = $1
|
641
|
+
'[ ]{0,3}' , // leading whitespace
|
642
|
+
'(?![:][ ]|[ ])' , // negative lookahead for a definition
|
643
|
+
// mark (colon) or more whitespace
|
644
|
+
'(?:\\S.*\\n)+?' , // actual term (not whitespace)
|
645
|
+
')' ,
|
646
|
+
'(?=\\n?[ ]{0,3}:[ ])' // lookahead for following line feed
|
647
|
+
].join(''), // with a definition mark
|
648
|
+
'gm'
|
649
|
+
);
|
650
|
+
|
651
|
+
var dd = new RegExp(
|
652
|
+
['\\n(\\n+)?' , // leading line = $1
|
653
|
+
'(' , // marker space = $2
|
654
|
+
'[ ]{0,3}' , // whitespace before colon
|
655
|
+
'[:][ ]+' , // definition mark (colon)
|
656
|
+
')' ,
|
657
|
+
'([\\s\\S]+?)' , // definition text = $3
|
658
|
+
'(?=\\n*' , // stop at next definition mark,
|
659
|
+
'(?:' , // next term or end of text
|
660
|
+
'\\n[ ]{0,3}[:][ ]|' ,
|
661
|
+
'<dt>|\\x03' , // \z
|
662
|
+
')' ,
|
663
|
+
')'
|
664
|
+
].join(''),
|
665
|
+
'gm'
|
666
|
+
);
|
667
|
+
|
668
|
+
listStr = addAnchors(listStr);
|
669
|
+
// trim trailing blank lines:
|
670
|
+
listStr = listStr.replace(/\n{2,}(?=\\x03)/, "\n");
|
671
|
+
|
672
|
+
// Process definition terms.
|
673
|
+
listStr = listStr.replace(dt, function(match, pre, termsStr) {
|
674
|
+
var terms = trim(termsStr).split("\n");
|
675
|
+
var text = '';
|
676
|
+
for (var i = 0; i < terms.length; i++) {
|
677
|
+
var term = terms[i];
|
678
|
+
// process spans inside dt
|
679
|
+
term = convertSpans(trim(term), self);
|
680
|
+
text += "\n<dt>" + term + "</dt>";
|
681
|
+
}
|
682
|
+
return text + "\n";
|
683
|
+
});
|
684
|
+
|
685
|
+
// Process actual definitions.
|
686
|
+
listStr = listStr.replace(dd, function(match, leadingLine, markerSpace, def) {
|
687
|
+
if (leadingLine || def.match(/\n{2,}/)) {
|
688
|
+
// replace marker with the appropriate whitespace indentation
|
689
|
+
def = Array(markerSpace.length + 1).join(' ') + def;
|
690
|
+
// process markdown inside definition
|
691
|
+
// TODO?: currently doesn't apply extensions
|
692
|
+
def = outdent(def) + "\n\n";
|
693
|
+
def = "\n" + convertAll(def, self) + "\n";
|
694
|
+
} else {
|
695
|
+
// convert span-level markdown inside definition
|
696
|
+
def = rtrim(def);
|
697
|
+
def = convertSpans(outdent(def), self);
|
698
|
+
}
|
699
|
+
|
700
|
+
return "\n<dd>" + def + "</dd>\n";
|
701
|
+
});
|
702
|
+
|
703
|
+
return removeAnchors(listStr);
|
704
|
+
};
|
705
|
+
|
706
|
+
})();
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pagedown-bootstrap-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-08-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: railties
|
@@ -41,6 +41,7 @@ files:
|
|
41
41
|
- vendor/assets/images/pagedown-bootstrap-buttons.png
|
42
42
|
- vendor/assets/javascripts/markdown.converter.js
|
43
43
|
- vendor/assets/javascripts/markdown.editor.js
|
44
|
+
- vendor/assets/javascripts/markdown.extra.js
|
44
45
|
- vendor/assets/javascripts/markdown.sanitizer.js
|
45
46
|
- vendor/assets/javascripts/pagedown_bootstrap.js
|
46
47
|
- vendor/assets/stylesheets/pagedown_bootstrap.css.scss
|