markdown-pages
Static Site Generator for Markdown


Page contents

Tutorial

Install markdown-pages with the installation command first.

Sites

In a new folder, add a bit of Markdown to index.md and run markdown-pages. http://localhost:8000 will show the docs/index.html that results from converting index.md. The docs folder contains all the generated pages and assets of the site, it is the public directory. Any changes made to the sources will be reflected in docs automatically. Press CTRL+C to stop the site generator and the web server.

mkdir my-website
cd my-website
echo "# Home" > index.md
markdown-pages

Pages

To create a new page add a Markdown or HTML file. Markdown files are converted to HTML with the help of a template. HTML files are copied to the docs folder directly. Files with extensions .draft.md or .draft.html will be excluded, and so will the files in each line of .exclude. Add content to about.md for http://localhost:8000/about.html to appear.

echo "# About" > about.md
markdown-pages

Metadata

Pages themselves can set their metadata in the header. Add the required title, description and author information to about.md to change the values shown at http://localhost:8000/about.html.

---
title: My website
description: My website in cyberspace
author:
  - Jane Dev
---

# About

The metadata folder contains YAML files that set page metadata, but page headers will override them.

mkdir metadata

Copy the header of about.md inside metadata/about.yaml, and add page links to nav. Since the YAML file contains the same values, the page header can be removed.

---
title: My website
description: My website in cyberspace
author:
  - Jane Dev
nav:
  - label: Home
    href: index.html
  - label: About
    href: about.html
---

The metadata/default.yaml file is applied to all pages, but a metadata file for a page will override it. Move the contents of metadata/about.yaml to metadata/default.yaml, and http://localhost:8000 will show the new values.

mv metadata/about.yaml metadata/default.yaml

Sitemap

The sitemap at https://localhost:8000/sitemap.xml is generated from metadata/sitemap.yaml. The required parameters are base and url.loc. url.loc is relative to base.

---
base: https://www.my-website.net/
url:
  - loc: index.html
    lastmod: 2024-01-01
    changefreq: monthly
    priority: 0.5
---

Add a link to the sitemap in the navigation list of metadata/default.yaml.

---
title: My website
description: My website in cyberspace
author:
  - Jane Dev
nav:
  - label: Home
    href: index.html
  - label: About
    href: about.html
  - label: Sitemap
    href: sitemap.xml
---

Feed

The RSS feed at https://localhost:8000/rss.xml is generated from metadata/rss.yaml. channel, channel.link, channel.title, channel.description, item.link, item.title and item.description are required. item.link, image.url and enclosure.url are relative to channel.link. item.enclosure.length is in bytes.

Put a value like '{{index.html}}' in item.description to import the contents of that file, or use a normal string for a description in plain text.

---
channel:
  title: My website
  link: https://www.my-website.net/
  description: My website in cyberspace
item:
  - link: index.html
    title: Home
    description: '{{index.html}}'
---

Set the rss parameter and add a link to the RSS feed in the navigation list of metadata/default.yaml.

---
rss: rss.xml
title: My website
description: My website in cyberspace
author:
  - Jane Dev
nav:
  - label: Home
    href: index.html
  - label: About
    href: about.html
  - label: RSS
    href: rss.xml
  - label: Sitemap
    href: sitemap.xml
---

Assets

The assets folder can contain anything, it is copied entirely to docs. To copy any other file add it to a new line in .include.

mkdir assets
echo "There are more than 1 billion websites in cyberspace." > assets/fact.txt
echo "This project is open source." > LICENSE
echo LICENSE >> .include

In page documents like index.md files are accessed via the assets route, available at http://localhost:8000/assets.

# Home

- Download a [fact about the web](assets/fact.txt) in text format.
- Read the [LICENSE](LICENSE).

Stylesheets

Add a stylesheet to the assets folder, like assets/style.css.

main h1 {
  font-size: 3em;
}

