Tutorial: Publishing your own Github blog (Part 3)

Part 3: adding an formatting your blog content

Hugo FerreiraHugo Ferreira

Introduction

In the the first article of this series we looked at how to setup a JBake site using the Mill build tool (Mill documentation) to develop and execute the required scripts to generate and publish the website's content. In the second article of this series, I showed you how to download, configure and brand the JBake Future Imperfect template, which is a freely available template that is not part of the JBake default templates. In this third instalment the goal is to show you how to publish content. So lets get to it.

The Baking Process

The baking process refers to the process of parsing the content, collecting data, storing this data and then using this data to generate the content. There are three sources of data:

  • Default values defined by JBake (for example the JBake version);
  • Values defined in the jbake.properties (for example the icon, titles and social media links);
  • The content metadata that is provided as a header in each content file;

The baking process consists of three main steps. During the first step the configuration file and content headers are parsed, the data is stored in a database. During this phase, the content files are also parsed and converted to HTML snippets if required. These HTML snippets are stored in the database with the corresponding metadata. Note that we are free to add our own custom metadata to the headers. The HTML content and all of the metadata are recorded as named variables.

During a second phase, each HTML snippet and respective variable values are injected into the template engine. The template engine processes this data and generates the HTML files. The type metadata variable determines which template is used by the template engine to generate the final output. JBake has default templates for the type marked as page or post. However, you can also add your own custom templates for specific rendering.

In a final phase, JBake processes a set of default templates that don't require any content (index, archive, feed, tag and sitemap) and the index.ftl template that processes the site's content. JBake expects the content to be placed in a well defined path. It uses this information to generate the index, archive, tag and sitemap links. More concretely we must place the content in the site/src/content directory. We will now explore how to add and publish content.

Adding your articles

To add a blog post we must add its sources to the content directory. You can use any folder structure you want to store your content files. JBake examples use a blog root and then have a single folder for each year. Depending on the number of articles you publish, you can use alternate structures as per your requirements. For example, you can create folders using month, trimester or even topic names. I am currently using a year and then topic sub-structure.

Your content may include text made available via raw HTML, Markdown or AsciiDoc files. You may also include any other files required to generate your HTML content. These may include images, cascading stylesheets (CSS), fonts and even JavaScript libraries and functions. Technically you can place these files anywhere within the website's sources. In this tutorial's running example, that would be the project's root folder site/src (see part 1 for more details on this). However, JBake has the dedicated assets directory that contains the following folders for sharing specific kinds of content:

  • css: cascade style sheets used for configuring the look and feel of the website (see part 2 for details on how they were used);
  • fonts: fonts used by JavaScript libraries and CSS files;
  • img: image files (see part 2 for details on how they were used);
  • js: JavaScript (see part 2 for details on how they were used).

I only use the above folders if an asset is used by at least 2 content files. In all other cases, I collocate the assets with the content file were it is used.

Metadata header

All content files, including those that use raw HTML, must have a metadata header (see part 2 for more details). Here is an example:

title=Tutorial: Publishing your own Github blog 
date=2022-06-24
last_updated=2022-06-25
type=post
tags=Mill, JBake
categories=Scala, Mill, BJake
status=published
author=Hugo Ferreira
description=Part 1: how to easily to set up and manage your own blog
---------

In regards to content presentations and indexing the following are important:

  • date: this is the date that JBake uses to order the indexed articles (main page), the archive and the site's map. If you don't provide this date, the file's last OS modified timestamp will be used. The last_updated field does not seem to be used for indexing and ordering;
  • type: this must be set to post to be indexed and served. Another possible value is page but it should not be used (explained below);
  • status: this must be set to published. If not it will not be indexed and served;
  • tags: must be provided if you have the tagging activated. This will allow a user to select and list all the articles tagged with a given term. The field categories does not seem to be used. Currently I set tags and categories to be the same, but may change this in the future to implement a more general index (for example: software, hardware, math, machine learning, etc.);

Content type page

Besides the post type we also have the page type. These types indicate which templates are used to render the content. The page template of the current site generates the same content, but the header does not include the small icon, author name and estimated reading time of the article. In addition to this, even though these articles' tags are listed and the links to them are generated in the website's site map, no other links are created. A quick search shows that the published_pages global data variable is not used in the FreeMark templates. The published_content global data variable is only used for listing the tags that generate the indexing of the site map.

This document type is usually reserved for permanent content such as the 404.html and about.html. Use this content type for content with permanent links that are accessible from the site's menu.

Add and post your content

You can now add your blog posts and use the Mill commands described in parts 1 and 2 to serve your content locally or on your project's github pages. If you don't see the expected changes, refresh the page. If that does not work, try to clean the browser's cache. You are also free to change any of the other assets of your website. Below I describe what changes I did to these assets in order to tweak my site to my liking.

Formatting and processing issues

When testing the rendered content I found the following issues that required additional changes to the JBake Future Imperfect template (see part 2 for more details):

  1. Github style footnotes did not work;
  2. Scala syntax highlighting in the code fence did not work (but other languages did);
  3. The code fence syntax highlighting for light and dark mode was missing;
  4. Relative links of images in the generated index HTML content were broken (missing images);
  5. Images were not centred but were left aligned;
  6. Text is not justified but left aligned;
  7. The inline code block appears with the same opaque background colour as the code fences thereby hiding linking cues;
  8. Use of the standard metadata header separator made Markdown syntax highlighting in the IDE editor fail;
  9. Light and dark mode does not work on Google Chrome.
  10. Header's description is not rendered as HTML (only simple text);
  11. Light and dark mode exhibits the Flash of unstyled content problem;

