Usage With Markdown #
Following this tutorial you will learn how to output Duet components from Markdown using the popular JavaScript library Marked.js.
Installation #
The package @duetds/marked
offers a custom renderer for the most popular JavaScript Markdown library marked.js, so that it outputs Duet components like <duet-paragraph>
rather than standard HTML tags like <p>
.
As the first step, you will want to install Marked.js and Duet’s Markdown package. To do so, run the following commands in your terminal:
npm install marked
npm install @duetds/marked
Usage #
For usage in vanilla JavaScript:
import marked from "marked"
import { createDuetMarkedRenderer } from "@duetds/marked"
const renderer = createDuetMarkedRenderer()
marked.use({ renderer })
console.log(marked("# hello duet!"))
Optionally, you can supply a function isExternalUrl
which is used to determine whether a link should open in a new window or not:
import marked from "marked"
import { createDuetMarkedRenderer } from "@duetds/marked"
// the logic can be as simple or as complex as necessary
function isExternalUrl(url) {
return new URL(url).host !== "www.duetds.com"
}
const renderer = createDuetMarkedRenderer({ isExternalUrl })
marked.use({ renderer })
console.log(marked("[an external link](https://www.lahitapiola.fi)"))
Usage with Angular #
The @duetds/marked
package can be integrated with ngx-markdown
. In your app.module.ts
file:
// 1. import these
import { createDuetMarkedRenderer } from "@duetds/marked";
import { MarkdownModule, MarkedRenderer, MarkedOptions } from "ngx-markdown";
// 2. define this function
function markedOptionsFactory(): MarkedOptions {
return {
renderer: Object.assign(
new MarkedRenderer(),
createDuetMarkedRenderer()
),
};
}
// 3. configure the MarkdownModule to use our options factory
@NgModule({
imports: [
MarkdownModule.forRoot({
markedOptions: {
provide: MarkedOptions,
useFactory: markedOptionsFactory,
},
}),
],
})
export class AppModule {}
Then in a template, such as app.component.html
, you can render markdown like so:
<!-- with string literal, must use ngPreserveWhitespaces -->
<markdown ngPreserveWhitespaces># hello duet!</markdown>
<!-- binding to data -->
<markdown [data]="someComponentProperty"></markdown>
<!-- using pipes -->
<div>{{ someComponentProperty | markdown }}</div>
For more information, please read the ngx-markdown documentation.
Sanitization #
From v9
onwards, ngx-markdown
uses Angular's DOMSanitizer to sanitise its output. This has an internal, non-configurable whitelist of HTML tags, meaning Duet components get rejected/stripped from the output. To work around this, disable the sanitizer for markdown:
@NgModule({
imports: [
MarkdownModule.forRoot({
// the line below is necessary for ngx-markdown >= 9
sanitize: SecurityContext.NONE,
markedOptions: {
provide: MarkedOptions,
useFactory: markedOptionsFactory,
},
}),
],
})
export class AppModule {}
For additional safety, consider integrating a library like DOMPurify as recommended by marked.js, which offers a configurable whitelist of HTML tags.
Usage with React #
First you should create a Markdown
component:
import React from "react"
import marked from "marked"
import { createDuetMarkedRenderer } from "@duetds/marked"
marked.use({ renderer: createDuetMarkedRenderer() })
// use React.memo to avoid re-renders if the markdown source hasn't changed
const Markdown = React.memo(function Markdown({ source }) {
return <div dangerouslySetInnerHTML={{ __html: marked(source) }} />
})
export default Markdown
Which can then be used like this:
import React from "react";
import ReactDOM from "react-dom"
import Markdown from "./Markdown"
ReactDOM.render(
<Markdown source="# hello duet!" />,
document.querySelector("#app")
);
Example #
If you have followed along with the above, then the following input:
# Hello world
This is a paragraph containing [a link](https://www.duetds.com/).
## Sub-heading
* Here
* Is
* A
* List
1. Here
1. Is
1. An
1. Ordered
1. List
Should result in the following output:
<duet-heading level="h1">Hello world</duet-heading>
<duet-paragraph>
This is a paragraph containing
<duet-link url="https://www.duetds.com/">a link</duet-link>.
</duet-paragraph>
<duet-heading level="h2">Sub-heading</duet-heading>
<ul class="duet-list">
<li>Here</li>
<li>Is</li>
<li>A</li>
<li>List</li>
</ul>
<ol class="duet-list">
<li>Here</li>
<li>Is</li>
<li>An</li>
<li>Ordered</li>
<li>List</li>
</ol>