Rendering Markdown on the Web using zero-md

This post demonstrates the use of zero-md to render an external markdown file in a web page, and populate a navigation element.
markdown
zero-md
marked
Author

Chris Hansen

Published

March 23, 2024

Overview

Markdown was originally conceived as an easy-to-use text format that could be rendered to HTML.1 It has become extremely popular since its introduction, and many tools that target the web support it. For example:

  • static site generators such as Hugo allow content to be written in markdown
  • content management systems such as Tina allow content to be written in markdown
  • MDX allows users to embed JavaScript components via JSX in markdown documents
  • websites such as stack overflow allow users to author questions and answers in markdown
  • online DevOps platforms such as GitHub or GitLab can render markdown files
  • collaboration tools such as Microsoft Teams often support a subset of markdown in chats

But markdown has also grown beyond the original use case of simply providing an easier format for creating content for the web. Pandoc, for example, is an amazing tool which lets users convert any number of different markup formats into another, including markdown. That is, we can author documents using markdown, but target any number of different formats for our final output, including Microsoft Word (docx) or PDF.

In this post we look at a reasonably simple use case whereby we author a markdown document, and then include it in an existing website using zero-md–a native markdown-to-html convertor. Displaying an external markdown file is something readily supported by zero-md.2 We extend this basic use case by parsing the rendered content after render, and appending all section headings as links in a navigation element.

A Simple Example

The simplest example involves us just including a markdown file directly in an HTML page. Consider the following files:

<html>
  <head>
    <script
      type="module"
      src="https://cdn.jsdelivr.net/gh/zerodevx/zero-md@2/dist/zero-md.min.js"
    ></script>
  </head>
  <body>
    <zero-md src="ex.md"></zero-md>
  </body>
</html>
# Elit Cillum Id Labore

In frankfurter enim dolore, occaecat ea exercitation hamburger nostrud nulla. Ball tip labore capicola meatloaf veniam prosciutto. Dolor magna est pork loin, nulla pork cillum minim proident deserunt dolore ham hock boudin. Anim flank nostrud consequat velit spare ribs quis tail in.


## Nostrud beef tail ex bresaola strip steak

Andouille pastrami jerky chicken ground round, tempor voluptate cow officia dolore kevin quis flank brisket. Eiusmod picanha sint sausage deserunt pariatur meatball chuck meatloaf capicola ball tip officia strip steak kielbasa consectetur. Aliquip sausage short ribs, eu officia tail cow. Ground round burgdoggen short loin ut ea tri-tip. Meatloaf ut occaecat, kielbasa pastrami aute biltong non voluptate cillum t-bone. Esse sausage alcatra rump magna. Tail shank bresaola porchetta capicola beef ribs nostrud ullamco drumstick.

* Fugiat picanha jerky beef ribs labore ut leberkas eiusmod ipsum filet mignon cow ut sirloin duis. 
* Est cupim shoulder pork consectetur elit chuck ullamco pig ex corned beef ham ipsum deserunt. 
* Irure incididunt eu, id frankfurter cupim drumstick hamburger leberkas. 


## Magna venison sausage non

Pork belly ad pig tenderloin. Pork belly commodo non sed pork loin anim drumstick pork strip steak pariatur chuck. Lorem velit in strip steak aute andouille quis. Beef ribs sirloin in voluptate, consequat sausage nulla. Nulla in brisket shank.

Esse nostrud filet mignon aute. Alcatra biltong porchetta andouille nisi. Fatback irure ut, ex turkey qui pariatur. Nostrud sed tail, filet mignon et strip steak eu veniam ea frankfurter duis cupidatat. Non tenderloin incididunt landjaeger veniam dolore minim in pork chop burgdoggen. Boudin et cupim do tri-tip tenderloin in ribeye tongue flank enim incididunt anim officia. Ground round id exercitation adipisicing.


## Dolor consectetur pork loin esse doner

Laborum prosciutto nisi, fatback mollit et Elit cillum id labore cillum id laborebacon tail porchetta laboris ribeye reprehenderit drumstick tempor. Ipsum id occaecat mollit. Dolore chicken flank ex occaecat. Deserunt excepteur turducken aliquip cillum tenderloin non quis jerky picanha in. Shank nulla nostrud turducken commodo pancetta flank.