The following sections describe what changes were required to solve all the problems described above, except for the last two issues.

Footnotes

In many of the documents I write, I use footnotes. They are handy to add links and references or simply provide a warning to the reader without a need to change or add another sentence. I observed that these footnotes did not work out of the box in JBake. In order to have footnotes work, one needs to configure JBake by adding a Markdown extension. JBake uses Flexmark as its Markdown processor, so you need to use Flexmark extensions' and add the relevant Flexmark footnote extensions by adding the following line to the JBake configuration file:

markdown.extensions=FOOTNOTES

Later we will see that their are some issues with the configuration of the Flexmark extensions, but footnotes work as advertised.

Code fence syntax highlighting

One issue that I found, was that the code fences were not working as expected. In particular the Scala code did not have any of the expected syntax colouring. The code simply appeared as a text box using a monospaced typeface font. Here is a snippet of the HTML code generated by the Flexmark parser:

<pre><code class="language-scala">// MyScript.sc
// print banner
println(&quot;Hello World!!&quot;)

// common imports
import sys.process._
import collection.mutable

// common initialization code
val x = 123
println(&quot;x is &quot; + 123)
</code></pre>

Note the class="language-scala" that was used. To render the code snippet correctly we can use the PrismJS or HighLightJS JavaScript libraries by adding appropriate script links to the HTML files. So what library was the original template using? If we look at the footer.ftl file we will see the following code snippet:

		<script src="<#if (content.rootpath)??>${content.rootpath}<#else></#if>js/highlight.pack.js"></script>
		<script src="<#if (content.rootpath)??>${content.rootpath}<#else></#if>js/readingTime.js"></script>

		<#if (config.site_disqus_shortname?has_content)>
			<script id="dsq-count-scr" src="//${config.site_disqus_shortname}.disqus.com/count.js" async></script>
		</#if>
		<#if (config.site_google_trackingid?has_content)>
			<#include "commons/google-analytics.ftl" />
		</#if>
		<!-- This is called by default since this theme uses highlight.js -->
		<script>hljs.initHighlightingOnLoad();</script>
		<!--[if lte IE 8]><script src="/js/ie/respond.min.js"></script><![endif]-->

It seems like HighLightJS is being used. Looking at the HighLightJS download page I can confirm that the default version of the library does not support Scala out of the box. This means that even if we download the latest stable version via an external source (such as the CDNJS server using the following HTML snippet), we still wont' have support for Scala:

<link rel="stylesheet"
      href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/styles/default.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/highlight.min.js"></script>

The HighLightJS documentation explains how one can compile your own version of the library, but it is easiest to use the HighLightJS download page to configure what language support you want and to download a new version compiled on demand. While I was at it, I added the support for the following languages:

  • .properties
  • ARM Assembly
  • AVR Assembly
  • Arduino
  • Batch file (DOS)
  • HTTP
  • Intel x86 Assembly
  • LaTeX
  • Matlab
  • Maxima
  • MIPS Assembly
  • OCaml
  • PostgreSQL and PL/pgSQL
  • PowerShell
  • Processing
  • Prolog
  • Scala
  • VHDL
  • Verilog
  • Wolfram Language

The download will result in an archive named highlight.zip that contains the normal and minified version of the library (name highlight.min.js), a language folder with scripts for each supported language and a styles folder that has all of the available stylesheets with the different colour schemes for syntax highlighting. Note that in the section above, I added to the JBake configuration file (jbake.properties) the Flexmark extensions variable to use the footnote extension. To find out what other options are available, look at the Flexmark source code. Here I also added the fenced code plugin as is shown next:

markdown.extensions=FOOTNOTES,FENCED_CODE_BLOCKS

This plugin is usually on by default if you don't change this setting explicitly as done above.

After unpacking the downloaded archive, the following was done:

  1. The downloaded highlight.min.js file was copied to the site's /assets/js/ folder;
  2. Several pre-selected stylesheets were copied to the site's /assets/cds folder. Examples included lioshi.min.css, googlecode.min.css, default-dark.min.css, tomorrow-night-blue.min.css, magula.min.css and docco.min.css. The prefix hljs_ was added to the filenames. I will explain how we use these CSS files later;
  3. The footer.ftl template was changed to use the highlight.min.js script (replaced highlight.pack.js with highlight.min.js);
  4. The footer.ftl template was changed to call the highlightAll() script (replaced initHighlightingOnLoad() with highlightAll())1.

Here is a snippet containing the changes that were made to the footer.ftl template:

		<script src="<#if (content.rootpath)??>${content.rootpath}<#else></#if>js/highlight.min.js"></script>
		<script src="<#if (content.rootpath)??>${content.rootpath}<#else></#if>js/readingTime.js"></script>
		<script src="<#if (content.rootpath)??>${content.rootpath}<#else></#if>js/darkmode.js"></script>

		<#if (config.site_disqus_shortname?has_content)>
			<script id="dsq-count-scr" src="//${config.site_disqus_shortname}.disqus.com/count.js" async></script>
		</#if>
		<#if (config.site_google_trackingid?has_content)>
			<#include "commons/google-analytics.ftl" />
		</#if>
		<!-- This is called by default since this theme uses highlight.js -->
		<script>hljs.highlightAll();</script>
		<!--[if lte IE 8]><script src="/js/ie/respond.min.js"></script><![endif]-->

