ssg  
Where to Discuss?

Local Group

Preface

Goal: More about blog post content, Header, Footer, and Navigation.

Source Code

This article use tutor-08 theme. We will create it step by step.

Layout Preview for Tutor 08

Nunjucks: Layout Preview for Tutor 08


1: Prepare

This preparation is required.

Preview: General

Consider redesign the looks of blog post. This is what we want to achieve in this tutorial.

11ty Content: Preview All Artefacts

Layout: Nunjucks Post

We need to redesign layout/post.njk partial.

{% block main %}
  {% set color = color or 'blue' %}

  <main role="main" 
        class="column is-two-thirds">

    <section class="main-wrapper {{ color }}">
      <div class="blog white z-depth-3 hoverable">

        <section class="blog-header {{ color }} lighten-5">
          {% include "post/blog-header.njk" %}
        </section>

        <article class="blog-body" itemprop="articleBody">
          {% if toc %}
            {% include toc %}
          {% endif %}

          <div class="blog-content">
            {{ content | safe }}
          </div>

          {% include "post/navigation.njk" %}
        </article>

        <section class="blog-footer {{ color }} lighten-5">
          {% include "post/blog-footer.njk" %}
        </section>

      </div>
    </section>

  </main>
{% endblock %}

11ty Content: ViM Post Layout

Talking about schema, you can read this nice site:

Partials: New Artefacts

The code above require three new partials.

Create three empty partial artefacts,

  • views/_includes/post/blog-header.njk,

  • views/_includes/post/blog-footer.njk,

  • views/_includes/post/navigation.njk.

And one more artefact that is required in post/blog-header.njk.

  • views/_includes/post/time-elapsed.njk

SASS: Main

And add relevant stylesheet

...

// Tailor Made
@import "main/layout-page"
@import "main/layout-content"
@import "main/decoration"
@import "main/pagination"
@import "main/list"

@import "post/content"
@import "post/title"
@import "post/list"
@import "post/navigation"
@import "post/reset-highlight"

Even with Bulma, we still need adjustment. For example, you need to reset the font size, in Bulma Heading.


2: Header

Consider begin with header.

Preview

11ty Content: Blog Post Header

Partial: Header: Minimum

The minimum header is simply showing title.

  <div class="main_title"> 
    <h2 class="title is-4" itemprop="name headline">
      <a href="{{ page.url | url }}">
        {{ renderData.title or title or metadata.title }}
      </a></h2>
  </div>

Partial: Header: Meta

Consider add meta data in post header.

  • Author
    {% if (author or metadata.author) %}
    <div class="is-pulled-left">
    <span class="meta_author tag is-small is-dark
                 indigo z-depth-1 hoverable">
        <span class="fa fa-user"></span>
        &nbsp;
        <span itemprop="author"
              itemscope itemtype="http://schema.org/Person">
        <span itemprop="name">{{ author or metadata.author }}</span></span>            
    </span>
    &nbsp;
    </div>
    {% endif %} 
  • Time Elapsed (include partial)
    <div class="is-pulled-left">
      <span class="meta-time tag is-small is-dark
                   green z-depth-1 hoverable">
      {% include "post/time-elapsed.njk" %}
      </span>
      &nbsp;
    </div>
  • Tags
    <div class="is-pulled-right">
    {% for tag in tags %}
      {% set tagUrl %}/tags/{{ tag | slug }}/{% endset %}
        <a href="{{ tagUrl | url }}">
          <span class="tag is-dark is-small teal z-depth-1 hoverable">
            <span class="fa fa-tag"></span>&nbsp;{{ tag }}</span></a>
      {% endfor %}
      &nbsp;
    </div>

I use bulma tag, with the eye candy Awesome Font.

Partial: Header: Complete Code

