mirror of
https://github.com/showdownjs/showdown.git
synced 2024-03-22 13:30:55 +08:00
feature(extensionLoading): add support to legacy extensions in the new extension mechanism
Old extensions that register themselves in `showdown.extensions` can be loaded and validated using the new extension loading mechanism. However, a warn is issued, alerting users and developers that the extension should be updated to use the new mechanism BREAKING CHANGE: Deprecates `showdown.extensions` property. To migrate, you should use the new method `showdown.extension(<ext name>, <extension>)` to register the extension.
This commit is contained in:
parent
e7cb15f1e9
commit
4ebd0caa27
|
@ -108,10 +108,11 @@ module.exports = function (grunt) {
|
|||
|
||||
require('load-grunt-tasks')(grunt);
|
||||
|
||||
grunt.registerTask('concatenate', ['concat']);
|
||||
grunt.registerTask('lint', ['jshint', 'jscs']);
|
||||
grunt.registerTask('test', ['lint', 'concat', 'simplemocha']);
|
||||
grunt.registerTask('test-without-building', ['simplemocha']);
|
||||
grunt.registerTask('build', ['lint', 'test', 'uglify']);
|
||||
grunt.registerTask('build', ['test', 'uglify']);
|
||||
grunt.registerTask('prep-release', ['build', 'changelog']);
|
||||
|
||||
// Default task(s).
|
||||
|
|
BIN
dist/showdown.js
vendored
BIN
dist/showdown.js
vendored
Binary file not shown.
BIN
dist/showdown.js.map
vendored
BIN
dist/showdown.js.map
vendored
Binary file not shown.
BIN
dist/showdown.min.js
vendored
BIN
dist/showdown.min.js
vendored
Binary file not shown.
BIN
dist/showdown.min.js.map
vendored
BIN
dist/showdown.min.js.map
vendored
Binary file not shown.
|
@ -30,7 +30,7 @@
|
|||
"url": "https://github.com/showdownjs/showdown.git",
|
||||
"web": "https://github.com/showdownjs/showdown"
|
||||
},
|
||||
"license": "BSD",
|
||||
"license": "BSD-2-Clause",
|
||||
"main": "./dist/showdown.js",
|
||||
"scripts": {
|
||||
"test": "grunt test"
|
||||
|
|
108
src/converter.js
108
src/converter.js
|
@ -97,11 +97,12 @@ showdown.Converter = function (converterOptions) {
|
|||
if (showdown.helper.isString(ext)) {
|
||||
ext = showdown.helper.stdExtName(ext);
|
||||
|
||||
// TODO LEGACY SUPPORT CODE
|
||||
if (!showdown.helper.isUndefined(showdown.extensions[ext]) && showdown.extensions[ext]) {
|
||||
console.warn(ext + ' is an old extension that uses a deprecated loading method.' +
|
||||
// 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!');
|
||||
ext = showdown.extensions[ext];
|
||||
legacyExtensionLoading(showdown.extensions[ext], ext);
|
||||
return;
|
||||
// END LEGACY SUPPORT CODE
|
||||
|
||||
} else if (!showdown.helper.isUndefined(extensions[ext])) {
|
||||
|
@ -110,26 +111,66 @@ showdown.Converter = function (converterOptions) {
|
|||
} else {
|
||||
throw Error('Extension "' + ext + '" could not be loaded. It was either not found or is not a valid extension.');
|
||||
}
|
||||
} else if (typeof ext === 'function') {
|
||||
}
|
||||
|
||||
if (typeof ext === 'function') {
|
||||
ext = ext();
|
||||
}
|
||||
|
||||
if (!showdown.helper.isArray(ext)) {
|
||||
ext = [ext];
|
||||
}
|
||||
|
||||
if (!showdown.validateExtension(ext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (ext.type) {
|
||||
case 'lang':
|
||||
langExtensions.push(ext);
|
||||
break;
|
||||
for (var i = 0; i < ext.length; ++i) {
|
||||
switch (ext[i].type) {
|
||||
case 'lang':
|
||||
langExtensions.push(ext[i]);
|
||||
break;
|
||||
|
||||
case 'output':
|
||||
outputModifiers.push(ext);
|
||||
break;
|
||||
case 'output':
|
||||
outputModifiers.push(ext[i]);
|
||||
break;
|
||||
|
||||
default:
|
||||
// should never reach here
|
||||
throw Error('Extension loader error: Type unrecognized!!!');
|
||||
default:
|
||||
// should never reach here
|
||||
throw Error('Extension loader error: Type unrecognized!!!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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!!!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -200,7 +241,6 @@ showdown.Converter = function (converterOptions) {
|
|||
showdown.helper.forEach(outputModifiers, function (ext) {
|
||||
text = showdown.subParser('runExtension')(ext, text, options, globals);
|
||||
});
|
||||
text = parsers.outputModifiers(text, options, globals);
|
||||
|
||||
return text;
|
||||
};
|
||||
|
@ -240,20 +280,34 @@ showdown.Converter = function (converterOptions) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Remove an extension from THIS converter
|
||||
* @param {{}} extension
|
||||
* Use a global registered extension with THIS converter
|
||||
* @param {string} extensionName Name of the previously registered extension
|
||||
*/
|
||||
this.useExtension = function (extensionName) {
|
||||
_parseExtension(extensionName);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
for (var i = 0; i < langExtensions.length; ++i) {
|
||||
if (langExtensions[i] === extension) {
|
||||
langExtensions[i].splice(i, 1);
|
||||
return;
|
||||
}
|
||||
if (!showdown.helper.isArray(extension)) {
|
||||
extension = [extension];
|
||||
}
|
||||
for (var ii = 0; ii < outputModifiers.length; ++i) {
|
||||
if (outputModifiers[ii] === extension) {
|
||||
outputModifiers[ii].splice(i, 1);
|
||||
return;
|
||||
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[i].splice(i, 1);
|
||||
}
|
||||
}
|
||||
for (var ii = 0; ii < outputModifiers.length; ++i) {
|
||||
if (outputModifiers[ii] === ext) {
|
||||
outputModifiers[ii].splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
125
src/showdown.js
125
src/showdown.js
|
@ -113,10 +113,16 @@ showdown.extension = function (name, ext) {
|
|||
|
||||
// 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) {
|
||||
|
@ -155,83 +161,90 @@ showdown.resetExtensions = function () {
|
|||
|
||||
/**
|
||||
* Validate extension
|
||||
* @param {object} ext
|
||||
* @param {array} extension
|
||||
* @param {string} name
|
||||
* @returns {{valid: boolean, error: string}}
|
||||
*/
|
||||
function validate(ext, name) {
|
||||
function validate(extension, name) {
|
||||
'use strict';
|
||||
|
||||
var baseMsg = (name) ? 'Error in ' + name + ' extension: ' : 'Error in unnamed extension',
|
||||
var errMsg = (name) ? 'Error in ' + name + ' extension->' : 'Error in unnamed extension',
|
||||
ret = {
|
||||
valid: true,
|
||||
error: baseMsg
|
||||
error: ''
|
||||
};
|
||||
|
||||
if (typeof ext !== 'object') {
|
||||
ret.valid = false;
|
||||
ret.error = baseMsg + 'it must be an object, but ' + typeof ext + ' given';
|
||||
return ret;
|
||||
if (!showdown.helper.isArray(extension)) {
|
||||
extension = [extension];
|
||||
}
|
||||
|
||||
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') {
|
||||
ret.valid = false;
|
||||
ret.error = baseMsg + 'type ' + type + ' is not recognized. Valid values: "lang" or "output"';
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ext.filter) {
|
||||
if (typeof ext.filter !== 'function') {
|
||||
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 + '"filter" must be a function, but ' + typeof ext.filter + ' given';
|
||||
ret.error = baseMsg + 'must be an object, but ' + typeof ext + ' 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) {
|
||||
if (!showdown.helper.isString(ext.type)) {
|
||||
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';
|
||||
ret.error = baseMsg + 'property "type" must be a string, but ' + typeof ext.type + ' given';
|
||||
return ret;
|
||||
}
|
||||
|
||||
} else {
|
||||
ret.valid = false;
|
||||
ret.error = baseMsg + 'extensions must define either a "regex" property or a "filter" method';
|
||||
return ret;
|
||||
}
|
||||
var type = ext.type = ext.type.toLowerCase();
|
||||
|
||||
if (showdown.helper.isUndefined(ext.filter) && showdown.helper.isUndefined(ext.regex)) {
|
||||
ret.valid = false;
|
||||
ret.error = baseMsg + 'output extensions must define a filter property';
|
||||
return ret;
|
||||
}
|
||||
// normalize extension type
|
||||
if (type === 'language') {
|
||||
type = ext.type = 'lang';
|
||||
}
|
||||
|
||||
if (type === 'html') {
|
||||
type = ext.type = 'output';
|
||||
}
|
||||
|
||||
if (type !== 'lang' && type !== 'output') {
|
||||
ret.valid = false;
|
||||
ret.error = baseMsg + 'type ' + type + ' is not recognized. Valid values: "lang" or "output"';
|
||||
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;
|
||||
}
|
||||
|
||||
} else {
|
||||
ret.valid = false;
|
||||
ret.error = baseMsg + 'extensions must define either a "regex" property or a "filter" method';
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (showdown.helper.isUndefined(ext.filter) && showdown.helper.isUndefined(ext.regex)) {
|
||||
ret.valid = false;
|
||||
ret.error = baseMsg + 'output extensions must define a filter property';
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
/**
|
||||
* Run language extensions
|
||||
*/
|
||||
showdown.subParser('outputModifiers', function (text, config, globals) {
|
||||
'use strict';
|
||||
|
||||
showdown.helper.forEach(globals.outputModifiers, function (ext) {
|
||||
text = showdown.subParser('runExtension')(ext, text);
|
||||
});
|
||||
return text;
|
||||
});
|
24
test/node/legacyExtensionSupport.js
Normal file
24
test/node/legacyExtensionSupport.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* Created by Estevao on 06-06-2015.
|
||||
*/
|
||||
require('source-map-support').install();
|
||||
var expect = require('chai').expect,
|
||||
showdown = require('../../dist/showdown.js');
|
||||
|
||||
describe('showdown legacy extension support', function () {
|
||||
'use strict';
|
||||
var extObjMock =
|
||||
{
|
||||
type: 'lang',
|
||||
filter: function () {}
|
||||
},
|
||||
extFunc = function () {
|
||||
return [extObjMock];
|
||||
};
|
||||
|
||||
it('accept extensions loaded by the old mechanism', function () {
|
||||
showdown.extensions.bazinga = extFunc;
|
||||
var cnv = new showdown.Converter({extensions: ['bazinga']});
|
||||
expect(cnv.getAllExtensions().language).to.eql([extObjMock]);
|
||||
});
|
||||
});
|
|
@ -49,11 +49,11 @@ describe('showdown.Converter', function () {
|
|||
converter.getAllExtensions().language.should.contain(extObjMock);
|
||||
});
|
||||
|
||||
it('addExtension() should add a previous registered extension in showdown', function () {
|
||||
it('useExtension() should use a previous registered extension in showdown', function () {
|
||||
showdown.extension('foo', extObjMock);
|
||||
var converter = new showdown.Converter();
|
||||
|
||||
converter.addExtension('foo');
|
||||
converter.useExtension('foo');
|
||||
converter.getAllExtensions().language.should.contain(extObjMock);
|
||||
showdown.resetExtensions();
|
||||
});
|
||||
|
|
|
@ -16,7 +16,7 @@ describe('showdown.options', function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('showdown.extension', function () {
|
||||
describe('showdown.extension()', function () {
|
||||
'use strict';
|
||||
|
||||
var extObjMock = {
|
||||
|
@ -28,15 +28,15 @@ describe('showdown.extension', function () {
|
|||
};
|
||||
|
||||
describe('should register', function () {
|
||||
it('should register an extension object', function () {
|
||||
it('an extension object', function () {
|
||||
showdown.extension('foo', extObjMock);
|
||||
showdown.extension('foo').should.equal(extObjMock);
|
||||
showdown.extension('foo').should.eql([extObjMock]);
|
||||
showdown.resetExtensions();
|
||||
});
|
||||
|
||||
it('should register an extension function', function () {
|
||||
it('an extension function', function () {
|
||||
showdown.extension('foo', extObjFunc);
|
||||
showdown.extension('foo').should.equal(extObjMock);
|
||||
showdown.extension('foo').should.eql([extObjMock]);
|
||||
showdown.resetExtensions();
|
||||
});
|
||||
});
|
||||
|
@ -78,6 +78,6 @@ describe('showdown.getAllExtensions()', function () {
|
|||
|
||||
it('should return all extensions', function () {
|
||||
showdown.extension('bar', extObjMock);
|
||||
showdown.getAllExtensions().should.eql({bar: extObjMock});
|
||||
showdown.getAllExtensions().should.eql({bar: [extObjMock]});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue
Block a user