We have effectively upgraded the highlighting library highlight.min.js from the original v9.2.0 version to the current v11.5.1 (git: b8f233c8e2) version. Hopefully it will be just as easy to update or change the highlighting library to a different version when you read this.

And here is a snippet of the HTML code above after the HighLightJS script has parsed the <code> block:

<code class="language-scala hljs"><span class="hljs-comment">// MyScript.sc</span>
<span class="hljs-comment">// print banner</span>
println(<span class="hljs-string">"Hello World!!"</span>)

<span class="hljs-comment">// common imports</span>
<span class="hljs-keyword">import</span> sys.process._
<span class="hljs-keyword">import</span> collection.mutable

<span class="hljs-comment">// common initialization code</span>
<span class="hljs-keyword">val</span> x = <span class="hljs-number">123</span>
println(<span class="hljs-string">"x is "</span> + <span class="hljs-number">123</span>)
</code>

Code fence highlight styles

The website I set up allows for the use of light and dark mode visualization. So the first question is how to change the colour scheme used by the highlighting library. One of the available HighLightJS styles is called Monokai Sublime. The use of this template is set in the header.ftl. Here is a snippet of the relevant code:

  <head>
    <meta charset="utf-8"/>
    <title>${config.site_title}<#if (content.title)??> - <#escape x as x?xml>${content.title}</#escape></#if></title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="">
    <meta name="author" content="${content.author!config.site_author}">
    <meta name="keywords" content="">
    <meta name="generator" content="JBake">

    <link rel="stylesheet" href="<#if (content.rootpath)??>${content.rootpath}<#else></#if>css/google-font.css" />
    <link rel="stylesheet" href="<#if (content.rootpath)??>${content.rootpath}<#else></#if>css/font-awesome.min.css" />
    <link rel="stylesheet" href="<#if (content.rootpath)??>${content.rootpath}<#else></#if>css/theme.css" />
    <link rel="stylesheet" href="<#if (content.rootpath)??>${content.rootpath}<#else></#if>css/main.css" />
    <link rel="stylesheet" href="<#if (content.rootpath)??>${content.rootpath}<#else></#if>css/add-on.css" />
    <link rel="stylesheet" href="<#if (content.rootpath)??>${content.rootpath}<#else></#if>css/monokai-sublime.css">

    <!-- Fav and touch icons -->
    <link rel="shortcut icon" href="<#if (content.rootpath)??>${content.rootpath}<#else></#if>img/favicon/favicon.png">
  </head>

The Monokai Sublime template is a dark mode stylesheet that does not fit well with the blue light mode pallet that I selected. So I used HighlightJS's demonstrator page to visualize what the different styles looked like and make a triage of those that I found more appealing2. I then replaced the Monokai Sublime template file with each of these new templates and refreshed the website to see what they looked like. I finally selected the Lioshi (hljs_lioshi.min.css) and Googlecode (hljs_googlecode.min.css) templates for the dark and light modes respectively. For aesthetic reasons I also tweaked these templates by changing the background to conform to the website's "active" colour pallet. So for the Lioshi template I changed background: #303030 to background: #242526 and for the Googlecode template I changed background: #fff to background: #8fb9d7 in the themes.css. In the CSS code I actually used the var(--background_body) CSS value that is either set to --facebook_card for the dark mode or --blue_card for the light mode. If you haven't read the second article of this series, do so to understand how to set these variables. You will also need this to understand the next section.

Toggling the code fence highlight

In the previous article of this series I allowed the user to select the light or dark mode via the browser, operating system and website. I used the CSS media feature prefers-color-scheme to detect if a user has by default selected the dark mode or used the website's checkbox to toggle the mode explicitly. The CSS media feature prefers-color-scheme can also be used to conditionally load the highlighting style using the `@import, so it stands to reason that I need only add these lines to the start of the theme.css stylesheet:

/* Assume light mode by default */
@import "hljs_googlecode.min.css" screen;
/* Supersede dark mode when applicable */  
@import "hljs_lioshi.min.css" screen and (prefers-color-scheme: dark);

It does indeed work. In fact we can also do this at the HTML level by using the media attribute of the <link> element. However this will not allow us to dynamically set the preferred colour scheme via the web application checkbox. To solve this, I changed the darkmode.js script to to dynamically add or remove the stylesheet link from the header. The external resource link containing a URL to the HighlightJS style sheet is not required any more and can be removed from the header.ftl template.

The darkmode.js script now looks like this:


