showdown/src/showdown.js

287 lines
7.1 KiB
JavaScript
Raw Normal View History

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 = {};
/**
* Set a global option
2015-03-02 02:15:32 +08:00
* @static
* @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
};
/**
* Get a global option
2015-03-02 02:15:32 +08:00
* @static
* @param {string} key
* @returns {*}
*/
showdown.getOption = function (key) {
2015-01-19 19:37:21 +08:00
'use strict';
return globalOptions[key];
};
/**
* Get the global options
2015-03-02 02:15:32 +08:00
* @static
* @returns {{omitExtraWLInCodeBlocks: boolean, prefixHeaderId: boolean}}
*/
showdown.getOptions = function () {
2015-01-19 19:37:21 +08:00
'use strict';
return globalOptions;
};
2015-01-16 05:21:33 +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
/**
* 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 || {};
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'
];
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
// 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) {
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') {
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 {
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
}
});
}
/**
* 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
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-19 19:37:21 +08:00
return {
makeHtml: makeHtml
};
2015-01-16 05:21:33 +08:00
};