Allows the use of commonmark generic directive extension in markdown, generating components through remark-html or remark-react.
This module also works in browser environments.
Here's a demo using remark-react and react-toolbox. It is made using create-react-app, the source is here and can also be run locally.
Remember that the following syntax is experimental in regards to the commonmark spec, and will perhaps never be supported officialy.
Generic Directives is still in active discussion in http://talk.commonmark.org/t/generic-directives-plugins-syntax . But for brainstorming purposes, here is some possible extensions to support, or at least adhere to if not included.
It might look like !extensionName[](){} for inline
and for block (Current talk page at http://talk.commonmark.org/t/block-directives/802 )
extentionNames: argumentField
:::
...BlockContent...
:::
{ #id .class key1=value key2=value }
There is a known bug in remark-react < 4.0.1, that wrongly coerces non-string values to strings.
Make sure to use at least v4.0.1.
!Extension[Content](Argument){Properties}
:information_source: The extension syntax is validated through regexes, that you can inspect here if needed
Extension
defines the element you want to use
It matches the \w
character class.
Matches any alphanumeric character from the basic Latin alphabet, including the underscore. Equivalent to [A-Za-z0-9_].
Content
defines the element content
It matches everything but the ]
character.
It can be mapped to any hast element property or value, see placeholders.
Argument
defines the element argument
It matches everything but the )
character.
It can be mapped to any hast element property or value, see placeholders.
Properties
defines the element properties
They can have leading and / or trailing spaces before / after the opening / closing braces.
The different properties are separated by spaces, and so each of them match any character but spaces, except for quoted properties.
Extension: Argument
:::
[Content]
:::
{Properties}
:information_source: The extension syntax is validated through regexes, that you can inspect here if needed
Extension
defines the element you want to use
It matches the \w
character class.
Matches any alphanumeric character from the basic Latin alphabet, including the underscore. Equivalent to [A-Za-z0-9_].
Argument
defines the element argument
It matches everything but a line break.
It can be mapped to any hast element property or value, see placeholders.
Content
defines the element content
It matches everything, and stops at the next :::
occurence.
Leading and trailing line breaks will be stripped.
It can be mapped to any hast element property or value, see placeholders.
Properties
defines the element properties
They can have leading and / or trailing spaces before / after the opening / closing braces.
The different properties are separated by spaces, and so each of them match any character but spaces, except for quoted properties.
id: #my-id
It will be applied to the top-level hast element.
class: .my-class .my-other-class
It will be applied to the top-level hast element.
key / value property: my-prop=my-value
It can be mapped to any hast element property or value, see placeholders.
key / value quoted property: my-other-prop="my value with spaces"
It can be mapped to any hast element property or value, see placeholders.
lone property: my-alone-prop
It can be mapped to any hast element property or value, see placeholders.
npm install remark-generic-extensions
OR
yarn add remark-generic-extensions
remark-react
See demo
remark-html
Say we have the following file, example.md
:
# Alpha
!alert[My message!](my subtext is rad){ #my-alert .custom-alert }
youtube: C8NAYW-Z54o
:::
My featured video!
:::
{ #my-video .custom-video-style spanClassName=custom-span-class }
## Bravo
## Delta
And our script, example.js
, looks as follows:
import vfile from "to-vfile"
import remark from "remark"
import genericExtensions from "remark-generic-extensions"
import html from "remark-html"
remark()
.use(genericExtensions,
{
elements: {
alert: {
html: {
tagName: "span",
children: [
{
type: "element",
tagName: "i",
properties: {
className: "fa fa-exclamation",
ariaHidden: true
}
},
{
type: "element",
tagName: "span",
children: [
{
type: "text",
value: "::content::"
}
]
},
{
type: "element",
tagName: "span",
properties: {
className: "subtext"
},
children: [
{
type: "text",
value: "::argument::"
}
]
}
]
}
}
},
youtube: {
html: {
tagName: "div",
children: [
{
type: "element",
tagName: "iframe",
properties: {
width: 420,
height: 315,
src: "https://www.youtube.com/embed/::argument::"
}
},
{
tagName: "span",
properties: {
className: "::prop::spanClassName::"
},
children: [
{
type: "text",
value: "::content::"
}
]
}
]
}
}
}
)
.use(html)
.process(vfile.readSync('example.md'), (err, file) => {
if (err) throw err
console.log(String(file))
})
Now, running node example
yields (indented):
<h1>Alpha</h1>
<p>
<span id="my-alert" class="custom-alert">
<i class="fa fa-exclamation" aria-hidden="true"></i>
<span>My message!</span>
<span class="subtext">my subtext is rad</span>
</span>
</p>
<div class="custom-video-style" id="my-video">
<iframe width="420" height="315" src="https://www.youtube.com/embed/C8NAYW-Z54o"></iframe>
<span class="custom-span-class">My featured video!</span>
</div>
<h2>Bravo</h2>
<h2>Delta</h2>
remark().use(genericExtensions[, options])
Convert generic extensions in a markdown document to an hast syntax tree, suitable for rendering to html / react.
options
(object)All options are validated through joi. You can find the schema here. In case of error, it will be logged to the console and this module bypassed by remark.
elements (Elements
, optional, default {}
) - A list of inline elements to support
along with their configuration
placeholderAffix (string, optional, default "::"
) - String that encloses content
,
argument
and prop
tokens in properties
of the elements
option, for dynamic replacement with properties specified in the markdown element.
See HastProperties
for more information.
debug (boolean, default: false
) - Whether to show debug messages
through vfile-reporter
See Logging for more information.
Elements
(object)This object defines the extensions configuration.
The keys are strings corresponding to the inline extension names (i.e. Icon
).
You can define one or more extensions if needed.
extensionName (string): (object)
propsDefaultValues (object, optional)
propertyName (string): (any) - default value of the property
It will be applied to items that specify the corresponding property without a defined value, i.e.
```javascript import remark from "remark" import genericExtensions "remark-generic-extensions" import html from "remark-html" remark() .use(genericExtensions, { elements: { icon: { propsDefaultValues: { highlight: true, } } } } ) .use(html) .process("!icon{highlight}", (err, file) => { if (err) throw err console.log(String(file)) }) ``` Running this example would yield `
html (object, optional, mutually exclusive with replace
) - html element representation used for rendering the extension
tagName (string, optional, defaults to the extension name) - html5 element tag name
children (Hast
, optional) - the element children
properties (HastProperties
, optional) - the element properties
replace (function, optional, mutually exclusive with html
) - function whose return value will be used for rendering the extension
arguments:
Hast
(array[object])This structure is a hast tree, with a few restrictions.
It is recursive, the children
property also being of Hast
type.
"element"
) - hast node type
Hast
, optional) - the element childrenHastProperties
) - the element propertiesHastProperties
(object)This object pairs are mapped to hast properties, and thus follow the same rules.
propertyName (string, except "class" && "for"): (enum)
(string) - When a property value is a string, it supports placeholders that will be replaced by their corresponding value.
(any) - A property value can also be of any other type
:information_source: Any property present in a markdown extension and not referenced by a placeholder will be applied to the top-level element.
:information_source: If the content
property is not referenced on a block element, it will be applied to a child text
node of the top-level element.
The value
property and all properties
members in Hast
children support placeholders.
The available placeholders are:
content
part of the extension in markdown (the part between []
)argument
part of the extension in markdown (the part between ()
)property
value present in the extension in markdown (in the part between {}
):information_source: the available properties are id
, className
or any other property defined in your markdown.
Specifying the placeholderAffix
option allows changing the placeholders.
For example, using "||" would make the available placeholders become "||content||", "||argument||" and "||prop||property||".
All logging, except the options validation that is directly logged to the console, takes place through vfile-reporter.
It allows having nice error messages with positional information, and is also the standard way of logging with unifiedjs.
By default, this module logs warnings, but enabling the debug
option will also log debug messages, useful for troubleshooting.
```javascript import remark from "remark" import genericExtensions "remark-generic-extensions" import html from "remark-html" import report from "vfile-reporter" remark() .use(genericExtensions, {}) .use(html) .process(input, function(err, file) { console.log(report(err || file)) }) ```
```javascript import remark from "remark" import genericExtensions "remark-generic-extensions" import html from "remark-html" import report from "vfile-reporter" remark() .use(genericExtensions, { debug: true }) .use(html) .process(input, function(err, file) { console.log(report(err || file)) }) ```
$ yarn test
).Copyright (c) Mehdi Lahlou mlahlou@protonmail.ch