$(function(){
	// Stylesheets used by highlight.js
	const hljs_dark = '\n<link rel="stylesheet" href="css/hljs_lioshi.min.css">';
	const hljs_light = '\n<link rel="stylesheet" href="css/hljs_googlecode.min.css">';

	function updateHLjsStyle(theme, deleteLast = true) { 
		var head = document.getElementsByTagName('head')[0].innerHTML;
		var withThis = ""
		if (deleteLast) {
			// Delete last stylesheet
			if (theme == "dark") {
				withThis += hljs_light
			}
			else {
				withThis += hljs_dark
			}
			head = head.replace(withThis, '') 
		}
		// Add new stylesheet
		if (theme == "dark") {
			head += hljs_dark
		}
		else {
			head += hljs_light
		}
		document.getElementsByTagName('head')[0].innerHTML = head;		  
	}

	// Get initial stylesheet
	//const btn = document.querySelector("checkbox");
	const btn = document.getElementById("mode_checkbox");
	const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)");
	
	const currentTheme = localStorage.getItem("theme");
	if (currentTheme == "dark") {
		document.documentElement.className = "dark-theme";
	} else if (currentTheme == "light") {
		document.documentElement.className = "light-theme";
	}
	else {
		// default
		if (prefersDarkScheme.matches)
			document.documentElement.className = "dark-theme";  
		else
		  document.documentElement.className = "light-theme";  
	}
	//console.log("Initial class:" + document.documentElement.className)
	var theme = (document.documentElement.className == "light-theme") ? "light" : "dark";
	updateHLjsStyle(theme, false)
	
	// Toggle stylesheet
	btn.addEventListener("click", function () {
		//console.log("Current class: " + document.documentElement.className)
		// Toggle
		var theme = (document.documentElement.className == "light-theme")
				? "dark"
				: "light";
		// Set new class
		if (theme == "light")
			document.documentElement.className = "light-theme";
		else
			document.documentElement.className = "dark-theme";

		updateHLjsStyle(theme)

	  // Store mode
		//console.log("Save new mode:" + theme)
		localStorage.setItem("theme", theme);
	});
		
});

At the start of the function I created two constants (hljs_darkand hljs_light) that hold the URLs to the HighlightJS stylesheets. The function updateHLjsStyle(theme, deleteLast) is then defined. It uses the parameter theme to identify which stylesheet link to remove from the HTML document's header, if it exists. The deleteLast parameter is simply used to avoid attempting to remove the link when the HTML page is first loaded and has no existing stylesheet link. When the page is loaded, the correct stylesheet link is added using the updateHLjsStyle() function depending on the user's choice, including his last choice set via the checkbox. Finally, whenever the checkbox is toggled, the previous stylesheet is removed, and the new one is inserted via the updateHLjsStyle() function. With this setup both the website's colour scheme and the highlightJS stylesheet match.

Note: that the JavaScript variables hljs_dark and hljs_light must use absolute paths in order to find the assets in the css folder. If a path, such as /, is used will result in an error loading the cascading style sheets. The result is that no syntax highlighting will be performed.

Solving issues with Relative local links

When editing content in Markdown I use an IDE to visualize the output. I also place all of the content in the same folder. Any links I use are relative links to the folder or to folders in the same section (for example posts or pages). When testing the JBake Future Imperfect template, I noticed that the links to the images were broken. More concretely, all links in the posts worked correctly, such as in the following link:

http://localhost:8820//tech4rd/blog/2020/mill/javafx/javafx.html

However when accessing the home link such as this one:

http://localhost:8820/

the links were broken. All content accessed via the home page and the next and previous buttons did not work. So what is causing the problem? Let's start by analysing what JBake does when using this template. JBake seems to:

  1. Create the HTML content as per the structure of the pages and blogs;
  2. For each folder we find the HTML files with the original names and any other assets we placed there;
  3. This is placed under the "content" and "pages" folders as per the configuration;
  4. It then goes through these files and creates an index file. The index generates one directory (numbered from 2 to n) for each page and post and uses the index.ftl to generate a corresponding index.html file. Only the latest post or page (whose index is 1) is not placed in its own directory. This index.htmlfile is placed in the site's root;
  5. Each index.html file has the previous and next buttons that access the corresponding indexed index.html files of the other pages;
  6. No additional content is copied to the indexing folders. The index files contain the exact same content as the original sources.

This is the source code snippet of the index.flt showing how the content is used without any changes:

<#include "header.ftl">
	
	<#include "menu.ftl">
	
	<div id="main">
    <#list published_posts as post>
			<#if (post??) >
				<#include "post/content-single.ftl">
			</#if>
		</#list>
		
		<#include "post/prev-next.ftl">
	 </div>
	
	<#include "commons/sidebar.ftl">
<#include "footer.ftl">

Because the links are relative, those links remain valid. The relative link to:

./managed_run_screenshot.png#center

when accessed from:

http://localhost:8820//tech4rd/blog/2020/mill/javafx/javafx.html

is interpreted by the web server as:

http://localhost:8820//tech4rd/blog/2020/mill/javafx/managed_run_screenshot.png#center

However, when accessed from an index file:

http://localhost:8820/index.html

the same image link is interpreted by the web server as:

http://localhost:8820/managed_run_screenshot.png#center

which fails because it does not exist. Suffice to say, copying HTML source without processing links and/or copying all of the other files is not a good policy. One way to solve this is, to let JBake make all the links absolute. This is possible for the image paths. We need only configure the host name and the option to generate the absolute path by adding the host name as shown below:

site.host=https://hmf.github.io/tech4rd/
img.path.update=true
img.path.prepend.host=true

One big disadvantage with this solution, is that setting the host name, means we cannot easily cross-post the content to other hosts. We have to bake each site again using a different host name. Also note that care must be taken when accessing an image in the global img asset.

