pagedown-bootstrap-rails 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
  })();