pagedown-bootstrap-rails 2.0.0 → 2.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.
- checksums.yaml +4 -4
- data/lib/pagedown_bootstrap/rails/version.rb +1 -1
- data/vendor/assets/javascripts/markdown.extra.js +832 -664
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 277b26a1f585d221eb28ce6cc8e01398acaae592
|
4
|
+
data.tar.gz: cbbad5d6ecef19e4f261db4d075563c7b1dff47b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 18c3a8fd8e31cdb726200fa825af94864883aa5fe21bcd45d5784f5dcf65695095148343c3f89c38a1b019ddbd9c6be0ae43384c31ebf3dfd0b2a204ae90e724
|
7
|
+
data.tar.gz: 0198407389f1eff8ccc38dee2529c511e0654f88821083d313a3f6358122360b5436d2643c6834879c418564375d0fbac37d690d159e83b5cfb25a67b359d5ed
|
@@ -1,706 +1,874 @@
|
|
1
1
|
(function () {
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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]);
|
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
|
+
};
|
65
28
|
}
|
66
|
-
|
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"];
|
29
|
+
|
30
|
+
function trim(str) {
|
31
|
+
return str.replace(/^\s+|\s+$/g, '');
|
177
32
|
}
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
postConversionTransformations.push("applyAttributeBlocks");
|
182
|
-
extra.attributeBlocks = true;
|
33
|
+
|
34
|
+
function rtrim(str) {
|
35
|
+
return str.replace(/\s+$/g, '');
|
183
36
|
}
|
184
|
-
|
185
|
-
|
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'), '');
|
186
41
|
}
|
187
|
-
|
188
|
-
|
42
|
+
|
43
|
+
function contains(str, substr) {
|
44
|
+
return str.indexOf(substr) != -1;
|
189
45
|
}
|
190
|
-
|
191
|
-
|
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
|
+
});
|
192
52
|
}
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
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;
|
197
67
|
}
|
198
68
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
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';
|
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;
|
224
79
|
}
|
225
80
|
|
226
|
-
|
227
|
-
|
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;
|
228
88
|
}
|
229
89
|
|
230
|
-
|
231
|
-
|
232
|
-
|
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
|
-
}
|
90
|
+
// Convert markdown within an element, retaining only span-level tags
|
91
|
+
function convertSpans(text, extra) {
|
92
|
+
return sanitizeHtml(convertAll(text, extra), inlineTags);
|
266
93
|
}
|
267
|
-
recursiveUnHash();
|
268
|
-
return text;
|
269
|
-
};
|
270
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
|
+
}
|
271
104
|
|
272
|
-
|
273
|
-
|
274
|
-
|
105
|
+
// Convert escaped special characters
|
106
|
+
function processEscapesStep1(text) {
|
107
|
+
// Markdown extra adds two escapable characters, `:` and `|`
|
108
|
+
return text.replace(/\\\|/g, '~I').replace(/\\:/g, '~i');
|
109
|
+
}
|
110
|
+
function processEscapesStep2(text) {
|
111
|
+
return text.replace(/~I/g, '|').replace(/~i/g, ':');
|
112
|
+
}
|
275
113
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
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";
|
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;
|
289
122
|
}
|
290
123
|
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
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 postSpanGamutTransformations = [];
|
172
|
+
var postConversionTransformations = ["unHashExtraBlocks"];
|
173
|
+
|
174
|
+
options = options || {};
|
175
|
+
options.extensions = options.extensions || ["all"];
|
176
|
+
if (contains(options.extensions, "all")) {
|
177
|
+
options.extensions = ["tables", "fenced_code_gfm", "def_list", "attr_list", "footnotes", "smartypants", "strikethrough", "newlines"];
|
178
|
+
}
|
179
|
+
preBlockGamutTransformations.push("wrapHeaders");
|
180
|
+
if (contains(options.extensions, "attr_list")) {
|
181
|
+
postNormalizationTransformations.push("hashFcbAttributeBlocks");
|
182
|
+
preBlockGamutTransformations.push("hashHeaderAttributeBlocks");
|
183
|
+
postConversionTransformations.push("applyAttributeBlocks");
|
184
|
+
extra.attributeBlocks = true;
|
185
|
+
}
|
186
|
+
if (contains(options.extensions, "fenced_code_gfm")) {
|
187
|
+
// This step will convert fcb inside list items and blockquotes
|
188
|
+
preBlockGamutTransformations.push("fencedCodeBlocks");
|
189
|
+
// This extra step is to prevent html blocks hashing and link definition/footnotes stripping inside fcb
|
190
|
+
postNormalizationTransformations.push("fencedCodeBlocks");
|
191
|
+
}
|
192
|
+
if (contains(options.extensions, "tables")) {
|
193
|
+
preBlockGamutTransformations.push("tables");
|
194
|
+
}
|
195
|
+
if (contains(options.extensions, "def_list")) {
|
196
|
+
preBlockGamutTransformations.push("definitionLists");
|
197
|
+
}
|
198
|
+
if (contains(options.extensions, "footnotes")) {
|
199
|
+
postNormalizationTransformations.push("stripFootnoteDefinitions");
|
200
|
+
preBlockGamutTransformations.push("doFootnotes");
|
201
|
+
postConversionTransformations.push("printFootnotes");
|
202
|
+
}
|
203
|
+
if (contains(options.extensions, "smartypants")) {
|
204
|
+
postConversionTransformations.push("runSmartyPants");
|
205
|
+
}
|
206
|
+
if (contains(options.extensions, "strikethrough")) {
|
207
|
+
postSpanGamutTransformations.push("strikethrough");
|
208
|
+
}
|
209
|
+
if (contains(options.extensions, "newlines")) {
|
210
|
+
postSpanGamutTransformations.push("newlines");
|
211
|
+
}
|
212
|
+
|
213
|
+
converter.hooks.chain("postNormalization", function(text) {
|
214
|
+
return extra.doTransform(postNormalizationTransformations, text) + '\n';
|
215
|
+
});
|
216
|
+
|
217
|
+
converter.hooks.chain("preBlockGamut", function(text, blockGamutHookCallback) {
|
218
|
+
// Keep a reference to the block gamut callback to run recursively
|
219
|
+
extra.blockGamutHookCallback = blockGamutHookCallback;
|
220
|
+
text = processEscapesStep1(text);
|
221
|
+
text = extra.doTransform(preBlockGamutTransformations, text) + '\n';
|
222
|
+
text = processEscapesStep2(text);
|
223
|
+
return text;
|
224
|
+
});
|
225
|
+
|
226
|
+
converter.hooks.chain("postSpanGamut", function(text) {
|
227
|
+
return extra.doTransform(postSpanGamutTransformations, text);
|
228
|
+
});
|
229
|
+
|
230
|
+
// Keep a reference to the hook chain running before doPostConversion to apply on hashed extra blocks
|
231
|
+
extra.previousPostConversion = converter.hooks.postConversion;
|
232
|
+
converter.hooks.chain("postConversion", function(text) {
|
233
|
+
text = extra.doTransform(postConversionTransformations, text);
|
234
|
+
// Clear state vars that may use unnecessary memory
|
235
|
+
extra.hashBlocks = [];
|
236
|
+
extra.footnotes = {};
|
237
|
+
extra.usedFootnotes = [];
|
238
|
+
return text;
|
239
|
+
});
|
240
|
+
|
241
|
+
if ("highlighter" in options) {
|
242
|
+
extra.googleCodePrettify = options.highlighter === 'prettify';
|
243
|
+
extra.highlightJs = options.highlighter === 'highlight';
|
244
|
+
}
|
245
|
+
|
246
|
+
if ("table_class" in options) {
|
247
|
+
extra.tableClass = options.table_class;
|
248
|
+
}
|
249
|
+
|
250
|
+
extra.converter = converter;
|
251
|
+
|
252
|
+
// Caller usually won't need this, but it's handy for testing.
|
253
|
+
return extra;
|
254
|
+
};
|
255
|
+
|
256
|
+
// Do transformations
|
257
|
+
Markdown.Extra.prototype.doTransform = function(transformations, text) {
|
258
|
+
for(var i = 0; i < transformations.length; i++)
|
259
|
+
text = this[transformations[i]](text);
|
260
|
+
return text;
|
261
|
+
};
|
262
|
+
|
263
|
+
// Return a placeholder containing a key, which is the block's index in the
|
264
|
+
// hashBlocks array. We wrap our output in a <p> tag here so Pagedown won't.
|
265
|
+
Markdown.Extra.prototype.hashExtraBlock = function(block) {
|
266
|
+
return '\n<p>~X' + (this.hashBlocks.push(block) - 1) + 'X</p>\n';
|
267
|
+
};
|
268
|
+
Markdown.Extra.prototype.hashExtraInline = function(block) {
|
269
|
+
return '~X' + (this.hashBlocks.push(block) - 1) + 'X';
|
270
|
+
};
|
271
|
+
|
272
|
+
// Replace placeholder blocks in `text` with their corresponding
|
273
|
+
// html blocks in the hashBlocks array.
|
274
|
+
Markdown.Extra.prototype.unHashExtraBlocks = function(text) {
|
275
|
+
var self = this;
|
276
|
+
function recursiveUnHash() {
|
277
|
+
var hasHash = false;
|
278
|
+
text = text.replace(/(?:<p>)?~X(\d+)X(?:<\/p>)?/g, function(wholeMatch, m1) {
|
279
|
+
hasHash = true;
|
280
|
+
var key = parseInt(m1, 10);
|
281
|
+
return self.hashBlocks[key];
|
282
|
+
});
|
283
|
+
if(hasHash === true) {
|
284
|
+
recursiveUnHash();
|
285
|
+
}
|
286
|
+
}
|
287
|
+
recursiveUnHash();
|
288
|
+
return text;
|
289
|
+
};
|
290
|
+
|
291
|
+
// Wrap headers to make sure they won't be in def lists
|
292
|
+
Markdown.Extra.prototype.wrapHeaders = function(text) {
|
293
|
+
function wrap(text) {
|
294
|
+
return '\n' + text + '\n';
|
295
|
+
}
|
296
|
+
text = text.replace(/^.+[ \t]*\n=+[ \t]*\n+/gm, wrap);
|
297
|
+
text = text.replace(/^.+[ \t]*\n-+[ \t]*\n+/gm, wrap);
|
298
|
+
text = text.replace(/^\#{1,6}[ \t]*.+?[ \t]*\#*\n+/gm, wrap);
|
299
|
+
return text;
|
300
|
+
};
|
301
|
+
|
302
|
+
|
303
|
+
/******************************************************************
|
304
|
+
* Attribute Blocks *
|
305
|
+
*****************************************************************/
|
295
306
|
|
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
307
|
// TODO: use sentinels. Should we just add/remove them in doConversion?
|
300
308
|
// TODO: better matches for id / class attributes
|
301
|
-
var attrBlock = "\\{\\
|
302
|
-
var
|
303
|
-
|
309
|
+
var attrBlock = "\\{[ \\t]*((?:[#.][-_:a-zA-Z0-9]+[ \\t]*)+)\\}";
|
310
|
+
var hdrAttributesA = new RegExp("^(#{1,6}.*#{0,6})[ \\t]+" + attrBlock + "[ \\t]*(?:\\n|0x03)", "gm");
|
311
|
+
var hdrAttributesB = new RegExp("^(.*)[ \\t]+" + attrBlock + "[ \\t]*\\n" +
|
312
|
+
"(?=[\\-|=]+\\s*(?:\\n|0x03))", "gm"); // underline lookahead
|
313
|
+
var fcbAttributes = new RegExp("^(```[ \\t]*[^{\\s]*)[ \\t]+" + attrBlock + "[ \\t]*\\n" +
|
314
|
+
"(?=([\\s\\S]*?)\\n```[ \\t]*(\\n|0x03))", "gm");
|
315
|
+
|
316
|
+
// Extract headers attribute blocks, move them above the element they will be
|
317
|
+
// applied to, and hash them for later.
|
318
|
+
Markdown.Extra.prototype.hashHeaderAttributeBlocks = function(text) {
|
319
|
+
|
320
|
+
var self = this;
|
321
|
+
function attributeCallback(wholeMatch, pre, attr) {
|
322
|
+
return '<p>~XX' + (self.hashBlocks.push(attr) - 1) + 'XX</p>\n' + pre + "\n";
|
323
|
+
}
|
304
324
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
}
|
325
|
+
text = text.replace(hdrAttributesA, attributeCallback); // ## headers
|
326
|
+
text = text.replace(hdrAttributesB, attributeCallback); // underline headers
|
327
|
+
return text;
|
328
|
+
};
|
309
329
|
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
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('');
|
330
|
+
// Extract FCB attribute blocks, move them above the element they will be
|
331
|
+
// applied to, and hash them for later.
|
332
|
+
Markdown.Extra.prototype.hashFcbAttributeBlocks = function(text) {
|
333
|
+
// TODO: use sentinels. Should we just add/remove them in doConversion?
|
334
|
+
// TODO: better matches for id / class attributes
|
335
|
+
|
336
|
+
var self = this;
|
337
|
+
function attributeCallback(wholeMatch, pre, attr) {
|
338
|
+
return '<p>~XX' + (self.hashBlocks.push(attr) - 1) + 'XX</p>\n' + pre + "\n";
|
449
339
|
}
|
450
|
-
html += "</tr>\n";
|
451
|
-
}
|
452
340
|
|
453
|
-
|
341
|
+
return text.replace(fcbAttributes, attributeCallback);
|
342
|
+
};
|
454
343
|
|
455
|
-
|
456
|
-
|
457
|
-
|
344
|
+
Markdown.Extra.prototype.applyAttributeBlocks = function(text) {
|
345
|
+
var self = this;
|
346
|
+
var blockRe = new RegExp('<p>~XX(\\d+)XX</p>[\\s]*' +
|
347
|
+
'(?:<(h[1-6]|pre)(?: +class="(\\S+)")?(>[\\s\\S]*?</\\2>))', "gm");
|
348
|
+
text = text.replace(blockRe, function(wholeMatch, k, tag, cls, rest) {
|
349
|
+
if (!tag) // no following header or fenced code block.
|
350
|
+
return '';
|
458
351
|
|
459
|
-
|
460
|
-
|
352
|
+
// get attributes list from hash
|
353
|
+
var key = parseInt(k, 10);
|
354
|
+
var attributes = self.hashBlocks[key];
|
461
355
|
|
356
|
+
// get id
|
357
|
+
var id = attributes.match(/#[^\s#.]+/g) || [];
|
358
|
+
var idStr = id[0] ? ' id="' + id[0].substr(1, id[0].length - 1) + '"' : '';
|
462
359
|
|
463
|
-
|
464
|
-
|
465
|
-
|
360
|
+
// get classes and merge with existing classes
|
361
|
+
var classes = attributes.match(/\.[^\s#.]+/g) || [];
|
362
|
+
for (var i = 0; i < classes.length; i++) // Remove leading dot
|
363
|
+
classes[i] = classes[i].substr(1, classes[i].length - 1);
|
466
364
|
|
467
|
-
|
468
|
-
|
469
|
-
|
365
|
+
var classStr = '';
|
366
|
+
if (cls)
|
367
|
+
classes = union(classes, [cls]);
|
470
368
|
|
471
|
-
|
472
|
-
|
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
|
-
});
|
369
|
+
if (classes.length > 0)
|
370
|
+
classStr = ' class="' + classes.join(' ') + '"';
|
480
371
|
|
481
|
-
|
482
|
-
|
372
|
+
return "<" + tag + idStr + classStr + rest;
|
373
|
+
});
|
483
374
|
|
375
|
+
return text;
|
376
|
+
};
|
484
377
|
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
378
|
+
/******************************************************************
|
379
|
+
* Tables *
|
380
|
+
*****************************************************************/
|
381
|
+
|
382
|
+
// Find and convert Markdown Extra tables into html.
|
383
|
+
Markdown.Extra.prototype.tables = function(text) {
|
384
|
+
|
385
|
+
var self = this;
|
386
|
+
|
387
|
+
var leadingPipe = new RegExp(
|
388
|
+
['^' ,
|
389
|
+
'[ ]{0,3}' , // Allowed whitespace
|
390
|
+
'[|]' , // Initial pipe
|
391
|
+
'(.+)\\n' , // $1: Header Row
|
392
|
+
|
393
|
+
'[ ]{0,3}' , // Allowed whitespace
|
394
|
+
'[|]([ ]*[-:]+[-| :]*)\\n' , // $2: Separator
|
395
|
+
|
396
|
+
'(' , // $3: Table Body
|
397
|
+
'(?:[ ]*[|].*\\n?)*' , // Table rows
|
398
|
+
')',
|
399
|
+
'(?:\\n|$)' // Stop at final newline
|
400
|
+
].join(''),
|
401
|
+
'gm'
|
402
|
+
);
|
403
|
+
|
404
|
+
var noLeadingPipe = new RegExp(
|
405
|
+
['^' ,
|
406
|
+
'[ ]{0,3}' , // Allowed whitespace
|
407
|
+
'(\\S.*[|].*)\\n' , // $1: Header Row
|
408
|
+
|
409
|
+
'[ ]{0,3}' , // Allowed whitespace
|
410
|
+
'([-:]+[ ]*[|][-| :]*)\\n' , // $2: Separator
|
411
|
+
|
412
|
+
'(' , // $3: Table Body
|
413
|
+
'(?:.*[|].*\\n?)*' , // Table rows
|
414
|
+
')' ,
|
415
|
+
'(?:\\n|$)' // Stop at final newline
|
416
|
+
].join(''),
|
417
|
+
'gm'
|
418
|
+
);
|
419
|
+
|
420
|
+
text = text.replace(leadingPipe, doTable);
|
421
|
+
text = text.replace(noLeadingPipe, doTable);
|
422
|
+
|
423
|
+
// $1 = header, $2 = separator, $3 = body
|
424
|
+
function doTable(match, header, separator, body, offset, string) {
|
425
|
+
// remove any leading pipes and whitespace
|
426
|
+
header = header.replace(/^ *[|]/m, '');
|
427
|
+
separator = separator.replace(/^ *[|]/m, '');
|
428
|
+
body = body.replace(/^ *[|]/gm, '');
|
429
|
+
|
430
|
+
// remove trailing pipes and whitespace
|
431
|
+
header = header.replace(/[|] *$/m, '');
|
432
|
+
separator = separator.replace(/[|] *$/m, '');
|
433
|
+
body = body.replace(/[|] *$/gm, '');
|
434
|
+
|
435
|
+
// determine column alignments
|
436
|
+
alignspecs = separator.split(/ *[|] */);
|
437
|
+
align = [];
|
438
|
+
for (var i = 0; i < alignspecs.length; i++) {
|
439
|
+
var spec = alignspecs[i];
|
440
|
+
if (spec.match(/^ *-+: *$/m))
|
441
|
+
align[i] = ' style="text-align:right;"';
|
442
|
+
else if (spec.match(/^ *:-+: *$/m))
|
443
|
+
align[i] = ' style="text-align:center;"';
|
444
|
+
else if (spec.match(/^ *:-+ *$/m))
|
445
|
+
align[i] = ' style="text-align:left;"';
|
446
|
+
else align[i] = '';
|
447
|
+
}
|
448
|
+
|
449
|
+
// TODO: parse spans in header and rows before splitting, so that pipes
|
450
|
+
// inside of tags are not interpreted as separators
|
451
|
+
var headers = header.split(/ *[|] */);
|
452
|
+
var colCount = headers.length;
|
453
|
+
|
454
|
+
// build html
|
455
|
+
var cls = self.tableClass ? ' class="' + self.tableClass + '"' : '';
|
456
|
+
var html = ['<table', cls, '>\n', '<thead>\n', '<tr>\n'].join('');
|
457
|
+
|
458
|
+
// build column headers.
|
459
|
+
for (i = 0; i < colCount; i++) {
|
460
|
+
var headerHtml = convertSpans(trim(headers[i]), self);
|
461
|
+
html += [" <th", align[i], ">", headerHtml, "</th>\n"].join('');
|
462
|
+
}
|
463
|
+
html += "</tr>\n</thead>\n";
|
464
|
+
|
465
|
+
// build rows
|
466
|
+
var rows = body.split('\n');
|
467
|
+
for (i = 0; i < rows.length; i++) {
|
468
|
+
if (rows[i].match(/^\s*$/)) // can apply to final row
|
469
|
+
continue;
|
470
|
+
|
471
|
+
// ensure number of rowCells matches colCount
|
472
|
+
var rowCells = rows[i].split(/ *[|] */);
|
473
|
+
var lenDiff = colCount - rowCells.length;
|
474
|
+
for (var j = 0; j < lenDiff; j++)
|
475
|
+
rowCells.push('');
|
476
|
+
|
477
|
+
html += "<tr>\n";
|
478
|
+
for (j = 0; j < colCount; j++) {
|
479
|
+
var colHtml = convertSpans(trim(rowCells[j]), self);
|
480
|
+
html += [" <td", align[j], ">", colHtml, "</td>\n"].join('');
|
481
|
+
}
|
482
|
+
html += "</tr>\n";
|
483
|
+
}
|
484
|
+
|
485
|
+
html += "</table>\n";
|
486
|
+
|
487
|
+
// replace html with placeholder until postConversion step
|
488
|
+
return self.hashExtraBlock(html);
|
489
|
+
}
|
491
490
|
|
492
|
-
|
493
|
-
|
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
|
-
}
|
491
|
+
return text;
|
492
|
+
};
|
517
493
|
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
494
|
+
|
495
|
+
/******************************************************************
|
496
|
+
* Footnotes *
|
497
|
+
*****************************************************************/
|
498
|
+
|
499
|
+
// Strip footnote, store in hashes.
|
500
|
+
Markdown.Extra.prototype.stripFootnoteDefinitions = function(text) {
|
501
|
+
var self = this;
|
502
|
+
|
503
|
+
text = text.replace(
|
504
|
+
/\n[ ]{0,3}\[\^(.+?)\]\:[ \t]*\n?([\s\S]*?)\n{1,2}((?=\n[ ]{0,3}\S)|$)/g,
|
505
|
+
function(wholeMatch, m1, m2) {
|
506
|
+
m1 = slugify(m1);
|
507
|
+
m2 += "\n";
|
508
|
+
m2 = m2.replace(/^[ ]{0,3}/g, "");
|
509
|
+
self.footnotes[m1] = m2;
|
510
|
+
return "\n";
|
511
|
+
});
|
512
|
+
|
513
|
+
return text;
|
514
|
+
};
|
515
|
+
|
516
|
+
|
517
|
+
// Find and convert footnotes references.
|
518
|
+
Markdown.Extra.prototype.doFootnotes = function(text) {
|
519
|
+
var self = this;
|
520
|
+
if(self.isConvertingFootnote === true) {
|
521
|
+
return text;
|
522
|
+
}
|
523
|
+
|
524
|
+
var footnoteCounter = 0;
|
525
|
+
text = text.replace(/\[\^(.+?)\]/g, function(wholeMatch, m1) {
|
526
|
+
var id = slugify(m1);
|
527
|
+
var footnote = self.footnotes[id];
|
528
|
+
if (footnote === undefined) {
|
529
|
+
return wholeMatch;
|
530
|
+
}
|
531
|
+
footnoteCounter++;
|
532
|
+
self.usedFootnotes.push(id);
|
533
|
+
var html = '<a href="#fn:' + id + '" id="fnref:' + id
|
534
|
+
+ '" title="See footnote" class="footnote">' + footnoteCounter
|
535
|
+
+ '</a>';
|
536
|
+
return self.hashExtraInline(html);
|
537
|
+
});
|
538
|
+
|
539
|
+
return text;
|
540
|
+
};
|
541
|
+
|
542
|
+
// Print footnotes at the end of the document
|
543
|
+
Markdown.Extra.prototype.printFootnotes = function(text) {
|
544
|
+
var self = this;
|
545
|
+
|
546
|
+
if (self.usedFootnotes.length === 0) {
|
547
|
+
return text;
|
548
|
+
}
|
549
|
+
|
550
|
+
text += '\n\n<div class="footnotes">\n<hr>\n<ol>\n\n';
|
551
|
+
for(var i=0; i<self.usedFootnotes.length; i++) {
|
552
|
+
var id = self.usedFootnotes[i];
|
553
|
+
var footnote = self.footnotes[id];
|
554
|
+
self.isConvertingFootnote = true;
|
555
|
+
var formattedfootnote = convertSpans(footnote, self);
|
556
|
+
delete self.isConvertingFootnote;
|
557
|
+
text += '<li id="fn:'
|
558
|
+
+ id
|
559
|
+
+ '">'
|
560
|
+
+ formattedfootnote
|
561
|
+
+ ' <a href="#fnref:'
|
562
|
+
+ id
|
563
|
+
+ '" title="Return to article" class="reversefootnote">↩</a></li>\n\n';
|
564
|
+
}
|
565
|
+
text += '</ol>\n</div>';
|
566
|
+
return text;
|
567
|
+
};
|
568
|
+
|
569
|
+
|
570
|
+
/******************************************************************
|
571
|
+
* Fenced Code Blocks (gfm) *
|
572
|
+
******************************************************************/
|
573
|
+
|
574
|
+
// Find and convert gfm-inspired fenced code blocks into html.
|
575
|
+
Markdown.Extra.prototype.fencedCodeBlocks = function(text) {
|
576
|
+
function encodeCode(code) {
|
577
|
+
code = code.replace(/&/g, "&");
|
578
|
+
code = code.replace(/</g, "<");
|
579
|
+
code = code.replace(/>/g, ">");
|
580
|
+
// These were escaped by PageDown before postNormalization
|
581
|
+
code = code.replace(/~D/g, "$$");
|
582
|
+
code = code.replace(/~T/g, "~");
|
583
|
+
return code;
|
584
|
+
}
|
585
|
+
|
586
|
+
var self = this;
|
587
|
+
text = text.replace(/(?:^|\n)```[ \t]*(\S*)[ \t]*\n([\s\S]*?)\n```[ \t]*(?=\n)/g, function(match, m1, m2) {
|
588
|
+
var language = m1, codeblock = m2;
|
589
|
+
|
590
|
+
// adhere to specified options
|
591
|
+
var preclass = self.googleCodePrettify ? ' class="prettyprint"' : '';
|
592
|
+
var codeclass = '';
|
593
|
+
if (language) {
|
594
|
+
if (self.googleCodePrettify || self.highlightJs) {
|
595
|
+
// use html5 language- class names. supported by both prettify and highlight.js
|
596
|
+
codeclass = ' class="language-' + language + '"';
|
597
|
+
} else {
|
598
|
+
codeclass = ' class="' + language + '"';
|
599
|
+
}
|
600
|
+
}
|
601
|
+
|
602
|
+
var html = ['<pre', preclass, '><code', codeclass, '>',
|
603
|
+
encodeCode(codeblock), '</code></pre>'].join('');
|
604
|
+
|
605
|
+
// replace codeblock with placeholder until postConversion step
|
606
|
+
return self.hashExtraBlock(html);
|
607
|
+
});
|
608
|
+
|
609
|
+
return text;
|
610
|
+
};
|
611
|
+
|
612
|
+
|
613
|
+
/******************************************************************
|
614
|
+
* SmartyPants *
|
615
|
+
******************************************************************/
|
616
|
+
|
617
|
+
Markdown.Extra.prototype.educatePants = function(text) {
|
618
|
+
var self = this;
|
619
|
+
var result = '';
|
620
|
+
var blockOffset = 0;
|
621
|
+
// Here we parse HTML in a very bad manner
|
622
|
+
text.replace(/(?:<!--[\s\S]*?-->)|(<)([a-zA-Z1-6]+)([^\n]*?>)([\s\S]*?)(<\/\2>)/g, function(wholeMatch, m1, m2, m3, m4, m5, offset) {
|
623
|
+
var token = text.substring(blockOffset, offset);
|
624
|
+
result += self.applyPants(token);
|
625
|
+
self.smartyPantsLastChar = result.substring(result.length - 1);
|
626
|
+
blockOffset = offset + wholeMatch.length;
|
627
|
+
if(!m1) {
|
628
|
+
// Skip commentary
|
629
|
+
result += wholeMatch;
|
630
|
+
return;
|
631
|
+
}
|
632
|
+
// Skip special tags
|
633
|
+
if(!/code|kbd|pre|script|noscript|iframe|math|ins|del|pre/i.test(m2)) {
|
634
|
+
m4 = self.educatePants(m4);
|
635
|
+
}
|
636
|
+
else {
|
637
|
+
self.smartyPantsLastChar = m4.substring(m4.length - 1);
|
638
|
+
}
|
639
|
+
result += m1 + m2 + m3 + m4 + m5;
|
640
|
+
});
|
641
|
+
var lastToken = text.substring(blockOffset);
|
642
|
+
result += self.applyPants(lastToken);
|
643
|
+
self.smartyPantsLastChar = result.substring(result.length - 1);
|
644
|
+
return result;
|
645
|
+
};
|
646
|
+
|
647
|
+
function revertPants(wholeMatch, m1) {
|
648
|
+
var blockText = m1;
|
649
|
+
blockText = blockText.replace(/&\#8220;/g, "\"");
|
650
|
+
blockText = blockText.replace(/&\#8221;/g, "\"");
|
651
|
+
blockText = blockText.replace(/&\#8216;/g, "'");
|
652
|
+
blockText = blockText.replace(/&\#8217;/g, "'");
|
653
|
+
blockText = blockText.replace(/&\#8212;/g, "---");
|
654
|
+
blockText = blockText.replace(/&\#8211;/g, "--");
|
655
|
+
blockText = blockText.replace(/&\#8230;/g, "...");
|
656
|
+
return blockText;
|
552
657
|
}
|
553
658
|
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
if
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
659
|
+
Markdown.Extra.prototype.applyPants = function(text) {
|
660
|
+
// Dashes
|
661
|
+
text = text.replace(/---/g, "—").replace(/--/g, "–");
|
662
|
+
// Ellipses
|
663
|
+
text = text.replace(/\.\.\./g, "…").replace(/\.\s\.\s\./g, "…");
|
664
|
+
// Backticks
|
665
|
+
text = text.replace(/``/g, "“").replace (/''/g, "”");
|
666
|
+
|
667
|
+
if(/^'$/.test(text)) {
|
668
|
+
// Special case: single-character ' token
|
669
|
+
if(/\S/.test(this.smartyPantsLastChar)) {
|
670
|
+
return "’";
|
671
|
+
}
|
672
|
+
return "‘";
|
567
673
|
}
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
// Process definition
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
var
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
674
|
+
if(/^"$/.test(text)) {
|
675
|
+
// Special case: single-character " token
|
676
|
+
if(/\S/.test(this.smartyPantsLastChar)) {
|
677
|
+
return "”";
|
678
|
+
}
|
679
|
+
return "“";
|
680
|
+
}
|
681
|
+
|
682
|
+
// Special case if the very first character is a quote
|
683
|
+
// followed by punctuation at a non-word-break. Close the quotes by brute force:
|
684
|
+
text = text.replace (/^'(?=[!"#\$\%'()*+,\-.\/:;<=>?\@\[\\]\^_`{|}~]\B)/, "’");
|
685
|
+
text = text.replace (/^"(?=[!"#\$\%'()*+,\-.\/:;<=>?\@\[\\]\^_`{|}~]\B)/, "”");
|
686
|
+
|
687
|
+
// Special case for double sets of quotes, e.g.:
|
688
|
+
// <p>He said, "'Quoted' words in a larger quote."</p>
|
689
|
+
text = text.replace(/"'(?=\w)/g, "“‘");
|
690
|
+
text = text.replace(/'"(?=\w)/g, "‘“");
|
691
|
+
|
692
|
+
// Special case for decade abbreviations (the '80s):
|
693
|
+
text = text.replace(/'(?=\d{2}s)/g, "’");
|
694
|
+
|
695
|
+
// Get most opening single quotes:
|
696
|
+
text = text.replace(/(\s| |--|&[mn]dash;|&\#8211;|&\#8212;|&\#x201[34];)'(?=\w)/g, "$1‘");
|
697
|
+
|
698
|
+
// Single closing quotes:
|
699
|
+
text = text.replace(/([^\s\[\{\(\-])'/g, "$1’");
|
700
|
+
text = text.replace(/'(?=\s|s\b)/g, "’");
|
701
|
+
|
702
|
+
// Any remaining single quotes should be opening ones:
|
703
|
+
text = text.replace(/'/g, "‘");
|
704
|
+
|
705
|
+
// Get most opening double quotes:
|
706
|
+
text = text.replace(/(\s| |--|&[mn]dash;|&\#8211;|&\#8212;|&\#x201[34];)"(?=\w)/g, "$1“");
|
707
|
+
|
708
|
+
// Double closing quotes:
|
709
|
+
text = text.replace(/([^\s\[\{\(\-])"/g, "$1”");
|
710
|
+
text = text.replace(/"(?=\s)/g, "”");
|
711
|
+
|
712
|
+
// Any remaining quotes should be opening ones.
|
713
|
+
text = text.replace(/"/ig, "“");
|
714
|
+
return text;
|
715
|
+
};
|
716
|
+
|
717
|
+
// Find and convert markdown extra definition lists into html.
|
718
|
+
Markdown.Extra.prototype.runSmartyPants = function(text) {
|
719
|
+
this.smartyPantsLastChar = '';
|
720
|
+
text = this.educatePants(text);
|
721
|
+
// Clean everything inside html tags (some of them may have been converted due to our rough html parsing)
|
722
|
+
text = text.replace(/(<([a-zA-Z1-6]+)\b([^\n>]*?)(\/)?>)/g, revertPants);
|
723
|
+
return text;
|
724
|
+
};
|
725
|
+
|
726
|
+
/******************************************************************
|
727
|
+
* Definition Lists *
|
728
|
+
******************************************************************/
|
729
|
+
|
730
|
+
// Find and convert markdown extra definition lists into html.
|
731
|
+
Markdown.Extra.prototype.definitionLists = function(text) {
|
732
|
+
var wholeList = new RegExp(
|
733
|
+
['(\\x02\\n?|\\n\\n)' ,
|
734
|
+
'(?:' ,
|
735
|
+
'(' , // $1 = whole list
|
736
|
+
'(' , // $2
|
737
|
+
'[ ]{0,3}' ,
|
738
|
+
'((?:[ \\t]*\\S.*\\n)+)', // $3 = defined term
|
739
|
+
'\\n?' ,
|
740
|
+
'[ ]{0,3}:[ ]+' , // colon starting definition
|
741
|
+
')' ,
|
742
|
+
'([\\s\\S]+?)' ,
|
743
|
+
'(' , // $4
|
744
|
+
'(?=\\0x03)' , // \z
|
745
|
+
'|' ,
|
746
|
+
'(?=' ,
|
747
|
+
'\\n{2,}' ,
|
748
|
+
'(?=\\S)' ,
|
749
|
+
'(?!' , // Negative lookahead for another term
|
750
|
+
'[ ]{0,3}' ,
|
751
|
+
'(?:\\S.*\\n)+?' , // defined term
|
752
|
+
'\\n?' ,
|
753
|
+
'[ ]{0,3}:[ ]+' , // colon starting definition
|
754
|
+
')' ,
|
755
|
+
'(?!' , // Negative lookahead for another definition
|
756
|
+
'[ ]{0,3}:[ ]+' , // colon starting definition
|
757
|
+
')' ,
|
758
|
+
')' ,
|
759
|
+
')' ,
|
760
|
+
')' ,
|
761
|
+
')'
|
762
|
+
].join(''),
|
763
|
+
'gm'
|
764
|
+
);
|
765
|
+
|
766
|
+
var self = this;
|
767
|
+
text = addAnchors(text);
|
768
|
+
|
769
|
+
text = text.replace(wholeList, function(match, pre, list) {
|
770
|
+
var result = trim(self.processDefListItems(list));
|
771
|
+
result = "<dl>\n" + result + "\n</dl>";
|
772
|
+
return pre + self.hashExtraBlock(result) + "\n\n";
|
773
|
+
});
|
774
|
+
|
775
|
+
return removeAnchors(text);
|
776
|
+
};
|
777
|
+
|
778
|
+
// Process the contents of a single definition list, splitting it
|
779
|
+
// into individual term and definition list items.
|
780
|
+
Markdown.Extra.prototype.processDefListItems = function(listStr) {
|
781
|
+
var self = this;
|
782
|
+
|
783
|
+
var dt = new RegExp(
|
784
|
+
['(\\x02\\n?|\\n\\n+)' , // leading line
|
785
|
+
'(' , // definition terms = $1
|
786
|
+
'[ ]{0,3}' , // leading whitespace
|
787
|
+
'(?![:][ ]|[ ])' , // negative lookahead for a definition
|
788
|
+
// mark (colon) or more whitespace
|
789
|
+
'(?:\\S.*\\n)+?' , // actual term (not whitespace)
|
790
|
+
')' ,
|
791
|
+
'(?=\\n?[ ]{0,3}:[ ])' // lookahead for following line feed
|
792
|
+
].join(''), // with a definition mark
|
793
|
+
'gm'
|
794
|
+
);
|
795
|
+
|
796
|
+
var dd = new RegExp(
|
797
|
+
['\\n(\\n+)?' , // leading line = $1
|
798
|
+
'(' , // marker space = $2
|
799
|
+
'[ ]{0,3}' , // whitespace before colon
|
800
|
+
'[:][ ]+' , // definition mark (colon)
|
801
|
+
')' ,
|
802
|
+
'([\\s\\S]+?)' , // definition text = $3
|
803
|
+
'(?=\\n*' , // stop at next definition mark,
|
804
|
+
'(?:' , // next term or end of text
|
805
|
+
'\\n[ ]{0,3}[:][ ]|' ,
|
806
|
+
'<dt>|\\x03' , // \z
|
807
|
+
')' ,
|
808
|
+
')'
|
809
|
+
].join(''),
|
810
|
+
'gm'
|
811
|
+
);
|
812
|
+
|
813
|
+
listStr = addAnchors(listStr);
|
814
|
+
// trim trailing blank lines:
|
815
|
+
listStr = listStr.replace(/\n{2,}(?=\\x03)/, "\n");
|
816
|
+
|
817
|
+
// Process definition terms.
|
818
|
+
listStr = listStr.replace(dt, function(match, pre, termsStr) {
|
819
|
+
var terms = trim(termsStr).split("\n");
|
820
|
+
var text = '';
|
821
|
+
for (var i = 0; i < terms.length; i++) {
|
822
|
+
var term = terms[i];
|
823
|
+
// process spans inside dt
|
824
|
+
term = convertSpans(trim(term), self);
|
825
|
+
text += "\n<dt>" + term + "</dt>";
|
826
|
+
}
|
827
|
+
return text + "\n";
|
828
|
+
});
|
829
|
+
|
830
|
+
// Process actual definitions.
|
831
|
+
listStr = listStr.replace(dd, function(match, leadingLine, markerSpace, def) {
|
832
|
+
if (leadingLine || def.match(/\n{2,}/)) {
|
833
|
+
// replace marker with the appropriate whitespace indentation
|
834
|
+
def = Array(markerSpace.length + 1).join(' ') + def;
|
835
|
+
// process markdown inside definition
|
836
|
+
// TODO?: currently doesn't apply extensions
|
837
|
+
def = outdent(def) + "\n\n";
|
838
|
+
def = "\n" + convertAll(def, self) + "\n";
|
839
|
+
} else {
|
840
|
+
// convert span-level markdown inside definition
|
841
|
+
def = rtrim(def);
|
842
|
+
def = convertSpans(outdent(def), self);
|
843
|
+
}
|
844
|
+
|
845
|
+
return "\n<dd>" + def + "</dd>\n";
|
846
|
+
});
|
847
|
+
|
848
|
+
return removeAnchors(listStr);
|
849
|
+
};
|
850
|
+
|
851
|
+
|
852
|
+
/***********************************************************
|
853
|
+
* Strikethrough *
|
854
|
+
************************************************************/
|
855
|
+
|
856
|
+
Markdown.Extra.prototype.strikethrough = function(text) {
|
857
|
+
// Pretty much duplicated from _DoItalicsAndBold
|
858
|
+
return text.replace(/([\W_]|^)~T~T(?=\S)([^\r]*?\S[\*_]*)~T~T([\W_]|$)/g,
|
859
|
+
"$1<del>$2</del>$3");
|
860
|
+
};
|
861
|
+
|
862
|
+
|
863
|
+
/***********************************************************
|
864
|
+
* New lines *
|
865
|
+
************************************************************/
|
866
|
+
|
867
|
+
Markdown.Extra.prototype.newlines = function(text) {
|
868
|
+
// We have to ignore already converted newlines and line breaks in sub-list items
|
869
|
+
return text.replace(/(<(?:br|\/li)>)?\n/g, function(wholeMatch, previousTag) {
|
870
|
+
return previousTag ? wholeMatch : " <br>\n";
|
871
|
+
});
|
872
|
+
};
|
705
873
|
|
706
874
|
})();
|