Using a relative path such as this:

![Preview in Dark Mode](./screenshot_jbake_example_site.png)

or this:

<img src="./../../img/blog/sample-image.jpg"/>

will work. Using an "explicit" absolute path shown below will also work:

![Test absolute /B](/img/main/avatar.png)

Using an "implicit" absolute path will not work:

![Test absolute A](img/blog/sample-image.jpg)

Note: while working on this problem I learned that if you duplicate entries in the JBake configuration file, they will be concatenated and result in a sequence. When used as a string in a FreeMark template, it will result in an exception. FreeMarker will complain that it cannot convert the element into a string. You can use ?join to bypass this issue, but get incorrect results. Make sure you don't duplicate configuration file entries.

Note: The instructions above are valid for the Github and many other servers. When testing with JBakes localhost server, relative paths work correctly. If one uses an absolute path such as ![site/src/assets/img/main/favicon.png](/img/favicon/favicon.png), JBake will generate the following URL - http://localhost:8820//img/favicon/favicon.png. Note that repeated forward slashes. This is an invalid URL. Most HTTP web servers are configured to collapse duplicate forward slashes to a single one, and the links works. However, JBake's localhost web server does not, and the links will not work. Use an alternate HTTP server that deals with this issue or configure the remote host server appropriately and test the links.

Center Image

To be able to centre the images you can use standard HTML for this. However, with a little CSS trickery you can also centre the images. One can use URL style parameters or the ALT tags and a corresponding CSS selector on those parameters or tags. In the first case (URL style parameter), the Markdown link would be:

![A cute kitten](http://placekitten.com/200/300?style=centerme)

and the CSS selector would be:

	img[src$="centerme"] {
		display:block;
		margin: 0 auto;
	}

The second option would use the following Markdown link:

![A cute kitten](http://placekitten.com/200/300#center)

and the CSS selector would be:

img[src*='#left'] {
    float: left;
}
img[src*='#right'] {
    float: right;
}
img[src*='#center'] {
    display: block;
    margin: auto;
}

You can try these in one of the online JavaScript editors. Here is the original example. The solutions above are good because the template already has several CSS styles for the images and we don't want to mess around with those. In particular the main.css file has a "section" for images that starts with:

/* Image */

	.image {
		border: 0;
		display: inline-block;
		position: relative;
	}

In addition to this, the add-on.css has the following selector that is activated by the images within the posts and pages:

    #content img {
      max-width: 100%;
    }

To centre all the images we could simply change it to:

    #content img {
      max-width: 100%;
			display:block;
			margin: 0 auto;
    }

I opted for the second CSS solution I linked to above. This way, I can explicitly mark and format the images how I see fit without inadvertently messing with the rest of the template. To do this in the add-on.css stylesheet I added the #content img selectors as shown below:

.post {
    margin: 0 0 2em 0;
}

    #content img[src*='#left'] {
        float: left;
    }
    #content img[src*='#right'] {
        float: right;
    }
    #content img[src*='#center'] {
        display: block;
        margin: auto;
    }

    #content img {
        max-width: 100%;
    }

The links I then changed from:

![GUI from managed.run](./managed_run_screenshot.png)

to

![GUI from managed.run](./managed_run_screenshot.png#center)

Justify content text

I prefer seeing my content with its text justified. The default template setting has the text aligned to the left. The HTML output is equivalent to:

<p style="text-align=left">Content text.</p>

Choosing a random sample of the content text and using the browser's inspect (Q) context-sensitive menu option, we can see (via the developer tools) that the initial text alignment setting are selected via the following CSS snippet found in the add-on.css stylesheet:

    #content p a, #content ul li a {
        border-bottom: dotted 2px var(--content_border);
    }

        #content p a:hover {
            border-bottom-color: transparent;
        }

    #content blockquote, #content p, #content ul{
        margin: 0 0 1em;
    }

In addition to this, the main.css CSS file also has the following relevant set of selectors:

    article.post p,
    article.post h2,
    article.post h3,
    article.post h4 {
        color: var(--foreground);
        /* Theoretically for IE 8 & 9 (more valid) */
        /* ...but not required as filter works too */
        /* should come BEFORE filter */
        -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=87)";

        /* This works in IE 8 & 9 too */
        /* ... but also 5, 6, 7 */
        filter: alpha(opacity=87);

        /* Modern Browsers */
        opacity: 0.87;
    }

The snippets above shows that we can use a combination of element, class and ID selectors to assign an alternate text alignment style solely to the article's content. From the snippet above we can see that one can:

  • Select an element with a class for the post's content : article.post;
  • Select the <p> element nested inside an article element with the class post: article.post p (Note that the article.post is an ancestor and not specifically the direct parent <p>);
  • We can also select the <p> element that is a direct descendant of article.post: article.post > p.

A look at the templates output shows the following general structure for the content:

<article class="post">
   <header>
    <div class="title">
        <h1>About</h1>
    </div>
	</header>
         
    <div id="content">
      <div class="paragraph"> 
				<p>About content.....</p> 
			</div> 
    </div>

    <footer>
	    No footer
    </footer>
</article>

The above structure is true for both the post and page type content. According to this structure I think the use of an ID selector is warranted because it will avoid unintentionally changing other elements of the template. Just to be on the safe side, combining the type class and ID selectors will even be better. Here is an example of this:

        div#content p  {
            border-bottom-color: transparent;
        }

