This is a draft guide on how to write plugins and what properties they have.
Plugins are used to extend the functionalities of the core renderer. They are located under
src/plugins
. Each of them is registered in src/plugins.json
, grouped by the phase they need to
act upon and in the order they should be loaded and executed.
The behaviour of registering is just like any other node module - they use their containing folder
name while having an index.js
inside or directly the name of the file if they don't need to have
a folder. Generally you'd want to have a folder to hold your resource like css, fonts, js, etc.
Plugins have four phases: external
, resource
, extend
, before
, after
and cleanup
.
Each plugin can be registered for multiple phases but only once per phase.
The external
phase registers an external showdown plugin.
Read the external plugins section for more information.
The resource
phase does nothing other that registering resources.
If your plugin is using some frontend scripts or styles you need to add it to this phase.
The extend
phase extends slides directly. You can add whole slides as markdown in this phase.
The slides array is passed to the extend
function as first argument and the function is expected
to return an array containing all the slides.
Example usage would be to add wrapping slides like a speaker introduction or a Q&A slide.
The before
phase is a replace phase that runs before html generation. It translates to
showdown's lang
phase. It requires that pattern
regex and replace
function are placed
in an object and set as the before
property.
They work like String.prototype.replace
. The pattern regex matches against the markdown slides and
when a match is found the replace
function is executed with the whole match as a first argument
and each capture groups is given in order as the next arguments. You need to return a string that is
the replacement for the match.
The after
phase is similar to before
but instead of executing before html generation it executes
after it. It works on the same principle and maps to showdown's output
phase.
After having created the plugin structure and registered it as described above you can proceed to
writing the actual plugin. Then in your main file you need to provide module.exports
with a
function that returns the object representing your plugin.
Here is an example of such a file:
// index.js
module.exports = () => ({
before: {
pattern: /hello/gm,
replace: () => 'hi',
}
});
The above plugin would replace any occurrences of hello
with hi
in the presentation markdown.
There are two types of plugins - native showdown plugins and mrend plugins. For now we're just going to look at the mrend plugins.
Most of the time you'd want to have additional styling or some other resource with your plugin.
Firstly register the plugin for the resource
phase. To add a resource you need to put it in the
plugin's dist
directory.
Take this structure for example:
my-plugin/
└─ index.js
└─ dist/
└─ my-plugin-style.css
How you would register the resource itself would be:
module.exports = () => ({
resources: ['my-plugin-style.css'],
// ...
});
Everything else around linking css or js would be handled by the core functionality.
You can override the dist
directory by using
module.exports = () => ({
resources: {
links: ['my-plugin-style.css'],
dist: 'styles',
},
// ...
});
Some plugins might want to have some king of configuration provided by the presentation header.
That configuration is passed as the first argument of of the function you export and is called
metadata. You should scope your metadata parameters with your plugin name as a prefix.
Lets say your plugin is called awesomeblock
. Then you should expect the metadata to hold
parameters prefixed with awesomeblock-
.
module.exports = metadata => {
console.log('Awesome color:', metadata['awesomeblock-color']);
return {
// ...
};
};
The metadata is not separated by plugin so you can read global or other plugins' properties but you cannot edit them.
If you need to run initialization steps before actually running the plugin you can put your code inside the exported function. This can be useful if you need to move resource files or prepare for the actual execution.
module.exports = (metadata, utils) => {
console.log('Initializing my-plugin.');
console.log('node_modules path:', utils.MODULES_DIR);
// ...
};
The system supports locale files which look like this
{
"<language>": {
"<key>": "<value>",
...
},
...
}
Here's an example locale file
{
"en": {
"hello": "Hello"
},
"bg": {
"hello": "Здравей"
}
}
You should set the lang
metadata property in your presentation
---
lang: en
---
And use the locale service
module.exports = (metadata, utils) => {
console.log(utils.i18n('hello'), 'world!');
// ...
};
showdownjs
provides a plugin system of its own which the mrend
plugins utilize. You can use the
existing showdown plugin ecosystem by creating an external mrend plugin. Note that this is the
only plugin type that doesn't have an mrend phase. Its execution order is determined by whether
it's a lang
or an output
plugin and it's position in the plugin.json
file. External plugins
are always executed before regular mrend plugins.
module.exports = () => ({
resources: [ /* ... */ ],
external: require('showdown-highlight'),
});
To make that work you need to also copy the resources from node_modules
and register them.
For your convenience this is already done in the highlight
plugin that comes with the tool.
If you have any questions or find a bug feel free to open an issue at https://github.com/d3lio/mrend/issues. Pull requests are appreciated as well.