pagedown-bootstrap-rails 1.0.0 → 1.1.0

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