showdown/dist/showdown.js
Estevão Soares dos Santos c2cfcc2c36 build
2022-04-25 07:36:59 +01:00

8080 lines
253 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;/*! showdown v 3.0.0-alpha - 25-04-2022 */
(function(){
/**
* Created by Tivie on 13-07-2015.
*/
function getDefaultOpts (simple) {
'use strict';
let defaultOptions = {
omitExtraWLInCodeBlocks: {
defaultValue: false,
describe: 'Omit the default extra whiteline added to code blocks',
type: 'boolean'
},
noHeaderId: {
defaultValue: false,
describe: 'Turn on/off generated header id',
type: 'boolean'
},
prefixHeaderId: {
defaultValue: false,
describe: 'Add a prefix to the generated header ids. Passing a string will prefix that string to the header id. Setting to true will add a generic \'section-\' prefix',
type: 'string'
},
rawPrefixHeaderId: {
defaultValue: false,
describe: 'Setting this option to true will prevent showdown from modifying the prefix. This might result in malformed IDs (if, for instance, the " char is used in the prefix)',
type: 'boolean'
},
ghCompatibleHeaderId: {
defaultValue: false,
describe: 'Generate header ids compatible with github style (spaces are replaced with dashes, a bunch of non alphanumeric chars are removed)',
type: 'boolean'
},
rawHeaderId: {
defaultValue: false,
describe: 'Remove only spaces, \' and " from generated header ids (including prefixes), replacing them with dashes (-). WARNING: This might result in malformed ids',
type: 'boolean'
},
headerLevelStart: {
defaultValue: 1,
describe: 'The header blocks level start',
type: 'number'
},
parseImgDimensions: {
defaultValue: false,
describe: 'Turn on/off image dimension parsing',
type: 'boolean'
},
simplifiedAutoLink: {
defaultValue: false,
describe: 'Turn on/off GFM autolink style',
type: 'boolean'
},
literalMidWordUnderscores: {
defaultValue: false,
describe: 'Parse midword underscores as literal underscores',
type: 'boolean'
},
literalMidWordAsterisks: {
defaultValue: false,
describe: 'Parse midword asterisks as literal asterisks',
type: 'boolean'
},
strikethrough: {
defaultValue: false,
describe: 'Turn on/off strikethrough support',
type: 'boolean'
},
tables: {
defaultValue: false,
describe: 'Turn on/off tables support',
type: 'boolean'
},
tablesHeaderId: {
defaultValue: false,
describe: 'Add an id to table headers',
type: 'boolean'
},
ghCodeBlocks: {
defaultValue: true,
describe: 'Turn on/off GFM fenced code blocks support',
type: 'boolean'
},
tasklists: {
defaultValue: false,
describe: 'Turn on/off GFM tasklist support',
type: 'boolean'
},
smoothLivePreview: {
defaultValue: false,
describe: 'Prevents weird effects in live previews due to incomplete input',
type: 'boolean'
},
smartIndentationFix: {
defaultValue: false,
describe: 'Tries to smartly fix indentation in es6 strings',
type: 'boolean'
},
disableForced4SpacesIndentedSublists: {
defaultValue: false,
describe: 'Disables the requirement of indenting nested sublists by 4 spaces',
type: 'boolean'
},
simpleLineBreaks: {
defaultValue: false,
describe: 'Parses simple line breaks as <br> (GFM Style)',
type: 'boolean'
},
requireSpaceBeforeHeadingText: {
defaultValue: false,
describe: 'Makes adding a space between `#` and the header text mandatory (GFM Style)',
type: 'boolean'
},
ghMentions: {
defaultValue: false,
describe: 'Enables github @mentions',
type: 'boolean'
},
ghMentionsLink: {
defaultValue: 'https://github.com/{u}',
describe: 'Changes the link generated by @mentions. Only applies if ghMentions option is enabled.',
type: 'string'
},
encodeEmails: {
defaultValue: true,
describe: 'Encode e-mail addresses through the use of Character Entities, transforming ASCII e-mail addresses into its equivalent decimal entities',
type: 'boolean'
},
openLinksInNewWindow: {
defaultValue: false,
describe: 'Open all links in new windows',
type: 'boolean'
},
backslashEscapesHTMLTags: {
defaultValue: false,
describe: 'Support for HTML Tag escaping. ex: \<div>foo\</div>',
type: 'boolean'
},
emoji: {
defaultValue: false,
describe: 'Enable emoji support. Ex: `this is a :smile: emoji`',
type: 'boolean'
},
underline: {
defaultValue: false,
describe: 'Enable support for underline. Syntax is double or triple underscores: `__underline word__`. With this option enabled, underscores no longer parses into `<em>` and `<strong>`',
type: 'boolean'
},
ellipsis: {
defaultValue: true,
describe: 'Replaces three dots with the ellipsis unicode character',
type: 'boolean'
},
completeHTMLDocument: {
defaultValue: false,
describe: 'Outputs a complete html document, including `<html>`, `<head>` and `<body>` tags',
type: 'boolean'
},
metadata: {
defaultValue: false,
describe: 'Enable support for document metadata (defined at the top of the document between `«««` and `»»»` or between `---` and `---`).',
type: 'boolean'
},
splitAdjacentBlockquotes: {
defaultValue: false,
describe: 'Split adjacent blockquote blocks',
type: 'boolean'
},
moreStyling: {
defaultValue: false,
describe: 'Adds some useful styling css classes in the generated html',
type: 'boolean'
},
relativePathBaseUrl: {
defaultValue: '',
describe: 'Prepends a base URL to relative paths',
type: 'string'
},
};
if (simple === false) {
return JSON.parse(JSON.stringify(defaultOptions));
}
let ret = {};
for (let opt in defaultOptions) {
if (defaultOptions.hasOwnProperty(opt)) {
ret[opt] = defaultOptions[opt].defaultValue;
}
}
return ret;
}
function allOptionsOn () {
'use strict';
let options = getDefaultOpts(true),
ret = {};
for (let opt in options) {
if (options.hasOwnProperty(opt)) {
ret[opt] = true;
}
}
return ret;
}
/**
* Created by Tivie on 06-01-2015.
*/
// Private properties
var showdown = {},
parsers = {},
extensions = {},
globalOptions = getDefaultOpts(true),
setFlavor = 'vanilla',
flavor = {
github: {
omitExtraWLInCodeBlocks: true,
simplifiedAutoLink: true,
literalMidWordUnderscores: true,
strikethrough: true,
tables: true,
tablesHeaderId: true,
ghCodeBlocks: true,
tasklists: true,
disableForced4SpacesIndentedSublists: true,
simpleLineBreaks: true,
requireSpaceBeforeHeadingText: true,
ghCompatibleHeaderId: true,
ghMentions: true,
backslashEscapesHTMLTags: true,
emoji: true,
splitAdjacentBlockquotes: true
},
original: {
noHeaderId: true,
ghCodeBlocks: false
},
ghost: {
omitExtraWLInCodeBlocks: true,
parseImgDimensions: true,
simplifiedAutoLink: true,
literalMidWordUnderscores: true,
strikethrough: true,
tables: true,
tablesHeaderId: true,
ghCodeBlocks: true,
tasklists: true,
smoothLivePreview: true,
simpleLineBreaks: true,
requireSpaceBeforeHeadingText: true,
ghMentions: false,
encodeEmails: true
},
vanilla: getDefaultOpts(true),
allOn: allOptionsOn()
};
/**
* helper namespace
* @type {{}}
*/
showdown.helper = {};
/**
* TODO LEGACY SUPPORT CODE
* @type {{}}
*/
showdown.extensions = {};
/**
* Set a global option
* @static
* @param {string} key
* @param {*} value
* @returns {showdown}
*/
showdown.setOption = function (key, value) {
'use strict';
globalOptions[key] = value;
return this;
};
/**
* Get a global option
* @static
* @param {string} key
* @returns {*}
*/
showdown.getOption = function (key) {
'use strict';
return globalOptions[key];
};
/**
* Get the global options
* @static
* @returns {{}}
*/
showdown.getOptions = function () {
'use strict';
return globalOptions;
};
/**
* Reset global options to the default values
* @static
*/
showdown.resetOptions = function () {
'use strict';
globalOptions = getDefaultOpts(true);
};
/**
* Set the flavor showdown should use as default
* @param {string} name
*/
showdown.setFlavor = function (name) {
'use strict';
if (!flavor.hasOwnProperty(name)) {
throw Error(name + ' flavor was not found');
}
showdown.resetOptions();
var preset = flavor[name];
setFlavor = name;
for (var option in preset) {
if (preset.hasOwnProperty(option)) {
globalOptions[option] = preset[option];
}
}
};
/**
* Get the currently set flavor
* @returns {string}
*/
showdown.getFlavor = function () {
'use strict';
return setFlavor;
};
/**
* Get the options of a specified flavor. Returns undefined if the flavor was not found
* @param {string} name Name of the flavor
* @returns {{}|undefined}
*/
showdown.getFlavorOptions = function (name) {
'use strict';
if (flavor.hasOwnProperty(name)) {
return flavor[name];
}
};
/**
* Get the default options
* @static
* @param {boolean} [simple=true]
* @returns {{}}
*/
showdown.getDefaultOptions = function (simple) {
'use strict';
return getDefaultOpts(simple);
};
/**
* Get or set a subParser
*
* subParser(name) - Get a registered subParser
* subParser(name, func) - Register a subParser
* @static
* @param {string} name
* @param {function} [func]
* @returns {*}
*/
showdown.subParser = function (name, func) {
'use strict';
if (showdown.helper.isString(name)) {
if (typeof func !== 'undefined') {
parsers[name] = func;
} else {
if (parsers.hasOwnProperty(name)) {
return parsers[name];
} else {
throw Error('SubParser named ' + name + ' not registered!');
}
}
} else {
throw Error('showdown.subParser function first argument must be a string (the name of the subparser)');
}
};
/**
*
* @returns {{}}
*/
showdown.getSubParserList = function () {
return parsers;
};
/**
* Gets or registers an extension
* @static
* @param {string} name
* @param {object|object[]|function=} ext
* @returns {*}
*/
showdown.extension = function (name, ext) {
'use strict';
if (!showdown.helper.isString(name)) {
throw Error('Extension \'name\' must be a string');
}
name = showdown.helper.stdExtName(name);
// Getter
if (showdown.helper.isUndefined(ext)) {
if (!extensions.hasOwnProperty(name)) {
throw Error('Extension named ' + name + ' is not registered!');
}
return extensions[name];
// Setter
} else {
// Expand extension if it's wrapped in a function
if (typeof ext === 'function') {
ext = ext();
}
// Ensure extension is an array
if (!showdown.helper.isArray(ext)) {
ext = [ext];
}
var validExtension = validate(ext, name);
if (validExtension.valid) {
extensions[name] = ext;
} else {
throw Error(validExtension.error);
}
}
};
/**
* Gets all extensions registered
* @returns {{}}
*/
showdown.getAllExtensions = function () {
'use strict';
return extensions;
};
/**
* Remove an extension
* @param {string} name
*/
showdown.removeExtension = function (name) {
'use strict';
delete extensions[name];
};
/**
* Removes all extensions
*/
showdown.resetExtensions = function () {
'use strict';
extensions = {};
};
/**
* Validate extension
* @param {array} extension
* @param {string} name
* @returns {{valid: boolean, error: string}}
*/
function validate (extension, name) {
'use strict';
var errMsg = (name) ? 'Error in ' + name + ' extension->' : 'Error in unnamed extension',
ret = {
valid: true,
error: ''
};
if (!showdown.helper.isArray(extension)) {
extension = [extension];
}
for (var i = 0; i < extension.length; ++i) {
var baseMsg = errMsg + ' sub-extension ' + i + ': ',
ext = extension[i];
if (typeof ext !== 'object') {
ret.valid = false;
ret.error = baseMsg + 'must be an object, but ' + typeof ext + ' given';
return ret;
}
if (!showdown.helper.isString(ext.type)) {
ret.valid = false;
ret.error = baseMsg + 'property "type" must be a string, but ' + typeof ext.type + ' given';
return ret;
}
var type = ext.type = ext.type.toLowerCase();
// normalize extension type
if (type === 'language') {
type = ext.type = 'lang';
}
if (type === 'html') {
type = ext.type = 'output';
}
if (type !== 'lang' && type !== 'output' && type !== 'listener') {
ret.valid = false;
ret.error = baseMsg + 'type ' + type + ' is not recognized. Valid values: "lang/language", "output/html" or "listener"';
return ret;
}
if (type === 'listener') {
if (showdown.helper.isUndefined(ext.listeners)) {
ret.valid = false;
ret.error = baseMsg + '. Extensions of type "listener" must have a property called "listeners"';
return ret;
}
} else {
if (showdown.helper.isUndefined(ext.filter) && showdown.helper.isUndefined(ext.regex)) {
ret.valid = false;
ret.error = baseMsg + type + ' extensions must define either a "regex" property or a "filter" method';
return ret;
}
}
if (ext.listeners) {
if (typeof ext.listeners !== 'object') {
ret.valid = false;
ret.error = baseMsg + '"listeners" property must be an object but ' + typeof ext.listeners + ' given';
return ret;
}
for (var ln in ext.listeners) {
if (ext.listeners.hasOwnProperty(ln)) {
if (typeof ext.listeners[ln] !== 'function') {
ret.valid = false;
ret.error = baseMsg + '"listeners" property must be an hash of [event name]: [callback]. listeners.' + ln +
' must be a function but ' + typeof ext.listeners[ln] + ' given';
return ret;
}
}
}
}
if (ext.filter) {
if (typeof ext.filter !== 'function') {
ret.valid = false;
ret.error = baseMsg + '"filter" must be a function, but ' + typeof ext.filter + ' given';
return ret;
}
} else if (ext.regex) {
if (showdown.helper.isString(ext.regex)) {
ext.regex = new RegExp(ext.regex, 'g');
}
if (!(ext.regex instanceof RegExp)) {
ret.valid = false;
ret.error = baseMsg + '"regex" property must either be a string or a RegExp object, but ' + typeof ext.regex + ' given';
return ret;
}
if (showdown.helper.isUndefined(ext.replace)) {
ret.valid = false;
ret.error = baseMsg + '"regex" extensions must implement a replace string or function';
return ret;
}
}
}
return ret;
}
/**
* Validate extension
* @param {object} ext
* @returns {boolean}
*/
showdown.validateExtension = function (ext) {
'use strict';
var validateExtension = validate(ext, null);
if (!validateExtension.valid) {
console.warn(validateExtension.error);
return false;
}
return true;
};
/**
* showdownjs helper functions
*/
if (!showdown.hasOwnProperty('helper')) {
showdown.helper = {};
}
if (typeof this === 'undefined' && typeof window !== 'undefined') {
showdown.helper.document = window.document;
} else {
if (typeof this.document === 'undefined' && typeof this.window === 'undefined') {
let jsdom = require('jsdom');
this.window = new jsdom.JSDOM('', {}).window; // jshint ignore:line
}
showdown.helper.document = this.window.document;
}
/**
* Check if var is string
* @static
* @param {*} a
* @returns {boolean}
*/
showdown.helper.isString = function (a) {
'use strict';
return (typeof a === 'string' || a instanceof String);
};
/**
* Check if var is a number
* @static
* @param {*} a
* @returns {boolean}
*/
showdown.helper.isNumber = function (a) {
return !isNaN(a);
};
/**
* Check if var is a function
* @static
* @param {*} a
* @returns {boolean}
*/
showdown.helper.isFunction = function (a) {
'use strict';
let getType = {};
return a && getType.toString.call(a) === '[object Function]';
};
/**
* isArray helper function
* @static
* @param {*} a
* @returns {boolean}
*/
showdown.helper.isArray = function (a) {
'use strict';
let isArray;
if (!Array.isArray) {
isArray = function (arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
} else {
isArray = Array.isArray;
}
return isArray(a);
};
/**
* Check if value is undefined
* @static
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.
*/
showdown.helper.isUndefined = function (value) {
'use strict';
return typeof value === 'undefined';
};
/**
* Check if value is an object (excluding arrays)
* @param {*} value
* @returns {boolean}
*/
showdown.helper.isObject = function (value) {
'use strict';
return (
typeof value === 'object' &&
!Array.isArray(value) &&
value !== null
);
};
/**
* ForEach helper function
* Iterates over Arrays and Objects (own properties only)
* @static
* @param {*} obj
* @param {function} callback Accepts 3 params: 1. value, 2. key, 3. the original array/object
*/
showdown.helper.forEach = function (obj, callback) {
'use strict';
// check if obj is defined
if (showdown.helper.isUndefined(obj)) {
throw new Error('obj param is required');
}
if (showdown.helper.isUndefined(callback)) {
throw new Error('callback param is required');
}
if (!showdown.helper.isFunction(callback)) {
throw new Error('callback param must be a function/closure');
}
if (typeof obj.forEach === 'function') {
obj.forEach(callback);
} else if (showdown.helper.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
callback(obj[i], i, obj);
}
} else if (typeof (obj) === 'object') {
for (let prop in obj) {
if (obj.hasOwnProperty(prop)) {
callback(obj[prop], prop, obj);
}
}
} else {
throw new Error('obj does not seem to be an array or an iterable object');
}
};
/**
* Standardize extension name
* @static
* @param {string} s extension name
* @returns {string}
*/
showdown.helper.stdExtName = function (s) {
'use strict';
return s.replace(/[_?*+\/\\.^-]/g, '').replace(/\s/g, '').toLowerCase();
};
function escapeCharactersCallback (wholeMatch, m1) {
'use strict';
let charCodeToEscape = m1.charCodeAt(0);
return '¨E' + charCodeToEscape + 'E';
}
/**
* Callback used to escape characters when passing through String.replace
* @static
* @param {string} wholeMatch
* @param {string} m1
* @returns {string}
*/
showdown.helper.escapeCharactersCallback = escapeCharactersCallback;
/**
* Escape characters in a string
* @static
* @param {string} text
* @param {string} charsToEscape
* @param {boolean} afterBackslash
* @returns {string|void|*}
*/
showdown.helper.escapeCharacters = function (text, charsToEscape, afterBackslash) {
'use strict';
// First we have to escape the escape characters so that
// we can build a character class out of them
let regexString = '([' + charsToEscape.replace(/([\[\]\\])/g, '\\$1') + '])';
if (afterBackslash) {
regexString = '\\\\' + regexString;
}
let regex = new RegExp(regexString, 'g');
text = text.replace(regex, escapeCharactersCallback);
return text;
};
let rgxFindMatchPos = function (str, left, right, flags) {
'use strict';
let f = flags || '',
g = f.indexOf('g') > -1,
x = new RegExp(left + '|' + right, 'g' + f.replace(/g/g, '')),
l = new RegExp(left, f.replace(/g/g, '')),
pos = [],
t, s, m, start, end;
do {
t = 0;
while ((m = x.exec(str))) {
if (l.test(m[0])) {
if (!(t++)) {
s = x.lastIndex;
start = s - m[0].length;
}
} else if (t) {
if (!--t) {
end = m.index + m[0].length;
let obj = {
left: {start: start, end: s},
match: {start: s, end: m.index},
right: {start: m.index, end: end},
wholeMatch: {start: start, end: end}
};
pos.push(obj);
if (!g) {
return pos;
}
}
}
}
} while (t && (x.lastIndex = s));
return pos;
};
/**
* matchRecursiveRegExp
*
* (c) 2007 Steven Levithan <stevenlevithan.com>
* MIT License
*
* Accepts a string to search, a left and right format delimiter
* as regex patterns, and optional regex flags. Returns an array
* of matches, allowing nested instances of left/right delimiters.
* Use the "g" flag to return all matches, otherwise only the
* first is returned. Be careful to ensure that the left and
* right format delimiters produce mutually exclusive matches.
* Backreferences are not supported within the right delimiter
* due to how it is internally combined with the left delimiter.
* When matching strings whose format delimiters are unbalanced
* to the left or right, the output is intentionally as a
* conventional regex library with recursion support would
* produce, e.g. "<<x>" and "<x>>" both produce ["x"] when using
* "<" and ">" as the delimiters (both strings contain a single,
* balanced instance of "<x>").
*
* examples:
* matchRecursiveRegExp("test", "\\(", "\\)")
* returns: []
* matchRecursiveRegExp("<t<<e>><s>>t<>", "<", ">", "g")
* returns: ["t<<e>><s>", ""]
* matchRecursiveRegExp("<div id=\"x\">test</div>", "<div\\b[^>]*>", "</div>", "gi")
* returns: ["test"]
*/
showdown.helper.matchRecursiveRegExp = function (str, left, right, flags) {
'use strict';
let matchPos = rgxFindMatchPos (str, left, right, flags),
results = [];
for (let i = 0; i < matchPos.length; ++i) {
results.push([
str.slice(matchPos[i].wholeMatch.start, matchPos[i].wholeMatch.end),
str.slice(matchPos[i].match.start, matchPos[i].match.end),
str.slice(matchPos[i].left.start, matchPos[i].left.end),
str.slice(matchPos[i].right.start, matchPos[i].right.end)
]);
}
return results;
};
/**
*
* @param {string} str
* @param {string|function} replacement
* @param {string} left
* @param {string} right
* @param {string} flags
* @returns {string}
*/
showdown.helper.replaceRecursiveRegExp = function (str, replacement, left, right, flags) {
'use strict';
if (!showdown.helper.isFunction(replacement)) {
let repStr = replacement;
replacement = function () {
return repStr;
};
}
let matchPos = rgxFindMatchPos(str, left, right, flags),
finalStr = str,
lng = matchPos.length;
if (lng > 0) {
let bits = [];
if (matchPos[0].wholeMatch.start !== 0) {
bits.push(str.slice(0, matchPos[0].wholeMatch.start));
}
for (let i = 0; i < lng; ++i) {
bits.push(
replacement(
str.slice(matchPos[i].wholeMatch.start, matchPos[i].wholeMatch.end),
str.slice(matchPos[i].match.start, matchPos[i].match.end),
str.slice(matchPos[i].left.start, matchPos[i].left.end),
str.slice(matchPos[i].right.start, matchPos[i].right.end)
)
);
if (i < lng - 1) {
bits.push(str.slice(matchPos[i].wholeMatch.end, matchPos[i + 1].wholeMatch.start));
}
}
if (matchPos[lng - 1].wholeMatch.end < str.length) {
bits.push(str.slice(matchPos[lng - 1].wholeMatch.end));
}
finalStr = bits.join('');
}
return finalStr;
};
/**
* Returns the index within the passed String object of the first occurrence of the specified regex,
* starting the search at fromIndex. Returns -1 if the value is not found.
*
* @param {string} str string to search
* @param {RegExp} regex Regular expression to search
* @param {int} [fromIndex = 0] Index to start the search
* @returns {Number}
* @throws InvalidArgumentError
*/
showdown.helper.regexIndexOf = function (str, regex, fromIndex) {
'use strict';
if (!showdown.helper.isString(str)) {
throw 'InvalidArgumentError: first parameter of showdown.helper.regexIndexOf function must be a string';
}
if (!(regex instanceof RegExp)) {
throw 'InvalidArgumentError: second parameter of showdown.helper.regexIndexOf function must be an instance of RegExp';
}
let indexOf = str.substring(fromIndex || 0).search(regex);
return (indexOf >= 0) ? (indexOf + (fromIndex || 0)) : indexOf;
};
/**
* Splits the passed string object at the defined index, and returns an array composed of the two substrings
* @param {string} str string to split
* @param {int} index index to split string at
* @returns {[string,string]}
* @throws InvalidArgumentError
*/
showdown.helper.splitAtIndex = function (str, index) {
'use strict';
if (!showdown.helper.isString(str)) {
throw 'InvalidArgumentError: first parameter of showdown.helper.regexIndexOf function must be a string';
}
return [str.substring(0, index), str.substring(index)];
};
/**
* MurmurHash3's mixing function
* https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript/47593316#47593316
*
* @param {string} string
* @returns {Number}
*/
/*jshint bitwise: false*/
function xmur3 (str) {
let h;
for (let i = 0, h = 1779033703 ^ str.length; i < str.length; i++) {
h = Math.imul(h ^ str.charCodeAt(i), 3432918353);
h = h << 13 | h >>> 19;
}
return function () {
h = Math.imul(h ^ h >>> 16, 2246822507);
h = Math.imul(h ^ h >>> 13, 3266489909);
return (h ^= h >>> 16) >>> 0;
};
}
/**
* Random Number Generator
* https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript/47593316#47593316
*
* @param {Number} seed
* @returns {Number}
*/
/*jshint bitwise: false*/
function mulberry32 (a) {
return function () {
let t = a += 0x6D2B79F5;
t = Math.imul(t ^ t >>> 15, t | 1);
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
return ((t ^ t >>> 14) >>> 0) / 4294967296;
};
}
/**
* Obfuscate an e-mail address through the use of Character Entities,
* transforming ASCII characters into their equivalent decimal or hex entities.
*
*
* @param {string} mail
* @returns {string}
*/
showdown.helper.encodeEmailAddress = function (mail) {
'use strict';
let encode = [
function (ch) {
return '&#' + ch.charCodeAt(0) + ';';
},
function (ch) {
return '&#x' + ch.charCodeAt(0).toString(16) + ';';
},
function (ch) {
return ch;
}
];
// RNG seeded with mail, so that we can get determined results for each email.
let rand = mulberry32(xmur3(mail));
mail = mail.replace(/./g, function (ch) {
if (ch === '@') {
// this *must* be encoded. I insist.
ch = encode[Math.floor(rand() * 2)](ch);
} else {
let r = rand();
// roughly 10% raw, 45% hex, 45% dec
ch = (
r > 0.9 ? encode[2](ch) : r > 0.45 ? encode[1](ch) : encode[0](ch)
);
}
return ch;
});
return mail;
};
/**
* String.prototype.repeat polyfill
*
* @param {string} str
* @param {int} count
* @returns {string}
*/
showdown.helper.repeat = function (str, count) {
'use strict';
// use built-in method if it's available
if (!showdown.helper.isUndefined(String.prototype.repeat)) {
return str.repeat(count);
}
str = '' + str;
if (count < 0) {
throw new RangeError('repeat count must be non-negative');
}
if (count === Infinity) {
throw new RangeError('repeat count must be less than infinity');
}
count = Math.floor(count);
if (str.length === 0 || count === 0) {
return '';
}
// Ensuring count is a 31-bit integer allows us to heavily optimize the
// main part. But anyway, most current (August 2014) browsers can't handle
// strings 1 << 28 chars or longer, so:
/*jshint bitwise: false*/
if (str.length * count >= 1 << 28) {
throw new RangeError('repeat count must not overflow maximum string size');
}
/*jshint bitwise: true*/
let maxCount = str.length * count;
count = Math.floor(Math.log(count) / Math.log(2));
while (count) {
str += str;
count--;
}
str += str.substring(0, maxCount - str.length);
return str;
};
/**
* String.prototype.padEnd polyfill
*
* @param {string} str
* @param {int} targetLength
* @param {string} [padString]
* @returns {string}
*/
showdown.helper.padEnd = function padEnd (str, targetLength, padString) {
'use strict';
/*jshint bitwise: false*/
// eslint-disable-next-line space-infix-ops
targetLength = targetLength>>0; //floor if number or convert non-number to 0;
/*jshint bitwise: true*/
padString = String(padString || ' ');
if (str.length > targetLength) {
return String(str);
} else {
targetLength = targetLength - str.length;
if (targetLength > padString.length) {
padString += showdown.helper.repeat(padString, targetLength / padString.length); //append to original to ensure we are longer than needed
}
return String(str) + padString.slice(0,targetLength);
}
};
/**
* Unescape HTML entities
* @param txt
* @returns {string}
*/
showdown.helper.unescapeHTMLEntities = function (txt) {
'use strict';
return txt
.replace(/&quot;/g, '"')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&amp;/g, '&');
};
showdown.helper._hashHTMLSpan = function (html, globals) {
return '¨C' + (globals.gHtmlSpans.push(html) - 1) + 'C';
};
/**
* Prepends a base URL to relative paths.
*
* @param {string} baseUrl the base URL to prepend to a relative path
* @param {string} url the path to modify, which may be relative
* @returns {string} the full URL
*/
showdown.helper.applyBaseUrl = function (baseUrl, url) {
// Only prepend if given a base URL and the path is not absolute.
if (baseUrl && baseUrl !== '' && !showdown.helper.isAbsolutePath(url)) {
let urlResolve = new showdown.helper.URLUtils(url, baseUrl);
url = urlResolve.href;
}
return url;
};
/**
* Checks if the given path is absolute.
*
* @param {string} path the path to test for absolution
* @returns {boolean} `true` if the given path is absolute, else `false`
*/
showdown.helper.isAbsolutePath = function (path) {
// Absolute paths begin with '[protocol:]//' or '#' (anchors)
return /(^([a-z]+:)?\/\/)|(^#)/i.test(path);
};
showdown.helper.URLUtils = function (url, baseURL) {
const pattern1 = /^\s+|\s+$/g;
const pattern2 = /^([^:\/?#]+:)?(?:\/\/(?:([^:@\/?#]*)(?::([^:@\/?#]*))?@)?(([^:\/?#]*)(?::(\d*))?))?([^?#]*)(\?[^#]*)?(#[\s\S]*)?/;
let m = String(url)
.replace(pattern1, '')
.match(pattern2);
if (!m) {
throw new RangeError();
}
let protocol = m[1] || '';
let username = m[2] || '';
let password = m[3] || '';
let host = m[4] || '';
let hostname = m[5] || '';
let port = m[6] || '';
let pathname = m[7] || '';
let search = m[8] || '';
let hash = m[9] || '';
if (baseURL !== undefined) {
let base = new showdown.helper.URLUtils(baseURL);
let flag = protocol === '' && host === '' && username === '';
if (flag && pathname === '' && search === '') {
search = base.search;
}
if (flag && pathname.charAt(0) !== '/') {
pathname = (pathname !== '' ? (((base.host !== '' || base.username !== '') && base.pathname === '' ? '/' : '') + base.pathname.slice(0, base.pathname.lastIndexOf('/') + 1) + pathname) : base.pathname);
}
// dot segments removal
let output = [];
pathname.replace(/^(\.\.?(\/|$))+/, '')
.replace(/\/(\.(\/|$))+/g, '/')
.replace(/\/\.\.$/, '/../')
.replace(/\/?[^\/]*/g, function (p) {
if (p === '/..') {
output.pop();
} else {
output.push(p);
}
});
pathname = output.join('').replace(/^\//, pathname.charAt(0) === '/' ? '/' : '');
if (flag) {
port = base.port;
hostname = base.hostname;
host = base.host;
password = base.password;
username = base.username;
}
if (protocol === '') {
protocol = base.protocol;
}
}
this.origin = protocol + (protocol !== '' || host !== '' ? '//' : '') + host;
this.href = protocol + (protocol !== '' || host !== '' ? '//' : '') + (username !== '' ? username + (password !== '' ? ':' + password : '') + '@' : '') + host + pathname + search + hash;
this.protocol = protocol;
this.username = username;
this.password = password;
this.host = host;
this.hostname = hostname;
this.port = port;
this.pathname = pathname;
this.search = search;
this.hash = hash;
};
/**
* Clones an object . If the second parameter is true, it deep clones the object.
* Note: It should not be used in other contexts than showdown, since this algorithm might fail for
* cyclic references, and dataypes such as Dates, RegExps, Typed Arrays, etc...
* @param {{}} obj Object to clone
* @param {boolean} [deep] [optional] If it should deep clone the object. Default is false
*/
showdown.helper.cloneObject = function (obj, deep) {
deep = !!deep;
if (obj === null || typeof (obj) !== 'object') {
return obj;
}
if (obj instanceof Date) {
return new Date(obj);
}
if (!deep) {
let newObj = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key];
}
}
return newObj;
}
if (typeof structuredClone === 'function') {
return structuredClone(obj);
} else {
// note: This is not a real deep clone, and might work in weird ways if used in a dif
//this is costly and should be used sparsly
return JSON.parse(JSON.stringify(obj));
}
};
/**
* Populate attributes in output text
* @param {{}} attributes
* @returns {string}
*/
showdown.helper._populateAttributes = function (attributes) {
let text = '';
if (!attributes || !showdown.helper.isObject(attributes)) {
return text;
}
for (let attr in attributes) {
if (attributes.hasOwnProperty(attr)) {
let key = attr,
val;
// since class is a javascript reserved word we use classes
if (attr === 'classes') {
key = 'class';
}
if (attributes[attr] === null || showdown.helper.isUndefined(attributes[attr])) {
val = null;
} else if (showdown.helper.isArray(attributes[attr])) {
val = attributes[attr].join(' ');
if (val === '') {
val = null;
}
} else if (showdown.helper.isString(attributes[attr])) {
val = attributes[attr];
} else if (showdown.helper.isNumber(attributes[attr])) {
val = String(attributes[attr]);
} else {
throw new TypeError('Attribute "' + attr + '" must be either an array or string but ' + typeof attributes[attr] + ' given');
}
// special attributes
switch (attr) {
// these attributes don't expect a value. If they are false, they should be removed. If they are true,
// they should be present but without any value
case 'checked':
case 'disabled':
if (val === true || val === 'true' || val === attr) {
text += ' ' + key;
}
// if falsy, they are just ignored
break;
// default behavior for all other attributes types
default:
text += (val === null) ? '' : ' ' + key + '="' + val + '"';
break;
}
}
}
return text;
};
/**
* Remove one level of line-leading tabs or spaces
* @param {string} text
* @returns {string}
*/
showdown.helper.outdent = function (text) {
'use strict';
if (!showdown.helper.isString(text)) {
return text;
}
return text.replace(/^(\t| {1,4})/gm, '');
};
/**
* Validate options
* @param {{}} options
* @returns {{}}
*/
showdown.helper.validateOptions = function (options) {
if (!showdown.helper.isObject(options)) {
throw new TypeError('Options must be an object, but ' + typeof options + ' given');
}
let defaultOptions = getDefaultOpts(false);
for (let opt in defaultOptions) {
if (!defaultOptions.hasOwnProperty(opt)) {
continue;
}
if (!options.hasOwnProperty(opt)) {
options[opt] = defaultOptions[opt].defaultValue;
}
// TODO: dirty code. think about this we refactoring options
switch (opt) {
case 'prefixHeaderId':
if (typeof options[opt] !== 'boolean' && !showdown.helper.isString(options[opt])) {
throw new TypeError('Option prefixHeaderId must be of type boolean or string but ' + typeof options[opt] + ' given');
}
break;
default:
if (typeof options[opt] !== defaultOptions[opt].type) {
throw new TypeError('Option ' + opt + ' must be of type ' + defaultOptions[opt].type + ' but ' + typeof options[opt] + ' given');
}
}
}
//options.headerLevelStart = (isNaN(parseInt(options.headerLevelStart))) ? 1 : parseInt(options.headerLevelStart);
return options;
};
/**
* POLYFILLS
*/
// use this instead of builtin is undefined for IE8 compatibility
if (typeof (console) === 'undefined') {
console = {
warn: function (msg) {
'use strict';
alert(msg);
},
log: function (msg) {
'use strict';
alert(msg);
},
error: function (msg) {
'use strict';
throw msg;
}
};
}
// Math.imul() polyfill
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul
if (!Math.imul) {
Math.imul = function (opA, opB) {
opB |= 0; // ensure that opB is an integer. opA will automatically be coerced.
// floating points give us 53 bits of precision to work with plus 1 sign bit
// automatically handled for our convienence:
// 1. 0x003fffff /*opA & 0x000fffff*/ * 0x7fffffff /*opB*/ = 0x1fffff7fc00001
// 0x1fffff7fc00001 < Number.MAX_SAFE_INTEGER /*0x1fffffffffffff*/
let result = (opA & 0x003fffff) * opB;
// 2. We can remove an integer coersion from the statement above because:
// 0x1fffff7fc00001 + 0xffc00000 = 0x1fffffff800001
// 0x1fffffff800001 < Number.MAX_SAFE_INTEGER /*0x1fffffffffffff*/
if (opA & 0xffc00000 /*!== 0*/) {
result += (opA & 0xffc00000) * opB | 0;
}
return result | 0;
};
}
/**
* Common regexes.
* We declare some common regexes to improve performance
*/
showdown.helper.regexes = {
asteriskDashTildeAndColon: /([*_:~])/g,
asteriskDashAndTilde: /([*_~])/g
};
/**
* EMOJIS LIST
*/
showdown.helper.emojis = {
'100': '\ud83d\udcaf',
'1234': '\ud83d\udd22',
'+1': '\ud83d\udc4d',
'-1': '\ud83d\udc4e',
'1st_place_medal': '\ud83e\udd47',
'2nd_place_medal': '\ud83e\udd48',
'3rd_place_medal': '\ud83e\udd49',
'8ball': '\ud83c\udfb1',
'a': '\ud83c\udd70\ufe0f',
'ab': '\ud83c\udd8e',
'abacus': '\ud83e\uddee',
'abc': '\ud83d\udd24',
'abcd': '\ud83d\udd21',
'accept': '\ud83c\ude51',
'adhesive_bandage': '\ud83e\ude79',
'adult': '\ud83e\uddd1',
'aerial_tramway': '\ud83d\udea1',
'afghanistan': '\ud83c\udde6\ud83c\uddeb',
'airplane': '\u2708\ufe0f',
'aland_islands': '\ud83c\udde6\ud83c\uddfd',
'alarm_clock': '\u23f0',
'albania': '\ud83c\udde6\ud83c\uddf1',
'alembic': '\u2697\ufe0f',
'algeria': '\ud83c\udde9\ud83c\uddff',
'alien': '\ud83d\udc7d',
'ambulance': '\ud83d\ude91',
'american_samoa': '\ud83c\udde6\ud83c\uddf8',
'amphora': '\ud83c\udffa',
'anchor': '\u2693',
'andorra': '\ud83c\udde6\ud83c\udde9',
'angel': '\ud83d\udc7c',
'anger': '\ud83d\udca2',
'angola': '\ud83c\udde6\ud83c\uddf4',
'angry': '\ud83d\ude20',
'anguilla': '\ud83c\udde6\ud83c\uddee',
'anguished': '\ud83d\ude27',
'ant': '\ud83d\udc1c',
'antarctica': '\ud83c\udde6\ud83c\uddf6',
'antigua_barbuda': '\ud83c\udde6\ud83c\uddec',
'apple': '\ud83c\udf4e',
'aquarius': '\u2652',
'argentina': '\ud83c\udde6\ud83c\uddf7',
'aries': '\u2648',
'armenia': '\ud83c\udde6\ud83c\uddf2',
'arrow_backward': '\u25c0\ufe0f',
'arrow_double_down': '\u23ec',
'arrow_double_up': '\u23eb',
'arrow_down': '\u2b07\ufe0f',
'arrow_down_small': '\ud83d\udd3d',
'arrow_forward': '\u25b6\ufe0f',
'arrow_heading_down': '\u2935\ufe0f',
'arrow_heading_up': '\u2934\ufe0f',
'arrow_left': '\u2b05\ufe0f',
'arrow_lower_left': '\u2199\ufe0f',
'arrow_lower_right': '\u2198\ufe0f',
'arrow_right': '\u27a1\ufe0f',
'arrow_right_hook': '\u21aa\ufe0f',
'arrow_up': '\u2b06\ufe0f',
'arrow_up_down': '\u2195\ufe0f',
'arrow_up_small': '\ud83d\udd3c',
'arrow_upper_left': '\u2196\ufe0f',
'arrow_upper_right': '\u2197\ufe0f',
'arrows_clockwise': '\ud83d\udd03',
'arrows_counterclockwise': '\ud83d\udd04',
'art': '\ud83c\udfa8',
'articulated_lorry': '\ud83d\ude9b',
'artificial_satellite': '\ud83d\udef0\ufe0f',
'artist': '\ud83e\uddd1\u200d\ud83c\udfa8',
'aruba': '\ud83c\udde6\ud83c\uddfc',
'ascension_island': '\ud83c\udde6\ud83c\udde8',
'asterisk': '*\ufe0f\u20e3',
'astonished': '\ud83d\ude32',
'astronaut': '\ud83e\uddd1\u200d\ud83d\ude80',
'athletic_shoe': '\ud83d\udc5f',
'atm': '\ud83c\udfe7',
'atom_symbol': '\u269b\ufe0f',
'australia': '\ud83c\udde6\ud83c\uddfa',
'austria': '\ud83c\udde6\ud83c\uddf9',
'auto_rickshaw': '\ud83d\udefa',
'avocado': '\ud83e\udd51',
'axe': '\ud83e\ude93',
'azerbaijan': '\ud83c\udde6\ud83c\uddff',
'b': '\ud83c\udd71\ufe0f',
'baby': '\ud83d\udc76',
'baby_bottle': '\ud83c\udf7c',
'baby_chick': '\ud83d\udc24',
'baby_symbol': '\ud83d\udebc',
'back': '\ud83d\udd19',
'bacon': '\ud83e\udd53',
'badger': '\ud83e\udda1',
'badminton': '\ud83c\udff8',
'bagel': '\ud83e\udd6f',
'baggage_claim': '\ud83d\udec4',
'baguette_bread': '\ud83e\udd56',
'bahamas': '\ud83c\udde7\ud83c\uddf8',
'bahrain': '\ud83c\udde7\ud83c\udded',
'balance_scale': '\u2696\ufe0f',
'bald_man': '\ud83d\udc68\u200d\ud83e\uddb2',
'bald_woman': '\ud83d\udc69\u200d\ud83e\uddb2',
'ballet_shoes': '\ud83e\ude70',
'balloon': '\ud83c\udf88',
'ballot_box': '\ud83d\uddf3\ufe0f',
'ballot_box_with_check': '\u2611\ufe0f',
'bamboo': '\ud83c\udf8d',
'banana': '\ud83c\udf4c',
'bangbang': '\u203c\ufe0f',
'bangladesh': '\ud83c\udde7\ud83c\udde9',
'banjo': '\ud83e\ude95',
'bank': '\ud83c\udfe6',
'bar_chart': '\ud83d\udcca',
'barbados': '\ud83c\udde7\ud83c\udde7',
'barber': '\ud83d\udc88',
'baseball': '\u26be',
'basket': '\ud83e\uddfa',
'basketball': '\ud83c\udfc0',
'basketball_man': '\u26f9\ufe0f\u200d\u2642\ufe0f',
'basketball_woman': '\u26f9\ufe0f\u200d\u2640\ufe0f',
'bat': '\ud83e\udd87',
'bath': '\ud83d\udec0',
'bathtub': '\ud83d\udec1',
'battery': '\ud83d\udd0b',
'beach_umbrella': '\ud83c\udfd6\ufe0f',
'bear': '\ud83d\udc3b',
'bearded_person': '\ud83e\uddd4',
'bed': '\ud83d\udecf\ufe0f',
'bee': '\ud83d\udc1d',
'beer': '\ud83c\udf7a',
'beers': '\ud83c\udf7b',
'beetle': '\ud83d\udc1e',
'beginner': '\ud83d\udd30',
'belarus': '\ud83c\udde7\ud83c\uddfe',
'belgium': '\ud83c\udde7\ud83c\uddea',
'belize': '\ud83c\udde7\ud83c\uddff',
'bell': '\ud83d\udd14',
'bellhop_bell': '\ud83d\udece\ufe0f',
'benin': '\ud83c\udde7\ud83c\uddef',
'bento': '\ud83c\udf71',
'bermuda': '\ud83c\udde7\ud83c\uddf2',
'beverage_box': '\ud83e\uddc3',
'bhutan': '\ud83c\udde7\ud83c\uddf9',
'bicyclist': '\ud83d\udeb4',
'bike': '\ud83d\udeb2',
'biking_man': '\ud83d\udeb4\u200d\u2642\ufe0f',
'biking_woman': '\ud83d\udeb4\u200d\u2640\ufe0f',
'bikini': '\ud83d\udc59',
'billed_cap': '\ud83e\udde2',
'biohazard': '\u2623\ufe0f',
'bird': '\ud83d\udc26',
'birthday': '\ud83c\udf82',
'black_circle': '\u26ab',
'black_flag': '\ud83c\udff4',
'black_heart': '\ud83d\udda4',
'black_joker': '\ud83c\udccf',
'black_large_square': '\u2b1b',
'black_medium_small_square': '\u25fe',
'black_medium_square': '\u25fc\ufe0f',
'black_nib': '\u2712\ufe0f',
'black_small_square': '\u25aa\ufe0f',
'black_square_button': '\ud83d\udd32',
'blond_haired_man': '\ud83d\udc71\u200d\u2642\ufe0f',
'blond_haired_person': '\ud83d\udc71',
'blond_haired_woman': '\ud83d\udc71\u200d\u2640\ufe0f',
'blonde_woman': '\ud83d\udc71\u200d\u2640\ufe0f',
'blossom': '\ud83c\udf3c',
'blowfish': '\ud83d\udc21',
'blue_book': '\ud83d\udcd8',
'blue_car': '\ud83d\ude99',
'blue_heart': '\ud83d\udc99',
'blue_square': '\ud83d\udfe6',
'blush': '\ud83d\ude0a',
'boar': '\ud83d\udc17',
'boat': '\u26f5',
'bolivia': '\ud83c\udde7\ud83c\uddf4',
'bomb': '\ud83d\udca3',
'bone': '\ud83e\uddb4',
'book': '\ud83d\udcd6',
'bookmark': '\ud83d\udd16',
'bookmark_tabs': '\ud83d\udcd1',
'books': '\ud83d\udcda',
'boom': '\ud83d\udca5',
'boot': '\ud83d\udc62',
'bosnia_herzegovina': '\ud83c\udde7\ud83c\udde6',
'botswana': '\ud83c\udde7\ud83c\uddfc',
'bouncing_ball_man': '\u26f9\ufe0f\u200d\u2642\ufe0f',
'bouncing_ball_person': '\u26f9\ufe0f',
'bouncing_ball_woman': '\u26f9\ufe0f\u200d\u2640\ufe0f',
'bouquet': '\ud83d\udc90',
'bouvet_island': '\ud83c\udde7\ud83c\uddfb',
'bow': '\ud83d\ude47',
'bow_and_arrow': '\ud83c\udff9',
'bowing_man': '\ud83d\ude47\u200d\u2642\ufe0f',
'bowing_woman': '\ud83d\ude47\u200d\u2640\ufe0f',
'bowl_with_spoon': '\ud83e\udd63',
'bowling': '\ud83c\udfb3',
'boxing_glove': '\ud83e\udd4a',
'boy': '\ud83d\udc66',
'brain': '\ud83e\udde0',
'brazil': '\ud83c\udde7\ud83c\uddf7',
'bread': '\ud83c\udf5e',
'breast_feeding': '\ud83e\udd31',
'bricks': '\ud83e\uddf1',
'bride_with_veil': '\ud83d\udc70',
'bridge_at_night': '\ud83c\udf09',
'briefcase': '\ud83d\udcbc',
'british_indian_ocean_territory': '\ud83c\uddee\ud83c\uddf4',
'british_virgin_islands': '\ud83c\uddfb\ud83c\uddec',
'broccoli': '\ud83e\udd66',
'broken_heart': '\ud83d\udc94',
'broom': '\ud83e\uddf9',
'brown_circle': '\ud83d\udfe4',
'brown_heart': '\ud83e\udd0e',
'brown_square': '\ud83d\udfeb',
'brunei': '\ud83c\udde7\ud83c\uddf3',
'bug': '\ud83d\udc1b',
'building_construction': '\ud83c\udfd7\ufe0f',
'bulb': '\ud83d\udca1',
'bulgaria': '\ud83c\udde7\ud83c\uddec',
'bullettrain_front': '\ud83d\ude85',
'bullettrain_side': '\ud83d\ude84',
'burkina_faso': '\ud83c\udde7\ud83c\uddeb',
'burrito': '\ud83c\udf2f',
'burundi': '\ud83c\udde7\ud83c\uddee',
'bus': '\ud83d\ude8c',
'business_suit_levitating': '\ud83d\udd74\ufe0f',
'busstop': '\ud83d\ude8f',
'bust_in_silhouette': '\ud83d\udc64',
'busts_in_silhouette': '\ud83d\udc65',
'butter': '\ud83e\uddc8',
'butterfly': '\ud83e\udd8b',
'cactus': '\ud83c\udf35',
'cake': '\ud83c\udf70',
'calendar': '\ud83d\udcc6',
'call_me_hand': '\ud83e\udd19',
'calling': '\ud83d\udcf2',
'cambodia': '\ud83c\uddf0\ud83c\udded',
'camel': '\ud83d\udc2b',
'camera': '\ud83d\udcf7',
'camera_flash': '\ud83d\udcf8',
'cameroon': '\ud83c\udde8\ud83c\uddf2',
'camping': '\ud83c\udfd5\ufe0f',
'canada': '\ud83c\udde8\ud83c\udde6',
'canary_islands': '\ud83c\uddee\ud83c\udde8',
'cancer': '\u264b',
'candle': '\ud83d\udd6f\ufe0f',
'candy': '\ud83c\udf6c',
'canned_food': '\ud83e\udd6b',
'canoe': '\ud83d\udef6',
'cape_verde': '\ud83c\udde8\ud83c\uddfb',
'capital_abcd': '\ud83d\udd20',
'capricorn': '\u2651',
'car': '\ud83d\ude97',
'card_file_box': '\ud83d\uddc3\ufe0f',
'card_index': '\ud83d\udcc7',
'card_index_dividers': '\ud83d\uddc2\ufe0f',
'caribbean_netherlands': '\ud83c\udde7\ud83c\uddf6',
'carousel_horse': '\ud83c\udfa0',
'carrot': '\ud83e\udd55',
'cartwheeling': '\ud83e\udd38',
'cat': '\ud83d\udc31',
'cat2': '\ud83d\udc08',
'cayman_islands': '\ud83c\uddf0\ud83c\uddfe',
'cd': '\ud83d\udcbf',
'central_african_republic': '\ud83c\udde8\ud83c\uddeb',
'ceuta_melilla': '\ud83c\uddea\ud83c\udde6',
'chad': '\ud83c\uddf9\ud83c\udde9',
'chains': '\u26d3\ufe0f',
'chair': '\ud83e\ude91',
'champagne': '\ud83c\udf7e',
'chart': '\ud83d\udcb9',
'chart_with_downwards_trend': '\ud83d\udcc9',
'chart_with_upwards_trend': '\ud83d\udcc8',
'checkered_flag': '\ud83c\udfc1',
'cheese': '\ud83e\uddc0',
'cherries': '\ud83c\udf52',
'cherry_blossom': '\ud83c\udf38',
'chess_pawn': '\u265f\ufe0f',
'chestnut': '\ud83c\udf30',
'chicken': '\ud83d\udc14',
'child': '\ud83e\uddd2',
'children_crossing': '\ud83d\udeb8',
'chile': '\ud83c\udde8\ud83c\uddf1',
'chipmunk': '\ud83d\udc3f\ufe0f',
'chocolate_bar': '\ud83c\udf6b',
'chopsticks': '\ud83e\udd62',
'christmas_island': '\ud83c\udde8\ud83c\uddfd',
'christmas_tree': '\ud83c\udf84',
'church': '\u26ea',
'cinema': '\ud83c\udfa6',
'circus_tent': '\ud83c\udfaa',
'city_sunrise': '\ud83c\udf07',
'city_sunset': '\ud83c\udf06',
'cityscape': '\ud83c\udfd9\ufe0f',
'cl': '\ud83c\udd91',
'clamp': '\ud83d\udddc\ufe0f',
'clap': '\ud83d\udc4f',
'clapper': '\ud83c\udfac',
'classical_building': '\ud83c\udfdb\ufe0f',
'climbing': '\ud83e\uddd7',
'climbing_man': '\ud83e\uddd7\u200d\u2642\ufe0f',
'climbing_woman': '\ud83e\uddd7\u200d\u2640\ufe0f',
'clinking_glasses': '\ud83e\udd42',
'clipboard': '\ud83d\udccb',
'clipperton_island': '\ud83c\udde8\ud83c\uddf5',
'clock1': '\ud83d\udd50',
'clock10': '\ud83d\udd59',
'clock1030': '\ud83d\udd65',
'clock11': '\ud83d\udd5a',
'clock1130': '\ud83d\udd66',
'clock12': '\ud83d\udd5b',
'clock1230': '\ud83d\udd67',
'clock130': '\ud83d\udd5c',
'clock2': '\ud83d\udd51',
'clock230': '\ud83d\udd5d',
'clock3': '\ud83d\udd52',
'clock330': '\ud83d\udd5e',
'clock4': '\ud83d\udd53',
'clock430': '\ud83d\udd5f',
'clock5': '\ud83d\udd54',
'clock530': '\ud83d\udd60',
'clock6': '\ud83d\udd55',
'clock630': '\ud83d\udd61',
'clock7': '\ud83d\udd56',
'clock730': '\ud83d\udd62',
'clock8': '\ud83d\udd57',
'clock830': '\ud83d\udd63',
'clock9': '\ud83d\udd58',
'clock930': '\ud83d\udd64',
'closed_book': '\ud83d\udcd5',
'closed_lock_with_key': '\ud83d\udd10',
'closed_umbrella': '\ud83c\udf02',
'cloud': '\u2601\ufe0f',
'cloud_with_lightning': '\ud83c\udf29\ufe0f',
'cloud_with_lightning_and_rain': '\u26c8\ufe0f',
'cloud_with_rain': '\ud83c\udf27\ufe0f',
'cloud_with_snow': '\ud83c\udf28\ufe0f',
'clown_face': '\ud83e\udd21',
'clubs': '\u2663\ufe0f',
'cn': '\ud83c\udde8\ud83c\uddf3',
'coat': '\ud83e\udde5',
'cocktail': '\ud83c\udf78',
'coconut': '\ud83e\udd65',
'cocos_islands': '\ud83c\udde8\ud83c\udde8',
'coffee': '\u2615',
'coffin': '\u26b0\ufe0f',
'cold_face': '\ud83e\udd76',
'cold_sweat': '\ud83d\ude30',
'collision': '\ud83d\udca5',
'colombia': '\ud83c\udde8\ud83c\uddf4',
'comet': '\u2604\ufe0f',
'comoros': '\ud83c\uddf0\ud83c\uddf2',
'compass': '\ud83e\udded',
'computer': '\ud83d\udcbb',
'computer_mouse': '\ud83d\uddb1\ufe0f',
'confetti_ball': '\ud83c\udf8a',
'confounded': '\ud83d\ude16',
'confused': '\ud83d\ude15',
'congo_brazzaville': '\ud83c\udde8\ud83c\uddec',
'congo_kinshasa': '\ud83c\udde8\ud83c\udde9',
'congratulations': '\u3297\ufe0f',
'construction': '\ud83d\udea7',
'construction_worker': '\ud83d\udc77',
'construction_worker_man': '\ud83d\udc77\u200d\u2642\ufe0f',
'construction_worker_woman': '\ud83d\udc77\u200d\u2640\ufe0f',
'control_knobs': '\ud83c\udf9b\ufe0f',
'convenience_store': '\ud83c\udfea',
'cook': '\ud83e\uddd1\u200d\ud83c\udf73',
'cook_islands': '\ud83c\udde8\ud83c\uddf0',
'cookie': '\ud83c\udf6a',
'cool': '\ud83c\udd92',
'cop': '\ud83d\udc6e',
'copyright': '\u00a9\ufe0f',
'corn': '\ud83c\udf3d',
'costa_rica': '\ud83c\udde8\ud83c\uddf7',
'cote_divoire': '\ud83c\udde8\ud83c\uddee',
'couch_and_lamp': '\ud83d\udecb\ufe0f',
'couple': '\ud83d\udc6b',
'couple_with_heart': '\ud83d\udc91',
'couple_with_heart_man_man': '\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc68',
'couple_with_heart_woman_man': '\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc68',
'couple_with_heart_woman_woman': '\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc69',
'couplekiss': '\ud83d\udc8f',
'couplekiss_man_man': '\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68',
'couplekiss_man_woman': '\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68',
'couplekiss_woman_woman': '\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69',
'cow': '\ud83d\udc2e',
'cow2': '\ud83d\udc04',
'cowboy_hat_face': '\ud83e\udd20',
'crab': '\ud83e\udd80',
'crayon': '\ud83d\udd8d\ufe0f',
'credit_card': '\ud83d\udcb3',
'crescent_moon': '\ud83c\udf19',
'cricket': '\ud83e\udd97',
'cricket_game': '\ud83c\udfcf',
'croatia': '\ud83c\udded\ud83c\uddf7',
'crocodile': '\ud83d\udc0a',
'croissant': '\ud83e\udd50',
'crossed_fingers': '\ud83e\udd1e',
'crossed_flags': '\ud83c\udf8c',
'crossed_swords': '\u2694\ufe0f',
'crown': '\ud83d\udc51',
'cry': '\ud83d\ude22',
'crying_cat_face': '\ud83d\ude3f',
'crystal_ball': '\ud83d\udd2e',
'cuba': '\ud83c\udde8\ud83c\uddfa',
'cucumber': '\ud83e\udd52',
'cup_with_straw': '\ud83e\udd64',
'cupcake': '\ud83e\uddc1',
'cupid': '\ud83d\udc98',
'curacao': '\ud83c\udde8\ud83c\uddfc',
'curling_stone': '\ud83e\udd4c',
'curly_haired_man': '\ud83d\udc68\u200d\ud83e\uddb1',
'curly_haired_woman': '\ud83d\udc69\u200d\ud83e\uddb1',
'curly_loop': '\u27b0',
'currency_exchange': '\ud83d\udcb1',
'curry': '\ud83c\udf5b',
'cursing_face': '\ud83e\udd2c',
'custard': '\ud83c\udf6e',
'customs': '\ud83d\udec3',
'cut_of_meat': '\ud83e\udd69',
'cyclone': '\ud83c\udf00',
'cyprus': '\ud83c\udde8\ud83c\uddfe',
'czech_republic': '\ud83c\udde8\ud83c\uddff',
'dagger': '\ud83d\udde1\ufe0f',
'dancer': '\ud83d\udc83',
'dancers': '\ud83d\udc6f',
'dancing_men': '\ud83d\udc6f\u200d\u2642\ufe0f',
'dancing_women': '\ud83d\udc6f\u200d\u2640\ufe0f',
'dango': '\ud83c\udf61',
'dark_sunglasses': '\ud83d\udd76\ufe0f',
'dart': '\ud83c\udfaf',
'dash': '\ud83d\udca8',
'date': '\ud83d\udcc5',
'de': '\ud83c\udde9\ud83c\uddea',
'deaf_man': '\ud83e\uddcf\u200d\u2642\ufe0f',
'deaf_person': '\ud83e\uddcf',
'deaf_woman': '\ud83e\uddcf\u200d\u2640\ufe0f',
'deciduous_tree': '\ud83c\udf33',
'deer': '\ud83e\udd8c',
'denmark': '\ud83c\udde9\ud83c\uddf0',
'department_store': '\ud83c\udfec',
'derelict_house': '\ud83c\udfda\ufe0f',
'desert': '\ud83c\udfdc\ufe0f',
'desert_island': '\ud83c\udfdd\ufe0f',
'desktop_computer': '\ud83d\udda5\ufe0f',
'detective': '\ud83d\udd75\ufe0f',
'diamond_shape_with_a_dot_inside': '\ud83d\udca0',
'diamonds': '\u2666\ufe0f',
'diego_garcia': '\ud83c\udde9\ud83c\uddec',
'disappointed': '\ud83d\ude1e',
'disappointed_relieved': '\ud83d\ude25',
'diving_mask': '\ud83e\udd3f',
'diya_lamp': '\ud83e\ude94',
'dizzy': '\ud83d\udcab',
'dizzy_face': '\ud83d\ude35',
'djibouti': '\ud83c\udde9\ud83c\uddef',
'dna': '\ud83e\uddec',
'do_not_litter': '\ud83d\udeaf',
'dog': '\ud83d\udc36',
'dog2': '\ud83d\udc15',
'dollar': '\ud83d\udcb5',
'dolls': '\ud83c\udf8e',
'dolphin': '\ud83d\udc2c',
'dominica': '\ud83c\udde9\ud83c\uddf2',
'dominican_republic': '\ud83c\udde9\ud83c\uddf4',
'door': '\ud83d\udeaa',
'doughnut': '\ud83c\udf69',
'dove': '\ud83d\udd4a\ufe0f',
'dragon': '\ud83d\udc09',
'dragon_face': '\ud83d\udc32',
'dress': '\ud83d\udc57',
'dromedary_camel': '\ud83d\udc2a',
'drooling_face': '\ud83e\udd24',
'drop_of_blood': '\ud83e\ude78',
'droplet': '\ud83d\udca7',
'drum': '\ud83e\udd41',
'duck': '\ud83e\udd86',
'dumpling': '\ud83e\udd5f',
'dvd': '\ud83d\udcc0',
'e-mail': '\ud83d\udce7',
'eagle': '\ud83e\udd85',
'ear': '\ud83d\udc42',
'ear_of_rice': '\ud83c\udf3e',
'ear_with_hearing_aid': '\ud83e\uddbb',
'earth_africa': '\ud83c\udf0d',
'earth_americas': '\ud83c\udf0e',
'earth_asia': '\ud83c\udf0f',
'ecuador': '\ud83c\uddea\ud83c\udde8',
'egg': '\ud83e\udd5a',
'eggplant': '\ud83c\udf46',
'egypt': '\ud83c\uddea\ud83c\uddec',
'eight': '8\ufe0f\u20e3',
'eight_pointed_black_star': '\u2734\ufe0f',
'eight_spoked_asterisk': '\u2733\ufe0f',
'eject_button': '\u23cf\ufe0f',
'el_salvador': '\ud83c\uddf8\ud83c\uddfb',
'electric_plug': '\ud83d\udd0c',
'elephant': '\ud83d\udc18',
'elf': '\ud83e\udddd',
'elf_man': '\ud83e\udddd\u200d\u2642\ufe0f',
'elf_woman': '\ud83e\udddd\u200d\u2640\ufe0f',
'email': '\u2709\ufe0f',
'end': '\ud83d\udd1a',
'england': '\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f',
'envelope': '\u2709\ufe0f',
'envelope_with_arrow': '\ud83d\udce9',
'equatorial_guinea': '\ud83c\uddec\ud83c\uddf6',
'eritrea': '\ud83c\uddea\ud83c\uddf7',
'es': '\ud83c\uddea\ud83c\uddf8',
'estonia': '\ud83c\uddea\ud83c\uddea',
'ethiopia': '\ud83c\uddea\ud83c\uddf9',
'eu': '\ud83c\uddea\ud83c\uddfa',
'euro': '\ud83d\udcb6',
'european_castle': '\ud83c\udff0',
'european_post_office': '\ud83c\udfe4',
'european_union': '\ud83c\uddea\ud83c\uddfa',
'evergreen_tree': '\ud83c\udf32',
'exclamation': '\u2757',
'exploding_head': '\ud83e\udd2f',
'expressionless': '\ud83d\ude11',
'eye': '\ud83d\udc41\ufe0f',
'eye_speech_bubble': '\ud83d\udc41\ufe0f\u200d\ud83d\udde8\ufe0f',
'eyeglasses': '\ud83d\udc53',
'eyes': '\ud83d\udc40',
'face_with_head_bandage': '\ud83e\udd15',
'face_with_thermometer': '\ud83e\udd12',
'facepalm': '\ud83e\udd26',
'facepunch': '\ud83d\udc4a',
'factory': '\ud83c\udfed',
'factory_worker': '\ud83e\uddd1\u200d\ud83c\udfed',
'fairy': '\ud83e\uddda',
'fairy_man': '\ud83e\uddda\u200d\u2642\ufe0f',
'fairy_woman': '\ud83e\uddda\u200d\u2640\ufe0f',
'falafel': '\ud83e\uddc6',
'falkland_islands': '\ud83c\uddeb\ud83c\uddf0',
'fallen_leaf': '\ud83c\udf42',
'family': '\ud83d\udc6a',
'family_man_boy': '\ud83d\udc68\u200d\ud83d\udc66',
'family_man_boy_boy': '\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66',
'family_man_girl': '\ud83d\udc68\u200d\ud83d\udc67',
'family_man_girl_boy': '\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d\udc66',
'family_man_girl_girl': '\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d\udc67',
'family_man_man_boy': '\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc66',
'family_man_man_boy_boy': '\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66',
'family_man_man_girl': '\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc67',
'family_man_man_girl_boy': '\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d\udc66',
'family_man_man_girl_girl': '\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d\udc67',
'family_man_woman_boy': '\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc66',
'family_man_woman_boy_boy': '\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66',
'family_man_woman_girl': '\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67',
'family_man_woman_girl_boy': '\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc66',
'family_man_woman_girl_girl': '\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc67',
'family_woman_boy': '\ud83d\udc69\u200d\ud83d\udc66',
'family_woman_boy_boy': '\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66',
'family_woman_girl': '\ud83d\udc69\u200d\ud83d\udc67',
'family_woman_girl_boy': '\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc66',
'family_woman_girl_girl': '\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc67',
'family_woman_woman_boy': '\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc66',
'family_woman_woman_boy_boy': '\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66',
'family_woman_woman_girl': '\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67',
'family_woman_woman_girl_boy': '\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc66',
'family_woman_woman_girl_girl': '\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc67',
'farmer': '\ud83e\uddd1\u200d\ud83c\udf3e',
'faroe_islands': '\ud83c\uddeb\ud83c\uddf4',
'fast_forward': '\u23e9',
'fax': '\ud83d\udce0',
'fearful': '\ud83d\ude28',
'feet': '\ud83d\udc3e',
'female_detective': '\ud83d\udd75\ufe0f\u200d\u2640\ufe0f',
'female_sign': '\u2640\ufe0f',
'ferris_wheel': '\ud83c\udfa1',
'ferry': '\u26f4\ufe0f',
'field_hockey': '\ud83c\udfd1',
'fiji': '\ud83c\uddeb\ud83c\uddef',
'file_cabinet': '\ud83d\uddc4\ufe0f',
'file_folder': '\ud83d\udcc1',
'film_projector': '\ud83d\udcfd\ufe0f',
'film_strip': '\ud83c\udf9e\ufe0f',
'finland': '\ud83c\uddeb\ud83c\uddee',
'fire': '\ud83d\udd25',
'fire_engine': '\ud83d\ude92',
'fire_extinguisher': '\ud83e\uddef',
'firecracker': '\ud83e\udde8',
'firefighter': '\ud83e\uddd1\u200d\ud83d\ude92',
'fireworks': '\ud83c\udf86',
'first_quarter_moon': '\ud83c\udf13',
'first_quarter_moon_with_face': '\ud83c\udf1b',
'fish': '\ud83d\udc1f',
'fish_cake': '\ud83c\udf65',
'fishing_pole_and_fish': '\ud83c\udfa3',
'fist': '\u270a',
'fist_left': '\ud83e\udd1b',
'fist_oncoming': '\ud83d\udc4a',
'fist_raised': '\u270a',
'fist_right': '\ud83e\udd1c',
'five': '5\ufe0f\u20e3',
'flags': '\ud83c\udf8f',
'flamingo': '\ud83e\udda9',
'flashlight': '\ud83d\udd26',
'flat_shoe': '\ud83e\udd7f',
'fleur_de_lis': '\u269c\ufe0f',
'flight_arrival': '\ud83d\udeec',
'flight_departure': '\ud83d\udeeb',
'flipper': '\ud83d\udc2c',
'floppy_disk': '\ud83d\udcbe',
'flower_playing_cards': '\ud83c\udfb4',
'flushed': '\ud83d\ude33',
'flying_disc': '\ud83e\udd4f',
'flying_saucer': '\ud83d\udef8',
'fog': '\ud83c\udf2b\ufe0f',
'foggy': '\ud83c\udf01',
'foot': '\ud83e\uddb6',
'football': '\ud83c\udfc8',
'footprints': '\ud83d\udc63',
'fork_and_knife': '\ud83c\udf74',
'fortune_cookie': '\ud83e\udd60',
'fountain': '\u26f2',
'fountain_pen': '\ud83d\udd8b\ufe0f',
'four': '4\ufe0f\u20e3',
'four_leaf_clover': '\ud83c\udf40',
'fox_face': '\ud83e\udd8a',
'fr': '\ud83c\uddeb\ud83c\uddf7',
'framed_picture': '\ud83d\uddbc\ufe0f',
'free': '\ud83c\udd93',
'french_guiana': '\ud83c\uddec\ud83c\uddeb',
'french_polynesia': '\ud83c\uddf5\ud83c\uddeb',
'french_southern_territories': '\ud83c\uddf9\ud83c\uddeb',
'fried_egg': '\ud83c\udf73',
'fried_shrimp': '\ud83c\udf64',
'fries': '\ud83c\udf5f',
'frog': '\ud83d\udc38',
'frowning': '\ud83d\ude26',
'frowning_face': '\u2639\ufe0f',
'frowning_man': '\ud83d\ude4d\u200d\u2642\ufe0f',
'frowning_person': '\ud83d\ude4d',
'frowning_woman': '\ud83d\ude4d\u200d\u2640\ufe0f',
'fu': '\ud83d\udd95',
'fuelpump': '\u26fd',
'full_moon': '\ud83c\udf15',
'full_moon_with_face': '\ud83c\udf1d',
'funeral_urn': '\u26b1\ufe0f',
'gabon': '\ud83c\uddec\ud83c\udde6',
'gambia': '\ud83c\uddec\ud83c\uddf2',
'game_die': '\ud83c\udfb2',
'garlic': '\ud83e\uddc4',
'gb': '\ud83c\uddec\ud83c\udde7',
'gear': '\u2699\ufe0f',
'gem': '\ud83d\udc8e',
'gemini': '\u264a',
'genie': '\ud83e\uddde',
'genie_man': '\ud83e\uddde\u200d\u2642\ufe0f',
'genie_woman': '\ud83e\uddde\u200d\u2640\ufe0f',
'georgia': '\ud83c\uddec\ud83c\uddea',
'ghana': '\ud83c\uddec\ud83c\udded',
'ghost': '\ud83d\udc7b',
'gibraltar': '\ud83c\uddec\ud83c\uddee',
'gift': '\ud83c\udf81',
'gift_heart': '\ud83d\udc9d',
'giraffe': '\ud83e\udd92',
'girl': '\ud83d\udc67',
'globe_with_meridians': '\ud83c\udf10',
'gloves': '\ud83e\udde4',
'goal_net': '\ud83e\udd45',
'goat': '\ud83d\udc10',
'goggles': '\ud83e\udd7d',
'golf': '\u26f3',
'golfing': '\ud83c\udfcc\ufe0f',
'golfing_man': '\ud83c\udfcc\ufe0f\u200d\u2642\ufe0f',
'golfing_woman': '\ud83c\udfcc\ufe0f\u200d\u2640\ufe0f',
'gorilla': '\ud83e\udd8d',
'grapes': '\ud83c\udf47',
'greece': '\ud83c\uddec\ud83c\uddf7',
'green_apple': '\ud83c\udf4f',
'green_book': '\ud83d\udcd7',
'green_circle': '\ud83d\udfe2',
'green_heart': '\ud83d\udc9a',
'green_salad': '\ud83e\udd57',
'green_square': '\ud83d\udfe9',
'greenland': '\ud83c\uddec\ud83c\uddf1',
'grenada': '\ud83c\uddec\ud83c\udde9',
'grey_exclamation': '\u2755',
'grey_question': '\u2754',
'grimacing': '\ud83d\ude2c',
'grin': '\ud83d\ude01',
'grinning': '\ud83d\ude00',
'guadeloupe': '\ud83c\uddec\ud83c\uddf5',
'guam': '\ud83c\uddec\ud83c\uddfa',
'guard': '\ud83d\udc82',
'guardsman': '\ud83d\udc82\u200d\u2642\ufe0f',
'guardswoman': '\ud83d\udc82\u200d\u2640\ufe0f',
'guatemala': '\ud83c\uddec\ud83c\uddf9',
'guernsey': '\ud83c\uddec\ud83c\uddec',
'guide_dog': '\ud83e\uddae',
'guinea': '\ud83c\uddec\ud83c\uddf3',
'guinea_bissau': '\ud83c\uddec\ud83c\uddfc',
'guitar': '\ud83c\udfb8',
'gun': '\ud83d\udd2b',
'guyana': '\ud83c\uddec\ud83c\uddfe',
'haircut': '\ud83d\udc87',
'haircut_man': '\ud83d\udc87\u200d\u2642\ufe0f',
'haircut_woman': '\ud83d\udc87\u200d\u2640\ufe0f',
'haiti': '\ud83c\udded\ud83c\uddf9',
'hamburger': '\ud83c\udf54',
'hammer': '\ud83d\udd28',
'hammer_and_pick': '\u2692\ufe0f',
'hammer_and_wrench': '\ud83d\udee0\ufe0f',
'hamster': '\ud83d\udc39',
'hand': '\u270b',
'hand_over_mouth': '\ud83e\udd2d',
'handbag': '\ud83d\udc5c',
'handball_person': '\ud83e\udd3e',
'handshake': '\ud83e\udd1d',
'hankey': '\ud83d\udca9',
'hash': '#\ufe0f\u20e3',
'hatched_chick': '\ud83d\udc25',
'hatching_chick': '\ud83d\udc23',
'headphones': '\ud83c\udfa7',
'health_worker': '\ud83e\uddd1\u200d\u2695\ufe0f',
'hear_no_evil': '\ud83d\ude49',
'heard_mcdonald_islands': '\ud83c\udded\ud83c\uddf2',
'heart': '\u2764\ufe0f',
'heart_decoration': '\ud83d\udc9f',
'heart_eyes': '\ud83d\ude0d',
'heart_eyes_cat': '\ud83d\ude3b',
'heartbeat': '\ud83d\udc93',
'heartpulse': '\ud83d\udc97',
'hearts': '\u2665\ufe0f',
'heavy_check_mark': '\u2714\ufe0f',
'heavy_division_sign': '\u2797',
'heavy_dollar_sign': '\ud83d\udcb2',
'heavy_exclamation_mark': '\u2757',
'heavy_heart_exclamation': '\u2763\ufe0f',
'heavy_minus_sign': '\u2796',
'heavy_multiplication_x': '\u2716\ufe0f',
'heavy_plus_sign': '\u2795',
'hedgehog': '\ud83e\udd94',
'helicopter': '\ud83d\ude81',
'herb': '\ud83c\udf3f',
'hibiscus': '\ud83c\udf3a',
'high_brightness': '\ud83d\udd06',
'high_heel': '\ud83d\udc60',
'hiking_boot': '\ud83e\udd7e',
'hindu_temple': '\ud83d\uded5',
'hippopotamus': '\ud83e\udd9b',
'hocho': '\ud83d\udd2a',
'hole': '\ud83d\udd73\ufe0f',
'honduras': '\ud83c\udded\ud83c\uddf3',
'honey_pot': '\ud83c\udf6f',
'honeybee': '\ud83d\udc1d',
'hong_kong': '\ud83c\udded\ud83c\uddf0',
'horse': '\ud83d\udc34',
'horse_racing': '\ud83c\udfc7',
'hospital': '\ud83c\udfe5',
'hot_face': '\ud83e\udd75',
'hot_pepper': '\ud83c\udf36\ufe0f',
'hotdog': '\ud83c\udf2d',
'hotel': '\ud83c\udfe8',
'hotsprings': '\u2668\ufe0f',
'hourglass': '\u231b',
'hourglass_flowing_sand': '\u23f3',
'house': '\ud83c\udfe0',
'house_with_garden': '\ud83c\udfe1',
'houses': '\ud83c\udfd8\ufe0f',
'hugs': '\ud83e\udd17',
'hungary': '\ud83c\udded\ud83c\uddfa',
'hushed': '\ud83d\ude2f',
'ice_cream': '\ud83c\udf68',
'ice_cube': '\ud83e\uddca',
'ice_hockey': '\ud83c\udfd2',
'ice_skate': '\u26f8\ufe0f',
'icecream': '\ud83c\udf66',
'iceland': '\ud83c\uddee\ud83c\uddf8',
'id': '\ud83c\udd94',
'ideograph_advantage': '\ud83c\ude50',
'imp': '\ud83d\udc7f',
'inbox_tray': '\ud83d\udce5',
'incoming_envelope': '\ud83d\udce8',
'india': '\ud83c\uddee\ud83c\uddf3',
'indonesia': '\ud83c\uddee\ud83c\udde9',
'infinity': '\u267e\ufe0f',
'information_desk_person': '\ud83d\udc81',
'information_source': '\u2139\ufe0f',
'innocent': '\ud83d\ude07',
'interrobang': '\u2049\ufe0f',
'iphone': '\ud83d\udcf1',
'iran': '\ud83c\uddee\ud83c\uddf7',
'iraq': '\ud83c\uddee\ud83c\uddf6',
'ireland': '\ud83c\uddee\ud83c\uddea',
'isle_of_man': '\ud83c\uddee\ud83c\uddf2',
'israel': '\ud83c\uddee\ud83c\uddf1',
'it': '\ud83c\uddee\ud83c\uddf9',
'izakaya_lantern': '\ud83c\udfee',
'jack_o_lantern': '\ud83c\udf83',
'jamaica': '\ud83c\uddef\ud83c\uddf2',
'japan': '\ud83d\uddfe',
'japanese_castle': '\ud83c\udfef',
'japanese_goblin': '\ud83d\udc7a',
'japanese_ogre': '\ud83d\udc79',
'jeans': '\ud83d\udc56',
'jersey': '\ud83c\uddef\ud83c\uddea',
'jigsaw': '\ud83e\udde9',
'jordan': '\ud83c\uddef\ud83c\uddf4',
'joy': '\ud83d\ude02',
'joy_cat': '\ud83d\ude39',
'joystick': '\ud83d\udd79\ufe0f',
'jp': '\ud83c\uddef\ud83c\uddf5',
'judge': '\ud83e\uddd1\u200d\u2696\ufe0f',
'juggling_person': '\ud83e\udd39',
'kaaba': '\ud83d\udd4b',
'kangaroo': '\ud83e\udd98',
'kazakhstan': '\ud83c\uddf0\ud83c\uddff',
'kenya': '\ud83c\uddf0\ud83c\uddea',
'key': '\ud83d\udd11',
'keyboard': '\u2328\ufe0f',
'keycap_ten': '\ud83d\udd1f',
'kick_scooter': '\ud83d\udef4',
'kimono': '\ud83d\udc58',
'kiribati': '\ud83c\uddf0\ud83c\uddee',
'kiss': '\ud83d\udc8b',
'kissing': '\ud83d\ude17',
'kissing_cat': '\ud83d\ude3d',
'kissing_closed_eyes': '\ud83d\ude1a',
'kissing_heart': '\ud83d\ude18',
'kissing_smiling_eyes': '\ud83d\ude19',
'kite': '\ud83e\ude81',
'kiwi_fruit': '\ud83e\udd5d',
'kneeling_man': '\ud83e\uddce\u200d\u2642\ufe0f',
'kneeling_person': '\ud83e\uddce',
'kneeling_woman': '\ud83e\uddce\u200d\u2640\ufe0f',
'knife': '\ud83d\udd2a',
'koala': '\ud83d\udc28',
'koko': '\ud83c\ude01',
'kosovo': '\ud83c\uddfd\ud83c\uddf0',
'kr': '\ud83c\uddf0\ud83c\uddf7',
'kuwait': '\ud83c\uddf0\ud83c\uddfc',
'kyrgyzstan': '\ud83c\uddf0\ud83c\uddec',
'lab_coat': '\ud83e\udd7c',
'label': '\ud83c\udff7\ufe0f',
'lacrosse': '\ud83e\udd4d',
'lantern': '\ud83c\udfee',
'laos': '\ud83c\uddf1\ud83c\udde6',
'large_blue_circle': '\ud83d\udd35',
'large_blue_diamond': '\ud83d\udd37',
'large_orange_diamond': '\ud83d\udd36',
'last_quarter_moon': '\ud83c\udf17',
'last_quarter_moon_with_face': '\ud83c\udf1c',
'latin_cross': '\u271d\ufe0f',
'latvia': '\ud83c\uddf1\ud83c\uddfb',
'laughing': '\ud83d\ude06',
'leafy_green': '\ud83e\udd6c',
'leaves': '\ud83c\udf43',
'lebanon': '\ud83c\uddf1\ud83c\udde7',
'ledger': '\ud83d\udcd2',
'left_luggage': '\ud83d\udec5',
'left_right_arrow': '\u2194\ufe0f',
'left_speech_bubble': '\ud83d\udde8\ufe0f',
'leftwards_arrow_with_hook': '\u21a9\ufe0f',
'leg': '\ud83e\uddb5',
'lemon': '\ud83c\udf4b',
'leo': '\u264c',
'leopard': '\ud83d\udc06',
'lesotho': '\ud83c\uddf1\ud83c\uddf8',
'level_slider': '\ud83c\udf9a\ufe0f',
'liberia': '\ud83c\uddf1\ud83c\uddf7',
'libra': '\u264e',
'libya': '\ud83c\uddf1\ud83c\uddfe',
'liechtenstein': '\ud83c\uddf1\ud83c\uddee',
'light_rail': '\ud83d\ude88',
'link': '\ud83d\udd17',
'lion': '\ud83e\udd81',
'lips': '\ud83d\udc44',
'lipstick': '\ud83d\udc84',
'lithuania': '\ud83c\uddf1\ud83c\uddf9',
'lizard': '\ud83e\udd8e',
'llama': '\ud83e\udd99',
'lobster': '\ud83e\udd9e',
'lock': '\ud83d\udd12',
'lock_with_ink_pen': '\ud83d\udd0f',
'lollipop': '\ud83c\udf6d',
'loop': '\u27bf',
'lotion_bottle': '\ud83e\uddf4',
'lotus_position': '\ud83e\uddd8',
'lotus_position_man': '\ud83e\uddd8\u200d\u2642\ufe0f',
'lotus_position_woman': '\ud83e\uddd8\u200d\u2640\ufe0f',
'loud_sound': '\ud83d\udd0a',
'loudspeaker': '\ud83d\udce2',
'love_hotel': '\ud83c\udfe9',
'love_letter': '\ud83d\udc8c',
'love_you_gesture': '\ud83e\udd1f',
'low_brightness': '\ud83d\udd05',
'luggage': '\ud83e\uddf3',
'luxembourg': '\ud83c\uddf1\ud83c\uddfa',
'lying_face': '\ud83e\udd25',
'm': '\u24c2\ufe0f',
'macau': '\ud83c\uddf2\ud83c\uddf4',
'macedonia': '\ud83c\uddf2\ud83c\uddf0',
'madagascar': '\ud83c\uddf2\ud83c\uddec',
'mag': '\ud83d\udd0d',
'mag_right': '\ud83d\udd0e',
'mage': '\ud83e\uddd9',
'mage_man': '\ud83e\uddd9\u200d\u2642\ufe0f',
'mage_woman': '\ud83e\uddd9\u200d\u2640\ufe0f',
'magnet': '\ud83e\uddf2',
'mahjong': '\ud83c\udc04',
'mailbox': '\ud83d\udceb',
'mailbox_closed': '\ud83d\udcea',
'mailbox_with_mail': '\ud83d\udcec',
'mailbox_with_no_mail': '\ud83d\udced',
'malawi': '\ud83c\uddf2\ud83c\uddfc',
'malaysia': '\ud83c\uddf2\ud83c\uddfe',
'maldives': '\ud83c\uddf2\ud83c\uddfb',
'male_detective': '\ud83d\udd75\ufe0f\u200d\u2642\ufe0f',
'male_sign': '\u2642\ufe0f',
'mali': '\ud83c\uddf2\ud83c\uddf1',
'malta': '\ud83c\uddf2\ud83c\uddf9',
'man': '\ud83d\udc68',
'man_artist': '\ud83d\udc68\u200d\ud83c\udfa8',
'man_astronaut': '\ud83d\udc68\u200d\ud83d\ude80',
'man_cartwheeling': '\ud83e\udd38\u200d\u2642\ufe0f',
'man_cook': '\ud83d\udc68\u200d\ud83c\udf73',
'man_dancing': '\ud83d\udd7a',
'man_facepalming': '\ud83e\udd26\u200d\u2642\ufe0f',
'man_factory_worker': '\ud83d\udc68\u200d\ud83c\udfed',
'man_farmer': '\ud83d\udc68\u200d\ud83c\udf3e',
'man_firefighter': '\ud83d\udc68\u200d\ud83d\ude92',
'man_health_worker': '\ud83d\udc68\u200d\u2695\ufe0f',
'man_in_manual_wheelchair': '\ud83d\udc68\u200d\ud83e\uddbd',
'man_in_motorized_wheelchair': '\ud83d\udc68\u200d\ud83e\uddbc',
'man_in_tuxedo': '\ud83e\udd35',
'man_judge': '\ud83d\udc68\u200d\u2696\ufe0f',
'man_juggling': '\ud83e\udd39\u200d\u2642\ufe0f',
'man_mechanic': '\ud83d\udc68\u200d\ud83d\udd27',
'man_office_worker': '\ud83d\udc68\u200d\ud83d\udcbc',
'man_pilot': '\ud83d\udc68\u200d\u2708\ufe0f',
'man_playing_handball': '\ud83e\udd3e\u200d\u2642\ufe0f',
'man_playing_water_polo': '\ud83e\udd3d\u200d\u2642\ufe0f',
'man_scientist': '\ud83d\udc68\u200d\ud83d\udd2c',
'man_shrugging': '\ud83e\udd37\u200d\u2642\ufe0f',
'man_singer': '\ud83d\udc68\u200d\ud83c\udfa4',
'man_student': '\ud83d\udc68\u200d\ud83c\udf93',
'man_teacher': '\ud83d\udc68\u200d\ud83c\udfeb',
'man_technologist': '\ud83d\udc68\u200d\ud83d\udcbb',
'man_with_gua_pi_mao': '\ud83d\udc72',
'man_with_probing_cane': '\ud83d\udc68\u200d\ud83e\uddaf',
'man_with_turban': '\ud83d\udc73\u200d\u2642\ufe0f',
'mandarin': '\ud83c\udf4a',
'mango': '\ud83e\udd6d',
'mans_shoe': '\ud83d\udc5e',
'mantelpiece_clock': '\ud83d\udd70\ufe0f',
'manual_wheelchair': '\ud83e\uddbd',
'maple_leaf': '\ud83c\udf41',
'marshall_islands': '\ud83c\uddf2\ud83c\udded',
'martial_arts_uniform': '\ud83e\udd4b',
'martinique': '\ud83c\uddf2\ud83c\uddf6',
'mask': '\ud83d\ude37',
'massage': '\ud83d\udc86',
'massage_man': '\ud83d\udc86\u200d\u2642\ufe0f',
'massage_woman': '\ud83d\udc86\u200d\u2640\ufe0f',
'mate': '\ud83e\uddc9',
'mauritania': '\ud83c\uddf2\ud83c\uddf7',
'mauritius': '\ud83c\uddf2\ud83c\uddfa',
'mayotte': '\ud83c\uddfe\ud83c\uddf9',
'meat_on_bone': '\ud83c\udf56',
'mechanic': '\ud83e\uddd1\u200d\ud83d\udd27',
'mechanical_arm': '\ud83e\uddbe',
'mechanical_leg': '\ud83e\uddbf',
'medal_military': '\ud83c\udf96\ufe0f',
'medal_sports': '\ud83c\udfc5',
'medical_symbol': '\u2695\ufe0f',
'mega': '\ud83d\udce3',
'melon': '\ud83c\udf48',
'memo': '\ud83d\udcdd',
'men_wrestling': '\ud83e\udd3c\u200d\u2642\ufe0f',
'menorah': '\ud83d\udd4e',
'mens': '\ud83d\udeb9',
'mermaid': '\ud83e\udddc\u200d\u2640\ufe0f',
'merman': '\ud83e\udddc\u200d\u2642\ufe0f',
'merperson': '\ud83e\udddc',
'metal': '\ud83e\udd18',
'metro': '\ud83d\ude87',
'mexico': '\ud83c\uddf2\ud83c\uddfd',
'microbe': '\ud83e\udda0',
'micronesia': '\ud83c\uddeb\ud83c\uddf2',
'microphone': '\ud83c\udfa4',
'microscope': '\ud83d\udd2c',
'middle_finger': '\ud83d\udd95',
'milk_glass': '\ud83e\udd5b',
'milky_way': '\ud83c\udf0c',
'minibus': '\ud83d\ude90',
'minidisc': '\ud83d\udcbd',
'mobile_phone_off': '\ud83d\udcf4',
'moldova': '\ud83c\uddf2\ud83c\udde9',
'monaco': '\ud83c\uddf2\ud83c\udde8',
'money_mouth_face': '\ud83e\udd11',
'money_with_wings': '\ud83d\udcb8',
'moneybag': '\ud83d\udcb0',
'mongolia': '\ud83c\uddf2\ud83c\uddf3',
'monkey': '\ud83d\udc12',
'monkey_face': '\ud83d\udc35',
'monocle_face': '\ud83e\uddd0',
'monorail': '\ud83d\ude9d',
'montenegro': '\ud83c\uddf2\ud83c\uddea',
'montserrat': '\ud83c\uddf2\ud83c\uddf8',
'moon': '\ud83c\udf14',
'moon_cake': '\ud83e\udd6e',
'morocco': '\ud83c\uddf2\ud83c\udde6',
'mortar_board': '\ud83c\udf93',
'mosque': '\ud83d\udd4c',
'mosquito': '\ud83e\udd9f',
'motor_boat': '\ud83d\udee5\ufe0f',
'motor_scooter': '\ud83d\udef5',
'motorcycle': '\ud83c\udfcd\ufe0f',
'motorized_wheelchair': '\ud83e\uddbc',
'motorway': '\ud83d\udee3\ufe0f',
'mount_fuji': '\ud83d\uddfb',
'mountain': '\u26f0\ufe0f',
'mountain_bicyclist': '\ud83d\udeb5',
'mountain_biking_man': '\ud83d\udeb5\u200d\u2642\ufe0f',
'mountain_biking_woman': '\ud83d\udeb5\u200d\u2640\ufe0f',
'mountain_cableway': '\ud83d\udea0',
'mountain_railway': '\ud83d\ude9e',
'mountain_snow': '\ud83c\udfd4\ufe0f',
'mouse': '\ud83d\udc2d',
'mouse2': '\ud83d\udc01',
'movie_camera': '\ud83c\udfa5',
'moyai': '\ud83d\uddff',
'mozambique': '\ud83c\uddf2\ud83c\uddff',
'mrs_claus': '\ud83e\udd36',
'muscle': '\ud83d\udcaa',
'mushroom': '\ud83c\udf44',
'musical_keyboard': '\ud83c\udfb9',
'musical_note': '\ud83c\udfb5',
'musical_score': '\ud83c\udfbc',
'mute': '\ud83d\udd07',
'myanmar': '\ud83c\uddf2\ud83c\uddf2',
'nail_care': '\ud83d\udc85',
'name_badge': '\ud83d\udcdb',
'namibia': '\ud83c\uddf3\ud83c\udde6',
'national_park': '\ud83c\udfde\ufe0f',
'nauru': '\ud83c\uddf3\ud83c\uddf7',
'nauseated_face': '\ud83e\udd22',
'nazar_amulet': '\ud83e\uddff',
'necktie': '\ud83d\udc54',
'negative_squared_cross_mark': '\u274e',
'nepal': '\ud83c\uddf3\ud83c\uddf5',
'nerd_face': '\ud83e\udd13',
'netherlands': '\ud83c\uddf3\ud83c\uddf1',
'neutral_face': '\ud83d\ude10',
'new': '\ud83c\udd95',
'new_caledonia': '\ud83c\uddf3\ud83c\udde8',
'new_moon': '\ud83c\udf11',
'new_moon_with_face': '\ud83c\udf1a',
'new_zealand': '\ud83c\uddf3\ud83c\uddff',
'newspaper': '\ud83d\udcf0',
'newspaper_roll': '\ud83d\uddde\ufe0f',
'next_track_button': '\u23ed\ufe0f',
'ng': '\ud83c\udd96',
'ng_man': '\ud83d\ude45\u200d\u2642\ufe0f',
'ng_woman': '\ud83d\ude45\u200d\u2640\ufe0f',
'nicaragua': '\ud83c\uddf3\ud83c\uddee',
'niger': '\ud83c\uddf3\ud83c\uddea',
'nigeria': '\ud83c\uddf3\ud83c\uddec',
'night_with_stars': '\ud83c\udf03',
'nine': '9\ufe0f\u20e3',
'niue': '\ud83c\uddf3\ud83c\uddfa',
'no_bell': '\ud83d\udd15',
'no_bicycles': '\ud83d\udeb3',
'no_entry': '\u26d4',
'no_entry_sign': '\ud83d\udeab',
'no_good': '\ud83d\ude45',
'no_good_man': '\ud83d\ude45\u200d\u2642\ufe0f',
'no_good_woman': '\ud83d\ude45\u200d\u2640\ufe0f',
'no_mobile_phones': '\ud83d\udcf5',
'no_mouth': '\ud83d\ude36',
'no_pedestrians': '\ud83d\udeb7',
'no_smoking': '\ud83d\udead',
'non-potable_water': '\ud83d\udeb1',
'norfolk_island': '\ud83c\uddf3\ud83c\uddeb',
'north_korea': '\ud83c\uddf0\ud83c\uddf5',
'northern_mariana_islands': '\ud83c\uddf2\ud83c\uddf5',
'norway': '\ud83c\uddf3\ud83c\uddf4',
'nose': '\ud83d\udc43',
'notebook': '\ud83d\udcd3',
'notebook_with_decorative_cover': '\ud83d\udcd4',
'notes': '\ud83c\udfb6',
'nut_and_bolt': '\ud83d\udd29',
'o': '\u2b55',
'o2': '\ud83c\udd7e\ufe0f',
'ocean': '\ud83c\udf0a',
'octopus': '\ud83d\udc19',
'oden': '\ud83c\udf62',
'office': '\ud83c\udfe2',
'office_worker': '\ud83e\uddd1\u200d\ud83d\udcbc',
'oil_drum': '\ud83d\udee2\ufe0f',
'ok': '\ud83c\udd97',
'ok_hand': '\ud83d\udc4c',
'ok_man': '\ud83d\ude46\u200d\u2642\ufe0f',
'ok_person': '\ud83d\ude46',
'ok_woman': '\ud83d\ude46\u200d\u2640\ufe0f',
'old_key': '\ud83d\udddd\ufe0f',
'older_adult': '\ud83e\uddd3',
'older_man': '\ud83d\udc74',
'older_woman': '\ud83d\udc75',
'om': '\ud83d\udd49\ufe0f',
'oman': '\ud83c\uddf4\ud83c\uddf2',
'on': '\ud83d\udd1b',
'oncoming_automobile': '\ud83d\ude98',
'oncoming_bus': '\ud83d\ude8d',
'oncoming_police_car': '\ud83d\ude94',
'oncoming_taxi': '\ud83d\ude96',
'one': '1\ufe0f\u20e3',
'one_piece_swimsuit': '\ud83e\ude71',
'onion': '\ud83e\uddc5',
'open_book': '\ud83d\udcd6',
'open_file_folder': '\ud83d\udcc2',
'open_hands': '\ud83d\udc50',
'open_mouth': '\ud83d\ude2e',
'open_umbrella': '\u2602\ufe0f',
'ophiuchus': '\u26ce',
'orange': '\ud83c\udf4a',
'orange_book': '\ud83d\udcd9',
'orange_circle': '\ud83d\udfe0',
'orange_heart': '\ud83e\udde1',
'orange_square': '\ud83d\udfe7',
'orangutan': '\ud83e\udda7',
'orthodox_cross': '\u2626\ufe0f',
'otter': '\ud83e\udda6',
'outbox_tray': '\ud83d\udce4',
'owl': '\ud83e\udd89',
'ox': '\ud83d\udc02',
'oyster': '\ud83e\uddaa',
'package': '\ud83d\udce6',
'page_facing_up': '\ud83d\udcc4',
'page_with_curl': '\ud83d\udcc3',
'pager': '\ud83d\udcdf',
'paintbrush': '\ud83d\udd8c\ufe0f',
'pakistan': '\ud83c\uddf5\ud83c\uddf0',
'palau': '\ud83c\uddf5\ud83c\uddfc',
'palestinian_territories': '\ud83c\uddf5\ud83c\uddf8',
'palm_tree': '\ud83c\udf34',
'palms_up_together': '\ud83e\udd32',
'panama': '\ud83c\uddf5\ud83c\udde6',
'pancakes': '\ud83e\udd5e',
'panda_face': '\ud83d\udc3c',
'paperclip': '\ud83d\udcce',
'paperclips': '\ud83d\udd87\ufe0f',
'papua_new_guinea': '\ud83c\uddf5\ud83c\uddec',
'parachute': '\ud83e\ude82',
'paraguay': '\ud83c\uddf5\ud83c\uddfe',
'parasol_on_ground': '\u26f1\ufe0f',
'parking': '\ud83c\udd7f\ufe0f',
'parrot': '\ud83e\udd9c',
'part_alternation_mark': '\u303d\ufe0f',
'partly_sunny': '\u26c5',
'partying_face': '\ud83e\udd73',
'passenger_ship': '\ud83d\udef3\ufe0f',
'passport_control': '\ud83d\udec2',
'pause_button': '\u23f8\ufe0f',
'paw_prints': '\ud83d\udc3e',
'peace_symbol': '\u262e\ufe0f',
'peach': '\ud83c\udf51',
'peacock': '\ud83e\udd9a',
'peanuts': '\ud83e\udd5c',
'pear': '\ud83c\udf50',
'pen': '\ud83d\udd8a\ufe0f',
'pencil': '\ud83d\udcdd',
'pencil2': '\u270f\ufe0f',
'penguin': '\ud83d\udc27',
'pensive': '\ud83d\ude14',
'people_holding_hands': '\ud83e\uddd1\u200d\ud83e\udd1d\u200d\ud83e\uddd1',
'performing_arts': '\ud83c\udfad',
'persevere': '\ud83d\ude23',
'person_bald': '\ud83e\uddd1\u200d\ud83e\uddb2',
'person_curly_hair': '\ud83e\uddd1\u200d\ud83e\uddb1',
'person_fencing': '\ud83e\udd3a',
'person_in_manual_wheelchair': '\ud83e\uddd1\u200d\ud83e\uddbd',
'person_in_motorized_wheelchair': '\ud83e\uddd1\u200d\ud83e\uddbc',
'person_red_hair': '\ud83e\uddd1\u200d\ud83e\uddb0',
'person_white_hair': '\ud83e\uddd1\u200d\ud83e\uddb3',
'person_with_probing_cane': '\ud83e\uddd1\u200d\ud83e\uddaf',
'person_with_turban': '\ud83d\udc73',
'peru': '\ud83c\uddf5\ud83c\uddea',
'petri_dish': '\ud83e\uddeb',
'philippines': '\ud83c\uddf5\ud83c\udded',
'phone': '\u260e\ufe0f',
'pick': '\u26cf\ufe0f',
'pie': '\ud83e\udd67',
'pig': '\ud83d\udc37',
'pig2': '\ud83d\udc16',
'pig_nose': '\ud83d\udc3d',
'pill': '\ud83d\udc8a',
'pilot': '\ud83e\uddd1\u200d\u2708\ufe0f',
'pinching_hand': '\ud83e\udd0f',
'pineapple': '\ud83c\udf4d',
'ping_pong': '\ud83c\udfd3',
'pirate_flag': '\ud83c\udff4\u200d\u2620\ufe0f',
'pisces': '\u2653',
'pitcairn_islands': '\ud83c\uddf5\ud83c\uddf3',
'pizza': '\ud83c\udf55',
'place_of_worship': '\ud83d\uded0',
'plate_with_cutlery': '\ud83c\udf7d\ufe0f',
'play_or_pause_button': '\u23ef\ufe0f',
'pleading_face': '\ud83e\udd7a',
'point_down': '\ud83d\udc47',
'point_left': '\ud83d\udc48',
'point_right': '\ud83d\udc49',
'point_up': '\u261d\ufe0f',
'point_up_2': '\ud83d\udc46',
'poland': '\ud83c\uddf5\ud83c\uddf1',
'police_car': '\ud83d\ude93',
'police_officer': '\ud83d\udc6e',
'policeman': '\ud83d\udc6e\u200d\u2642\ufe0f',
'policewoman': '\ud83d\udc6e\u200d\u2640\ufe0f',
'poodle': '\ud83d\udc29',
'poop': '\ud83d\udca9',
'popcorn': '\ud83c\udf7f',
'portugal': '\ud83c\uddf5\ud83c\uddf9',
'post_office': '\ud83c\udfe3',
'postal_horn': '\ud83d\udcef',
'postbox': '\ud83d\udcee',
'potable_water': '\ud83d\udeb0',
'potato': '\ud83e\udd54',
'pouch': '\ud83d\udc5d',
'poultry_leg': '\ud83c\udf57',
'pound': '\ud83d\udcb7',
'pout': '\ud83d\ude21',
'pouting_cat': '\ud83d\ude3e',
'pouting_face': '\ud83d\ude4e',
'pouting_man': '\ud83d\ude4e\u200d\u2642\ufe0f',
'pouting_woman': '\ud83d\ude4e\u200d\u2640\ufe0f',
'pray': '\ud83d\ude4f',
'prayer_beads': '\ud83d\udcff',
'pregnant_woman': '\ud83e\udd30',
'pretzel': '\ud83e\udd68',
'previous_track_button': '\u23ee\ufe0f',
'prince': '\ud83e\udd34',
'princess': '\ud83d\udc78',
'printer': '\ud83d\udda8\ufe0f',
'probing_cane': '\ud83e\uddaf',
'puerto_rico': '\ud83c\uddf5\ud83c\uddf7',
'punch': '\ud83d\udc4a',
'purple_circle': '\ud83d\udfe3',
'purple_heart': '\ud83d\udc9c',
'purple_square': '\ud83d\udfea',
'purse': '\ud83d\udc5b',
'pushpin': '\ud83d\udccc',
'put_litter_in_its_place': '\ud83d\udeae',
'qatar': '\ud83c\uddf6\ud83c\udde6',
'question': '\u2753',
'rabbit': '\ud83d\udc30',
'rabbit2': '\ud83d\udc07',
'raccoon': '\ud83e\udd9d',
'racehorse': '\ud83d\udc0e',
'racing_car': '\ud83c\udfce\ufe0f',
'radio': '\ud83d\udcfb',
'radio_button': '\ud83d\udd18',
'radioactive': '\u2622\ufe0f',
'rage': '\ud83d\ude21',
'railway_car': '\ud83d\ude83',
'railway_track': '\ud83d\udee4\ufe0f',
'rainbow': '\ud83c\udf08',
'rainbow_flag': '\ud83c\udff3\ufe0f\u200d\ud83c\udf08',
'raised_back_of_hand': '\ud83e\udd1a',
'raised_eyebrow': '\ud83e\udd28',
'raised_hand': '\u270b',
'raised_hand_with_fingers_splayed': '\ud83d\udd90\ufe0f',
'raised_hands': '\ud83d\ude4c',
'raising_hand': '\ud83d\ude4b',
'raising_hand_man': '\ud83d\ude4b\u200d\u2642\ufe0f',
'raising_hand_woman': '\ud83d\ude4b\u200d\u2640\ufe0f',
'ram': '\ud83d\udc0f',
'ramen': '\ud83c\udf5c',
'rat': '\ud83d\udc00',
'razor': '\ud83e\ude92',
'receipt': '\ud83e\uddfe',
'record_button': '\u23fa\ufe0f',
'recycle': '\u267b\ufe0f',
'red_car': '\ud83d\ude97',
'red_circle': '\ud83d\udd34',
'red_envelope': '\ud83e\udde7',
'red_haired_man': '\ud83d\udc68\u200d\ud83e\uddb0',
'red_haired_woman': '\ud83d\udc69\u200d\ud83e\uddb0',
'red_square': '\ud83d\udfe5',
'registered': '\u00ae\ufe0f',
'relaxed': '\u263a\ufe0f',
'relieved': '\ud83d\ude0c',
'reminder_ribbon': '\ud83c\udf97\ufe0f',
'repeat': '\ud83d\udd01',
'repeat_one': '\ud83d\udd02',
'rescue_worker_helmet': '\u26d1\ufe0f',
'restroom': '\ud83d\udebb',
'reunion': '\ud83c\uddf7\ud83c\uddea',
'revolving_hearts': '\ud83d\udc9e',
'rewind': '\u23ea',
'rhinoceros': '\ud83e\udd8f',
'ribbon': '\ud83c\udf80',
'rice': '\ud83c\udf5a',
'rice_ball': '\ud83c\udf59',
'rice_cracker': '\ud83c\udf58',
'rice_scene': '\ud83c\udf91',
'right_anger_bubble': '\ud83d\uddef\ufe0f',
'ring': '\ud83d\udc8d',
'ringed_planet': '\ud83e\ude90',
'robot': '\ud83e\udd16',
'rocket': '\ud83d\ude80',
'rofl': '\ud83e\udd23',
'roll_eyes': '\ud83d\ude44',
'roll_of_paper': '\ud83e\uddfb',
'roller_coaster': '\ud83c\udfa2',
'romania': '\ud83c\uddf7\ud83c\uddf4',
'rooster': '\ud83d\udc13',
'rose': '\ud83c\udf39',
'rosette': '\ud83c\udff5\ufe0f',
'rotating_light': '\ud83d\udea8',
'round_pushpin': '\ud83d\udccd',
'rowboat': '\ud83d\udea3',
'rowing_man': '\ud83d\udea3\u200d\u2642\ufe0f',
'rowing_woman': '\ud83d\udea3\u200d\u2640\ufe0f',
'ru': '\ud83c\uddf7\ud83c\uddfa',
'rugby_football': '\ud83c\udfc9',
'runner': '\ud83c\udfc3',
'running': '\ud83c\udfc3',
'running_man': '\ud83c\udfc3\u200d\u2642\ufe0f',
'running_shirt_with_sash': '\ud83c\udfbd',
'running_woman': '\ud83c\udfc3\u200d\u2640\ufe0f',
'rwanda': '\ud83c\uddf7\ud83c\uddfc',
'sa': '\ud83c\ude02\ufe0f',
'safety_pin': '\ud83e\uddf7',
'safety_vest': '\ud83e\uddba',
'sagittarius': '\u2650',
'sailboat': '\u26f5',
'sake': '\ud83c\udf76',
'salt': '\ud83e\uddc2',
'samoa': '\ud83c\uddfc\ud83c\uddf8',
'san_marino': '\ud83c\uddf8\ud83c\uddf2',
'sandal': '\ud83d\udc61',
'sandwich': '\ud83e\udd6a',
'santa': '\ud83c\udf85',
'sao_tome_principe': '\ud83c\uddf8\ud83c\uddf9',
'sari': '\ud83e\udd7b',
'sassy_man': '\ud83d\udc81\u200d\u2642\ufe0f',
'sassy_woman': '\ud83d\udc81\u200d\u2640\ufe0f',
'satellite': '\ud83d\udce1',
'satisfied': '\ud83d\ude06',
'saudi_arabia': '\ud83c\uddf8\ud83c\udde6',
'sauna_man': '\ud83e\uddd6\u200d\u2642\ufe0f',
'sauna_person': '\ud83e\uddd6',
'sauna_woman': '\ud83e\uddd6\u200d\u2640\ufe0f',
'sauropod': '\ud83e\udd95',
'saxophone': '\ud83c\udfb7',
'scarf': '\ud83e\udde3',
'school': '\ud83c\udfeb',
'school_satchel': '\ud83c\udf92',
'scientist': '\ud83e\uddd1\u200d\ud83d\udd2c',
'scissors': '\u2702\ufe0f',
'scorpion': '\ud83e\udd82',
'scorpius': '\u264f',
'scotland': '\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc73\udb40\udc63\udb40\udc74\udb40\udc7f',
'scream': '\ud83d\ude31',
'scream_cat': '\ud83d\ude40',
'scroll': '\ud83d\udcdc',
'seat': '\ud83d\udcba',
'secret': '\u3299\ufe0f',
'see_no_evil': '\ud83d\ude48',
'seedling': '\ud83c\udf31',
'selfie': '\ud83e\udd33',
'senegal': '\ud83c\uddf8\ud83c\uddf3',
'serbia': '\ud83c\uddf7\ud83c\uddf8',
'service_dog': '\ud83d\udc15\u200d\ud83e\uddba',
'seven': '7\ufe0f\u20e3',
'seychelles': '\ud83c\uddf8\ud83c\udde8',
'shallow_pan_of_food': '\ud83e\udd58',
'shamrock': '\u2618\ufe0f',
'shark': '\ud83e\udd88',
'shaved_ice': '\ud83c\udf67',
'sheep': '\ud83d\udc11',
'shell': '\ud83d\udc1a',
'shield': '\ud83d\udee1\ufe0f',
'shinto_shrine': '\u26e9\ufe0f',
'ship': '\ud83d\udea2',
'shirt': '\ud83d\udc55',
'shit': '\ud83d\udca9',
'shoe': '\ud83d\udc5e',
'shopping': '\ud83d\udecd\ufe0f',
'shopping_cart': '\ud83d\uded2',
'shorts': '\ud83e\ude73',
'shower': '\ud83d\udebf',
'shrimp': '\ud83e\udd90',
'shrug': '\ud83e\udd37',
'shushing_face': '\ud83e\udd2b',
'sierra_leone': '\ud83c\uddf8\ud83c\uddf1',
'signal_strength': '\ud83d\udcf6',
'singapore': '\ud83c\uddf8\ud83c\uddec',
'singer': '\ud83e\uddd1\u200d\ud83c\udfa4',
'sint_maarten': '\ud83c\uddf8\ud83c\uddfd',
'six': '6\ufe0f\u20e3',
'six_pointed_star': '\ud83d\udd2f',
'skateboard': '\ud83d\udef9',
'ski': '\ud83c\udfbf',
'skier': '\u26f7\ufe0f',
'skull': '\ud83d\udc80',
'skull_and_crossbones': '\u2620\ufe0f',
'skunk': '\ud83e\udda8',
'sled': '\ud83d\udef7',
'sleeping': '\ud83d\ude34',
'sleeping_bed': '\ud83d\udecc',
'sleepy': '\ud83d\ude2a',
'slightly_frowning_face': '\ud83d\ude41',
'slightly_smiling_face': '\ud83d\ude42',
'slot_machine': '\ud83c\udfb0',
'sloth': '\ud83e\udda5',
'slovakia': '\ud83c\uddf8\ud83c\uddf0',
'slovenia': '\ud83c\uddf8\ud83c\uddee',
'small_airplane': '\ud83d\udee9\ufe0f',
'small_blue_diamond': '\ud83d\udd39',
'small_orange_diamond': '\ud83d\udd38',
'small_red_triangle': '\ud83d\udd3a',
'small_red_triangle_down': '\ud83d\udd3b',
'smile': '\ud83d\ude04',
'smile_cat': '\ud83d\ude38',
'smiley': '\ud83d\ude03',
'smiley_cat': '\ud83d\ude3a',
'smiling_face_with_three_hearts': '\ud83e\udd70',
'smiling_imp': '\ud83d\ude08',
'smirk': '\ud83d\ude0f',
'smirk_cat': '\ud83d\ude3c',
'smoking': '\ud83d\udeac',
'snail': '\ud83d\udc0c',
'snake': '\ud83d\udc0d',
'sneezing_face': '\ud83e\udd27',
'snowboarder': '\ud83c\udfc2',
'snowflake': '\u2744\ufe0f',
'snowman': '\u26c4',
'snowman_with_snow': '\u2603\ufe0f',
'soap': '\ud83e\uddfc',
'sob': '\ud83d\ude2d',
'soccer': '\u26bd',
'socks': '\ud83e\udde6',
'softball': '\ud83e\udd4e',
'solomon_islands': '\ud83c\uddf8\ud83c\udde7',
'somalia': '\ud83c\uddf8\ud83c\uddf4',
'soon': '\ud83d\udd1c',
'sos': '\ud83c\udd98',
'sound': '\ud83d\udd09',
'south_africa': '\ud83c\uddff\ud83c\udde6',
'south_georgia_south_sandwich_islands': '\ud83c\uddec\ud83c\uddf8',
'south_sudan': '\ud83c\uddf8\ud83c\uddf8',
'space_invader': '\ud83d\udc7e',
'spades': '\u2660\ufe0f',
'spaghetti': '\ud83c\udf5d',
'sparkle': '\u2747\ufe0f',
'sparkler': '\ud83c\udf87',
'sparkles': '\u2728',
'sparkling_heart': '\ud83d\udc96',
'speak_no_evil': '\ud83d\ude4a',
'speaker': '\ud83d\udd08',
'speaking_head': '\ud83d\udde3\ufe0f',
'speech_balloon': '\ud83d\udcac',
'speedboat': '\ud83d\udea4',
'spider': '\ud83d\udd77\ufe0f',
'spider_web': '\ud83d\udd78\ufe0f',
'spiral_calendar': '\ud83d\uddd3\ufe0f',
'spiral_notepad': '\ud83d\uddd2\ufe0f',
'sponge': '\ud83e\uddfd',
'spoon': '\ud83e\udd44',
'squid': '\ud83e\udd91',
'sri_lanka': '\ud83c\uddf1\ud83c\uddf0',
'st_barthelemy': '\ud83c\udde7\ud83c\uddf1',
'st_helena': '\ud83c\uddf8\ud83c\udded',
'st_kitts_nevis': '\ud83c\uddf0\ud83c\uddf3',
'st_lucia': '\ud83c\uddf1\ud83c\udde8',
'st_martin': '\ud83c\uddf2\ud83c\uddeb',
'st_pierre_miquelon': '\ud83c\uddf5\ud83c\uddf2',
'st_vincent_grenadines': '\ud83c\uddfb\ud83c\udde8',
'stadium': '\ud83c\udfdf\ufe0f',
'standing_man': '\ud83e\uddcd\u200d\u2642\ufe0f',
'standing_person': '\ud83e\uddcd',
'standing_woman': '\ud83e\uddcd\u200d\u2640\ufe0f',
'star': '\u2b50',
'star2': '\ud83c\udf1f',
'star_and_crescent': '\u262a\ufe0f',
'star_of_david': '\u2721\ufe0f',
'star_struck': '\ud83e\udd29',
'stars': '\ud83c\udf20',
'station': '\ud83d\ude89',
'statue_of_liberty': '\ud83d\uddfd',
'steam_locomotive': '\ud83d\ude82',
'stethoscope': '\ud83e\ude7a',
'stew': '\ud83c\udf72',
'stop_button': '\u23f9\ufe0f',
'stop_sign': '\ud83d\uded1',
'stopwatch': '\u23f1\ufe0f',
'straight_ruler': '\ud83d\udccf',
'strawberry': '\ud83c\udf53',
'stuck_out_tongue': '\ud83d\ude1b',
'stuck_out_tongue_closed_eyes': '\ud83d\ude1d',
'stuck_out_tongue_winking_eye': '\ud83d\ude1c',
'student': '\ud83e\uddd1\u200d\ud83c\udf93',
'studio_microphone': '\ud83c\udf99\ufe0f',
'stuffed_flatbread': '\ud83e\udd59',
'sudan': '\ud83c\uddf8\ud83c\udde9',
'sun_behind_large_cloud': '\ud83c\udf25\ufe0f',
'sun_behind_rain_cloud': '\ud83c\udf26\ufe0f',
'sun_behind_small_cloud': '\ud83c\udf24\ufe0f',
'sun_with_face': '\ud83c\udf1e',
'sunflower': '\ud83c\udf3b',
'sunglasses': '\ud83d\ude0e',
'sunny': '\u2600\ufe0f',
'sunrise': '\ud83c\udf05',
'sunrise_over_mountains': '\ud83c\udf04',
'superhero': '\ud83e\uddb8',
'superhero_man': '\ud83e\uddb8\u200d\u2642\ufe0f',
'superhero_woman': '\ud83e\uddb8\u200d\u2640\ufe0f',
'supervillain': '\ud83e\uddb9',
'supervillain_man': '\ud83e\uddb9\u200d\u2642\ufe0f',
'supervillain_woman': '\ud83e\uddb9\u200d\u2640\ufe0f',
'surfer': '\ud83c\udfc4',
'surfing_man': '\ud83c\udfc4\u200d\u2642\ufe0f',
'surfing_woman': '\ud83c\udfc4\u200d\u2640\ufe0f',
'suriname': '\ud83c\uddf8\ud83c\uddf7',
'sushi': '\ud83c\udf63',
'suspension_railway': '\ud83d\ude9f',
'svalbard_jan_mayen': '\ud83c\uddf8\ud83c\uddef',
'swan': '\ud83e\udda2',
'swaziland': '\ud83c\uddf8\ud83c\uddff',
'sweat': '\ud83d\ude13',
'sweat_drops': '\ud83d\udca6',
'sweat_smile': '\ud83d\ude05',
'sweden': '\ud83c\uddf8\ud83c\uddea',
'sweet_potato': '\ud83c\udf60',
'swim_brief': '\ud83e\ude72',
'swimmer': '\ud83c\udfca',
'swimming_man': '\ud83c\udfca\u200d\u2642\ufe0f',
'swimming_woman': '\ud83c\udfca\u200d\u2640\ufe0f',
'switzerland': '\ud83c\udde8\ud83c\udded',
'symbols': '\ud83d\udd23',
'synagogue': '\ud83d\udd4d',
'syria': '\ud83c\uddf8\ud83c\uddfe',
'syringe': '\ud83d\udc89',
't-rex': '\ud83e\udd96',
'taco': '\ud83c\udf2e',
'tada': '\ud83c\udf89',
'taiwan': '\ud83c\uddf9\ud83c\uddfc',
'tajikistan': '\ud83c\uddf9\ud83c\uddef',
'takeout_box': '\ud83e\udd61',
'tanabata_tree': '\ud83c\udf8b',
'tangerine': '\ud83c\udf4a',
'tanzania': '\ud83c\uddf9\ud83c\uddff',
'taurus': '\u2649',
'taxi': '\ud83d\ude95',
'tea': '\ud83c\udf75',
'teacher': '\ud83e\uddd1\u200d\ud83c\udfeb',
'technologist': '\ud83e\uddd1\u200d\ud83d\udcbb',
'teddy_bear': '\ud83e\uddf8',
'telephone': '\u260e\ufe0f',
'telephone_receiver': '\ud83d\udcde',
'telescope': '\ud83d\udd2d',
'tennis': '\ud83c\udfbe',
'tent': '\u26fa',
'test_tube': '\ud83e\uddea',
'thailand': '\ud83c\uddf9\ud83c\udded',
'thermometer': '\ud83c\udf21\ufe0f',
'thinking': '\ud83e\udd14',
'thought_balloon': '\ud83d\udcad',
'thread': '\ud83e\uddf5',
'three': '3\ufe0f\u20e3',
'thumbsdown': '\ud83d\udc4e',
'thumbsup': '\ud83d\udc4d',
'ticket': '\ud83c\udfab',
'tickets': '\ud83c\udf9f\ufe0f',
'tiger': '\ud83d\udc2f',
'tiger2': '\ud83d\udc05',
'timer_clock': '\u23f2\ufe0f',
'timor_leste': '\ud83c\uddf9\ud83c\uddf1',
'tipping_hand_man': '\ud83d\udc81\u200d\u2642\ufe0f',
'tipping_hand_person': '\ud83d\udc81',
'tipping_hand_woman': '\ud83d\udc81\u200d\u2640\ufe0f',
'tired_face': '\ud83d\ude2b',
'tm': '\u2122\ufe0f',
'togo': '\ud83c\uddf9\ud83c\uddec',
'toilet': '\ud83d\udebd',
'tokelau': '\ud83c\uddf9\ud83c\uddf0',
'tokyo_tower': '\ud83d\uddfc',
'tomato': '\ud83c\udf45',
'tonga': '\ud83c\uddf9\ud83c\uddf4',
'tongue': '\ud83d\udc45',
'toolbox': '\ud83e\uddf0',
'tooth': '\ud83e\uddb7',
'top': '\ud83d\udd1d',
'tophat': '\ud83c\udfa9',
'tornado': '\ud83c\udf2a\ufe0f',
'tr': '\ud83c\uddf9\ud83c\uddf7',
'trackball': '\ud83d\uddb2\ufe0f',
'tractor': '\ud83d\ude9c',
'traffic_light': '\ud83d\udea5',
'train': '\ud83d\ude8b',
'train2': '\ud83d\ude86',
'tram': '\ud83d\ude8a',
'triangular_flag_on_post': '\ud83d\udea9',
'triangular_ruler': '\ud83d\udcd0',
'trident': '\ud83d\udd31',
'trinidad_tobago': '\ud83c\uddf9\ud83c\uddf9',
'tristan_da_cunha': '\ud83c\uddf9\ud83c\udde6',
'triumph': '\ud83d\ude24',
'trolleybus': '\ud83d\ude8e',
'trophy': '\ud83c\udfc6',
'tropical_drink': '\ud83c\udf79',
'tropical_fish': '\ud83d\udc20',
'truck': '\ud83d\ude9a',
'trumpet': '\ud83c\udfba',
'tshirt': '\ud83d\udc55',
'tulip': '\ud83c\udf37',
'tumbler_glass': '\ud83e\udd43',
'tunisia': '\ud83c\uddf9\ud83c\uddf3',
'turkey': '\ud83e\udd83',
'turkmenistan': '\ud83c\uddf9\ud83c\uddf2',
'turks_caicos_islands': '\ud83c\uddf9\ud83c\udde8',
'turtle': '\ud83d\udc22',
'tuvalu': '\ud83c\uddf9\ud83c\uddfb',
'tv': '\ud83d\udcfa',
'twisted_rightwards_arrows': '\ud83d\udd00',
'two': '2\ufe0f\u20e3',
'two_hearts': '\ud83d\udc95',
'two_men_holding_hands': '\ud83d\udc6c',
'two_women_holding_hands': '\ud83d\udc6d',
'u5272': '\ud83c\ude39',
'u5408': '\ud83c\ude34',
'u55b6': '\ud83c\ude3a',
'u6307': '\ud83c\ude2f',
'u6708': '\ud83c\ude37\ufe0f',
'u6709': '\ud83c\ude36',
'u6e80': '\ud83c\ude35',
'u7121': '\ud83c\ude1a',
'u7533': '\ud83c\ude38',
'u7981': '\ud83c\ude32',
'u7a7a': '\ud83c\ude33',
'uganda': '\ud83c\uddfa\ud83c\uddec',
'uk': '\ud83c\uddec\ud83c\udde7',
'ukraine': '\ud83c\uddfa\ud83c\udde6',
'umbrella': '\u2614',
'unamused': '\ud83d\ude12',
'underage': '\ud83d\udd1e',
'unicorn': '\ud83e\udd84',
'united_arab_emirates': '\ud83c\udde6\ud83c\uddea',
'united_nations': '\ud83c\uddfa\ud83c\uddf3',
'unlock': '\ud83d\udd13',
'up': '\ud83c\udd99',
'upside_down_face': '\ud83d\ude43',
'uruguay': '\ud83c\uddfa\ud83c\uddfe',
'us': '\ud83c\uddfa\ud83c\uddf8',
'us_outlying_islands': '\ud83c\uddfa\ud83c\uddf2',
'us_virgin_islands': '\ud83c\uddfb\ud83c\uddee',
'uzbekistan': '\ud83c\uddfa\ud83c\uddff',
'v': '\u270c\ufe0f',
'vampire': '\ud83e\udddb',
'vampire_man': '\ud83e\udddb\u200d\u2642\ufe0f',
'vampire_woman': '\ud83e\udddb\u200d\u2640\ufe0f',
'vanuatu': '\ud83c\uddfb\ud83c\uddfa',
'vatican_city': '\ud83c\uddfb\ud83c\udde6',
'venezuela': '\ud83c\uddfb\ud83c\uddea',
'vertical_traffic_light': '\ud83d\udea6',
'vhs': '\ud83d\udcfc',
'vibration_mode': '\ud83d\udcf3',
'video_camera': '\ud83d\udcf9',
'video_game': '\ud83c\udfae',
'vietnam': '\ud83c\uddfb\ud83c\uddf3',
'violin': '\ud83c\udfbb',
'virgo': '\u264d',
'volcano': '\ud83c\udf0b',
'volleyball': '\ud83c\udfd0',
'vomiting_face': '\ud83e\udd2e',
'vs': '\ud83c\udd9a',
'vulcan_salute': '\ud83d\udd96',
'waffle': '\ud83e\uddc7',
'wales': '\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f',
'walking': '\ud83d\udeb6',
'walking_man': '\ud83d\udeb6\u200d\u2642\ufe0f',
'walking_woman': '\ud83d\udeb6\u200d\u2640\ufe0f',
'wallis_futuna': '\ud83c\uddfc\ud83c\uddeb',
'waning_crescent_moon': '\ud83c\udf18',
'waning_gibbous_moon': '\ud83c\udf16',
'warning': '\u26a0\ufe0f',
'wastebasket': '\ud83d\uddd1\ufe0f',
'watch': '\u231a',
'water_buffalo': '\ud83d\udc03',
'water_polo': '\ud83e\udd3d',
'watermelon': '\ud83c\udf49',
'wave': '\ud83d\udc4b',
'wavy_dash': '\u3030\ufe0f',
'waxing_crescent_moon': '\ud83c\udf12',
'waxing_gibbous_moon': '\ud83c\udf14',
'wc': '\ud83d\udebe',
'weary': '\ud83d\ude29',
'wedding': '\ud83d\udc92',
'weight_lifting': '\ud83c\udfcb\ufe0f',
'weight_lifting_man': '\ud83c\udfcb\ufe0f\u200d\u2642\ufe0f',
'weight_lifting_woman': '\ud83c\udfcb\ufe0f\u200d\u2640\ufe0f',
'western_sahara': '\ud83c\uddea\ud83c\udded',
'whale': '\ud83d\udc33',
'whale2': '\ud83d\udc0b',
'wheel_of_dharma': '\u2638\ufe0f',
'wheelchair': '\u267f',
'white_check_mark': '\u2705',
'white_circle': '\u26aa',
'white_flag': '\ud83c\udff3\ufe0f',
'white_flower': '\ud83d\udcae',
'white_haired_man': '\ud83d\udc68\u200d\ud83e\uddb3',
'white_haired_woman': '\ud83d\udc69\u200d\ud83e\uddb3',
'white_heart': '\ud83e\udd0d',
'white_large_square': '\u2b1c',
'white_medium_small_square': '\u25fd',
'white_medium_square': '\u25fb\ufe0f',
'white_small_square': '\u25ab\ufe0f',
'white_square_button': '\ud83d\udd33',
'wilted_flower': '\ud83e\udd40',
'wind_chime': '\ud83c\udf90',
'wind_face': '\ud83c\udf2c\ufe0f',
'wine_glass': '\ud83c\udf77',
'wink': '\ud83d\ude09',
'wolf': '\ud83d\udc3a',
'woman': '\ud83d\udc69',
'woman_artist': '\ud83d\udc69\u200d\ud83c\udfa8',
'woman_astronaut': '\ud83d\udc69\u200d\ud83d\ude80',
'woman_cartwheeling': '\ud83e\udd38\u200d\u2640\ufe0f',
'woman_cook': '\ud83d\udc69\u200d\ud83c\udf73',
'woman_dancing': '\ud83d\udc83',
'woman_facepalming': '\ud83e\udd26\u200d\u2640\ufe0f',
'woman_factory_worker': '\ud83d\udc69\u200d\ud83c\udfed',
'woman_farmer': '\ud83d\udc69\u200d\ud83c\udf3e',
'woman_firefighter': '\ud83d\udc69\u200d\ud83d\ude92',
'woman_health_worker': '\ud83d\udc69\u200d\u2695\ufe0f',
'woman_in_manual_wheelchair': '\ud83d\udc69\u200d\ud83e\uddbd',
'woman_in_motorized_wheelchair': '\ud83d\udc69\u200d\ud83e\uddbc',
'woman_judge': '\ud83d\udc69\u200d\u2696\ufe0f',
'woman_juggling': '\ud83e\udd39\u200d\u2640\ufe0f',
'woman_mechanic': '\ud83d\udc69\u200d\ud83d\udd27',
'woman_office_worker': '\ud83d\udc69\u200d\ud83d\udcbc',
'woman_pilot': '\ud83d\udc69\u200d\u2708\ufe0f',
'woman_playing_handball': '\ud83e\udd3e\u200d\u2640\ufe0f',
'woman_playing_water_polo': '\ud83e\udd3d\u200d\u2640\ufe0f',
'woman_scientist': '\ud83d\udc69\u200d\ud83d\udd2c',
'woman_shrugging': '\ud83e\udd37\u200d\u2640\ufe0f',
'woman_singer': '\ud83d\udc69\u200d\ud83c\udfa4',
'woman_student': '\ud83d\udc69\u200d\ud83c\udf93',
'woman_teacher': '\ud83d\udc69\u200d\ud83c\udfeb',
'woman_technologist': '\ud83d\udc69\u200d\ud83d\udcbb',
'woman_with_headscarf': '\ud83e\uddd5',
'woman_with_probing_cane': '\ud83d\udc69\u200d\ud83e\uddaf',
'woman_with_turban': '\ud83d\udc73\u200d\u2640\ufe0f',
'womans_clothes': '\ud83d\udc5a',
'womans_hat': '\ud83d\udc52',
'women_wrestling': '\ud83e\udd3c\u200d\u2640\ufe0f',
'womens': '\ud83d\udeba',
'woozy_face': '\ud83e\udd74',
'world_map': '\ud83d\uddfa\ufe0f',
'worried': '\ud83d\ude1f',
'wrench': '\ud83d\udd27',
'wrestling': '\ud83e\udd3c',
'writing_hand': '\u270d\ufe0f',
'x': '\u274c',
'yarn': '\ud83e\uddf6',
'yawning_face': '\ud83e\udd71',
'yellow_circle': '\ud83d\udfe1',
'yellow_heart': '\ud83d\udc9b',
'yellow_square': '\ud83d\udfe8',
'yemen': '\ud83c\uddfe\ud83c\uddea',
'yen': '\ud83d\udcb4',
'yin_yang': '\u262f\ufe0f',
'yo_yo': '\ud83e\ude80',
'yum': '\ud83d\ude0b',
'zambia': '\ud83c\uddff\ud83c\uddf2',
'zany_face': '\ud83e\udd2a',
'zap': '\u26a1',
'zebra': '\ud83e\udd93',
'zero': '0\ufe0f\u20e3',
'zimbabwe': '\ud83c\uddff\ud83c\uddfc',
'zipper_mouth_face': '\ud83e\udd10',
'zombie': '\ud83e\udddf',
'zombie_man': '\ud83e\udddf\u200d\u2642\ufe0f',
'zombie_woman': '\ud83e\udddf\u200d\u2640\ufe0f',
'zzz': '\ud83d\udca4',
/* special emojis :P */
'atom': '<img alt="atom" width="20" height="20" align="absmiddle" src="https://github.githubassets.com/images/icons/emoji/atom.png?v8">',
'basecamp': '<img alt="basecamp" width="20" height="20" align="absmiddle" src="https://github.githubassets.com/images/icons/emoji/basecamp.png?v8">',
'basecampy': '<img alt="basecampy" width="20" height="20" align="absmiddle" src="https://github.githubassets.com/images/icons/emoji/basecampy.png?v8">',
'bowtie': '<img alt="bowtie" width="20" height="20" align="absmiddle" src="https://github.githubassets.com/images/icons/emoji/bowtie.png?v8">',
'electron': '<img alt="electron" width="20" height="20" align="absmiddle" src="https://github.githubassets.com/images/icons/emoji/electron.png?v8">',
'feelsgood': '<img alt="feelsgood" width="20" height="20" align="absmiddle" src="https://github.githubassets.com/images/icons/emoji/feelsgood.png?v8">',
'finnadie': '<img alt="finnadie" width="20" height="20" align="absmiddle" src="https://github.githubassets.com/images/icons/emoji/finnadie.png?v8">',
'goberserk': '<img alt="goberserk" width="20" height="20" align="absmiddle" src="https://github.githubassets.com/images/icons/emoji/goberserk.png?v8">',
'godmode': '<img alt="godmode" width="20" height="20" align="absmiddle" src="https://github.githubassets.com/images/icons/emoji/godmode.png?v8">',
'hurtrealbad': '<img alt="hurtrealbad" width="20" height="20" align="absmiddle" src="https://github.githubassets.com/images/icons/emoji/hurtrealbad.png?v8">',
'neckbeard': '<img alt="neckbeard" width="20" height="20" align="absmiddle" src="https://github.githubassets.com/images/icons/emoji/neckbeard.png?v8">',
'octocat': '<img alt="octocat" width="20" height="20" align="absmiddle" src="https://github.githubassets.com/images/icons/emoji/octocat.png?v8">',
'rage1': '<img alt="rage1" width="20" height="20" align="absmiddle" src="https://github.githubassets.com/images/icons/emoji/rage1.png?v8">',
'rage2': '<img alt="rage2" width="20" height="20" align="absmiddle" src="https://github.githubassets.com/images/icons/emoji/rage2.png?v8">',
'rage3': '<img alt="rage3" width="20" height="20" align="absmiddle" src="https://github.githubassets.com/images/icons/emoji/rage3.png?v8">',
'rage4': '<img alt="rage4" width="20" height="20" align="absmiddle" src="https://github.githubassets.com/images/icons/emoji/rage4.png?v8">',
'shipit': '<img alt="shipit" width="20" height="20" align="absmiddle" src="https://github.githubassets.com/images/icons/emoji/shipit.png?v8">',
'suspect': '<img alt="suspect" width="20" height="20" align="absmiddle" src="https://github.githubassets.com/images/icons/emoji/suspect.png?v8">',
'trollface': '<img alt="trollface" width="20" height="20" align="absmiddle" src="https://github.githubassets.com/images/icons/emoji/trollface.png?v8">',
'showdown': '<img alt="showdown" width="20" height="20" align="absmiddle" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAS1BMVEX///8jJS0jJS0jJS0jJS0jJS0jJS0jJS0jJS0jJS0jJS0jJS0jJS0jJS0jJS0jJS0jJS3b1q3b1q3b1q3b1q3b1q3b1q3b1q3b1q0565CIAAAAGXRSTlMAQHCAYCCw/+DQwPCQUBAwoHCAEP+wwFBgS2fvBgAAAUZJREFUeAHs1cGy7BAUheFFsEDw/k97VTq3T6ge2EmdM+pvrP6Iwd74XV9Kb52xuMU4/uc1YNgZLFOeV8FGdhGrNk5SEgUyPxAEdj4LlMRDyhVAMVEa2M7TBSeVZAFPdqHgzSZJwPKgcLFLAooHDJo4EDCw4gAtBoJA5UFj4Ng5LOGLwVXZuoIlji/jeQHFk7+baHxrCjeUwB9+s88KndvlhcyBN5BSkYNQIVVb4pV+Npm7hhuKDs/uMP5KxT3WzSNNLIuuoDpMmuAVMruMSeDyQBi24DTr43LAY7ILA1QYaWkgfHzFthYYzg67SQsCbB8GhJUEGCtO9n0rSaCLxgJQjS/JSgMTg2eBDEHAJ+H350AsjYNYscrErgI2e/l+mdR967TCX/v6N0EhPECYCP0i+IAoYQOE8BogNhQMEMdrgAQWHaMAAGi5I5euoY9NAAAAAElFTkSuQmCC">'
};
/**
* Created by Estevao on 31-05-2015.
*/
showdown.Event = class {
/**
* Creates a new showdown Event object
* @param {string} name
* @param {string} input
* @param {{}} [params]
* @param {string} params.output
* @param {RegExp} params.regexp
* @param {{}} params.matches
* @param {{}} params.attributes
* @param {{}} params.globals
* @param {{}} params.options
* @param {showdown.Converter} params.converter
*/
constructor (name, input, params) {
params = params || {};
let {output, regexp, matches, attributes, globals, options, converter} = params;
if (!showdown.helper.isString(name)) {
if (!showdown.helper.isString(name)) {
throw new TypeError('Event.name must be a string but ' + typeof name + ' given');
}
}
this._name = name.toLowerCase();
this.input = input;
this.output = output || input;
this.regexp = regexp || null;
this.matches = matches || {};
this.attributes = attributes || {};
this._globals = globals || {};
this._options = showdown.helper.cloneObject(options, true) || {};
this._converter = converter || undefined;
}
/** @returns {string} */
get name () {
return this._name;
}
/** @returns {string} */
get input () {
return this._input;
}
/** @param {string} value */
set input (value) {
if (!showdown.helper.isString(value)) {
throw new TypeError('Event.input must be a string but ' + typeof value + ' given');
}
this._input = value;
}
/** @returns {string} */
get output () {
return this._output;
}
/** @param {string|null} value */
set output (value) {
if (!showdown.helper.isString(value) && value !== null) {
throw new TypeError('Event.output must be a string but ' + typeof value + ' given');
}
this._output = value;
}
/** @returns {null|RegExp} */
get regexp () {
return this._regexp;
}
/** @param {null|RegExp} value */
set regexp (value) {
if (!(value instanceof RegExp) && value !== null) {
throw new TypeError('Event.regexp must be a RegExp object (or null) but ' + typeof value + ' given');
}
this._regexp = value;
}
/** @returns {{}} */
get matches () {
return this._matches;
}
/** @param {{}}value */
set matches (value) {
if (typeof value !== 'object') {
throw new TypeError('Event.matches must be an object (or null) but ' + typeof value + ' given');
}
this._matches = {};
for (let prop in value) {
if (value.hasOwnProperty(prop)) {
let descriptor = {};
if (/^_(.+)/.test(prop)) {
descriptor = {
enumerable: true,
configurable: false,
writable: false,
value: value[prop]
};
} else {
descriptor = {
enumerable: true,
configurable: false,
writable: true,
value: value[prop]
};
}
Object.defineProperty(this._matches, prop, descriptor);
}
}
}
/** @returns {{}} */
get attributes () {
return this._attributes;
}
/** @param {{}} value */
set attributes (value) {
if (typeof value !== 'object') {
throw new TypeError('Event.attributes must be an object (or null) but ' + typeof value + ' given');
}
this._attributes = value;
}
/** @param {showdown.Converter} converter */
set converter (converter) {
this._converter = converter;
}
/** @returns {showdown.Converter} */
get converter () {
return this._converter;
}
get options () {
return this._options;
}
get globals () {
return this._globals;
}
// FLUID INTERFACE
/**
*
* @param {string} value
* @returns {showdown.Event}
*/
setInput (value) {
this.input = value;
return this;
}
/**
*
* @param {string|null} value
* @returns {showdown.Event}
*/
setOutput (value) {
this.output = value;
return this;
}
/**
*
* @param {RegExp} value
* @returns {showdown.Event}
*/
setRegexp (value) {
this.regexp = value;
return this;
}
/**
*
* @param {{}}value
* @returns {showdown.Event}
*/
setMatches (value) {
this.matches = value;
return this;
}
/**
*
* @param {{}}value
* @returns {showdown.Event}
*/
setAttributes (value) {
this.attributes = value;
return this;
}
_setOptions (value) {
this._options = value;
return this;
}
_setGlobals (value) {
this._globals = value;
return this;
}
_setConverter (value) {
this.converter = value;
return this;
}
/**
* Legacy: Return the output text
* @returns {string}
*/
getText () {
return this.output;
}
getMatches () {
return this.matches;
}
};
////
// makehtml/blockGamut.js
// Copyright (c) 2018 ShowdownJS
//
// These are all the transformations that form block-level
// tags like paragraphs, headers, and list items.
//
// ***Author:***
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.blockGamut', function (text, options, globals) {
'use strict';
let startEvent = new showdown.Event('makehtml.blockGamut.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
// we parse blockquotes first so that we can have headings and hrs
// inside blockquotes
text = showdown.subParser('makehtml.blockquote')(text, options, globals);
text = showdown.subParser('makehtml.heading')(text, options, globals);
// Do Horizontal Rules:
text = showdown.subParser('makehtml.horizontalRule')(text, options, globals);
text = showdown.subParser('makehtml.list')(text, options, globals);
text = showdown.subParser('makehtml.codeBlock')(text, options, globals);
text = showdown.subParser('makehtml.table')(text, options, globals);
// We already ran _HashHTMLBlocks() before, in Markdown(), but that
// was to escape raw HTML in the original Markdown source. This time,
// we're escaping the markup we've just created, so that we don't wrap
// <p> tags around block-level tags.
text = showdown.subParser('makehtml.hashHTMLBlocks')(text, options, globals);
text = showdown.subParser('makehtml.paragraphs')(text, options, globals);
let afterEvent = new showdown.Event('makehtml.blockGamut.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
////
// makehtml/blockquote.js
// Copyright (c) 2018 ShowdownJS
//
// Transforms MD blockquotes into `<blockquote>` html entities
//
// Markdown uses email-style > characters for blockquoting.
// Markdown allows you to be lazy and only put the > before the first line of a hard-wrapped paragraph but
// it looks best if the text is hard wrapped with a > before every line.
//
// ***Author:***
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.blockquote', function (text, options, globals) {
'use strict';
let startEvent = new showdown.Event('makehtml.blockquote.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
// add a couple extra lines after the text and endtext mark
text = text + '\n\n';
let pattern = /(^ {0,3}>[ \t]?.+\n(.+\n)*\n*)+/gm;
if (options.splitAdjacentBlockquotes) {
pattern = /^ {0,3}>[\s\S]*?\n\n/gm;
}
text = text.replace(pattern, function (bq) {
let otp,
attributes = {},
wholeMatch = bq;
// attacklab: hack around Konqueror 3.5.4 bug:
// "----------bug".replace(/^-/g,"") == "bug"
bq = bq.replace(/^[ \t]*>[ \t]?/gm, ''); // trim one level of quoting
// attacklab: clean up hack
bq = bq.replace(/¨0/g, '');
bq = bq.replace(/^[ \t]+$/gm, ''); // trim whitespace-only lines
let captureStartEvent = new showdown.Event('makehtml.blockquote.onCapture', bq);
captureStartEvent
.setOutput(null)
._setGlobals(globals)
._setOptions(options)
.setRegexp(pattern)
.setMatches({
_wholeMatch: wholeMatch,
blockquote: bq
})
.setAttributes({});
captureStartEvent = globals.converter.dispatch(captureStartEvent);
// if something was passed as output, it takes precedence
// and will be used as output
if (captureStartEvent.output && captureStartEvent.output !== '') {
otp = captureStartEvent.output;
} else {
bq = captureStartEvent.matches.blockquote;
bq = showdown.subParser('makehtml.githubCodeBlock')(bq, options, globals);
bq = showdown.subParser('makehtml.blockGamut')(bq, options, globals); // recurse
bq = bq.replace(/(^|\n)/g, '$1 ');
// These leading spaces screw with <pre> content, so we need to fix that:
bq = bq.replace(/(\s*<pre>[^\r]+?<\/pre>)/gm, function (wm, m1) {
return m1.replace(/^ {2}/mg, '');
});
attributes = captureStartEvent.attributes;
otp = '<blockquote' + showdown.helper._populateAttributes(attributes) + '>\n' + bq + '\n</blockquote>';
}
let beforeHashEvent = new showdown.Event('makehtml.blockquote.onHash', otp);
beforeHashEvent
.setOutput(otp)
._setGlobals(globals)
._setOptions(options);
beforeHashEvent = globals.converter.dispatch(beforeHashEvent);
otp = beforeHashEvent.output;
return showdown.subParser('makehtml.hashBlock')(otp, options, globals);
});
let afterEvent = new showdown.Event('makehtml.blockquote.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
////
// makehtml/codeBlock.js
// Copyright (c) 2022 ShowdownJS
//
// Process Markdown `<pre><code>` blocks.
//
// ***Author:***
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.codeBlock', function (text, options, globals) {
'use strict';
let startEvent = new showdown.Event('makehtml.codeBlock.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
// sentinel workarounds for lack of \A and \Z, safari\khtml bug
text += '¨0';
let pattern = /(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=¨0))/g;
text = text.replace(pattern, function (wholeMatch, m1, m2) {
let codeblock = m1,
nextChar = m2,
end = '\n',
otp,
attributes = {
pre: {},
code: {}
};
let captureStartEvent = new showdown.Event('makehtml.codeBlock.onCapture', codeblock);
captureStartEvent
.setOutput(null)
._setGlobals(globals)
._setOptions(options)
.setRegexp(pattern)
.setMatches({
_wholeMatch: wholeMatch,
codeblock: codeblock
})
.setAttributes(attributes);
captureStartEvent = globals.converter.dispatch(captureStartEvent);
// if something was passed as output, it takes precedence
// and will be used as output
if (captureStartEvent.output && captureStartEvent.output !== '') {
otp = captureStartEvent.output;
} else {
codeblock = captureStartEvent.matches.codeblock;
codeblock = showdown.helper.outdent(codeblock);
codeblock = showdown.subParser('makehtml.encodeCode')(codeblock, options, globals);
codeblock = showdown.subParser('makehtml.detab')(codeblock, options, globals);
codeblock = codeblock.replace(/^\n+/g, ''); // trim leading newlines
codeblock = codeblock.replace(/\n+$/g, ''); // trim trailing newlines
attributes = captureStartEvent.attributes;
otp = '<pre><code>';
if (!showdown.helper.isUndefined(attributes)) {
otp = '<pre' + showdown.helper._populateAttributes(attributes.pre) + '>';
otp += '<code' + showdown.helper._populateAttributes(attributes.code) + '>';
}
if (options.omitExtraWLInCodeBlocks) {
end = '';
}
otp += codeblock + end + '</code></pre>';
}
let beforeHashEvent = new showdown.Event('makehtml.codeBlock.onHash', otp);
beforeHashEvent
.setOutput(otp)
._setGlobals(globals)
._setOptions(options);
beforeHashEvent = globals.converter.dispatch(beforeHashEvent);
otp = beforeHashEvent.output;
return showdown.subParser('makehtml.hashBlock')(otp, options, globals) + nextChar;
});
// strip sentinel
text = text.replace(/¨0/, '');
let afterEvent = new showdown.Event('makehtml.codeBlock.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
////
// makehtml/codeSpan.js
// Copyright (c) 2018 ShowdownJS
//
// Transforms MD code spans into `<code>` html entities
//
// Backtick quotes are used for <code></code> spans.
//
// You can use multiple backticks as the delimiters if you want to
// include literal backticks in the code span. So, this input:
//
// Just type ``foo `bar` baz`` at the prompt.
//
// Will translate to:
//
// <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
//
// There's no arbitrary limit to the number of backticks you
// can use as delimters. If you need three consecutive backticks
// in your code, use four for delimiters, etc.
//
// You can use spaces to get literal backticks at the edges:
// ... type `` `bar` `` ...
//
// Turns to:
// ... type <code>`bar`</code> ...
//
// ***Author:***
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.codeSpan', function (text, options, globals) {
'use strict';
let startEvent = new showdown.Event('makehtml.codeSpan.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
if (showdown.helper.isUndefined((text))) {
text = '';
}
let pattern = /(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm;
text = text.replace(pattern, function (wholeMatch, m1, m2, c) {
let otp,
attributes = {};
c = c.replace(/^([ \t]*)/g, ''); // leading whitespace
c = c.replace(/[ \t]*$/g, ''); // trailing whitespace
let captureStartEvent = new showdown.Event('makehtml.codeSpan.onCapture', c);
captureStartEvent
.setOutput(null)
._setGlobals(globals)
._setOptions(options)
.setRegexp(pattern)
.setMatches({
_wholeMatch: wholeMatch,
code: c
})
.setAttributes({});
captureStartEvent = globals.converter.dispatch(captureStartEvent);
// if something was passed as output, it takes precedence
// and will be used as output
if (captureStartEvent.output && captureStartEvent.output !== '') {
otp = m1 + captureStartEvent.output;
} else {
c = captureStartEvent.matches.code;
c = showdown.subParser('makehtml.encodeCode')(c, options, globals);
otp = m1 + '<code' + showdown.helper._populateAttributes(attributes) + '>' + c + '</code>';
}
let beforeHashEvent = new showdown.Event('makehtml.codeSpan.onHash', otp);
beforeHashEvent
.setOutput(otp)
._setGlobals(globals)
._setOptions(options);
beforeHashEvent = globals.converter.dispatch(beforeHashEvent);
otp = beforeHashEvent.output;
return showdown.subParser('makehtml.hashHTMLSpans')(otp, options, globals);
}
);
let afterEvent = new showdown.Event('makehtml.codeSpan.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
////
// makehtml/completeHTMLDocument.js
// Copyright (c) 2018 ShowdownJS
//
// Create a full HTML document from the processed markdown
//
// ***Author:***
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.completeHTMLDocument', function (text, options, globals) {
'use strict';
if (!options.completeHTMLDocument) {
return text;
}
let startEvent = new showdown.Event('makehtml.completeHTMLDocument.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
let doctype = 'html',
doctypeParsed = '<!DOCTYPE HTML>\n',
title = '',
charset = '<meta charset="utf-8">\n',
lang = '',
metadata = '';
if (typeof globals.metadata.parsed.doctype !== 'undefined') {
doctypeParsed = '<!DOCTYPE ' + globals.metadata.parsed.doctype + '>\n';
doctype = globals.metadata.parsed.doctype.toString().toLowerCase();
if (doctype === 'html' || doctype === 'html5') {
charset = '<meta charset="utf-8">';
}
}
for (let meta in globals.metadata.parsed) {
if (globals.metadata.parsed.hasOwnProperty(meta)) {
switch (meta.toLowerCase()) {
case 'doctype':
break;
case 'title':
title = '<title>' + globals.metadata.parsed.title + '</title>\n';
break;
case 'charset':
if (doctype === 'html' || doctype === 'html5') {
charset = '<meta charset="' + globals.metadata.parsed.charset + '">\n';
} else {
charset = '<meta name="charset" content="' + globals.metadata.parsed.charset + '">\n';
}
break;
case 'language':
case 'lang':
lang = ' lang="' + globals.metadata.parsed[meta] + '"';
metadata += '<meta name="' + meta + '" content="' + globals.metadata.parsed[meta] + '">\n';
break;
default:
metadata += '<meta name="' + meta + '" content="' + globals.metadata.parsed[meta] + '">\n';
}
}
}
text = doctypeParsed + '<html' + lang + '>\n<head>\n' + title + charset + metadata + '</head>\n<body>\n' + text.trim() + '\n</body>\n</html>';
let afterEvent = new showdown.Event('makehtml.completeHTMLDocument.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
////
// makehtml/detab.js
// Copyright (c) 2018 ShowdownJS
//
// Convert all tabs to spaces
//
// ***Author:***
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.detab', function (text, options, globals) {
'use strict';
let startEvent = new showdown.Event('makehtml.detab.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
// expand first n-1 tabs
text = text.replace(/\t(?=\t)/g, ' '); // g_tab_width
// replace the nth with two sentinels
text = text.replace(/\t/g, '¨A¨B');
// use the sentinel to anchor our regex so it doesn't explode
text = text.replace(/¨B(.+?)¨A/g, function (wholeMatch, m1) {
var leadingText = m1,
numSpaces = 4 - leadingText.length % 4; // g_tab_width
// there *must* be a better way to do this:
for (var i = 0; i < numSpaces; i++) {
leadingText += ' ';
}
return leadingText;
});
// clean up sentinels
text = text.replace(/¨A/g, ' '); // g_tab_width
text = text.replace(/¨B/g, '');
let afterEvent = new showdown.Event('makehtml.detab.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
////
// makehtml/ellipsis.js
// Copyright (c) 2018 ShowdownJS
//
// transform three dots (...) into ellipsis (…)
//
// ***Author:***
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.ellipsis', function (text, options, globals) {
'use strict';
if (!options.ellipsis) {
return text;
}
let startEvent = new showdown.Event('makehtml.ellipsis.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
text = text.replace(/\.\.\./g, '…');
let afterEvent = new showdown.Event('makehtml.ellipsis.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
////
// makehtml/emoji.js
// Copyright (c) 2018 ShowdownJS
//
// Turn emoji codes into emojis
// List of supported emojis: https://github.com/showdownjs/showdown/wiki/Emojis
//
// ***Author:***
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.emoji', function (text, options, globals) {
'use strict';
if (!options.emoji) {
return text;
}
let startEvent = new showdown.Event('makehtml.emoji.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
let pattern = /:(\S+?):/g;
text = text.replace(pattern, function (wholeMatch, emojiCode) {
if (!showdown.helper.emojis.hasOwnProperty(emojiCode)) {
return wholeMatch;
}
let otp = '';
let captureStartEvent = new showdown.Event('makehtml.emoji.onCapture', emojiCode);
captureStartEvent
.setOutput(null)
._setGlobals(globals)
._setOptions(options)
.setRegexp(pattern)
.setMatches({
_wholeMatch: wholeMatch,
emojiCode: emojiCode
})
.setAttributes({});
captureStartEvent = globals.converter.dispatch(captureStartEvent);
// if something was passed as output, it takes precedence and will be used as output
if (captureStartEvent.output && captureStartEvent.output !== '') {
otp = captureStartEvent.output;
} else {
otp = showdown.helper.emojis[emojiCode];
}
let beforeHashEvent = new showdown.Event('makehtml.emoji.onHash', otp);
beforeHashEvent
.setOutput(otp)
._setGlobals(globals)
._setOptions(options);
beforeHashEvent = globals.converter.dispatch(beforeHashEvent);
return beforeHashEvent.output;
});
let afterEvent = new showdown.Event('makehtml.emoji.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
////
// makehtml/emphasisAndStrong.js
// Copyright (c) 2022 ShowdownJS
//
// Transforms MD emphasis and strong into `<em>` and `<strong>` html entities
//
// Markdown treats asterisks (*) and underscores (_) as indicators of emphasis.
// Text wrapped with one * or _ will be wrapped with an HTML <em> tag;
// double *s or _s will be wrapped with an HTML <strong> tag
//
// ***Author:***
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.emphasisAndStrong', function (text, options, globals) {
'use strict';
let startEvent = new showdown.Event('makehtml.emphasisAndStrong.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
/**
* @param {string} txt
* @param {string} tags
* @param {string} wholeMatch
* @param {RegExp} pattern
* @returns {string}
*/
function parseInside (txt, tags, wholeMatch, pattern) {
let otp = 'ERROR',
attributes,
subEventName;
switch (tags) {
case '<em>':
attributes = {
em: {}
};
subEventName = 'emphasis';
break;
case '<strong>':
attributes = {
strong: {}
};
subEventName = 'strong';
break;
case '<strong><em>':
attributes = {
em: {},
strong: {}
};
subEventName = 'emphasisAndStrong';
break;
default:
attributes = {};
subEventName = 'emphasisAndStrong';
break;
}
let captureStartEvent = new showdown.Event('makehtml.emphasisAndStrong.' + subEventName + '.onCapture', txt);
captureStartEvent
.setOutput(null)
._setGlobals(globals)
._setOptions(options)
.setRegexp(pattern)
.setMatches({
_wholeMatch: wholeMatch,
text: txt
})
.setAttributes(attributes);
captureStartEvent = globals.converter.dispatch(captureStartEvent);
// if something was passed as output, it takes precedence
// and will be used as output
if (captureStartEvent.output && captureStartEvent.output !== '') {
otp = captureStartEvent.output;
} else {
attributes = captureStartEvent.attributes;
if (showdown.helper.isUndefined(attributes.em)) {
attributes.em = {};
}
if (showdown.helper.isUndefined(attributes.strong)) {
attributes.strong = {};
}
switch (tags) {
case '<em>':
otp = '<em' + showdown.helper._populateAttributes(attributes.em) + '>' + txt + '</em>';
break;
case '<strong>':
otp = '<strong' + showdown.helper._populateAttributes(attributes.strong) + '>' + txt + '</strong>';
break;
case '<strong><em>':
otp = '<strong' + showdown.helper._populateAttributes(attributes.strong) + '>' +
'<em' + showdown.helper._populateAttributes(attributes.em) + '>' +
txt +
'</em>' +
'</strong>';
break;
}
}
let beforeHashEvent = new showdown.Event('makehtml.emphasisAndStrong.' + subEventName + '.onHash', otp);
beforeHashEvent
.setOutput(otp)
._setGlobals(globals)
._setOptions(options);
beforeHashEvent = globals.converter.dispatch(beforeHashEvent);
otp = beforeHashEvent.output;
return otp;
}
// it's faster to have 3 separate regexes for each case than have just one
// because of backtracking, in some cases, it could lead to an exponential effect
// called "catastrophic backtrace". Ominous!
const lmwuStrongEmRegex = /\b___(\S[\s\S]*?)___\b/g,
lmwuStrongRegex = /\b__(\S[\s\S]*?)__\b/g,
lmwuEmRegex = /\b_(\S[\s\S]*?)_\b/g,
underscoreStrongEmRegex = /___(\S[\s\S]*?)___/g,
unserscoreStrongRegex = /__(\S[\s\S]*?)__/g,
unserscoreEmRegex = /_([^\s_][\s\S]*?)_/g,
asteriskStrongEm = /\*\*\*(\S[\s\S]*?)\*\*\*/g,
asteriskStrong = /\*\*(\S[\s\S]*?)\*\*/g,
asteriskEm = /\*([^\s*][\s\S]*?)\*/g;
// Parse underscores
if (options.literalMidWordUnderscores) {
text = text.replace(lmwuStrongEmRegex, function (wm, txt) {
return parseInside (txt, '<strong><em>', wm, lmwuStrongEmRegex);
});
text = text.replace(lmwuStrongRegex, function (wm, txt) {
return parseInside (txt, '<strong>', wm, lmwuStrongRegex);
});
text = text.replace(lmwuEmRegex, function (wm, txt) {
return parseInside (txt, '<em>', wm, lmwuEmRegex);
});
} else {
text = text.replace(underscoreStrongEmRegex, function (wm, m) {
return (/\S$/.test(m)) ? parseInside (m, '<strong><em>', wm, underscoreStrongEmRegex) : wm;
});
text = text.replace(unserscoreStrongRegex, function (wm, m) {
return (/\S$/.test(m)) ? parseInside (m, '<strong>', wm, unserscoreStrongRegex) : wm;
});
text = text.replace(unserscoreEmRegex, function (wm, m) {
// !/^_[^_]/.test(m) - test if it doesn't start with __ (since it seems redundant, we removed it)
return (/\S$/.test(m)) ? parseInside (m, '<em>', wm, unserscoreEmRegex) : wm;
});
}
// Now parse asterisks
text = text.replace(asteriskStrongEm, function (wm, m) {
return (/\S$/.test(m)) ? parseInside (m, '<strong><em>', wm, asteriskStrongEm) : wm;
});
text = text.replace(asteriskStrong, function (wm, m) {
return (/\S$/.test(m)) ? parseInside (m, '<strong>', wm, asteriskStrong) : wm;
});
text = text.replace(asteriskEm, function (wm, m) {
// !/^\*[^*]/.test(m) - test if it doesn't start with ** (since it seems redundant, we removed it)
return (/\S$/.test(m)) ? parseInside (m, '<em>', wm, asteriskEm) : wm;
});
//}
let afterEvent = new showdown.Event('makehtml.emphasisAndStrong.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
////
// makehtml/encodeAmpsAndAngles.js
// Copyright (c) 2018 ShowdownJS
//
// Smart processing for ampersands and angle brackets that need to be encoded.
//
// ***Author:***
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.encodeAmpsAndAngles', function (text, options, globals) {
'use strict';
let startEvent = new showdown.Event('makehtml.encodeAmpsAndAngles.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
// Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
// http://bumppo.net/projects/amputator/
text = text.replace(/&(?!#?[xX]?(?:[\da-fA-F]+|\w+);)/g, '&amp;');
// Encode naked <'s
text = text.replace(/<(?![a-z\/?$!])/gi, '&lt;');
// Encode <
text = text.replace(/</g, '&lt;');
// Encode >
text = text.replace(/>/g, '&gt;');
let afterEvent = new showdown.Event('makehtml.encodeAmpsAndAngles.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
////
// makehtml/encodeBackslashEscapes.js
// Copyright (c) 2018 ShowdownJS
//
// Returns the string, with after processing the following backslash escape sequences.
//
// The polite way to do this is with the new escapeCharacters() function:
//
// text = escapeCharacters(text,"\\",true);
// text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
//
// ...but we're sidestepping its use of the (slow) RegExp constructor
// as an optimization for Firefox. This function gets called a LOT.
//
// ***Author:***
// - attacklab
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.encodeBackslashEscapes', function (text, options, globals) {
'use strict';
let startEvent = new showdown.Event('makehtml.encodeBackslashEscapes.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
text = text.replace(/\\(\\)/g, showdown.helper.escapeCharactersCallback);
text = text.replace(/\\([`*_{}\[\]()>#+.!~=|:-])/g, showdown.helper.escapeCharactersCallback);
let afterEvent = new showdown.Event('makehtml.encodeBackslashEscapes.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
////
// makehtml/emoji.js
// Copyright (c) 2018 ShowdownJS
//
// Encode/escape certain characters inside Markdown code runs.
// The point is that in code, these characters are literals,
// and lose their special Markdown meanings.
//
// ***Author:***
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.encodeCode', function (text, options, globals) {
'use strict';
let startEvent = new showdown.Event('makehtml.encodeCode.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
// Encode all ampersands; HTML entities are not
// entities within a Markdown code span.
text = text
.replace(/&/g, '&amp;')
// Do the angle bracket song and dance:
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
// Now, escape characters that are magic in Markdown:
.replace(/([*_{}\[\]\\=~-])/g, showdown.helper.escapeCharactersCallback);
let afterEvent = new showdown.Event('makehtml.encodeCode.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
////
// makehtml/escapeSpecialCharsWithinTagAttributes.js
// Copyright (c) 2018 ShowdownJS
//
// Within tags -- meaning between < and > -- encode [\ ` * _ ~ =] so they
// don't conflict with their use in Markdown for code, italics and strong.
//
// ***Author:***
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.escapeSpecialCharsWithinTagAttributes', function (text, options, globals) {
'use strict';
let startEvent = new showdown.Event('makehtml.escapeSpecialCharsWithinTagAttributes.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
// Build a regex to find HTML tags.
let tags = /<\/?[a-z\d_:-]+(?:[\s]+[\s\S]+?)?>/gi,
comments = /<!(--(([^>-]|-[^>])([^-]|-[^-])*)--)>/gi;
text = text.replace(tags, function (wholeMatch) {
return wholeMatch
.replace(/(.)<\/?code>(?=.)/g, '$1`')
.replace(/([\\`*_~=|])/g, showdown.helper.escapeCharactersCallback);
});
text = text.replace(comments, function (wholeMatch) {
return wholeMatch
.replace(/([\\`*_~=|])/g, showdown.helper.escapeCharactersCallback);
});
let afterEvent = new showdown.Event('makehtml.escapeSpecialCharsWithinTagAttributes.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
////
// makehtml/githubCodeBlock.js
// Copyright (c) 2018 ShowdownJS
//
// Handle github codeblocks prior to running HashHTML so that
// HTML contained within the codeblock gets escaped properly
// Example:
// ```ruby
// def hello_world(x)
// puts "Hello, #{x}"
// end
// ```
//
// ***Author:***
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.githubCodeBlock', function (text, options, globals) {
'use strict';
// early exit if option is not enabled
if (!options.ghCodeBlocks) {
return text;
}
let startEvent = new showdown.Event('makehtml.githubCodeBlock.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output + '¨0';
let pattern = /(?:^|\n) {0,3}(```+|~~~+) *([^\n\t`~]*)\n([\s\S]*?)\n {0,3}\1/g;
text = text.replace(pattern, function (wholeMatch, delim, language, codeblock) {
let end = (options.omitExtraWLInCodeBlocks) ? '' : '\n',
otp,
attributes = {
pre: {},
code: {},
};
let captureStartEvent = new showdown.Event('makehtml.githubCodeBlock.onCapture', codeblock);
captureStartEvent
.setOutput(null)
._setGlobals(globals)
._setOptions(options)
.setRegexp(pattern)
.setMatches({
_whoteMatch: wholeMatch,
codeblock: codeblock,
infostring: language
})
.setAttributes(attributes);
captureStartEvent = globals.converter.dispatch(captureStartEvent);
// if something was passed as output, it takes precedence
// and will be used as output
if (captureStartEvent.output && captureStartEvent.output !== '') {
otp = captureStartEvent.output;
} else {
// First parse the github code block
let infostring = captureStartEvent.matches.infostring;
let lang = infostring.trim().split(' ')[0];
codeblock = captureStartEvent.matches.codeblock;
codeblock = showdown.subParser('makehtml.encodeCode')(codeblock, options, globals);
codeblock = showdown.subParser('makehtml.detab')(codeblock, options, globals);
codeblock = codeblock
.replace(/^\n+/g, '') // trim leading newlines
.replace(/\n+$/g, ''); // trim trailing whitespace
attributes = captureStartEvent.attributes;
otp = '<pre><code>';
if (!showdown.helper.isUndefined(attributes)) {
otp = '<pre' + showdown.helper._populateAttributes(attributes.pre) + '>';
// if the language has spaces followed by some other chars, according to the spec we should just ignore everything
// after the first space
if (infostring) {
if (!attributes.code) {
attributes.code = {};
}
if (!attributes.code.classes) {
attributes.code.classes = [];
}
if (attributes.code.classes) {
if (showdown.helper.isString(attributes.code.classes)) {
attributes.code.classes += ' ' + lang + ' language-' + lang;
} else if (showdown.helper.isArray(attributes.code.classes)) {
attributes.code.classes.push(lang);
attributes.code.classes.push('language-' + lang);
}
}
}
otp += '<code' + showdown.helper._populateAttributes(attributes.code) + '>';
}
if (options.omitExtraWLInCodeBlocks) {
end = '';
}
otp += codeblock + end + '</code></pre>';
}
let beforeHashEvent = new showdown.Event('makehtml.githubCodeBlock.onHash', otp);
beforeHashEvent
.setOutput(otp)
._setGlobals(globals)
._setOptions(options);
beforeHashEvent = globals.converter.dispatch(beforeHashEvent);
otp = beforeHashEvent.output;
otp = showdown.subParser('makehtml.hashBlock')(otp, options, globals);
// Since GHCodeblocks can be false positives, we need to
// store the primitive text and the parsed text in a global var,
// and then return a token
return '\n\n¨G' + (globals.ghCodeBlocks.push({text: wholeMatch, codeblock: otp}) - 1) + 'G\n\n';
});
// attacklab: strip sentinel
text = text.replace(/¨0/, '');
let afterEvent = new showdown.Event('makehtml.githubCodeBlock.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
////
// makehtml/hashBlock.js
// Copyright (c) 2018 ShowdownJS
//
//
// ***Author:***
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.hashBlock', function (text, options, globals) {
'use strict';
let startEvent = new showdown.Event('makehtml.hashBlock.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
text = text.replace(/(^\n+|\n+$)/g, '');
text = '\n\n¨K' + (globals.gHtmlBlocks.push(text) - 1) + 'K\n\n';
let afterEvent = new showdown.Event('makehtml.hashBlock.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
////
// makehtml/hashCodeTags.js
// Copyright (c) 2018 ShowdownJS
//
// Hash and escape <code> elements that should not be parsed as markdown
//
// ***Author:***
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.hashCodeTags', function (text, options, globals) {
'use strict';
let startEvent = new showdown.Event('makehtml.hashCodeTags.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
let repFunc = function (wholeMatch, match, left, right) {
let codeblock = left + showdown.subParser('makehtml.encodeCode')(match, options, globals) + right;
return '¨C' + (globals.gHtmlSpans.push(codeblock) - 1) + 'C';
};
// Hash naked <code>
text = showdown.helper.replaceRecursiveRegExp(text, repFunc, '<code\\b[^>]*>', '</code>', 'gim');
let afterEvent = new showdown.Event('makehtml.hashCodeTags.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
showdown.subParser('makehtml.hashElement', function (text, options, globals) {
'use strict';
return function (wholeMatch, m1) {
let blockText = m1;
// Undo double lines
blockText = blockText.replace(/\n\n/g, '\n');
blockText = blockText.replace(/^\n/, '');
// strip trailing blank lines
blockText = blockText.replace(/\n+$/g, '');
// Replace the element text with a marker ("¨KxK" where x is its key)
blockText = '\n\n¨K' + (globals.gHtmlBlocks.push(blockText) - 1) + 'K\n\n';
return blockText;
};
});
////
// makehtml/hashHTMLBlocks.js
// Copyright (c) 2018 ShowdownJS
//
// Hash HTML blocks
//
// ***Author:***
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.hashHTMLBlocks', function (text, options, globals) {
'use strict';
let startEvent = new showdown.Event('makehtml.hashHTMLBlocks.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
let blockTags = [
'pre',
'div',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'blockquote',
'table',
'dl',
'ol',
'ul',
'script',
'noscript',
'form',
'fieldset',
'iframe',
'math',
'style',
'section',
'header',
'footer',
'nav',
'article',
'aside',
'address',
'audio',
'canvas',
'figure',
'hgroup',
'output',
'video',
'details',
'p'
],
repFunc = function (wholeMatch, match, left, right) {
let txt = wholeMatch;
// check if this html element is marked as markdown
// if so, it's contents should be parsed as markdown
if (left.search(/\bmarkdown\b/) !== -1) {
txt = left + globals.converter.makeHtml(match) + right;
}
return '\n\n¨K' + (globals.gHtmlBlocks.push(txt) - 1) + 'K\n\n';
};
if (options.backslashEscapesHTMLTags) {
// encode backslash escaped HTML tags
text = text.replace(/\\<(\/?[^>]+?)>/g, function (wm, inside) {
return '&lt;' + inside + '&gt;';
});
}
// hash HTML Blocks
for (let i = 0; i < blockTags.length; ++i) {
let opTagPos,
rgx1 = new RegExp('^ {0,3}(<' + blockTags[i] + '\\b[^>]*>)', 'im'),
patLeft = '<' + blockTags[i] + '\\b[^>]*>',
patRight = '</' + blockTags[i] + '>';
// 1. Look for the first position of the first opening HTML tag in the text
while ((opTagPos = showdown.helper.regexIndexOf(text, rgx1)) !== -1) {
// if the HTML tag is \ escaped, we need to escape it and break
//2. Split the text in that position
let subTexts = showdown.helper.splitAtIndex(text, opTagPos),
//3. Match recursively
newSubText1 = showdown.helper.replaceRecursiveRegExp(subTexts[1], repFunc, patLeft, patRight, 'im');
// prevent an infinite loop
if (newSubText1 === subTexts[1]) {
break;
}
text = subTexts[0].concat(newSubText1);
}
}
// HR SPECIAL CASE
text = text.replace(/(\n {0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,
showdown.subParser('makehtml.hashElement')(text, options, globals));
// Special case for standalone HTML comments
text = showdown.helper.replaceRecursiveRegExp(text, function (txt) {
return '\n\n¨K' + (globals.gHtmlBlocks.push(txt) - 1) + 'K\n\n';
}, '^ {0,3}<!--', '-->', 'gm');
// PHP and ASP-style processor instructions (<?...?> and <%...%>)
text = text.replace(/\n\n( {0,3}<([?%])[^\r]*?\2>[ \t]*(?=\n{2,}))/g,
showdown.subParser('makehtml.hashElement')(text, options, globals));
let afterEvent = new showdown.Event('makehtml.hashHTMLBlocks.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
////
// makehtml/hashHTMLSpans.js
// Copyright (c) 2018 ShowdownJS
//
// Hash span elements that should not be parsed as markdown
//
// ***Author:***
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.hashHTMLSpans', function (text, options, globals) {
'use strict';
let startEvent = new showdown.Event('makehtml.hashHTMLSpans.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
// Hash Self Closing tags
text = text.replace(/<[^>]+?\/>/gi, function (wm) {
return showdown.helper._hashHTMLSpan(wm, globals);
});
// Hash tags without properties
text = text.replace(/<([^>]+?)>[\s\S]*?<\/\1>/g, function (wm) {
return showdown.helper._hashHTMLSpan(wm, globals);
});
// Hash tags with properties
text = text.replace(/<([^>]+?)\s[^>]+?>[\s\S]*?<\/\1>/g, function (wm) {
return showdown.helper._hashHTMLSpan(wm, globals);
});
// Hash self closing tags without />
text = text.replace(/<[^>]+?>/gi, function (wm) {
return showdown.helper._hashHTMLSpan(wm, globals);
});
let afterEvent = new showdown.Event('makehtml.hashHTMLSpans.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
////
// makehtml/githubCodeBlock.js
// Copyright (c) 2018 ShowdownJS
//
// Hash and escape <pre><code> elements that should not be parsed as markdown
//
// ***Author:***
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.hashPreCodeTags', function (text, options, globals) {
'use strict';
let startEvent = new showdown.Event('makehtml.hashPreCodeTags.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
let repFunc = function (wholeMatch, match, left, right) {
// encode html entities
let codeblock = left + showdown.subParser('makehtml.encodeCode')(match, options, globals) + right;
return '\n\n¨G' + (globals.ghCodeBlocks.push({text: wholeMatch, codeblock: codeblock}) - 1) + 'G\n\n';
};
// Hash <pre><code>
text = showdown.helper.replaceRecursiveRegExp(text, repFunc, '^ {0,3}<pre\\b[^>]*>\\s*<code\\b[^>]*>', '^ {0,3}</code>\\s*</pre>', 'gim');
let afterEvent = new showdown.Event('makehtml.hashPreCodeTags.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
////
// makehtml/blockquote.js
// Copyright (c) 2018 ShowdownJS
//
// Transforms MD headings into `<h#>` html entities
//
// Setext-style headers:
// Header 1
// ========
//
// Header 2
// --------
//
// atx-style headers:
// # Header 1
// ## Header 2
// ## Header 2 with closing hashes ##
// ...
// ###### Header 6
//
// ***Author:***
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.heading', function (text, options, globals) {
'use strict';
function parseHeader (pattern, wholeMatch, headingText, headingLevel, headingId) {
let captureStartEvent = new showdown.Event('makehtml.heading.onCapture', headingText),
otp;
captureStartEvent
.setOutput(null)
._setGlobals(globals)
._setOptions(options)
.setRegexp(pattern)
.setMatches({
_wholeMatch: wholeMatch,
heading: headingText
})
.setAttributes({
id: headingId
});
captureStartEvent = globals.converter.dispatch(captureStartEvent);
// if something was passed as output, it takes precedence
// and will be used as output
if (captureStartEvent.output && captureStartEvent.output !== '') {
otp = captureStartEvent.output;
} else {
headingText = captureStartEvent.matches.heading;
let spanGamut = showdown.subParser('makehtml.spanGamut')(headingText, options, globals),
attributes = captureStartEvent.attributes;
otp = '<h' + headingLevel + showdown.helper._populateAttributes(attributes) + '>' + spanGamut + '</h' + headingLevel + '>';
}
let beforeHashEvent = new showdown.Event('makehtml.heading.onHash', otp);
beforeHashEvent
.setOutput(otp)
._setGlobals(globals)
._setOptions(options);
beforeHashEvent = globals.converter.dispatch(beforeHashEvent);
otp = beforeHashEvent.output;
return showdown.subParser('makehtml.hashBlock')(otp, options, globals);
}
let startEvent = new showdown.Event('makehtml.heading.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
let setextRegexH1 = (options.smoothLivePreview) ? /^(.+)[ \t]*\n={2,}[ \t]*\n+/gm : /^(.+)[ \t]*\n=+[ \t]*\n+/gm,
setextRegexH2 = (options.smoothLivePreview) ? /^(.+)[ \t]*\n-{2,}[ \t]*\n+/gm : /^(.+)[ \t]*\n-+[ \t]*\n+/gm,
atxRegex = (options.requireSpaceBeforeHeadingText) ? /^(#{1,6})[ \t]+(.+?)[ \t]*#*\n+/gm : /^(#{1,6})[ \t]*(.+?)[ \t]*#*\n+/gm;
text = text.replace(setextRegexH1, function (wholeMatch, headingText) {
let id = (options.noHeaderId) ? null : showdown.subParser('makehtml.heading.id')(headingText, options, globals);
return parseHeader(setextRegexH1, wholeMatch, headingText, options.headerLevelStart, id);
});
text = text.replace(setextRegexH2, function (wholeMatch, headingText) {
let id = (options.noHeaderId) ? null : showdown.subParser('makehtml.heading.id')(headingText, options, globals);
return parseHeader(setextRegexH2, wholeMatch, headingText, options.headerLevelStart + 1, id);
});
text = text.replace(atxRegex, function (wholeMatch, m1, m2) {
let headingLevel = options.headerLevelStart - 1 + m1.length,
headingText = (options.customizedHeaderId) ? m2.replace(/\s?{([^{]+?)}\s*$/, '') : m2,
id = (options.noHeaderId) ? null : showdown.subParser('makehtml.heading.id')(m2, options, globals);
return parseHeader(setextRegexH2, wholeMatch, headingText, headingLevel, id);
});
let afterEvent = new showdown.Event('makehtml.heading.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
showdown.subParser('makehtml.heading.id', function (m, options, globals) {
let title,
prefix;
// It is separate from other options to allow combining prefix and customized
if (options.customizedHeaderId) {
let match = m.match(/{([^{]+?)}\s*$/);
if (match && match[1]) {
m = match[1];
}
}
title = m;
// Prefix id to prevent causing inadvertent pre-existing style matches.
if (showdown.helper.isString(options.prefixHeaderId)) {
prefix = options.prefixHeaderId;
} else if (options.prefixHeaderId === true) {
prefix = 'section-';
} else {
prefix = '';
}
if (!options.rawPrefixHeaderId) {
title = prefix + title;
}
if (options.ghCompatibleHeaderId) {
title = title
.replace(/ /g, '-')
// replace previously escaped chars (&, ¨ and $)
.replace(/&amp;/g, '')
.replace(/¨T/g, '')
.replace(/¨D/g, '')
// replace rest of the chars (&~$ are repeated as they might have been escaped)
// borrowed from github's redcarpet (so they should produce similar results)
.replace(/[&+$,\/:;=?@"#{}|^¨~\[\]`\\*)(%.!'<>]/g, '')
.toLowerCase();
} else if (options.rawHeaderId) {
title = title
.replace(/ /g, '-')
// replace previously escaped chars (&, ¨ and $)
.replace(/&amp;/g, '&')
.replace(/¨T/g, '¨')
.replace(/¨D/g, '$')
// replace " and '
.replace(/["']/g, '-')
.toLowerCase();
} else {
title = title
.replace(/[^\w]/g, '')
.toLowerCase();
}
if (options.rawPrefixHeaderId) {
title = prefix + title;
}
if (globals.hashLinkCounts[title]) {
title = title + '-' + (globals.hashLinkCounts[title]++);
} else {
globals.hashLinkCounts[title] = 1;
}
return title;
});
////
// makehtml/blockquote.js
// Copyright (c) 2018 ShowdownJS
//
// Turn Markdown horizontal rule shortcuts into <hr /> tags.
//
// Any 3 or more unindented consecutive hyphens, asterisks or underscores with or without a space beetween them
// in a single line is considered a horizontal rule
//
// ***Author:***
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.horizontalRule', function (text, options, globals) {
'use strict';
let startEvent = new showdown.Event('makehtml.horizontalRule.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
const rgx1 = /^ {0,2}( ?-){3,}[ \t]*$/gm;
text = text.replace(/^ {0,2}( ?-){3,}[ \t]*$/gm, function (wholeMatch) {
return parse(rgx1, wholeMatch);
});
const rgx2 = /^ {0,2}( ?\*){3,}[ \t]*$/gm;
text = text.replace(/^ {0,2}( ?\*){3,}[ \t]*$/gm, function (wholeMatch) {
return parse(rgx2, wholeMatch);
});
const rgx3 = /^ {0,2}( ?\*){3,}[ \t]*$/gm;
text = text.replace(/^ {0,2}( ?_){3,}[ \t]*$/gm, function (wholeMatch) {
return parse(rgx3, wholeMatch);
});
let afterEvent = new showdown.Event('makehtml.horizontalRule.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
/**
*
* @param {RegExp} pattern
* @param {string} wholeMatch
* @returns {string}
*/
function parse (pattern, wholeMatch) {
let otp;
let captureStartEvent = new showdown.Event('makehtml.horizontalRule.onCapture', wholeMatch);
captureStartEvent
.setOutput(null)
._setGlobals(globals)
._setOptions(options)
.setRegexp(pattern)
.setMatches({
_whoteMatch: wholeMatch
})
.setAttributes({});
captureStartEvent = globals.converter.dispatch(captureStartEvent);
// if something was passed as output, it takes precedence
// and will be used as output
if (captureStartEvent.output && captureStartEvent.output !== '') {
otp = captureStartEvent.output;
} else {
otp = '<hr' + showdown.helper._populateAttributes(captureStartEvent.attributes) + ' />';
}
let beforeHashEvent = new showdown.Event('makehtml.horizontalRule.onHash', otp);
beforeHashEvent
.setOutput(otp)
._setGlobals(globals)
._setOptions(options);
beforeHashEvent = globals.converter.dispatch(beforeHashEvent);
otp = beforeHashEvent.output;
otp = showdown.subParser('makehtml.hashBlock')(otp, options, globals);
return otp;
}
});
////
// makehtml/blockquote.js
// Copyright (c) 2018 ShowdownJS
//
// Turn Markdown image into <img> tags.
//
// ***Author:***
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.image', function (text, options, globals) {
'use strict';
let startEvent = new showdown.Event('makehtml.image.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
let inlineRegExp = /!\[([^\]]*?)][ \t]*\([ \t]?<?(\S+?(?:\(\S*?\)\S*?)?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\5)?[ \t]?\)/g,
crazyRegExp = /!\[([^\]]*?)][ \t]*\([ \t]?<([^>]*)>(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\5)?[ \t]?\)/g,
base64RegExp = /!\[([^\]]*?)][ \t]*\([ \t]?<?(data:.+?\/.+?;base64,[A-Za-z\d+/=\n]+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\6)?[ \t]?\)/g,
referenceRegExp = /!\[([^\]]*?)] ?(?:\n *)?\[([\s\S]*?)]/g,
refShortcutRegExp = /!\[([^\[\]]+)]/g;
// First, handle reference-style labeled images: ![alt text][id]
text = text.replace(referenceRegExp, function (wholeMatch, altText, linkId) {
return writeImageTag ('reference', referenceRegExp, wholeMatch, altText, null, linkId);
});
// Next, handle inline images: ![alt text](url =<width>x<height> "optional title")
// base64 encoded images
text = text.replace(base64RegExp, function (wholeMatch, altText, url, width, height, m5, title) {
url = url.replace(/\s/g, '');
return writeImageTag ('inline', base64RegExp, wholeMatch, altText, url, null, width, height, title);
});
// cases with crazy urls like ./image/cat1).png
text = text.replace(crazyRegExp, function (wholeMatch, altText, url, width, height, m5, title) {
url = showdown.helper.applyBaseUrl(options.relativePathBaseUrl, url);
return writeImageTag ('inline', crazyRegExp, wholeMatch, altText, url, null, width, height, title);
});
// normal cases
text = text.replace(inlineRegExp, function (wholeMatch, altText, url, width, height, m5, title) {
url = showdown.helper.applyBaseUrl(options.relativePathBaseUrl, url);
return writeImageTag ('inline', inlineRegExp, wholeMatch, altText, url, null, width, height, title);
});
// handle reference-style shortcuts: ![img text]
text = text.replace(refShortcutRegExp, function (wholeMatch, altText) {
return writeImageTag ('reference', refShortcutRegExp, wholeMatch, altText);
});
let afterEvent = new showdown.Event('makehtml.image.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
/**
* @param {string} subEvtName
* @param {RegExp} pattern
* @param {string} wholeMatch
* @param {string} altText
* @param {string|null} [url]
* @param {string|null} [linkId]
* @param {string|null} [width]
* @param {string|null} [height]
* @param {string|null} [title]
* @returns {string}
*/
function writeImageTag (subEvtName, pattern, wholeMatch, altText, url, linkId, width, height, title) {
let gUrls = globals.gUrls,
gTitles = globals.gTitles,
gDims = globals.gDimensions,
matches = {
_wholeMatch: wholeMatch,
_altText: altText,
_linkId: linkId,
_url: url,
_width: width,
_height: height,
_title: title
},
otp,
attributes = {};
linkId = (linkId) ? linkId.toLowerCase() : null;
if (!title) {
title = null;
}
// Special case for explicit empty url
if (wholeMatch.search(/\(<?\s*>? ?(['"].*['"])?\)$/m) > -1) {
url = '';
} else if (showdown.helper.isUndefined(url) || url === '' || url === null) {
if (linkId === '' || linkId === null) {
// lower-case and turn embedded newlines into spaces
linkId = altText.toLowerCase().replace(/ ?\n/g, ' ');
}
url = '#' + linkId;
if (!showdown.helper.isUndefined(gUrls[linkId])) {
url = gUrls[linkId];
if (!showdown.helper.isUndefined(gTitles[linkId])) {
title = gTitles[linkId];
}
if (!showdown.helper.isUndefined(gDims[linkId])) {
width = gDims[linkId].width;
height = gDims[linkId].height;
}
} else {
return wholeMatch;
}
}
altText = altText
.replace(/"/g, '&quot;')
//altText = showdown.helper.escapeCharacters(altText, '*_', false);
.replace(showdown.helper.regexes.asteriskDashTildeAndColon, showdown.helper.escapeCharactersCallback);
//url = showdown.helper.escapeCharacters(url, '*_', false);
url = url.replace(showdown.helper.regexes.asteriskDashTildeAndColon, showdown.helper.escapeCharactersCallback);
if (title && showdown.helper.isString(title)) {
title = title
.replace(/"/g, '&quot;')
.replace(showdown.helper.regexes.asteriskDashTildeAndColon, showdown.helper.escapeCharactersCallback);
}
if (width) {
width = (width === '*') ? 'auto' : width;
} else {
width = null;
}
if (height) {
height = (height === '*') ? 'auto' : height;
} else {
height = null;
}
let captureStartEvent = new showdown.Event('makehtml.image.' + subEvtName + '.onCapture', wholeMatch);
captureStartEvent
.setOutput(null)
._setGlobals(globals)
._setOptions(options)
.setRegexp(pattern)
.setMatches(matches)
.setAttributes({
src: url,
alt: altText,
title: title,
width: width,
height: height
});
captureStartEvent = globals.converter.dispatch(captureStartEvent);
// if something was passed as output, it takes precedence
// and will be used as output
if (captureStartEvent.output && captureStartEvent.output !== '') {
otp = captureStartEvent.output;
} else {
attributes = captureStartEvent.attributes;
otp = '<img' + showdown.helper._populateAttributes(attributes) + ' />';
}
let beforeHashEvent = new showdown.Event('makehtml.image.' + subEvtName + '.onHash', otp);
beforeHashEvent
.setOutput(otp)
._setGlobals(globals)
._setOptions(options);
beforeHashEvent = globals.converter.dispatch(beforeHashEvent);
otp = beforeHashEvent.output;
return otp;
}
});
////
// makehtml/links.js
// Copyright (c) 2018 ShowdownJS
//
// Transforms MD links into `<a>` html anchors
//
// A link contains link text (the visible text), a link destination (the URI that is the link destination), and
// optionally a link title. There are two basic kinds of links in Markdown.
// In inline links the destination and title are given immediately after the link text.
// In reference links the destination and title are defined elsewhere in the document.
//
// ***Author:***
// - Estevão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.link', function (text, options, globals) {
//
// Parser starts here
//
let startEvent = new showdown.Event('makehtml.link.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
// 1. Handle reference-style links: [link text] [id]
let referenceRegex = /\[((?:\[[^\]]*]|[^\[\]])*)] ?(?:\n *)?\[(.*?)]/g;
text = text.replace(referenceRegex, function (wholeMatch, text, linkId) {
// bail if we find 2 newlines somewhere
if (/\n\n/.test(wholeMatch)) {
return wholeMatch;
}
return writeAnchorTag ('reference', referenceRegex, wholeMatch, text, linkId);
});
// 2. Handle inline-style links: [link text](url "optional title")
// 2.1. Look for empty cases: []() and [empty]() and []("title")
let inlineEmptyRegex = /\[(.*?)]\(<? ?>? ?(["'](.*)["'])?\)/g;
text = text.replace(inlineEmptyRegex, function (wholeMatch, text, m1, title) {
return writeAnchorTag ('inline', inlineEmptyRegex, wholeMatch, text, null, null, title, true);
});
// 2.2. Look for cases with crazy urls like ./image/cat1).png
// the url mus be enclosed in <>
let inlineCrazyRegex = /\[((?:\[[^\]]*]|[^\[\]])*)]\s?\([ \t]?<([^>]*)>(?:[ \t]*((["'])([^"]*?)\4))?[ \t]?\)/g;
text = text.replace(inlineCrazyRegex, function (wholeMatch, text, url, m1, m2, title) {
return writeAnchorTag ('inline', inlineCrazyRegex, wholeMatch, text, null, url, title);
});
// 2.3. inline links with no title or titles wrapped in ' or ":
// [text](url.com) || [text](<url.com>) || [text](url.com "title") || [text](<url.com> "title")
let inlineNormalRegex1 = /\[([\S ]*?)]\s?\( *<?([^\s'"]*?(?:\(\S*?\)\S*?)?)>?\s*(?:(['"])(.*?)\3)? *\)/g;
text = text.replace(inlineNormalRegex1, function (wholeMatch, text, url, m1, title) {
return writeAnchorTag ('inline', inlineNormalRegex1, wholeMatch, text, null, url, title);
});
// 2.4. inline links with titles wrapped in (): [foo](bar.com (title))
let inlineNormalRegex2 = /\[([\S ]*?)]\s?\( *<?([^\s'"]*?(?:\(\S*?\)\S*?)?)>?\s+\((.*?)\) *\)/g;
text = text.replace(inlineNormalRegex2, function (wholeMatch, text, url, title) {
return writeAnchorTag ('inline', inlineNormalRegex2, wholeMatch, text, null, url, title);
});
// 3. Handle reference-style shortcuts: [link text]
// These must come last in case there's a [link text][1] or [link text](/foo)
let referenceShortcutRegex = /\[([^\[\]]+)]/g;
text = text.replace(referenceShortcutRegex, function (wholeMatch, text) {
return writeAnchorTag ('reference', referenceShortcutRegex, wholeMatch, text);
});
// 4. Handle angle brackets links -> `<http://example.com/>`
// Must come after links, because you can use < and > delimiters in inline links like [this](<url>).
// 4.1. Handle links first
let angleBracketsLinksRegex = /<(((?:https?|ftp):\/\/|www\.)[^'">\s]+)>/gi;
text = text.replace(angleBracketsLinksRegex, function (wholeMatch, url, urlStart) {
let text = url;
url = (urlStart === 'www.') ? 'http://' + url : url;
return writeAnchorTag ('angleBrackets', angleBracketsLinksRegex, wholeMatch, text, null, url);
});
// 4.2. Then mail adresses
let angleBracketsMailRegex = /<(?:mailto:)?([-.\w]+@[-a-z\d]+(\.[-a-z\d]+)*\.[a-z]+)>/gi;
text = text.replace(angleBracketsMailRegex, function (wholeMatch, mail) {
const m = parseMail(mail);
return writeAnchorTag ('angleBrackets', angleBracketsMailRegex, wholeMatch, m.mail, null, m.url);
});
// 5. Handle GithubMentions (if option is enabled)
if (options.ghMentions) {
let ghMentionsRegex = /(^|\s)(\\)?(@([a-z\d]+(?:[a-z\d._-]+?[a-z\d]+)*))/gi;
text = text.replace(ghMentionsRegex, function (wholeMatch, st, escape, mentions, username) {
// bail if the mentions was escaped
if (escape === '\\') {
return st + mentions;
}
// check if options.ghMentionsLink is a string
// TODO Validation should be done at initialization not at runtime
if (!showdown.helper.isString(options.ghMentionsLink)) {
throw new Error('ghMentionsLink option must be a string');
}
let url = options.ghMentionsLink.replace(/\{u}/g, username);
return st + writeAnchorTag ('reference', ghMentionsRegex, wholeMatch, mentions, null, url);
});
}
// 6 and 7 have to come here to prevent naked links to catch html
// 6. Handle <a> tags
text = text.replace(/<a\s[^>]*>[\s\S]*<\/a>/g, function (wholeMatch) {
return showdown.helper._hashHTMLSpan(wholeMatch, globals);
});
// 7. Handle <img> tags
text = text.replace(/<img\s[^>]*\/?>/g, function (wholeMatch) {
return showdown.helper._hashHTMLSpan(wholeMatch, globals);
});
// 8. Handle naked links (if option is enabled)
if (options.simplifiedAutoLink) {
// 8.1. Check for naked URLs
// we also include leading markdown magic chars [_*~] for cases like __https://www.google.com/foobar__
let nakedUrlRegex = /([_*~]*?)(((?:https?|ftp):\/\/|www\.)[^\s<>"'`´.-][^\s<>"'`´]*?\.[a-z\d.]+[^\s<>"']*)\1/gi;
text = text.replace(nakedUrlRegex, function (wholeMatch, leadingMDChars, url, urlPrefix) {
// we now will start traversing the url from the front to back, looking for punctuation chars [_*~,;:.!?\)\]]
const len = url.length;
let suffix = '';
for (let i = len - 1; i >= 0; --i) {
let char = url.charAt(i);
if (/[_*~,;:.!?]/.test(char)) {
// it's a punctuation char so we remove it from the url
url = url.slice(0, -1);
// and prepend it to the suffix
suffix = char + suffix;
} else if (/[)\]]/.test(char)) {
// it's a parenthesis so we need to check for "balance" (kinda)
let opPar, clPar;
if (/\)/.test(char)) {
// it's a curved parenthesis
opPar = url.match(/\(/g) || [];
clPar = url.match(/\)/g);
} else {
// it's a squared parenthesis
opPar = url.match(/\[/g) || [];
clPar = url.match(/]/g);
}
if (opPar.length < clPar.length) {
// there are more closing Parenthesis than opening so chop it!!!!!
url = url.slice(0, -1);
// and prepend it to the suffix
suffix = char + suffix;
} else {
// it's (kinda) balanced so our work is done
break;
}
} else {
// it's not a punctuation or a parenthesis so our work is done
break;
}
}
// we copy the treated url to the text variable
let txt = url;
// finally, if it's a www shortcut, we prepend http
url = (urlPrefix === 'www.') ? 'http://' + url : url;
// url part is done so let's take care of text now
// we need to escape the text (because of links such as www.example.com/foo__bar__baz)
txt = txt.replace(showdown.helper.regexes.asteriskDashTildeAndColon, showdown.helper.escapeCharactersCallback);
// and return the link tag, with the leadingMDChars and suffix. The leadingMDChars are added at the end too because
// we consumed those characters in the regexp
return leadingMDChars +
writeAnchorTag ('autoLink', nakedUrlRegex, wholeMatch, txt, null, url) +
suffix +
leadingMDChars;
});
// 8.2. Now check for naked mail
let nakedMailRegex = /(^|\s)(?:mailto:)?([A-Za-z\d!#$%&'*+-/=?^_`{|}~.]+@[-a-z\d]+(\.[-a-z\d]+)*\.[a-z]+)(?=$|\s)/gmi;
text = text.replace(nakedMailRegex, function (wholeMatch, leadingChar, mail) {
const m = parseMail(mail);
return leadingChar + writeAnchorTag ('autoLink', nakedMailRegex, wholeMatch, m.mail, null, m.url);
});
}
let afterEvent = new showdown.Event('makehtml.link.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
/**
*
* @param {string} subEvtName
* @param {RegExp} pattern
* @param {string} wholeMatch
* @param {string} text
* @param {string|null} [linkId]
* @param {string|null} [url]
* @param {string|null} [title]
* @param {boolean} [emptyCase]
* @returns {string}
*/
function writeAnchorTag (subEvtName, pattern, wholeMatch, text, linkId, url, title, emptyCase) {
let matches = {
_wholeMatch: wholeMatch,
_linkId: linkId,
_url: url,
_title: title,
text: text
},
otp,
attributes = {};
title = title || null;
url = url || null;
linkId = (linkId) ? linkId.toLowerCase() : null;
emptyCase = !!emptyCase;
if (emptyCase) {
url = '';
} else if (!url) {
if (!linkId) {
// lower-case and turn embedded newlines into spaces
linkId = text.toLowerCase().replace(/ ?\n/g, ' ');
}
url = '#' + linkId;
if (!showdown.helper.isUndefined(globals.gUrls[linkId])) {
url = globals.gUrls[linkId];
if (!showdown.helper.isUndefined(globals.gTitles[linkId])) {
title = globals.gTitles[linkId];
}
} else {
return wholeMatch;
}
}
url = showdown.helper.applyBaseUrl(options.relativePathBaseUrl, url);
url = url.replace(showdown.helper.regexes.asteriskDashTildeAndColon, showdown.helper.escapeCharactersCallback);
attributes.href = url;
if (title && showdown.helper.isString(title)) {
title = title
.replace(/"/g, '&quot;')
.replace(showdown.helper.regexes.asteriskDashTildeAndColon, showdown.helper.escapeCharactersCallback);
attributes.title = title;
}
// optionLinksInNewWindow only applies
// to external links. Hash links (#) open in same page
if (options.openLinksInNewWindow && !/^#/.test(url)) {
attributes.rel = 'noopener noreferrer';
attributes.target = '¨E95Eblank'; // escaped _
}
let captureStartEvent = new showdown.Event('makehtml.link.' + subEvtName + '.onCapture', wholeMatch);
captureStartEvent
.setOutput(null)
._setGlobals(globals)
._setOptions(options)
.setRegexp(pattern)
.setMatches(matches)
.setAttributes(attributes);
captureStartEvent = globals.converter.dispatch(captureStartEvent);
// if something was passed as output, it takes precedence
// and will be used as output
if (captureStartEvent.output && captureStartEvent.output !== '') {
otp = captureStartEvent.output;
} else {
attributes = captureStartEvent.attributes;
text = captureStartEvent.matches.text || '';
// Text can be a markdown element, so we run through the appropriate parsers
text = showdown.subParser('makehtml.codeSpan')(text, options, globals);
text = showdown.subParser('makehtml.emoji')(text, options, globals);
text = showdown.subParser('makehtml.underline')(text, options, globals);
text = showdown.subParser('makehtml.emphasisAndStrong')(text, options, globals);
text = showdown.subParser('makehtml.strikethrough')(text, options, globals);
text = showdown.subParser('makehtml.ellipsis')(text, options, globals);
text = showdown.subParser('makehtml.hashHTMLSpans')(text, options, globals);
otp = '<a' + showdown.helper._populateAttributes(attributes) + '>' + text + '</a>';
}
let beforeHashEvent = new showdown.Event('makehtml.link.' + subEvtName + '.onHash', otp);
beforeHashEvent
.setOutput(otp)
._setGlobals(globals)
._setOptions(options);
beforeHashEvent = globals.converter.dispatch(beforeHashEvent);
otp = beforeHashEvent.output;
return showdown.subParser('makehtml.hashHTMLSpans')(otp, options, globals);
}
/**
* @param {string} mail
* @returns {{mail: string, url: string}}
*/
function parseMail (mail) {
let url = 'mailto:';
mail = showdown.subParser('makehtml.unescapeSpecialChars')(mail, options, globals);
if (options.encodeEmails) {
url = showdown.helper.encodeEmailAddress(url + mail);
mail = showdown.helper.encodeEmailAddress(mail);
} else {
url = url + mail;
}
return {
mail: mail,
url: url
};
}
});
////
// makehtml/list.js
// Copyright (c) 2022 ShowdownJS
//
// Transforms MD lists into `<ul>` or `<ol>` html list
//
// Markdown supports ordered (numbered) and unordered (bulleted) lists.
// Unordered lists use asterisks, pluses, and hyphens - interchangably - as list markers
// Ordered lists use numbers followed by periods.
//
// ***Author:***
// - Estevão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.list', function (text, options, globals) {
'use strict';
// Start of list parsing
const subListRgx = /^(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(¨0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
const mainListRgx = /(\n\n|^\n?)(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(¨0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
const listTypeRgx = /[*+-]/g;
let startEvent = new showdown.Event('makehtml.list.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
// add sentinel to hack around khtml/safari bug:
// http://bugs.webkit.org/show_bug.cgi?id=11231
text += '¨0';
if (globals.gListLevel) {
text = text.replace(subListRgx, function (wholeMatch, list, m2) {
return parseConsecutiveLists(subListRgx, list, (m2.search(listTypeRgx) > -1) ? 'ul' : 'ol', true);
});
} else {
text = text.replace(mainListRgx, function (wholeMatch, m1, list, m3) {
return parseConsecutiveLists(mainListRgx, list, (m3.search(listTypeRgx) > -1) ? 'ul' : 'ol', false);
});
}
// strip sentinel
text = text.replace(/¨0/, '');
let afterEvent = new showdown.Event('makehtml.list.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
/**
*
* @param {RegExp} pattern
* @param {string} item
* @param {boolean} checked
* @returns {string}
*/
function processTaskListItem (pattern, item, checked) {
const checkboxRgx = /^[ \t]*\[([xX ])]/m;
item = item.replace(checkboxRgx, function (wm, checkedRaw) {
let attributes = {
type: 'checkbox',
disabled: true,
style: 'margin: 0px 0.35em 0.25em -1.6em; vertical-align: middle;',
checked: !!checked
};
let captureStartEvent = new showdown.Event('makehtml.list.taskListItem.checkbox.onCapture', item);
captureStartEvent
.setOutput(null)
._setGlobals(globals)
._setOptions(options)
.setRegexp(pattern)
.setMatches({
_wholeMatch: item,
_tasklistButton: wm,
_taksListButtonChecked: checkedRaw
})
.setAttributes(attributes);
captureStartEvent = globals.converter.dispatch(captureStartEvent);
let otp;
if (captureStartEvent.output && captureStartEvent.output !== '') {
otp = captureStartEvent.output;
} else {
attributes = captureStartEvent.attributes;
otp = '<input' + showdown.helper._populateAttributes(attributes) + '>';
}
let beforeHashEvent = new showdown.Event('makehtml.list.taskListItem.checkbox.onHash', otp);
beforeHashEvent
.setOutput(otp)
._setGlobals(globals)
._setOptions(options);
beforeHashEvent = globals.converter.dispatch(beforeHashEvent);
otp = beforeHashEvent.output;
return otp;
});
return item;
}
/**
* Process the contents of a single ordered or unordered list, splitting it
* into individual list items.
* @param {string} listStr
* @param {boolean} trimTrailing
* @returns {string}
*/
function processListItems (listStr, trimTrailing) {
// The $g_list_level global keeps track of when we're inside a list.
// Each time we enter a list, we increment it; when we leave a list,
// we decrement. If it's zero, we're not in a list anymore.
//
// We do this because when we're not inside a list, we want to treat
// something like this:
//
// I recommend upgrading to version
// 8. Oops, now this line is treated
// as a sub-list.
//
// As a single paragraph, despite the fact that the second line starts
// with a digit-period-space sequence.
//
// Whereas when we're inside a list (or sub-list), that line will be
// treated as the start of a sub-list. What a kludge, huh? This is
// an aspect of Markdown's syntax that's hard to parse perfectly
// without resorting to mind-reading. Perhaps the solution is to
// change the syntax rules such that sub-lists must start with a
// starting cardinal number; e.g. "1." or "a.".
globals.gListLevel++;
// trim trailing blank lines:
listStr = listStr.replace(/\n{2,}$/, '\n');
// attacklab: add sentinel to emulate \z
listStr += '¨0';
let rgx = /(\n)?(^ {0,3})([*+-]|\d+[.])[ \t]+((\[([xX ])])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(¨0| {0,3}([*+-]|\d+[.])[ \t]+))/gm,
isParagraphed = (/\n[ \t]*\n(?!¨0)/.test(listStr));
// Since version 1.5, nesting sublists requires 4 spaces (or 1 tab) indentation,
// which is a syntax breaking change
// activating this option reverts to old behavior
// This will be removed in version 2.0
if (options.disableForced4SpacesIndentedSublists) {
rgx = /(\n)?(^ {0,3})([*+-]|\d+[.])[ \t]+((\[([xX ])])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(¨0|\2([*+-]|\d+[.])[ \t]+))/gm;
}
listStr = listStr.replace(rgx, function (wholeMatch, m1, m2, m3, m4, taskbtn, checkedRaw) {
let item = showdown.helper.outdent(m4),
attributes = {},
checked = (checkedRaw && checkedRaw.trim() !== ''),
eventName = 'makehtml.list.listItem',
captureStartEvent,
matches = {
_wholeMatch: wholeMatch,
listItem: item,
};
// Support for github tasklists
if (taskbtn && options.tasklists) {
// it's a github tasklist and tasklists are enabled
// Style used for tasklist bullets
attributes.classes = ['task-list-item'];
attributes.style = 'list-style-type: none;';
if (options.moreStyling && checked) {
attributes.classes.push('task-list-item-complete');
}
eventName = 'makehtml.list.taskListItem';
matches._taskListButton = taskbtn;
matches._taskListButtonChecked = checkedRaw;
}
captureStartEvent = new showdown.Event(eventName + '.onCapture', item);
captureStartEvent
.setOutput(null)
._setGlobals(globals)
._setOptions(options)
.setRegexp(rgx)
.setMatches(matches)
.setAttributes(attributes);
captureStartEvent = globals.converter.dispatch(captureStartEvent);
// if something was passed as output, it takes precedence
// and will be used as output
if (captureStartEvent.output && captureStartEvent.output !== '') {
item = captureStartEvent.output;
} else {
attributes = captureStartEvent.attributes;
item = captureStartEvent.matches.listItem;
// even if user there's no tasklist, it's fine because the tasklist handler will bail without raising the event
if (options.tasklists) {
item = processTaskListItem(rgx, item, checked);
}
// ISSUE #312
// This input: - - - a
// causes trouble to the parser, since it interprets it as:
// <ul><li><li><li>a</li></li></li></ul>
// instead of:
// <ul><li>- - a</li></ul>
// So, to prevent it, we will put a marker (¨A)in the beginning of the line
// Kind of hackish/monkey patching, but seems more effective than overcomplicating the list parser
item = item.replace(/^([-*+]|\d\.)[ \t]+[\S\n ]*/g, function (wm2) {
return '¨A' + wm2;
});
// SPECIAL CASE: a heading followed by a paragraph of text that is not separated by a double newline
// or/nor indented. ex:
//
// - # foo
// bar is great
//
// While this does now follow the spec per se, not allowing for this might cause confusion since
// header blocks don't need double-newlines after
if (/^#+.+\n.+/.test(item)) {
item = item.replace(/^(#+.+)$/m, '$1\n');
}
// m1 - Leading line or
// Has a double return (multi paragraph)
if (m1 || (item.search(/\n{2,}/) > -1)) {
item = showdown.subParser('makehtml.githubCodeBlock')(item, options, globals);
item = showdown.subParser('makehtml.blockquote')(item, options, globals);
item = showdown.subParser('makehtml.heading')(item, options, globals);
item = showdown.subParser('makehtml.list')(item, options, globals);
item = showdown.subParser('makehtml.codeBlock')(item, options, globals);
item = showdown.subParser('makehtml.table')(item, options, globals);
item = showdown.subParser('makehtml.hashHTMLBlocks')(item, options, globals);
//item = showdown.subParser('makehtml.paragraphs')(item, options, globals);
// TODO: This is a copy of the paragraph parser
// This is a provisory fix for issue #494
// For a permanent fix we need to rewrite the paragraph parser, passing the unhashify logic outside
// so that we can call the paragraph parser without accidently unashifying previously parsed blocks
// Strip leading and trailing lines:
item = item.replace(/^\n+/g, '');
item = item.replace(/\n+$/g, '');
let grafs = item.split(/\n{2,}/g),
grafsOut = [],
end = grafs.length; // Wrap <p> tags
for (let i = 0; i < end; i++) {
let str = grafs[i];
// if this is an HTML marker, copy it
if (str.search(/¨([KG])(\d+)\1/g) >= 0) {
grafsOut.push(str);
// test for presence of characters to prevent empty lines being parsed
// as paragraphs (resulting in undesired extra empty paragraphs)
} else if (str.search(/\S/) >= 0) {
str = showdown.subParser('makehtml.spanGamut')(str, options, globals);
str = str.replace(/^([ \t]*)/g, '<p>');
str += '</p>';
grafsOut.push(str);
}
}
item = grafsOut.join('\n');
// Strip leading and trailing lines:
item = item.replace(/^\n+/g, '');
item = item.replace(/\n+$/g, '');
} else {
// Recursion for sub-lists:
item = showdown.subParser('makehtml.list')(item, options, globals);
item = item.replace(/\n$/, ''); // chomp(item)
item = showdown.subParser('makehtml.hashHTMLBlocks')(item, options, globals);
// Colapse double linebreaks
item = item.replace(/\n\n+/g, '\n\n');
if (isParagraphed) {
item = showdown.subParser('makehtml.paragraphs')(item, options, globals);
} else {
item = showdown.subParser('makehtml.spanGamut')(item, options, globals);
}
}
// now we need to remove the marker (¨A)
item = item.replace('¨A', '');
// we can finally wrap the line in list item tags
item = '<li' + showdown.helper._populateAttributes(attributes) + '>' + item + '</li>\n';
}
let beforeHashEvent = new showdown.Event(eventName + '.onHash', item);
beforeHashEvent
.setOutput(item)
._setGlobals(globals)
._setOptions(options);
beforeHashEvent = globals.converter.dispatch(beforeHashEvent);
return beforeHashEvent.output;
});
// attacklab: strip sentinel
listStr = listStr.replace(/¨0/g, '');
globals.gListLevel--;
if (trimTrailing) {
listStr = listStr.replace(/\s+$/, '');
}
return listStr;
}
/**
*
* @param {string} list
* @param {string} listType
* @returns {string|null}
*/
function styleStartNumber (list, listType) {
// check if ol and starts by a number different than 1
if (listType === 'ol') {
let res = list.match(/^ *(\d+)\./);
if (res && res[1] !== '1') {
return res[1];
}
}
return null;
}
/**
* Check and parse consecutive lists (better fix for issue #142)
* @param {RegExp} pattern
* @param {string} list
* @param {string} listType
* @param {boolean} trimTrailing
* @returns {string}
*/
function parseConsecutiveLists (pattern, list, listType, trimTrailing) {
let otp = '';
let captureStartEvent = new showdown.Event('makehtml.list.onCapture', list);
captureStartEvent
.setOutput(null)
._setGlobals(globals)
._setOptions(options)
.setRegexp(pattern)
.setMatches({
_wholeMatch: list,
list: list
})
.setAttributes({});
captureStartEvent = globals.converter.dispatch(captureStartEvent);
let attributes = captureStartEvent.attributes;
// if something was passed as output, it takes precedence
// and will be used as output
if (captureStartEvent.output && captureStartEvent.output !== '') {
otp = captureStartEvent.output;
} else {
// check if we caught 2 or more consecutive lists by mistake
// we use the counterRgx, meaning if listType is UL we look for OL and vice versa
const olRgx = (options.disableForced4SpacesIndentedSublists) ? /^ ?\d+\.[ \t]/gm : /^ {0,3}\d+\.[ \t]/gm;
const ulRgx = (options.disableForced4SpacesIndentedSublists) ? /^ ?[*+-][ \t]/gm : /^ {0,3}[*+-][ \t]/gm;
let counterRxg = (listType === 'ul') ? olRgx : ulRgx;
let attrs = showdown.helper.cloneObject(attributes);
if (list.search(counterRxg) !== -1) {
(function parseCL (txt) {
let pos = txt.search(counterRxg);
attrs.start = styleStartNumber(list, listType);
if (pos !== -1) {
// slice
otp += '\n\n<' + listType + showdown.helper._populateAttributes(attrs) + '>\n' + processListItems(txt.slice(0, pos), !!trimTrailing) + '</' + listType + '>\n';
// invert counterType and listType
listType = (listType === 'ul') ? 'ol' : 'ul';
counterRxg = (listType === 'ul') ? olRgx : ulRgx;
//recurse
parseCL(txt.slice(pos), attrs);
} else {
otp += '\n\n<' + listType + showdown.helper._populateAttributes(attrs) + '>\n' + processListItems(txt, !!trimTrailing) + '</' + listType + '>\n';
}
})(list, attributes);
} else {
attrs.start = styleStartNumber(list, listType);
otp = '\n\n<' + listType + showdown.helper._populateAttributes(attrs) + '>\n' + processListItems(list, !!trimTrailing) + '</' + listType + '>\n';
}
}
return otp;
}
});
/**
* Parse metadata at the top of the document
*/
showdown.subParser('makehtml.metadata', function (text, options, globals) {
'use strict';
if (!options.metadata) {
return text;
}
let startEvent = new showdown.Event('makehtml.metadata.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
/**
* @param {RegExp} pattern
* @param {string} wholeMatch
* @param {string} format
* @param {string} content
* @returns {string}
*/
function parseMetadataContents (pattern, wholeMatch, format, content) {
let otp;
let captureStartEvent = new showdown.Event('makehtml.metadata.onCapture', content);
captureStartEvent
.setOutput(null)
._setGlobals(globals)
._setOptions(options)
.setRegexp(pattern)
.setMatches({
_wholeMatch: wholeMatch,
format: format,
content: content
})
.setAttributes({});
captureStartEvent = globals.converter.dispatch(captureStartEvent);
format = captureStartEvent.matches.format;
content = captureStartEvent.matches.content;
// if something was passed as output, it will be used as output
if (captureStartEvent.output && captureStartEvent.output !== '') {
otp = captureStartEvent.output;
} else {
otp = '¨M';
}
// raw is raw so it's not changed in any way
globals.metadata.raw = content;
globals.metadata.format = format;
// escape chars forbidden in html attributes
// double quotes
content = content
// ampersand first
.replace(/&/g, '&amp;')
// double quotes
.replace(/"/g, '&quot;')
// Restore dollar signs and tremas
.replace(/¨D/g, '$$')
.replace(/¨T/g, '¨')
// replace multiple spaces
.replace(/\n {4}/g, ' ');
content.replace(/^([\S ]+): +([\s\S]+?)$/gm, function (wm, key, value) {
globals.metadata.parsed[key] = value;
return '';
});
let beforeHashEvent = new showdown.Event('makehtml.metadata.onHash', otp);
beforeHashEvent
.setOutput(otp)
._setGlobals(globals)
._setOptions(options);
beforeHashEvent = globals.converter.dispatch(beforeHashEvent);
otp = beforeHashEvent.output;
if (!otp) {
otp = '¨M';
}
return otp;
}
// 1. Metadata with «««»»» delimiters
const rgx1 = /^\s*«««+\s*(\S*?)\n([\s\S]+?)\n»»»+\s*\n/;
text = text.replace(rgx1, function (wholeMatch, format, content) {
return parseMetadataContents(rgx1, wholeMatch, format, content);
});
// 2. Metadata with YAML delimiters
const rgx2 = /^\s*---+\s*(\S*?)\n([\s\S]+?)\n---+\s*\n/;
text = text.replace(rgx2, function (wholeMatch, format, content) {
return parseMetadataContents(rgx1, wholeMatch, format, content);
});
text = text.replace(/¨M/g, '');
let afterEvent = new showdown.Event('makehtml.metadata.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
/**
*
*/
showdown.subParser('makehtml.paragraphs', function (text, options, globals) {
'use strict';
// Strip leading and trailing lines:
text = text.replace(/^\n+/g, '');
text = text.replace(/\n+$/g, '');
var grafs = text.split(/\n{2,}/g),
grafsOut = [],
end = grafs.length; // Wrap <p> tags
for (var i = 0; i < end; i++) {
var str = grafs[i];
// if this is an HTML marker, copy it
if (str.search(/¨(K|G)(\d+)\1/g) >= 0) {
grafsOut.push(str);
// test for presence of characters to prevent empty lines being parsed
// as paragraphs (resulting in undesired extra empty paragraphs)
} else if (str.search(/\S/) >= 0) {
str = showdown.subParser('makehtml.spanGamut')(str, options, globals);
str = str.replace(/^([ \t]*)/g, '<p>');
str += '</p>';
grafsOut.push(str);
}
}
/** Unhashify HTML blocks */
end = grafsOut.length;
for (i = 0; i < end; i++) {
var blockText = '',
grafsOutIt = grafsOut[i],
codeFlag = false;
// if this is a marker for an html block...
// use RegExp.test instead of string.search because of QML bug
while (/¨([KG])(\d+)\1/.test(grafsOutIt)) {
var delim = RegExp.$1,
num = RegExp.$2;
if (delim === 'K') {
blockText = globals.gHtmlBlocks[num];
} else {
// we need to check if ghBlock is a false positive
if (codeFlag) {
// use encoded version of all text
blockText = showdown.subParser('makehtml.encodeCode')(globals.ghCodeBlocks[num].text, options, globals);
} else {
blockText = globals.ghCodeBlocks[num].codeblock;
}
}
blockText = blockText.replace(/\$/g, '$$$$'); // Escape any dollar signs
grafsOutIt = grafsOutIt.replace(/(\n\n)?¨([KG])\d+\2(\n\n)?/, blockText);
// Check if grafsOutIt is a pre->code
if (/^<pre\b[^>]*>\s*<code\b[^>]*>/.test(grafsOutIt)) {
codeFlag = true;
}
}
grafsOut[i] = grafsOutIt;
}
text = grafsOut.join('\n');
// Strip leading and trailing lines:
text = text.replace(/^\n+/g, '');
text = text.replace(/\n+$/g, '');
return text;
});
/**
* Run extension
*/
showdown.subParser('makehtml.runExtension', function (ext, text, options, globals) {
'use strict';
if (ext.filter) {
text = ext.filter(text, globals.converter, options);
} else if (ext.regex) {
// TODO remove this when old extension loading mechanism is deprecated
let re = ext.regex;
if (!(re instanceof RegExp)) {
re = new RegExp(re, 'g');
}
text = text.replace(re, ext.replace);
}
return text;
});
/**
* These are all the transformations that occur *within* block-level
* tags like paragraphs, headers, and list items.
*/
showdown.subParser('makehtml.spanGamut', function (text, options, globals) {
'use strict';
let startEvent = new showdown.Event('makehtml.spanGamut.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
text = showdown.subParser('makehtml.codeSpan')(text, options, globals);
text = showdown.subParser('makehtml.escapeSpecialCharsWithinTagAttributes')(text, options, globals);
text = showdown.subParser('makehtml.encodeBackslashEscapes')(text, options, globals);
// Process link and image tags. Images must come first,
// because ![foo][f] looks like a link.
text = showdown.subParser('makehtml.image')(text, options, globals);
text = showdown.subParser('makehtml.link')(text, options, globals);
text = showdown.subParser('makehtml.emoji')(text, options, globals);
text = showdown.subParser('makehtml.underline')(text, options, globals);
text = showdown.subParser('makehtml.emphasisAndStrong')(text, options, globals);
text = showdown.subParser('makehtml.strikethrough')(text, options, globals);
text = showdown.subParser('makehtml.ellipsis')(text, options, globals);
// we need to hash HTML tags inside spans
text = showdown.subParser('makehtml.hashHTMLSpans')(text, options, globals);
// now we encode amps and angles
text = showdown.subParser('makehtml.encodeAmpsAndAngles')(text, options, globals);
// Do hard breaks
if (options.simpleLineBreaks) {
// GFM style hard breaks
// only add line breaks if the text does not contain a block (special case for lists)
if (!/\n\n¨K/.test(text)) {
text = text.replace(/\n+/g, '<br />\n');
}
} else {
// Vanilla hard breaks
text = text.replace(/ +\n/g, '<br />\n');
}
let afterEvent = new showdown.Event('makehtml.spanGamut.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
showdown.subParser('makehtml.strikethrough', function (text, options, globals) {
'use strict';
if (!options.strikethrough) {
return text;
}
let startEvent = new showdown.Event('makehtml.strikethrough.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
const strikethroughRegex = /~{2}([\s\S]+?)~{2}/g;
text = text.replace(strikethroughRegex, function (wholeMatch, txt) {
let otp;
let captureStartEvent = new showdown.Event('makehtml.strikethrough.onCapture', txt);
captureStartEvent
.setOutput(null)
._setGlobals(globals)
._setOptions(options)
.setRegexp(strikethroughRegex)
.setMatches({
_wholeMatch: wholeMatch,
strikethrough: txt
})
.setAttributes({});
captureStartEvent = globals.converter.dispatch(captureStartEvent);
// if something was passed as output, it takes precedence
// and will be used as output
if (captureStartEvent.output && captureStartEvent.output !== '') {
otp = captureStartEvent.output;
} else {
otp = '<del' + showdown.helper._populateAttributes(captureStartEvent.attributes) + '>' + txt + '</del>';
}
let beforeHashEvent = new showdown.Event('makehtml.strikethrough.onHash', otp);
beforeHashEvent
.setOutput(otp)
._setGlobals(globals)
._setOptions(options);
beforeHashEvent = globals.converter.dispatch(beforeHashEvent);
return beforeHashEvent.output;
});
let afterEvent = new showdown.Event('makehtml.strikethrough.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
/**
* Strips link definitions from text, stores the URLs and titles in
* hash references.
* Link defs are in the form: ^[id]: url "optional title"
*/
showdown.subParser('makehtml.stripLinkDefinitions', function (text, options, globals) {
'use strict';
const regex = /^ {0,3}\[([^\]]+)]:[ \t]*\n?[ \t]*<?([^>\s]+)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*\n?[ \t]*(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n+|(?=¨0))/gm,
base64Regex = /^ {0,3}\[([^\]]+)]:[ \t]*\n?[ \t]*<?(data:.+?\/.+?;base64,[A-Za-z\d+/=\n]+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*\n?[ \t]*(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n\n|(?=¨0)|(?=\n\[))/gm;
// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
text += '¨0';
let replaceFunc = function (wholeMatch, linkId, url, width, height, blankLines, title) {
// if there aren't two instances of linkId it must not be a reference link so back out
linkId = linkId.toLowerCase();
if (text.toLowerCase().split(linkId).length - 1 < 2) {
return wholeMatch;
}
if (url.match(/^data:.+?\/.+?;base64,/)) {
// remove newlines
globals.gUrls[linkId] = url.replace(/\s/g, '');
} else {
url = showdown.helper.applyBaseUrl(options.relativePathBaseUrl, url);
globals.gUrls[linkId] = showdown.subParser('makehtml.encodeAmpsAndAngles')(url, options, globals); // Link IDs are case-insensitive
}
if (blankLines) {
// Oops, found blank lines, so it's not a title.
// Put back the parenthetical statement we stole.
return blankLines + title;
} else {
if (title) {
globals.gTitles[linkId] = title.replace(/["']/g, '&quot;');
}
if (options.parseImgDimensions && width && height) {
globals.gDimensions[linkId] = {
width: width,
height: height
};
}
}
// Completely remove the definition from the text
return '';
};
// first we try to find base64 link references
text = text.replace(base64Regex, replaceFunc);
text = text.replace(regex, replaceFunc);
// attacklab: strip sentinel
text = text.replace(/¨0/, '');
return text;
});
showdown.subParser('makehtml.table', function (text, options, globals) {
'use strict';
if (!options.tables) {
return text;
}
// find escaped pipe characters
text = text.replace(/\\(\|)/g, showdown.helper.escapeCharactersCallback);
//
// parser starts here
//
let startEvent = new showdown.Event('makehtml.table.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
// parse multi column tables
const tableRgx = /^ {0,3}\|?.+\|.+\n {0,3}\|?[ \t]*:?[ \t]*[-=]{2,}[ \t]*:?[ \t]*\|[ \t]*:?[ \t]*[-=]{2,}[\s\S]+?(?:\n\n|¨0)/gm;
text = text.replace(tableRgx, function (wholeMatch) {
return parse(tableRgx, wholeMatch);
});
const singeColTblRgx = /^ {0,3}\|.+\|[ \t]*\n {0,3}\|[ \t]*:?[ \t]*[-=]{2,}[ \t]*:?[ \t]*\|[ \t]*\n( {0,3}\|.+\|[ \t]*\n)*(?:\n|¨0)/gm;
text = text.replace(singeColTblRgx, function (wholeMatch) {
return parse(singeColTblRgx, wholeMatch);
});
let afterEvent = new showdown.Event('makehtml.table.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
/**
*
* @param {RegExp} pattern
* @param {string} wholeMatch
* @returns {string}
*/
function parse (pattern, wholeMatch) {
let tab = preParse(wholeMatch);
// if parseTable returns null then it's a malformed table so we return what we caught
if (!tab) {
return wholeMatch;
}
// only now we consider it to be a markdown table and start doing stuff
let headers = tab.rawHeaders;
let styles = tab.rawStyles;
let cells = tab.rawCells;
let otp;
let captureStartEvent = new showdown.Event('makehtml.table.onCapture', wholeMatch);
captureStartEvent
.setOutput(null)
._setGlobals(globals)
._setOptions(options)
.setRegexp(pattern)
.setMatches({
_wholeMatch: wholeMatch,
table: wholeMatch
})
.setAttributes({});
captureStartEvent = globals.converter.dispatch(captureStartEvent);
if (captureStartEvent.output && captureStartEvent.output !== '') {
// user provided an otp, so we use it
otp = captureStartEvent.output;
} else {
// user changed matches.table, so we need to generate headers, styles, and cells again
if (captureStartEvent.matches.table !== wholeMatch) {
tab = preParse(captureStartEvent.matches.table);
// user passed a malformed table, so we bail
if (!tab) {
return wholeMatch;
}
headers = tab.rawHeaders;
styles = tab.rawStyles;
cells = tab.rawCells;
}
let parsedTab = parseTable (headers, styles, cells);
let attributes = captureStartEvent.attributes;
otp = buildTableOtp(parsedTab.headers, parsedTab.cells, attributes);
}
let beforeHashEvent = new showdown.Event('makehtml.table.onHash', otp);
beforeHashEvent
.setOutput(otp)
._setGlobals(globals)
._setOptions(options);
beforeHashEvent = globals.converter.dispatch(beforeHashEvent);
otp = beforeHashEvent.output;
return otp;
}
/**
*
* @param {string[]} headers
* @param {string[]} cells
* @param {{}} attributes
* @returns {string}
*/
function buildTableOtp (headers, cells, attributes) {
let otp;
const colCount = headers.length,
rowCount = cells.length;
otp = '<table' + showdown.helper._populateAttributes(attributes) + '>\n<thead>\n<tr>\n';
for (let i = 0; i < colCount; ++i) {
otp += headers[i];
}
otp += '</tr>\n</thead>\n<tbody>\n';
for (let i = 0; i < rowCount; ++i) {
otp += '<tr>\n';
for (let ii = 0; ii < colCount; ++ii) {
otp += cells[i][ii];
}
otp += '</tr>\n';
}
otp += '</tbody>\n</table>\n';
return otp;
}
/**
* @param {string} rawTable
* @returns {{rawHeaders: string[], rawStyles: string[], rawCells: *[]}|null}
*/
function preParse (rawTable) {
let tableLines = rawTable.split('\n');
for (let i = 0; i < tableLines.length; ++i) {
// strip wrong first and last column if wrapped tables are used
if (/^ {0,3}\|/.test(tableLines[i])) {
tableLines[i] = tableLines[i].replace(/^ {0,3}\|/, '');
}
if (/\|[ \t]*$/.test(tableLines[i])) {
tableLines[i] = tableLines[i].replace(/\|[ \t]*$/, '');
}
// parse code spans first, but we only support one line code spans
tableLines[i] = showdown.subParser('makehtml.codeSpan')(tableLines[i], options, globals);
}
let rawHeaders = tableLines[0].split('|').map(function (s) { return s.trim();}),
rawStyles = tableLines[1].split('|').map(function (s) { return s.trim();}),
rawCells = [];
tableLines.shift();
tableLines.shift();
for (let i = 0; i < tableLines.length; ++i) {
if (tableLines[i].trim() === '') {
continue;
}
rawCells.push(
tableLines[i]
.split('|')
.map(function (s) {
return s.trim();
})
);
}
if (rawHeaders.length < rawStyles.length) {
return null;
}
return {
rawHeaders: rawHeaders,
rawStyles: rawStyles,
rawCells: rawCells
};
}
/**
*
* @param {string[]} rawHeaders
* @param {string[]} rawStyles
* @param {string[]} rawCells
* @returns {{headers: *, cells: *}}
*/
function parseTable (rawHeaders, rawStyles, rawCells) {
const headers = [],
cells = [],
styles = [],
colCount = rawHeaders.length,
rowCount = rawCells.length;
for (let i = 0; i < colCount; ++i) {
styles.push(parseStyle(rawStyles[i]));
}
for (let i = 0; i < colCount; ++i) {
let header = rawHeaders[i];
let captureStartEvent = new showdown.Event('makehtml.table.header.onCapture', rawHeaders[i]);
captureStartEvent
.setOutput(null)
._setGlobals(globals)
._setOptions(options)
.setRegexp(null)
.setMatches({
_wholeMatch: rawHeaders[i],
header: rawHeaders[i]
})
.setAttributes(styles[i]);
captureStartEvent = globals.converter.dispatch(captureStartEvent);
if (captureStartEvent.output && captureStartEvent.output !== '') {
// user provided an otp, so we use it
header = captureStartEvent.output;
} else {
header = parseHeader(captureStartEvent.matches.header, styles[i]);
}
let beforeHashEvent = new showdown.Event('makehtml.table.header.onHash', header);
beforeHashEvent
.setOutput(header)
._setGlobals(globals)
._setOptions(options);
beforeHashEvent = globals.converter.dispatch(beforeHashEvent);
header = beforeHashEvent.output;
headers.push(header);
}
for (let i = 0; i < rowCount; ++i) {
let row = [];
for (let ii = 0; ii < colCount; ++ii) {
let cell = (!showdown.helper.isUndefined(rawCells[i][ii])) ? rawCells[i][ii] : '',
attributes = (!showdown.helper.isUndefined(styles[ii])) ? styles[ii] : {};
// since we're reusing attributes, we might need to remove the id that we previously set for headers
if (attributes.id) {
attributes.classes = [attributes.id] + '_col';
delete attributes.id;
}
let captureStartEvent = new showdown.Event('makehtml.table.cell.onCapture', cell);
captureStartEvent
.setOutput(null)
._setGlobals(globals)
._setOptions(options)
.setRegexp(null)
.setMatches({
_wholeMatch: cell,
cell: cell
})
.setAttributes(attributes);
captureStartEvent = globals.converter.dispatch(captureStartEvent);
if (captureStartEvent.output && captureStartEvent.output !== '') {
// user provided an otp, so we use it
cell = captureStartEvent.output;
} else {
attributes = captureStartEvent.attributes;
cell = parseCell(captureStartEvent.matches.cell, attributes);
}
let beforeHashEvent = new showdown.Event('makehtml.table.cell.onHash', cell);
beforeHashEvent
.setOutput(cell)
._setGlobals(globals)
._setOptions(options);
beforeHashEvent = globals.converter.dispatch(beforeHashEvent);
cell = beforeHashEvent.output;
row.push(cell);
}
cells.push(row);
}
return {headers: headers, cells: cells};
}
/**
* @param {string} sLine
* @returns {{}|{style: string}}
*/
function parseStyle (sLine) {
if (/^:[ \t]*-+$/.test(sLine)) {
return { style: 'text-align:left;' };
} else if (/^-+[ \t]*:[ \t]*$/.test(sLine)) {
return { style: 'text-align:right;' };
} else if (/^:[ \t]*-+[ \t]*:$/.test(sLine)) {
return { style: 'text-align:center;' };
} else {
return {};
}
}
/**
*
* @param {string} headerText
* @param {{id: string}} attributes
* @returns {string}
*/
function parseHeader (headerText, attributes) {
headerText = headerText.trim();
headerText = showdown.subParser('makehtml.spanGamut')(headerText, options, globals);
// support both tablesHeaderId and tableHeaderId due to error in documentation so we don't break backwards compatibility
// TODO think about this option!!! and remove backwards compatibility
if (options.tablesHeaderId || options.tableHeaderId) {
attributes.id = headerText.replace(/ /g, '_').toLowerCase();
}
return '<th' + showdown.helper._populateAttributes(attributes) + '>' + headerText + '</th>\n';
}
/**
*
* @param {string} cellText
* @param {{}} attributes
* @returns {string}
*/
function parseCell (cellText, attributes) {
cellText = showdown.subParser('makehtml.spanGamut')(cellText, options, globals);
return '<td' + showdown.helper._populateAttributes(attributes) + '>' + cellText + '</td>\n';
}
});
showdown.subParser('makehtml.underline', function (text, options, globals) {
'use strict';
if (!options.underline) {
return text;
}
let startEvent = new showdown.Event('makehtml.underline.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
if (options.literalMidWordUnderscores) {
const rgx1 = /\b___(\S[\s\S]*?)___\b/g;
text = text.replace(rgx1, function (wm, txt) {
return parse(rgx1, wm, txt);
});
const rgx2 = /\b__(\S[\s\S]*?)__\b/g;
text = text.replace(rgx2, function (wm, txt) {
return parse(rgx2, wm, txt);
});
} else {
const rgx3 = /___(\S[\s\S]*?)___/g;
text = text.replace(rgx3, function (wm, txt) {
if (!(/\S$/.test(txt))) {
return wm;
}
return parse(rgx3, wm, txt);
});
const rgx4 = /__(\S[\s\S]*?)__/g;
text = text.replace(rgx4, function (wm, txt) {
if (!(/\S$/.test(txt))) {
return wm;
}
return parse(rgx4, wm, txt);
});
}
// escape remaining underscores to prevent them being parsed by italic and bold
text = text.replace(/(_)/g, showdown.helper.escapeCharactersCallback);
let afterEvent = new showdown.Event('makehtml.underline.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
function parse (pattern, wholeMatch, txt) {
let otp;
let captureStartEvent = new showdown.Event('makehtml.underline.onCapture', txt);
captureStartEvent
.setOutput(null)
._setGlobals(globals)
._setOptions(options)
.setRegexp(pattern)
.setMatches({
_wholeMatch: wholeMatch,
strikethrough: txt
})
.setAttributes({});
captureStartEvent = globals.converter.dispatch(captureStartEvent);
// if something was passed as output, it takes precedence
// and will be used as output
if (captureStartEvent.output && captureStartEvent.output !== '') {
otp = captureStartEvent.output;
} else {
otp = '<u' + showdown.helper._populateAttributes(captureStartEvent.attributes) + '>' + txt + '</u>';
}
let beforeHashEvent = new showdown.Event('makehtml.underline.onHash', otp);
beforeHashEvent
.setOutput(otp)
._setGlobals(globals)
._setOptions(options);
beforeHashEvent = globals.converter.dispatch(beforeHashEvent);
return beforeHashEvent.output;
}
});
/**
* Swap back in all the special characters we've hidden.
*/
showdown.subParser('makehtml.unescapeSpecialChars', function (text, options, globals) {
'use strict';
let startEvent = new showdown.Event('makehtml.unescapeSpecialChars.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
text = text.replace(/¨E(\d+)E/g, function (wholeMatch, m1) {
let charCodeToReplace = parseInt(m1);
return String.fromCharCode(charCodeToReplace);
});
let afterEvent = new showdown.Event('makehtml.unescapeSpecialChars.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
////
// makehtml/unhashHTMLSpans.js
// Copyright (c) 2018 ShowdownJS
//
// Unhash HTML spans
//
// ***Author:***
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
////
showdown.subParser('makehtml.unhashHTMLSpans', function (text, options, globals) {
'use strict';
let startEvent = new showdown.Event('makehtml.unhashHTMLSpans.onStart', text);
startEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
for (let i = 0; i < globals.gHtmlSpans.length; ++i) {
let repText = globals.gHtmlSpans[i],
// limiter to prevent infinite loop (assume 20 as limit for recurse)
limit = 0;
while (/¨C(\d+)C/.test(repText)) {
let num = repText.match(/¨C(\d+)C/)[1];
repText = repText.replace('¨C' + num + 'C', globals.gHtmlSpans[num]);
if (limit === 10) {
console.error('maximum nesting of 20 spans reached!!!');
break;
}
++limit;
}
text = text.replace('¨C' + i + 'C', repText);
}
let afterEvent = new showdown.Event('makehtml.unhashHTMLSpans.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});
showdown.subParser('makeMarkdown.blockquote', function (node, globals) {
'use strict';
var txt = '';
if (node.hasChildNodes()) {
var children = node.childNodes,
childrenLength = children.length;
for (var i = 0; i < childrenLength; ++i) {
var innerTxt = showdown.subParser('makeMarkdown.node')(children[i], globals);
if (innerTxt === '') {
continue;
}
txt += innerTxt;
}
}
// cleanup
txt = txt.trim();
txt = '> ' + txt.split('\n').join('\n> ');
return txt;
});
showdown.subParser('makeMarkdown.break', function () {
'use strict';
return ' \n';
});
showdown.subParser('makeMarkdown.codeBlock', function (node, globals) {
'use strict';
var lang = node.getAttribute('language'),
num = node.getAttribute('precodenum');
return '```' + lang + '\n' + globals.preList[num] + '\n```';
});
showdown.subParser('makeMarkdown.codeSpan', function (node) {
'use strict';
return '`' + node.innerHTML + '`';
});
showdown.subParser('makeMarkdown.emphasis', function (node, globals) {
'use strict';
var txt = '';
if (node.hasChildNodes()) {
txt += '*';
var children = node.childNodes,
childrenLength = children.length;
for (var i = 0; i < childrenLength; ++i) {
txt += showdown.subParser('makeMarkdown.node')(children[i], globals);
}
txt += '*';
}
return txt;
});
showdown.subParser('makeMarkdown.header', function (node, globals, headerLevel) {
'use strict';
var headerMark = new Array(headerLevel + 1).join('#'),
txt = '';
if (node.hasChildNodes()) {
txt = headerMark + ' ';
var children = node.childNodes,
childrenLength = children.length;
for (var i = 0; i < childrenLength; ++i) {
txt += showdown.subParser('makeMarkdown.node')(children[i], globals);
}
}
return txt;
});
showdown.subParser('makeMarkdown.hr', function () {
'use strict';
return '---';
});
showdown.subParser('makeMarkdown.image', function (node) {
'use strict';
var txt = '';
if (node.hasAttribute('src')) {
txt += '![' + node.getAttribute('alt') + '](';
txt += '<' + node.getAttribute('src') + '>';
if (node.hasAttribute('width') && node.hasAttribute('height')) {
txt += ' =' + node.getAttribute('width') + 'x' + node.getAttribute('height');
}
if (node.hasAttribute('title')) {
txt += ' "' + node.getAttribute('title') + '"';
}
txt += ')';
}
return txt;
});
showdown.subParser('makeMarkdown.input', function (node, globals) {
'use strict';
var txt = '';
if (node.getAttribute('checked') !== null) {
txt += '[x]';
} else {
txt += '[ ]';
}
var children = node.childNodes,
childrenLength = children.length;
for (var i = 0; i < childrenLength; ++i) {
txt += showdown.subParser('makeMarkdown.node')(children[i], globals);
}
return txt;
});
showdown.subParser('makeMarkdown.links', function (node, globals) {
'use strict';
var txt = '';
if (node.hasChildNodes() && node.hasAttribute('href')) {
var children = node.childNodes,
childrenLength = children.length;
txt = '[';
for (var i = 0; i < childrenLength; ++i) {
txt += showdown.subParser('makeMarkdown.node')(children[i], globals);
}
txt += '](';
txt += '<' + node.getAttribute('href') + '>';
if (node.hasAttribute('title')) {
txt += ' "' + node.getAttribute('title') + '"';
}
txt += ')';
}
return txt;
});
showdown.subParser('makeMarkdown.list', function (node, globals, type) {
'use strict';
var txt = '';
if (!node.hasChildNodes()) {
return '';
}
var listItems = node.childNodes,
listItemsLenght = listItems.length,
listNum = node.getAttribute('start') || 1;
for (var i = 0; i < listItemsLenght; ++i) {
if (typeof listItems[i].tagName === 'undefined' || listItems[i].tagName.toLowerCase() !== 'li') {
continue;
}
// define the bullet to use in list
var bullet = '';
if (type === 'ol') {
bullet = listNum.toString() + '. ';
} else {
bullet = '- ';
}
// parse list item
txt += bullet + showdown.subParser('makeMarkdown.listItem')(listItems[i], globals);
++listNum;
}
return txt.trim();
});
showdown.subParser('makeMarkdown.listItem', function (node, globals) {
'use strict';
var listItemTxt = '';
var children = node.childNodes,
childrenLenght = children.length;
for (var i = 0; i < childrenLenght; ++i) {
listItemTxt += showdown.subParser('makeMarkdown.node')(children[i], globals);
}
// if it's only one liner, we need to add a newline at the end
if (!/\n$/.test(listItemTxt)) {
listItemTxt += '\n';
} else {
// it's multiparagraph, so we need to indent
listItemTxt = listItemTxt
.split('\n')
.join('\n ')
.replace(/^ {4}$/gm, '')
.replace(/\n\n+/g, '\n\n');
}
return listItemTxt;
});
showdown.subParser('makeMarkdown.node', function (node, globals, spansOnly) {
'use strict';
spansOnly = spansOnly || false;
var txt = '';
// edge case of text without wrapper paragraph
if (node.nodeType === 3) {
return showdown.subParser('makeMarkdown.txt')(node, globals);
}
// HTML comment
if (node.nodeType === 8) {
return '<!--' + node.data + '-->\n\n';
}
// process only node elements
if (node.nodeType !== 1) {
return '';
}
var tagName = node.tagName.toLowerCase();
switch (tagName) {
//
// BLOCKS
//
case 'h1':
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.header')(node, globals, 1) + '\n\n'; }
break;
case 'h2':
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.header')(node, globals, 2) + '\n\n'; }
break;
case 'h3':
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.header')(node, globals, 3) + '\n\n'; }
break;
case 'h4':
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.header')(node, globals, 4) + '\n\n'; }
break;
case 'h5':
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.header')(node, globals, 5) + '\n\n'; }
break;
case 'h6':
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.header')(node, globals, 6) + '\n\n'; }
break;
case 'p':
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.paragraph')(node, globals) + '\n\n'; }
break;
case 'blockquote':
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.blockquote')(node, globals) + '\n\n'; }
break;
case 'hr':
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.hr')(node, globals) + '\n\n'; }
break;
case 'ol':
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.list')(node, globals, 'ol') + '\n\n'; }
break;
case 'ul':
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.list')(node, globals, 'ul') + '\n\n'; }
break;
case 'precode':
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.codeBlock')(node, globals) + '\n\n'; }
break;
case 'pre':
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.pre')(node, globals) + '\n\n'; }
break;
case 'table':
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.table')(node, globals) + '\n\n'; }
break;
//
// SPANS
//
case 'code':
txt = showdown.subParser('makeMarkdown.codeSpan')(node, globals);
break;
case 'em':
case 'i':
txt = showdown.subParser('makeMarkdown.emphasis')(node, globals);
break;
case 'strong':
case 'b':
txt = showdown.subParser('makeMarkdown.strong')(node, globals);
break;
case 'del':
txt = showdown.subParser('makeMarkdown.strikethrough')(node, globals);
break;
case 'a':
txt = showdown.subParser('makeMarkdown.links')(node, globals);
break;
case 'img':
txt = showdown.subParser('makeMarkdown.image')(node, globals);
break;
case 'br':
txt = showdown.subParser('makeMarkdown.break')(node, globals);
break;
case 'input':
txt = showdown.subParser('makeMarkdown.input')(node, globals);
break;
default:
txt = node.outerHTML + '\n\n';
}
// common normalization
// TODO eventually
return txt;
});
showdown.subParser('makeMarkdown.paragraph', function (node, globals) {
'use strict';
var txt = '';
if (node.hasChildNodes()) {
var children = node.childNodes,
childrenLength = children.length;
for (var i = 0; i < childrenLength; ++i) {
txt += showdown.subParser('makeMarkdown.node')(children[i], globals);
}
}
// some text normalization
txt = txt.trim();
return txt;
});
showdown.subParser('makeMarkdown.pre', function (node, globals) {
'use strict';
var num = node.getAttribute('prenum');
return '<pre>' + globals.preList[num] + '</pre>';
});
showdown.subParser('makeMarkdown.strikethrough', function (node, globals) {
'use strict';
var txt = '';
if (node.hasChildNodes()) {
txt += '~~';
var children = node.childNodes,
childrenLength = children.length;
for (var i = 0; i < childrenLength; ++i) {
txt += showdown.subParser('makeMarkdown.node')(children[i], globals);
}
txt += '~~';
}
return txt;
});
showdown.subParser('makeMarkdown.strong', function (node, globals) {
'use strict';
var txt = '';
if (node.hasChildNodes()) {
txt += '**';
var children = node.childNodes,
childrenLength = children.length;
for (var i = 0; i < childrenLength; ++i) {
txt += showdown.subParser('makeMarkdown.node')(children[i], globals);
}
txt += '**';
}
return txt;
});
showdown.subParser('makeMarkdown.table',
/**
*
* @param {DocumentFragment} node
* @param {{}} globals
* @returns {string}
*/
function (node, globals) {
'use strict';
var txt = '',
tableArray = [[], []],
headings,
rows = [],
colCount,
i,
ii;
/**
* @param {Element} tr
*/
function iterateRow (tr) {
var children = tr.childNodes,
cols = [];
// we need to iterate by order, since td and th can be used interchangeably and in any order
// we will ignore malformed stuff, comments and floating text.
for (var i = 0; i < children.length; ++i) {
var childName = children[i].nodeName.toUpperCase();
if (childName === 'TD' || childName === 'TH') {
cols.push(children[i]);
}
}
return cols;
}
// first lets look for <thead>
// we will ignore thead without <tr> children
// also, since markdown doesn't support tables with multiple heading rows, only the first one will be transformed
// the rest will count as regular rows
if (node.querySelectorAll(':scope>thead').length !== 0 && node.querySelectorAll(':scope>thead>tr').length !== 0) {
var thead = node.querySelectorAll(':scope>thead>tr');
// thead>tr can have td and th children
for (i = 0; i < thead.length; ++i) {
rows.push(iterateRow(thead[i]));
}
}
// now let's look for tbody
// we will ignore tbody without <tr> children
if (node.querySelectorAll(':scope>tbody').length !== 0 && node.querySelectorAll(':scope>tbody>tr').length !== 0) {
var tbody = node.querySelectorAll(':scope>tbody>tr');
// tbody>tr can have td and th children, although th are not very screen reader friendly
for (i = 0; i < tbody.length; ++i) {
rows.push(iterateRow(tbody[i]));
}
}
// now look for tfoot
if (node.querySelectorAll(':scope>tfoot').length !== 0 && node.querySelectorAll(':scope>tfoot>tr').length !== 0) {
var tfoot = node.querySelectorAll(':scope>tfoot>tr');
// tfoot>tr can have td and th children, although th are not very screen reader friendly
for (i = 0; i < tfoot.length; ++i) {
rows.push(iterateRow(tfoot[i]));
}
}
// lastly look for naked tr
if (node.querySelectorAll(':scope>tr').length !== 0) {
var tr = node.querySelectorAll(':scope>tr');
// tfoot>tr can have td and th children, although th are not very screen reader friendly
for (i = 0; i < tr.length; ++i) {
rows.push(iterateRow(tr[i]));
}
}
// TODO: implement <caption> in tables https://developer.mozilla.org/pt-BR/docs/Web/HTML/Element/caption
// note: <colgroup> is ignored, since they are basically styling
// we need now to account for cases of completely empty tables, like <table></table> or equivalent
if (rows.length === 0) {
// table is empty, return empty text
return txt;
}
// count the first row. We need it to trim the table (if table rows have inconsistent number of columns)
colCount = rows[0].length;
// let's shift the first row as a heading
headings = rows.shift();
for (i = 0; i < headings.length; ++i) {
var headContent = showdown.subParser('makeMarkdown.tableCell')(headings[i], globals),
align = '---';
if (headings[i].hasAttribute('style')) {
var style = headings[i].getAttribute('style').toLowerCase().replace(/\s/g, '');
switch (style) {
case 'text-align:left;':
align = ':---';
break;
case 'text-align:right;':
align = '---:';
break;
case 'text-align:center;':
align = ':---:';
break;
}
}
tableArray[0][i] = headContent.trim();
tableArray[1][i] = align;
}
// now iterate through the rows and create the pseudo output (not pretty yet)
for (i = 0; i < rows.length; ++i) {
var r = tableArray.push([]) - 1;
for (ii = 0; ii < colCount; ++ii) {
var cellContent = ' ';
if (typeof rows[i][ii] !== 'undefined') {
// Note: if rows[i][ii] is undefined, it means the row has fewer elements than the header,
// and empty content will be added
cellContent = showdown.subParser('makeMarkdown.tableCell')(rows[i][ii], globals);
}
tableArray[r].push(cellContent);
}
}
// now tidy up the output, aligning cells and stuff
var cellSpacesCount = 3;
for (i = 0; i < tableArray.length; ++i) {
for (ii = 0; ii < tableArray[i].length; ++ii) {
var strLen = tableArray[i][ii].length;
if (strLen > cellSpacesCount) {
cellSpacesCount = strLen;
}
}
}
for (i = 0; i < tableArray.length; ++i) {
for (ii = 0; ii < tableArray[i].length; ++ii) {
if (i === 1) {
if (tableArray[i][ii].slice(-1) === ':') {
tableArray[i][ii] = showdown.helper.padEnd(tableArray[i][ii].slice(0, -1), cellSpacesCount - 1, '-') + ':';
} else {
tableArray[i][ii] = showdown.helper.padEnd(tableArray[i][ii], cellSpacesCount, '-');
}
} else {
tableArray[i][ii] = showdown.helper.padEnd(tableArray[i][ii], cellSpacesCount);
}
}
txt += '| ' + tableArray[i].join(' | ') + ' |\n';
}
return txt.trim();
}
);
showdown.subParser('makeMarkdown.tableCell', function (node, globals) {
'use strict';
var txt = '';
if (!node.hasChildNodes()) {
return '';
}
var children = node.childNodes,
childrenLength = children.length;
for (var i = 0; i < childrenLength; ++i) {
txt += showdown.subParser('makeMarkdown.node')(children[i], globals, true);
}
return txt.trim();
});
showdown.subParser('makeMarkdown.txt', function (node) {
'use strict';
var txt = node.nodeValue;
// multiple spaces are collapsed
txt = txt.replace(/ +/g, ' ');
// replace the custom ¨NBSP; with a space
txt = txt.replace(/¨NBSP;/g, ' ');
// ", <, > and & should replace escaped html entities
txt = showdown.helper.unescapeHTMLEntities(txt);
// escape markdown magic characters
// emphasis, strong and strikethrough - can appear everywhere
// we also escape pipe (|) because of tables
// and escape ` because of code blocks and spans
txt = txt.replace(/([*_~|`])/g, '\\$1');
// escape > because of blockquotes
txt = txt.replace(/^(\s*)>/g, '\\$1>');
// hash character, only troublesome at the beginning of a line because of headers
txt = txt.replace(/^#/gm, '\\#');
// horizontal rules
txt = txt.replace(/^(\s*)([-=]{3,})(\s*)$/, '$1\\$2$3');
// dot, because of ordered lists, only troublesome at the beginning of a line when preceded by an integer
txt = txt.replace(/^( {0,3}\d+)\./gm, '$1\\.');
// +, * and -, at the beginning of a line becomes a list, so we need to escape them also (asterisk was already escaped)
txt = txt.replace(/^( {0,3})([+-])/gm, '$1\\$2');
// images and links, ] followed by ( is problematic, so we escape it
txt = txt.replace(/]([\s]*)\(/g, '\\]$1\\(');
// reference URIs must also be escaped
txt = txt.replace(/^ {0,3}\[([\S \t]*?)]:/gm, '\\[$1]:');
return txt;
});
/**
* Created by Estevao on 31-05-2015.
*/
/**
* Showdown Converter class
* @class
* @param {object} [converterOptions]
* @returns {Converter}
*/
showdown.Converter = function (converterOptions) {
'use strict';
let
/**
* Options used by this converter
* @private
* @type {{}}
*/
options = {},
/**
* Language extensions used by this converter
* @private
* @type {Array}
*/
langExtensions = [],
/**
* Output modifiers extensions used by this converter
* @private
* @type {Array}
*/
outputModifiers = [],
/**
* Event listeners
* @private
* @type {{}}
*/
listeners = {},
/**
* The flavor set in this converter
*/
setConvFlavor = setFlavor,
/**
* Metadata of the document
* @type {{parsed: {}, raw: string, format: string}}
*/
metadata = {
parsed: {},
raw: '',
format: ''
};
_constructor();
/**
* Converter constructor
* @private
*/
function _constructor () {
converterOptions = converterOptions || {};
for (let gOpt in globalOptions) {
if (globalOptions.hasOwnProperty(gOpt)) {
options[gOpt] = globalOptions[gOpt];
}
}
// Merge options
if (typeof converterOptions === 'object') {
for (let opt in converterOptions) {
if (converterOptions.hasOwnProperty(opt)) {
options[opt] = converterOptions[opt];
}
}
} else {
throw Error('Converter expects the passed parameter to be an object, but ' + typeof converterOptions +
' was passed instead.');
}
if (options.extensions) {
showdown.helper.forEach(options.extensions, _parseExtension);
}
options = showdown.helper.validateOptions(options);
}
/**
* Parse extension
* @param {*} ext
* @param {string} [name='']
* @private
*/
function _parseExtension (ext, name) {
name = name || null;
// If it's a string, the extension was previously loaded
if (showdown.helper.isString(ext)) {
ext = showdown.helper.stdExtName(ext);
name = ext;
// LEGACY_SUPPORT CODE
if (showdown.extensions[ext]) {
console.warn('DEPRECATION WARNING: ' + ext + ' is an old extension that uses a deprecated loading method.' +
'Please inform the developer that the extension should be updated!');
legacyExtensionLoading(showdown.extensions[ext], ext);
return;
// END LEGACY SUPPORT CODE
} else if (!showdown.helper.isUndefined(extensions[ext])) {
ext = extensions[ext];
} else {
throw Error('Extension "' + ext + '" could not be loaded. It was either not found or is not a valid extension.');
}
}
if (typeof ext === 'function') {
ext = ext();
}
if (!showdown.helper.isArray(ext)) {
ext = [ext];
}
var validExt = validate(ext, name);
if (!validExt.valid) {
throw Error(validExt.error);
}
for (var i = 0; i < ext.length; ++i) {
switch (ext[i].type) {
case 'lang':
langExtensions.push(ext[i]);
break;
case 'output':
outputModifiers.push(ext[i]);
break;
}
if (ext[i].hasOwnProperty('listeners')) {
for (var ln in ext[i].listeners) {
if (ext[i].listeners.hasOwnProperty(ln)) {
listen(ln, ext[i].listeners[ln]);
}
}
}
}
}
/**
* LEGACY_SUPPORT
* @param {*} ext
* @param {string} name
*/
function legacyExtensionLoading (ext, name) {
if (typeof ext === 'function') {
ext = ext(new showdown.Converter());
}
if (!showdown.helper.isArray(ext)) {
ext = [ext];
}
var valid = validate(ext, name);
if (!valid.valid) {
throw Error(valid.error);
}
for (var i = 0; i < ext.length; ++i) {
switch (ext[i].type) {
case 'lang':
langExtensions.push(ext[i]);
break;
case 'output':
outputModifiers.push(ext[i]);
break;
default:// should never reach here
throw Error('Extension loader error: Type unrecognized!!!');
}
}
}
/**
* Listen to an event
* @param {string} name
* @param {function} callback
*/
function listen (name, callback) {
if (!showdown.helper.isString(name)) {
throw Error('Invalid argument in converter.listen() method: name must be a string, but ' + typeof name + ' given');
}
if (typeof callback !== 'function') {
throw Error('Invalid argument in converter.listen() method: callback must be a function, but ' + typeof callback + ' given');
}
name = name.toLowerCase();
if (!listeners.hasOwnProperty(name)) {
listeners[name] = [];
}
listeners[name].push(callback);
}
function rTrimInputText (text) {
let rsp = text.match(/^\s*/)[0].length,
rgx = new RegExp('^\\s{0,' + rsp + '}', 'gm');
return text.replace(rgx, '');
}
/**
*
* @param {showdown.Event} event
* @returns showdown.Event
*/
this.dispatch = function (event) {
if (!(event instanceof showdown.Event)) {
throw new TypeError('dispatch only accepts showdown.Event objects as param, but ' + typeof event + ' given');
}
event.converter = this;
if (listeners.hasOwnProperty(event.name)) {
for (let i = 0; i < listeners[event.name].length; ++i) {
let listRet = listeners[event.name][i](event);
if (showdown.helper.isString(listRet)) {
event.output = listRet;
event.input = listRet;
} else if (listRet instanceof showdown.Event && listRet.name === event.name) {
event = listRet;
}
}
}
return event;
};
/**
* Listen to an event
* @param {string} name
* @param {function} callback
* @returns {showdown.Converter}
*/
this.listen = function (name, callback) {
listen(name, callback);
return this;
};
/**
* Converts a markdown string into HTML string
* @param {string} text
* @returns {*}
*/
this.makeHtml = function (text) {
//check if text is not falsy
if (!text) {
return text;
}
var globals = {
gHtmlBlocks: [],
gHtmlMdBlocks: [],
gHtmlSpans: [],
gUrls: {},
gTitles: {},
gDimensions: {},
gListLevel: 0,
hashLinkCounts: {},
langExtensions: langExtensions,
outputModifiers: outputModifiers,
converter: this,
ghCodeBlocks: [],
metadata: {
parsed: {},
raw: '',
format: ''
}
};
// This lets us use ¨ trema as an escape char to avoid md5 hashes
// The choice of character is arbitrary; anything that isn't
// magic in Markdown will work.
text = text.replace(/¨/g, '¨T');
// Replace $ with ¨D
// RegExp interprets $ as a special character
// when it's in a replacement string
text = text.replace(/\$/g, '¨D');
// Standardize line endings
text = text.replace(/\r\n/g, '\n'); // DOS to Unix
text = text.replace(/\r/g, '\n'); // Mac to Unix
// Stardardize line spaces
text = text.replace(/\u00A0/g, '&nbsp;');
if (options.smartIndentationFix) {
text = rTrimInputText(text);
}
// Make sure text begins and ends with a couple of newlines:
text = '\n\n' + text + '\n\n';
// detab
text = showdown.subParser('makehtml.detab')(text, options, globals);
/**
* Strip any lines consisting only of spaces and tabs.
* This makes subsequent regexs easier to write, because we can
* match consecutive blank lines with /\n+/ instead of something
* contorted like /[ \t]*\n+/
*/
text = text.replace(/^[ \t]+$/mg, '');
//run languageExtensions
showdown.helper.forEach(langExtensions, function (ext) {
text = showdown.subParser('makehtml.runExtension')(ext, text, options, globals);
});
// run the sub parsers
text = showdown.subParser('makehtml.metadata')(text, options, globals);
text = showdown.subParser('makehtml.hashPreCodeTags')(text, options, globals);
text = showdown.subParser('makehtml.githubCodeBlock')(text, options, globals);
text = showdown.subParser('makehtml.hashHTMLBlocks')(text, options, globals);
text = showdown.subParser('makehtml.hashCodeTags')(text, options, globals);
text = showdown.subParser('makehtml.stripLinkDefinitions')(text, options, globals);
text = showdown.subParser('makehtml.blockGamut')(text, options, globals);
text = showdown.subParser('makehtml.unhashHTMLSpans')(text, options, globals);
text = showdown.subParser('makehtml.unescapeSpecialChars')(text, options, globals);
// attacklab: Restore dollar signs
text = text.replace(/¨D/g, '$$');
// attacklab: Restore tremas
text = text.replace(/¨T/g, '¨');
// render a complete html document instead of a partial if the option is enabled
text = showdown.subParser('makehtml.completeHTMLDocument')(text, options, globals);
// Run output modifiers
showdown.helper.forEach(outputModifiers, function (ext) {
text = showdown.subParser('makehtml.runExtension')(ext, text, options, globals);
});
// update metadata
metadata = globals.metadata;
return text;
};
/**
* Converts an HTML string into a markdown string
* @param src
* @returns {string}
*/
this.makeMarkdown = function (src) {
// replace \r\n with \n
src = src.replace(/\r\n/g, '\n');
src = src.replace(/\r/g, '\n'); // old macs
// due to an edge case, we need to find this: > <
// to prevent removing of non silent white spaces
// ex: <em>this is</em> <strong>sparta</strong>
src = src.replace(/>[ \t]+</, '>¨NBSP;<');
var doc = showdown.helper.document.createElement('div');
doc.innerHTML = src;
var globals = {
preList: substitutePreCodeTags(doc)
};
// remove all newlines and collapse spaces
clean(doc);
// some stuff, like accidental reference links must now be escaped
// TODO
// doc.innerHTML = doc.innerHTML.replace(/\[[\S\t ]]/);
var nodes = doc.childNodes,
mdDoc = '';
for (var i = 0; i < nodes.length; i++) {
mdDoc += showdown.subParser('makeMarkdown.node')(nodes[i], globals);
}
function clean (node) {
for (var n = 0; n < node.childNodes.length; ++n) {
var child = node.childNodes[n];
if (child.nodeType === 3) {
if (!/\S/.test(child.nodeValue) && !/^[ ]+$/.test(child.nodeValue)) {
node.removeChild(child);
--n;
} else {
child.nodeValue = child.nodeValue.split('\n').join(' ');
child.nodeValue = child.nodeValue.replace(/(\s)+/g, '$1');
}
} else if (child.nodeType === 1) {
clean(child);
}
}
}
// find all pre tags and replace contents with placeholder
// we need this so that we can remove all indentation from html
// to ease up parsing
function substitutePreCodeTags (doc) {
var pres = doc.querySelectorAll('pre'),
presPH = [];
for (var i = 0; i < pres.length; ++i) {
if (pres[i].childElementCount === 1 && pres[i].firstChild.tagName.toLowerCase() === 'code') {
var content = pres[i].firstChild.innerHTML.trim(),
language = pres[i].firstChild.getAttribute('data-language') || '';
// if data-language attribute is not defined, then we look for class language-*
if (language === '') {
var classes = pres[i].firstChild.className.split(' ');
for (var c = 0; c < classes.length; ++c) {
var matches = classes[c].match(/^language-(.+)$/);
if (matches !== null) {
language = matches[1];
break;
}
}
}
// unescape html entities in content
content = showdown.helper.unescapeHTMLEntities(content);
presPH.push(content);
pres[i].outerHTML = '<precode language="' + language + '" precodenum="' + i.toString() + '"></precode>';
} else {
presPH.push(pres[i].innerHTML);
pres[i].innerHTML = '';
pres[i].setAttribute('prenum', i.toString());
}
}
return presPH;
}
return mdDoc;
};
/**
* Set an option of this Converter instance
* @param {string} key
* @param {*} value
*/
this.setOption = function (key, value) {
options[key] = value;
};
/**
* Get the option of this Converter instance
* @param {string} key
* @returns {*}
*/
this.getOption = function (key) {
return options[key];
};
/**
* Get the options of this Converter instance
* @returns {{}}
*/
this.getOptions = function () {
return options;
};
/**
* Add extension to THIS converter
* @param {{}} extension
* @param {string} [name=null]
*/
this.addExtension = function (extension, name) {
name = name || null;
_parseExtension(extension, name);
};
/**
* Use a global registered extension with THIS converter
* @param {string} extensionName Name of the previously registered extension
*/
this.useExtension = function (extensionName) {
_parseExtension(extensionName);
};
/**
* Set the flavor THIS converter should use
* @param {string} name
*/
this.setFlavor = function (name) {
if (!flavor.hasOwnProperty(name)) {
throw Error(name + ' flavor was not found');
}
var preset = flavor[name];
setConvFlavor = name;
for (var option in preset) {
if (preset.hasOwnProperty(option)) {
options[option] = preset[option];
}
}
};
/**
* Get the currently set flavor of this converter
* @returns {string}
*/
this.getFlavor = function () {
return setConvFlavor;
};
/**
* Remove an extension from THIS converter.
* Note: This is a costly operation. It's better to initialize a new converter
* and specify the extensions you wish to use
* @param {Array} extension
*/
this.removeExtension = function (extension) {
if (!showdown.helper.isArray(extension)) {
extension = [extension];
}
for (var a = 0; a < extension.length; ++a) {
var ext = extension[a];
for (var i = 0; i < langExtensions.length; ++i) {
if (langExtensions[i] === ext) {
langExtensions.splice(i, 1);
}
}
for (var ii = 0; ii < outputModifiers.length; ++ii) {
if (outputModifiers[ii] === ext) {
outputModifiers.splice(ii, 1);
}
}
}
};
/**
* Get all extension of THIS converter
* @returns {{language: Array, output: Array}}
*/
this.getAllExtensions = function () {
return {
language: langExtensions,
output: outputModifiers
};
};
/**
* Get the metadata of the previously parsed document
* @param raw
* @returns {string|{}}
*/
this.getMetadata = function (raw) {
if (raw) {
return metadata.raw;
} else {
return metadata.parsed;
}
};
/**
* Get the metadata format of the previously parsed document
* @returns {string}
*/
this.getMetadataFormat = function () {
return metadata.format;
};
/**
* Private: set a single key, value metadata pair
* @param {string} key
* @param {string} value
*/
this._setMetadataPair = function (key, value) {
metadata.parsed[key] = value;
};
/**
* Private: set metadata format
* @param {string} format
*/
this._setMetadataFormat = function (format) {
metadata.format = format;
};
/**
* Private: set metadata raw text
* @param {string} raw
*/
this._setMetadataRaw = function (raw) {
metadata.raw = raw;
};
};
var root = this;
// AMD Loader
if (typeof define === 'function' && define.amd) {
define(function () {
'use strict';
return showdown;
});
// CommonJS/nodeJS Loader
} else if (typeof module !== 'undefined' && module.exports) {
module.exports = showdown;
// Regular Browser loader
} else {
root.showdown = showdown;
}
}).call(this);
//# sourceMappingURL=showdown.js.map