Burgdoggen non pastrami exercitation incididunt magna officia aliquip nostrud in ex et andouille. Swine burgdoggen ham hock shoulder dolore pastrami. Proident beef ribs ut, picanha occaecat spare ribs rump ground round cupim nostrud drumstick pork loin exercitation sint doner. Boudin buffalo occaecat swine incididunt corned beef lorem meatloaf tongue.


## Spare ribs et id, officia chuck dolore nostrud laboris enim tenderloin. 

Consectetur nisi meatloaf, tenderloin aute ipsum non ea irure ribeye. Ut turkey ham, chicken t-bone non laborum cillum chislic ribeye tempor. Brisket tail short ribs enim, aute in pastrami proident pork. Minim buffalo meatloaf beef ex incididunt burgdoggen capicola dolore ullamco laboris exercitation qui consequat. Ipsum beef ribs pancetta enim dolore rump.

Aute pancetta ad esse burgdoggen hamburger salami prosciutto t-bone et aliqua aliquip. Ipsum fatback biltong cupim ball tip picanha, sint swine eiusmod ham hock anim et. Mollit do magna aute burgdoggen ullamco beef adipisicing biltong non short ribs swine sint. Spare ribs in bacon fatback cillum, corned beef mollit sausage hamburger consequat pork belly shoulder proident turkey. Filet mignon anim quis, dolore hamburger boudin shankle aute.

The result of this (with a small amount of sytling added) is as follows:

A Less Simple Example

Let us consider a basic page, coded entirely in HTML (with CSS and JavaScript), consisting of a basic navigation function. It might be coded as follows:

<html>
  <head>
    <link href="style.css" rel="stylesheet">
    <script src="scroll.js"></script>
  </head>
  <body>
    <div id="nav" class="nav"> 
      <ul>
      <li><a class="top" href="#elit-cillum-id-labore">Elit Cillum Id Labore</a></li>
      <li><a href="#nostrud-beef-tail-ex-bresaola-strip-steak">Nostrud beef tail ex bresaola strip steak</a></li>
      <li><a href="#magna-venison-sausage-non">Magna venison sausage non</a></li>
      </ul>
    </div>

    <div class="content">
      <h1 id="elit-cillum-id-labore">Elit Cillum Id Labore</h1>

      In frankfurter enim dolore, occaecat ea exercitation hamburger nostrud nulla. Ball tip labore capicola meatloaf veniam prosciutto. Dolor magna est pork loin, nulla pork cillum minim proident deserunt dolore ham hock boudin. Anim flank nostrud consequat velit spare ribs quis tail in.
      
      <h2 id="nostrud-beef-tail-ex-bresaola-strip-steak">Nostrud beef tail ex bresaola strip steak</h2>
      
      Andouille pastrami jerky chicken ground round, tempor voluptate cow officia dolore kevin quis flank brisket. Eiusmod picanha sint sausage deserunt pariatur meatball chuck meatloaf capicola ball tip officia strip steak kielbasa consectetur. Aliquip sausage short ribs, eu officia tail cow. Ground round burgdoggen short loin ut ea tri-tip. Meatloaf ut occaecat, kielbasa pastrami aute biltong non voluptate cillum t-bone. Esse sausage alcatra rump magna. Tail shank bresaola porchetta capicola beef ribs nostrud ullamco drumstick.
      
      <ul>
      <li>Fugiat picanha jerky beef ribs labore ut leberkas eiusmod ipsum filet mignon cow ut sirloin duis.</li>
      <li>Est cupim shoulder pork consectetur elit chuck ullamco pig ex corned beef ham ipsum deserunt.</li> 
      <li>Irure incididunt eu, id frankfurter cupim drumstick hamburger leberkas.</li>
      </ul>
      
      <h2 id="magna-venison-sausage-non">Magna venison sausage non</h2>
      
      Pork belly ad pig tenderloin. Pork belly commodo non sed pork loin anim drumstick pork strip steak pariatur chuck. Lorem velit in strip steak aute andouille quis. Beef ribs sirloin in voluptate, consequat sausage nulla. Nulla in brisket shank.
      
      Esse nostrud filet mignon aute. Alcatra biltong porchetta andouille nisi. Fatback irure ut, ex turkey qui pariatur. Nostrud sed tail, filet mignon et strip steak eu veniam ea frankfurter duis cupidatat. Non tenderloin incididunt landjaeger veniam dolore minim in pork chop burgdoggen. Boudin et cupim do tri-tip tenderloin in ribeye tongue flank enim incididunt anim officia. Ground round id exercitation adipisicing.
    </div>
  </body>