Here below is my actual code.

  <div class="main_title"> 
    <h2 class="title is-4" itemprop="name headline">
      <a href="{{ page.url | url }}">
        {{ renderData.title or title or metadata.title }}
      </a></h2>
  </div>

  <div class="field p-t-5">
    {% if (author or metadata.author) %}
    <div class="is-pulled-left">
    <span class="meta_author tag is-small is-dark
                 indigo z-depth-1 hoverable">
        <span class="fa fa-user"></span>
        &nbsp;
        <span itemprop="author"
              itemscope itemtype="http://schema.org/Person">
        <span itemprop="name">{{ author or metadata.author }}</span></span>            
    </span>
    &nbsp;
    </div>
    {% endif %} 

    <div class="is-pulled-left">
      <span class="meta-time tag is-small is-dark
                   green z-depth-1 hoverable">
      {% include "post/time-elapsed.njk" %}
      </span>
      &nbsp;
    </div>

    <div class="is-pulled-right">
    {% for tag in tags %}
      {% set tagUrl %}/tags/{{ tag | slug }}/{% endset %}
        <a href="{{ tagUrl | url }}">
          <span class="tag is-dark is-small teal z-depth-1 hoverable">
            <span class="fa fa-tag"></span>&nbsp;{{ tag }}</span></a>
      {% endfor %}
      &nbsp;
    </div>

  </div>

  <div class="is-clearfix p-b-5"></div>

3: Elapsed Time

As it has been mentioned above, we need special partial for this.

Issue

As a static generator, eleventy build the content whenever there are any changes. If there are no changes, the generated time remain static. It means we cannot tell relative time in string such as three days ago, because after a week without changes, the time remain three days ago, not changing into ten days ago.

The solution is using javascript. You can download the script from here

That way the time ago is updated dynamically as time passes, as opposed to having to rebuild eleventy every day.

Do not forget to put the script in static directory.

Partial: Time Elapsed

    <time datetime="{{ page.date | date() }}" 
          itemprop="datePublished">
    <span class="timeago"
          datetime="{{ page.date | date('Y-MM-DD hh:mm:ss') }}">
    </span></time>
    &nbsp; 

    <script src="{{ "/assets/js/timeago.min.js" | url }}"></script>
    <script type="text/javascript">
      var timeagoInstance = timeago();
      var nodes = document.querySelectorAll('.timeago');
      timeagoInstance.render(nodes, 'en_US');
      timeago.cancel();
      timeago.cancel(nodes[0]);
    </script>

We already have header. Why do not we continue with footer?

Preview

11ty Content: Blog Post Footer

  <footer class="columns m-5">

    <div class="column is-narrow has-text-centered">
      <img src="{{ "/assets/images/license/cc-by-sa.png" | url }}" 
           class="bio-photo" 
           height="31"
           width="88"
           alt="CC BY-SA 4.0"></a>
    </div>

    <div class="column has-text-left">
      This article is licensed under:&nbsp;
      <a href="https://creativecommons.org/licenses/by-sa/4.0/deed" 
         class="text-dark">
        <b>Attribution-ShareAlike</b>
        4.0 International (CC BY-SA 4.0)</a>
    </div>

  </footer>

Assets: License

I have collect some image related with license, so that you can use license easily.


5: Post Navigation

Each post also need simple navigation.

Preview

11ty Content: Blog Post Navigation

Issue

By default, eleventy does not have built in function, to handle post navigation. After duckduckwent fo a while, I found this issue below:

Luckily, in that thread, there is already a solution from Boris Schapira. He propose to use collection, and it works well for me.

Colection: postsPrevNext

We need to add postPrevNext filter in .eleventy.js:

  // Modified from Boris Schapira
  eleventyConfig.addCollection("postsPrevNext", function(collection) {
    var posts = collection.getAllSorted().filter(function(item) {
      // Filter by layout name
      return "post" === item.data.layout;
    });

    return helper.addPrevNext(posts);
  });

Where the code in helper.js file is as below:

 
// https://github.com/11ty/eleventy/issues/426
// Copy paste from Boris Schapira

exports.addPrevNext = function (collectionArray) {
  const l = collectionArray.length;
  for (let p = 0; p < l; p++) {
    if (p > 0)
      collectionArray[p].data.previous = {
        title: collectionArray[p - 1].data.title,
        url: collectionArray[p - 1].url
      };
    if (p < l - 1)
      collectionArray[p].data.next = {
        title: collectionArray[p + 1].data.title,
        url: collectionArray[p + 1].url
      };
  }
  return collectionArray;
}

