Skip to main content

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 this package. To do so, run the following command in your terminal:

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 {}
Be aware, this should only be disabled in situations where the markdown is not user-generated and is entirely under your control.

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>
Edit