Adding a sidebar to this blog

Contents

One thing I’ve noticed about the more visually appealing and user friendly blogs is they have sidebars. So this week I set about adding one to this blog.

I use Nikola for building my blog, and there’s a sidebar plugin for it. But it’s far from plug and play. The plugin can generate the required HTML for the sidebar, but it’s up to the user to get it displaying in the output. That means playing around with the template system and CSS, and adding the sidebar content to the HTML pages generated by Nikola.

Adding translations for the sidebar plugin

The plugin’s rather sparse documentation mentions the following almost in passing:

Note that the default templates expect some extra messages (translations), namely:

  • Recent Posts
  • Archives
  • Categories
  • Tags

But it gives no pointers on how to go about doing this. My solution was to update the base Nikola messages_en.py file at the following location:
  nikola/lib/python3.7/site-packages/nikola/data/themes/base/messages/messages_en.py

I added the following lines:

"Recent Posts": "Recent Posts",
"Archives": "Archives",

Adding sidebar content to the generated HTML files

Next up was getting the content of the generated sidebar_en.inc file included with the rest of the blog. From the documentation:

The generated include file, one per language, must be somehow included in the rest of the blog. This can be done by using JavaScript to dynamically include the file, or by using tools like File Tree Subs.

(Emphasis mine.)

It seems to me that using JavaScript to include content is rather inconsistent with the idea of Nikola being a static site builder. The File Tree Subs tool mentioned is one that was written by the plugin’s author, but when I reviewed it I saw it wanted a yaml configuration file, and I don’t like hand-writing yaml. It also uses doit, which in my opinion is overkill for this task.

But even before I could get around to adding the sidebar content to the HTML files generated by Nikola, I needed to modify the theme’s base template file (base.tmpl) to indicate where the sidebar should go.

<div class="container" id="content" role="main">
    <div class="body-content">
        <!--Body content-->
        ${template_hooks['page_header']()}
        <%block name="content"></%block>
        <!--End of body content-->

        <footer id="footer">
            ${content_footer}
            ${template_hooks['page_footer']()}
        </footer>
    </div>
    <!-- The following four lines were added for the sidebar -->
    <div class="sidebar-content">
        <!-- Sidebar content starts -->
        <!-- Sidebar content ends -->
    </div>
</div>

(Positioning the sidebar is left to CSS and explained in the next section.)

Rather than make use of the good work put in to File Tree Subs, I applied the Not Invented Here principle and rolled my own updater.

##---------------------------------------------------------------------------##
#   update_sidebar_content: Like all the other items in the blog, the sidebar
#   is static. But 'nikola build' updates only 'output/sidebar.inc'; it's
#   up to this function to update it in all the generated files. 
##---------------------------------------------------------------------------##
function update_sidebar_content {
    local TEMP_FN="/tmp/blog-buid.add-sidebar.$$.tmp"
    cd output

    # Compute an sha1 hash value for the current sidebar content
    SHA1SUM="$(grep -v 'Sidebar sha1sum:' sidebar-en.inc | tee $TEMP_FN |
        sha1sum | cut -f1 -d' ')"
    echo "<!-- Sidebar sha1sum: $SHA1SUM -->" >>sidebar-en.inc

    # Update the sidebar as needed in the post files. If it changes, every post
    # needs to be updated to get the new sidebar.
    find . -name '*html' | xargs grep -l 'Sidebar content starts' | while read FILE
    do
        unset SB_START SB_END SB_SHA1SUM
        # The mawk program generates three environment variables:
        #  SB_START=number  Line where 'Sidebar content starts' was found
        #  SB_END=number    Line where 'Sidebar content ends' was found
        #  SB_SHA1SUM=sum   Sidebar sha1 as found in the post file
        # The 'eval' brings these variables into the environment
        eval "$(mawk '/Sidebar content starts/{ print "SB_START=" NR }
            /Sidebar sha1sum:/{ print "SB_SHA1SUM=\"" $4 "\"" }
            /Sidebar content ends/{ print "SB_END=" NR }' $FILE)"

        # Update the sidebar content if its sha1 value changed or wasn't found
        if [ "$SB_SHA1SUM" != "$SHA1SUM" ]
        then
            echo $FILE
            (head -n $SB_START $FILE
                sed 's:href=":href="/blog:g' sidebar-en.inc
                tail -n +$SB_END $FILE) >$TEMP_FN
            mv $TEMP_FN $FILE
        fi
    done
    rm -f $TEMP_FN
    cd ..
}