To reinforce this even further, I see that the <p> element is always a direct descendant of the content ID or of the class "paragraph", which in turn is a direct parent of the article class. So we can also use the combinator as follows:

    article.post > div#content > p {
      text-align: justify;
    }

I did not find any use of the paragraph class, except in the case of the latest JBake example asciidoctor.css cascading style sheet:

p.lead, .paragraph.lead > p, #preamble > .sectionbody > .paragraph:first-of-type p { font-size: 1.21875em; line-height: 1.6; }

Even so, I opted to add it. So the final solution is:

    article > div#content > p,
    article > div#content .paragraph > p {
        text-align: justify;
    }

Note that I am not using the classes post or page, which allows us to format both posts and pages without messing up the rest of the website's formatting.

Single code line

In many cases I need to show code snippets to the reader. I can show code inline or as a separate element that occupies its own section of the rendered text. We can format these code sections using either "standard" code blocks or fenced code blocks. The fenced code block has the advantage of allowing one to use syntax highlighting. If you render the HTML with JBake, you will observe that in both cases a <code> element is used to "hold" the code snippet within. The difference however, is that the fenced code blocks are placed with the preformatted text element (<pre>), which does not format the text flow (all line breaks and text positions are maintained). It is a text section in and of itself, much like a paragraph.

The standard code block text, on the other hand, is formatted just as any other text in the paragraph. Here is an example: This sentence holds an inline code block of text. The following is the HTML generated by the Flexmark Markdown processor.

<i>This sentence holds an inline <code>code block of text</code></i>.

The HTML source above is not changed and is rendered using the main.css stylesheet based on the code element selector shown below:

code {
  background: var(--dark_gray_alpha_2);
  border: solid 1px var(--dark_gray_alpha);
  font-family: "Courier New", monospace;
  font-size: 0.9em;
  margin: 0 0.25em;
  padding: 0.25em 0.65em;
}

The following fenced code block:

'''scala
// MyScript.sc
// print banner
println("Hello World!!")

// common imports
import sys.process._
import collection.mutable

// common initialization code
val x = 123
println("x is " + 123)
'''

is converted by the Flexmark Markdown processor into the following HTML code:

<pre><code class="language-scala">// MyScript.sc
// print banner
println("Hello World!!")

// common imports
import sys.process._
import collection.mutable

// common initialization code
val x = 123
println("x is " + 123)
</code></pre> 

The HighLightJS script then takes the HTML code above and changes it, on the fly, to:

<pre>
<code class="language-scala hljs">
  <span class="hljs-comment">// MyScript.sc</span>
  <span class="hljs-comment">// print banner</span>
  println(<span class="hljs-string">"Hello World!!"</span>)

  <span class="hljs-comment">// common imports</span>
  <span class="hljs-keyword">import</span> sys.process._
  <span class="hljs-keyword">import</span> collection.mutable

  <span class="hljs-comment">// common initialization code</span>
  <span class="hljs-keyword">val</span> x = <span class="hljs-number">123</span>
  println(<span class="hljs-string">"x is "</span> + <span class="hljs-number">123</span>)
</code>
</pre>

Note that the resulting HTML has the same <code> element placed within the <pre> element. The altered HTML is rendered using the main.css stylesheet based on the combined pre code element selector shown below:

		pre code {
			display: block;
			line-height: 1.75em;
			padding: 1em 1.5em;
			overflow-x: auto;
		}

This allows us to effectively render the code blocks and fenced code blocks differently. I used this to remove the border from the standard code blocks and add this back to fenced code blocks only. In addition to this, several standard code blocks are used as hyperlinks (shown as the anchor <a>'s text). The original colour --dark_gray_alpha_2, set in the themes.css, was opaque. This covered the links' underline and hid the visual cue that users need. In this case I added a 50% alpha channel to the colour and used that as the line code's background colour. Here is the final version of the selectors:

	code {
	  background: var(--dark_gray_alpha_2);
      font-family: "Courier New", monospace;
      font-size: 0.9em;
      margin: 0 0.25em;
      padding: 0.25em 0.65em;
      border-radius: 15px;
	}

	pre {
		-webkit-overflow-scrolling: touch;
		font-family: "Courier New", monospace;
		font-size: 0.9em;
		margin: 0 0 2em 0;
	}

		pre code {
		  display: block;
          background: var(--dark_gray_alpha);
		  border: solid 1px var(--dark_gray_alpha);
		  line-height: 1.75em;
		  padding: 1em 1.5em;
		  overflow-x: auto;
		}

Originally I wanted to change the --dark_gray_alpha_2 background colour's opacity to 50% via CSS. For standard CSS, apparently this is only possible with the rgba() function. The suggested solution was to use the following combination:

    --dark_gray_alpha_2: 240, 240, 240;
...
	background: rgba(var(--dark_gray_alpha_2), 0.5);

That means the colour definition would have to be a triplet of values and not a single colour code as currently used in the theme.css stylesheet. Recall that in part 2 of this series we set up colour pallets for light and dark modes. So the base colour is not a single --dark_gray_alpha_2 value but can be either a --blue_hover or --facebook_hover colour. And many other colours set in the template use these hover pallet colours. Specifically here is a list of those colours that are affected:

    /* light mode */
    --dark_gray            : var(--blue_hover);
    --dark_gray_alpha      : var(--blue_hover);
    --dark_gray_alpha_2    : var(--blue_hover);    
    --active_button        : var(--blue_hover);
    --share_btn            : var(--blue_hover);
    --hover_alpha          : var(--blue_hover);

    /* dark mode */
    --dark_gray            : var(--facebook_hover);
    --dark_gray_alpha      : var(--facebook_hover);
    --dark_gray_alpha_2    : var(--facebook_hover);
    --active_button        : var(--facebook_hover);
    --share_btn            : var(--facebook_hover);
    --hover_alpha          : var(--facebook_hover);

Were I to use this technique to add the alpha channel, I would have to a) define the hover colours in RGB and b) replace all of the above variable assignments with a call to either rgb(var(--variable)) or rgba(var(--variable), opacity). Because the --dark_gray_alpha_2 is only used once in the main.css, I simply created 2 new variables (--blue_hover_light and --facebook_hover_light) and assigned those in only two places as shown below:

    --dark_gray_alpha_2    : var(--blue_hover_light);
    --dark_gray_alpha_2    : var(--facebook_hover_light);

