Jenaro Calvino

Collected Notes as a full-fledged CMS

Ok, all of the notes I've made about using Collected Notes were more oriented to creating fully static pages like blog posts, but what if you can use it to populate everything on your website and remain in control of all of the markup?

Let's try 11ty one more time.

This time, we'll create an empty starter project like last time, if you didn't read the last post, here is how to start:

mkdir collected-notes-powered && cd collected-notes-powered
yarn init -y
yarn add @11ty/eleventy axios

Add the scripts to our package.json

{
  "name": "collected-notes-powered",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "@11ty/eleventy": "^0.11.0",
    "axios": "^0.19.2"
  },
  "scripts": {
    "prod": "eleventy",
    "dev": "eleventy --serve"
  }
}

We are adding axios to request the data from collected notes, but you can use your preferred library, like node-fetch, keep in mind this will run at build time so the browser native fetch is not available.

We will create a file called index.md at the root level and all we'll add to it is the layout:

---
layout: layout.11ty.js
---

Then, under _includes/layout.11ty.js we'll make a standard, empty html template for now:

module.exports = async () => {
  return /*html*/`
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Home</title>
        </head>
        <body>
        </body>
        </html>
    `;
};

Now here comes the tricky part, to fill in the data, we'll use a custom "convention" to write markdown on Collected Notes and consume it later on our website. Here's how we'll write the markdown emulating frontmatter:

# Testing frontmatter

---

HERO

title: Example App

subtitle: This is a great example app!

--color: #000000

--backgroundColor: #ffffff

---

ABOUT

title: About Us

subtitle: This is where we tell you about our company!

--backgroundColor: #000000

--color: #ffffff

Now to consume the data on our website we will create a file under _data/cms.js and it should look something like this:

const axios = require("axios");

module.exports = async () => {
  // fetch markdown from collected notes
  const result = await axios.get(
    "https://collectednotes.com/<youruser>/<yournote>.md"
  );
  // split each section
  const sections = result.data.split("---");
  // parse sections and return them
  const sectionsData = sections
    .map(parseToObject)
    .reduce((acc, curr) => ({ ...acc, ...curr }), {});
  return sectionsData;
};

function parseToObject(section) {
  // split on newline and only keep non empty parts
  const parts = section.split("\n").filter((p) => !!p);
  let sectionName = "";
  // grab each key value pair for each section
  const sectionValues = parts.reduce((acc, curr, index) => {
    if (index === 0) {
      sectionName = curr;
    }
    const objParts = curr.split(": ");
    if (objParts.length === 2) {
      acc = { ...acc, [objParts[0]]: objParts[1] };
    }

    return acc;
  }, {});

  return { [sectionName]: sectionValues };
}

Now we have an object that looks like this:

{
  '# Testing frontmatter': {},
  HERO: {
    title: 'Example App',
    subtitle: 'This is a great example app!',
    '--color': '#000000',
    '--backgroundColor': '#ffffff'
  },
  ABOUT: {
    title: 'About Us',
    subtitle: 'This is where we tell you about our company!',
    '--backgroundColor': '#000000',
    '--color': '#ffffff'
  }
}

And this is available to us on the layout.11ty.js file with the name cms, because that is how we named the file under the _data folder.

We'll add the following to layout.11ty.js just to demonstrate the possibilities:

const HomeTemplate = require('../components/HomeTemplate.11ty')
const AboutTemplate = require('../components/AboutTemplate.11ty')

module.exports = async ({ cms }) => {
       //...
        <body>
          <style>
            section {
              background-color: var(--backgroundColor);
              color: var(--color);
            }
          </style>
            ${HomeTemplate(cms.HERO)}
            ${AboutTemplate(cms.ABOUT)}
        </body>
        //...

Now let's create those components, you can add them wherever you want, but as you can see I added them at ./components/

And they both look like this: (this is the about page but the hero looks the same)

module.exports = (about) => {
  return /*html*/`
    <section id="about" style="--color: ${about["--color"]}; --backgroundColor: ${about["--backgroundColor"]}">
        <h1>About</h1>
        <h2>${about.title}</h2>
        <p>${about.subtitle}</p>
    </section>
    `;
};

Now you can see that running yarn dev on your terminal will start a development server and by navigating to http://localhost:8080 you can see your content!

Now imagine the possibilities, imagine you are using pagination as we did on the previous 11ty post to showcase your case studies, you could actually add css variables with different theme colors and for each case study customize the rendered website with said colors. You could mix and match this data file with the same data file as before and render blog posts on one hand and case studies on the other.

Hope you enjoyed this post and it was useful, as a footnote, I should clarify Collected Notes allows you to set a note as unlisted, I would set a CMS file as unlisted and if needed do some filtering on our website.


over 3 years ago

Jenaro Calviño