vendor/assets/javascripts/markdown.extra.js in pagedown-bootstrap-rails-2.0.0 vs vendor/assets/javascripts/markdown.extra.js in pagedown-bootstrap-rails-2.1.0
- old
+ new
@@ -1,706 +1,874 @@
(function () {
- // A quick way to make sure we're only keeping span-level tags when we need to.
- // This isn't supposed to be foolproof. It's just a quick way to make sure we
- // keep all span-level tags returned by a pagedown converter. It should allow
- // all span-level tags through, with or without attributes.
- var inlineTags = new RegExp(['^(<\\/?(a|abbr|acronym|applet|area|b|basefont|',
- 'bdo|big|button|cite|code|del|dfn|em|figcaption|',
- 'font|i|iframe|img|input|ins|kbd|label|map|',
- 'mark|meter|object|param|progress|q|ruby|rp|rt|s|',
- 'samp|script|select|small|span|strike|strong|',
- 'sub|sup|textarea|time|tt|u|var|wbr)[^>]*>|',
- '<(br)\\s?\\/?>)$'].join(''), 'i');
+ // A quick way to make sure we're only keeping span-level tags when we need to.
+ // This isn't supposed to be foolproof. It's just a quick way to make sure we
+ // keep all span-level tags returned by a pagedown converter. It should allow
+ // all span-level tags through, with or without attributes.
+ var inlineTags = new RegExp(['^(<\\/?(a|abbr|acronym|applet|area|b|basefont|',
+ 'bdo|big|button|cite|code|del|dfn|em|figcaption|',
+ 'font|i|iframe|img|input|ins|kbd|label|map|',
+ 'mark|meter|object|param|progress|q|ruby|rp|rt|s|',
+ 'samp|script|select|small|span|strike|strong|',
+ 'sub|sup|textarea|time|tt|u|var|wbr)[^>]*>|',
+ '<(br)\\s?\\/?>)$'].join(''), 'i');
- /******************************************************************
- * Utility Functions *
- *****************************************************************/
+ /******************************************************************
+ * Utility Functions *
+ *****************************************************************/
- // patch for ie7
- if (!Array.indexOf) {
- Array.prototype.indexOf = function(obj) {
- for (var i = 0; i < this.length; i++) {
- if (this[i] == obj) {
- return i;
- }
- }
- return -1;
- };
- }
+ // patch for ie7
+ if (!Array.indexOf) {
+ Array.prototype.indexOf = function(obj) {
+ for (var i = 0; i < this.length; i++) {
+ if (this[i] == obj) {
+ return i;
+ }
+ }
+ return -1;
+ };
+ }
- function trim(str) {
- return str.replace(/^\s+|\s+$/g, '');
- }
+ function trim(str) {
+ return str.replace(/^\s+|\s+$/g, '');
+ }
- function rtrim(str) {
- return str.replace(/\s+$/g, '');
- }
+ function rtrim(str) {
+ return str.replace(/\s+$/g, '');
+ }
- // Remove one level of indentation from text. Indent is 4 spaces.
- function outdent(text) {
- return text.replace(new RegExp('^(\\t|[ ]{1,4})', 'gm'), '');
- }
+ // Remove one level of indentation from text. Indent is 4 spaces.
+ function outdent(text) {
+ return text.replace(new RegExp('^(\\t|[ ]{1,4})', 'gm'), '');
+ }
- function contains(str, substr) {
- return str.indexOf(substr) != -1;
- }
+ function contains(str, substr) {
+ return str.indexOf(substr) != -1;
+ }
- // Sanitize html, removing tags that aren't in the whitelist
- function sanitizeHtml(html, whitelist) {
- return html.replace(/<[^>]*>?/gi, function(tag) {
- return tag.match(whitelist) ? tag : '';
- });
- }
+ // Sanitize html, removing tags that aren't in the whitelist
+ function sanitizeHtml(html, whitelist) {
+ return html.replace(/<[^>]*>?/gi, function(tag) {
+ return tag.match(whitelist) ? tag : '';
+ });
+ }
- // Merge two arrays, keeping only unique elements.
- function union(x, y) {
- var obj = {};
- for (var i = 0; i < x.length; i++)
- obj[x[i]] = x[i];
- for (i = 0; i < y.length; i++)
- obj[y[i]] = y[i];
- var res = [];
- for (var k in obj) {
- if (obj.hasOwnProperty(k))
- res.push(obj[k]);
+ // Merge two arrays, keeping only unique elements.
+ function union(x, y) {
+ var obj = {};
+ for (var i = 0; i < x.length; i++)
+ obj[x[i]] = x[i];
+ for (i = 0; i < y.length; i++)
+ obj[y[i]] = y[i];
+ var res = [];
+ for (var k in obj) {
+ if (obj.hasOwnProperty(k))
+ res.push(obj[k]);
+ }
+ return res;
}
- return res;
- }
- // JS regexes don't support \A or \Z, so we add sentinels, as Pagedown
- // does. In this case, we add the ascii codes for start of text (STX) and
- // end of text (ETX), an idea borrowed from:
- // https://github.com/tanakahisateru/js-markdown-extra
- function addAnchors(text) {
- if(text.charAt(0) != '\x02')
- text = '\x02' + text;
- if(text.charAt(text.length - 1) != '\x03')
- text = text + '\x03';
- return text;
- }
+ // JS regexes don't support \A or \Z, so we add sentinels, as Pagedown
+ // does. In this case, we add the ascii codes for start of text (STX) and
+ // end of text (ETX), an idea borrowed from:
+ // https://github.com/tanakahisateru/js-markdown-extra
+ function addAnchors(text) {
+ if(text.charAt(0) != '\x02')
+ text = '\x02' + text;
+ if(text.charAt(text.length - 1) != '\x03')
+ text = text + '\x03';
+ return text;
+ }
- // Remove STX and ETX sentinels.
- function removeAnchors(text) {
- if(text.charAt(0) == '\x02')
- text = text.substr(1);
- if(text.charAt(text.length - 1) == '\x03')
- text = text.substr(0, text.length - 1);
- return text;
- }
+ // Remove STX and ETX sentinels.
+ function removeAnchors(text) {
+ if(text.charAt(0) == '\x02')
+ text = text.substr(1);
+ if(text.charAt(text.length - 1) == '\x03')
+ text = text.substr(0, text.length - 1);
+ return text;
+ }
- // Convert markdown within an element, retaining only span-level tags
- function convertSpans(text, extra) {
- return sanitizeHtml(convertAll(text, extra), inlineTags);
- }
+ // Convert markdown within an element, retaining only span-level tags
+ function convertSpans(text, extra) {
+ return sanitizeHtml(convertAll(text, extra), inlineTags);
+ }
- // Convert internal markdown using the stock pagedown converter
- function convertAll(text, extra) {
- var result = extra.blockGamutHookCallback(text);
- // We need to perform these operations since we skip the steps in the converter
- result = unescapeSpecialChars(result);
- result = result.replace(/~D/g, "$$").replace(/~T/g, "~");
- result = extra.previousPostConversion(result);
- return result;
- }
+ // Convert internal markdown using the stock pagedown converter
+ function convertAll(text, extra) {
+ var result = extra.blockGamutHookCallback(text);
+ // We need to perform these operations since we skip the steps in the converter
+ result = unescapeSpecialChars(result);
+ result = result.replace(/~D/g, "$$").replace(/~T/g, "~");
+ result = extra.previousPostConversion(result);
+ return result;
+ }
- // Convert escaped special characters to HTML decimal entity codes.
- function processEscapes(text) {
- // Markdown extra adds two escapable characters, `:` and `|`
- // If escaped, we convert them to html entities so our
- // regexes don't recognize them. Markdown doesn't support escaping
- // the escape character, e.g. `\\`, which make this even simpler.
- return text.replace(/\\\|/g, '|').replace(/\\:/g, ':');
- }
+ // Convert escaped special characters
+ function processEscapesStep1(text) {
+ // Markdown extra adds two escapable characters, `:` and `|`
+ return text.replace(/\\\|/g, '~I').replace(/\\:/g, '~i');
+ }
+ function processEscapesStep2(text) {
+ return text.replace(/~I/g, '|').replace(/~i/g, ':');
+ }
- // Duplicated from PageDown converter
- function unescapeSpecialChars(text) {
- // Swap back in all the special characters we've hidden.
- text = text.replace(/~E(\d+)E/g, function(wholeMatch, m1) {
- var charCodeToReplace = parseInt(m1);
- return String.fromCharCode(charCodeToReplace);
- });
- return text;
- }
+ // Duplicated from PageDown converter
+ function unescapeSpecialChars(text) {
+ // Swap back in all the special characters we've hidden.
+ text = text.replace(/~E(\d+)E/g, function(wholeMatch, m1) {
+ var charCodeToReplace = parseInt(m1);
+ return String.fromCharCode(charCodeToReplace);
+ });
+ return text;
+ }
- function slugify(text) {
- return text.toLowerCase()
- .replace(/\s+/g, '-') // Replace spaces with -
- .replace(/[^\w\-]+/g, '') // Remove all non-word chars
- .replace(/\-\-+/g, '-') // Replace multiple - with single -
- .replace(/^-+/, '') // Trim - from start of text
- .replace(/-+$/, ''); // Trim - from end of text
- }
+ function slugify(text) {
+ return text.toLowerCase()
+ .replace(/\s+/g, '-') // Replace spaces with -
+ .replace(/[^\w\-]+/g, '') // Remove all non-word chars
+ .replace(/\-\-+/g, '-') // Replace multiple - with single -
+ .replace(/^-+/, '') // Trim - from start of text
+ .replace(/-+$/, ''); // Trim - from end of text
+ }
- /*****************************************************************************
- * Markdown.Extra *
- ****************************************************************************/
+ /*****************************************************************************
+ * Markdown.Extra *
+ ****************************************************************************/
- Markdown.Extra = function() {
- // For converting internal markdown (in tables for instance).
- // This is necessary since these methods are meant to be called as
- // preConversion hooks, and the Markdown converter passed to init()
- // won't convert any markdown contained in the html tags we return.
- this.converter = null;
+ Markdown.Extra = function() {
+ // For converting internal markdown (in tables for instance).
+ // This is necessary since these methods are meant to be called as
+ // preConversion hooks, and the Markdown converter passed to init()
+ // won't convert any markdown contained in the html tags we return.
+ this.converter = null;
- // Stores html blocks we generate in hooks so that
- // they're not destroyed if the user is using a sanitizing converter
- this.hashBlocks = [];
+ // Stores html blocks we generate in hooks so that
+ // they're not destroyed if the user is using a sanitizing converter
+ this.hashBlocks = [];
- // Stores footnotes
- this.footnotes = {};
- this.usedFootnotes = [];
+ // Stores footnotes
+ this.footnotes = {};
+ this.usedFootnotes = [];
- // Special attribute blocks for fenced code blocks and headers enabled.
- this.attributeBlocks = false;
+ // Special attribute blocks for fenced code blocks and headers enabled.
+ this.attributeBlocks = false;
- // Fenced code block options
- this.googleCodePrettify = false;
- this.highlightJs = false;
+ // Fenced code block options
+ this.googleCodePrettify = false;
+ this.highlightJs = false;
- // Table options
- this.tableClass = '';
+ // Table options
+ this.tableClass = '';
- this.tabWidth = 4;
- };
+ this.tabWidth = 4;
+ };
- Markdown.Extra.init = function(converter, options) {
- // Each call to init creates a new instance of Markdown.Extra so it's
- // safe to have multiple converters, with different options, on a single page
- var extra = new Markdown.Extra();
- var postNormalizationTransformations = [];
- var preBlockGamutTransformations = [];
- var postConversionTransformations = ["unHashExtraBlocks"];
+ Markdown.Extra.init = function(converter, options) {
+ // Each call to init creates a new instance of Markdown.Extra so it's
+ // safe to have multiple converters, with different options, on a single page
+ var extra = new Markdown.Extra();
+ var postNormalizationTransformations = [];
+ var preBlockGamutTransformations = [];
+ var postSpanGamutTransformations = [];
+ var postConversionTransformations = ["unHashExtraBlocks"];
- options = options || {};
- options.extensions = options.extensions || ["all"];
- if (contains(options.extensions, "all")) {
- options.extensions = ["tables", "fenced_code_gfm", "def_list", "attr_list", "footnotes"];
- }
- if (contains(options.extensions, "attr_list")) {
- postNormalizationTransformations.push("hashFcbAttributeBlocks");
- preBlockGamutTransformations.push("hashHeaderAttributeBlocks");
- postConversionTransformations.push("applyAttributeBlocks");
- extra.attributeBlocks = true;
- }
- if (contains(options.extensions, "tables")) {
- preBlockGamutTransformations.push("tables");
- }
- if (contains(options.extensions, "fenced_code_gfm")) {
- postNormalizationTransformations.push("fencedCodeBlocks");
- }
- if (contains(options.extensions, "def_list")) {
- preBlockGamutTransformations.push("definitionLists");
- }
- if (contains(options.extensions, "footnotes")) {
- postNormalizationTransformations.push("stripFootnoteDefinitions");
- preBlockGamutTransformations.push("doFootnotes");
- postConversionTransformations.push("printFootnotes");
- }
+ options = options || {};
+ options.extensions = options.extensions || ["all"];
+ if (contains(options.extensions, "all")) {
+ options.extensions = ["tables", "fenced_code_gfm", "def_list", "attr_list", "footnotes", "smartypants", "strikethrough", "newlines"];
+ }
+ preBlockGamutTransformations.push("wrapHeaders");
+ if (contains(options.extensions, "attr_list")) {
+ postNormalizationTransformations.push("hashFcbAttributeBlocks");
+ preBlockGamutTransformations.push("hashHeaderAttributeBlocks");
+ postConversionTransformations.push("applyAttributeBlocks");
+ extra.attributeBlocks = true;
+ }
+ if (contains(options.extensions, "fenced_code_gfm")) {
+ // This step will convert fcb inside list items and blockquotes
+ preBlockGamutTransformations.push("fencedCodeBlocks");
+ // This extra step is to prevent html blocks hashing and link definition/footnotes stripping inside fcb
+ postNormalizationTransformations.push("fencedCodeBlocks");
+ }
+ if (contains(options.extensions, "tables")) {
+ preBlockGamutTransformations.push("tables");
+ }
+ if (contains(options.extensions, "def_list")) {
+ preBlockGamutTransformations.push("definitionLists");
+ }
+ if (contains(options.extensions, "footnotes")) {
+ postNormalizationTransformations.push("stripFootnoteDefinitions");
+ preBlockGamutTransformations.push("doFootnotes");
+ postConversionTransformations.push("printFootnotes");
+ }
+ if (contains(options.extensions, "smartypants")) {
+ postConversionTransformations.push("runSmartyPants");
+ }
+ if (contains(options.extensions, "strikethrough")) {
+ postSpanGamutTransformations.push("strikethrough");
+ }
+ if (contains(options.extensions, "newlines")) {
+ postSpanGamutTransformations.push("newlines");
+ }
- converter.hooks.chain("postNormalization", function(text) {
- return extra.doTransform(postNormalizationTransformations, text) + '\n';
- });
+ converter.hooks.chain("postNormalization", function(text) {
+ return extra.doTransform(postNormalizationTransformations, text) + '\n';
+ });
- converter.hooks.chain("preBlockGamut", function(text, blockGamutHookCallback) {
- // Keep a reference to the block gamut callback to run recursively
- extra.blockGamutHookCallback = blockGamutHookCallback;
- text = processEscapes(text);
- return extra.doTransform(preBlockGamutTransformations, text) + '\n';
- });
+ converter.hooks.chain("preBlockGamut", function(text, blockGamutHookCallback) {
+ // Keep a reference to the block gamut callback to run recursively
+ extra.blockGamutHookCallback = blockGamutHookCallback;
+ text = processEscapesStep1(text);
+ text = extra.doTransform(preBlockGamutTransformations, text) + '\n';
+ text = processEscapesStep2(text);
+ return text;
+ });
- // Keep a reference to the hook chain running before doPostConversion to apply on hashed extra blocks
- extra.previousPostConversion = converter.hooks.postConversion;
- converter.hooks.chain("postConversion", function(text) {
- text = extra.doTransform(postConversionTransformations, text);
- // Clear state vars that may use unnecessary memory
- extra.hashBlocks = [];
- extra.footnotes = {};
- extra.usedFootnotes = [];
- return text;
- });
+ converter.hooks.chain("postSpanGamut", function(text) {
+ return extra.doTransform(postSpanGamutTransformations, text);
+ });
- if ("highlighter" in options) {
- extra.googleCodePrettify = options.highlighter === 'prettify';
- extra.highlightJs = options.highlighter === 'highlight';
- }
+ // Keep a reference to the hook chain running before doPostConversion to apply on hashed extra blocks
+ extra.previousPostConversion = converter.hooks.postConversion;
+ converter.hooks.chain("postConversion", function(text) {
+ text = extra.doTransform(postConversionTransformations, text);
+ // Clear state vars that may use unnecessary memory
+ extra.hashBlocks = [];
+ extra.footnotes = {};
+ extra.usedFootnotes = [];
+ return text;
+ });
- if ("table_class" in options) {
- extra.tableClass = options.table_class;
- }
+ if ("highlighter" in options) {
+ extra.googleCodePrettify = options.highlighter === 'prettify';
+ extra.highlightJs = options.highlighter === 'highlight';
+ }
- extra.converter = converter;
+ if ("table_class" in options) {
+ extra.tableClass = options.table_class;
+ }
- // Caller usually won't need this, but it's handy for testing.
- return extra;
- };
+ extra.converter = converter;
- // Do transformations
- Markdown.Extra.prototype.doTransform = function(transformations, text) {
- for(var i = 0; i < transformations.length; i++)
- text = this[transformations[i]](text);
- return text;
- };
+ // Caller usually won't need this, but it's handy for testing.
+ return extra;
+ };
- // Return a placeholder containing a key, which is the block's index in the
- // hashBlocks array. We wrap our output in a <p> tag here so Pagedown won't.
- Markdown.Extra.prototype.hashExtraBlock = function(block) {
- return '\n<p>~X' + (this.hashBlocks.push(block) - 1) + 'X</p>\n';
- };
- Markdown.Extra.prototype.hashExtraInline = function(block) {
- return '~X' + (this.hashBlocks.push(block) - 1) + 'X';
- };
+ // Do transformations
+ Markdown.Extra.prototype.doTransform = function(transformations, text) {
+ for(var i = 0; i < transformations.length; i++)
+ text = this[transformations[i]](text);
+ return text;
+ };
- // Replace placeholder blocks in `text` with their corresponding
- // html blocks in the hashBlocks array.
- Markdown.Extra.prototype.unHashExtraBlocks = function(text) {
- var self = this;
- function recursiveUnHash() {
- var hasHash = false;
- text = text.replace(/(?:<p>)?~X(\d+)X(?:<\/p>)?/g, function(wholeMatch, m1) {
- hasHash = true;
- var key = parseInt(m1, 10);
- return self.hashBlocks[key];
- });
- if(hasHash === true) {
+ // Return a placeholder containing a key, which is the block's index in the
+ // hashBlocks array. We wrap our output in a <p> tag here so Pagedown won't.
+ Markdown.Extra.prototype.hashExtraBlock = function(block) {
+ return '\n<p>~X' + (this.hashBlocks.push(block) - 1) + 'X</p>\n';
+ };
+ Markdown.Extra.prototype.hashExtraInline = function(block) {
+ return '~X' + (this.hashBlocks.push(block) - 1) + 'X';
+ };
+
+ // Replace placeholder blocks in `text` with their corresponding
+ // html blocks in the hashBlocks array.
+ Markdown.Extra.prototype.unHashExtraBlocks = function(text) {
+ var self = this;
+ function recursiveUnHash() {
+ var hasHash = false;
+ text = text.replace(/(?:<p>)?~X(\d+)X(?:<\/p>)?/g, function(wholeMatch, m1) {
+ hasHash = true;
+ var key = parseInt(m1, 10);
+ return self.hashBlocks[key];
+ });
+ if(hasHash === true) {
+ recursiveUnHash();
+ }
+ }
recursiveUnHash();
- }
- }
- recursiveUnHash();
- return text;
- };
+ return text;
+ };
+ // Wrap headers to make sure they won't be in def lists
+ Markdown.Extra.prototype.wrapHeaders = function(text) {
+ function wrap(text) {
+ return '\n' + text + '\n';
+ }
+ text = text.replace(/^.+[ \t]*\n=+[ \t]*\n+/gm, wrap);
+ text = text.replace(/^.+[ \t]*\n-+[ \t]*\n+/gm, wrap);
+ text = text.replace(/^\#{1,6}[ \t]*.+?[ \t]*\#*\n+/gm, wrap);
+ return text;
+ };
- /******************************************************************
- * Attribute Blocks *
- *****************************************************************/
- // Extract headers attribute blocks, move them above the element they will be
- // applied to, and hash them for later.
- Markdown.Extra.prototype.hashHeaderAttributeBlocks = function(text) {
+ /******************************************************************
+ * Attribute Blocks *
+ *****************************************************************/
+
// TODO: use sentinels. Should we just add/remove them in doConversion?
// TODO: better matches for id / class attributes
- var attrBlock = "\\{\\s*[.|#][^}]+\\}";
- var hdrAttributesA = new RegExp("^(#{1,6}.*#{0,6})\\s+(" + attrBlock + ")[ \\t]*(\\n|0x03)", "gm");
- var hdrAttributesB = new RegExp("^(.*)\\s+(" + attrBlock + ")[ \\t]*\\n" +
- "(?=[\\-|=]+\\s*(\\n|0x03))", "gm"); // underline lookahead
+ var attrBlock = "\\{[ \\t]*((?:[#.][-_:a-zA-Z0-9]+[ \\t]*)+)\\}";
+ var hdrAttributesA = new RegExp("^(#{1,6}.*#{0,6})[ \\t]+" + attrBlock + "[ \\t]*(?:\\n|0x03)", "gm");
+ var hdrAttributesB = new RegExp("^(.*)[ \\t]+" + attrBlock + "[ \\t]*\\n" +
+ "(?=[\\-|=]+\\s*(?:\\n|0x03))", "gm"); // underline lookahead
+ var fcbAttributes = new RegExp("^(```[ \\t]*[^{\\s]*)[ \\t]+" + attrBlock + "[ \\t]*\\n" +
+ "(?=([\\s\\S]*?)\\n```[ \\t]*(\\n|0x03))", "gm");
- var self = this;
- function attributeCallback(wholeMatch, pre, attr) {
- return '<p>~XX' + (self.hashBlocks.push(attr) - 1) + 'XX</p>\n' + pre + "\n";
- }
+ // Extract headers attribute blocks, move them above the element they will be
+ // applied to, and hash them for later.
+ Markdown.Extra.prototype.hashHeaderAttributeBlocks = function(text) {
- text = text.replace(hdrAttributesA, attributeCallback); // ## headers
- text = text.replace(hdrAttributesB, attributeCallback); // underline headers
- return text;
- };
+ var self = this;
+ function attributeCallback(wholeMatch, pre, attr) {
+ return '<p>~XX' + (self.hashBlocks.push(attr) - 1) + 'XX</p>\n' + pre + "\n";
+ }
- // Extract FCB attribute blocks, move them above the element they will be
- // applied to, and hash them for later.
- Markdown.Extra.prototype.hashFcbAttributeBlocks = function(text) {
- // TODO: use sentinels. Should we just add/remove them in doConversion?
- // TODO: better matches for id / class attributes
- var attrBlock = "\\{\\s*[.|#][^}]+\\}";
- var fcbAttributes = new RegExp("^(```[^{\\n]*)\\s+(" + attrBlock + ")[ \\t]*\\n" +
- "(?=([\\s\\S]*?)\\n```\\s*(\\n|0x03))", "gm");
+ text = text.replace(hdrAttributesA, attributeCallback); // ## headers
+ text = text.replace(hdrAttributesB, attributeCallback); // underline headers
+ return text;
+ };
- var self = this;
- function attributeCallback(wholeMatch, pre, attr) {
- return '<p>~XX' + (self.hashBlocks.push(attr) - 1) + 'XX</p>\n' + pre + "\n";
- }
+ // Extract FCB attribute blocks, move them above the element they will be
+ // applied to, and hash them for later.
+ Markdown.Extra.prototype.hashFcbAttributeBlocks = function(text) {
+ // TODO: use sentinels. Should we just add/remove them in doConversion?
+ // TODO: better matches for id / class attributes
- return text.replace(fcbAttributes, attributeCallback);
- };
+ var self = this;
+ function attributeCallback(wholeMatch, pre, attr) {
+ return '<p>~XX' + (self.hashBlocks.push(attr) - 1) + 'XX</p>\n' + pre + "\n";
+ }
- Markdown.Extra.prototype.applyAttributeBlocks = function(text) {
- var self = this;
- var blockRe = new RegExp('<p>~XX(\\d+)XX</p>[\\s]*' +
- '(?:<(h[1-6]|pre)(?: +class="(\\S+)")?(>[\\s\\S]*?</\\2>))', "gm");
- text = text.replace(blockRe, function(wholeMatch, k, tag, cls, rest) {
- if (!tag) // no following header or fenced code block.
- return '';
+ return text.replace(fcbAttributes, attributeCallback);
+ };
- // get attributes list from hash
- var key = parseInt(k, 10);
- var attributes = self.hashBlocks[key];
+ Markdown.Extra.prototype.applyAttributeBlocks = function(text) {
+ var self = this;
+ var blockRe = new RegExp('<p>~XX(\\d+)XX</p>[\\s]*' +
+ '(?:<(h[1-6]|pre)(?: +class="(\\S+)")?(>[\\s\\S]*?</\\2>))', "gm");
+ text = text.replace(blockRe, function(wholeMatch, k, tag, cls, rest) {
+ if (!tag) // no following header or fenced code block.
+ return '';
- // get id
- var id = attributes.match(/#[^\s{}]+/g) || [];
- var idStr = id[0] ? ' id="' + id[0].substr(1, id[0].length - 1) + '"' : '';
+ // get attributes list from hash
+ var key = parseInt(k, 10);
+ var attributes = self.hashBlocks[key];
- // get classes and merge with existing classes
- var classes = attributes.match(/\.[^\s{}]+/g) || [];
- for (var i = 0; i < classes.length; i++) // Remove leading dot
- classes[i] = classes[i].substr(1, classes[i].length - 1);
+ // get id
+ var id = attributes.match(/#[^\s#.]+/g) || [];
+ var idStr = id[0] ? ' id="' + id[0].substr(1, id[0].length - 1) + '"' : '';
- var classStr = '';
- if (cls)
- classes = union(classes, [cls]);
+ // get classes and merge with existing classes
+ var classes = attributes.match(/\.[^\s#.]+/g) || [];
+ for (var i = 0; i < classes.length; i++) // Remove leading dot
+ classes[i] = classes[i].substr(1, classes[i].length - 1);
- if (classes.length > 0)
- classStr = ' class="' + classes.join(' ') + '"';
+ var classStr = '';
+ if (cls)
+ classes = union(classes, [cls]);
- return "<" + tag + idStr + classStr + rest;
- });
+ if (classes.length > 0)
+ classStr = ' class="' + classes.join(' ') + '"';
- return text;
- };
+ return "<" + tag + idStr + classStr + rest;
+ });
- /******************************************************************
- * Tables *
- *****************************************************************/
+ return text;
+ };
- // Find and convert Markdown Extra tables into html.
- Markdown.Extra.prototype.tables = function(text) {
- var self = this;
+ /******************************************************************
+ * Tables *
+ *****************************************************************/
- var leadingPipe = new RegExp(
- ['^' ,
- '[ ]{0,3}' , // Allowed whitespace
- '[|]' , // Initial pipe
- '(.+)\\n' , // $1: Header Row
+ // Find and convert Markdown Extra tables into html.
+ Markdown.Extra.prototype.tables = function(text) {
- '[ ]{0,3}' , // Allowed whitespace
- '[|]([ ]*[-:]+[-| :]*)\\n' , // $2: Separator
+ var self = this;
- '(' , // $3: Table Body
- '(?:[ ]*[|].*\\n?)*' , // Table rows
- ')',
- '(?:\\n|$)' // Stop at final newline
- ].join(''),
- 'gm'
- );
+ var leadingPipe = new RegExp(
+ ['^' ,
+ '[ ]{0,3}' , // Allowed whitespace
+ '[|]' , // Initial pipe
+ '(.+)\\n' , // $1: Header Row
- var noLeadingPipe = new RegExp(
- ['^' ,
- '[ ]{0,3}' , // Allowed whitespace
- '(\\S.*[|].*)\\n' , // $1: Header Row
+ '[ ]{0,3}' , // Allowed whitespace
+ '[|]([ ]*[-:]+[-| :]*)\\n' , // $2: Separator
- '[ ]{0,3}' , // Allowed whitespace
- '([-:]+[ ]*[|][-| :]*)\\n' , // $2: Separator
+ '(' , // $3: Table Body
+ '(?:[ ]*[|].*\\n?)*' , // Table rows
+ ')',
+ '(?:\\n|$)' // Stop at final newline
+ ].join(''),
+ 'gm'
+ );
- '(' , // $3: Table Body
- '(?:.*[|].*\\n?)*' , // Table rows
- ')' ,
- '(?:\\n|$)' // Stop at final newline
- ].join(''),
- 'gm'
- );
+ var noLeadingPipe = new RegExp(
+ ['^' ,
+ '[ ]{0,3}' , // Allowed whitespace
+ '(\\S.*[|].*)\\n' , // $1: Header Row
- text = text.replace(leadingPipe, doTable);
- text = text.replace(noLeadingPipe, doTable);
+ '[ ]{0,3}' , // Allowed whitespace
+ '([-:]+[ ]*[|][-| :]*)\\n' , // $2: Separator
- // $1 = header, $2 = separator, $3 = body
- function doTable(match, header, separator, body, offset, string) {
- // remove any leading pipes and whitespace
- header = header.replace(/^ *[|]/m, '');
- separator = separator.replace(/^ *[|]/m, '');
- body = body.replace(/^ *[|]/gm, '');
+ '(' , // $3: Table Body
+ '(?:.*[|].*\\n?)*' , // Table rows
+ ')' ,
+ '(?:\\n|$)' // Stop at final newline
+ ].join(''),
+ 'gm'
+ );
- // remove trailing pipes and whitespace
- header = header.replace(/[|] *$/m, '');
- separator = separator.replace(/[|] *$/m, '');
- body = body.replace(/[|] *$/gm, '');
+ text = text.replace(leadingPipe, doTable);
+ text = text.replace(noLeadingPipe, doTable);
- // determine column alignments
- alignspecs = separator.split(/ *[|] */);
- align = [];
- for (var i = 0; i < alignspecs.length; i++) {
- var spec = alignspecs[i];
- if (spec.match(/^ *-+: *$/m))
- align[i] = ' style="text-align:right;"';
- else if (spec.match(/^ *:-+: *$/m))
- align[i] = ' style="text-align:center;"';
- else if (spec.match(/^ *:-+ *$/m))
- align[i] = ' style="text-align:left;"';
- else align[i] = '';
- }
+ // $1 = header, $2 = separator, $3 = body
+ function doTable(match, header, separator, body, offset, string) {
+ // remove any leading pipes and whitespace
+ header = header.replace(/^ *[|]/m, '');
+ separator = separator.replace(/^ *[|]/m, '');
+ body = body.replace(/^ *[|]/gm, '');
- // TODO: parse spans in header and rows before splitting, so that pipes
- // inside of tags are not interpreted as separators
- var headers = header.split(/ *[|] */);
- var colCount = headers.length;
+ // remove trailing pipes and whitespace
+ header = header.replace(/[|] *$/m, '');
+ separator = separator.replace(/[|] *$/m, '');
+ body = body.replace(/[|] *$/gm, '');
- // build html
- var cls = self.tableClass ? ' class="' + self.tableClass + '"' : '';
- var html = ['<table', cls, '>\n', '<thead>\n', '<tr>\n'].join('');
+ // determine column alignments
+ alignspecs = separator.split(/ *[|] */);
+ align = [];
+ for (var i = 0; i < alignspecs.length; i++) {
+ var spec = alignspecs[i];
+ if (spec.match(/^ *-+: *$/m))
+ align[i] = ' style="text-align:right;"';
+ else if (spec.match(/^ *:-+: *$/m))
+ align[i] = ' style="text-align:center;"';
+ else if (spec.match(/^ *:-+ *$/m))
+ align[i] = ' style="text-align:left;"';
+ else align[i] = '';
+ }
- // build column headers.
- for (i = 0; i < colCount; i++) {
- var headerHtml = convertSpans(trim(headers[i]), self);
- html += [" <th", align[i], ">", headerHtml, "</th>\n"].join('');
- }
- html += "</tr>\n</thead>\n";
+ // TODO: parse spans in header and rows before splitting, so that pipes
+ // inside of tags are not interpreted as separators
+ var headers = header.split(/ *[|] */);
+ var colCount = headers.length;
- // build rows
- var rows = body.split('\n');
- for (i = 0; i < rows.length; i++) {
- if (rows[i].match(/^\s*$/)) // can apply to final row
- continue;
+ // build html
+ var cls = self.tableClass ? ' class="' + self.tableClass + '"' : '';
+ var html = ['<table', cls, '>\n', '<thead>\n', '<tr>\n'].join('');
- // ensure number of rowCells matches colCount
- var rowCells = rows[i].split(/ *[|] */);
- var lenDiff = colCount - rowCells.length;
- for (var j = 0; j < lenDiff; j++)
- rowCells.push('');
+ // build column headers.
+ for (i = 0; i < colCount; i++) {
+ var headerHtml = convertSpans(trim(headers[i]), self);
+ html += [" <th", align[i], ">", headerHtml, "</th>\n"].join('');
+ }
+ html += "</tr>\n</thead>\n";
- html += "<tr>\n";
- for (j = 0; j < colCount; j++) {
- var colHtml = convertSpans(trim(rowCells[j]), self);
- html += [" <td", align[j], ">", colHtml, "</td>\n"].join('');
+ // build rows
+ var rows = body.split('\n');
+ for (i = 0; i < rows.length; i++) {
+ if (rows[i].match(/^\s*$/)) // can apply to final row
+ continue;
+
+ // ensure number of rowCells matches colCount
+ var rowCells = rows[i].split(/ *[|] */);
+ var lenDiff = colCount - rowCells.length;
+ for (var j = 0; j < lenDiff; j++)
+ rowCells.push('');
+
+ html += "<tr>\n";
+ for (j = 0; j < colCount; j++) {
+ var colHtml = convertSpans(trim(rowCells[j]), self);
+ html += [" <td", align[j], ">", colHtml, "</td>\n"].join('');
+ }
+ html += "</tr>\n";
+ }
+
+ html += "</table>\n";
+
+ // replace html with placeholder until postConversion step
+ return self.hashExtraBlock(html);
}
- html += "</tr>\n";
- }
- html += "</table>\n";
+ return text;
+ };
- // replace html with placeholder until postConversion step
- return self.hashExtraBlock(html);
- }
- return text;
- };
+ /******************************************************************
+ * Footnotes *
+ *****************************************************************/
+ // Strip footnote, store in hashes.
+ Markdown.Extra.prototype.stripFootnoteDefinitions = function(text) {
+ var self = this;
- /******************************************************************
- * Footnotes *
- *****************************************************************/
+ text = text.replace(
+ /\n[ ]{0,3}\[\^(.+?)\]\:[ \t]*\n?([\s\S]*?)\n{1,2}((?=\n[ ]{0,3}\S)|$)/g,
+ function(wholeMatch, m1, m2) {
+ m1 = slugify(m1);
+ m2 += "\n";
+ m2 = m2.replace(/^[ ]{0,3}/g, "");
+ self.footnotes[m1] = m2;
+ return "\n";
+ });
- // Strip footnote, store in hashes.
- Markdown.Extra.prototype.stripFootnoteDefinitions = function(text) {
- var self = this;
+ return text;
+ };
- text = text.replace(
- /\n[ ]{0,3}\[\^(.+?)\]\:[ \t]*\n?([\s\S]*?)\n{1,2}((?=\n[ ]{0,3}\S)|$)/g,
- function(wholeMatch, m1, m2) {
- m1 = slugify(m1);
- m2 += "\n";
- m2 = m2.replace(/^[ ]{0,3}/g, "");
- self.footnotes[m1] = m2;
- return "\n";
- });
- return text;
- };
+ // Find and convert footnotes references.
+ Markdown.Extra.prototype.doFootnotes = function(text) {
+ var self = this;
+ if(self.isConvertingFootnote === true) {
+ return text;
+ }
+ var footnoteCounter = 0;
+ text = text.replace(/\[\^(.+?)\]/g, function(wholeMatch, m1) {
+ var id = slugify(m1);
+ var footnote = self.footnotes[id];
+ if (footnote === undefined) {
+ return wholeMatch;
+ }
+ footnoteCounter++;
+ self.usedFootnotes.push(id);
+ var html = '<a href="#fn:' + id + '" id="fnref:' + id
+ + '" title="See footnote" class="footnote">' + footnoteCounter
+ + '</a>';
+ return self.hashExtraInline(html);
+ });
- // Find and convert footnotes references.
- Markdown.Extra.prototype.doFootnotes = function(text) {
- var self = this;
- if(self.isConvertingFootnote === true) {
- return text;
- }
+ return text;
+ };
- var footnoteCounter = 0;
- text = text.replace(/\[\^(.+?)\]/g, function(wholeMatch, m1) {
- var id = slugify(m1);
- var footnote = self.footnotes[id];
- if (footnote === undefined) {
- return "";
- }
- footnoteCounter++;
- self.usedFootnotes.push(id);
- var html = '<a href="#fn:' + id + '" id="fnref:' + id
- + '" title="See footnote" class="footnote">' + footnoteCounter
- + '</a>';
- return self.hashExtraInline(html);
- });
+ // Print footnotes at the end of the document
+ Markdown.Extra.prototype.printFootnotes = function(text) {
+ var self = this;
- return text;
- };
+ if (self.usedFootnotes.length === 0) {
+ return text;
+ }
- // Print footnotes at the end of the document
- Markdown.Extra.prototype.printFootnotes = function(text) {
- var self = this;
+ text += '\n\n<div class="footnotes">\n<hr>\n<ol>\n\n';
+ for(var i=0; i<self.usedFootnotes.length; i++) {
+ var id = self.usedFootnotes[i];
+ var footnote = self.footnotes[id];
+ self.isConvertingFootnote = true;
+ var formattedfootnote = convertSpans(footnote, self);
+ delete self.isConvertingFootnote;
+ text += '<li id="fn:'
+ + id
+ + '">'
+ + formattedfootnote
+ + ' <a href="#fnref:'
+ + id
+ + '" title="Return to article" class="reversefootnote">↩</a></li>\n\n';
+ }
+ text += '</ol>\n</div>';
+ return text;
+ };
- if (self.usedFootnotes.length === 0) {
- return text;
- }
- text += '\n\n<div class="footnotes">\n<hr>\n<ol>\n\n';
- for(var i=0; i<self.usedFootnotes.length; i++) {
- var id = self.usedFootnotes[i];
- var footnote = self.footnotes[id];
- self.isConvertingFootnote = true;
- var formattedfootnote = convertSpans(footnote, self);
- delete self.isConvertingFootnote;
- text += '<li id="fn:'
- + id
- + '">'
- + formattedfootnote
- + ' <a href="#fnref:'
- + id
- + '" title="Return to article" class="reversefootnote">↩</a></li>\n\n';
- }
- text += '</ol>\n</div>';
- return text;
- };
+ /******************************************************************
+ * Fenced Code Blocks (gfm) *
+ ******************************************************************/
+ // Find and convert gfm-inspired fenced code blocks into html.
+ Markdown.Extra.prototype.fencedCodeBlocks = function(text) {
+ function encodeCode(code) {
+ code = code.replace(/&/g, "&");
+ code = code.replace(/</g, "<");
+ code = code.replace(/>/g, ">");
+ // These were escaped by PageDown before postNormalization
+ code = code.replace(/~D/g, "$$");
+ code = code.replace(/~T/g, "~");
+ return code;
+ }
- /******************************************************************
- * Fenced Code Blocks (gfm) *
- ******************************************************************/
+ var self = this;
+ text = text.replace(/(?:^|\n)```[ \t]*(\S*)[ \t]*\n([\s\S]*?)\n```[ \t]*(?=\n)/g, function(match, m1, m2) {
+ var language = m1, codeblock = m2;
- // Find and convert gfm-inspired fenced code blocks into html.
- Markdown.Extra.prototype.fencedCodeBlocks = function(text) {
- function encodeCode(code) {
- code = code.replace(/&/g, "&");
- code = code.replace(/</g, "<");
- code = code.replace(/>/g, ">");
- // These were escaped by PageDown before postNormalization
- code = code.replace(/~D/g, "$$");
- code = code.replace(/~T/g, "~");
- return code;
+ // adhere to specified options
+ var preclass = self.googleCodePrettify ? ' class="prettyprint"' : '';
+ var codeclass = '';
+ if (language) {
+ if (self.googleCodePrettify || self.highlightJs) {
+ // use html5 language- class names. supported by both prettify and highlight.js
+ codeclass = ' class="language-' + language + '"';
+ } else {
+ codeclass = ' class="' + language + '"';
+ }
+ }
+
+ var html = ['<pre', preclass, '><code', codeclass, '>',
+ encodeCode(codeblock), '</code></pre>'].join('');
+
+ // replace codeblock with placeholder until postConversion step
+ return self.hashExtraBlock(html);
+ });
+
+ return text;
+ };
+
+
+ /******************************************************************
+ * SmartyPants *
+ ******************************************************************/
+
+ Markdown.Extra.prototype.educatePants = function(text) {
+ var self = this;
+ var result = '';
+ var blockOffset = 0;
+ // Here we parse HTML in a very bad manner
+ text.replace(/(?:<!--[\s\S]*?-->)|(<)([a-zA-Z1-6]+)([^\n]*?>)([\s\S]*?)(<\/\2>)/g, function(wholeMatch, m1, m2, m3, m4, m5, offset) {
+ var token = text.substring(blockOffset, offset);
+ result += self.applyPants(token);
+ self.smartyPantsLastChar = result.substring(result.length - 1);
+ blockOffset = offset + wholeMatch.length;
+ if(!m1) {
+ // Skip commentary
+ result += wholeMatch;
+ return;
+ }
+ // Skip special tags
+ if(!/code|kbd|pre|script|noscript|iframe|math|ins|del|pre/i.test(m2)) {
+ m4 = self.educatePants(m4);
+ }
+ else {
+ self.smartyPantsLastChar = m4.substring(m4.length - 1);
+ }
+ result += m1 + m2 + m3 + m4 + m5;
+ });
+ var lastToken = text.substring(blockOffset);
+ result += self.applyPants(lastToken);
+ self.smartyPantsLastChar = result.substring(result.length - 1);
+ return result;
+ };
+
+ function revertPants(wholeMatch, m1) {
+ var blockText = m1;
+ blockText = blockText.replace(/&\#8220;/g, "\"");
+ blockText = blockText.replace(/&\#8221;/g, "\"");
+ blockText = blockText.replace(/&\#8216;/g, "'");
+ blockText = blockText.replace(/&\#8217;/g, "'");
+ blockText = blockText.replace(/&\#8212;/g, "---");
+ blockText = blockText.replace(/&\#8211;/g, "--");
+ blockText = blockText.replace(/&\#8230;/g, "...");
+ return blockText;
}
- var self = this;
- text = text.replace(/(?:^|\n)```(.*)\n([\s\S]*?)\n```/g, function(match, m1, m2) {
- var language = m1, codeblock = m2;
+ Markdown.Extra.prototype.applyPants = function(text) {
+ // Dashes
+ text = text.replace(/---/g, "—").replace(/--/g, "–");
+ // Ellipses
+ text = text.replace(/\.\.\./g, "…").replace(/\.\s\.\s\./g, "…");
+ // Backticks
+ text = text.replace(/``/g, "“").replace (/''/g, "”");
- // adhere to specified options
- var preclass = self.googleCodePrettify ? ' class="prettyprint"' : '';
- var codeclass = '';
- if (language) {
- if (self.googleCodePrettify || self.highlightJs) {
- // use html5 language- class names. supported by both prettify and highlight.js
- codeclass = ' class="language-' + language + '"';
- } else {
- codeclass = ' class="' + language + '"';
+ if(/^'$/.test(text)) {
+ // Special case: single-character ' token
+ if(/\S/.test(this.smartyPantsLastChar)) {
+ return "’";
+ }
+ return "‘";
}
- }
+ if(/^"$/.test(text)) {
+ // Special case: single-character " token
+ if(/\S/.test(this.smartyPantsLastChar)) {
+ return "”";
+ }
+ return "“";
+ }
- var html = ['<pre', preclass, '><code', codeclass, '>',
- encodeCode(codeblock), '</code></pre>'].join('');
+ // Special case if the very first character is a quote
+ // followed by punctuation at a non-word-break. Close the quotes by brute force:
+ text = text.replace (/^'(?=[!"#\$\%'()*+,\-.\/:;<=>?\@\[\\]\^_`{|}~]\B)/, "’");
+ text = text.replace (/^"(?=[!"#\$\%'()*+,\-.\/:;<=>?\@\[\\]\^_`{|}~]\B)/, "”");
- // replace codeblock with placeholder until postConversion step
- return self.hashExtraBlock(html);
- });
+ // Special case for double sets of quotes, e.g.:
+ // <p>He said, "'Quoted' words in a larger quote."</p>
+ text = text.replace(/"'(?=\w)/g, "“‘");
+ text = text.replace(/'"(?=\w)/g, "‘“");
- return text;
- };
+ // Special case for decade abbreviations (the '80s):
+ text = text.replace(/'(?=\d{2}s)/g, "’");
+ // Get most opening single quotes:
+ text = text.replace(/(\s| |--|&[mn]dash;|&\#8211;|&\#8212;|&\#x201[34];)'(?=\w)/g, "$1‘");
- /******************************************************************
- * Definition Lists *
- ******************************************************************/
+ // Single closing quotes:
+ text = text.replace(/([^\s\[\{\(\-])'/g, "$1’");
+ text = text.replace(/'(?=\s|s\b)/g, "’");
- // Find and convert markdown extra definition lists into html.
- Markdown.Extra.prototype.definitionLists = function(text) {
- var wholeList = new RegExp(
- ['(\\x02\\n?|\\n\\n)' ,
- '(?:' ,
- '(' , // $1 = whole list
- '(' , // $2
- '[ ]{0,3}' ,
- '((?:[ \\t]*\\S.*\\n)+)', // $3 = defined term
- '\\n?' ,
- '[ ]{0,3}:[ ]+' , // colon starting definition
- ')' ,
- '([\\s\\S]+?)' ,
- '(' , // $4
- '(?=\\0x03)' , // \z
- '|' ,
- '(?=' ,
- '\\n{2,}' ,
- '(?=\\S)' ,
- '(?!' , // Negative lookahead for another term
- '[ ]{0,3}' ,
- '(?:\\S.*\\n)+?' , // defined term
- '\\n?' ,
- '[ ]{0,3}:[ ]+' , // colon starting definition
- ')' ,
- '(?!' , // Negative lookahead for another definition
- '[ ]{0,3}:[ ]+' , // colon starting definition
- ')' ,
- ')' ,
- ')' ,
- ')' ,
- ')'
- ].join(''),
- 'gm'
- );
+ // Any remaining single quotes should be opening ones:
+ text = text.replace(/'/g, "‘");
- var self = this;
- text = addAnchors(text);
+ // Get most opening double quotes:
+ text = text.replace(/(\s| |--|&[mn]dash;|&\#8211;|&\#8212;|&\#x201[34];)"(?=\w)/g, "$1“");
- text = text.replace(wholeList, function(match, pre, list) {
- var result = trim(self.processDefListItems(list));
- result = "<dl>\n" + result + "\n</dl>";
- return pre + self.hashExtraBlock(result) + "\n\n";
- });
+ // Double closing quotes:
+ text = text.replace(/([^\s\[\{\(\-])"/g, "$1”");
+ text = text.replace(/"(?=\s)/g, "”");
- return removeAnchors(text);
- };
+ // Any remaining quotes should be opening ones.
+ text = text.replace(/"/ig, "“");
+ return text;
+ };
- // Process the contents of a single definition list, splitting it
- // into individual term and definition list items.
- Markdown.Extra.prototype.processDefListItems = function(listStr) {
- var self = this;
+ // Find and convert markdown extra definition lists into html.
+ Markdown.Extra.prototype.runSmartyPants = function(text) {
+ this.smartyPantsLastChar = '';
+ text = this.educatePants(text);
+ // Clean everything inside html tags (some of them may have been converted due to our rough html parsing)
+ text = text.replace(/(<([a-zA-Z1-6]+)\b([^\n>]*?)(\/)?>)/g, revertPants);
+ return text;
+ };
- var dt = new RegExp(
- ['(\\x02\\n?|\\n\\n+)' , // leading line
- '(' , // definition terms = $1
- '[ ]{0,3}' , // leading whitespace
- '(?![:][ ]|[ ])' , // negative lookahead for a definition
- // mark (colon) or more whitespace
- '(?:\\S.*\\n)+?' , // actual term (not whitespace)
- ')' ,
- '(?=\\n?[ ]{0,3}:[ ])' // lookahead for following line feed
- ].join(''), // with a definition mark
- 'gm'
- );
+ /******************************************************************
+ * Definition Lists *
+ ******************************************************************/
- var dd = new RegExp(
- ['\\n(\\n+)?' , // leading line = $1
- '(' , // marker space = $2
- '[ ]{0,3}' , // whitespace before colon
- '[:][ ]+' , // definition mark (colon)
- ')' ,
- '([\\s\\S]+?)' , // definition text = $3
- '(?=\\n*' , // stop at next definition mark,
- '(?:' , // next term or end of text
- '\\n[ ]{0,3}[:][ ]|' ,
- '<dt>|\\x03' , // \z
- ')' ,
- ')'
- ].join(''),
- 'gm'
- );
+ // Find and convert markdown extra definition lists into html.
+ Markdown.Extra.prototype.definitionLists = function(text) {
+ var wholeList = new RegExp(
+ ['(\\x02\\n?|\\n\\n)' ,
+ '(?:' ,
+ '(' , // $1 = whole list
+ '(' , // $2
+ '[ ]{0,3}' ,
+ '((?:[ \\t]*\\S.*\\n)+)', // $3 = defined term
+ '\\n?' ,
+ '[ ]{0,3}:[ ]+' , // colon starting definition
+ ')' ,
+ '([\\s\\S]+?)' ,
+ '(' , // $4
+ '(?=\\0x03)' , // \z
+ '|' ,
+ '(?=' ,
+ '\\n{2,}' ,
+ '(?=\\S)' ,
+ '(?!' , // Negative lookahead for another term
+ '[ ]{0,3}' ,
+ '(?:\\S.*\\n)+?' , // defined term
+ '\\n?' ,
+ '[ ]{0,3}:[ ]+' , // colon starting definition
+ ')' ,
+ '(?!' , // Negative lookahead for another definition
+ '[ ]{0,3}:[ ]+' , // colon starting definition
+ ')' ,
+ ')' ,
+ ')' ,
+ ')' ,
+ ')'
+ ].join(''),
+ 'gm'
+ );
- listStr = addAnchors(listStr);
- // trim trailing blank lines:
- listStr = listStr.replace(/\n{2,}(?=\\x03)/, "\n");
+ var self = this;
+ text = addAnchors(text);
- // Process definition terms.
- listStr = listStr.replace(dt, function(match, pre, termsStr) {
- var terms = trim(termsStr).split("\n");
- var text = '';
- for (var i = 0; i < terms.length; i++) {
- var term = terms[i];
- // process spans inside dt
- term = convertSpans(trim(term), self);
- text += "\n<dt>" + term + "</dt>";
- }
- return text + "\n";
- });
+ text = text.replace(wholeList, function(match, pre, list) {
+ var result = trim(self.processDefListItems(list));
+ result = "<dl>\n" + result + "\n</dl>";
+ return pre + self.hashExtraBlock(result) + "\n\n";
+ });
- // Process actual definitions.
- listStr = listStr.replace(dd, function(match, leadingLine, markerSpace, def) {
- if (leadingLine || def.match(/\n{2,}/)) {
- // replace marker with the appropriate whitespace indentation
- def = Array(markerSpace.length + 1).join(' ') + def;
- // process markdown inside definition
- // TODO?: currently doesn't apply extensions
- def = outdent(def) + "\n\n";
- def = "\n" + convertAll(def, self) + "\n";
- } else {
- // convert span-level markdown inside definition
- def = rtrim(def);
- def = convertSpans(outdent(def), self);
- }
+ return removeAnchors(text);
+ };
- return "\n<dd>" + def + "</dd>\n";
- });
+ // Process the contents of a single definition list, splitting it
+ // into individual term and definition list items.
+ Markdown.Extra.prototype.processDefListItems = function(listStr) {
+ var self = this;
- return removeAnchors(listStr);
- };
+ var dt = new RegExp(
+ ['(\\x02\\n?|\\n\\n+)' , // leading line
+ '(' , // definition terms = $1
+ '[ ]{0,3}' , // leading whitespace
+ '(?![:][ ]|[ ])' , // negative lookahead for a definition
+ // mark (colon) or more whitespace
+ '(?:\\S.*\\n)+?' , // actual term (not whitespace)
+ ')' ,
+ '(?=\\n?[ ]{0,3}:[ ])' // lookahead for following line feed
+ ].join(''), // with a definition mark
+ 'gm'
+ );
+
+ var dd = new RegExp(
+ ['\\n(\\n+)?' , // leading line = $1
+ '(' , // marker space = $2
+ '[ ]{0,3}' , // whitespace before colon
+ '[:][ ]+' , // definition mark (colon)
+ ')' ,
+ '([\\s\\S]+?)' , // definition text = $3
+ '(?=\\n*' , // stop at next definition mark,
+ '(?:' , // next term or end of text
+ '\\n[ ]{0,3}[:][ ]|' ,
+ '<dt>|\\x03' , // \z
+ ')' ,
+ ')'
+ ].join(''),
+ 'gm'
+ );
+
+ listStr = addAnchors(listStr);
+ // trim trailing blank lines:
+ listStr = listStr.replace(/\n{2,}(?=\\x03)/, "\n");
+
+ // Process definition terms.
+ listStr = listStr.replace(dt, function(match, pre, termsStr) {
+ var terms = trim(termsStr).split("\n");
+ var text = '';
+ for (var i = 0; i < terms.length; i++) {
+ var term = terms[i];
+ // process spans inside dt
+ term = convertSpans(trim(term), self);
+ text += "\n<dt>" + term + "</dt>";
+ }
+ return text + "\n";
+ });
+
+ // Process actual definitions.
+ listStr = listStr.replace(dd, function(match, leadingLine, markerSpace, def) {
+ if (leadingLine || def.match(/\n{2,}/)) {
+ // replace marker with the appropriate whitespace indentation
+ def = Array(markerSpace.length + 1).join(' ') + def;
+ // process markdown inside definition
+ // TODO?: currently doesn't apply extensions
+ def = outdent(def) + "\n\n";
+ def = "\n" + convertAll(def, self) + "\n";
+ } else {
+ // convert span-level markdown inside definition
+ def = rtrim(def);
+ def = convertSpans(outdent(def), self);
+ }
+
+ return "\n<dd>" + def + "</dd>\n";
+ });
+
+ return removeAnchors(listStr);
+ };
+
+
+ /***********************************************************
+ * Strikethrough *
+ ************************************************************/
+
+ Markdown.Extra.prototype.strikethrough = function(text) {
+ // Pretty much duplicated from _DoItalicsAndBold
+ return text.replace(/([\W_]|^)~T~T(?=\S)([^\r]*?\S[\*_]*)~T~T([\W_]|$)/g,
+ "$1<del>$2</del>$3");
+ };
+
+
+ /***********************************************************
+ * New lines *
+ ************************************************************/
+
+ Markdown.Extra.prototype.newlines = function(text) {
+ // We have to ignore already converted newlines and line breaks in sub-list items
+ return text.replace(/(<(?:br|\/li)>)?\n/g, function(wholeMatch, previousTag) {
+ return previousTag ? wholeMatch : " <br>\n";
+ });
+ };
})();