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-06-01 03:56:28 +08:00
|
|
|
defaultOptions = {
|
2015-07-11 22:45:58 +08:00
|
|
|
omitExtraWLInCodeBlocks: false,
|
|
|
|
prefixHeaderId: false,
|
|
|
|
noHeaderId: false,
|
|
|
|
headerLevelStart: 1,
|
|
|
|
parseImgDimensions: false,
|
|
|
|
simplifiedAutoLink: false,
|
2015-07-11 22:59:06 +08:00
|
|
|
literalMidWordUnderscores: false,
|
2015-07-11 23:44:24 +08:00
|
|
|
strikethrough: false,
|
2015-07-12 01:21:43 +08:00
|
|
|
tables: false,
|
2015-07-12 03:33:11 +08:00
|
|
|
tablesHeaderId: false,
|
2015-07-12 06:02:02 +08:00
|
|
|
ghCodeBlocks: true, // true due to historical reasons
|
|
|
|
tasklists: false
|
2015-06-01 03:56:28 +08:00
|
|
|
},
|
|
|
|
globalOptions = JSON.parse(JSON.stringify(defaultOptions)); //clone default options out of laziness =P
|
2015-01-16 05:21:33 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* helper namespace
|
|
|
|
* @type {{}}
|
|
|
|
*/
|
|
|
|
showdown.helper = {};
|
|
|
|
|
2015-06-01 03:56:28 +08:00
|
|
|
/**
|
|
|
|
* TODO LEGACY SUPPORT CODE
|
|
|
|
* @type {{}}
|
|
|
|
*/
|
2015-01-16 05:21:33 +08:00
|
|
|
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
|
2015-05-27 08:43:08 +08:00
|
|
|
* @param {*} value
|
2015-01-20 00:28:14 +08:00
|
|
|
* @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-06-15 21:49:26 +08:00
|
|
|
* @returns {{}}
|
2015-01-20 00:28:14 +08:00
|
|
|
*/
|
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-06-15 21:49:26 +08:00
|
|
|
/**
|
|
|
|
* Reset global options to the default values
|
|
|
|
* @static
|
|
|
|
*/
|
2015-06-01 03:56:28 +08:00
|
|
|
showdown.resetOptions = function () {
|
|
|
|
'use strict';
|
|
|
|
globalOptions = JSON.parse(JSON.stringify(defaultOptions));
|
|
|
|
};
|
|
|
|
|
2015-06-15 21:49:26 +08:00
|
|
|
/**
|
|
|
|
* Get the default options
|
|
|
|
* @static
|
|
|
|
* @returns {{}}
|
|
|
|
*/
|
|
|
|
showdown.getDefaultOptions = function () {
|
|
|
|
'use strict';
|
|
|
|
return defaultOptions;
|
|
|
|
};
|
|
|
|
|
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-06-01 03:56:28 +08:00
|
|
|
/**
|
|
|
|
* Gets or registers an extension
|
|
|
|
* @static
|
|
|
|
* @param {string} name
|
|
|
|
* @param {object|function=} ext
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
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);
|
|
|
|
|
2015-06-01 03:56:28 +08:00
|
|
|
// Getter
|
2015-03-02 02:15:32 +08:00
|
|
|
if (showdown.helper.isUndefined(ext)) {
|
2015-06-01 03:56:28 +08:00
|
|
|
if (!extensions.hasOwnProperty(name)) {
|
|
|
|
throw Error('Extension named ' + name + ' is not registered!');
|
|
|
|
}
|
|
|
|
return extensions[name];
|
|
|
|
|
|
|
|
// Setter
|
2015-03-02 02:15:32 +08:00
|
|
|
} else {
|
2015-06-08 02:02:45 +08:00
|
|
|
// Expand extension if it's wrapped in a function
|
2015-06-01 03:56:28 +08:00
|
|
|
if (typeof ext === 'function') {
|
|
|
|
ext = ext();
|
|
|
|
}
|
2015-03-02 02:15:32 +08:00
|
|
|
|
2015-06-08 02:02:45 +08:00
|
|
|
// Ensure extension is an array
|
|
|
|
if (!showdown.helper.isArray(ext)) {
|
|
|
|
ext = [ext];
|
|
|
|
}
|
|
|
|
|
2015-06-01 03:56:28 +08:00
|
|
|
var validExtension = validate(ext, name);
|
2015-03-02 02:15:32 +08:00
|
|
|
|
2015-06-01 03:56:28 +08:00
|
|
|
if (validExtension.valid) {
|
|
|
|
extensions[name] = ext;
|
|
|
|
} else {
|
|
|
|
throw Error(validExtension.error);
|
|
|
|
}
|
2015-03-02 02:15:32 +08:00
|
|
|
}
|
2015-06-01 03:56:28 +08:00
|
|
|
};
|
2015-03-02 02:15:32 +08:00
|
|
|
|
2015-06-01 03:56:28 +08:00
|
|
|
/**
|
|
|
|
* Gets all extensions registered
|
|
|
|
* @returns {{}}
|
|
|
|
*/
|
|
|
|
showdown.getAllExtensions = function () {
|
2015-03-02 02:15:32 +08:00
|
|
|
'use strict';
|
2015-06-01 03:56:28 +08:00
|
|
|
return extensions;
|
|
|
|
};
|
2015-03-02 02:15:32 +08:00
|
|
|
|
2015-06-01 03:56:28 +08:00
|
|
|
/**
|
|
|
|
* Remove an extension
|
|
|
|
* @param {string} name
|
|
|
|
*/
|
|
|
|
showdown.removeExtension = function (name) {
|
|
|
|
'use strict';
|
|
|
|
delete extensions[name];
|
|
|
|
};
|
2015-03-02 02:15:32 +08:00
|
|
|
|
2015-06-01 03:56:28 +08:00
|
|
|
/**
|
|
|
|
* Removes all extensions
|
|
|
|
*/
|
|
|
|
showdown.resetExtensions = function () {
|
|
|
|
'use strict';
|
|
|
|
extensions = {};
|
|
|
|
};
|
2015-03-02 02:15:32 +08:00
|
|
|
|
2015-01-16 05:21:33 +08:00
|
|
|
/**
|
2015-06-01 03:56:28 +08:00
|
|
|
* Validate extension
|
2015-06-08 02:02:45 +08:00
|
|
|
* @param {array} extension
|
2015-06-01 03:56:28 +08:00
|
|
|
* @param {string} name
|
|
|
|
* @returns {{valid: boolean, error: string}}
|
2015-01-16 05:21:33 +08:00
|
|
|
*/
|
2015-06-08 02:02:45 +08:00
|
|
|
function validate(extension, name) {
|
2015-01-19 19:37:21 +08:00
|
|
|
'use strict';
|
|
|
|
|
2015-06-08 02:02:45 +08:00
|
|
|
var errMsg = (name) ? 'Error in ' + name + ' extension->' : 'Error in unnamed extension',
|
2015-06-01 03:56:28 +08:00
|
|
|
ret = {
|
|
|
|
valid: true,
|
2015-06-08 02:02:45 +08:00
|
|
|
error: ''
|
2015-06-01 03:56:28 +08:00
|
|
|
};
|
2015-05-27 02:46:07 +08:00
|
|
|
|
2015-06-08 02:02:45 +08:00
|
|
|
if (!showdown.helper.isArray(extension)) {
|
|
|
|
extension = [extension];
|
2015-01-19 19:37:21 +08:00
|
|
|
}
|
2015-01-16 05:21:33 +08:00
|
|
|
|
2015-06-08 02:02:45 +08:00
|
|
|
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;
|
|
|
|
}
|
2015-04-22 23:58:07 +08:00
|
|
|
|
2015-06-08 02:02:45 +08:00
|
|
|
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;
|
|
|
|
}
|
2015-04-22 23:58:07 +08:00
|
|
|
|
2015-06-08 02:02:45 +08:00
|
|
|
var type = ext.type = ext.type.toLowerCase();
|
2015-03-02 02:15:32 +08:00
|
|
|
|
2015-06-08 02:02:45 +08:00
|
|
|
// normalize extension type
|
|
|
|
if (type === 'language') {
|
|
|
|
type = ext.type = 'lang';
|
|
|
|
}
|
2015-03-02 02:15:32 +08:00
|
|
|
|
2015-06-08 02:02:45 +08:00
|
|
|
if (type === 'html') {
|
|
|
|
type = ext.type = 'output';
|
|
|
|
}
|
2015-01-19 19:37:21 +08:00
|
|
|
|
2015-06-08 02:02:45 +08:00
|
|
|
if (type !== 'lang' && type !== 'output') {
|
2015-06-01 03:56:28 +08:00
|
|
|
ret.valid = false;
|
2015-06-08 02:02:45 +08:00
|
|
|
ret.error = baseMsg + 'type ' + type + ' is not recognized. Valid values: "lang" or "output"';
|
2015-06-01 03:56:28 +08:00
|
|
|
return ret;
|
2015-01-16 05:21:33 +08:00
|
|
|
}
|
|
|
|
|
2015-06-08 02:02:45 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
2015-06-01 03:56:28 +08:00
|
|
|
ret.valid = false;
|
2015-06-08 02:02:45 +08:00
|
|
|
ret.error = baseMsg + 'extensions must define either a "regex" property or a "filter" method';
|
2015-06-01 03:56:28 +08:00
|
|
|
return ret;
|
|
|
|
}
|
2015-06-08 02:02:45 +08:00
|
|
|
|
|
|
|
if (showdown.helper.isUndefined(ext.filter) && showdown.helper.isUndefined(ext.regex)) {
|
2015-06-01 03:56:28 +08:00
|
|
|
ret.valid = false;
|
2015-06-08 02:02:45 +08:00
|
|
|
ret.error = baseMsg + 'output extensions must define a filter property';
|
2015-06-01 03:56:28 +08:00
|
|
|
return ret;
|
2015-01-19 19:37:21 +08:00
|
|
|
}
|
2015-01-20 00:28:14 +08:00
|
|
|
}
|
2015-06-01 03:56:28 +08:00
|
|
|
return ret;
|
|
|
|
}
|
2015-05-27 08:37:01 +08:00
|
|
|
|
2015-06-01 03:56:28 +08:00
|
|
|
/**
|
|
|
|
* Validate extension
|
|
|
|
* @param {object} ext
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
|
|
|
showdown.validateExtension = function (ext) {
|
|
|
|
'use strict';
|
2015-05-27 08:37:01 +08:00
|
|
|
|
2015-06-01 03:56:28 +08:00
|
|
|
var validateExtension = validate(ext, null);
|
|
|
|
if (!validateExtension.valid) {
|
|
|
|
console.warn(validateExtension.error);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
2015-01-16 05:21:33 +08:00
|
|
|
};
|