Positioning the sidebar using CSS, take 1

Next up was getting the sidebar on the side of the output instead of inline. With CSS it was pretty straightforward. I added the following lines to files/assets/css/custom.css:

div.body-content {
    width: 75%;
    float: left;
}
div.sidebar-content {
    display: block;
    width: 24%;
    float: right;
    font-size: 75%;
    padding: 1em 1em 1em 1em;
    border: solid lightgrey 1px;
}
div.sidebar-content h2 {
    font-size: 200%;
    background: lightblue;
    padding-left: 1em;
}

Removing the sidebar from program listings

Then I noticed a problem: the sidebar was appearing on program listing pages, making the listings too narrow and hiding the ends of many lines.

The solution to this was to use the template system to alter the output for listing pages. Nikola makes many variables available to the template system. One of these is a list named pagekind, which indicates what type of page is being generated. A program listing page includes the value listing in the pagekind list.

First up was adding a CSS tweak to re-expand the body-content div to 100% on listing pages. I added the following at the top of themes/bootstrap4/templates/base.tmpl:

## -*- coding: utf-8 -*-
<%namespace name="base" file="base_helper.tmpl" import="*" />
<%namespace name="notes" file="annotation_helper.tmpl" import="*" />
${set_locale(lang)}
${base.html_headstart()}
<%block name="extra_head">
### Leave this block alone.
</%block>
${template_hooks['extra_head']()}
<!-- No sidebar on for listing pages, so expand body-content to 100% -->
\%if 'listing' in pagekind:
<style type="text/css">
    div.body-content { width: 100%; }
</style>
\%endif
</head>

Then I told the template not to generate the sidebar for listing pages:

    <!-- The following six lines were added for the sidebar -->
\%if not 'listing' in pagekind:
    <div class="sidebar-content">
        <!-- Sidebar content starts -->
        <!-- Sidebar content ends -->
    </div>
\%endif

Positioning the sidebar using CSS, take 2

Like all modern web pages, the bootstrap theme is responsive; that it, it changes its layout in response to different screen widths. The is especially noticeable on smartphones, where a page may display quite differently if the phone is in portrait orientation (long edges on the left and right) or landscape (long edges at the top and bottom.)

While tweaking the CSS to handle the orientation, I added a light grey background to the body. On wider screens the sidebar is a true sidebar with a solid border, smaller font, and and (for now) a a shaded background. On narrow pages the sidebar is much more similar to the standard page content: the border and background colours are gone and the font is the same size as the other content.

Here’s the updated CSS.

/* Main content and sidebar */
body { background: #DDD; }

/* On narrow screens, the sidebar follows the body */
div.body-content { 
    background: white;
    padding-left: 1em;
}
div.sidebar-content {
    margin-top: 1em;
    padding: 1em 1em 1em 1em;
    background: white;
}
div.sidebar-content h2 {
    font-size: 200%;
    background: lightblue;
    padding-left: 5px;
}
/* On wider screens, float the sidebar on the right hand side */
@media (min-width:768px) {
    div.body-content { 
        width: 75%;
        float: left;
    }
    div.sidebar-content {
        margin-top: 0px;
        width: 24%;
        font-size: 75%;
        float: right;
        border: solid black 1px;
        background: floralwhite;
    }
}