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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a307a393788650a9f78969912ff53bdca22a9fae
4
- data.tar.gz: ada91b0e137e1798ae79cb0765b1b7e2d5ee7b89
3
+ metadata.gz: 277b26a1f585d221eb28ce6cc8e01398acaae592
4
+ data.tar.gz: cbbad5d6ecef19e4f261db4d075563c7b1dff47b
5
5
  SHA512:
6
- metadata.gz: ee4440e135283ea4577ca6dcb24eac207b65899c8c779868eb13c7fd98912eba62df24e660c06f762fb54f50ee197c5910f616114e6cd540ad3c6caba9c86daa
7
- data.tar.gz: 121c1e590ecf395c588b510e35dca7a7a4640463e030a549acc617b17699b134fd48a9eea253e94422535ae7dcddd4051c015b4e30f72c35f855223f8e473ea0
6
+ metadata.gz: 18c3a8fd8e31cdb726200fa825af94864883aa5fe21bcd45d5784f5dcf65695095148343c3f89c38a1b019ddbd9c6be0ae43384c31ebf3dfd0b2a204ae90e724
7
+ data.tar.gz: 0198407389f1eff8ccc38dee2529c511e0654f88821083d313a3f6358122360b5436d2643c6834879c418564375d0fbac37d690d159e83b5cfb25a67b359d5ed
@@ -1,5 +1,5 @@
1
1
  module PageDownBootstrap
2
2
  module Rails
3
- VERSION = "2.0.0"
3
+ VERSION = "2.1.0"
4
4
  end
5
5
  end