Then add it to the metadata in metadata/default.yaml. style can include any URL that responds with a valid stylesheet.

---
rss: rss.xml
title: My website
description: My website in cyberspace
author:
  - Jane Dev
nav:
  - label: Home
    href: index.html
  - label: About
    href: about.html
  - label: RSS
    href: rss.xml
  - label: Sitemap
    href: sitemap.xml
style:
  - assets/style.css
---

Scripts

Add a script to the assets folder, like assets/script.js.

alert("Hello, visitor!");

Then add it to the metadata in metadata/default.yaml. script can include any URL that responds with a valid script.

---
rss: rss.xml
title: My website
description: My website in cyberspace
author:
  - Jane Dev
nav:
  - label: Home
    href: index.html
  - label: About
    href: about.html
  - label: RSS
    href: rss.xml
  - label: Sitemap
    href: sitemap.xml
style:
  - assets/style.css
script:
  - assets/script.js
---

Modules

Add a module to the assets folder, like assets/Counter.js.

import htm from "https://esm.sh/htm";
import { h, render } from "https://esm.sh/preact";
import { useState } from "https://esm.sh/preact/hooks";

const html = htm.bind(h);

export function Counter() {
  const [value, setValue] = useState(0);
  return html`<button onClick=${() => setValue(value + 1)}>
    Clicked ${value} ${value === 1 ? 'time' : 'times'}
  </button>`;
}

render(html`<${Counter} />`, document.getElementById("counter"));

Then add it to the metadata in metadata/default.yaml. module can include any URL that responds with a valid module.

---
rss: rss.xml
title: My website
description: My website in cyberspace
author:
  - Jane Dev
nav:
  - label: Home
    href: index.html
  - label: About
    href: about.html
  - label: RSS
    href: rss.xml
  - label: Sitemap
    href: sitemap.xml
style:
  - assets/style.css
script:
  - assets/script.js
module:
  - assets/Counter.js
---

To use the new module add an HTML element with id="counter" to a document like index.md.

# Home

- Download a [fact about the web](assets/fact.txt) in text format.
- Read the [LICENSE](LICENSE).

<p id="counter"></p>

Templates

The template folder contains HTML and XML files that use metadata to generate page layouts.

mkdir template

Templates are applied to pages by name. The template/index.html template is applied to index.md. template/default.html is applied to all pages, but a template for a page will override it.

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
$if(base)$
  <base href="$base$$file$">
$endif$
  <title>$title$</title>
$if(author)$$
for(author)$
  <meta name="author" content="$author$">
$endfor$
$endif$
$if(description)$
  <meta name="description" content="$description$">
$endif$
$if(rss)$
  <link rel="alternate" type="application/rss+xml" href="$rss$">
$endif$
$if(theme.style)$
$for(theme.style)$
  <link rel="stylesheet" href="$theme.style$">
$endfor$
$endif$
$if(style)$
$for(style)$
  <link rel="stylesheet" href="$style$">
$endfor$
$endif$
$if(theme.module)$
$for(theme.module)$
  <script type="module" src="$theme.module$"></script>
$endfor$
$endif$
$if(module)$
$for(module)$
  <script type="module" src="$module$"></script>
$endfor$
$endif$
</head>

<body>
  <header>
    <h1><a href="index.html">$title$</a>$if(description)$<br><small>$description$</small>$endif$</h1>
$if(nav)$
    <nav>$for(nav)$<a href="$nav.href$">$nav.label$</a>$sep$ / $endfor$</nav>
$endif$
  </header>

  <main>
$body$
$if(counter)$<p id="counter"></p>$endif$
  </main>

  <footer>
    <p><a href="index.html">$title$</a>$if(description)$<br><small>$description$</small>$endif$</p>
$if(nav)$
    <nav>$for(nav)$<a href="$nav.href$">$nav.label$</a>$sep$ / $endfor$</nav>
$endif$
  </footer>