Later you can use this filter as code below:

{% set posts = collections.postsPrevNext %}
{% for post in posts %}
  ...
{% endfor %}

Then you can access post.data.previous and post.data.next.

How does it works?

Magic

Imagine a blog with number of posts is 17. It has array of [0..l6] of post,

  • For array [1..16]: add previous post data.

  • For array [0..15]: add next post data.

I can understand the logic, but I still amaze, how he find the solution, and also pour in code well.

Partial: Navigation

{% set posts = collections.postsPrevNext %}

{% for post in posts %}
  {% if (post.url == page.url) %}

<nav class="pagination is-centered" 
     role="navigation" aria-label="pagination">

    <!-- Previous Page. -->
    {% if post.data.previous %}
      <a class="button is-small pagination-previous post-previous"
         href="{{ post.data.previous.url | url }}"
         title="{{ post.data.previous.title }}">
        <span class="fas fa-chevron-left"></span>&nbsp;&nbsp;</a>
    {% endif %}

    {% if post.data.next %}
      <a class="button is-small pagination-next post-next"
         href="{{ post.data.next.url | url }}" 
         title="{{ post.data.next.title }}">&nbsp;&nbsp;
        <span class="fas fa-chevron-right"></span></a>
    {% endif %}
</nav>

  {% endif %}
{% endfor %}

SASS: Post Navigation

Code above require two more classes

  1. post-previous

  2. post-next

// -- -- -- -- --
// _post-navigation.sass

a.post-previous:after
  content: " Previous"

a.post-next:before
  content: "Next "

a.post-previous:hover,
a.post-next:hover
  background-color: map-get($yellow, 'lighten-2')
  color: #000

.


6: Before Content: Table of Content

Sometimes we need to show recurring content, such as table of content in article series. For article series, we only need one TOC. And we do not want to repeat ourself, writing it over and over again.

To solve this case, we need help form frontmatter.

Layout: Nunjucks Post

We are going to insert TOC, before the content, by including toc something partial, provided from frontmatter.

{% block main %}
  ...

        <article class="blog-body" itemprop="articleBody">
          {% if toc %}
            {% include toc %}
          {% endif %}

          <div class="blog-content">
            {{ content | safe }}
          </div>

          {% include "post/navigation.njk" %}
        </article>
  ...
{% endblock %}

Page Content: Example Frontmatter

Now here it is, the TOC in frontmatter, as shown in this example below.

---
layout    : post
title     : Marilyn Manson - Redeemer
date      : 2015-07-25 07:35:05

tags      : ["industrial metal", "90s"]
keywords  : ["OST", "Queen of the Damned"]

author    : marylin
toc       : "toc/2015-07-marylin.njk"
---

...

Layout: Nunjucks TOC

Now you can have your TOC here.

  <div class="white hoverable p-t-5 p-b-5">
    <div class="widget-header blue lighten-4">

      <strong>Table of Content</strong>
      <span class="fa fa-archive is-pulled-right"></span>

    </div>
    <div class="widget-body blue lighten-5">

      <ul class="widget-list">
        <li><a href="{{ "/lyrics/eurythmics-sweet-dreams/" | url }}"
              >Marilyn Manson - Sweet Dreams</a></li>
        <li><a href="{{ "/lyrics/marylin-manson-redeemer/" | url }}"
              >Marilyn Manson - Redeemer</a></li>
      </ul>

    </div>
  </div>

Notice the .njk file extension. We are still using nunjucks.

Render: Browser

Now you can see the result in the browser.

11ty Raw HTML: Table of Content


7: Conclusion

It is enough for now. There are many part that can be enhanced, in about content area.

After all, it is about imagination.


What is Next ?

Consider continue reading [ Eleventy - Content - Markdown ]. We are going to explore markdown in blog post content.

Thank you for reading.