Web-component to load an external markdown file (.md) and render it into sanitized HTML.
- Syntax highlighting
- Table of contents
- Copy code to clipboard
- Light DOM CSS styling
- Optional JavaScript API
📦 Scoped @xan105
packages are for my own personal use but feel free to use them.
Import and define the Web-component:
import { Markdown } from "/path/to/md.js"
customElements.define("mark-down", Markdown);
HTML:
<mark-down src="/path/to/md"></mark-down>
Optional JavaScript API:
const el = document.querySelector("mark-down");
el.addEventListener("load", ()=>{
console.log("loading...");
});
el.addEventListener("success", ()=>{
console.log("ok");
});
el.addEventListener("failure", ({detail})=>{
console.error(detail.error);
});
//auto rendering (default)
el.integrity = "sha384-0xABCD...";
el.src = "/path/to/md";
//manual rendering
el.manual = true;
el.src = "/path/to/md";
el.render().catch((err)=>{
console.error(err);
});
//Table of contents
querySelector("#toc").innerHTML = el.headings.toHTML({ depth: 4 });
el.addEventListener("intersect", ({detail})=>{
//Do something when a heading (h1, h2, ...) has entered the top of the viewport
querySelector(`#toc a[href="#${detail.id}"]`).classList.add("active");
});
npm i @xan105/markdown
💡 The bundled library and its minified version can be found in the ./dist
folder.
Create an importmap and add it to your html:
<script type="importmap">
{
"imports": {
"@xan105/markdown": "./path/to/node_modules/@xan105/markdown/dist/md.min.js"
}
}
</script>
<script src="./index.js" type="module"></script>
</body>
</html>
index.js:
import { Markdown } from "@xan105/markdown"
customElements.define("mark-down", Markdown);
Markdown is rendered into the light DOM without any predefined CSS styling, this is by design.
Use regular selectors to style just like you would for the rest of the page.
For syntax highlighting you can use one of the many hljs themes available.
💡That being said, there is a basic CSS style with Github-like syntax highlighting available in the ./dist
folder to get you started.
To target the "copy to clipboard" unstyled button added to "code blocks" use CSS ::part()
selector:
clipboard-copy-code { display: block } //by default it is not rendered (display: none)
clipboard-copy-code::part(button) { ... }
clipboard-copy-code::part(button)::before { /*go nuts this also works*/ }
clipboard-copy-code
will have the attribute copied
set when the content has been copied to the clipboard;
You can target it via CSS and add a timeout
(ms) attribute/property value if you wish to do some kind of animation on copy.
clipboard-copy-code
also fires a copied
event just in case.
This is a Web-component as such you need to define it:
import { Markdown } from "/path/to/md.js"
customElements.define("mark-down", Markdown);
Events
-
change()
The source (src) attribute has changed.
-
load()
Markdown is being loaded.
-
render()
Markdown is being rendered.
-
success()
Markdown was rendered without any issue.
-
failure(detail: object)
Something went wrong, see
detail
:{ error: Error }
-
intersect(detail: object)
A heading (h1, h2, ...) has entered the top of the viewport, see
detail
:{ id: string }
Attribute / Property
-
src: string
Path/URL to the
.md
file to load. -
integrity: string
Integrity hash passed to
fetch()
. See Subresource Integrity for more details. -
manual: boolean
If set markdown will not be rendered automatically and you will have to call the
render()
method yourself (see below). -
rendered: boolean
(Read-only)Whether the markdown was succesfuly rendered or not. You can use
:not([rendered])
in your CSS to style the element differently before rendering.
Property
-
headings: Set<object>
(Read-only)List of all headings (h1, h2, ...) with an id and text content represented as follows:
{ id: string, level: number, title: string }
Example:
//<h2 id="user-content-links">Links</h2> { id: "user-content-links", title: "Links", level: 2 }
The returned
Set
is extended with an additionaltoHTML()
function:-
toHTML(options?: object): string
Which returns sanitized HTML string representing the table of contents from the headings (nested list).
<ul> <li><a href="#id">title</a></li> <li> <ul> <li><a href="#id">title</a></li> <li><a href="#id">title</a></li> </ul> </li> <ul/>
Options:
-
depth?: number
(6)How deep to list ? Headings start from 1 to 6.
-
ordered?: boolean
(false)When set to false the root of the list is
ul
otherwiseol
.
-
-
Methods
-
render(): Promise<void>
Load and render markdown into sanitized HTML.
✔️ Resolves when markdown has been sucesfully rendered.
❌ Rejects on error💡 Invoking this method still triggers related events.
-
estimateReadingTime(speed?: number): number
Estimate the "time to read" of the markdown's content in minutes.
By defaultspeed
is265
words per minute; the average reading speed of an adult (English).