From 4b0ca55eb79e963523eb6c8197825e9e8ae904e2 Mon Sep 17 00:00:00 2001 From: Wu Cheng-Han Date: Mon, 4 May 2015 15:53:29 +0800 Subject: First commit, version 0.2.7 --- public/vendor/remarkable.js | 9444 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 9444 insertions(+) create mode 100755 public/vendor/remarkable.js (limited to 'public/vendor/remarkable.js') diff --git a/public/vendor/remarkable.js b/public/vendor/remarkable.js new file mode 100755 index 00000000..81b8c01b --- /dev/null +++ b/public/vendor/remarkable.js @@ -0,0 +1,9444 @@ +/*! remarkable 1.5.0 https://github.com//jonschlinkert/remarkable @license MIT */!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.Remarkable=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o`\x00-\x20]+/; +var single_quoted = /'[^']*'/; +var double_quoted = /"[^"]*"/; + +/*eslint no-spaced-func:0*/ +var attr_value = replace(/(?:unquoted|single_quoted|double_quoted)/) + ('unquoted', unquoted) + ('single_quoted', single_quoted) + ('double_quoted', double_quoted) + (); + +var attribute = replace(/(?:\s+attr_name(?:\s*=\s*attr_value)?)/) + ('attr_name', attr_name) + ('attr_value', attr_value) + (); + +var open_tag = replace(/<[A-Za-z][A-Za-z0-9]*attribute*\s*\/?>/) + ('attribute', attribute) + (); + +var close_tag = /<\/[A-Za-z][A-Za-z0-9]*\s*>/; +var comment = //; +var processing = /<[?].*?[?]>/; +var declaration = /]*>/; +var cdata = /])*\]\]>/; + +var HTML_TAG_RE = replace(/^(?:open_tag|close_tag|comment|processing|declaration|cdata)/) + ('open_tag', open_tag) + ('close_tag', close_tag) + ('comment', comment) + ('processing', processing) + ('declaration', declaration) + ('cdata', cdata) + (); + + +module.exports.HTML_TAG_RE = HTML_TAG_RE; + +},{}],4:[function(require,module,exports){ +// List of valid url schemas, accorting to commonmark spec +// http://jgm.github.io/CommonMark/spec.html#autolinks + +'use strict'; + + +module.exports = [ + 'coap', + 'doi', + 'javascript', + 'aaa', + 'aaas', + 'about', + 'acap', + 'cap', + 'cid', + 'crid', + 'data', + 'dav', + 'dict', + 'dns', + 'file', + 'ftp', + 'geo', + 'go', + 'gopher', + 'h323', + 'http', + 'https', + 'iax', + 'icap', + 'im', + 'imap', + 'info', + 'ipp', + 'iris', + 'iris.beep', + 'iris.xpc', + 'iris.xpcs', + 'iris.lwz', + 'ldap', + 'mailto', + 'mid', + 'msrp', + 'msrps', + 'mtqp', + 'mupdate', + 'news', + 'nfs', + 'ni', + 'nih', + 'nntp', + 'opaquelocktoken', + 'pop', + 'pres', + 'rtsp', + 'service', + 'session', + 'shttp', + 'sieve', + 'sip', + 'sips', + 'sms', + 'snmp', + 'soap.beep', + 'soap.beeps', + 'tag', + 'tel', + 'telnet', + 'tftp', + 'thismessage', + 'tn3270', + 'tip', + 'tv', + 'urn', + 'vemmi', + 'ws', + 'wss', + 'xcon', + 'xcon-userid', + 'xmlrpc.beep', + 'xmlrpc.beeps', + 'xmpp', + 'z39.50r', + 'z39.50s', + 'adiumxtra', + 'afp', + 'afs', + 'aim', + 'apt', + 'attachment', + 'aw', + 'beshare', + 'bitcoin', + 'bolo', + 'callto', + 'chrome', + 'chrome-extension', + 'com-eventbrite-attendee', + 'content', + 'cvs', + 'dlna-playsingle', + 'dlna-playcontainer', + 'dtn', + 'dvb', + 'ed2k', + 'facetime', + 'feed', + 'finger', + 'fish', + 'gg', + 'git', + 'gizmoproject', + 'gtalk', + 'hcp', + 'icon', + 'ipn', + 'irc', + 'irc6', + 'ircs', + 'itms', + 'jar', + 'jms', + 'keyparc', + 'lastfm', + 'ldaps', + 'magnet', + 'maps', + 'market', + 'message', + 'mms', + 'ms-help', + 'msnim', + 'mumble', + 'mvn', + 'notes', + 'oid', + 'palm', + 'paparazzi', + 'platform', + 'proxy', + 'psyc', + 'query', + 'res', + 'resource', + 'rmi', + 'rsync', + 'rtmp', + 'secondlife', + 'sftp', + 'sgn', + 'skype', + 'smb', + 'soldat', + 'spotify', + 'ssh', + 'steam', + 'svn', + 'teamspeak', + 'things', + 'udp', + 'unreal', + 'ut2004', + 'ventrilo', + 'view-source', + 'webcal', + 'wtai', + 'wyciwyg', + 'xfire', + 'xri', + 'ymsgr' +]; + +},{}],5:[function(require,module,exports){ +// Utilities +// +'use strict'; + + +function _class(obj) { return Object.prototype.toString.call(obj); } + +function isString(obj) { return _class(obj) === '[object String]'; } + +var _hasOwnProperty = Object.prototype.hasOwnProperty; + +function has(object, key) { + return object ? _hasOwnProperty.call(object, key) : false; +} + +// Merge objects +// +function assign(obj /*from1, from2, from3, ...*/) { + var sources = Array.prototype.slice.call(arguments, 1); + + sources.forEach(function (source) { + if (!source) { return; } + + if (typeof source !== 'object') { + throw new TypeError(source + 'must be object'); + } + + Object.keys(source).forEach(function (key) { + obj[key] = source[key]; + }); + }); + + return obj; +} + +//////////////////////////////////////////////////////////////////////////////// + +var UNESCAPE_MD_RE = /\\([\\!"#$%&'()*+,.\/:;<=>?@[\]^_`{|}~-])/g; + +function unescapeMd(str) { + if (str.indexOf('\\') < 0) { return str; } + return str.replace(UNESCAPE_MD_RE, '$1'); +} + +//////////////////////////////////////////////////////////////////////////////// + +function isValidEntityCode(c) { + /*eslint no-bitwise:0*/ + // broken sequence + if (c >= 0xD800 && c <= 0xDFFF) { return false; } + // never used + if (c >= 0xFDD0 && c <= 0xFDEF) { return false; } + if ((c & 0xFFFF) === 0xFFFF || (c & 0xFFFF) === 0xFFFE) { return false; } + // control codes + if (c >= 0x00 && c <= 0x08) { return false; } + if (c === 0x0B) { return false; } + if (c >= 0x0E && c <= 0x1F) { return false; } + if (c >= 0x7F && c <= 0x9F) { return false; } + // out of range + if (c > 0x10FFFF) { return false; } + return true; +} + +function fromCodePoint(c) { + /*eslint no-bitwise:0*/ + if (c > 0xffff) { + c -= 0x10000; + var surrogate1 = 0xd800 + (c >> 10), + surrogate2 = 0xdc00 + (c & 0x3ff); + + return String.fromCharCode(surrogate1, surrogate2); + } + return String.fromCharCode(c); +} + +var NAMED_ENTITY_RE = /&([a-z#][a-z0-9]{1,31});/gi; +var DIGITAL_ENTITY_TEST_RE = /^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))/i; +var entities = require('./entities'); + +function replaceEntityPattern(match, name) { + var code = 0; + + if (has(entities, name)) { + return entities[name]; + } else if (name.charCodeAt(0) === 0x23/* # */ && DIGITAL_ENTITY_TEST_RE.test(name)) { + code = name[1].toLowerCase() === 'x' ? + parseInt(name.slice(2), 16) + : + parseInt(name.slice(1), 10); + if (isValidEntityCode(code)) { + return fromCodePoint(code); + } + } + return match; +} + +function replaceEntities(str) { + if (str.indexOf('&') < 0) { return str; } + + return str.replace(NAMED_ENTITY_RE, replaceEntityPattern); +} + +//////////////////////////////////////////////////////////////////////////////// + +var HTML_ESCAPE_TEST_RE = /[&<>"]/; +var HTML_ESCAPE_REPLACE_RE = /[&<>"]/g; +var HTML_REPLACEMENTS = { + '&': '&', + '<': '<', + '>': '>', + '"': '"' +}; + +function replaceUnsafeChar(ch) { + return HTML_REPLACEMENTS[ch]; +} + +function escapeHtml(str) { + if (HTML_ESCAPE_TEST_RE.test(str)) { + return str.replace(HTML_ESCAPE_REPLACE_RE, replaceUnsafeChar); + } + return str; +} + +//////////////////////////////////////////////////////////////////////////////// + +exports.assign = assign; +exports.isString = isString; +exports.has = has; +exports.unescapeMd = unescapeMd; +exports.isValidEntityCode = isValidEntityCode; +exports.fromCodePoint = fromCodePoint; +exports.replaceEntities = replaceEntities; +exports.escapeHtml = escapeHtml; + +},{"./entities":1}],6:[function(require,module,exports){ +// Commonmark default options + +'use strict'; + + +module.exports = { + options: { + html: true, // Enable HTML tags in source + xhtmlOut: true, // Use '/' to close single tags (
) + breaks: false, // Convert '\n' in paragraphs into
+ langPrefix: 'language-', // CSS language prefix for fenced blocks + linkify: false, // autoconvert URL-like texts to links + + // Enable some language-neutral replacements + quotes beautification + typographer: false, + + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Set doubles to '«»' for Russian, '„“' for German. + quotes: '“”‘’', + + // Highlighter function. Should return escaped HTML, + // or '' if input not changed + // + // function (/*str, lang*/) { return ''; } + // + highlight: null, + + maxNesting: 20 // Internal protection, recursion limit + }, + + components: { + + core: { + rules: [ + 'block', + 'inline', + 'references', + 'abbr2' + ] + }, + + block: { + rules: [ + 'blockquote', + 'code', + 'fences', + 'heading', + 'hr', + 'htmlblock', + 'lheading', + 'list', + 'paragraph' + ] + }, + + inline: { + rules: [ + 'autolink', + 'backticks', + 'emphasis', + 'entity', + 'escape', + 'htmltag', + 'links', + 'newline', + 'text' + ] + } + } +}; + +},{}],7:[function(require,module,exports){ +// Remarkable default options + +'use strict'; + + +module.exports = { + options: { + html: false, // Enable HTML tags in source + xhtmlOut: false, // Use '/' to close single tags (
) + breaks: false, // Convert '\n' in paragraphs into
+ langPrefix: 'language-', // CSS language prefix for fenced blocks + linkify: false, // autoconvert URL-like texts to links + + // Enable some language-neutral replacements + quotes beautification + typographer: false, + + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Set doubles to '«»' for Russian, '„“' for German. + quotes: '“”‘’', + + // Highlighter function. Should return escaped HTML, + // or '' if input not changed + // + // function (/*str, lang*/) { return ''; } + // + highlight: null, + + maxNesting: 20 // Internal protection, recursion limit + }, + + components: { + + core: { + rules: [ + 'block', + 'inline', + 'references', + 'replacements', + 'linkify', + 'smartquotes', + 'references', + 'abbr2', + 'footnote_tail' + ] + }, + + block: { + rules: [ + 'blockquote', + 'code', + 'fences', + 'heading', + 'hr', + 'htmlblock', + 'lheading', + 'list', + 'paragraph', + 'table' + ] + }, + + inline: { + rules: [ + 'autolink', + 'backticks', + 'del', + 'emphasis', + 'entity', + 'escape', + 'footnote_ref', + 'htmltag', + 'links', + 'newline', + 'text' + ] + } + } +}; + +},{}],8:[function(require,module,exports){ +// Remarkable default options + +'use strict'; + + +module.exports = { + options: { + html: false, // Enable HTML tags in source + xhtmlOut: false, // Use '/' to close single tags (
) + breaks: false, // Convert '\n' in paragraphs into
+ langPrefix: 'language-', // CSS language prefix for fenced blocks + linkify: false, // autoconvert URL-like texts to links + + // Enable some language-neutral replacements + quotes beautification + typographer: false, + + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Set doubles to '«»' for Russian, '„“' for German. + quotes: '“”‘’', + + // Highlighter function. Should return escaped HTML, + // or '' if input not changed + // + // function (/*str, lang*/) { return ''; } + // + highlight: null, + + maxNesting: 20 // Internal protection, recursion limit + }, + + components: { + + // Don't restrict core/block/inline rules + core: {}, + block: {}, + inline: {} + } +}; + +},{}],9:[function(require,module,exports){ +'use strict'; + + +var replaceEntities = require('../common/utils').replaceEntities; + + +module.exports = function normalizeLink(url) { + var normalized = replaceEntities(url); + + // We don't care much about result of mailformed URIs, + // but shoud not throw exception. + try { + normalized = decodeURI(normalized); + } catch (__) {} + + return encodeURI(normalized); +}; + +},{"../common/utils":5}],10:[function(require,module,exports){ +'use strict'; + +module.exports = function normalizeReference(str) { + // use .toUpperCase() instead of .toLowerCase() + // here to avoid a conflict with Object.prototype + // members (most notably, `__proto__`) + return str.trim().replace(/\s+/g, ' ').toUpperCase(); +}; + +},{}],11:[function(require,module,exports){ +// Parse link destination +// +// on success it returns a string and updates state.pos; +// on failure it returns null +// +'use strict'; + + +var normalizeLink = require('./normalize_link'); +var unescapeMd = require('../common/utils').unescapeMd; + + +module.exports = function parseLinkDestination(state, pos) { + var code, level, link, + start = pos, + max = state.posMax; + + if (state.src.charCodeAt(pos) === 0x3C /* < */) { + pos++; + while (pos < max) { + code = state.src.charCodeAt(pos); + if (code === 0x0A /* \n */) { return false; } + if (code === 0x3E /* > */) { + link = normalizeLink(unescapeMd(state.src.slice(start + 1, pos))); + if (!state.parser.validateLink(link)) { return false; } + state.pos = pos + 1; + state.linkContent = link; + return true; + } + if (code === 0x5C /* \ */ && pos + 1 < max) { + pos += 2; + continue; + } + + pos++; + } + + // no closing '>' + return false; + } + + // this should be ... } else { ... branch + + level = 0; + while (pos < max) { + code = state.src.charCodeAt(pos); + + if (code === 0x20) { break; } + + // ascii control characters + if (code < 0x20 || code === 0x7F) { break; } + + if (code === 0x5C /* \ */ && pos + 1 < max) { + pos += 2; + continue; + } + + if (code === 0x28 /* ( */) { + level++; + if (level > 1) { break; } + } + + if (code === 0x29 /* ) */) { + level--; + if (level < 0) { break; } + } + + pos++; + } + + if (start === pos) { return false; } + + link = normalizeLink(unescapeMd(state.src.slice(start, pos))); + if (!state.parser.validateLink(link)) { return false; } + + state.linkContent = link; + state.pos = pos; + return true; +}; + +},{"../common/utils":5,"./normalize_link":9}],12:[function(require,module,exports){ +// Parse link label +// +// this function assumes that first character ("[") already matches; +// returns the end of the label +// +'use strict'; + +module.exports = function parseLinkLabel(state, start) { + var level, found, marker, + labelEnd = -1, + max = state.posMax, + oldPos = state.pos, + oldFlag = state.isInLabel; + + if (state.isInLabel) { return -1; } + + if (state.labelUnmatchedScopes) { + state.labelUnmatchedScopes--; + return -1; + } + + state.pos = start + 1; + state.isInLabel = true; + level = 1; + + while (state.pos < max) { + marker = state.src.charCodeAt(state.pos); + if (marker === 0x5B /* [ */) { + level++; + } else if (marker === 0x5D /* ] */) { + level--; + if (level === 0) { + found = true; + break; + } + } + + state.parser.skipToken(state); + } + + if (found) { + labelEnd = state.pos; + state.labelUnmatchedScopes = 0; + } else { + state.labelUnmatchedScopes = level - 1; + } + + // restore old state + state.pos = oldPos; + state.isInLabel = oldFlag; + + return labelEnd; +}; + +},{}],13:[function(require,module,exports){ +// Parse link title +// +// on success it returns a string and updates state.pos; +// on failure it returns null +// +'use strict'; + + +var unescapeMd = require('../common/utils').unescapeMd; + + +module.exports = function parseLinkTitle(state, pos) { + var code, + start = pos, + max = state.posMax, + marker = state.src.charCodeAt(pos); + + if (marker !== 0x22 /* " */ && marker !== 0x27 /* ' */ && marker !== 0x28 /* ( */) { return false; } + + pos++; + + // if opening marker is "(", switch it to closing marker ")" + if (marker === 0x28) { marker = 0x29; } + + while (pos < max) { + code = state.src.charCodeAt(pos); + if (code === marker) { + state.pos = pos + 1; + state.linkContent = unescapeMd(state.src.slice(start + 1, pos)); + return true; + } + if (code === 0x5C /* \ */ && pos + 1 < max) { + pos += 2; + continue; + } + + pos++; + } + + return false; +}; + +},{"../common/utils":5}],14:[function(require,module,exports){ +// Main perser class + +'use strict'; + + +var assign = require('./common/utils').assign; +var isString = require('./common/utils').isString; +var Renderer = require('./renderer'); +var ParserCore = require('./parser_core'); +var ParserBlock = require('./parser_block'); +var ParserInline = require('./parser_inline'); +var Ruler = require('./ruler'); + +var config = { + 'default': require('./configs/default'), + full: require('./configs/full'), + commonmark: require('./configs/commonmark') +}; + + +function StateCore(self, src, env) { + this.src = src; + this.env = env; + this.options = self.options; + this.tokens = []; + this.inlineMode = false; + + this.inline = self.inline; + this.block = self.block; + this.renderer = self.renderer; + this.typographer = self.typographer; +} + +// Main class +// +function Remarkable(presetName, options) { + if (!options) { + if (!isString(presetName)) { + options = presetName || {}; + presetName = 'default'; + } + } + + this.inline = new ParserInline(); + this.block = new ParserBlock(); + this.core = new ParserCore(); + this.renderer = new Renderer(); + this.ruler = new Ruler(); + + this.options = {}; + this.configure(config[presetName]); + + if (options) { this.set(options); } +} + + +// Set options, if you did not passed those to constructor +// +Remarkable.prototype.set = function (options) { + assign(this.options, options); +}; + + +// Batch loader for components rules states & options +// +Remarkable.prototype.configure = function (presets) { + var self = this; + + if (!presets) { throw new Error('Wrong `remarkable` preset, check name/content'); } + + if (presets.options) { self.set(presets.options); } + + if (presets.components) { + Object.keys(presets.components).forEach(function (name) { + if (presets.components[name].rules) { + self[name].ruler.enable(presets.components[name].rules, true); + } + }); + } +}; + + +// Sugar for curried plugins init: +// +// var md = new Remarkable(); +// +// md.use(plugin1) +// .use(plugin2, opts) +// .use(plugin3); +// +Remarkable.prototype.use = function (plugin, opts) { + plugin(this, opts); + return this; +}; + + +// Parse input string, returns tokens array. Modify `env` with +// definitions data. +// +Remarkable.prototype.parse = function (src, env) { + var state = new StateCore(this, src, env); + + this.core.process(state); + + return state.tokens; +}; + +// Main method that does all magic :) +// +Remarkable.prototype.render = function (src, env) { + env = env || {}; + + return this.renderer.render(this.parse(src, env), this.options, env); +}; + + +// Parse content as single string +// +Remarkable.prototype.parseInline = function (src, env) { + var state = new StateCore(this, src, env); + + state.inlineMode = true; + this.core.process(state); + + return state.tokens; +}; + +// Render single string, without wrapping it to paragraphs +// +Remarkable.prototype.renderInline = function (src, env) { + env = env || {}; + + return this.renderer.render(this.parseInline(src, env), this.options, env); +}; + + +module.exports = Remarkable; + +// Expose helpers, useful for custom renderer functions +module.exports.utils = require('./common/utils'); + +},{"./common/utils":5,"./configs/commonmark":6,"./configs/default":7,"./configs/full":8,"./parser_block":15,"./parser_core":16,"./parser_inline":17,"./renderer":18,"./ruler":19}],15:[function(require,module,exports){ +// Block parser + + +'use strict'; + + +var Ruler = require('./ruler'); +var StateBlock = require('./rules_block/state_block'); + + +var _rules = [ + [ 'code', require('./rules_block/code') ], + [ 'fences', require('./rules_block/fences'), [ 'paragraph', 'blockquote', 'list' ] ], + [ 'blockquote', require('./rules_block/blockquote'), [ 'paragraph', 'blockquote', 'list' ] ], + [ 'hr', require('./rules_block/hr'), [ 'paragraph', 'blockquote', 'list' ] ], + [ 'list', require('./rules_block/list'), [ 'paragraph', 'blockquote' ] ], + [ 'footnote', require('./rules_block/footnote'), [ 'paragraph' ] ], + [ 'heading', require('./rules_block/heading'), [ 'paragraph', 'blockquote' ] ], + [ 'lheading', require('./rules_block/lheading') ], + [ 'htmlblock', require('./rules_block/htmlblock'), [ 'paragraph', 'blockquote' ] ], + [ 'table', require('./rules_block/table'), [ 'paragraph' ] ], + [ 'deflist', require('./rules_block/deflist'), [ 'paragraph' ] ], + [ 'paragraph', require('./rules_block/paragraph') ] +]; + + +// Block Parser class +// +function ParserBlock() { + this.ruler = new Ruler(); + + for (var i = 0; i < _rules.length; i++) { + this.ruler.push(_rules[i][0], _rules[i][1], { alt: (_rules[i][2] || []).slice() }); + } +} + + +// Generate tokens for input range +// +ParserBlock.prototype.tokenize = function (state, startLine, endLine) { + var ok, i, + rules = this.ruler.getRules(''), + len = rules.length, + line = startLine, + hasEmptyLines = false; + + while (line < endLine) { + state.line = line = state.skipEmptyLines(line); + if (line >= endLine) { break; } + + // Termination condition for nested calls. + // Nested calls currently used for blockquotes & lists + if (state.tShift[line] < state.blkIndent) { break; } + + // Try all possible rules. + // On success, rule should: + // + // - update `state.line` + // - update `state.tokens` + // - return true + + for (i = 0; i < len; i++) { + ok = rules[i](state, line, endLine, false); + if (ok) { break; } + } + + // set state.tight iff we had an empty line before current tag + // i.e. latest empty line should not count + state.tight = !hasEmptyLines; + + // paragraph might "eat" one newline after it in nested lists + if (state.isEmpty(state.line - 1)) { + hasEmptyLines = true; + } + + line = state.line; + + if (line < endLine && state.isEmpty(line)) { + hasEmptyLines = true; + line++; + + // two empty lines should stop the parser in list mode + if (line < endLine && state.parentType === 'list' && state.isEmpty(line)) { break; } + state.line = line; + } + } +}; + +var TABS_SCAN_RE = /[\n\t]/g; +var NEWLINES_RE = /\r[\n\u0085]|[\u2424\u2028\u0085]/g; +var SPACES_RE = /\u00a0/g; + +ParserBlock.prototype.parse = function (src, options, env, outTokens) { + var state, lineStart = 0, lastTabPos = 0; + + if (!src) { return []; } + + // Normalize spaces + src = src.replace(SPACES_RE, ' '); + + // Normalize newlines + src = src.replace(NEWLINES_RE, '\n'); + + // Replace tabs with proper number of spaces (1..4) + if (src.indexOf('\t') >= 0) { + src = src.replace(TABS_SCAN_RE, function (match, offset) { + var result; + if (src.charCodeAt(offset) === 0x0A) { + lineStart = offset + 1; + lastTabPos = 0; + return match; + } + result = ' '.slice((offset - lineStart - lastTabPos) % 4); + lastTabPos = offset - lineStart + 1; + return result; + }); + } + + state = new StateBlock( + src, + this, + options, + env, + outTokens + ); + + this.tokenize(state, state.line, state.lineMax); +}; + + +module.exports = ParserBlock; + +},{"./ruler":19,"./rules_block/blockquote":20,"./rules_block/code":21,"./rules_block/deflist":22,"./rules_block/fences":23,"./rules_block/footnote":24,"./rules_block/heading":25,"./rules_block/hr":26,"./rules_block/htmlblock":27,"./rules_block/lheading":28,"./rules_block/list":29,"./rules_block/paragraph":30,"./rules_block/state_block":31,"./rules_block/table":32}],16:[function(require,module,exports){ +// Class of top level (`core`) rules +// +'use strict'; + + +var Ruler = require('./ruler'); + + +var _rules = [ + [ 'block', require('./rules_core/block') ], + [ 'abbr', require('./rules_core/abbr') ], + [ 'references', require('./rules_core/references') ], + [ 'inline', require('./rules_core/inline') ], + [ 'footnote_tail', require('./rules_core/footnote_tail') ], + [ 'abbr2', require('./rules_core/abbr2') ], + [ 'replacements', require('./rules_core/replacements') ], + [ 'smartquotes', require('./rules_core/smartquotes') ], + [ 'linkify', require('./rules_core/linkify') ] +]; + + +function Core() { + this.options = {}; + + this.ruler = new Ruler(); + + for (var i = 0; i < _rules.length; i++) { + this.ruler.push(_rules[i][0], _rules[i][1]); + } +} + + +Core.prototype.process = function (state) { + var i, l, rules; + + rules = this.ruler.getRules(''); + + for (i = 0, l = rules.length; i < l; i++) { + rules[i](state); + } +}; + + +module.exports = Core; + +},{"./ruler":19,"./rules_core/abbr":33,"./rules_core/abbr2":34,"./rules_core/block":35,"./rules_core/footnote_tail":36,"./rules_core/inline":37,"./rules_core/linkify":38,"./rules_core/references":39,"./rules_core/replacements":40,"./rules_core/smartquotes":41}],17:[function(require,module,exports){ +// Inline parser + +'use strict'; + + +var Ruler = require('./ruler'); +var StateInline = require('./rules_inline/state_inline'); +var replaceEntities = require('./common/utils').replaceEntities; + +//////////////////////////////////////////////////////////////////////////////// +// Parser rules + +var _rules = [ + [ 'text', require('./rules_inline/text') ], + [ 'newline', require('./rules_inline/newline') ], + [ 'escape', require('./rules_inline/escape') ], + [ 'backticks', require('./rules_inline/backticks') ], + [ 'del', require('./rules_inline/del') ], + [ 'ins', require('./rules_inline/ins') ], + [ 'mark', require('./rules_inline/mark') ], + [ 'emphasis', require('./rules_inline/emphasis') ], + [ 'sub', require('./rules_inline/sub') ], + [ 'sup', require('./rules_inline/sup') ], + [ 'links', require('./rules_inline/links') ], + [ 'footnote_inline', require('./rules_inline/footnote_inline') ], + [ 'footnote_ref', require('./rules_inline/footnote_ref') ], + [ 'autolink', require('./rules_inline/autolink') ], + [ 'htmltag', require('./rules_inline/htmltag') ], + [ 'entity', require('./rules_inline/entity') ] +]; + + +var BAD_PROTOCOLS = [ 'vbscript', 'javascript', 'file' ]; + +function validateLink(url) { + var str = url.trim().toLowerCase(); + + // Care about digital entities "javascript:alert(1)" + str = replaceEntities(str); + + if (str.indexOf(':') >= 0 && BAD_PROTOCOLS.indexOf(str.split(':')[0]) >= 0) { + return false; + } + return true; +} + +// Inline Parser class +// +function ParserInline() { + // By default CommonMark allows too much in links + // If you need to restrict it - override this with your validator. + this.validateLink = validateLink; + + this.ruler = new Ruler(); + + for (var i = 0; i < _rules.length; i++) { + this.ruler.push(_rules[i][0], _rules[i][1]); + } +} + + +// Skip single token by running all rules in validation mode; +// returns `true` if any rule reported success +// +ParserInline.prototype.skipToken = function (state) { + var i, cached_pos, pos = state.pos, + rules = this.ruler.getRules(''), + len = rules.length; + + if ((cached_pos = state.cacheGet(pos)) > 0) { + state.pos = cached_pos; + return; + } + + for (i = 0; i < len; i++) { + if (rules[i](state, true)) { + state.cacheSet(pos, state.pos); + return; + } + } + + state.pos++; + state.cacheSet(pos, state.pos); +}; + + +// Generate tokens for input range +// +ParserInline.prototype.tokenize = function (state) { + var ok, i, + rules = this.ruler.getRules(''), + len = rules.length, + end = state.posMax; + + while (state.pos < end) { + + // Try all possible rules. + // On success, rule should: + // + // - update `state.pos` + // - update `state.tokens` + // - return true + + for (i = 0; i < len; i++) { + ok = rules[i](state, false); + if (ok) { break; } + } + + if (ok) { + if (state.pos >= end) { break; } + continue; + } + + state.pending += state.src[state.pos++]; + } + + if (state.pending) { + state.pushPending(); + } +}; + + +// Parse input string. +// +ParserInline.prototype.parse = function (str, options, env, outTokens) { + var state = new StateInline(str, this, options, env, outTokens); + + this.tokenize(state); +}; + + +module.exports = ParserInline; + +},{"./common/utils":5,"./ruler":19,"./rules_inline/autolink":42,"./rules_inline/backticks":43,"./rules_inline/del":44,"./rules_inline/emphasis":45,"./rules_inline/entity":46,"./rules_inline/escape":47,"./rules_inline/footnote_inline":48,"./rules_inline/footnote_ref":49,"./rules_inline/htmltag":50,"./rules_inline/ins":51,"./rules_inline/links":52,"./rules_inline/mark":53,"./rules_inline/newline":54,"./rules_inline/state_inline":55,"./rules_inline/sub":56,"./rules_inline/sup":57,"./rules_inline/text":58}],18:[function(require,module,exports){ +'use strict'; + + +var assign = require('./common/utils').assign; +var has = require('./common/utils').has; +var unescapeMd = require('./common/utils').unescapeMd; +var replaceEntities = require('./common/utils').replaceEntities; +var escapeHtml = require('./common/utils').escapeHtml; + + +//////////////////////////////////////////////////////////////////////////////// +// Helpers + +function nextToken(tokens, idx) { + if (++idx >= tokens.length - 2) { return idx; } + if ((tokens[idx].type === 'paragraph_open' && tokens[idx].tight) && + (tokens[idx + 1].type === 'inline' && tokens[idx + 1].content.length === 0) && + (tokens[idx + 2].type === 'paragraph_close' && tokens[idx + 2].tight)) { + return nextToken(tokens, idx + 2); + } + return idx; +} + + +// check if we need to hide '\n' before next token +function getBreak(tokens, idx) { + idx = nextToken(tokens, idx); + if (idx < tokens.length && + tokens[idx].type === 'list_item_close') { + return ''; + } + + return '\n'; +} + +//////////////////////////////////////////////////////////////////////////////// + +var rules = {}; + + + +rules.blockquote_open = function (/* tokens, idx, options, env */) { + return '
\n'; +}; +rules.blockquote_close = function (tokens, idx /*, options, env */) { + return '
' + getBreak(tokens, idx); +}; + + +rules.code = function (tokens, idx /*, options, env */) { + if (tokens[idx].block) { + return '
' + escapeHtml(tokens[idx].content) + '
' + getBreak(tokens, idx); + } + + return '' + escapeHtml(tokens[idx].content) + ''; +}; + + +rules.fence = function (tokens, idx, options, env, self) { + var token = tokens[idx]; + var langClass = ''; + var langPrefix = options.langPrefix; + var langName = '', fenceName; + var highlighted; + + if (token.params) { + + // + // ```foo bar + // + // Try custom renderer "foo" first. That will simplify overwrite + // for diagrams, latex, and any other fenced block with custom look + // + + fenceName = token.params.split(/\s+/g)[0]; + + if (has(self.rules.fence_custom, fenceName)) { + return self.rules.fence_custom[fenceName](tokens, idx, options, env, self); + } + + langName = escapeHtml(replaceEntities(unescapeMd(fenceName))); + langClass = ' class="' + langPrefix + langName + '"'; + } + + if (options.highlight) { + highlighted = options.highlight(token.content, langName) || escapeHtml(token.content); + } else { + highlighted = escapeHtml(token.content); + } + + + return '
'
+        + highlighted
+        + '
' + getBreak(tokens, idx); +}; + +rules.fence_custom = {}; + +rules.heading_open = function (tokens, idx /*, options, env */) { + return ''; +}; +rules.heading_close = function (tokens, idx /*, options, env */) { + return '\n'; +}; + + +rules.hr = function (tokens, idx, options /*, env */) { + return (options.xhtmlOut ? '
' : '
') + getBreak(tokens, idx); +}; + + +rules.bullet_list_open = function (/* tokens, idx, options, env */) { + return '' + getBreak(tokens, idx); +}; +rules.list_item_open = function (/* tokens, idx, options, env */) { + return '
  • '; +}; +rules.list_item_close = function (/* tokens, idx, options, env */) { + return '
  • \n'; +}; +rules.ordered_list_open = function (tokens, idx /*, options, env */) { + var token = tokens[idx]; + return ' 1 ? ' start="' + token.order + '"' : '') + + '>\n'; +}; +rules.ordered_list_close = function (tokens, idx /*, options, env */) { + return '' + getBreak(tokens, idx); +}; + + +rules.paragraph_open = function (tokens, idx /*, options, env */) { + return tokens[idx].tight ? '' : '

    '; +}; +rules.paragraph_close = function (tokens, idx /*, options, env */) { + var addBreak = !(tokens[idx].tight && idx && tokens[idx - 1].type === 'inline' && !tokens[idx - 1].content); + return (tokens[idx].tight ? '' : '

    ') + (addBreak ? getBreak(tokens, idx) : ''); +}; + + +rules.link_open = function (tokens, idx /*, options, env */) { + var title = tokens[idx].title ? (' title="' + escapeHtml(replaceEntities(tokens[idx].title)) + '"') : ''; + return ''; +}; +rules.link_close = function (/* tokens, idx, options, env */) { + return ''; +}; + + +rules.image = function (tokens, idx, options /*, env */) { + var src = ' src="' + escapeHtml(tokens[idx].src) + '"'; + var title = tokens[idx].title ? (' title="' + escapeHtml(replaceEntities(tokens[idx].title)) + '"') : ''; + var alt = ' alt="' + (tokens[idx].alt ? escapeHtml(replaceEntities(tokens[idx].alt)) : '') + '"'; + var suffix = options.xhtmlOut ? ' /' : ''; + return ''; +}; + + +rules.table_open = function (/* tokens, idx, options, env */) { + return '\n'; +}; +rules.table_close = function (/* tokens, idx, options, env */) { + return '
    \n'; +}; +rules.thead_open = function (/* tokens, idx, options, env */) { + return '\n'; +}; +rules.thead_close = function (/* tokens, idx, options, env */) { + return '\n'; +}; +rules.tbody_open = function (/* tokens, idx, options, env */) { + return '\n'; +}; +rules.tbody_close = function (/* tokens, idx, options, env */) { + return '\n'; +}; +rules.tr_open = function (/* tokens, idx, options, env */) { + return ''; +}; +rules.tr_close = function (/* tokens, idx, options, env */) { + return '\n'; +}; +rules.th_open = function (tokens, idx /*, options, env */) { + var token = tokens[idx]; + return ''; +}; +rules.th_close = function (/* tokens, idx, options, env */) { + return ''; +}; +rules.td_open = function (tokens, idx /*, options, env */) { + var token = tokens[idx]; + return ''; +}; +rules.td_close = function (/* tokens, idx, options, env */) { + return ''; +}; + + +rules.strong_open = function (/* tokens, idx, options, env */) { + return ''; +}; +rules.strong_close = function (/* tokens, idx, options, env */) { + return ''; +}; +rules.em_open = function (/* tokens, idx, options, env */) { + return ''; +}; +rules.em_close = function (/* tokens, idx, options, env */) { + return ''; +}; + + +rules.del_open = function (/* tokens, idx, options, env */) { + return ''; +}; +rules.del_close = function (/* tokens, idx, options, env */) { + return ''; +}; + + +rules.ins_open = function (/* tokens, idx, options, env */) { + return ''; +}; +rules.ins_close = function (/* tokens, idx, options, env */) { + return ''; +}; + + +rules.mark_open = function (/* tokens, idx, options, env */) { + return ''; +}; +rules.mark_close = function (/* tokens, idx, options, env */) { + return ''; +}; + + +rules.sub = function (tokens, idx /*, options, env */) { + return '' + escapeHtml(tokens[idx].content) + ''; +}; +rules.sup = function (tokens, idx /*, options, env */) { + return '' + escapeHtml(tokens[idx].content) + ''; +}; + + +rules.hardbreak = function (tokens, idx, options /*, env */) { + return options.xhtmlOut ? '
    \n' : '
    \n'; +}; +rules.softbreak = function (tokens, idx, options /*, env */) { + return options.breaks ? (options.xhtmlOut ? '
    \n' : '
    \n') : '\n'; +}; + + +rules.text = function (tokens, idx /*, options, env */) { + return escapeHtml(tokens[idx].content); +}; + + +rules.htmlblock = function (tokens, idx /*, options, env */) { + return tokens[idx].content; +}; +rules.htmltag = function (tokens, idx /*, options, env */) { + return tokens[idx].content; +}; + + +rules.abbr_open = function (tokens, idx /*, options, env */) { + return ''; +}; +rules.abbr_close = function (/* tokens, idx, options, env */) { + return ''; +}; + + +rules.footnote_ref = function (tokens, idx) { + var n = Number(tokens[idx].id + 1).toString(); + var id = 'fnref' + n; + if (tokens[idx].subId > 0) { + id += ':' + tokens[idx].subId; + } + return '[' + n + ']'; +}; +rules.footnote_block_open = function (tokens, idx, options) { + return (options.xhtmlOut ? '
    \n' : '
    \n') + + '
    \n' + + '
      \n'; +}; +rules.footnote_block_close = function () { + return '
    \n
    \n'; +}; +rules.footnote_open = function (tokens, idx) { + var id = Number(tokens[idx].id + 1).toString(); + return '
  • '; +}; +rules.footnote_close = function () { + return '
  • \n'; +}; +rules.footnote_anchor = function (tokens, idx) { + var n = Number(tokens[idx].id + 1).toString(); + var id = 'fnref' + n; + if (tokens[idx].subId > 0) { + id += ':' + tokens[idx].subId; + } + return ' '; +}; + + +rules.dl_open = function() { + return '
    \n'; +}; +rules.dt_open = function() { + return '
    '; +}; +rules.dd_open = function() { + return '
    '; +}; +rules.dl_close = function() { + return '
    \n'; +}; +rules.dt_close = function() { + return '\n'; +}; +rules.dd_close = function() { + return '\n'; +}; + + +// Renderer class +function Renderer() { + // Clone rules object to allow local modifications + this.rules = assign({}, rules); + // exported helper, for custom rules only + this.getBreak = getBreak; +} + + +Renderer.prototype.renderInline = function (tokens, options, env) { + var result = '', + _rules = this.rules; + + for (var i = 0, len = tokens.length; i < len; i++) { + result += _rules[tokens[i].type](tokens, i, options, env, this); + } + + return result; +}; + + +Renderer.prototype.render = function (tokens, options, env) { + var i, len, + result = '', + _rules = this.rules; + + for (i = 0, len = tokens.length; i < len; i++) { + if (tokens[i].type === 'inline') { + result += this.renderInline(tokens[i].children, options, env); + } else { + result += _rules[tokens[i].type](tokens, i, options, env, this); + } + } + + return result; +}; + +module.exports = Renderer; + +},{"./common/utils":5}],19:[function(require,module,exports){ +// Ruler is helper class to build responsibility chains from parse rules. +// It allows: +// +// - easy stack rules chains +// - getting main chain and named chains content (as arrays of functions) +// +'use strict'; + + +//////////////////////////////////////////////////////////////////////////////// + +function Ruler() { + // List of added rules. Each element is: + // + // { + // name: XXX, + // enabled: Boolean, + // fn: Function(), + // alt: [ name2, name3 ] + // } + // + this.__rules__ = []; + + // Cached rule chains. + // + // First level - chain name, '' for default. + // Second level - diginal anchor for fast filtering by charcodes. + // + this.__cache__ = null; +} + +//////////////////////////////////////////////////////////////////////////////// +// Helper methods, should not be used directly + + +// Find rule index by name +// +Ruler.prototype.__find__ = function (name) { + for (var i = 0; i < this.__rules__.length; i++) { + if (this.__rules__[i].name === name) { + return i; + } + } + return -1; +}; + + +// Build rules lookup cache +// +Ruler.prototype.__compile__ = function () { + var self = this; + var chains = [ '' ]; + + // collect unique names + self.__rules__.forEach(function (rule) { + if (!rule.enabled) { return; } + + rule.alt.forEach(function (altName) { + if (chains.indexOf(altName) < 0) { + chains.push(altName); + } + }); + }); + + self.__cache__ = {}; + + chains.forEach(function (chain) { + self.__cache__[chain] = []; + self.__rules__.forEach(function (rule) { + if (!rule.enabled) { return; } + + if (chain && rule.alt.indexOf(chain) < 0) { return; } + + self.__cache__[chain].push(rule.fn); + }); + }); +}; + + +//////////////////////////////////////////////////////////////////////////////// +// Public methods + + +// Replace rule function +// +Ruler.prototype.at = function (name, fn, options) { + var index = this.__find__(name); + var opt = options || {}; + + if (index === -1) { throw new Error('Parser rule not found: ' + name); } + + this.__rules__[index].fn = fn; + this.__rules__[index].alt = opt.alt || []; + this.__cache__ = null; +}; + + +// Add rule to chain before one with given name. +// +Ruler.prototype.before = function (beforeName, ruleName, fn, options) { + var index = this.__find__(beforeName); + var opt = options || {}; + + if (index === -1) { throw new Error('Parser rule not found: ' + beforeName); } + + this.__rules__.splice(index, 0, { + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [] + }); + + this.__cache__ = null; +}; + + +// Add rule to chain after one with given name. +// +Ruler.prototype.after = function (afterName, ruleName, fn, options) { + var index = this.__find__(afterName); + var opt = options || {}; + + if (index === -1) { throw new Error('Parser rule not found: ' + afterName); } + + this.__rules__.splice(index + 1, 0, { + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [] + }); + + this.__cache__ = null; +}; + +// Add rule to the end of chain. +// +Ruler.prototype.push = function (ruleName, fn, options) { + var opt = options || {}; + + this.__rules__.push({ + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [] + }); + + this.__cache__ = null; +}; + + +// Enable list of rules by names. If `strict` is true, then all non listed +// rules will be disabled. +// +Ruler.prototype.enable = function (list, strict) { + if (!Array.isArray(list)) { + list = [ list ]; + } + + // In strict mode disable all existing rules first + if (strict) { + this.__rules__.forEach(function (rule) { + rule.enabled = false; + }); + } + + // Search by name and enable + list.forEach(function (name) { + var idx = this.__find__(name); + + if (idx < 0) { throw new Error('Rules manager: invalid rule name ' + name); } + this.__rules__[idx].enabled = true; + + }, this); + + this.__cache__ = null; +}; + + +// Disable list of rules by names. +// +Ruler.prototype.disable = function (list) { + if (!Array.isArray(list)) { + list = [ list ]; + } + + // Search by name and disable + list.forEach(function (name) { + var idx = this.__find__(name); + + if (idx < 0) { throw new Error('Rules manager: invalid rule name ' + name); } + this.__rules__[idx].enabled = false; + + }, this); + + this.__cache__ = null; +}; + + +// Get rules list as array of functions. +// +Ruler.prototype.getRules = function (chainName) { + if (this.__cache__ === null) { + this.__compile__(); + } + + return this.__cache__[chainName]; +}; + +module.exports = Ruler; + +},{}],20:[function(require,module,exports){ +// Block quotes + +'use strict'; + + +module.exports = function blockquote(state, startLine, endLine, silent) { + var nextLine, lastLineEmpty, oldTShift, oldBMarks, oldIndent, oldParentType, lines, + terminatorRules, + i, l, terminate, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + if (pos > max) { return false; } + + // check the block quote marker + if (state.src.charCodeAt(pos++) !== 0x3E/* > */) { return false; } + + if (state.level >= state.options.maxNesting) { return false; } + + // we know that it's going to be a valid blockquote, + // so no point trying to find the end of it in silent mode + if (silent) { return true; } + + // skip one optional space after '>' + if (state.src.charCodeAt(pos) === 0x20) { pos++; } + + oldIndent = state.blkIndent; + state.blkIndent = 0; + + oldBMarks = [ state.bMarks[startLine] ]; + state.bMarks[startLine] = pos; + + // check if we have an empty blockquote + pos = pos < max ? state.skipSpaces(pos) : pos; + lastLineEmpty = pos >= max; + + oldTShift = [ state.tShift[startLine] ]; + state.tShift[startLine] = pos - state.bMarks[startLine]; + + terminatorRules = state.parser.ruler.getRules('blockquote'); + + // Search the end of the block + // + // Block ends with either: + // 1. an empty line outside: + // ``` + // > test + // + // ``` + // 2. an empty line inside: + // ``` + // > + // test + // ``` + // 3. another tag + // ``` + // > test + // - - - + // ``` + for (nextLine = startLine + 1; nextLine < endLine; nextLine++) { + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + + if (pos >= max) { + // Case 1: line is not inside the blockquote, and this line is empty. + break; + } + + if (state.src.charCodeAt(pos++) === 0x3E/* > */) { + // This line is inside the blockquote. + + // skip one optional space after '>' + if (state.src.charCodeAt(pos) === 0x20) { pos++; } + + oldBMarks.push(state.bMarks[nextLine]); + state.bMarks[nextLine] = pos; + + pos = pos < max ? state.skipSpaces(pos) : pos; + lastLineEmpty = pos >= max; + + oldTShift.push(state.tShift[nextLine]); + state.tShift[nextLine] = pos - state.bMarks[nextLine]; + continue; + } + + // Case 2: line is not inside the blockquote, and the last line was empty. + if (lastLineEmpty) { break; } + + // Case 3: another tag found. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { break; } + + oldBMarks.push(state.bMarks[nextLine]); + oldTShift.push(state.tShift[nextLine]); + + // A negative number means that this is a paragraph continuation; + // + // Any negative number will do the job here, but it's better for it + // to be large enough to make any bugs obvious. + state.tShift[nextLine] = -1337; + } + + oldParentType = state.parentType; + state.parentType = 'blockquote'; + state.tokens.push({ + type: 'blockquote_open', + lines: lines = [ startLine, 0 ], + level: state.level++ + }); + state.parser.tokenize(state, startLine, nextLine); + state.tokens.push({ + type: 'blockquote_close', + level: --state.level + }); + state.parentType = oldParentType; + lines[1] = state.line; + + // Restore original tShift; this might not be necessary since the parser + // has already been here, but just to make sure we can do that. + for (i = 0; i < oldTShift.length; i++) { + state.bMarks[i + startLine] = oldBMarks[i]; + state.tShift[i + startLine] = oldTShift[i]; + } + state.blkIndent = oldIndent; + + return true; +}; + +},{}],21:[function(require,module,exports){ +// Code block (4 spaces padded) + +'use strict'; + + +module.exports = function code(state, startLine, endLine/*, silent*/) { + var nextLine, last; + + if (state.tShift[startLine] - state.blkIndent < 4) { return false; } + + last = nextLine = startLine + 1; + + while (nextLine < endLine) { + if (state.isEmpty(nextLine)) { + nextLine++; + continue; + } + if (state.tShift[nextLine] - state.blkIndent >= 4) { + nextLine++; + last = nextLine; + continue; + } + break; + } + + state.line = nextLine; + state.tokens.push({ + type: 'code', + content: state.getLines(startLine, last, 4 + state.blkIndent, true), + block: true, + lines: [ startLine, state.line ], + level: state.level + }); + + return true; +}; + +},{}],22:[function(require,module,exports){ +// Definition lists + +'use strict'; + + +// Search `[:~][\n ]`, returns next pos after marker on success +// or -1 on fail. +function skipMarker(state, line) { + var pos, marker, + start = state.bMarks[line] + state.tShift[line], + max = state.eMarks[line]; + + if (start >= max) { return -1; } + + // Check bullet + marker = state.src.charCodeAt(start++); + if (marker !== 0x7E/* ~ */ && marker !== 0x3A/* : */) { return -1; } + + pos = state.skipSpaces(start); + + // require space after ":" + if (start === pos) { return -1; } + + // no empty definitions, e.g. " : " + if (pos >= max) { return -1; } + + return pos; +} + +function markTightParagraphs(state, idx) { + var i, l, + level = state.level + 2; + + for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) { + if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') { + state.tokens[i + 2].tight = true; + state.tokens[i].tight = true; + i += 2; + } + } +} + +module.exports = function deflist(state, startLine, endLine, silent) { + var contentStart, + ddLine, + dtLine, + itemLines, + listLines, + listTokIdx, + nextLine, + oldIndent, + oldDDIndent, + oldParentType, + oldTShift, + oldTight, + prevEmptyEnd, + tight; + + if (silent) { + // quirk: validation mode validates a dd block only, not a whole deflist + if (state.ddIndent < 0) { return false; } + return skipMarker(state, startLine) >= 0; + } + + nextLine = startLine + 1; + if (state.isEmpty(nextLine)) { + if (++nextLine > endLine) { return false; } + } + + if (state.tShift[nextLine] < state.blkIndent) { return false; } + contentStart = skipMarker(state, nextLine); + if (contentStart < 0) { return false; } + + if (state.level >= state.options.maxNesting) { return false; } + + // Start list + listTokIdx = state.tokens.length; + + state.tokens.push({ + type: 'dl_open', + lines: listLines = [ startLine, 0 ], + level: state.level++ + }); + + // + // Iterate list items + // + + dtLine = startLine; + ddLine = nextLine; + + // One definition list can contain multiple DTs, + // and one DT can be followed by multiple DDs. + // + // Thus, there is two loops here, and label is + // needed to break out of the second one + // + /*eslint no-labels:0,block-scoped-var:0*/ + OUTER: + for (;;) { + tight = true; + prevEmptyEnd = false; + + state.tokens.push({ + type: 'dt_open', + lines: [ dtLine, dtLine ], + level: state.level++ + }); + state.tokens.push({ + type: 'inline', + content: state.getLines(dtLine, dtLine + 1, state.blkIndent, false).trim(), + level: state.level + 1, + lines: [ dtLine, dtLine ], + children: [] + }); + state.tokens.push({ + type: 'dt_close', + level: --state.level + }); + + for (;;) { + state.tokens.push({ + type: 'dd_open', + lines: itemLines = [ nextLine, 0 ], + level: state.level++ + }); + + oldTight = state.tight; + oldDDIndent = state.ddIndent; + oldIndent = state.blkIndent; + oldTShift = state.tShift[ddLine]; + oldParentType = state.parentType; + state.blkIndent = state.ddIndent = state.tShift[ddLine] + 2; + state.tShift[ddLine] = contentStart - state.bMarks[ddLine]; + state.tight = true; + state.parentType = 'deflist'; + + state.parser.tokenize(state, ddLine, endLine, true); + + // If any of list item is tight, mark list as tight + if (!state.tight || prevEmptyEnd) { + tight = false; + } + // Item become loose if finish with empty line, + // but we should filter last element, because it means list finish + prevEmptyEnd = (state.line - ddLine) > 1 && state.isEmpty(state.line - 1); + + state.tShift[ddLine] = oldTShift; + state.tight = oldTight; + state.parentType = oldParentType; + state.blkIndent = oldIndent; + state.ddIndent = oldDDIndent; + + state.tokens.push({ + type: 'dd_close', + level: --state.level + }); + + itemLines[1] = nextLine = state.line; + + if (nextLine >= endLine) { break OUTER; } + + if (state.tShift[nextLine] < state.blkIndent) { break OUTER; } + contentStart = skipMarker(state, nextLine); + if (contentStart < 0) { break; } + + ddLine = nextLine; + + // go to the next loop iteration: + // insert DD tag and repeat checking + } + + if (nextLine >= endLine) { break; } + dtLine = nextLine; + + if (state.isEmpty(dtLine)) { break; } + if (state.tShift[dtLine] < state.blkIndent) { break; } + + ddLine = dtLine + 1; + if (ddLine >= endLine) { break; } + if (state.isEmpty(ddLine)) { ddLine++; } + if (ddLine >= endLine) { break; } + + if (state.tShift[ddLine] < state.blkIndent) { break; } + contentStart = skipMarker(state, ddLine); + if (contentStart < 0) { break; } + + // go to the next loop iteration: + // insert DT and DD tags and repeat checking + } + + // Finilize list + state.tokens.push({ + type: 'dl_close', + level: --state.level + }); + listLines[1] = nextLine; + + state.line = nextLine; + + // mark paragraphs tight if needed + if (tight) { + markTightParagraphs(state, listTokIdx); + } + + return true; +}; + +},{}],23:[function(require,module,exports){ +// fences (``` lang, ~~~ lang) + +'use strict'; + + +module.exports = function fences(state, startLine, endLine, silent) { + var marker, len, params, nextLine, mem, + haveEndMarker = false, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + if (pos + 3 > max) { return false; } + + marker = state.src.charCodeAt(pos); + + if (marker !== 0x7E/* ~ */ && marker !== 0x60 /* ` */) { + return false; + } + + // scan marker length + mem = pos; + pos = state.skipChars(pos, marker); + + len = pos - mem; + + if (len < 3) { return false; } + + params = state.src.slice(pos, max).trim(); + + if (params.indexOf('`') >= 0) { return false; } + + // Since start is found, we can report success here in validation mode + if (silent) { return true; } + + // search end of block + nextLine = startLine; + + for (;;) { + nextLine++; + if (nextLine >= endLine) { + // unclosed block should be autoclosed by end of document. + // also block seems to be autoclosed by end of parent + break; + } + + pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + + if (pos < max && state.tShift[nextLine] < state.blkIndent) { + // non-empty line with negative indent should stop the list: + // - ``` + // test + break; + } + + if (state.src.charCodeAt(pos) !== marker) { continue; } + + if (state.tShift[nextLine] - state.blkIndent >= 4) { + // closing fence should be indented less than 4 spaces + continue; + } + + pos = state.skipChars(pos, marker); + + // closing code fence must be at least as long as the opening one + if (pos - mem < len) { continue; } + + // make sure tail has spaces only + pos = state.skipSpaces(pos); + + if (pos < max) { continue; } + + haveEndMarker = true; + // found! + break; + } + + // If a fence has heading spaces, they should be removed from its inner block + len = state.tShift[startLine]; + + state.line = nextLine + (haveEndMarker ? 1 : 0); + state.tokens.push({ + type: 'fence', + params: params, + content: state.getLines(startLine + 1, nextLine, len, true), + lines: [ startLine, state.line ], + level: state.level + }); + + return true; +}; + +},{}],24:[function(require,module,exports){ +// Process footnote reference list + +'use strict'; + + +module.exports = function footnote(state, startLine, endLine, silent) { + var oldBMark, oldTShift, oldParentType, pos, label, + start = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // line should be at least 5 chars - "[^x]:" + if (start + 4 > max) { return false; } + + if (state.src.charCodeAt(start) !== 0x5B/* [ */) { return false; } + if (state.src.charCodeAt(start + 1) !== 0x5E/* ^ */) { return false; } + if (state.level >= state.options.maxNesting) { return false; } + + for (pos = start + 2; pos < max; pos++) { + if (state.src.charCodeAt(pos) === 0x20) { return false; } + if (state.src.charCodeAt(pos) === 0x5D /* ] */) { + break; + } + } + + if (pos === start + 2) { return false; } // no empty footnote labels + if (pos + 1 >= max || state.src.charCodeAt(++pos) !== 0x3A /* : */) { return false; } + if (silent) { return true; } + pos++; + + if (!state.env.footnotes) { state.env.footnotes = {}; } + if (!state.env.footnotes.refs) { state.env.footnotes.refs = {}; } + label = state.src.slice(start + 2, pos - 2); + state.env.footnotes.refs[':' + label] = -1; + + state.tokens.push({ + type: 'footnote_reference_open', + label: label, + level: state.level++ + }); + + oldBMark = state.bMarks[startLine]; + oldTShift = state.tShift[startLine]; + oldParentType = state.parentType; + state.tShift[startLine] = state.skipSpaces(pos) - pos; + state.bMarks[startLine] = pos; + state.blkIndent += 4; + state.parentType = 'footnote'; + + if (state.tShift[startLine] < state.blkIndent) { + state.tShift[startLine] += state.blkIndent; + state.bMarks[startLine] -= state.blkIndent; + } + + state.parser.tokenize(state, startLine, endLine, true); + + state.parentType = oldParentType; + state.blkIndent -= 4; + state.tShift[startLine] = oldTShift; + state.bMarks[startLine] = oldBMark; + + state.tokens.push({ + type: 'footnote_reference_close', + level: --state.level + }); + + return true; +}; + +},{}],25:[function(require,module,exports){ +// heading (#, ##, ...) + +'use strict'; + + +module.exports = function heading(state, startLine, endLine, silent) { + var ch, level, tmp, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + if (pos >= max) { return false; } + + ch = state.src.charCodeAt(pos); + + if (ch !== 0x23/* # */ || pos >= max) { return false; } + + // count heading level + level = 1; + ch = state.src.charCodeAt(++pos); + while (ch === 0x23/* # */ && pos < max && level <= 6) { + level++; + ch = state.src.charCodeAt(++pos); + } + + if (level > 6 || (pos < max && ch !== 0x20/* space */)) { return false; } + + if (silent) { return true; } + + // Let's cut tails like ' ### ' from the end of string + + max = state.skipCharsBack(max, 0x20, pos); // space + tmp = state.skipCharsBack(max, 0x23, pos); // # + if (tmp > pos && state.src.charCodeAt(tmp - 1) === 0x20/* space */) { + max = tmp; + } + + state.line = startLine + 1; + + state.tokens.push({ type: 'heading_open', + hLevel: level, + lines: [ startLine, state.line ], + level: state.level + }); + + // only if header is not empty + if (pos < max) { + state.tokens.push({ + type: 'inline', + content: state.src.slice(pos, max).trim(), + level: state.level + 1, + lines: [ startLine, state.line ], + children: [] + }); + } + state.tokens.push({ type: 'heading_close', hLevel: level, level: state.level }); + + return true; +}; + +},{}],26:[function(require,module,exports){ +// Horizontal rule + +'use strict'; + + +module.exports = function hr(state, startLine, endLine, silent) { + var marker, cnt, ch, + pos = state.bMarks[startLine], + max = state.eMarks[startLine]; + + pos += state.tShift[startLine]; + + if (pos > max) { return false; } + + marker = state.src.charCodeAt(pos++); + + // Check hr marker + if (marker !== 0x2A/* * */ && + marker !== 0x2D/* - */ && + marker !== 0x5F/* _ */) { + return false; + } + + // markers can be mixed with spaces, but there should be at least 3 one + + cnt = 1; + while (pos < max) { + ch = state.src.charCodeAt(pos++); + if (ch !== marker && ch !== 0x20/* space */) { return false; } + if (ch === marker) { cnt++; } + } + + if (cnt < 3) { return false; } + + if (silent) { return true; } + + state.line = startLine + 1; + state.tokens.push({ + type: 'hr', + lines: [ startLine, state.line ], + level: state.level + }); + + return true; +}; + +},{}],27:[function(require,module,exports){ +// HTML block + +'use strict'; + + +var block_names = require('../common/html_blocks'); + + +var HTML_TAG_OPEN_RE = /^<([a-zA-Z]{1,15})[\s\/>]/; +var HTML_TAG_CLOSE_RE = /^<\/([a-zA-Z]{1,15})[\s>]/; + +function isLetter(ch) { + /*eslint no-bitwise:0*/ + var lc = ch | 0x20; // to lower case + return (lc >= 0x61/* a */) && (lc <= 0x7a/* z */); +} + +module.exports = function htmlblock(state, startLine, endLine, silent) { + var ch, match, nextLine, + pos = state.bMarks[startLine], + max = state.eMarks[startLine], + shift = state.tShift[startLine]; + + pos += shift; + + if (!state.options.html) { return false; } + + if (shift > 3 || pos + 2 >= max) { return false; } + + if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; } + + ch = state.src.charCodeAt(pos + 1); + + if (ch === 0x21/* ! */ || ch === 0x3F/* ? */) { + // Directive start / comment start / processing instruction start + if (silent) { return true; } + + } else if (ch === 0x2F/* / */ || isLetter(ch)) { + + // Probably start or end of tag + if (ch === 0x2F/* \ */) { + // closing tag + match = state.src.slice(pos, max).match(HTML_TAG_CLOSE_RE); + if (!match) { return false; } + } else { + // opening tag + match = state.src.slice(pos, max).match(HTML_TAG_OPEN_RE); + if (!match) { return false; } + } + // Make sure tag name is valid + if (block_names[match[1].toLowerCase()] !== true) { return false; } + if (silent) { return true; } + + } else { + return false; + } + + // If we are here - we detected HTML block. + // Let's roll down till empty line (block end). + nextLine = startLine + 1; + while (nextLine < state.lineMax && !state.isEmpty(nextLine)) { + nextLine++; + } + + state.line = nextLine; + state.tokens.push({ + type: 'htmlblock', + level: state.level, + lines: [ startLine, state.line ], + content: state.getLines(startLine, nextLine, 0, true) + }); + + return true; +}; + +},{"../common/html_blocks":2}],28:[function(require,module,exports){ +// lheading (---, ===) + +'use strict'; + + +module.exports = function lheading(state, startLine, endLine/*, silent*/) { + var marker, pos, max, + next = startLine + 1; + + if (next >= endLine) { return false; } + if (state.tShift[next] < state.blkIndent) { return false; } + + // Scan next line + + if (state.tShift[next] - state.blkIndent > 3) { return false; } + + pos = state.bMarks[next] + state.tShift[next]; + max = state.eMarks[next]; + + if (pos >= max) { return false; } + + marker = state.src.charCodeAt(pos); + + if (marker !== 0x2D/* - */ && marker !== 0x3D/* = */) { return false; } + + pos = state.skipChars(pos, marker); + + pos = state.skipSpaces(pos); + + if (pos < max) { return false; } + + pos = state.bMarks[startLine] + state.tShift[startLine]; + + state.line = next + 1; + state.tokens.push({ + type: 'heading_open', + hLevel: marker === 0x3D/* = */ ? 1 : 2, + lines: [ startLine, state.line ], + level: state.level + }); + state.tokens.push({ + type: 'inline', + content: state.src.slice(pos, state.eMarks[startLine]).trim(), + level: state.level + 1, + lines: [ startLine, state.line - 1 ], + children: [] + }); + state.tokens.push({ + type: 'heading_close', + hLevel: marker === 0x3D/* = */ ? 1 : 2, + level: state.level + }); + + return true; +}; + +},{}],29:[function(require,module,exports){ +// Lists + +'use strict'; + + +// Search `[-+*][\n ]`, returns next pos arter marker on success +// or -1 on fail. +function skipBulletListMarker(state, startLine) { + var marker, pos, max; + + pos = state.bMarks[startLine] + state.tShift[startLine]; + max = state.eMarks[startLine]; + + if (pos >= max) { return -1; } + + marker = state.src.charCodeAt(pos++); + // Check bullet + if (marker !== 0x2A/* * */ && + marker !== 0x2D/* - */ && + marker !== 0x2B/* + */) { + return -1; + } + + if (pos < max && state.src.charCodeAt(pos) !== 0x20) { + // " 1.test " - is not a list item + return -1; + } + + return pos; +} + +// Search `\d+[.)][\n ]`, returns next pos arter marker on success +// or -1 on fail. +function skipOrderedListMarker(state, startLine) { + var ch, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + if (pos + 1 >= max) { return -1; } + + ch = state.src.charCodeAt(pos++); + + if (ch < 0x30/* 0 */ || ch > 0x39/* 9 */) { return -1; } + + for (;;) { + // EOL -> fail + if (pos >= max) { return -1; } + + ch = state.src.charCodeAt(pos++); + + if (ch >= 0x30/* 0 */ && ch <= 0x39/* 9 */) { + continue; + } + + // found valid marker + if (ch === 0x29/* ) */ || ch === 0x2e/* . */) { + break; + } + + return -1; + } + + + if (pos < max && state.src.charCodeAt(pos) !== 0x20/* space */) { + // " 1.test " - is not a list item + return -1; + } + return pos; +} + +function markTightParagraphs(state, idx) { + var i, l, + level = state.level + 2; + + for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) { + if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') { + state.tokens[i + 2].tight = true; + state.tokens[i].tight = true; + i += 2; + } + } +} + + +module.exports = function list(state, startLine, endLine, silent) { + var nextLine, + indent, + oldTShift, + oldIndent, + oldTight, + oldParentType, + start, + posAfterMarker, + max, + indentAfterMarker, + markerValue, + markerCharCode, + isOrdered, + contentStart, + listTokIdx, + prevEmptyEnd, + listLines, + itemLines, + tight = true, + terminatorRules, + i, l, terminate; + + // Detect list type and position after marker + if ((posAfterMarker = skipOrderedListMarker(state, startLine)) >= 0) { + isOrdered = true; + } else if ((posAfterMarker = skipBulletListMarker(state, startLine)) >= 0) { + isOrdered = false; + } else { + return false; + } + + if (state.level >= state.options.maxNesting) { return false; } + + // We should terminate list on style change. Remember first one to compare. + markerCharCode = state.src.charCodeAt(posAfterMarker - 1); + + // For validation mode we can terminate immediately + if (silent) { return true; } + + // Start list + listTokIdx = state.tokens.length; + + if (isOrdered) { + start = state.bMarks[startLine] + state.tShift[startLine]; + markerValue = Number(state.src.substr(start, posAfterMarker - start - 1)); + + state.tokens.push({ + type: 'ordered_list_open', + order: markerValue, + lines: listLines = [ startLine, 0 ], + level: state.level++ + }); + + } else { + state.tokens.push({ + type: 'bullet_list_open', + lines: listLines = [ startLine, 0 ], + level: state.level++ + }); + } + + // + // Iterate list items + // + + nextLine = startLine; + prevEmptyEnd = false; + terminatorRules = state.parser.ruler.getRules('list'); + + while (nextLine < endLine) { + contentStart = state.skipSpaces(posAfterMarker); + max = state.eMarks[nextLine]; + + if (contentStart >= max) { + // trimming space in "- \n 3" case, indent is 1 here + indentAfterMarker = 1; + } else { + indentAfterMarker = contentStart - posAfterMarker; + } + + // If we have more than 4 spaces, the indent is 1 + // (the rest is just indented code block) + if (indentAfterMarker > 4) { indentAfterMarker = 1; } + + // If indent is less than 1, assume that it's one, example: + // "-\n test" + if (indentAfterMarker < 1) { indentAfterMarker = 1; } + + // " - test" + // ^^^^^ - calculating total length of this thing + indent = (posAfterMarker - state.bMarks[nextLine]) + indentAfterMarker; + + // Run subparser & write tokens + state.tokens.push({ + type: 'list_item_open', + lines: itemLines = [ startLine, 0 ], + level: state.level++ + }); + + oldIndent = state.blkIndent; + oldTight = state.tight; + oldTShift = state.tShift[startLine]; + oldParentType = state.parentType; + state.tShift[startLine] = contentStart - state.bMarks[startLine]; + state.blkIndent = indent; + state.tight = true; + state.parentType = 'list'; + + state.parser.tokenize(state, startLine, endLine, true); + + // If any of list item is tight, mark list as tight + if (!state.tight || prevEmptyEnd) { + tight = false; + } + // Item become loose if finish with empty line, + // but we should filter last element, because it means list finish + prevEmptyEnd = (state.line - startLine) > 1 && state.isEmpty(state.line - 1); + + state.blkIndent = oldIndent; + state.tShift[startLine] = oldTShift; + state.tight = oldTight; + state.parentType = oldParentType; + + state.tokens.push({ + type: 'list_item_close', + level: --state.level + }); + + nextLine = startLine = state.line; + itemLines[1] = nextLine; + contentStart = state.bMarks[startLine]; + + if (nextLine >= endLine) { break; } + + if (state.isEmpty(nextLine)) { + break; + } + + // + // Try to check if list is terminated or continued. + // + if (state.tShift[nextLine] < state.blkIndent) { break; } + + // fail if terminating block found + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { break; } + + // fail if list has another type + if (isOrdered) { + posAfterMarker = skipOrderedListMarker(state, nextLine); + if (posAfterMarker < 0) { break; } + } else { + posAfterMarker = skipBulletListMarker(state, nextLine); + if (posAfterMarker < 0) { break; } + } + + if (markerCharCode !== state.src.charCodeAt(posAfterMarker - 1)) { break; } + } + + // Finilize list + state.tokens.push({ + type: isOrdered ? 'ordered_list_close' : 'bullet_list_close', + level: --state.level + }); + listLines[1] = nextLine; + + state.line = nextLine; + + // mark paragraphs tight if needed + if (tight) { + markTightParagraphs(state, listTokIdx); + } + + return true; +}; + +},{}],30:[function(require,module,exports){ +// Paragraph + +'use strict'; + + +module.exports = function paragraph(state, startLine/*, endLine*/) { + var endLine, content, terminate, i, l, + nextLine = startLine + 1, + terminatorRules; + + endLine = state.lineMax; + + // jump line-by-line until empty one or EOF + if (nextLine < endLine && !state.isEmpty(nextLine)) { + terminatorRules = state.parser.ruler.getRules('paragraph'); + + for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { + // this would be a code block normally, but after paragraph + // it's considered a lazy continuation regardless of what's there + if (state.tShift[nextLine] - state.blkIndent > 3) { continue; } + + // Some tags can terminate paragraph without empty line. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { break; } + } + } + + content = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); + + state.line = nextLine; + if (content.length) { + state.tokens.push({ + type: 'paragraph_open', + tight: false, + lines: [ startLine, state.line ], + level: state.level + }); + state.tokens.push({ + type: 'inline', + content: content, + level: state.level + 1, + lines: [ startLine, state.line ], + children: [] + }); + state.tokens.push({ + type: 'paragraph_close', + tight: false, + level: state.level + }); + } + + return true; +}; + +},{}],31:[function(require,module,exports){ +// Parser state class + +'use strict'; + + +function StateBlock(src, parser, options, env, tokens) { + var ch, s, start, pos, len, indent, indent_found; + + this.src = src; + + // Shortcuts to simplify nested calls + this.parser = parser; + + this.options = options; + + this.env = env; + + // + // Internal state vartiables + // + + this.tokens = tokens; + + this.bMarks = []; // line begin offsets for fast jumps + this.eMarks = []; // line end offsets for fast jumps + this.tShift = []; // indent for each line + + // block parser variables + this.blkIndent = 0; // required block content indent + // (for example, if we are in list) + this.line = 0; // line index in src + this.lineMax = 0; // lines count + this.tight = false; // loose/tight mode for lists + this.parentType = 'root'; // if `list`, block parser stops on two newlines + this.ddIndent = -1; // indent of the current dd block (-1 if there isn't any) + + this.level = 0; + + // renderer + this.result = ''; + + // Create caches + // Generate markers. + s = this.src; + indent = 0; + indent_found = false; + + for (start = pos = indent = 0, len = s.length; pos < len; pos++) { + ch = s.charCodeAt(pos); + + if (!indent_found) { + if (ch === 0x20/* space */) { + indent++; + continue; + } else { + indent_found = true; + } + } + + if (ch === 0x0A || pos === len - 1) { + if (ch !== 0x0A) { pos++; } + this.bMarks.push(start); + this.eMarks.push(pos); + this.tShift.push(indent); + + indent_found = false; + indent = 0; + start = pos + 1; + } + } + + // Push fake entry to simplify cache bounds checks + this.bMarks.push(s.length); + this.eMarks.push(s.length); + this.tShift.push(0); + + this.lineMax = this.bMarks.length - 1; // don't count last fake line +} + +StateBlock.prototype.isEmpty = function isEmpty(line) { + return this.bMarks[line] + this.tShift[line] >= this.eMarks[line]; +}; + +StateBlock.prototype.skipEmptyLines = function skipEmptyLines(from) { + for (var max = this.lineMax; from < max; from++) { + if (this.bMarks[from] + this.tShift[from] < this.eMarks[from]) { + break; + } + } + return from; +}; + +// Skip spaces from given position. +StateBlock.prototype.skipSpaces = function skipSpaces(pos) { + for (var max = this.src.length; pos < max; pos++) { + if (this.src.charCodeAt(pos) !== 0x20/* space */) { break; } + } + return pos; +}; + +// Skip char codes from given position +StateBlock.prototype.skipChars = function skipChars(pos, code) { + for (var max = this.src.length; pos < max; pos++) { + if (this.src.charCodeAt(pos) !== code) { break; } + } + return pos; +}; + +// Skip char codes reverse from given position - 1 +StateBlock.prototype.skipCharsBack = function skipCharsBack(pos, code, min) { + if (pos <= min) { return pos; } + + while (pos > min) { + if (code !== this.src.charCodeAt(--pos)) { return pos + 1; } + } + return pos; +}; + +// cut lines range from source. +StateBlock.prototype.getLines = function getLines(begin, end, indent, keepLastLF) { + var i, first, last, queue, shift, + line = begin; + + if (begin >= end) { + return ''; + } + + // Opt: don't use push queue for single line; + if (line + 1 === end) { + first = this.bMarks[line] + Math.min(this.tShift[line], indent); + last = keepLastLF ? this.bMarks[end] : this.eMarks[end - 1]; + return this.src.slice(first, last); + } + + queue = new Array(end - begin); + + for (i = 0; line < end; line++, i++) { + shift = this.tShift[line]; + if (shift > indent) { shift = indent; } + if (shift < 0) { shift = 0; } + + first = this.bMarks[line] + shift; + + if (line + 1 < end || keepLastLF) { + // No need for bounds check because we have fake entry on tail. + last = this.eMarks[line] + 1; + } else { + last = this.eMarks[line]; + } + + queue[i] = this.src.slice(first, last); + } + + return queue.join(''); +}; + + +module.exports = StateBlock; + +},{}],32:[function(require,module,exports){ +// GFM table, non-standard + +'use strict'; + + +function getLine(state, line) { + var pos = state.bMarks[line] + state.blkIndent, + max = state.eMarks[line]; + + return state.src.substr(pos, max - pos); +} + + +module.exports = function table(state, startLine, endLine, silent) { + var ch, lineText, pos, i, nextLine, rows, + aligns, t, tableLines, tbodyLines; + + // should have at least three lines + if (startLine + 2 > endLine) { return false; } + + nextLine = startLine + 1; + + if (state.tShift[nextLine] < state.blkIndent) { return false; } + + // first character of the second line should be '|' or '-' + + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + if (pos >= state.eMarks[nextLine]) { return false; } + + ch = state.src.charCodeAt(pos); + if (ch !== 0x7C/* | */ && ch !== 0x2D/* - */ && ch !== 0x3A/* : */) { return false; } + + lineText = getLine(state, startLine + 1); + if (!/^[-:| ]+$/.test(lineText)) { return false; } + + rows = lineText.split('|'); + if (rows <= 2) { return false; } + aligns = []; + for (i = 0; i < rows.length; i++) { + t = rows[i].trim(); + if (!t) { + // allow empty columns before and after table, but not in between columns; + // e.g. allow ` |---| `, disallow ` ---||--- ` + if (i === 0 || i === rows.length - 1) { + continue; + } else { + return false; + } + } + + if (!/^:?-+:?$/.test(t)) { return false; } + if (t.charCodeAt(t.length - 1) === 0x3A/* : */) { + aligns.push(t.charCodeAt(0) === 0x3A/* : */ ? 'center' : 'right'); + } else if (t.charCodeAt(0) === 0x3A/* : */) { + aligns.push('left'); + } else { + aligns.push(''); + } + } + + lineText = getLine(state, startLine).trim(); + if (lineText.indexOf('|') === -1) { return false; } + rows = lineText.replace(/^\||\|$/g, '').split('|'); + if (aligns.length !== rows.length) { return false; } + if (silent) { return true; } + + state.tokens.push({ + type: 'table_open', + lines: tableLines = [ startLine, 0 ], + level: state.level++ + }); + state.tokens.push({ + type: 'thead_open', + lines: [ startLine, startLine + 1 ], + level: state.level++ + }); + + state.tokens.push({ + type: 'tr_open', + lines: [ startLine, startLine + 1 ], + level: state.level++ + }); + for (i = 0; i < rows.length; i++) { + state.tokens.push({ + type: 'th_open', + align: aligns[i], + lines: [ startLine, startLine + 1 ], + level: state.level++ + }); + state.tokens.push({ + type: 'inline', + content: rows[i].trim(), + lines: [ startLine, startLine + 1 ], + level: state.level, + children: [] + }); + state.tokens.push({ type: 'th_close', level: --state.level }); + } + state.tokens.push({ type: 'tr_close', level: --state.level }); + state.tokens.push({ type: 'thead_close', level: --state.level }); + + state.tokens.push({ + type: 'tbody_open', + lines: tbodyLines = [ startLine + 2, 0 ], + level: state.level++ + }); + + for (nextLine = startLine + 2; nextLine < endLine; nextLine++) { + if (state.tShift[nextLine] < state.blkIndent) { break; } + + lineText = getLine(state, nextLine).trim(); + if (lineText.indexOf('|') === -1) { break; } + rows = lineText.replace(/^\||\|$/g, '').split('|'); + + state.tokens.push({ type: 'tr_open', level: state.level++ }); + for (i = 0; i < rows.length; i++) { + state.tokens.push({ type: 'td_open', align: aligns[i], level: state.level++ }); + state.tokens.push({ + type: 'inline', + content: rows[i].replace(/^\|? *| *\|?$/g, ''), + level: state.level, + children: [] + }); + state.tokens.push({ type: 'td_close', level: --state.level }); + } + state.tokens.push({ type: 'tr_close', level: --state.level }); + } + state.tokens.push({ type: 'tbody_close', level: --state.level }); + state.tokens.push({ type: 'table_close', level: --state.level }); + + tableLines[1] = tbodyLines[1] = nextLine; + state.line = nextLine; + return true; +}; + +},{}],33:[function(require,module,exports){ +// Parse abbreviation definitions, i.e. `*[abbr]: description` +// + +'use strict'; + + +var StateInline = require('../rules_inline/state_inline'); +var parseLinkLabel = require('../helpers/parse_link_label'); + + +function parseAbbr(str, parserInline, options, env) { + var state, labelEnd, pos, max, label, title; + + if (str.charCodeAt(0) !== 0x2A/* * */) { return -1; } + if (str.charCodeAt(1) !== 0x5B/* [ */) { return -1; } + + if (str.indexOf(']:') === -1) { return -1; } + + state = new StateInline(str, parserInline, options, env, []); + labelEnd = parseLinkLabel(state, 1); + + if (labelEnd < 0 || str.charCodeAt(labelEnd + 1) !== 0x3A/* : */) { return -1; } + + max = state.posMax; + + // abbr title is always one line, so looking for ending "\n" here + for (pos = labelEnd + 2; pos < max; pos++) { + if (state.src.charCodeAt(pos) === 0x0A) { break; } + } + + label = str.slice(2, labelEnd); + title = str.slice(labelEnd + 2, pos).trim(); + if (title.length === 0) { return -1; } + if (!env.abbreviations) { env.abbreviations = {}; } + // prepend ':' to avoid conflict with Object.prototype members + if (typeof env.abbreviations[':' + label] === 'undefined') { + env.abbreviations[':' + label] = title; + } + + return pos; +} + +module.exports = function abbr(state) { + var tokens = state.tokens, i, l, content, pos; + + if (state.inlineMode) { + return; + } + + // Parse inlines + for (i = 1, l = tokens.length - 1; i < l; i++) { + if (tokens[i - 1].type === 'paragraph_open' && + tokens[i].type === 'inline' && + tokens[i + 1].type === 'paragraph_close') { + + content = tokens[i].content; + while (content.length) { + pos = parseAbbr(content, state.inline, state.options, state.env); + if (pos < 0) { break; } + content = content.slice(pos).trim(); + } + + tokens[i].content = content; + if (!content.length) { + tokens[i - 1].tight = true; + tokens[i + 1].tight = true; + } + } + } +}; + +},{"../helpers/parse_link_label":12,"../rules_inline/state_inline":55}],34:[function(require,module,exports){ +// Enclose abbreviations in tags +// +'use strict'; + + +var PUNCT_CHARS = ' \n()[]\'".,!?-'; + + +// from Google closure library +// http://closure-library.googlecode.com/git-history/docs/local_closure_goog_string_string.js.source.html#line1021 +function regEscape(s) { + return s.replace(/([-()\[\]{}+?*.$\^|,:#= 0; i--) { + token = tokens[i]; + if (token.type !== 'text') { continue; } + + pos = 0; + text = token.content; + reg.lastIndex = 0; + level = token.level; + nodes = []; + + while ((m = reg.exec(text))) { + if (reg.lastIndex > pos) { + nodes.push({ + type: 'text', + content: text.slice(pos, m.index + m[1].length), + level: level + }); + } + + nodes.push({ + type: 'abbr_open', + title: state.env.abbreviations[':' + m[2]], + level: level++ + }); + nodes.push({ + type: 'text', + content: m[2], + level: level + }); + nodes.push({ + type: 'abbr_close', + level: --level + }); + pos = reg.lastIndex - m[3].length; + } + + if (!nodes.length) { continue; } + + if (pos < text.length) { + nodes.push({ + type: 'text', + content: text.slice(pos), + level: level + }); + } + + // replace current node + blockTokens[j].children = tokens = [].concat(tokens.slice(0, i), nodes, tokens.slice(i + 1)); + } + } +}; + +},{}],35:[function(require,module,exports){ +'use strict'; + +module.exports = function block(state) { + + if (state.inlineMode) { + state.tokens.push({ + type: 'inline', + content: state.src.replace(/\n/g, ' ').trim(), + level: 0, + lines: [ 0, 1 ], + children: [] + }); + + } else { + state.block.parse(state.src, state.options, state.env, state.tokens); + } +}; + +},{}],36:[function(require,module,exports){ +'use strict'; + + +module.exports = function footnote_block(state) { + var i, l, j, t, lastParagraph, list, tokens, current, currentLabel, + level = 0, + insideRef = false, + refTokens = {}; + + if (!state.env.footnotes) { return; } + + state.tokens = state.tokens.filter(function(tok) { + if (tok.type === 'footnote_reference_open') { + insideRef = true; + current = []; + currentLabel = tok.label; + return false; + } + if (tok.type === 'footnote_reference_close') { + insideRef = false; + // prepend ':' to avoid conflict with Object.prototype members + refTokens[':' + currentLabel] = current; + return false; + } + if (insideRef) { current.push(tok); } + return !insideRef; + }); + + if (!state.env.footnotes.list) { return; } + list = state.env.footnotes.list; + + state.tokens.push({ + type: 'footnote_block_open', + level: level++ + }); + for (i = 0, l = list.length; i < l; i++) { + state.tokens.push({ + type: 'footnote_open', + id: i, + level: level++ + }); + + if (list[i].tokens) { + tokens = []; + tokens.push({ + type: 'paragraph_open', + tight: false, + level: level++ + }); + tokens.push({ + type: 'inline', + content: '', + level: level, + children: list[i].tokens + }); + tokens.push({ + type: 'paragraph_close', + tight: false, + level: --level + }); + } else if (list[i].label) { + tokens = refTokens[':' + list[i].label]; + } + + state.tokens = state.tokens.concat(tokens); + if (state.tokens[state.tokens.length - 1].type === 'paragraph_close') { + lastParagraph = state.tokens.pop(); + } else { + lastParagraph = null; + } + + t = list[i].count > 0 ? list[i].count : 1; + for (j = 0; j < t; j++) { + state.tokens.push({ + type: 'footnote_anchor', + id: i, + subId: j, + level: level + }); + } + + if (lastParagraph) { + state.tokens.push(lastParagraph); + } + + state.tokens.push({ + type: 'footnote_close', + level: --level + }); + } + state.tokens.push({ + type: 'footnote_block_close', + level: --level + }); +}; + +},{}],37:[function(require,module,exports){ +'use strict'; + +module.exports = function inline(state) { + var tokens = state.tokens, tok, i, l; + + // Parse inlines + for (i = 0, l = tokens.length; i < l; i++) { + tok = tokens[i]; + if (tok.type === 'inline') { + state.inline.parse(tok.content, state.options, state.env, tok.children); + } + } +}; + +},{}],38:[function(require,module,exports){ +// Replace link-like texts with link nodes. +// +// Currently restricted by `inline.validateLink()` to http/https/ftp +// +'use strict'; + + +var Autolinker = require('autolinker'); + + +var LINK_SCAN_RE = /www|@|\:\/\//; + + +function isLinkOpen(str) { + return /^\s]/i.test(str); +} +function isLinkClose(str) { + return /^<\/a\s*>/i.test(str); +} + +// Stupid fabric to avoid singletons, for thread safety. +// Required for engines like Nashorn. +// +function createLinkifier() { + var links = []; + var autolinker = new Autolinker({ + stripPrefix: false, + url: true, + email: true, + twitter: false, + replaceFn: function (autolinker, match) { + // Only collect matched strings but don't change anything. + switch (match.getType()) { + /*eslint default-case:0*/ + case 'url': + links.push({ + text: match.matchedText, + url: match.getUrl() + }); + break; + case 'email': + links.push({ + text: match.matchedText, + // normalize email protocol + url: 'mailto:' + match.getEmail().replace(/^mailto:/i, '') + }); + break; + } + return false; + } + }); + + return { + links: links, + autolinker: autolinker + }; +} + + +module.exports = function linkify(state) { + var i, j, l, tokens, token, text, nodes, ln, pos, level, htmlLinkLevel, + blockTokens = state.tokens, + linkifier = null, links, autolinker; + + if (!state.options.linkify) { return; } + + for (j = 0, l = blockTokens.length; j < l; j++) { + if (blockTokens[j].type !== 'inline') { continue; } + tokens = blockTokens[j].children; + + htmlLinkLevel = 0; + + // We scan from the end, to keep position when new tags added. + // Use reversed logic in links start/end match + for (i = tokens.length - 1; i >= 0; i--) { + token = tokens[i]; + + // Skip content of markdown links + if (token.type === 'link_close') { + i--; + while (tokens[i].level !== token.level && tokens[i].type !== 'link_open') { + i--; + } + continue; + } + + // Skip content of html tag links + if (token.type === 'htmltag') { + if (isLinkOpen(token.content) && htmlLinkLevel > 0) { + htmlLinkLevel--; + } + if (isLinkClose(token.content)) { + htmlLinkLevel++; + } + } + if (htmlLinkLevel > 0) { continue; } + + if (token.type === 'text' && LINK_SCAN_RE.test(token.content)) { + + // Init linkifier in lazy manner, only if required. + if (!linkifier) { + linkifier = createLinkifier(); + links = linkifier.links; + autolinker = linkifier.autolinker; + } + + text = token.content; + links.length = 0; + autolinker.link(text); + + if (!links.length) { continue; } + + // Now split string to nodes + nodes = []; + level = token.level; + + for (ln = 0; ln < links.length; ln++) { + + if (!state.inline.validateLink(links[ln].url)) { continue; } + + pos = text.indexOf(links[ln].text); + + if (pos) { + level = level; + nodes.push({ + type: 'text', + content: text.slice(0, pos), + level: level + }); + } + nodes.push({ + type: 'link_open', + href: links[ln].url, + title: '', + level: level++ + }); + nodes.push({ + type: 'text', + content: links[ln].text, + level: level + }); + nodes.push({ + type: 'link_close', + level: --level + }); + text = text.slice(pos + links[ln].text.length); + } + if (text.length) { + nodes.push({ + type: 'text', + content: text, + level: level + }); + } + + // replace current node + blockTokens[j].children = tokens = [].concat(tokens.slice(0, i), nodes, tokens.slice(i + 1)); + } + } + } +}; + +},{"autolinker":59}],39:[function(require,module,exports){ +'use strict'; + + +var StateInline = require('../rules_inline/state_inline'); +var parseLinkLabel = require('../helpers/parse_link_label'); +var parseLinkDestination = require('../helpers/parse_link_destination'); +var parseLinkTitle = require('../helpers/parse_link_title'); +var normalizeReference = require('../helpers/normalize_reference'); + + +function parseReference(str, parser, options, env) { + var state, labelEnd, pos, max, code, start, href, title, label; + + if (str.charCodeAt(0) !== 0x5B/* [ */) { return -1; } + + if (str.indexOf(']:') === -1) { return -1; } + + state = new StateInline(str, parser, options, env, []); + labelEnd = parseLinkLabel(state, 0); + + if (labelEnd < 0 || str.charCodeAt(labelEnd + 1) !== 0x3A/* : */) { return -1; } + + max = state.posMax; + + // [label]: destination 'title' + // ^^^ skip optional whitespace here + for (pos = labelEnd + 2; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (code !== 0x20 && code !== 0x0A) { break; } + } + + // [label]: destination 'title' + // ^^^^^^^^^^^ parse this + if (!parseLinkDestination(state, pos)) { return -1; } + href = state.linkContent; + pos = state.pos; + + // [label]: destination 'title' + // ^^^ skipping those spaces + start = pos; + for (pos = pos + 1; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (code !== 0x20 && code !== 0x0A) { break; } + } + + // [label]: destination 'title' + // ^^^^^^^ parse this + if (pos < max && start !== pos && parseLinkTitle(state, pos)) { + title = state.linkContent; + pos = state.pos; + } else { + title = ''; + pos = start; + } + + // ensure that the end of the line is empty + while (pos < max && state.src.charCodeAt(pos) === 0x20/* space */) { pos++; } + if (pos < max && state.src.charCodeAt(pos) !== 0x0A) { return -1; } + + label = normalizeReference(str.slice(1, labelEnd)); + if (typeof env.references[label] === 'undefined') { + env.references[label] = { title: title, href: href }; + } + + return pos; +} + + +module.exports = function references(state) { + var tokens = state.tokens, i, l, content, pos; + + state.env.references = state.env.references || {}; + + if (state.inlineMode) { + return; + } + + // Scan definitions in paragraph inlines + for (i = 1, l = tokens.length - 1; i < l; i++) { + if (tokens[i].type === 'inline' && + tokens[i - 1].type === 'paragraph_open' && + tokens[i + 1].type === 'paragraph_close') { + + content = tokens[i].content; + while (content.length) { + pos = parseReference(content, state.inline, state.options, state.env); + if (pos < 0) { break; } + content = content.slice(pos).trim(); + } + + tokens[i].content = content; + if (!content.length) { + tokens[i - 1].tight = true; + tokens[i + 1].tight = true; + } + } + } +}; + +},{"../helpers/normalize_reference":10,"../helpers/parse_link_destination":11,"../helpers/parse_link_label":12,"../helpers/parse_link_title":13,"../rules_inline/state_inline":55}],40:[function(require,module,exports){ +// Simple typographyc replacements +// +'use strict'; + +// TODO: +// - fractionals 1/2, 1/4, 3/4 -> ½, ¼, ¾ +// - miltiplication 2 x 4 -> 2 × 4 + +var RARE_RE = /\+-|\.\.|\?\?\?\?|!!!!|,,|--/; + +var SCOPED_ABBR_RE = /\((c|tm|r|p)\)/ig; +var SCOPED_ABBR = { + 'c': '©', + 'r': '®', + 'p': '§', + 'tm': '™' +}; + +function replaceScopedAbbr(str) { + if (str.indexOf('(') < 0) { return str; } + + return str.replace(SCOPED_ABBR_RE, function(match, name) { + return SCOPED_ABBR[name.toLowerCase()]; + }); +} + + +module.exports = function replace(state) { + var i, token, text, inlineTokens, blkIdx; + + if (!state.options.typographer) { return; } + + for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { + + if (state.tokens[blkIdx].type !== 'inline') { continue; } + + inlineTokens = state.tokens[blkIdx].children; + + for (i = inlineTokens.length - 1; i >= 0; i--) { + token = inlineTokens[i]; + if (token.type === 'text') { + text = token.content; + + text = replaceScopedAbbr(text); + + if (RARE_RE.test(text)) { + text = text.replace(/\+-/g, '±') + // .., ..., ....... -> … + // but ?..... & !..... -> ?.. & !.. + .replace(/\.{2,}/g, '…').replace(/([?!])…/g, '$1..') + .replace(/([?!]){4,}/g, '$1$1$1').replace(/,{2,}/g, ',') + // em-dash + .replace(/(^|[^-])---([^-]|$)/mg, '$1\u2014$2') + // en-dash + .replace(/(^|\s)--(\s|$)/mg, '$1\u2013$2') + .replace(/(^|[^-\s])--([^-\s]|$)/mg, '$1\u2013$2'); + } + + token.content = text; + } + } + } +}; + +},{}],41:[function(require,module,exports){ +// Convert straight quotation marks to typographic ones +// +'use strict'; + + +var QUOTE_TEST_RE = /['"]/; +var QUOTE_RE = /['"]/g; +var PUNCT_RE = /[-\s()\[\]]/; +var APOSTROPHE = '’'; + +// This function returns true if the character at `pos` +// could be inside a word. +function isLetter(str, pos) { + if (pos < 0 || pos >= str.length) { return false; } + return !PUNCT_RE.test(str[pos]); +} + + +function replaceAt(str, index, ch) { + return str.substr(0, index) + ch + str.substr(index + 1); +} + + +module.exports = function smartquotes(state) { + /*eslint max-depth:0*/ + var i, token, text, t, pos, max, thisLevel, lastSpace, nextSpace, item, + canOpen, canClose, j, isSingle, blkIdx, tokens, + stack; + + if (!state.options.typographer) { return; } + + stack = []; + + for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { + + if (state.tokens[blkIdx].type !== 'inline') { continue; } + + tokens = state.tokens[blkIdx].children; + stack.length = 0; + + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + + if (token.type !== 'text' || QUOTE_TEST_RE.test(token.text)) { continue; } + + thisLevel = tokens[i].level; + + for (j = stack.length - 1; j >= 0; j--) { + if (stack[j].level <= thisLevel) { break; } + } + stack.length = j + 1; + + text = token.content; + pos = 0; + max = text.length; + + /*eslint no-labels:0,block-scoped-var:0*/ + OUTER: + while (pos < max) { + QUOTE_RE.lastIndex = pos; + t = QUOTE_RE.exec(text); + if (!t) { break; } + + lastSpace = !isLetter(text, t.index - 1); + pos = t.index + 1; + isSingle = (t[0] === "'"); + nextSpace = !isLetter(text, pos); + + if (!nextSpace && !lastSpace) { + // middle of word + if (isSingle) { + token.content = replaceAt(token.content, t.index, APOSTROPHE); + } + continue; + } + + canOpen = !nextSpace; + canClose = !lastSpace; + + if (canClose) { + // this could be a closing quote, rewind the stack to get a match + for (j = stack.length - 1; j >= 0; j--) { + item = stack[j]; + if (stack[j].level < thisLevel) { break; } + if (item.single === isSingle && stack[j].level === thisLevel) { + item = stack[j]; + if (isSingle) { + tokens[item.token].content = replaceAt(tokens[item.token].content, item.pos, state.options.quotes[2]); + token.content = replaceAt(token.content, t.index, state.options.quotes[3]); + } else { + tokens[item.token].content = replaceAt(tokens[item.token].content, item.pos, state.options.quotes[0]); + token.content = replaceAt(token.content, t.index, state.options.quotes[1]); + } + stack.length = j; + continue OUTER; + } + } + } + + if (canOpen) { + stack.push({ + token: i, + pos: t.index, + single: isSingle, + level: thisLevel + }); + } else if (canClose && isSingle) { + token.content = replaceAt(token.content, t.index, APOSTROPHE); + } + } + } + } +}; + +},{}],42:[function(require,module,exports){ +// Process autolinks '' + +'use strict'; + +var url_schemas = require('../common/url_schemas'); +var normalizeLink = require('../helpers/normalize_link'); + + +/*eslint max-len:0*/ +var EMAIL_RE = /^<([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)>/; +var AUTOLINK_RE = /^<([a-zA-Z.\-]{1,25}):([^<>\x00-\x20]*)>/; + + +module.exports = function autolink(state, silent) { + var tail, linkMatch, emailMatch, url, fullUrl, pos = state.pos; + + if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; } + + tail = state.src.slice(pos); + + if (tail.indexOf('>') < 0) { return false; } + + linkMatch = tail.match(AUTOLINK_RE); + + if (linkMatch) { + if (url_schemas.indexOf(linkMatch[1].toLowerCase()) < 0) { return false; } + + url = linkMatch[0].slice(1, -1); + fullUrl = normalizeLink(url); + if (!state.parser.validateLink(url)) { return false; } + + if (!silent) { + state.push({ + type: 'link_open', + href: fullUrl, + level: state.level + }); + state.push({ + type: 'text', + content: url, + level: state.level + 1 + }); + state.push({ type: 'link_close', level: state.level }); + } + + state.pos += linkMatch[0].length; + return true; + } + + emailMatch = tail.match(EMAIL_RE); + + if (emailMatch) { + + url = emailMatch[0].slice(1, -1); + + fullUrl = normalizeLink('mailto:' + url); + if (!state.parser.validateLink(fullUrl)) { return false; } + + if (!silent) { + state.push({ + type: 'link_open', + href: fullUrl, + level: state.level + }); + state.push({ + type: 'text', + content: url, + level: state.level + 1 + }); + state.push({ type: 'link_close', level: state.level }); + } + + state.pos += emailMatch[0].length; + return true; + } + + return false; +}; + +},{"../common/url_schemas":4,"../helpers/normalize_link":9}],43:[function(require,module,exports){ +// Parse backticks + +'use strict'; + +module.exports = function backticks(state, silent) { + var start, max, marker, matchStart, matchEnd, + pos = state.pos, + ch = state.src.charCodeAt(pos); + + if (ch !== 0x60/* ` */) { return false; } + + start = pos; + pos++; + max = state.posMax; + + while (pos < max && state.src.charCodeAt(pos) === 0x60/* ` */) { pos++; } + + marker = state.src.slice(start, pos); + + matchStart = matchEnd = pos; + + while ((matchStart = state.src.indexOf('`', matchEnd)) !== -1) { + matchEnd = matchStart + 1; + + while (matchEnd < max && state.src.charCodeAt(matchEnd) === 0x60/* ` */) { matchEnd++; } + + if (matchEnd - matchStart === marker.length) { + if (!silent) { + state.push({ + type: 'code', + content: state.src.slice(pos, matchStart) + .replace(/[ \n]+/g, ' ') + .trim(), + block: false, + level: state.level + }); + } + state.pos = matchEnd; + return true; + } + } + + if (!silent) { state.pending += marker; } + state.pos += marker.length; + return true; +}; + +},{}],44:[function(require,module,exports){ +// Process ~~deleted text~~ + +'use strict'; + +module.exports = function del(state, silent) { + var found, + pos, + stack, + max = state.posMax, + start = state.pos, + lastChar, + nextChar; + + if (state.src.charCodeAt(start) !== 0x7E/* ~ */) { return false; } + if (silent) { return false; } // don't run any pairs in validation mode + if (start + 4 >= max) { return false; } + if (state.src.charCodeAt(start + 1) !== 0x7E/* ~ */) { return false; } + if (state.level >= state.options.maxNesting) { return false; } + + lastChar = start > 0 ? state.src.charCodeAt(start - 1) : -1; + nextChar = state.src.charCodeAt(start + 2); + + if (lastChar === 0x7E/* ~ */) { return false; } + if (nextChar === 0x7E/* ~ */) { return false; } + if (nextChar === 0x20 || nextChar === 0x0A) { return false; } + + pos = start + 2; + while (pos < max && state.src.charCodeAt(pos) === 0x7E/* ~ */) { pos++; } + if (pos > start + 3) { + // sequence of 4+ markers taking as literal, same as in a emphasis + state.pos += pos - start; + if (!silent) { state.pending += state.src.slice(start, pos); } + return true; + } + + state.pos = start + 2; + stack = 1; + + while (state.pos + 1 < max) { + if (state.src.charCodeAt(state.pos) === 0x7E/* ~ */) { + if (state.src.charCodeAt(state.pos + 1) === 0x7E/* ~ */) { + lastChar = state.src.charCodeAt(state.pos - 1); + nextChar = state.pos + 2 < max ? state.src.charCodeAt(state.pos + 2) : -1; + if (nextChar !== 0x7E/* ~ */ && lastChar !== 0x7E/* ~ */) { + if (lastChar !== 0x20 && lastChar !== 0x0A) { + // closing '~~' + stack--; + } else if (nextChar !== 0x20 && nextChar !== 0x0A) { + // opening '~~' + stack++; + } // else { + // // standalone ' ~~ ' indented with spaces + // } + if (stack <= 0) { + found = true; + break; + } + } + } + } + + state.parser.skipToken(state); + } + + if (!found) { + // parser failed to find ending tag, so it's not valid emphasis + state.pos = start; + return false; + } + + // found! + state.posMax = state.pos; + state.pos = start + 2; + + if (!silent) { + state.push({ type: 'del_open', level: state.level++ }); + state.parser.tokenize(state); + state.push({ type: 'del_close', level: --state.level }); + } + + state.pos = state.posMax + 2; + state.posMax = max; + return true; +}; + +},{}],45:[function(require,module,exports){ +// Process *this* and _that_ + +'use strict'; + + +function isAlphaNum(code) { + return (code >= 0x30 /* 0 */ && code <= 0x39 /* 9 */) || + (code >= 0x41 /* A */ && code <= 0x5A /* Z */) || + (code >= 0x61 /* a */ && code <= 0x7A /* z */); +} + +// parse sequence of emphasis markers, +// "start" should point at a valid marker +function scanDelims(state, start) { + var pos = start, lastChar, nextChar, count, + can_open = true, + can_close = true, + max = state.posMax, + marker = state.src.charCodeAt(start); + + lastChar = start > 0 ? state.src.charCodeAt(start - 1) : -1; + + while (pos < max && state.src.charCodeAt(pos) === marker) { pos++; } + if (pos >= max) { can_open = false; } + count = pos - start; + + if (count >= 4) { + // sequence of four or more unescaped markers can't start/end an emphasis + can_open = can_close = false; + } else { + nextChar = pos < max ? state.src.charCodeAt(pos) : -1; + + // check whitespace conditions + if (nextChar === 0x20 || nextChar === 0x0A) { can_open = false; } + if (lastChar === 0x20 || lastChar === 0x0A) { can_close = false; } + + if (marker === 0x5F /* _ */) { + // check if we aren't inside the word + if (isAlphaNum(lastChar)) { can_open = false; } + if (isAlphaNum(nextChar)) { can_close = false; } + } + } + + return { + can_open: can_open, + can_close: can_close, + delims: count + }; +} + +module.exports = function emphasis(state, silent) { + var startCount, + count, + found, + oldCount, + newCount, + stack, + res, + max = state.posMax, + start = state.pos, + marker = state.src.charCodeAt(start); + + if (marker !== 0x5F/* _ */ && marker !== 0x2A /* * */) { return false; } + if (silent) { return false; } // don't run any pairs in validation mode + + res = scanDelims(state, start); + startCount = res.delims; + if (!res.can_open) { + state.pos += startCount; + if (!silent) { state.pending += state.src.slice(start, state.pos); } + return true; + } + + if (state.level >= state.options.maxNesting) { return false; } + + state.pos = start + startCount; + stack = [ startCount ]; + + while (state.pos < max) { + if (state.src.charCodeAt(state.pos) === marker) { + res = scanDelims(state, state.pos); + count = res.delims; + if (res.can_close) { + oldCount = stack.pop(); + newCount = count; + + while (oldCount !== newCount) { + if (newCount < oldCount) { + stack.push(oldCount - newCount); + break; + } + + // assert(newCount > oldCount) + newCount -= oldCount; + + if (stack.length === 0) { break; } + state.pos += oldCount; + oldCount = stack.pop(); + } + + if (stack.length === 0) { + startCount = oldCount; + found = true; + break; + } + state.pos += count; + continue; + } + + if (res.can_open) { stack.push(count); } + state.pos += count; + continue; + } + + state.parser.skipToken(state); + } + + if (!found) { + // parser failed to find ending tag, so it's not valid emphasis + state.pos = start; + return false; + } + + // found! + state.posMax = state.pos; + state.pos = start + startCount; + + if (!silent) { + if (startCount === 2 || startCount === 3) { + state.push({ type: 'strong_open', level: state.level++ }); + } + if (startCount === 1 || startCount === 3) { + state.push({ type: 'em_open', level: state.level++ }); + } + + state.parser.tokenize(state); + + if (startCount === 1 || startCount === 3) { + state.push({ type: 'em_close', level: --state.level }); + } + if (startCount === 2 || startCount === 3) { + state.push({ type: 'strong_close', level: --state.level }); + } + } + + state.pos = state.posMax + startCount; + state.posMax = max; + return true; +}; + +},{}],46:[function(require,module,exports){ +// Process html entity - {, ¯, ", ... + +'use strict'; + +var entities = require('../common/entities'); +var has = require('../common/utils').has; +var isValidEntityCode = require('../common/utils').isValidEntityCode; +var fromCodePoint = require('../common/utils').fromCodePoint; + + +var DIGITAL_RE = /^&#((?:x[a-f0-9]{1,8}|[0-9]{1,8}));/i; +var NAMED_RE = /^&([a-z][a-z0-9]{1,31});/i; + + +module.exports = function entity(state, silent) { + var ch, code, match, pos = state.pos, max = state.posMax; + + if (state.src.charCodeAt(pos) !== 0x26/* & */) { return false; } + + if (pos + 1 < max) { + ch = state.src.charCodeAt(pos + 1); + + if (ch === 0x23 /* # */) { + match = state.src.slice(pos).match(DIGITAL_RE); + if (match) { + if (!silent) { + code = match[1][0].toLowerCase() === 'x' ? parseInt(match[1].slice(1), 16) : parseInt(match[1], 10); + state.pending += isValidEntityCode(code) ? fromCodePoint(code) : fromCodePoint(0xFFFD); + } + state.pos += match[0].length; + return true; + } + } else { + match = state.src.slice(pos).match(NAMED_RE); + if (match) { + if (has(entities, match[1])) { + if (!silent) { state.pending += entities[match[1]]; } + state.pos += match[0].length; + return true; + } + } + } + } + + if (!silent) { state.pending += '&'; } + state.pos++; + return true; +}; + +},{"../common/entities":1,"../common/utils":5}],47:[function(require,module,exports){ +// Proceess escaped chars and hardbreaks + +'use strict'; + +var ESCAPED = []; + +for (var i = 0; i < 256; i++) { ESCAPED.push(0); } + +'\\!"#$%&\'()*+,./:;<=>?@[]^_`{|}~-' + .split('').forEach(function(ch) { ESCAPED[ch.charCodeAt(0)] = 1; }); + + +module.exports = function escape(state, silent) { + var ch, pos = state.pos, max = state.posMax; + + if (state.src.charCodeAt(pos) !== 0x5C/* \ */) { return false; } + + pos++; + + if (pos < max) { + ch = state.src.charCodeAt(pos); + + if (ch < 256 && ESCAPED[ch] !== 0) { + if (!silent) { state.pending += state.src[pos]; } + state.pos += 2; + return true; + } + + if (ch === 0x0A) { + if (!silent) { + state.push({ + type: 'hardbreak', + level: state.level + }); + } + + pos++; + // skip leading whitespaces from next line + while (pos < max && state.src.charCodeAt(pos) === 0x20) { pos++; } + + state.pos = pos; + return true; + } + } + + if (!silent) { state.pending += '\\'; } + state.pos++; + return true; +}; + +},{}],48:[function(require,module,exports){ +// Process inline footnotes (^[...]) + +'use strict'; + +var parseLinkLabel = require('../helpers/parse_link_label'); + + +module.exports = function footnote_inline(state, silent) { + var labelStart, + labelEnd, + footnoteId, + oldLength, + max = state.posMax, + start = state.pos; + + if (start + 2 >= max) { return false; } + if (state.src.charCodeAt(start) !== 0x5E/* ^ */) { return false; } + if (state.src.charCodeAt(start + 1) !== 0x5B/* [ */) { return false; } + if (state.level >= state.options.maxNesting) { return false; } + + labelStart = start + 2; + labelEnd = parseLinkLabel(state, start + 1); + + // parser failed to find ']', so it's not a valid note + if (labelEnd < 0) { return false; } + + // We found the end of the link, and know for a fact it's a valid link; + // so all that's left to do is to call tokenizer. + // + if (!silent) { + if (!state.env.footnotes) { state.env.footnotes = {}; } + if (!state.env.footnotes.list) { state.env.footnotes.list = []; } + footnoteId = state.env.footnotes.list.length; + + state.pos = labelStart; + state.posMax = labelEnd; + + state.push({ + type: 'footnote_ref', + id: footnoteId, + level: state.level + }); + state.linkLevel++; + oldLength = state.tokens.length; + state.parser.tokenize(state); + state.env.footnotes.list[footnoteId] = { tokens: state.tokens.splice(oldLength) }; + state.linkLevel--; + } + + state.pos = labelEnd + 1; + state.posMax = max; + return true; +}; + +},{"../helpers/parse_link_label":12}],49:[function(require,module,exports){ +// Process footnote references ([^...]) + +'use strict'; + + +module.exports = function footnote_ref(state, silent) { + var label, + pos, + footnoteId, + footnoteSubId, + max = state.posMax, + start = state.pos; + + // should be at least 4 chars - "[^x]" + if (start + 3 > max) { return false; } + + if (!state.env.footnotes || !state.env.footnotes.refs) { return false; } + if (state.src.charCodeAt(start) !== 0x5B/* [ */) { return false; } + if (state.src.charCodeAt(start + 1) !== 0x5E/* ^ */) { return false; } + if (state.level >= state.options.maxNesting) { return false; } + + for (pos = start + 2; pos < max; pos++) { + if (state.src.charCodeAt(pos) === 0x20) { return false; } + if (state.src.charCodeAt(pos) === 0x0A) { return false; } + if (state.src.charCodeAt(pos) === 0x5D /* ] */) { + break; + } + } + + if (pos === start + 2) { return false; } // no empty footnote labels + if (pos >= max) { return false; } + pos++; + + label = state.src.slice(start + 2, pos - 1); + if (typeof state.env.footnotes.refs[':' + label] === 'undefined') { return false; } + + if (!silent) { + if (!state.env.footnotes.list) { state.env.footnotes.list = []; } + + if (state.env.footnotes.refs[':' + label] < 0) { + footnoteId = state.env.footnotes.list.length; + state.env.footnotes.list[footnoteId] = { label: label, count: 0 }; + state.env.footnotes.refs[':' + label] = footnoteId; + } else { + footnoteId = state.env.footnotes.refs[':' + label]; + } + + footnoteSubId = state.env.footnotes.list[footnoteId].count; + state.env.footnotes.list[footnoteId].count++; + + state.push({ + type: 'footnote_ref', + id: footnoteId, + subId: footnoteSubId, + level: state.level + }); + } + + state.pos = pos; + state.posMax = max; + return true; +}; + +},{}],50:[function(require,module,exports){ +// Process html tags + +'use strict'; + + +var HTML_TAG_RE = require('../common/html_re').HTML_TAG_RE; + + +function isLetter(ch) { + /*eslint no-bitwise:0*/ + var lc = ch | 0x20; // to lower case + return (lc >= 0x61/* a */) && (lc <= 0x7a/* z */); +} + + +module.exports = function htmltag(state, silent) { + var ch, match, max, pos = state.pos; + + if (!state.options.html) { return false; } + + // Check start + max = state.posMax; + if (state.src.charCodeAt(pos) !== 0x3C/* < */ || + pos + 2 >= max) { + return false; + } + + // Quick fail on second char + ch = state.src.charCodeAt(pos + 1); + if (ch !== 0x21/* ! */ && + ch !== 0x3F/* ? */ && + ch !== 0x2F/* / */ && + !isLetter(ch)) { + return false; + } + + match = state.src.slice(pos).match(HTML_TAG_RE); + if (!match) { return false; } + + if (!silent) { + state.push({ + type: 'htmltag', + content: state.src.slice(pos, pos + match[0].length), + level: state.level + }); + } + state.pos += match[0].length; + return true; +}; + +},{"../common/html_re":3}],51:[function(require,module,exports){ +// Process ++inserted text++ + +'use strict'; + +module.exports = function ins(state, silent) { + var found, + pos, + stack, + max = state.posMax, + start = state.pos, + lastChar, + nextChar; + + if (state.src.charCodeAt(start) !== 0x2B/* + */) { return false; } + if (silent) { return false; } // don't run any pairs in validation mode + if (start + 4 >= max) { return false; } + if (state.src.charCodeAt(start + 1) !== 0x2B/* + */) { return false; } + if (state.level >= state.options.maxNesting) { return false; } + + lastChar = start > 0 ? state.src.charCodeAt(start - 1) : -1; + nextChar = state.src.charCodeAt(start + 2); + + if (lastChar === 0x2B/* + */) { return false; } + if (nextChar === 0x2B/* + */) { return false; } + if (nextChar === 0x20 || nextChar === 0x0A) { return false; } + + pos = start + 2; + while (pos < max && state.src.charCodeAt(pos) === 0x2B/* + */) { pos++; } + if (pos !== start + 2) { + // sequence of 3+ markers taking as literal, same as in a emphasis + state.pos += pos - start; + if (!silent) { state.pending += state.src.slice(start, pos); } + return true; + } + + state.pos = start + 2; + stack = 1; + + while (state.pos + 1 < max) { + if (state.src.charCodeAt(state.pos) === 0x2B/* + */) { + if (state.src.charCodeAt(state.pos + 1) === 0x2B/* + */) { + lastChar = state.src.charCodeAt(state.pos - 1); + nextChar = state.pos + 2 < max ? state.src.charCodeAt(state.pos + 2) : -1; + if (nextChar !== 0x2B/* + */ && lastChar !== 0x2B/* + */) { + if (lastChar !== 0x20 && lastChar !== 0x0A) { + // closing '++' + stack--; + } else if (nextChar !== 0x20 && nextChar !== 0x0A) { + // opening '++' + stack++; + } // else { + // // standalone ' ++ ' indented with spaces + // } + if (stack <= 0) { + found = true; + break; + } + } + } + } + + state.parser.skipToken(state); + } + + if (!found) { + // parser failed to find ending tag, so it's not valid emphasis + state.pos = start; + return false; + } + + // found! + state.posMax = state.pos; + state.pos = start + 2; + + if (!silent) { + state.push({ type: 'ins_open', level: state.level++ }); + state.parser.tokenize(state); + state.push({ type: 'ins_close', level: --state.level }); + } + + state.pos = state.posMax + 2; + state.posMax = max; + return true; +}; + +},{}],52:[function(require,module,exports){ +// Process [links]( "stuff") + +'use strict'; + +var parseLinkLabel = require('../helpers/parse_link_label'); +var parseLinkDestination = require('../helpers/parse_link_destination'); +var parseLinkTitle = require('../helpers/parse_link_title'); +var normalizeReference = require('../helpers/normalize_reference'); + + +module.exports = function links(state, silent) { + var labelStart, + labelEnd, + label, + href, + title, + pos, + ref, + code, + isImage = false, + oldPos = state.pos, + max = state.posMax, + start = state.pos, + marker = state.src.charCodeAt(start); + + if (marker === 0x21/* ! */) { + isImage = true; + marker = state.src.charCodeAt(++start); + } + + if (marker !== 0x5B/* [ */) { return false; } + if (state.level >= state.options.maxNesting) { return false; } + + labelStart = start + 1; + labelEnd = parseLinkLabel(state, start); + + // parser failed to find ']', so it's not a valid link + if (labelEnd < 0) { return false; } + + pos = labelEnd + 1; + if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) { + // + // Inline link + // + + // [link]( "title" ) + // ^^ skipping these spaces + pos++; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (code !== 0x20 && code !== 0x0A) { break; } + } + if (pos >= max) { return false; } + + // [link]( "title" ) + // ^^^^^^ parsing link destination + start = pos; + if (parseLinkDestination(state, pos)) { + href = state.linkContent; + pos = state.pos; + } else { + href = ''; + } + + // [link]( "title" ) + // ^^ skipping these spaces + start = pos; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (code !== 0x20 && code !== 0x0A) { break; } + } + + // [link]( "title" ) + // ^^^^^^^ parsing link title + if (pos < max && start !== pos && parseLinkTitle(state, pos)) { + title = state.linkContent; + pos = state.pos; + + // [link]( "title" ) + // ^^ skipping these spaces + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (code !== 0x20 && code !== 0x0A) { break; } + } + } else { + title = ''; + } + + if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) { + state.pos = oldPos; + return false; + } + pos++; + } else { + // + // Link reference + // + + // do not allow nested reference links + if (state.linkLevel > 0) { return false; } + + // [foo] [bar] + // ^^ optional whitespace (can include newlines) + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (code !== 0x20 && code !== 0x0A) { break; } + } + + if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) { + start = pos + 1; + pos = parseLinkLabel(state, pos); + if (pos >= 0) { + label = state.src.slice(start, pos++); + } else { + pos = start - 1; + } + } + + // covers label === '' and label === undefined + // (collapsed reference link and shortcut reference link respectively) + if (!label) { label = state.src.slice(labelStart, labelEnd); } + + ref = state.env.references[normalizeReference(label)]; + if (!ref) { + state.pos = oldPos; + return false; + } + href = ref.href; + title = ref.title; + } + + // + // We found the end of the link, and know for a fact it's a valid link; + // so all that's left to do is to call tokenizer. + // + if (!silent) { + state.pos = labelStart; + state.posMax = labelEnd; + + if (isImage) { + state.push({ + type: 'image', + src: href, + title: title, + alt: state.src.substr(labelStart, labelEnd - labelStart), + level: state.level + }); + } else { + state.push({ + type: 'link_open', + href: href, + title: title, + level: state.level++ + }); + state.linkLevel++; + state.parser.tokenize(state); + state.linkLevel--; + state.push({ type: 'link_close', level: --state.level }); + } + } + + state.pos = pos; + state.posMax = max; + return true; +}; + +},{"../helpers/normalize_reference":10,"../helpers/parse_link_destination":11,"../helpers/parse_link_label":12,"../helpers/parse_link_title":13}],53:[function(require,module,exports){ +// Process ==highlighted text== + +'use strict'; + +module.exports = function del(state, silent) { + var found, + pos, + stack, + max = state.posMax, + start = state.pos, + lastChar, + nextChar; + + if (state.src.charCodeAt(start) !== 0x3D/* = */) { return false; } + if (silent) { return false; } // don't run any pairs in validation mode + if (start + 4 >= max) { return false; } + if (state.src.charCodeAt(start + 1) !== 0x3D/* = */) { return false; } + if (state.level >= state.options.maxNesting) { return false; } + + lastChar = start > 0 ? state.src.charCodeAt(start - 1) : -1; + nextChar = state.src.charCodeAt(start + 2); + + if (lastChar === 0x3D/* = */) { return false; } + if (nextChar === 0x3D/* = */) { return false; } + if (nextChar === 0x20 || nextChar === 0x0A) { return false; } + + pos = start + 2; + while (pos < max && state.src.charCodeAt(pos) === 0x3D/* = */) { pos++; } + if (pos !== start + 2) { + // sequence of 3+ markers taking as literal, same as in a emphasis + state.pos += pos - start; + if (!silent) { state.pending += state.src.slice(start, pos); } + return true; + } + + state.pos = start + 2; + stack = 1; + + while (state.pos + 1 < max) { + if (state.src.charCodeAt(state.pos) === 0x3D/* = */) { + if (state.src.charCodeAt(state.pos + 1) === 0x3D/* = */) { + lastChar = state.src.charCodeAt(state.pos - 1); + nextChar = state.pos + 2 < max ? state.src.charCodeAt(state.pos + 2) : -1; + if (nextChar !== 0x3D/* = */ && lastChar !== 0x3D/* = */) { + if (lastChar !== 0x20 && lastChar !== 0x0A) { + // closing '==' + stack--; + } else if (nextChar !== 0x20 && nextChar !== 0x0A) { + // opening '==' + stack++; + } // else { + // // standalone ' == ' indented with spaces + // } + if (stack <= 0) { + found = true; + break; + } + } + } + } + + state.parser.skipToken(state); + } + + if (!found) { + // parser failed to find ending tag, so it's not valid emphasis + state.pos = start; + return false; + } + + // found! + state.posMax = state.pos; + state.pos = start + 2; + + if (!silent) { + state.push({ type: 'mark_open', level: state.level++ }); + state.parser.tokenize(state); + state.push({ type: 'mark_close', level: --state.level }); + } + + state.pos = state.posMax + 2; + state.posMax = max; + return true; +}; + +},{}],54:[function(require,module,exports){ +// Proceess '\n' + +'use strict'; + +module.exports = function newline(state, silent) { + var pmax, max, pos = state.pos; + + if (state.src.charCodeAt(pos) !== 0x0A/* \n */) { return false; } + + pmax = state.pending.length - 1; + max = state.posMax; + + // ' \n' -> hardbreak + // Lookup in pending chars is bad practice! Don't copy to other rules! + // Pending string is stored in concat mode, indexed lookups will cause + // convertion to flat mode. + if (!silent) { + if (pmax >= 0 && state.pending.charCodeAt(pmax) === 0x20) { + if (pmax >= 1 && state.pending.charCodeAt(pmax - 1) === 0x20) { + state.pending = state.pending.replace(/ +$/, ''); + state.push({ + type: 'hardbreak', + level: state.level + }); + } else { + state.pending = state.pending.slice(0, -1); + state.push({ + type: 'softbreak', + level: state.level + }); + } + + } else { + state.push({ + type: 'softbreak', + level: state.level + }); + } + } + + pos++; + + // skip heading spaces for next line + while (pos < max && state.src.charCodeAt(pos) === 0x20) { pos++; } + + state.pos = pos; + return true; +}; + +},{}],55:[function(require,module,exports){ +// Inline parser state + +'use strict'; + + +function StateInline(src, parserInline, options, env, outTokens) { + this.src = src; + this.env = env; + this.options = options; + this.parser = parserInline; + this.tokens = outTokens; + this.pos = 0; + this.posMax = this.src.length; + this.level = 0; + this.pending = ''; + this.pendingLevel = 0; + + this.cache = []; // Stores { start: end } pairs. Useful for backtrack + // optimization of pairs parse (emphasis, strikes). + + // Link parser state vars + + this.isInLabel = false; // Set true when seek link label - we should disable + // "paired" rules (emphasis, strikes) to not skip + // tailing `]` + + this.linkLevel = 0; // Increment for each nesting link. Used to prevent + // nesting in definitions + + this.linkContent = ''; // Temporary storage for link url + + this.labelUnmatchedScopes = 0; // Track unpaired `[` for link labels + // (backtrack optimization) +} + + +// Flush pending text +// +StateInline.prototype.pushPending = function () { + this.tokens.push({ + type: 'text', + content: this.pending, + level: this.pendingLevel + }); + this.pending = ''; +}; + + +// Push new token to "stream". +// If pending text exists - flush it as text token +// +StateInline.prototype.push = function (token) { + if (this.pending) { + this.pushPending(); + } + + this.tokens.push(token); + this.pendingLevel = this.level; +}; + + +// Store value to cache. +// !!! Implementation has parser-specific optimizations +// !!! keys MUST be integer, >= 0; values MUST be integer, > 0 +// +StateInline.prototype.cacheSet = function (key, val) { + for (var i = this.cache.length; i <= key; i++) { + this.cache.push(0); + } + + this.cache[key] = val; +}; + + +// Get cache value +// +StateInline.prototype.cacheGet = function (key) { + return key < this.cache.length ? this.cache[key] : 0; +}; + + +module.exports = StateInline; + +},{}],56:[function(require,module,exports){ +// Process ~subscript~ + +'use strict'; + +// same as UNESCAPE_MD_RE plus a space +var UNESCAPE_RE = /\\([ \\!"#$%&'()*+,.\/:;<=>?@[\]^_`{|}~-])/g; + +module.exports = function sub(state, silent) { + var found, + content, + max = state.posMax, + start = state.pos; + + if (state.src.charCodeAt(start) !== 0x7E/* ~ */) { return false; } + if (silent) { return false; } // don't run any pairs in validation mode + if (start + 2 >= max) { return false; } + if (state.level >= state.options.maxNesting) { return false; } + + state.pos = start + 1; + + while (state.pos < max) { + if (state.src.charCodeAt(state.pos) === 0x7E/* ~ */) { + found = true; + break; + } + + state.parser.skipToken(state); + } + + if (!found || start + 1 === state.pos) { + state.pos = start; + return false; + } + + content = state.src.slice(start + 1, state.pos); + + // don't allow unescaped spaces/newlines inside + if (content.match(/(^|[^\\])(\\\\)*\s/)) { + state.pos = start; + return false; + } + + // found! + state.posMax = state.pos; + state.pos = start + 1; + + if (!silent) { + state.push({ + type: 'sub', + level: state.level, + content: content.replace(UNESCAPE_RE, '$1') + }); + } + + state.pos = state.posMax + 1; + state.posMax = max; + return true; +}; + +},{}],57:[function(require,module,exports){ +// Process ^superscript^ + +'use strict'; + +// same as UNESCAPE_MD_RE plus a space +var UNESCAPE_RE = /\\([ \\!"#$%&'()*+,.\/:;<=>?@[\]^_`{|}~-])/g; + +module.exports = function sup(state, silent) { + var found, + content, + max = state.posMax, + start = state.pos; + + if (state.src.charCodeAt(start) !== 0x5E/* ^ */) { return false; } + if (silent) { return false; } // don't run any pairs in validation mode + if (start + 2 >= max) { return false; } + if (state.level >= state.options.maxNesting) { return false; } + + state.pos = start + 1; + + while (state.pos < max) { + if (state.src.charCodeAt(state.pos) === 0x5E/* ^ */) { + found = true; + break; + } + + state.parser.skipToken(state); + } + + if (!found || start + 1 === state.pos) { + state.pos = start; + return false; + } + + content = state.src.slice(start + 1, state.pos); + + // don't allow unescaped spaces/newlines inside + if (content.match(/(^|[^\\])(\\\\)*\s/)) { + state.pos = start; + return false; + } + + // found! + state.posMax = state.pos; + state.pos = start + 1; + + if (!silent) { + state.push({ + type: 'sup', + level: state.level, + content: content.replace(UNESCAPE_RE, '$1') + }); + } + + state.pos = state.posMax + 1; + state.posMax = max; + return true; +}; + +},{}],58:[function(require,module,exports){ +// Skip text characters for text token, place those to pending buffer +// and increment current pos + +'use strict'; + + +// Rule to skip pure text +// '{}$%@~+=:' reserved for extentions + +function isTerminatorChar(ch) { + switch (ch) { + case 0x0A/* \n */: + case 0x5C/* \ */: + case 0x60/* ` */: + case 0x2A/* * */: + case 0x5F/* _ */: + case 0x5E/* ^ */: + case 0x5B/* [ */: + case 0x5D/* ] */: + case 0x21/* ! */: + case 0x26/* & */: + case 0x3C/* < */: + case 0x3E/* > */: + case 0x7B/* { */: + case 0x7D/* } */: + case 0x24/* $ */: + case 0x25/* % */: + case 0x40/* @ */: + case 0x7E/* ~ */: + case 0x2B/* + */: + case 0x3D/* = */: + case 0x3A/* : */: + return true; + default: + return false; + } +} + +module.exports = function text(state, silent) { + var pos = state.pos; + + while (pos < state.posMax && !isTerminatorChar(state.src.charCodeAt(pos))) { + pos++; + } + + if (pos === state.pos) { return false; } + + if (!silent) { state.pending += state.src.slice(state.pos, pos); } + + state.pos = pos; + + return true; +}; + +},{}],59:[function(require,module,exports){ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define([], function () { + return (root.returnExportsGlobal = factory()); + }); + } else if (typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like enviroments that support module.exports, + // like Node. + module.exports = factory(); + } else { + root['Autolinker'] = factory(); + } +}(this, function () { + + /*! + * Autolinker.js + * 0.15.0 + * + * Copyright(c) 2014 Gregory Jacobs + * MIT Licensed. http://www.opensource.org/licenses/mit-license.php + * + * https://github.com/gregjacobs/Autolinker.js + */ + /** + * @class Autolinker + * @extends Object + * + * Utility class used to process a given string of text, and wrap the URLs, email addresses, and Twitter handles in + * the appropriate anchor (<a>) tags to turn them into links. + * + * Any of the configuration options may be provided in an Object (map) provided to the Autolinker constructor, which + * will configure how the {@link #link link()} method will process the links. + * + * For example: + * + * var autolinker = new Autolinker( { + * newWindow : false, + * truncate : 30 + * } ); + * + * var html = autolinker.link( "Joe went to www.yahoo.com" ); + * // produces: 'Joe went to yahoo.com' + * + * + * The {@link #static-link static link()} method may also be used to inline options into a single call, which may + * be more convenient for one-off uses. For example: + * + * var html = Autolinker.link( "Joe went to www.yahoo.com", { + * newWindow : false, + * truncate : 30 + * } ); + * // produces: 'Joe went to yahoo.com' + * + * + * ## Custom Replacements of Links + * + * If the configuration options do not provide enough flexibility, a {@link #replaceFn} may be provided to fully customize + * the output of Autolinker. This function is called once for each URL/Email/Twitter handle match that is encountered. + * + * For example: + * + * var input = "..."; // string with URLs, Email Addresses, and Twitter Handles + * + * var linkedText = Autolinker.link( input, { + * replaceFn : function( autolinker, match ) { + * console.log( "href = ", match.getAnchorHref() ); + * console.log( "text = ", match.getAnchorText() ); + * + * switch( match.getType() ) { + * case 'url' : + * console.log( "url: ", match.getUrl() ); + * + * if( match.getUrl().indexOf( 'mysite.com' ) === -1 ) { + * var tag = autolinker.getTagBuilder().build( match ); // returns an `Autolinker.HtmlTag` instance, which provides mutator methods for easy changes + * tag.setAttr( 'rel', 'nofollow' ); + * tag.addClass( 'external-link' ); + * + * return tag; + * + * } else { + * return true; // let Autolinker perform its normal anchor tag replacement + * } + * + * case 'email' : + * var email = match.getEmail(); + * console.log( "email: ", email ); + * + * if( email === "my@own.address" ) { + * return false; // don't auto-link this particular email address; leave as-is + * } else { + * return; // no return value will have Autolinker perform its normal anchor tag replacement (same as returning `true`) + * } + * + * case 'twitter' : + * var twitterHandle = match.getTwitterHandle(); + * console.log( twitterHandle ); + * + * return '' + twitterHandle + ''; + * } + * } + * } ); + * + * + * The function may return the following values: + * + * - `true` (Boolean): Allow Autolinker to replace the match as it normally would. + * - `false` (Boolean): Do not replace the current match at all - leave as-is. + * - Any String: If a string is returned from the function, the string will be used directly as the replacement HTML for + * the match. + * - An {@link Autolinker.HtmlTag} instance, which can be used to build/modify an HTML tag before writing out its HTML text. + * + * @constructor + * @param {Object} [config] The configuration options for the Autolinker instance, specified in an Object (map). + */ + var Autolinker = function( cfg ) { + Autolinker.Util.assign( this, cfg ); // assign the properties of `cfg` onto the Autolinker instance. Prototype properties will be used for missing configs. + + this.matchValidator = new Autolinker.MatchValidator(); + }; + + + Autolinker.prototype = { + constructor : Autolinker, // fix constructor property + + /** + * @cfg {Boolean} urls + * + * `true` if miscellaneous URLs should be automatically linked, `false` if they should not be. + */ + urls : true, + + /** + * @cfg {Boolean} email + * + * `true` if email addresses should be automatically linked, `false` if they should not be. + */ + email : true, + + /** + * @cfg {Boolean} twitter + * + * `true` if Twitter handles ("@example") should be automatically linked, `false` if they should not be. + */ + twitter : true, + + /** + * @cfg {Boolean} newWindow + * + * `true` if the links should open in a new window, `false` otherwise. + */ + newWindow : true, + + /** + * @cfg {Boolean} stripPrefix + * + * `true` if 'http://' or 'https://' and/or the 'www.' should be stripped from the beginning of URL links' text, + * `false` otherwise. + */ + stripPrefix : true, + + /** + * @cfg {Number} truncate + * + * A number for how many characters long URLs/emails/twitter handles should be truncated to inside the text of + * a link. If the URL/email/twitter is over this number of characters, it will be truncated to this length by + * adding a two period ellipsis ('..') to the end of the string. + * + * For example: A url like 'http://www.yahoo.com/some/long/path/to/a/file' truncated to 25 characters might look + * something like this: 'yahoo.com/some/long/pat..' + */ + + /** + * @cfg {String} className + * + * A CSS class name to add to the generated links. This class will be added to all links, as well as this class + * plus url/email/twitter suffixes for styling url/email/twitter links differently. + * + * For example, if this config is provided as "myLink", then: + * + * - URL links will have the CSS classes: "myLink myLink-url" + * - Email links will have the CSS classes: "myLink myLink-email", and + * - Twitter links will have the CSS classes: "myLink myLink-twitter" + */ + className : "", + + /** + * @cfg {Function} replaceFn + * + * A function to individually process each URL/Email/Twitter match found in the input string. + * + * See the class's description for usage. + * + * This function is called with the following parameters: + * + * @cfg {Autolinker} replaceFn.autolinker The Autolinker instance, which may be used to retrieve child objects from (such + * as the instance's {@link #getTagBuilder tag builder}). + * @cfg {Autolinker.match.Match} replaceFn.match The Match instance which can be used to retrieve information about the + * {@link Autolinker.match.Url URL}/{@link Autolinker.match.Email email}/{@link Autolinker.match.Twitter Twitter} + * match that the `replaceFn` is currently processing. + */ + + + /** + * @private + * @property {RegExp} htmlCharacterEntitiesRegex + * + * The regular expression that matches common HTML character entities. + * + * Ignoring & as it could be part of a query string -- handling it separately. + */ + htmlCharacterEntitiesRegex: /( | |<|<|>|>)/gi, + + /** + * @private + * @property {RegExp} matcherRegex + * + * The regular expression that matches URLs, email addresses, and Twitter handles. + * + * This regular expression has the following capturing groups: + * + * 1. Group that is used to determine if there is a Twitter handle match (i.e. \@someTwitterUser). Simply check for its + * existence to determine if there is a Twitter handle match. The next couple of capturing groups give information + * about the Twitter handle match. + * 2. The whitespace character before the \@sign in a Twitter handle. This is needed because there are no lookbehinds in + * JS regular expressions, and can be used to reconstruct the original string in a replace(). + * 3. The Twitter handle itself in a Twitter match. If the match is '@someTwitterUser', the handle is 'someTwitterUser'. + * 4. Group that matches an email address. Used to determine if the match is an email address, as well as holding the full + * address. Ex: 'me@my.com' + * 5. Group that matches a URL in the input text. Ex: 'http://google.com', 'www.google.com', or just 'google.com'. + * This also includes a path, url parameters, or hash anchors. Ex: google.com/path/to/file?q1=1&q2=2#myAnchor + * 6. Group that matches a protocol URL (i.e. 'http://google.com'). This is used to match protocol URLs with just a single + * word, like 'http://localhost', where we won't double check that the domain name has at least one '.' in it. + * 7. A protocol-relative ('//') match for the case of a 'www.' prefixed URL. Will be an empty string if it is not a + * protocol-relative match. We need to know the character before the '//' in order to determine if it is a valid match + * or the // was in a string we don't want to auto-link. + * 8. A protocol-relative ('//') match for the case of a known TLD prefixed URL. Will be an empty string if it is not a + * protocol-relative match. See #6 for more info. + */ + matcherRegex : (function() { + var twitterRegex = /(^|[^\w])@(\w{1,15})/, // For matching a twitter handle. Ex: @gregory_jacobs + + emailRegex = /(?:[\-;:&=\+\$,\w\.]+@)/, // something@ for email addresses (a.k.a. local-part) + + protocolRegex = /(?:[A-Za-z][-.+A-Za-z0-9]+:(?![A-Za-z][-.+A-Za-z0-9]+:\/\/)(?!\d+\/?)(?:\/\/)?)/, // match protocol, allow in format "http://" or "mailto:". However, do not match the first part of something like 'link:http://www.google.com' (i.e. don't match "link:"). Also, make sure we don't interpret 'google.com:8000' as if 'google.com' was a protocol here (i.e. ignore a trailing port number in this regex) + wwwRegex = /(?:www\.)/, // starting with 'www.' + domainNameRegex = /[A-Za-z0-9\.\-]*[A-Za-z0-9\-]/, // anything looking at all like a domain, non-unicode domains, not ending in a period + tldRegex = /\.(?:international|construction|contractors|enterprises|photography|productions|foundation|immobilien|industries|management|properties|technology|christmas|community|directory|education|equipment|institute|marketing|solutions|vacations|bargains|boutique|builders|catering|cleaning|clothing|computer|democrat|diamonds|graphics|holdings|lighting|partners|plumbing|supplies|training|ventures|academy|careers|company|cruises|domains|exposed|flights|florist|gallery|guitars|holiday|kitchen|neustar|okinawa|recipes|rentals|reviews|shiksha|singles|support|systems|agency|berlin|camera|center|coffee|condos|dating|estate|events|expert|futbol|kaufen|luxury|maison|monash|museum|nagoya|photos|repair|report|social|supply|tattoo|tienda|travel|viajes|villas|vision|voting|voyage|actor|build|cards|cheap|codes|dance|email|glass|house|mango|ninja|parts|photo|shoes|solar|today|tokyo|tools|watch|works|aero|arpa|asia|best|bike|blue|buzz|camp|club|cool|coop|farm|fish|gift|guru|info|jobs|kiwi|kred|land|limo|link|menu|mobi|moda|name|pics|pink|post|qpon|rich|ruhr|sexy|tips|vote|voto|wang|wien|wiki|zone|bar|bid|biz|cab|cat|ceo|com|edu|gov|int|kim|mil|net|onl|org|pro|pub|red|tel|uno|wed|xxx|xyz|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)\b/, // match our known top level domains (TLDs) + + // Allow optional path, query string, and hash anchor, not ending in the following characters: "?!:,.;" + // http://blog.codinghorror.com/the-problem-with-urls/ + urlSuffixRegex = /[\-A-Za-z0-9+&@#\/%=~_()|'$*\[\]?!:,.;]*[\-A-Za-z0-9+&@#\/%=~_()|'$*\[\]]/; + + return new RegExp( [ + '(', // *** Capturing group $1, which can be used to check for a twitter handle match. Use group $3 for the actual twitter handle though. $2 may be used to reconstruct the original string in a replace() + // *** Capturing group $2, which matches the whitespace character before the '@' sign (needed because of no lookbehinds), and + // *** Capturing group $3, which matches the actual twitter handle + twitterRegex.source, + ')', + + '|', + + '(', // *** Capturing group $4, which is used to determine an email match + emailRegex.source, + domainNameRegex.source, + tldRegex.source, + ')', + + '|', + + '(', // *** Capturing group $5, which is used to match a URL + '(?:', // parens to cover match for protocol (optional), and domain + '(', // *** Capturing group $6, for a protocol-prefixed url (ex: http://google.com) + protocolRegex.source, + domainNameRegex.source, + ')', + + '|', + + '(?:', // non-capturing paren for a 'www.' prefixed url (ex: www.google.com) + '(.?//)?', // *** Capturing group $7 for an optional protocol-relative URL. Must be at the beginning of the string or start with a non-word character + wwwRegex.source, + domainNameRegex.source, + ')', + + '|', + + '(?:', // non-capturing paren for known a TLD url (ex: google.com) + '(.?//)?', // *** Capturing group $8 for an optional protocol-relative URL. Must be at the beginning of the string or start with a non-word character + domainNameRegex.source, + tldRegex.source, + ')', + ')', + + '(?:' + urlSuffixRegex.source + ')?', // match for path, query string, and/or hash anchor - optional + ')' + ].join( "" ), 'gi' ); + } )(), + + /** + * @private + * @property {RegExp} charBeforeProtocolRelMatchRegex + * + * The regular expression used to retrieve the character before a protocol-relative URL match. + * + * This is used in conjunction with the {@link #matcherRegex}, which needs to grab the character before a protocol-relative + * '//' due to the lack of a negative look-behind in JavaScript regular expressions. The character before the match is stripped + * from the URL. + */ + charBeforeProtocolRelMatchRegex : /^(.)?\/\//, + + /** + * @private + * @property {Autolinker.MatchValidator} matchValidator + * + * The MatchValidator object, used to filter out any false positives from the {@link #matcherRegex}. See + * {@link Autolinker.MatchValidator} for details. + */ + + /** + * @private + * @property {Autolinker.HtmlParser} htmlParser + * + * The HtmlParser instance used to skip over HTML tags, while finding text nodes to process. This is lazily instantiated + * in the {@link #getHtmlParser} method. + */ + + /** + * @private + * @property {Autolinker.AnchorTagBuilder} tagBuilder + * + * The AnchorTagBuilder instance used to build the URL/email/Twitter replacement anchor tags. This is lazily instantiated + * in the {@link #getTagBuilder} method. + */ + + + /** + * Automatically links URLs, email addresses, and Twitter handles found in the given chunk of HTML. + * Does not link URLs found within HTML tags. + * + * For instance, if given the text: `You should go to http://www.yahoo.com`, then the result + * will be `You should go to <a href="http://www.yahoo.com">http://www.yahoo.com</a>` + * + * This method finds the text around any HTML elements in the input `textOrHtml`, which will be the text that is processed. + * Any original HTML elements will be left as-is, as well as the text that is already wrapped in anchor (<a>) tags. + * + * @param {String} textOrHtml The HTML or text to link URLs, email addresses, and Twitter handles within (depending on if + * the {@link #urls}, {@link #email}, and {@link #twitter} options are enabled). + * @return {String} The HTML, with URLs/emails/Twitter handles automatically linked. + */ + link : function( textOrHtml ) { + var me = this, // for closure + htmlParser = this.getHtmlParser(), + htmlCharacterEntitiesRegex = this.htmlCharacterEntitiesRegex, + anchorTagStackCount = 0, // used to only process text around anchor tags, and any inner text/html they may have + resultHtml = []; + + htmlParser.parse( textOrHtml, { + // Process HTML nodes in the input `textOrHtml` + processHtmlNode : function( tagText, tagName, isClosingTag ) { + if( tagName === 'a' ) { + if( !isClosingTag ) { // it's the start tag + anchorTagStackCount++; + } else { // it's the end tag + anchorTagStackCount = Math.max( anchorTagStackCount - 1, 0 ); // attempt to handle extraneous tags by making sure the stack count never goes below 0 + } + } + resultHtml.push( tagText ); // now add the text of the tag itself verbatim + }, + + // Process text nodes in the input `textOrHtml` + processTextNode : function( text ) { + if( anchorTagStackCount === 0 ) { + // If we're not within an tag, process the text node + var unescapedText = Autolinker.Util.splitAndCapture( text, htmlCharacterEntitiesRegex ); // split at HTML entities, but include the HTML entities in the results array + + for ( var i = 0, len = unescapedText.length; i < len; i++ ) { + var textToProcess = unescapedText[ i ], + processedTextNode = me.processTextNode( textToProcess ); + + resultHtml.push( processedTextNode ); + } + + } else { + // `text` is within an tag, simply append the text - we do not want to autolink anything + // already within an ... tag + resultHtml.push( text ); + } + } + } ); + + return resultHtml.join( "" ); + }, + + + /** + * Lazily instantiates and returns the {@link #htmlParser} instance for this Autolinker instance. + * + * @protected + * @return {Autolinker.HtmlParser} + */ + getHtmlParser : function() { + var htmlParser = this.htmlParser; + + if( !htmlParser ) { + htmlParser = this.htmlParser = new Autolinker.HtmlParser(); + } + + return htmlParser; + }, + + + /** + * Returns the {@link #tagBuilder} instance for this Autolinker instance, lazily instantiating it + * if it does not yet exist. + * + * This method may be used in a {@link #replaceFn} to generate the {@link Autolinker.HtmlTag HtmlTag} instance that + * Autolinker would normally generate, and then allow for modifications before returning it. For example: + * + * var html = Autolinker.link( "Test google.com", { + * replaceFn : function( autolinker, match ) { + * var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance + * tag.setAttr( 'rel', 'nofollow' ); + * + * return tag; + * } + * } ); + * + * // generated html: + * // Test google.com + * + * @return {Autolinker.AnchorTagBuilder} + */ + getTagBuilder : function() { + var tagBuilder = this.tagBuilder; + + if( !tagBuilder ) { + tagBuilder = this.tagBuilder = new Autolinker.AnchorTagBuilder( { + newWindow : this.newWindow, + truncate : this.truncate, + className : this.className + } ); + } + + return tagBuilder; + }, + + + /** + * Process the text that lies inbetween HTML tags. This method does the actual wrapping of URLs with + * anchor tags. + * + * @private + * @param {String} text The text to auto-link. + * @return {String} The text with anchor tags auto-filled. + */ + processTextNode : function( text ) { + var me = this; // for closure + + return text.replace( this.matcherRegex, function( matchStr, $1, $2, $3, $4, $5, $6, $7, $8 ) { + var matchDescObj = me.processCandidateMatch( matchStr, $1, $2, $3, $4, $5, $6, $7, $8 ); // match description object + + // Return out with no changes for match types that are disabled (url, email, twitter), or for matches that are + // invalid (false positives from the matcherRegex, which can't use look-behinds since they are unavailable in JS). + if( !matchDescObj ) { + return matchStr; + + } else { + // Generate the replacement text for the match + var matchReturnVal = me.createMatchReturnVal( matchDescObj.match, matchDescObj.matchStr ); + return matchDescObj.prefixStr + matchReturnVal + matchDescObj.suffixStr; + } + } ); + }, + + + /** + * Processes a candidate match from the {@link #matcherRegex}. + * + * Not all matches found by the regex are actual URL/email/Twitter matches, as determined by the {@link #matchValidator}. In + * this case, the method returns `null`. Otherwise, a valid Object with `prefixStr`, `match`, and `suffixStr` is returned. + * + * @private + * @param {String} matchStr The full match that was found by the {@link #matcherRegex}. + * @param {String} twitterMatch The matched text of a Twitter handle, if the match is a Twitter match. + * @param {String} twitterHandlePrefixWhitespaceChar The whitespace char before the @ sign in a Twitter handle match. This + * is needed because of no lookbehinds in JS regexes, and is need to re-include the character for the anchor tag replacement. + * @param {String} twitterHandle The actual Twitter user (i.e the word after the @ sign in a Twitter match). + * @param {String} emailAddressMatch The matched email address for an email address match. + * @param {String} urlMatch The matched URL string for a URL match. + * @param {String} protocolUrlMatch The match URL string for a protocol match. Ex: 'http://yahoo.com'. This is used to match + * something like 'http://localhost', where we won't double check that the domain name has at least one '.' in it. + * @param {String} wwwProtocolRelativeMatch The '//' for a protocol-relative match from a 'www' url, with the character that + * comes before the '//'. + * @param {String} tldProtocolRelativeMatch The '//' for a protocol-relative match from a TLD (top level domain) match, with + * the character that comes before the '//'. + * + * @return {Object} A "match description object". This will be `null` if the match was invalid, or if a match type is disabled. + * Otherwise, this will be an Object (map) with the following properties: + * @return {String} return.prefixStr The char(s) that should be prepended to the replacement string. These are char(s) that + * were needed to be included from the regex match that were ignored by processing code, and should be re-inserted into + * the replacement stream. + * @return {String} return.suffixStr The char(s) that should be appended to the replacement string. These are char(s) that + * were needed to be included from the regex match that were ignored by processing code, and should be re-inserted into + * the replacement stream. + * @return {String} return.matchStr The `matchStr`, fixed up to remove characters that are no longer needed (which have been + * added to `prefixStr` and `suffixStr`). + * @return {Autolinker.match.Match} return.match The Match object that represents the match that was found. + */ + processCandidateMatch : function( + matchStr, twitterMatch, twitterHandlePrefixWhitespaceChar, twitterHandle, + emailAddressMatch, urlMatch, protocolUrlMatch, wwwProtocolRelativeMatch, tldProtocolRelativeMatch + ) { + var protocolRelativeMatch = wwwProtocolRelativeMatch || tldProtocolRelativeMatch, + match, // Will be an Autolinker.match.Match object + + prefixStr = "", // A string to use to prefix the anchor tag that is created. This is needed for the Twitter handle match + suffixStr = ""; // A string to suffix the anchor tag that is created. This is used if there is a trailing parenthesis that should not be auto-linked. + + + // Return out with `null` for match types that are disabled (url, email, twitter), or for matches that are + // invalid (false positives from the matcherRegex, which can't use look-behinds since they are unavailable in JS). + if( + ( twitterMatch && !this.twitter ) || ( emailAddressMatch && !this.email ) || ( urlMatch && !this.urls ) || + !this.matchValidator.isValidMatch( urlMatch, protocolUrlMatch, protocolRelativeMatch ) + ) { + return null; + } + + // Handle a closing parenthesis at the end of the match, and exclude it if there is not a matching open parenthesis + // in the match itself. + if( this.matchHasUnbalancedClosingParen( matchStr ) ) { + matchStr = matchStr.substr( 0, matchStr.length - 1 ); // remove the trailing ")" + suffixStr = ")"; // this will be added after the generated tag + } + + + if( emailAddressMatch ) { + match = new Autolinker.match.Email( { matchedText: matchStr, email: emailAddressMatch } ); + + } else if( twitterMatch ) { + // fix up the `matchStr` if there was a preceding whitespace char, which was needed to determine the match + // itself (since there are no look-behinds in JS regexes) + if( twitterHandlePrefixWhitespaceChar ) { + prefixStr = twitterHandlePrefixWhitespaceChar; + matchStr = matchStr.slice( 1 ); // remove the prefixed whitespace char from the match + } + match = new Autolinker.match.Twitter( { matchedText: matchStr, twitterHandle: twitterHandle } ); + + } else { // url match + // If it's a protocol-relative '//' match, remove the character before the '//' (which the matcherRegex needed + // to match due to the lack of a negative look-behind in JavaScript regular expressions) + if( protocolRelativeMatch ) { + var charBeforeMatch = protocolRelativeMatch.match( this.charBeforeProtocolRelMatchRegex )[ 1 ] || ""; + + if( charBeforeMatch ) { // fix up the `matchStr` if there was a preceding char before a protocol-relative match, which was needed to determine the match itself (since there are no look-behinds in JS regexes) + prefixStr = charBeforeMatch; + matchStr = matchStr.slice( 1 ); // remove the prefixed char from the match + } + } + + match = new Autolinker.match.Url( { + matchedText : matchStr, + url : matchStr, + protocolUrlMatch : !!protocolUrlMatch, + protocolRelativeMatch : !!protocolRelativeMatch, + stripPrefix : this.stripPrefix + } ); + } + + return { + prefixStr : prefixStr, + suffixStr : suffixStr, + matchStr : matchStr, + match : match + }; + }, + + + /** + * Determines if a match found has an unmatched closing parenthesis. If so, this parenthesis will be removed + * from the match itself, and appended after the generated anchor tag in {@link #processTextNode}. + * + * A match may have an extra closing parenthesis at the end of the match because the regular expression must include parenthesis + * for URLs such as "wikipedia.com/something_(disambiguation)", which should be auto-linked. + * + * However, an extra parenthesis *will* be included when the URL itself is wrapped in parenthesis, such as in the case of + * "(wikipedia.com/something_(disambiguation))". In this case, the last closing parenthesis should *not* be part of the URL + * itself, and this method will return `true`. + * + * @private + * @param {String} matchStr The full match string from the {@link #matcherRegex}. + * @return {Boolean} `true` if there is an unbalanced closing parenthesis at the end of the `matchStr`, `false` otherwise. + */ + matchHasUnbalancedClosingParen : function( matchStr ) { + var lastChar = matchStr.charAt( matchStr.length - 1 ); + + if( lastChar === ')' ) { + var openParensMatch = matchStr.match( /\(/g ), + closeParensMatch = matchStr.match( /\)/g ), + numOpenParens = ( openParensMatch && openParensMatch.length ) || 0, + numCloseParens = ( closeParensMatch && closeParensMatch.length ) || 0; + + if( numOpenParens < numCloseParens ) { + return true; + } + } + + return false; + }, + + + /** + * Creates the return string value for a given match in the input string, for the {@link #processTextNode} method. + * + * This method handles the {@link #replaceFn}, if one was provided. + * + * @private + * @param {Autolinker.match.Match} match The Match object that represents the match. + * @param {String} matchStr The original match string, after having been preprocessed to fix match edge cases (see + * the `prefixStr` and `suffixStr` vars in {@link #processTextNode}. + * @return {String} The string that the `match` should be replaced with. This is usually the anchor tag string, but + * may be the `matchStr` itself if the match is not to be replaced. + */ + createMatchReturnVal : function( match, matchStr ) { + // Handle a custom `replaceFn` being provided + var replaceFnResult; + if( this.replaceFn ) { + replaceFnResult = this.replaceFn.call( this, this, match ); // Autolinker instance is the context, and the first arg + } + + if( typeof replaceFnResult === 'string' ) { + return replaceFnResult; // `replaceFn` returned a string, use that + + } else if( replaceFnResult === false ) { + return matchStr; // no replacement for the match + + } else if( replaceFnResult instanceof Autolinker.HtmlTag ) { + return replaceFnResult.toString(); + + } else { // replaceFnResult === true, or no/unknown return value from function + // Perform Autolinker's default anchor tag generation + var tagBuilder = this.getTagBuilder(), + anchorTag = tagBuilder.build( match ); // returns an Autolinker.HtmlTag instance + + return anchorTag.toString(); + } + } + + }; + + + /** + * Automatically links URLs, email addresses, and Twitter handles found in the given chunk of HTML. + * Does not link URLs found within HTML tags. + * + * For instance, if given the text: `You should go to http://www.yahoo.com`, then the result + * will be `You should go to <a href="http://www.yahoo.com">http://www.yahoo.com</a>` + * + * Example: + * + * var linkedText = Autolinker.link( "Go to google.com", { newWindow: false } ); + * // Produces: "Go to google.com" + * + * @static + * @param {String} textOrHtml The HTML or text to find URLs, email addresses, and Twitter handles within (depending on if + * the {@link #urls}, {@link #email}, and {@link #twitter} options are enabled). + * @param {Object} [options] Any of the configuration options for the Autolinker class, specified in an Object (map). + * See the class description for an example call. + * @return {String} The HTML text, with URLs automatically linked + */ + Autolinker.link = function( textOrHtml, options ) { + var autolinker = new Autolinker( options ); + return autolinker.link( textOrHtml ); + }; + + + // Namespace for `match` classes + Autolinker.match = {}; + /*global Autolinker */ + /*jshint eqnull:true, boss:true */ + /** + * @class Autolinker.Util + * @singleton + * + * A few utility methods for Autolinker. + */ + Autolinker.Util = { + + /** + * @property {Function} abstractMethod + * + * A function object which represents an abstract method. + */ + abstractMethod : function() { throw "abstract"; }, + + + /** + * Assigns (shallow copies) the properties of `src` onto `dest`. + * + * @param {Object} dest The destination object. + * @param {Object} src The source object. + * @return {Object} The destination object (`dest`) + */ + assign : function( dest, src ) { + for( var prop in src ) { + if( src.hasOwnProperty( prop ) ) { + dest[ prop ] = src[ prop ]; + } + } + + return dest; + }, + + + /** + * Extends `superclass` to create a new subclass, adding the `protoProps` to the new subclass's prototype. + * + * @param {Function} superclass The constructor function for the superclass. + * @param {Object} protoProps The methods/properties to add to the subclass's prototype. This may contain the + * special property `constructor`, which will be used as the new subclass's constructor function. + * @return {Function} The new subclass function. + */ + extend : function( superclass, protoProps ) { + var superclassProto = superclass.prototype; + + var F = function() {}; + F.prototype = superclassProto; + + var subclass; + if( protoProps.hasOwnProperty( 'constructor' ) ) { + subclass = protoProps.constructor; + } else { + subclass = function() { superclassProto.constructor.apply( this, arguments ); }; + } + + var subclassProto = subclass.prototype = new F(); // set up prototype chain + subclassProto.constructor = subclass; // fix constructor property + subclassProto.superclass = superclassProto; + + delete protoProps.constructor; // don't re-assign constructor property to the prototype, since a new function may have been created (`subclass`), which is now already there + Autolinker.Util.assign( subclassProto, protoProps ); + + return subclass; + }, + + + /** + * Truncates the `str` at `len - ellipsisChars.length`, and adds the `ellipsisChars` to the + * end of the string (by default, two periods: '..'). If the `str` length does not exceed + * `len`, the string will be returned unchanged. + * + * @param {String} str The string to truncate and add an ellipsis to. + * @param {Number} truncateLen The length to truncate the string at. + * @param {String} [ellipsisChars=..] The ellipsis character(s) to add to the end of `str` + * when truncated. Defaults to '..' + */ + ellipsis : function( str, truncateLen, ellipsisChars ) { + if( str.length > truncateLen ) { + ellipsisChars = ( ellipsisChars == null ) ? '..' : ellipsisChars; + str = str.substring( 0, truncateLen - ellipsisChars.length ) + ellipsisChars; + } + return str; + }, + + + /** + * Supports `Array.prototype.indexOf()` functionality for old IE (IE8 and below). + * + * @param {Array} arr The array to find an element of. + * @param {*} element The element to find in the array, and return the index of. + * @return {Number} The index of the `element`, or -1 if it was not found. + */ + indexOf : function( arr, element ) { + if( Array.prototype.indexOf ) { + return arr.indexOf( element ); + + } else { + for( var i = 0, len = arr.length; i < len; i++ ) { + if( arr[ i ] === element ) return i; + } + return -1; + } + }, + + + + /** + * Performs the functionality of what modern browsers do when `String.prototype.split()` is called + * with a regular expression that contains capturing parenthesis. + * + * For example: + * + * // Modern browsers: + * "a,b,c".split( /(,)/ ); // --> [ 'a', ',', 'b', ',', 'c' ] + * + * // Old IE (including IE8): + * "a,b,c".split( /(,)/ ); // --> [ 'a', 'b', 'c' ] + * + * This method emulates the functionality of modern browsers for the old IE case. + * + * @param {String} str The string to split. + * @param {RegExp} splitRegex The regular expression to split the input `str` on. The splitting + * character(s) will be spliced into the array, as in the "modern browsers" example in the + * description of this method. + * Note #1: the supplied regular expression **must** have the 'g' flag specified. + * Note #2: for simplicity's sake, the regular expression does not need + * to contain capturing parenthesis - it will be assumed that any match has them. + * @return {String[]} The split array of strings, with the splitting character(s) included. + */ + splitAndCapture : function( str, splitRegex ) { + if( !splitRegex.global ) throw new Error( "`splitRegex` must have the 'g' flag set" ); + + var result = [], + lastIdx = 0, + match; + + while( match = splitRegex.exec( str ) ) { + result.push( str.substring( lastIdx, match.index ) ); + result.push( match[ 0 ] ); // push the splitting char(s) + + lastIdx = match.index + match[ 0 ].length; + } + result.push( str.substring( lastIdx ) ); + + return result; + } + + }; + /*global Autolinker */ + /** + * @private + * @class Autolinker.HtmlParser + * @extends Object + * + * An HTML parser implementation which simply walks an HTML string and calls the provided visitor functions to process + * HTML and text nodes. + * + * Autolinker uses this to only link URLs/emails/Twitter handles within text nodes, basically ignoring HTML tags. + */ + Autolinker.HtmlParser = Autolinker.Util.extend( Object, { + + /** + * @private + * @property {RegExp} htmlRegex + * + * The regular expression used to pull out HTML tags from a string. Handles namespaced HTML tags and + * attribute names, as specified by http://www.w3.org/TR/html-markup/syntax.html. + * + * Capturing groups: + * + * 1. The "!DOCTYPE" tag name, if a tag is a <!DOCTYPE> tag. + * 2. If it is an end tag, this group will have the '/'. + * 3. The tag name for all tags (other than the <!DOCTYPE> tag) + */ + htmlRegex : (function() { + var tagNameRegex = /[0-9a-zA-Z][0-9a-zA-Z:]*/, + attrNameRegex = /[^\s\0"'>\/=\x01-\x1F\x7F]+/, // the unicode range accounts for excluding control chars, and the delete char + attrValueRegex = /(?:".*?"|'.*?'|[^'"=<>`\s]+)/, // double quoted, single quoted, or unquoted attribute values + nameEqualsValueRegex = attrNameRegex.source + '(?:\\s*=\\s*' + attrValueRegex.source + ')?'; // optional '=[value]' + + return new RegExp( [ + // for tag. Ex: ) + '(?:', + '<(!DOCTYPE)', // *** Capturing Group 1 - If it's a doctype tag + + // Zero or more attributes following the tag name + '(?:', + '\\s+', // one or more whitespace chars before an attribute + + // Either: + // A. attr="value", or + // B. "value" alone (To cover example doctype tag: ) + '(?:', nameEqualsValueRegex, '|', attrValueRegex.source + ')', + ')*', + '>', + ')', + + '|', + + // All other HTML tags (i.e. tags that are not ) + '(?:', + '<(/)?', // Beginning of a tag. Either '<' for a start tag, or '' + '>', + ')' + ].join( "" ), 'gi' ); + } )(), + + + /** + * Walks an HTML string, calling the `options.processHtmlNode` function for each HTML tag that is encountered, and calling + * the `options.processTextNode` function when each text around HTML tags is encountered. + * + * @param {String} html The HTML to parse. + * @param {Object} [options] An Object (map) which may contain the following properties: + * + * @param {Function} [options.processHtmlNode] A visitor function which allows processing of an encountered HTML node. + * This function is called with the following arguments: + * @param {String} [options.processHtmlNode.tagText] The HTML tag text that was found. + * @param {String} [options.processHtmlNode.tagName] The tag name for the HTML tag that was found. Ex: 'a' for an anchor tag. + * @param {String} [options.processHtmlNode.isClosingTag] `true` if the tag is a closing tag (ex: </a>), `false` otherwise. + * + * @param {Function} [options.processTextNode] A visitor function which allows processing of an encountered text node. + * This function is called with the following arguments: + * @param {String} [options.processTextNode.text] The text node that was matched. + */ + parse : function( html, options ) { + options = options || {}; + + var processHtmlNodeVisitor = options.processHtmlNode || function() {}, + processTextNodeVisitor = options.processTextNode || function() {}, + htmlRegex = this.htmlRegex, + currentResult, + lastIndex = 0; + + // Loop over the HTML string, ignoring HTML tags, and processing the text that lies between them, + // wrapping the URLs in anchor tags + while( ( currentResult = htmlRegex.exec( html ) ) !== null ) { + var tagText = currentResult[ 0 ], + tagName = currentResult[ 1 ] || currentResult[ 3 ], // The tag (ex: "!DOCTYPE"), or another tag (ex: "a") + isClosingTag = !!currentResult[ 2 ], + inBetweenTagsText = html.substring( lastIndex, currentResult.index ); + + if( inBetweenTagsText ) { + processTextNodeVisitor( inBetweenTagsText ); + } + + processHtmlNodeVisitor( tagText, tagName.toLowerCase(), isClosingTag ); + + lastIndex = currentResult.index + tagText.length; + } + + // Process any remaining text after the last HTML element. Will process all of the text if there were no HTML elements. + if( lastIndex < html.length ) { + var text = html.substring( lastIndex ); + + if( text ) { + processTextNodeVisitor( text ); + } + } + } + + } ); + /*global Autolinker */ + /*jshint boss:true */ + /** + * @class Autolinker.HtmlTag + * @extends Object + * + * Represents an HTML tag, which can be used to easily build/modify HTML tags programmatically. + * + * Autolinker uses this abstraction to create HTML tags, and then write them out as strings. You may also use + * this class in your code, especially within a {@link Autolinker#replaceFn replaceFn}. + * + * ## Examples + * + * Example instantiation: + * + * var tag = new Autolinker.HtmlTag( { + * tagName : 'a', + * attrs : { 'href': 'http://google.com', 'class': 'external-link' }, + * innerHtml : 'Google' + * } ); + * + * tag.toString(); // Google + * + * // Individual accessor methods + * tag.getTagName(); // 'a' + * tag.getAttr( 'href' ); // 'http://google.com' + * tag.hasClass( 'external-link' ); // true + * + * + * Using mutator methods (which may be used in combination with instantiation config properties): + * + * var tag = new Autolinker.HtmlTag(); + * tag.setTagName( 'a' ); + * tag.setAttr( 'href', 'http://google.com' ); + * tag.addClass( 'external-link' ); + * tag.setInnerHtml( 'Google' ); + * + * tag.getTagName(); // 'a' + * tag.getAttr( 'href' ); // 'http://google.com' + * tag.hasClass( 'external-link' ); // true + * + * tag.toString(); // Google + * + * + * ## Example use within a {@link Autolinker#replaceFn replaceFn} + * + * var html = Autolinker.link( "Test google.com", { + * replaceFn : function( autolinker, match ) { + * var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance, configured with the Match's href and anchor text + * tag.setAttr( 'rel', 'nofollow' ); + * + * return tag; + * } + * } ); + * + * // generated html: + * // Test google.com + * + * + * ## Example use with a new tag for the replacement + * + * var html = Autolinker.link( "Test google.com", { + * replaceFn : function( autolinker, match ) { + * var tag = new Autolinker.HtmlTag( { + * tagName : 'button', + * attrs : { 'title': 'Load URL: ' + match.getAnchorHref() }, + * innerHtml : 'Load URL: ' + match.getAnchorText() + * } ); + * + * return tag; + * } + * } ); + * + * // generated html: + * // Test + */ + Autolinker.HtmlTag = Autolinker.Util.extend( Object, { + + /** + * @cfg {String} tagName + * + * The tag name. Ex: 'a', 'button', etc. + * + * Not required at instantiation time, but should be set using {@link #setTagName} before {@link #toString} + * is executed. + */ + + /** + * @cfg {Object.} attrs + * + * An key/value Object (map) of attributes to create the tag with. The keys are the attribute names, and the + * values are the attribute values. + */ + + /** + * @cfg {String} innerHtml + * + * The inner HTML for the tag. + * + * Note the camel case name on `innerHtml`. Acronyms are camelCased in this utility (such as not to run into the acronym + * naming inconsistency that the DOM developers created with `XMLHttpRequest`). You may alternatively use {@link #innerHTML} + * if you prefer, but this one is recommended. + */ + + /** + * @cfg {String} innerHTML + * + * Alias of {@link #innerHtml}, accepted for consistency with the browser DOM api, but prefer the camelCased version + * for acronym names. + */ + + + /** + * @protected + * @property {RegExp} whitespaceRegex + * + * Regular expression used to match whitespace in a string of CSS classes. + */ + whitespaceRegex : /\s+/, + + + /** + * @constructor + * @param {Object} [cfg] The configuration properties for this class, in an Object (map) + */ + constructor : function( cfg ) { + Autolinker.Util.assign( this, cfg ); + + this.innerHtml = this.innerHtml || this.innerHTML; // accept either the camelCased form or the fully capitalized acronym + }, + + + /** + * Sets the tag name that will be used to generate the tag with. + * + * @param {String} tagName + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + setTagName : function( tagName ) { + this.tagName = tagName; + return this; + }, + + + /** + * Retrieves the tag name. + * + * @return {String} + */ + getTagName : function() { + return this.tagName || ""; + }, + + + /** + * Sets an attribute on the HtmlTag. + * + * @param {String} attrName The attribute name to set. + * @param {String} attrValue The attribute value to set. + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + setAttr : function( attrName, attrValue ) { + var tagAttrs = this.getAttrs(); + tagAttrs[ attrName ] = attrValue; + + return this; + }, + + + /** + * Retrieves an attribute from the HtmlTag. If the attribute does not exist, returns `undefined`. + * + * @param {String} name The attribute name to retrieve. + * @return {String} The attribute's value, or `undefined` if it does not exist on the HtmlTag. + */ + getAttr : function( attrName ) { + return this.getAttrs()[ attrName ]; + }, + + + /** + * Sets one or more attributes on the HtmlTag. + * + * @param {Object.} attrs A key/value Object (map) of the attributes to set. + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + setAttrs : function( attrs ) { + var tagAttrs = this.getAttrs(); + Autolinker.Util.assign( tagAttrs, attrs ); + + return this; + }, + + + /** + * Retrieves the attributes Object (map) for the HtmlTag. + * + * @return {Object.} A key/value object of the attributes for the HtmlTag. + */ + getAttrs : function() { + return this.attrs || ( this.attrs = {} ); + }, + + + /** + * Sets the provided `cssClass`, overwriting any current CSS classes on the HtmlTag. + * + * @param {String} cssClass One or more space-separated CSS classes to set (overwrite). + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + setClass : function( cssClass ) { + return this.setAttr( 'class', cssClass ); + }, + + + /** + * Convenience method to add one or more CSS classes to the HtmlTag. Will not add duplicate CSS classes. + * + * @param {String} cssClass One or more space-separated CSS classes to add. + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + addClass : function( cssClass ) { + var classAttr = this.getClass(), + whitespaceRegex = this.whitespaceRegex, + indexOf = Autolinker.Util.indexOf, // to support IE8 and below + classes = ( !classAttr ) ? [] : classAttr.split( whitespaceRegex ), + newClasses = cssClass.split( whitespaceRegex ), + newClass; + + while( newClass = newClasses.shift() ) { + if( indexOf( classes, newClass ) === -1 ) { + classes.push( newClass ); + } + } + + this.getAttrs()[ 'class' ] = classes.join( " " ); + return this; + }, + + + /** + * Convenience method to remove one or more CSS classes from the HtmlTag. + * + * @param {String} cssClass One or more space-separated CSS classes to remove. + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + removeClass : function( cssClass ) { + var classAttr = this.getClass(), + whitespaceRegex = this.whitespaceRegex, + indexOf = Autolinker.Util.indexOf, // to support IE8 and below + classes = ( !classAttr ) ? [] : classAttr.split( whitespaceRegex ), + removeClasses = cssClass.split( whitespaceRegex ), + removeClass; + + while( classes.length && ( removeClass = removeClasses.shift() ) ) { + var idx = indexOf( classes, removeClass ); + if( idx !== -1 ) { + classes.splice( idx, 1 ); + } + } + + this.getAttrs()[ 'class' ] = classes.join( " " ); + return this; + }, + + + /** + * Convenience method to retrieve the CSS class(es) for the HtmlTag, which will each be separated by spaces when + * there are multiple. + * + * @return {String} + */ + getClass : function() { + return this.getAttrs()[ 'class' ] || ""; + }, + + + /** + * Convenience method to check if the tag has a CSS class or not. + * + * @param {String} cssClass The CSS class to check for. + * @return {Boolean} `true` if the HtmlTag has the CSS class, `false` otherwise. + */ + hasClass : function( cssClass ) { + return ( ' ' + this.getClass() + ' ' ).indexOf( ' ' + cssClass + ' ' ) !== -1; + }, + + + /** + * Sets the inner HTML for the tag. + * + * @param {String} html The inner HTML to set. + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + setInnerHtml : function( html ) { + this.innerHtml = html; + + return this; + }, + + + /** + * Retrieves the inner HTML for the tag. + * + * @return {String} + */ + getInnerHtml : function() { + return this.innerHtml || ""; + }, + + + /** + * Override of superclass method used to generate the HTML string for the tag. + * + * @return {String} + */ + toString : function() { + var tagName = this.getTagName(), + attrsStr = this.buildAttrsStr(); + + attrsStr = ( attrsStr ) ? ' ' + attrsStr : ''; // prepend a space if there are actually attributes + + return [ '<', tagName, attrsStr, '>', this.getInnerHtml(), '' ].join( "" ); + }, + + + /** + * Support method for {@link #toString}, returns the string space-separated key="value" pairs, used to populate + * the stringified HtmlTag. + * + * @protected + * @return {String} Example return: `attr1="value1" attr2="value2"` + */ + buildAttrsStr : function() { + if( !this.attrs ) return ""; // no `attrs` Object (map) has been set, return empty string + + var attrs = this.getAttrs(), + attrsArr = []; + + for( var prop in attrs ) { + if( attrs.hasOwnProperty( prop ) ) { + attrsArr.push( prop + '="' + attrs[ prop ] + '"' ); + } + } + return attrsArr.join( " " ); + } + + } ); + /*global Autolinker */ + /*jshint scripturl:true */ + /** + * @private + * @class Autolinker.MatchValidator + * @extends Object + * + * Used by Autolinker to filter out false positives from the {@link Autolinker#matcherRegex}. + * + * Due to the limitations of regular expressions (including the missing feature of look-behinds in JS regular expressions), + * we cannot always determine the validity of a given match. This class applies a bit of additional logic to filter out any + * false positives that have been matched by the {@link Autolinker#matcherRegex}. + */ + Autolinker.MatchValidator = Autolinker.Util.extend( Object, { + + /** + * @private + * @property {RegExp} invalidProtocolRelMatchRegex + * + * The regular expression used to check a potential protocol-relative URL match, coming from the + * {@link Autolinker#matcherRegex}. A protocol-relative URL is, for example, "//yahoo.com" + * + * This regular expression checks to see if there is a word character before the '//' match in order to determine if + * we should actually autolink a protocol-relative URL. This is needed because there is no negative look-behind in + * JavaScript regular expressions. + * + * For instance, we want to autolink something like "Go to: //google.com", but we don't want to autolink something + * like "abc//google.com" + */ + invalidProtocolRelMatchRegex : /^[\w]\/\//, + + /** + * Regex to test for a full protocol, with the two trailing slashes. Ex: 'http://' + * + * @private + * @property {RegExp} hasFullProtocolRegex + */ + hasFullProtocolRegex : /^[A-Za-z][-.+A-Za-z0-9]+:\/\//, + + /** + * Regex to find the URI scheme, such as 'mailto:'. + * + * This is used to filter out 'javascript:' and 'vbscript:' schemes. + * + * @private + * @property {RegExp} uriSchemeRegex + */ + uriSchemeRegex : /^[A-Za-z][-.+A-Za-z0-9]+:/, + + /** + * Regex to determine if at least one word char exists after the protocol (i.e. after the ':') + * + * @private + * @property {RegExp} hasWordCharAfterProtocolRegex + */ + hasWordCharAfterProtocolRegex : /:[^\s]*?[A-Za-z]/, + + + /** + * Determines if a given match found by {@link Autolinker#processTextNode} is valid. Will return `false` for: + * + * 1) URL matches which do not have at least have one period ('.') in the domain name (effectively skipping over + * matches like "abc:def"). However, URL matches with a protocol will be allowed (ex: 'http://localhost') + * 2) URL matches which do not have at least one word character in the domain name (effectively skipping over + * matches like "git:1.0"). + * 3) A protocol-relative url match (a URL beginning with '//') whose previous character is a word character + * (effectively skipping over strings like "abc//google.com") + * + * Otherwise, returns `true`. + * + * @param {String} urlMatch The matched URL, if there was one. Will be an empty string if the match is not a URL match. + * @param {String} protocolUrlMatch The match URL string for a protocol match. Ex: 'http://yahoo.com'. This is used to match + * something like 'http://localhost', where we won't double check that the domain name has at least one '.' in it. + * @param {String} protocolRelativeMatch The protocol-relative string for a URL match (i.e. '//'), possibly with a preceding + * character (ex, a space, such as: ' //', or a letter, such as: 'a//'). The match is invalid if there is a word character + * preceding the '//'. + * @return {Boolean} `true` if the match given is valid and should be processed, or `false` if the match is invalid and/or + * should just not be processed. + */ + isValidMatch : function( urlMatch, protocolUrlMatch, protocolRelativeMatch ) { + if( + ( protocolUrlMatch && !this.isValidUriScheme( protocolUrlMatch ) ) || + this.urlMatchDoesNotHaveProtocolOrDot( urlMatch, protocolUrlMatch ) || // At least one period ('.') must exist in the URL match for us to consider it an actual URL, *unless* it was a full protocol match (like 'http://localhost') + this.urlMatchDoesNotHaveAtLeastOneWordChar( urlMatch, protocolUrlMatch ) || // At least one letter character must exist in the domain name after a protocol match. Ex: skip over something like "git:1.0" + this.isInvalidProtocolRelativeMatch( protocolRelativeMatch ) // A protocol-relative match which has a word character in front of it (so we can skip something like "abc//google.com") + ) { + return false; + } + + return true; + }, + + + /** + * Determines if the URI scheme is a valid scheme to be autolinked. Returns `false` if the scheme is + * 'javascript:' or 'vbscript:' + * + * @private + * @param {String} uriSchemeMatch The match URL string for a full URI scheme match. Ex: 'http://yahoo.com' + * or 'mailto:a@a.com'. + * @return {Boolean} `true` if the scheme is a valid one, `false` otherwise. + */ + isValidUriScheme : function( uriSchemeMatch ) { + var uriScheme = uriSchemeMatch.match( this.uriSchemeRegex )[ 0 ]; + + return ( uriScheme !== 'javascript:' && uriScheme !== 'vbscript:' ); + }, + + + /** + * Determines if a URL match does not have either: + * + * a) a full protocol (i.e. 'http://'), or + * b) at least one dot ('.') in the domain name (for a non-full-protocol match). + * + * Either situation is considered an invalid URL (ex: 'git:d' does not have either the '://' part, or at least one dot + * in the domain name. If the match was 'git:abc.com', we would consider this valid.) + * + * @private + * @param {String} urlMatch The matched URL, if there was one. Will be an empty string if the match is not a URL match. + * @param {String} protocolUrlMatch The match URL string for a protocol match. Ex: 'http://yahoo.com'. This is used to match + * something like 'http://localhost', where we won't double check that the domain name has at least one '.' in it. + * @return {Boolean} `true` if the URL match does not have a full protocol, or at least one dot ('.') in a non-full-protocol + * match. + */ + urlMatchDoesNotHaveProtocolOrDot : function( urlMatch, protocolUrlMatch ) { + return ( !!urlMatch && ( !protocolUrlMatch || !this.hasFullProtocolRegex.test( protocolUrlMatch ) ) && urlMatch.indexOf( '.' ) === -1 ); + }, + + + /** + * Determines if a URL match does not have at least one word character after the protocol (i.e. in the domain name). + * + * At least one letter character must exist in the domain name after a protocol match. Ex: skip over something + * like "git:1.0" + * + * @private + * @param {String} urlMatch The matched URL, if there was one. Will be an empty string if the match is not a URL match. + * @param {String} protocolUrlMatch The match URL string for a protocol match. Ex: 'http://yahoo.com'. This is used to + * know whether or not we have a protocol in the URL string, in order to check for a word character after the protocol + * separator (':'). + * @return {Boolean} `true` if the URL match does not have at least one word character in it after the protocol, `false` + * otherwise. + */ + urlMatchDoesNotHaveAtLeastOneWordChar : function( urlMatch, protocolUrlMatch ) { + if( urlMatch && protocolUrlMatch ) { + return !this.hasWordCharAfterProtocolRegex.test( urlMatch ); + } else { + return false; + } + }, + + + /** + * Determines if a protocol-relative match is an invalid one. This method returns `true` if there is a `protocolRelativeMatch`, + * and that match contains a word character before the '//' (i.e. it must contain whitespace or nothing before the '//' in + * order to be considered valid). + * + * @private + * @param {String} protocolRelativeMatch The protocol-relative string for a URL match (i.e. '//'), possibly with a preceding + * character (ex, a space, such as: ' //', or a letter, such as: 'a//'). The match is invalid if there is a word character + * preceding the '//'. + * @return {Boolean} `true` if it is an invalid protocol-relative match, `false` otherwise. + */ + isInvalidProtocolRelativeMatch : function( protocolRelativeMatch ) { + return ( !!protocolRelativeMatch && this.invalidProtocolRelMatchRegex.test( protocolRelativeMatch ) ); + } + + } ); + /*global Autolinker */ + /*jshint sub:true */ + /** + * @protected + * @class Autolinker.AnchorTagBuilder + * @extends Object + * + * Builds anchor (<a>) tags for the Autolinker utility when a match is found. + * + * Normally this class is instantiated, configured, and used internally by an {@link Autolinker} instance, but may + * actually be retrieved in a {@link Autolinker#replaceFn replaceFn} to create {@link Autolinker.HtmlTag HtmlTag} instances + * which may be modified before returning from the {@link Autolinker#replaceFn replaceFn}. For example: + * + * var html = Autolinker.link( "Test google.com", { + * replaceFn : function( autolinker, match ) { + * var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance + * tag.setAttr( 'rel', 'nofollow' ); + * + * return tag; + * } + * } ); + * + * // generated html: + * // Test google.com + */ + Autolinker.AnchorTagBuilder = Autolinker.Util.extend( Object, { + + /** + * @cfg {Boolean} newWindow + * @inheritdoc Autolinker#newWindow + */ + + /** + * @cfg {Number} truncate + * @inheritdoc Autolinker#truncate + */ + + /** + * @cfg {String} className + * @inheritdoc Autolinker#className + */ + + + /** + * @constructor + * @param {Object} [cfg] The configuration options for the AnchorTagBuilder instance, specified in an Object (map). + */ + constructor : function( cfg ) { + Autolinker.Util.assign( this, cfg ); + }, + + + /** + * Generates the actual anchor (<a>) tag to use in place of the matched URL/email/Twitter text, + * via its `match` object. + * + * @param {Autolinker.match.Match} match The Match instance to generate an anchor tag from. + * @return {Autolinker.HtmlTag} The HtmlTag instance for the anchor tag. + */ + build : function( match ) { + var tag = new Autolinker.HtmlTag( { + tagName : 'a', + attrs : this.createAttrs( match.getType(), match.getAnchorHref() ), + innerHtml : this.processAnchorText( match.getAnchorText() ) + } ); + + return tag; + }, + + + /** + * Creates the Object (map) of the HTML attributes for the anchor (<a>) tag being generated. + * + * @protected + * @param {"url"/"email"/"twitter"} matchType The type of match that an anchor tag is being generated for. + * @param {String} href The href for the anchor tag. + * @return {Object} A key/value Object (map) of the anchor tag's attributes. + */ + createAttrs : function( matchType, anchorHref ) { + var attrs = { + 'href' : anchorHref // we'll always have the `href` attribute + }; + + var cssClass = this.createCssClass( matchType ); + if( cssClass ) { + attrs[ 'class' ] = cssClass; + } + if( this.newWindow ) { + attrs[ 'target' ] = "_blank"; + } + + return attrs; + }, + + + /** + * Creates the CSS class that will be used for a given anchor tag, based on the `matchType` and the {@link #className} + * config. + * + * @private + * @param {"url"/"email"/"twitter"} matchType The type of match that an anchor tag is being generated for. + * @return {String} The CSS class string for the link. Example return: "myLink myLink-url". If no {@link #className} + * was configured, returns an empty string. + */ + createCssClass : function( matchType ) { + var className = this.className; + + if( !className ) + return ""; + else + return className + " " + className + "-" + matchType; // ex: "myLink myLink-url", "myLink myLink-email", or "myLink myLink-twitter" + }, + + + /** + * Processes the `anchorText` by truncating the text according to the {@link #truncate} config. + * + * @private + * @param {String} anchorText The anchor tag's text (i.e. what will be displayed). + * @return {String} The processed `anchorText`. + */ + processAnchorText : function( anchorText ) { + anchorText = this.doTruncate( anchorText ); + + return anchorText; + }, + + + /** + * Performs the truncation of the `anchorText`, if the `anchorText` is longer than the {@link #truncate} option. + * Truncates the text to 2 characters fewer than the {@link #truncate} option, and adds ".." to the end. + * + * @private + * @param {String} text The anchor tag's text (i.e. what will be displayed). + * @return {String} The truncated anchor text. + */ + doTruncate : function( anchorText ) { + return Autolinker.Util.ellipsis( anchorText, this.truncate || Number.POSITIVE_INFINITY ); + } + + } ); + /*global Autolinker */ + /** + * @abstract + * @class Autolinker.match.Match + * + * Represents a match found in an input string which should be Autolinked. A Match object is what is provided in a + * {@link Autolinker#replaceFn replaceFn}, and may be used to query for details about the match. + * + * For example: + * + * var input = "..."; // string with URLs, Email Addresses, and Twitter Handles + * + * var linkedText = Autolinker.link( input, { + * replaceFn : function( autolinker, match ) { + * console.log( "href = ", match.getAnchorHref() ); + * console.log( "text = ", match.getAnchorText() ); + * + * switch( match.getType() ) { + * case 'url' : + * console.log( "url: ", match.getUrl() ); + * + * case 'email' : + * console.log( "email: ", match.getEmail() ); + * + * case 'twitter' : + * console.log( "twitter: ", match.getTwitterHandle() ); + * } + * } + * } ); + * + * See the {@link Autolinker} class for more details on using the {@link Autolinker#replaceFn replaceFn}. + */ + Autolinker.match.Match = Autolinker.Util.extend( Object, { + + /** + * @cfg {String} matchedText (required) + * + * The original text that was matched. + */ + + + /** + * @constructor + * @param {Object} cfg The configuration properties for the Match instance, specified in an Object (map). + */ + constructor : function( cfg ) { + Autolinker.Util.assign( this, cfg ); + }, + + + /** + * Returns a string name for the type of match that this class represents. + * + * @abstract + * @return {String} + */ + getType : Autolinker.Util.abstractMethod, + + + /** + * Returns the original text that was matched. + * + * @return {String} + */ + getMatchedText : function() { + return this.matchedText; + }, + + + /** + * Returns the anchor href that should be generated for the match. + * + * @abstract + * @return {String} + */ + getAnchorHref : Autolinker.Util.abstractMethod, + + + /** + * Returns the anchor text that should be generated for the match. + * + * @abstract + * @return {String} + */ + getAnchorText : Autolinker.Util.abstractMethod + + } ); + /*global Autolinker */ + /** + * @class Autolinker.match.Email + * @extends Autolinker.match.Match + * + * Represents a Email match found in an input string which should be Autolinked. + * + * See this class's superclass ({@link Autolinker.match.Match}) for more details. + */ + Autolinker.match.Email = Autolinker.Util.extend( Autolinker.match.Match, { + + /** + * @cfg {String} email (required) + * + * The email address that was matched. + */ + + + /** + * Returns a string name for the type of match that this class represents. + * + * @return {String} + */ + getType : function() { + return 'email'; + }, + + + /** + * Returns the email address that was matched. + * + * @return {String} + */ + getEmail : function() { + return this.email; + }, + + + /** + * Returns the anchor href that should be generated for the match. + * + * @return {String} + */ + getAnchorHref : function() { + return 'mailto:' + this.email; + }, + + + /** + * Returns the anchor text that should be generated for the match. + * + * @return {String} + */ + getAnchorText : function() { + return this.email; + } + + } ); + /*global Autolinker */ + /** + * @class Autolinker.match.Twitter + * @extends Autolinker.match.Match + * + * Represents a Twitter match found in an input string which should be Autolinked. + * + * See this class's superclass ({@link Autolinker.match.Match}) for more details. + */ + Autolinker.match.Twitter = Autolinker.Util.extend( Autolinker.match.Match, { + + /** + * @cfg {String} twitterHandle (required) + * + * The Twitter handle that was matched. + */ + + + /** + * Returns the type of match that this class represents. + * + * @return {String} + */ + getType : function() { + return 'twitter'; + }, + + + /** + * Returns a string name for the type of match that this class represents. + * + * @return {String} + */ + getTwitterHandle : function() { + return this.twitterHandle; + }, + + + /** + * Returns the anchor href that should be generated for the match. + * + * @return {String} + */ + getAnchorHref : function() { + return 'https://twitter.com/' + this.twitterHandle; + }, + + + /** + * Returns the anchor text that should be generated for the match. + * + * @return {String} + */ + getAnchorText : function() { + return '@' + this.twitterHandle; + } + + } ); + /*global Autolinker */ + /** + * @class Autolinker.match.Url + * @extends Autolinker.match.Match + * + * Represents a Url match found in an input string which should be Autolinked. + * + * See this class's superclass ({@link Autolinker.match.Match}) for more details. + */ + Autolinker.match.Url = Autolinker.Util.extend( Autolinker.match.Match, { + + /** + * @cfg {String} url (required) + * + * The url that was matched. + */ + + /** + * @cfg {Boolean} protocolUrlMatch (required) + * + * `true` if the URL is a match which already has a protocol (i.e. 'http://'), `false` if the match was from a 'www' or + * known TLD match. + */ + + /** + * @cfg {Boolean} protocolRelativeMatch (required) + * + * `true` if the URL is a protocol-relative match. A protocol-relative match is a URL that starts with '//', + * and will be either http:// or https:// based on the protocol that the site is loaded under. + */ + + /** + * @cfg {Boolean} stripPrefix (required) + * @inheritdoc Autolinker#stripPrefix + */ + + + /** + * @private + * @property {RegExp} urlPrefixRegex + * + * A regular expression used to remove the 'http://' or 'https://' and/or the 'www.' from URLs. + */ + urlPrefixRegex: /^(https?:\/\/)?(www\.)?/i, + + /** + * @private + * @property {RegExp} protocolRelativeRegex + * + * The regular expression used to remove the protocol-relative '//' from the {@link #url} string, for purposes + * of {@link #getAnchorText}. A protocol-relative URL is, for example, "//yahoo.com" + */ + protocolRelativeRegex : /^\/\//, + + /** + * @private + * @property {Boolean} protocolPrepended + * + * Will be set to `true` if the 'http://' protocol has been prepended to the {@link #url} (because the + * {@link #url} did not have a protocol) + */ + protocolPrepended : false, + + + /** + * Returns a string name for the type of match that this class represents. + * + * @return {String} + */ + getType : function() { + return 'url'; + }, + + + /** + * Returns the url that was matched, assuming the protocol to be 'http://' if the original + * match was missing a protocol. + * + * @return {String} + */ + getUrl : function() { + var url = this.url; + + // if the url string doesn't begin with a protocol, assume 'http://' + if( !this.protocolRelativeMatch && !this.protocolUrlMatch && !this.protocolPrepended ) { + url = this.url = 'http://' + url; + + this.protocolPrepended = true; + } + + return url; + }, + + + /** + * Returns the anchor href that should be generated for the match. + * + * @return {String} + */ + getAnchorHref : function() { + var url = this.getUrl(); + + return url.replace( /&/g, '&' ); // any &'s in the URL should be converted back to '&' if they were displayed as & in the source html + }, + + + /** + * Returns the anchor text that should be generated for the match. + * + * @return {String} + */ + getAnchorText : function() { + var anchorText = this.getUrl(); + + if( this.protocolRelativeMatch ) { + // Strip off any protocol-relative '//' from the anchor text + anchorText = this.stripProtocolRelativePrefix( anchorText ); + } + if( this.stripPrefix ) { + anchorText = this.stripUrlPrefix( anchorText ); + } + anchorText = this.removeTrailingSlash( anchorText ); // remove trailing slash, if there is one + + return anchorText; + }, + + + // --------------------------------------- + + // Utility Functionality + + /** + * Strips the URL prefix (such as "http://" or "https://") from the given text. + * + * @private + * @param {String} text The text of the anchor that is being generated, for which to strip off the + * url prefix (such as stripping off "http://") + * @return {String} The `anchorText`, with the prefix stripped. + */ + stripUrlPrefix : function( text ) { + return text.replace( this.urlPrefixRegex, '' ); + }, + + + /** + * Strips any protocol-relative '//' from the anchor text. + * + * @private + * @param {String} text The text of the anchor that is being generated, for which to strip off the + * protocol-relative prefix (such as stripping off "//") + * @return {String} The `anchorText`, with the protocol-relative prefix stripped. + */ + stripProtocolRelativePrefix : function( text ) { + return text.replace( this.protocolRelativeRegex, '' ); + }, + + + /** + * Removes any trailing slash from the given `anchorText`, in preparation for the text to be displayed. + * + * @private + * @param {String} anchorText The text of the anchor that is being generated, for which to remove any trailing + * slash ('/') that may exist. + * @return {String} The `anchorText`, with the trailing slash removed. + */ + removeTrailingSlash : function( anchorText ) { + if( anchorText.charAt( anchorText.length - 1 ) === '/' ) { + anchorText = anchorText.slice( 0, -1 ); + } + return anchorText; + } + + } ); + + return Autolinker; + + +})); + +},{}],"/":[function(require,module,exports){ +'use strict'; + + +module.exports = require('./lib/'); + +},{"./lib/":14}]},{},[])("/") +}); \ No newline at end of file -- cgit v1.2.3