</html>
body {
  margin: 0;
  color: rgb(52, 58, 64);
}

.nav {
  margin: 0;
  padding: 0;
  width: 200px;
  background-color: rgb(222, 226, 230);
  position: fixed;
  height: 100vh; 
  overflow: auto;
}

.nav a {
  display: block;
  color: rgb(52, 58, 64); 
  padding: 16px;
  text-decoration: none;
}

.nav a.top {
  background-color: rgb(17, 122, 101);
  color: #FFFFFF;
}

.nav a:hover:not(.top) {
  background-color: #555;
  color: #FFFFFF;
}

.nav ul {
  list-style-type: none;
  margin: 0;
  padding: 0;
}

div.content {
  margin-left: 200px;
  padding: 1px 16px;
}
window.onload = function() {
  const nav =  document.getElementById("nav");
  const navitems = document.querySelectorAll("#nav li > a");
  navitems.forEach((navitem) => {
    const section = document.getElementById(navitem.getAttribute("href").substring(1));
    navitem.onclick = (e) => {
      e.preventDefault();
      section.scrollIntoView({ behavior: "smooth", block: "nearest" });
    }
  })
}

This result looks as follows:

We can achieve something similar using zero-md–we simply load the markdown file in the same way as the simple example above, but we then run a little javascript which finds all the <h1> and <h2> tags, and then inserts an entry into the navigation panel. Consider the following files:

<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="../css/style.css" rel="stylesheet">
    <script
      type="module"
      src="https://cdn.jsdelivr.net/gh/zerodevx/zero-md@2/dist/zero-md.min.js"
    ></script>
    <script src="scroll.js"></script>
  </head>
  <body>
    <div id="nav" class="nav"> 
      <ul>
        <li></li>
      </ul>
    </div>

    <div class="content">      
      <zero-md id="md" src="ex.md"></zero-md>
    </div>
  </body>
</html>
# Elit Cillum Id Labore

In frankfurter enim dolore, occaecat ea exercitation hamburger nostrud nulla. Ball tip labore capicola meatloaf veniam prosciutto. Dolor magna est pork loin, nulla pork cillum minim proident deserunt dolore ham hock boudin. Anim flank nostrud consequat velit spare ribs quis tail in.


## Nostrud beef tail ex bresaola strip steak

Andouille pastrami jerky chicken ground round, tempor voluptate cow officia dolore kevin quis flank brisket. Eiusmod picanha sint sausage deserunt pariatur meatball chuck meatloaf capicola ball tip officia strip steak kielbasa consectetur. Aliquip sausage short ribs, eu officia tail cow. Ground round burgdoggen short loin ut ea tri-tip. Meatloaf ut occaecat, kielbasa pastrami aute biltong non voluptate cillum t-bone. Esse sausage alcatra rump magna. Tail shank bresaola porchetta capicola beef ribs nostrud ullamco drumstick.

* Fugiat picanha jerky beef ribs labore ut leberkas eiusmod ipsum filet mignon cow ut sirloin duis. 
* Est cupim shoulder pork consectetur elit chuck ullamco pig ex corned beef ham ipsum deserunt. 
* Irure incididunt eu, id frankfurter cupim drumstick hamburger leberkas. 


## Magna venison sausage non

Pork belly ad pig tenderloin. Pork belly commodo non sed pork loin anim drumstick pork strip steak pariatur chuck. Lorem velit in strip steak aute andouille quis. Beef ribs sirloin in voluptate, consequat sausage nulla. Nulla in brisket shank.

