markdown-components
Add custom React-like components to Markdown which can be safely used by end-users. Use with your favorite Markdown engine.
E.g.,
<# A Box which defaults to blue if user has no favorite color #> ## subheading * listElement1 * listElement2 [google](https://google.com) Box in box! _more_ markdown
Install
npm i markdown-components// plus your favorite markdown engine// npm i markdown// npm i showdown// npm i markdown-it
Quick start
var toHTML markdownItEngine = ; // define a Box component:var components = { ; ; // render internal elements ; }; // use the Box component:var customizedMarkdown = `Custom components:<Box lineSize=2 color={ user.favoriteColor }> Can contain... # Markdown with interpolated expressions: This box should be *{ user.favoriteColor }* And the _markdown_ can contain custom components: <Box lineSize=1 color="red"> which can contain *more markdown* and so on. Render open curly brace and open angle bracket: {{ and << </Box></Box>`; // render the markdown with your custom components,// providing context variables:var html = ;console; // ~=>// <p>Custom components:</p>// <div style="border-width:2; background-color>// <p>Can contain...</p>// <h1> Markdown with interpolation:</h1>// <p>This box should be <b>blue</b>// And the <i>markdown</i> can contain custom components:</p>// <div style="border-width:1; background-color:red>// <p>which can contain <b>more markdown</b>// and so on.// Render open curly brace and open angle bracket: { and <</p>// </div>// </div>
Custom Components
The components argument to toHTML
and Renderer.write
provides functions that generate HTML.
For example:
{ // generate custom HTML: ; ; // render elements between start and end tag ; }
Allows you to write:
# This markdownWill be displayed on a red background
Rationale
Markdown components provides a content authoring language with custom components which is safe for use by end-users.
JSX-Markdown | markdown-it-shortcodes | markdown-components | |
---|---|---|---|
end-users | unsafe | safe | safe |
nesting | yes | no | yes |
HOCs | yes | no | yes |
JSX-markdown libraries aren't suitable because React interpolated expressions are Javascript. I.e., you'd need to eval user-generated javascript either on your server or another user's browser. You could try evaluating such code in a sandboxed environment, but it's inefficient and asynchronous. The need for asynchronous evaluation rules out using a sandbox like jailed in a React client, since React rendering requires synchronous execution.
In this package, expressions, like { a.b }
or { foo(a) }
are restricted to a context object and a set of developer defined functions, so there is no script injection vulnerability. Authors of this markdown work inside a developer-defined sandbox.
API
toHTML
Easy one step method for generating HTML.
Parses and renders Markdown with components to HTML.
// requires: npm install markdown-it;;// =>// "<div class=my-component><p>a=interpolated;b=123;c=hello</p><h1>This is an interpolated heading</h1></div>"
Parser
Class for parsing component markdown input text.
Note that this function doesn't parse Markdown. Markdown parsing is currently done by the renderer. This is expected to change in future.
constructor arguments
markdownEngine
(required) The markdown engine function (required).indentedMarkdown
(optional, default: true) Allows a contiguous block of Markdown to start at an indentation point without creating a preformatted code block. This is useful when writing Markdown inside deeply nested components.
#parse
Returns a JSON object representing the parsed markdown.
;var parser = markdownEngine:; // use showdownjsvar parsedElements = parser;// =>// [// {// type: "tag",// name: 'mycomponent',// rawName: 'MyComponent',// attribs: {// a: {// type: "interpolation",// expression: ["accessor", "x.y.z"]// },// b: 123,// c: "hello",// d: true,// e: false// }// children: [// {// type: "text",// blocks: [// "<h1>User likes ",// { type: "interpolation",// expression: ["or", ["accessor", "user.color"], ["scalar", "no"]]// },// "color</h1>"// ]// }// ]// }// ]
Attribute types
Attributes can be ints, floats, strings, booleans and expressions.
Note: the d
attribute represents a true
boolean.
Renderer
A class representing the rendering logic.
constructor arguments
-
components
(required) An object of key:function pairs. Where the key is the componentName (matched case-insensitively with tags in the input text), and function is a function which takes parsed elements as input, and uses the render function to write HTML:{} -
defaultComponent
(optional) A function called when a matching component cannot be found for a tag. Same function signature as a component. -
functions
(optional) Functions which may be used in interpolation expressions, of the form:value
#write
Writes an element (e.g., the result from Parser.parse) to stream
, and uses the context
when evaluating expressions:
renderer;var html = stream;
Components
The components argument is an object where keys are tag names, and functions render HTML. This is a required argument of the Renderer
constructor and the toHTML
function.
For example:
{ // generate custom HTML: ; ; // render elements between start and end tag ; }
Allows you to write:
# This markdownWill be displayed on a red background
Component functions are of the form:
{ }
The first argument, tagArguments, contains values passed in the markup, plus two special keys:
__name
name of the tag
__children
array of Objects representing elements between the open and close tags, having the form:
The second argument, render
is a function which takes a string representing HTML or an object representing parsed entities and writes it to a stream.
Higher Order Components
Because the component has responsibility for rendering __children
, you can manipulate child elements at render time, choosing to ignore, rewrite or reorder them. For example, you could create elements that provide switch/case/default semantics:
# Your ResultsYou did _great_!Well doneBetter luck next time
Interpolation Functions
Interpolation blocks can contain simple expressions including function calls:
<Component value= and or xy />
Interpolation functions are provided to the renderer constructor:
components: {...} functions: { return myBool; } { return a+b; } ;
Given the above code, the value
attribute of Component
will be the value of x.y:
value= and 0 or xy
Markdown Engine
A number of wrappers for existing Markdown interpreters are provided in src/engines.js
. Each is a function which returns a rendering function. There are wrappers MarkdownIt, ShowdownJS and evilStreak's markdown. It's easy to write your own wrapper. See the source file.
; var html = ;
Separately Parse and Render
If you're concerned about efficiency, parse the input first, and cache the result (a plain JSON object). Call Renderer.write with different contexts:
Example
var markdownItEngine Renderer Parser = ; // "npm i markdown-it" to use markdownItEnginevar streams = ; // "npm i memory-streams"var renderer = componets: { ; ; ; } ; var parser = markdownEngine: ;var parsedElements = parser; // red boxstream = streams;renderer;console;// <div class="box" style="background-color:red"><i>Here is some</i> <b>markdown</b></div> // blue boxstream = streams;renderer;console;// <div class="box" style="background-color:blue"><i>Here is some</i> <b>markdown</b></div>