@@ -1,706 +1,874 @@
1
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]);
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
- return res;
67
- }
68
-
69
- // JS regexes don't support \A or \Z, so we add sentinels, as Pagedown
70
- // does. In this case, we add the ascii codes for start of text (STX) and
71
- // end of text (ETX), an idea borrowed from:
72
- // https://github.com/tanakahisateru/js-markdown-extra
73
- function addAnchors(text) {
74
- if(text.charAt(0) != '\x02')
75
- text = '\x02' + text;
76
- if(text.charAt(text.length - 1) != '\x03')
77
- text = text + '\x03';
78
- return text;
79
- }
80
-
81
- // Remove STX and ETX sentinels.
82
- function removeAnchors(text) {
83
- if(text.charAt(0) == '\x02')
84
- text = text.substr(1);
85
- if(text.charAt(text.length - 1) == '\x03')
86
- text = text.substr(0, text.length - 1);
87
- return text;
88
- }
89
-
90
- // Convert markdown within an element, retaining only span-level tags
91
- function convertSpans(text, extra) {
92
- return sanitizeHtml(convertAll(text, extra), inlineTags);
93
- }
94
-
95
- // Convert internal markdown using the stock pagedown converter
96
- function convertAll(text, extra) {
97
- var result = extra.blockGamutHookCallback(text);
98
- // We need to perform these operations since we skip the steps in the converter
99
- result = unescapeSpecialChars(result);
100
- result = result.replace(/~D/g, "$$").replace(/~T/g, "~");
101
- result = extra.previousPostConversion(result);
102
- return result;
103
- }
104
-
105
- // Convert escaped special characters to HTML decimal entity codes.
106
- function processEscapes(text) {
107
- // Markdown extra adds two escapable characters, `:` and `|`
108
- // If escaped, we convert them to html entities so our
109
- // regexes don't recognize them. Markdown doesn't support escaping
110
- // the escape character, e.g. `\\`, which make this even simpler.
111
- return text.replace(/\\\|/g, '&#124;').replace(/\\:/g, '&#58;');
112
- }
113
-
114
- // Duplicated from PageDown converter
115
- function unescapeSpecialChars(text) {
116
- // Swap back in all the special characters we've hidden.
117
- text = text.replace(/~E(\d+)E/g, function(wholeMatch, m1) {
118
- var charCodeToReplace = parseInt(m1);
119
- return String.fromCharCode(charCodeToReplace);
120
- });
121
- return text;
122
- }
123
-
124
- function slugify(text) {
125
- return text.toLowerCase()
126
- .replace(/\s+/g, '-') // Replace spaces with -
127
- .replace(/[^\w\-]+/g, '') // Remove all non-word chars
128
- .replace(/\-\-+/g, '-') // Replace multiple - with single -
129
- .replace(/^-+/, '') // Trim - from start of text
130
- .replace(/-+$/, ''); // Trim - from end of text
131
- }
132
-
133
- /*****************************************************************************
134
- * Markdown.Extra *
135
- ****************************************************************************/
136
-
137
- Markdown.Extra = function() {
138
- // For converting internal markdown (in tables for instance).
139
- // This is necessary since these methods are meant to be called as
140
- // preConversion hooks, and the Markdown converter passed to init()
141
- // won't convert any markdown contained in the html tags we return.
142
- this.converter = null;
143
-
144
- // Stores html blocks we generate in hooks so that
145
- // they're not destroyed if the user is using a sanitizing converter
146
- this.hashBlocks = [];
147
-
148
- // Stores footnotes
149
- this.footnotes = {};
150
- this.usedFootnotes = [];
151
-
152
- // Special attribute blocks for fenced code blocks and headers enabled.
153
- this.attributeBlocks = false;
154
-
155
- // Fenced code block options
156
- this.googleCodePrettify = false;
157
- this.highlightJs = false;
158
-
159
- // Table options
160
- this.tableClass = '';
161
-
162
- this.tabWidth = 4;
163
- };
164
-
165
- Markdown.Extra.init = function(converter, options) {
166
- // Each call to init creates a new instance of Markdown.Extra so it's
167
- // safe to have multiple converters, with different options, on a single page
168
- var extra = new Markdown.Extra();
169
- var postNormalizationTransformations = [];
170
- var preBlockGamutTransformations = [];
171
- var postConversionTransformations = ["unHashExtraBlocks"];
172
-
173
- options = options || {};
174
- options.extensions = options.extensions || ["all"];
175
- if (contains(options.extensions, "all")) {
176
- options.extensions = ["tables", "fenced_code_gfm", "def_list", "attr_list", "footnotes"];
29
+
30
+ function trim(str) {
31
+ return str.replace(/^\s+|\s+$/g, '');
177
32
  }
178
- if (contains(options.extensions, "attr_list")) {
179
- postNormalizationTransformations.push("hashFcbAttributeBlocks");
180
- preBlockGamutTransformations.push("hashHeaderAttributeBlocks");
181
- postConversionTransformations.push("applyAttributeBlocks");
182
- extra.attributeBlocks = true;
33
+
34
+ function rtrim(str) {
35
+ return str.replace(/\s+$/g, '');
183
36
  }
184
- if (contains(options.extensions, "tables")) {
185
- preBlockGamutTransformations.push("tables");
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
- if (contains(options.extensions, "fenced_code_gfm")) {
188
- postNormalizationTransformations.push("fencedCodeBlocks");
42
+
43
+ function contains(str, substr) {
44
+ return str.indexOf(substr) != -1;
189
45
  }
190
- if (contains(options.extensions, "def_list")) {
191
- preBlockGamutTransformations.push("definitionLists");
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
- if (contains(options.extensions, "footnotes")) {
194
- postNormalizationTransformations.push("stripFootnoteDefinitions");
195
- preBlockGamutTransformations.push("doFootnotes");
196
- postConversionTransformations.push("printFootnotes");
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
- 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';
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
- if ("table_class" in options) {
227
- extra.tableClass = options.table_class;
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
- 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
- }
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
- * Attribute Blocks *
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
- // 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";
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
- text = text.replace(hdrAttributesA, attributeCallback); // ## headers
292
- text = text.replace(hdrAttributesB, attributeCallback); // underline headers
293
- return text;
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 = "\\{\\s*[.|#][^}]+\\}";
302
- var fcbAttributes = new RegExp("^(```[^{\\n]*)\\s+(" + attrBlock + ")[ \\t]*\\n" +
303
- "(?=([\\s\\S]*?)\\n```\\s*(\\n|0x03))", "gm");
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
- var self = this;
306
- function attributeCallback(wholeMatch, pre, attr) {
307
- return '<p>~XX' + (self.hashBlocks.push(attr) - 1) + 'XX</p>\n' + pre + "\n";
308
- }
325
+ text = text.replace(hdrAttributesA, attributeCallback); // ## headers
326
+ text = text.replace(hdrAttributesB, attributeCallback); // underline headers
327
+ return text;
328
+ };
309
329
 
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('');
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
- html += "</table>\n";
341
+ return text.replace(fcbAttributes, attributeCallback);
342
+ };
454
343
 
455
- // replace html with placeholder until postConversion step
456
- return self.hashExtraBlock(html);
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
- return text;
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
- * Footnotes *
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
- // Strip footnote, store in hashes.
468
- Markdown.Extra.prototype.stripFootnoteDefinitions = function(text) {
469
- var self = this;
365
+ var classStr = '';
366
+ if (cls)
367
+ classes = union(classes, [cls]);
470
368
 
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
- });
369
+ if (classes.length > 0)
370
+ classStr = ' class="' + classes.join(' ') + '"';
480
371
 
481
- return text;
482
- };
372
+ return "<" + tag + idStr + classStr + rest;
373
+ });
483
374
 
375
+ return text;
376
+ };
484
377
 
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
- }
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
- 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
- }
491
+ return text;
492
+ };
517
493
 
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">&#8617;</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, "&amp;");
546
- code = code.replace(/</g, "&lt;");
547
- code = code.replace(/>/g, "&gt;");
548
- // These were escaped by PageDown before postNormalization
549
- code = code.replace(/~D/g, "$$");
550
- code = code.replace(/~T/g, "~");
551
- return code;
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">&#8617;</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, "&amp;");
578
+ code = code.replace(/</g, "&lt;");
579
+ code = code.replace(/>/g, "&gt;");
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
- 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 + '"';
659
+ Markdown.Extra.prototype.applyPants = function(text) {
660
+ // Dashes
661
+ text = text.replace(/---/g, "&#8212;").replace(/--/g, "&#8211;");
662
+ // Ellipses
663
+ text = text.replace(/\.\.\./g, "&#8230;").replace(/\.\s\.\s\./g, "&#8230;");
664
+ // Backticks
665
+ text = text.replace(/``/g, "&#8220;").replace (/''/g, "&#8221;");
666
+
667
+ if(/^'$/.test(text)) {
668
+ // Special case: single-character ' token
669
+ if(/\S/.test(this.smartyPantsLastChar)) {
670
+ return "&#8217;";
671
+ }
672
+ return "&#8216;";
567
673
  }
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
- };
674
+ if(/^"$/.test(text)) {
675
+ // Special case: single-character " token
676
+ if(/\S/.test(this.smartyPantsLastChar)) {
677
+ return "&#8221;";
678
+ }
679
+ return "&#8220;";
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)/, "&#8217;");
685
+ text = text.replace (/^"(?=[!"#\$\%'()*+,\-.\/:;<=>?\@\[\\]\^_`{|}~]\B)/, "&#8221;");
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, "&#8220;&#8216;");
690
+ text = text.replace(/'"(?=\w)/g, "&#8216;&#8220;");
691
+
692
+ // Special case for decade abbreviations (the '80s):
693
+ text = text.replace(/'(?=\d{2}s)/g, "&#8217;");
694
+
695
+ // Get most opening single quotes:
696
+ text = text.replace(/(\s|&nbsp;|--|&[mn]dash;|&\#8211;|&\#8212;|&\#x201[34];)'(?=\w)/g, "$1&#8216;");
697
+
698
+ // Single closing quotes:
699
+ text = text.replace(/([^\s\[\{\(\-])'/g, "$1&#8217;");
700
+ text = text.replace(/'(?=\s|s\b)/g, "&#8217;");
701
+
702
+ // Any remaining single quotes should be opening ones:
703
+ text = text.replace(/'/g, "&#8216;");
704
+
705
+ // Get most opening double quotes:
706
+ text = text.replace(/(\s|&nbsp;|--|&[mn]dash;|&\#8211;|&\#8212;|&\#x201[34];)"(?=\w)/g, "$1&#8220;");
707
+
708
+ // Double closing quotes:
709
+ text = text.replace(/([^\s\[\{\(\-])"/g, "$1&#8221;");
710
+ text = text.replace(/"(?=\s)/g, "&#8221;");
711
+
712
+ // Any remaining quotes should be opening ones.
713
+ text = text.replace(/"/ig, "&#8220;");
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
  })();