Create an extension
A Showdown extension is a function that returns an array of language or outputs 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 (myext1
and myext2
in the example above) should be an object that defines the behavior of the corresponding sub-extension.
Sub-extension object properties
A sub-extension object should have a type
property that defines the type of the sub-extension, and either regex
and replace
properties or a filter
property.
Type
Type is a required property that defines the nature of the corresponding sub-extensions. It takes one of the two values:
-
lang
: language extension to add new Markdown syntax to Showdown.lang
extensions have the highest priority in the subparser order, so they are called after escaping and normalizing the input text and before calling any other subparser (or extension).When to use
lang
typeFor example, if you want the
^^youtube http://www.youtube.com/watch?v=oHg5SJYRHA0
syntax to automatically be rendered as an embedded YouTube video. -
output
: output extension (or modifier) to alter the HTML output generated by Showdown.output
extensions have the lowest priority in the subparser order, so they are called right before the cleanup step and after calling all other subparsers.When to use
output
typeFor example, if you want the
<div class="header">
to become<header>
.
Regex and replace
regex
/replace
properties are similar to the Javascript's string.replace
function and work the same way:
-
regex
: astring
or aRegExp
object.If
regex
is astring
, it will automatically be assigned ag
(global) modifier, that is, all matches of that string will be replaced. -
replace
astring
or afunction
.If
replace
is astring
, you can use the$1
syntax for group substitution, exactly as if it were making use ofstring.replace
.
Regex and replace example
In this example, all the occurrences of markdown
will be replaced with showndown
.
var myext = {
type: 'lang',
regex: /markdown/g,
replace: 'showdown'
};
Filter
Alternately, if you'd like to have more control over the modification process, you can use filter
property.
This property should be used as a function that acts as a callback. The callback should receive the following parameters:
text
: the source text within the Showdown's engine.converter
: the full instance of the current Showdown's converter object.options
: the options used to initialize the converter
The filter function should return the transformed text. If it doesn't, it will fail silently and return an empty output.
Filter example
var myext = {
type: 'lang',
filter: function (text, converter, options) {
// ... do stuff to text ...
return text;
}
};
Use filter
with care
Although Filter extensions are more powerful, they have a few pitfalls that you should keep in mind before using them, especially regarding the converter
parameter.
Since the converter
parameter passed to the filter function is the fully initialized instance, any change made to it will be propagated outside the scope of the filter function and will remain there until a new converter instance is created. So, it is not recommended to make ANY change to the converter object.
Another aspect is that if you call the converter
recursively, it will call your extension itself at some point. It may lead to infinite recursion in some circumstances, and it's up to you to prevent this. A simple solution is to place a 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);
}
}
};
Register an extension
To let Showdown know what extensions are available, you need to register them in the Showdown global object.
To register an extension, call the showdown.extension
function with two parameters: the first one is the extension name; the second one is the actual extension.
showdown.extension('myext', myext);
Test an extension
The Showdown test runner is configured to automatically test cases for extensions.
To add test cases for an extension:
- Create a new folder under
./test/extensions
that matches with the name of the.js
file in./src/extensions
. - Place any test cases into the filter using the
md/html
format. These cases will automatically be executed when running tests.
Additional information
Escape and normalization
Showdown performs 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 only applies to language extensions since these chars are unescaped before output extensions are run.
Keep in mind that these modifications happen before language extensions are run, so if your extension relies on any of those chars, you have to make the appropriate adjustments.
Implementation concerns
One of the concerns is maintaining both client-side and server-side compatibility. You can do this with a few lines of boilerplate code.:
(function (extension) {
if (typeof showdown !== 'undefined') {
// global (browser or node.js 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 an error is thrown
throw Error('Could not find showdown library');
}
}(function (showdown) {
// loading extension into showdown
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. It has another benefit of creating several scope layers that can be useful for interaction between sub-extensions global-wise or local-wise.
It is also loaded conditionally to make it compatible with different loading mechanisms (such as browser, CommonJS, or AMD).