This solved the issues I identified, but while researching for a solution I chanced upon an alternate strategy that did not work due to "bugs" in JBake and Flexmark. Because of this I cannot claim that it will work, however I think this technique may be useful in other situations that require the use of custom attributes. Flexmark has an Attributes extension that allows one to add attributes to an HTML element. One can annotate the Markdown document to add an HTML ID, class or attribute value. It even allows one to add multiple attributes to the same HTML element. The full specification has the following example:

This markdown source snippet:

Cond 1.1 text node{.red}
Cond 1.1 text node {.red}

is converted to:

<p class="red">Cond 1.1 text node</p>
<p class="red">Cond 1.1 text node</p>

which now allows the site designer to add a CSS selector for the class "red". The specification includes details on the rules used to assign the attributes to specific elements in the Markdown source. For the case of the "inline code" described above, one could try to use, for example:

*This sentence holds an inline `code block of text`{inline="true"}*

which would hopefully produce:

<i>This sentence holds an inline <code inline="true">code block of text</code></i>.

and could then be formatted using this selector:

	code[inline="true"] {
	  background: var(--dark_gray_alpha_2);
      font-family: "Courier New", monospace;
      font-size: 0.9em;
      margin: 0 0.25em;
      padding: 0.25em 0.65em;
      border-radius: 15px;
	}

I am unsure if the use of attributes with no values are supported by this extension, but CSS selectors for such attributes are also possible. Unfortunately, I was unable to test this solution because I could not get JBake to activate the attribute extension. The problem stems from the fact that a com.vladsch.flexmark.parser.PegdownExtensions object is used by the JBake configuration code to activate the extension. However, not all the currently available extensions are listed in this object, so JBake cannot use these extensions. This nonetheless does not preclude someone from using Flexmark directly on Markdown sources.

Change Metadata header separator

One annoying issue is that the metadata header makes editing Markdown and AsciiDoc site sources using IDEs difficult. Because the metadata header' separator is not recognized, the editors fail to parse and therefore correctly render the content. The solution is to configure JBake to use an alternate separator that is recognized by various editors. To do this, add the following section to your jbake.properties file:

# Facilitates editing in VSCode and other IDEs
# Must be used in all headers now
header.separator=---------

It is important to note that all Markdown headers must use this separator.

Google Chrome fails to toggle light/dark mode

While testing the latest changes to the website's scripts in Google Chrome, I realized that the button to toggle between dark and light mode did not seem to work. Using the browser's developer tools shows that the toggle button does indeed work and the <html> element's class was being changed to the "dark-theme" and "light-theme" classes correctly. However the inherited html selector did not change between html.dark-theme and html.light-theme. This pointed to a potential problem in the theme.css stylesheet. In addition to this, even though the OS is configured for dark mode, the browser does not seem to detect this setting via the @media (prefers-color-scheme: dark) CSS selector. It may have to do with the 'Auto Dark Mode for Web Contents' flag used in the browser because the Google landing page was shown in dark automatically. I also noticed that the the DarkReader plugin does not work in auto mode but does so if set to on.

To solve this problem the theme.css stylesheet was changed to define both light and dark mode colour schemes. Only then is the @media selector used to reassign the dark mode as a fallback. I suspect this was working for the Firefox browser because I have only tested it with the OS set to dark mode.

:root,
:root.light-theme {

    --red         : #ff0000;
    --persian-red : #d1342fff;
    --green       : #00ff00;
    --greenyellow : #ADFF2F;

    --foreground           : var(--blue_primary_text); 
    --mid_ground           : var(--blue_secondary_text); 
    --background           : var(--blue_background);
    --background_body      : var(--blue_card);
    --dark_gray            : var(--blue_hover);
    --dark_gray_2          : var(--blue_secondary_text);
    --dark_gray_alpha      : var(--blue_hover);
    --dark_gray_alpha_2    : var(--blue_hover_light);
    --active_button        : var(--blue_hover);
    --share_btn            : var(--blue_hover);
    --content_border       : var(--mask_red_3);
    --hover_alpha          : var(--blue_hover);
    --hover                : var(--mask_red_3);
...
}

