2015-01-16 05:21:33 +08:00
|
|
|
/**
|
|
|
|
* Created by Tivie on 06-01-2015.
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Private properties
|
|
|
|
var showdown = {},
|
|
|
|
parsers = {},
|
2015-03-02 02:15:32 +08:00
|
|
|
extensions = {},
|
2015-01-16 05:21:33 +08:00
|
|
|
globalOptions = {
|
2015-01-19 19:37:21 +08:00
|
|
|
omitExtraWLInCodeBlocks: false,
|
2015-03-02 02:15:32 +08:00
|
|
|
prefixHeaderId: false
|
2015-01-16 05:21:33 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* helper namespace
|
|
|
|
* @type {{}}
|
|
|
|
*/
|
|
|
|
showdown.helper = {};
|
|
|
|
|
|
|
|
// Public properties
|
|
|
|
showdown.extensions = {};
|
|
|
|
|
2015-01-20 00:28:14 +08:00
|
|
|
/**
|
|
|
|
* Set a global option
|
2015-03-02 02:15:32 +08:00
|
|
|
* @static
|
2015-01-20 00:28:14 +08:00
|
|
|
* @param {string} key
|
|
|
|
* @param {string} value
|
|
|
|
* @returns {showdown}
|
|
|
|
*/
|
2015-01-16 05:21:33 +08:00
|
|
|
showdown.setOption = function (key, value) {
|
2015-01-19 19:37:21 +08:00
|
|
|
'use strict';
|
|
|
|
globalOptions[key] = value;
|
|
|
|
return this;
|
2015-01-16 05:21:33 +08:00
|
|
|
};
|
|
|
|
|
2015-01-20 00:28:14 +08:00
|
|
|
/**
|
|
|
|
* Get a global option
|
2015-03-02 02:15:32 +08:00
|
|
|
* @static
|
2015-01-20 00:28:14 +08:00
|
|
|
* @param {string} key
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
2015-01-18 10:12:32 +08:00
|
|
|
showdown.getOption = function (key) {
|
2015-01-19 19:37:21 +08:00
|
|
|
'use strict';
|
|
|
|
return globalOptions[key];
|
2015-01-18 10:12:32 +08:00
|
|
|
};
|
|
|
|
|
2015-01-20 00:28:14 +08:00
|
|
|
/**
|
|
|
|
* Get the global options
|
2015-03-02 02:15:32 +08:00
|
|
|
* @static
|
2015-01-20 00:28:14 +08:00
|
|
|
* @returns {{omitExtraWLInCodeBlocks: boolean, prefixHeaderId: boolean}}
|
|
|
|
*/
|
2015-01-18 10:12:32 +08:00
|
|
|
showdown.getOptions = function () {
|
2015-01-19 19:37:21 +08:00
|
|
|
'use strict';
|
|
|
|
return globalOptions;
|
2015-01-18 10:12:32 +08:00
|
|
|
};
|
|
|
|
|
2015-01-16 05:21:33 +08:00
|
|
|
/**
|
2015-01-20 00:28:14 +08:00
|
|
|
* Get or set a subParser
|
2015-01-16 05:21:33 +08:00
|
|
|
*
|
|
|
|
* subParser(name) - Get a registered subParser
|
|
|
|
* subParser(name, func) - Register a subParser
|
2015-03-02 02:15:32 +08:00
|
|
|
* @static
|
2015-01-16 05:21:33 +08:00
|
|
|
* @param {string} name
|
|
|
|
* @param {function} [func]
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
|
|
|
showdown.subParser = function (name, func) {
|
2015-01-19 19:37:21 +08:00
|
|
|
'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!');
|
|
|
|
}
|
2015-01-16 05:21:33 +08:00
|
|
|
}
|
2015-01-19 19:37:21 +08:00
|
|
|
}
|
2015-01-16 05:21:33 +08:00
|
|
|
};
|
|
|
|
|
2015-03-02 02:15:32 +08:00
|
|
|
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);
|
|
|
|
|
|
|
|
if (showdown.helper.isUndefined(ext)) {
|
|
|
|
return getExtension();
|
|
|
|
} else {
|
|
|
|
return setExtension();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
function getExtension(name) {
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
if (!extensions.hasOwnProperty(name)) {
|
|
|
|
throw Error('Extension named ' + name + ' is not registered!');
|
|
|
|
}
|
|
|
|
return extensions[name];
|
|
|
|
}
|
|
|
|
|
|
|
|
function setExtension(name, ext) {
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
if (typeof ext !== 'object') {
|
|
|
|
throw Error('A Showdown Extension must be an object, ' + typeof ext + ' given');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!showdown.helper.isString(ext.type)) {
|
|
|
|
throw Error('When registering a showdown extension, "type" must be a string, ' + typeof ext.type + ' given');
|
|
|
|
}
|
|
|
|
|
|
|
|
ext.type = ext.type.toLowerCase();
|
|
|
|
|
|
|
|
extensions[name] = ext;
|
|
|
|
}
|
|
|
|
|
2015-01-16 05:21:33 +08:00
|
|
|
/**
|
2015-01-20 00:28:14 +08:00
|
|
|
* Showdown Converter class
|
2015-01-16 05:21:33 +08:00
|
|
|
*
|
|
|
|
* @param {object} [converterOptions]
|
|
|
|
* @returns {{makeHtml: Function}}
|
|
|
|
*/
|
|
|
|
showdown.Converter = function (converterOptions) {
|
2015-01-19 19:37:21 +08:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
converterOptions = converterOptions || {};
|
|
|
|
|
2015-05-27 02:46:07 +08:00
|
|
|
var options = {},
|
2015-03-02 02:15:32 +08:00
|
|
|
langExtensions = [],
|
|
|
|
outputModifiers = [],
|
2015-01-19 19:37:21 +08:00
|
|
|
parserOrder = [
|
|
|
|
'githubCodeBlocks',
|
|
|
|
'hashHTMLBlocks',
|
|
|
|
'stripLinkDefinitions',
|
|
|
|
'blockGamut',
|
|
|
|
'unescapeSpecialChars'
|
|
|
|
];
|
|
|
|
|
2015-05-27 02:46:07 +08:00
|
|
|
for (var gOpt in globalOptions) {
|
|
|
|
if (globalOptions.hasOwnProperty(gOpt)) {
|
|
|
|
options[gOpt] = globalOptions[gOpt];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-19 19:37:21 +08:00
|
|
|
// Merge options
|
|
|
|
if (typeof converterOptions === 'object') {
|
|
|
|
for (var opt in converterOptions) {
|
|
|
|
if (converterOptions.hasOwnProperty(opt)) {
|
|
|
|
options[opt] = converterOptions[opt];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-01-16 05:21:33 +08:00
|
|
|
|
2015-04-23 07:15:54 +08:00
|
|
|
// This is a dirty workaround to maintain backwards extension compatibility
|
|
|
|
// We define a self var (which is a copy of this) and inject the makeHtml function
|
|
|
|
// directly to it. This ensures a full converter object is available when iterating over extensions
|
|
|
|
// We should rewrite the extension loading mechanism and use some kind of interface or decorator pattern
|
|
|
|
// and inject the object reference there instead.
|
|
|
|
var self = this;
|
|
|
|
self.makeHtml = makeHtml;
|
|
|
|
|
2015-03-02 02:15:32 +08:00
|
|
|
// Parse options
|
|
|
|
if (options.extensions) {
|
|
|
|
|
|
|
|
// Iterate over each plugin
|
|
|
|
showdown.helper.forEach(options.extensions, function (plugin) {
|
2015-04-22 23:58:07 +08:00
|
|
|
var pluginName = plugin;
|
2015-03-02 02:15:32 +08:00
|
|
|
|
|
|
|
// Assume it's a bundled plugin if a string is given
|
|
|
|
if (typeof plugin === 'string') {
|
2015-04-22 23:58:07 +08:00
|
|
|
var tPluginName = showdown.helper.stdExtName(plugin);
|
|
|
|
|
|
|
|
if (!showdown.helper.isUndefined(showdown.extensions[tPluginName]) && showdown.extensions[tPluginName]) {
|
|
|
|
//Trigger some kind of deprecated alert
|
|
|
|
plugin = showdown.extensions[tPluginName];
|
|
|
|
|
|
|
|
} else if (!showdown.helper.isUndefined(extensions[tPluginName])) {
|
|
|
|
plugin = extensions[tPluginName];
|
|
|
|
}
|
2015-03-02 02:15:32 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof plugin === 'function') {
|
|
|
|
// Iterate over each extension within that plugin
|
|
|
|
showdown.helper.forEach(plugin(self), function (ext) {
|
|
|
|
// Sort extensions by type
|
|
|
|
if (ext.type) {
|
|
|
|
if (ext.type === 'language' || ext.type === 'lang') {
|
|
|
|
langExtensions.push(ext);
|
|
|
|
} else if (ext.type === 'output' || ext.type === 'html') {
|
|
|
|
outputModifiers.push(ext);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Assume language extension
|
|
|
|
outputModifiers.push(ext);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
2015-04-22 23:58:07 +08:00
|
|
|
var errMsg = 'An extension could not be loaded. It was either not found or is not a valid extension.';
|
|
|
|
if (typeof pluginName === 'string') {
|
|
|
|
errMsg = 'Extension "' + pluginName + '" could not be loaded. It was either not found or is not a valid extension.';
|
|
|
|
}
|
|
|
|
throw errMsg;
|
2015-03-02 02:15:32 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-01-20 00:28:14 +08:00
|
|
|
/**
|
|
|
|
* Converts a markdown string into HTML
|
|
|
|
* @param {string} text
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
|
|
|
function makeHtml(text) {
|
2015-01-19 19:37:21 +08:00
|
|
|
|
|
|
|
//check if text is not falsy
|
|
|
|
if (!text) {
|
|
|
|
return text;
|
2015-01-16 05:21:33 +08:00
|
|
|
}
|
|
|
|
|
2015-01-19 19:37:21 +08:00
|
|
|
var globals = {
|
2015-03-02 02:15:32 +08:00
|
|
|
gHtmlBlocks: [],
|
|
|
|
gUrls: {},
|
|
|
|
gTitles: {},
|
|
|
|
gListLevel: 0,
|
|
|
|
hashLinkCounts: {},
|
|
|
|
langExtensions: langExtensions,
|
|
|
|
outputModifiers: outputModifiers
|
2015-01-16 05:21:33 +08:00
|
|
|
};
|
|
|
|
|
2015-01-19 19:37:21 +08:00
|
|
|
// attacklab: Replace ~ with ~T
|
|
|
|
// This lets us use tilde 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');
|
2015-01-16 05:21:33 +08:00
|
|
|
|
2015-01-19 19:37:21 +08:00
|
|
|
// attacklab: 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
|
|
|
|
|
|
|
|
// Make sure text begins and ends with a couple of newlines:
|
|
|
|
text = '\n\n' + text + '\n\n';
|
|
|
|
|
2015-03-02 02:15:32 +08:00
|
|
|
// detab
|
|
|
|
text = parsers.detab(text, options, globals);
|
|
|
|
|
|
|
|
// stripBlankLines
|
|
|
|
text = parsers.stripBlankLines(text, options, globals);
|
|
|
|
|
|
|
|
//run languageExtensions
|
|
|
|
text = parsers.languageExtensions(text, options, globals);
|
|
|
|
|
2015-01-19 19:37:21 +08:00
|
|
|
// Run all registered parsers
|
|
|
|
for (var i = 0; i < parserOrder.length; ++i) {
|
|
|
|
var name = parserOrder[i];
|
|
|
|
text = parsers[name](text, options, globals);
|
|
|
|
}
|
|
|
|
|
|
|
|
// attacklab: Restore dollar signs
|
|
|
|
text = text.replace(/~D/g, '$$');
|
|
|
|
|
|
|
|
// attacklab: Restore tildes
|
|
|
|
text = text.replace(/~T/g, '~');
|
|
|
|
|
|
|
|
// Run output modifiers
|
2015-04-22 21:51:57 +08:00
|
|
|
showdown.helper.forEach(globals.outputModifiers, function (ext) {
|
|
|
|
text = showdown.subParser('runExtension')(ext, text);
|
|
|
|
});
|
2015-03-02 02:15:32 +08:00
|
|
|
text = parsers.outputModifiers(text, options, globals);
|
2015-01-19 19:37:21 +08:00
|
|
|
|
|
|
|
return text;
|
2015-01-20 00:28:14 +08:00
|
|
|
}
|
2015-01-19 19:37:21 +08:00
|
|
|
|
2015-05-27 08:37:01 +08:00
|
|
|
/**
|
|
|
|
* Set an option of this Converter instance
|
|
|
|
* @param {string} key
|
|
|
|
* @param {string} value
|
|
|
|
*/
|
|
|
|
function setOption (key, value) {
|
|
|
|
options[key] = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the option of this Converter instance
|
|
|
|
* @param {string} key
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
|
|
|
function getOption(key) {
|
|
|
|
return options[key];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the options of this Converter instance
|
|
|
|
* @returns {{}}
|
|
|
|
*/
|
|
|
|
function getOptions() {
|
|
|
|
return options;
|
|
|
|
}
|
|
|
|
|
2015-01-19 19:37:21 +08:00
|
|
|
return {
|
2015-05-27 08:37:01 +08:00
|
|
|
makeHtml: makeHtml,
|
|
|
|
setOption: setOption,
|
|
|
|
getOption: getOption,
|
|
|
|
getOptions: getOptions
|
2015-01-19 19:37:21 +08:00
|
|
|
};
|
2015-01-16 05:21:33 +08:00
|
|
|
};
|