-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Extensions
Showdown allows additional functionality to be loaded via extensions.
<script src="src/showdown.js"></script>
<script src="src/extensions/twitter.js"></script>
<script>
var converter = new showdown.Converter({ extensions: ['twitter'] });
</script>
// Using a bundled extension
var showdown = require('showdown');
var converter = new showdown.Converter({ extensions: ['twitter'] });
// Using a custom extension
var mine = require('./custom-extensions/mine');
var converter = new showdown.Converter({ extensions: ['twitter', mine] });
Extensions can be loaded while using the CLI tool using the -e
flag.
showdown -e twitter -i foo.md -o bar.html
A showdown extension is simply a function which returns an array of language or output or listener (coming soon) extensions (henceforth called "sub-extensions").
var myext = function () {
var myext1 = {
type: 'lang',
regex: /markdown/g,
replace: 'showdown'
};
var myext2 = {
/* extension code */
};
return [myext1, myext2];
}
Each sub-extension (corresponds to myext1
and myext2
in the above example) should be an object that defines the behavior of said sub-extension.
Sub extensions object should have a type property (that defines the type of the sub-extension) and either a regex and a replace property or a filter property.
Type property defines the nature of said sub-extensions and can assume 2 values:
-
lang - Language extensions add new markdown syntax to showdown.
For example, say you wanted
^^youtube http://www.youtube.com/watch?v=oHg5SJYRHA0
to automatically render as an embedded YouTube video, that would be a language extension.They have the highest priority in the subparser order, which means they are called right after the input text is escaped and normalized and before any other subparser (or extension) is called.
-
output - Output extensions (or modifiers) alter the HTML output generated by showdown
For example, if you wanted to change
<div class="header">
to be<header>
, you could implement an output modifier.They have the lowest priority in the subparser order, which means they are called right before the cleanup step and after all other subparsers are called.
Regex/replace style extensions are very similar to javascript's string.replace
function. Two properties are given, regex
and replace
.
-
regex
should be either a string or a RegExp object. Keep in mind that, if a string is used, it will automatically be given ag
modifier, that is, it is assumed to be a global replacement. -
replace
can be either a string or a function. Ifreplace
is a string, it can use the$1
syntax for group substitution, exactly as if it were making use ofstring.replace
(internally it does this actually)
Example:
var myext = {
type: 'lang',
regex: /markdown/g,
replace: 'showdown'
};
Alternately, if you'd just like to do everything yourself, you can specify a filter property. The filter property should be a function that acts as a callback.
There are 3 parameters passed to the filter callback:
- text - the source text within showdown's engine.
- converter - the full instance of the current showdown's converter object
- options - the options used to initialize the converter
Keep in mind that the filter function should return the transformed text. If you don't, it will fail silently and will result in a blank output.
var myext = {
type: 'lang',
filter: function (text, converter, options) {
// ... do stuff to text ...
return text;
}
};
Although Filter extensions are more powerful, they have a few pitfalls that you should keep in mind before using them, specially regarding the converter parameter.
Since the converter param passed to the filter function is the fully initialized instance, any change done to it will propagate outside the scope of the filter function, and will remain until a new instance is created. So, it is not advisable to make ANY change to the converter object.
Another important thing is that, if you call the converter recursively, it will, at some point, call your extension itself. This can lead, in some circumstances, to infinite recursion and it's up to you to prevent this. A simple solution is to place some kind of safeguard to disable your extension if it's called more than x times:
var x = 0;
var myext = {
type: 'lang',
filter: function (text, converter) {
if (x < 3) {
++x;
someSubText = converter.makeHtml(someSubText);
}
}
};
In order for showdown to know which extensions are available, they need to be registered in the showdown global object.
This can be accomplished by calling showdown.extension
function, where the first parameter is the extension's name and the second is the actual extension.
showdown.extension('myext', myext);
One bit which should be taken into account is maintaining both client-side and server-side compatibility. This can be achieved with a few lines of boilerplate code.
(function (extension) {
if (typeof showdown !== 'undefined') {
// global (browser or nodejs global)
extension(showdown);
} else if (typeof define === 'function' && define.amd) {
// AMD
define(['showdown'], extension);
} else if (typeof exports === 'object') {
// Node, CommonJS-like
module.exports = extension(require('showdown'));
} else {
// showdown was not found so we throw
throw Error('Could not find showdown library');
}
}(function (showdown) {
// loading extension into shodown
showdown.extension('myext', function () {
var myext = { /* ... actual extension code ... */ };
return [myext];
});
}));
In the code above, the extension definition is wrapped in a self-executing function to prevent pollution of the global scope. This has the additional benefit of creating several scope layers that can be useful for interaction between sub-extensions global wise or local wise.
It's also loaded conditionaly, in order to make it compatible with different loading mechanisms (such as browser, CommonJS or AMD).
Showdown does the following escape/normalization:
- Replaces
¨
(trema) with¨T
- Replaces
$
(dollar sign) with¨D
- Normalizes line endings (
\r
,\r\n
are converted into\n
) - uses
\r
as a char placeholder
This happens before language extensions are run, which means that if your extension relies on any of those chars, you have to take this into consideration and make the appropriate modifications.
This only applies to language extensions since these chars are unescaped before output extensions are run.
The showdown test runner is setup to automatically test cases for extensions. To add test cases for an extension, create a new folder under ./test/extensions
which matches the name of the .js
file in ./src/extensions
. Place any test cases into the filter using the md/html format and they will automatically be run when tests are run.