$if(theme.script)$
$for(theme.script)$
  <script src="$theme.script$"></script>
$endfor$
$endif$
$if(script)$
$for(script)$
  <script src="$script$"></script>
$endfor$
$endif$
</body>

</html>

Add the counter parameter to the metadata in metadata/default.yaml to show the counter of the template.

---
rss: rss.xml
title: My website
description: My website in cyberspace
author:
  - Jane Dev
nav:
  - label: Home
    href: index.html
  - label: About
    href: about.html
  - label: RSS
    href: rss.xml
  - label: Sitemap
    href: sitemap.xml
style:
  - assets/style.css
script:
  - assets/script.js
module:
  - assets/Counter.js
counter: yes
---

And remove the counter element from index.md.

# Home

- Download a [fact about the web](assets/fact.txt) in text format.
- Read the [LICENSE](LICENSE).

Theme

The theme folder contains assets, metadata and templates used when the project does not provide them. In that case, it will get them from the installation.

mkdir -p theme/assets theme/metadata theme/template

The theme/assets folder can contain anything, it is copied entirely to docs. In page documents like index.md files are accessed via the /theme route, available at http://localhost:8000/theme. Use only the stylesheet and module.

mv assets/style.css assets/Counter.js theme/assets

Use the template from template/default.html and delete the empty folder.

mv template/default.html theme/template
rm -rf template

Put the metadata needed for a new project inside theme/metadata/default.yaml.

---
title: My website
description: My website in cyberspace
author:
  - Jane Dev
nav:
  - label: Home
    href: index.html
theme:
  style:
    - theme/style.css
  module:
    - theme/Counter.js
counter: yes
---

And leave only the metadata for the current project in metadata/default.yaml.

---
rss: rss.xml
nav:
  - label: Home
    href: index.html
  - label: About
    href: about.html
  - label: RSS
    href: rss.xml
  - label: Sitemap
    href: sitemap.xml
script:
  - assets/script.js
---

Tests

The test folder contains Spock test cases named *Spec.groovy.

mkdir test

To check that the homepage, sitemap and feed exist add a new test for it, like test/MySiteSpec.groovy.

/* groovylint-disable CompileStatic, JavaIoPackageAccess, NoWildcardImports */
/* groovylint-disable MethodName, FactoryMethodName, MethodReturnTypeRequired */
/* groovylint-disable ClassJavadoc, JUnitPublicNonTestMethod */

import spock.lang.*

class WebPagesSpec extends Specification {

    def 'at least the index source should exist'() {
    expect:
        new File('index.md').exists()
    }

    def 'at least the index output should exist'() {
    expect:
        new File('docs/index.html').exists()
    }

    def 'the sitemap source should exist'() {
    expect:
        new File('metadata/sitemap.yaml').exists()
    }

    def 'the sitemap output should exist'() {
    expect:
        new File('docs/sitemap.xml').exists()
    }

    def 'the feed source should exist'() {
    expect:
        new File('metadata/rss.yaml').exists()
    }

    def 'the feed output should exist'() {
    expect:
        new File('docs/rss.xml').exists()
    }

}

Publish

Go live for free at Neocities, GitLab Pages, or GitHub Pages. For self-hosting use the docs folder as the public directory of the HTTP server.

The base URL can be set in metadata files like metadata/default.yaml with base. Remember to update metadata/rss.yaml and metadata/sitemap.yaml accordingly.

---
base: https://janedev.github.io/my-website/
rss: rss.xml
nav:
  - label: Home
    href: index.html
  - label: About
    href: about.html
  - label: RSS
    href: rss.xml
  - label: Sitemap
    href: sitemap.xml
script:
  - assets/script.js
---

The .rewrite file can be used to change the name of the generated HTML. Each line should contain a key-value pair separated by :. A good example is using the README document as the index page.

README:index

Next steps

The Elements page lists everything the Markdown documents can show.