Esse nostrud filet mignon aute. Alcatra biltong porchetta andouille nisi. Fatback irure ut, ex turkey qui pariatur. Nostrud sed tail, filet mignon et strip steak eu veniam ea frankfurter duis cupidatat. Non tenderloin incididunt landjaeger veniam dolore minim in pork chop burgdoggen. Boudin et cupim do tri-tip tenderloin in ribeye tongue flank enim incididunt anim officia. Ground round id exercitation adipisicing.


## Dolor consectetur pork loin esse doner

Laborum prosciutto nisi, fatback mollit et Elit cillum id labore cillum id laborebacon tail porchetta laboris ribeye reprehenderit drumstick tempor. Ipsum id occaecat mollit. Dolore chicken flank ex occaecat. Deserunt excepteur turducken aliquip cillum tenderloin non quis jerky picanha in. Shank nulla nostrud turducken commodo pancetta flank.

Burgdoggen non pastrami exercitation incididunt magna officia aliquip nostrud in ex et andouille. Swine burgdoggen ham hock shoulder dolore pastrami. Proident beef ribs ut, picanha occaecat spare ribs rump ground round cupim nostrud drumstick pork loin exercitation sint doner. Boudin buffalo occaecat swine incididunt corned beef lorem meatloaf tongue.


## Spare ribs et id, officia chuck dolore nostrud laboris enim tenderloin. 

Consectetur nisi meatloaf, tenderloin aute ipsum non ea irure ribeye. Ut turkey ham, chicken t-bone non laborum cillum chislic ribeye tempor. Brisket tail short ribs enim, aute in pastrami proident pork. Minim buffalo meatloaf beef ex incididunt burgdoggen capicola dolore ullamco laboris exercitation qui consequat. Ipsum beef ribs pancetta enim dolore rump.

Aute pancetta ad esse burgdoggen hamburger salami prosciutto t-bone et aliqua aliquip. Ipsum fatback biltong cupim ball tip picanha, sint swine eiusmod ham hock anim et. Mollit do magna aute burgdoggen ullamco beef adipisicing biltong non short ribs swine sint. Spare ribs in bacon fatback cillum, corned beef mollit sausage hamburger consequat pork belly shoulder proident turkey. Filet mignon anim quis, dolore hamburger boudin shankle aute.
body {
  margin: 0;
  color: rgb(52, 58, 64);
  font-family: Roboto, -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}

.nav {
  margin: 0;
  padding: 0;
  width: 200px;
  background-color: rgb(222, 226, 230);
  position: fixed;
  height: 100vh; 
  overflow: auto;
}

.nav li {
  list-style: none;
}

.nav a {
  display: block;
  color: rgb(52, 58, 64); 
  padding: 16px;
  text-decoration: none;
}

.nav .l1 a {
  background-color: rgb(17, 122, 101);
  color: #FFFFFF;
}

.nav li:not(.l1) > a:hover {
  background-color: #555;
  color: #FFFFFF;
}

.nav ul {
  list-style-type: none;
  margin: 0;
  padding: 0;
}

div.content {
  margin-left: 200px;
  padding: 1px 16px;
}
window.onload = function() {
  const app = document.querySelector("#md");
  app.addEventListener('zero-md-rendered', async () => {
    const nav =  document.getElementById("nav");
    const root = app.shadowRoot;
    const sections = root.querySelectorAll("h1, h2");
    sections.forEach((section) => {
      const li = document.createElement("li");
      if (section.nodeName == "H1") li.classList.add("l1")
      const a = document.createElement("a");
      a.href = "#" + section.id;
      a.innerHTML = section.getInnerHTML();
      a.onclick = () => section.scrollIntoView({ behavior: "smooth", block: "nearest" });
      li.append(a);
      nav.append(li);
    })
  });
}

The result of this is:

Summary

Markdown is a simple format that is relatively easy for content creators to use. And while many content management systems or static site generators support rendering markdown to HTML, one may have a legacy setup which doesn’t. In that case, an approach like the one shown here could be useful compromise.