:root.dark-theme {

  --foreground           : var(--facebook_primary_text);   
  --mid_ground           : var(--facebook_secondary_text); 
  --background           : var(--facebook_background);     
  --background_body      : var(--facebook_card);           
  --dark_gray            : var(--facebook_hover);          
  --dark_gray_2          : var(--facebook_secondary_text); 
  --dark_gray_alpha      : var(--facebook_hover);          
  --dark_gray_alpha_2    : var(--facebook_hover_light);    
  --active_button        : var(--facebook_hover);
  --share_btn            : var(--facebook_hover);
  --content_border       : var(--mask_red_1);    
  --hover_alpha          : var(--facebook_hover);
  --hover                : var(--mask_red_0);    
}

/* Dark mode */
@media (prefers-color-scheme: dark) {
  :root, :root.dark-theme {

    --foreground           : var(--facebook_primary_text);
    --mid_ground           : var(--facebook_secondary_text);
    --background           : var(--facebook_background);     
    --background_body      : var(--facebook_card);           
    --dark_gray            : var(--facebook_hover);          
    --dark_gray_2          : var(--facebook_secondary_text); 
    --dark_gray_alpha      : var(--facebook_hover);          
    --dark_gray_alpha_2    : var(--facebook_hover_light);    
    --active_button        : var(--facebook_hover);
    --share_btn            : var(--facebook_hover);
    --content_border       : var(--mask_red_1);    
    --hover_alpha          : var(--facebook_hover);
    --hover                : var(--mask_red_0);    
  }
}

For a detailed explanation on how this toggling works with CSS please refer to section 'Dark Mode'. I suspect that this setup may be further optimized. If you have any thoughts on this, please provide feedback.

Flash of unstyled content problem

I tried to solve the flash of unstyled content by placing the darkmode JavaScript in several places of the HTML header and body. However, this did not work. If you know how to solve this, please provide feedback.

Conclusion

This concludes the third instalment of this series. With this information you should have a working understanding of how JBake generates the site's static content and can now add content to your website. The content can be provided as raw HTML or as Markdown and AsciiDoc sources that are then converted to HTML. I have also provided you with some useful information on tweaking of the existing Freemarker templates and cascading style sheets to format the final HTML output. Finally, I also described how to activate some Flexmark extensions that may be required by you. The example I provided was for the footnotes extension3. In the next article we will delve into some details regarding documenting software code.

You can also find the first and second parts of this series in this website.

References

  1. Markdown
  2. AsciiDoc site
  3. AsciiDoc Wikipedia
  4. ReStructuredText
  5. Freemarker
  6. ThymeLeaf
  7. Google Analytics
  8. MathJax
  9. MathJS
  10. KaTex
  11. Mermaid-Js
  12. Flowchart.js
  13. ChartJS
  14. Plotly
  15. Chartist-Js
  16. RawGraphs.io
  17. d3js
  18. c3js
  19. nvd3
  20. Mill Github
  21. Mill Github pages
  22. FeatherIcons
  23. IonIcons
  24. FontAwesome
  25. Tabler
  26. css.gg
  27. Docusaurus.io
  28. Docusaurus Github
  29. Jekyll
  30. Jekyll Github
  31. JBake github
  32. JBake
  33. Laika
  34. Laika Github
  35. Scala-lang docs
  36. Scala3 ScalaDoc
  37. Scala3 ScalaDoc Guide
  38. Scala3 ScalaDoc Usage
  39. Scala3/Dotty - ScalaDocs github
  40. Javadoc
  41. Sbt-site Github
  42. Sbt-site
  43. sphinx-doc
  44. Pamflet
  45. Pamflet Github
  46. Nanoc
  47. GitBook
  48. Paradox
  49. Github Paradox
  50. GoHugo
  51. AsciiDoctor
  52. Mill Build tool
  53. Mill Docusaurus plugin
  54. Mill JBake plugin
  55. Mill MDoc plugin
  56. MDoc
  57. MDoc in Github
  58. Mill JBake plugin in Github
  59. Github pages
  60. Gitlab pages
  61. Github Flavoured Markdown
  62. Gitlab Flavoured Markdown
  63. John Gruber’s canonical description of Markdown’s syntax
  64. Commonmark variant
  65. Markdown Guide
  66. Markdown guide extended-syntax
  67. R Markdown variant
  68. JBake documentation
  69. Ammonite
  70. Coursier
  71. JBake Author template
  72. JBake Future Imperfect template
  73. Flexmark
  74. PrismJS
  75. HighLightJS
  76. HighLightJS download
  77. HighLightJS documentation
  78. Flexmark extensions
  79. Flexmark footnote extensions

https://typista.org/


Want to ruin the surprise?
Well, you asked for it!
Want to ruin the surprise?
Well, you asked for it!

TODOs:

  • TODO: Important thing to do
  • TODO: Less important thing to do
  • DONE: Breath deeply and improve karma

  1. Compilation error: "Deprecated as of 10.6.0. initHighlightingOnLoad() deprecated. Use highlightAll() now."

  2. Make sure you choose the *"language Category" that shows the language you want. For example Scala is in the "functional" category. Easiest may be selecting "all" and using the browser's text search option.

  3. As I have pointed out, due to some existing issues (circa 2022), it was not possible to activate all of the existing Flexmark extensions in JBake.