sconewrong

Blog category colours

Over a year ago pastels entered the chat on this blog, and at the time I mentioned a desire to increase the palette further with distinct colours based on the category of the post (subject to there being enough posts to warrant this).

At the time I had a basic idea on how to implement this practically. Already I used a css class called .post to modify the header to be red when generating an article page - and my thought was to simply create a css class for each category such that I would simply inject the category name as a class attribute into the HTML in the Jinja template and we'd be off.

On reflection, while this would definitely work - it breaks the code separation a bit. It requires the theme to have knowledge of the categories, when really it should have knowledge only of the colours, which are picked programmatically at the generation stage based on the content. So how can the theme be expanded to allow this feature?

A more correct solution would be to add the wanted colour as metadata to the input markdown file, the Jinja template in the theme then uses this colour variable to modify the colour of the post. This is all within Pelican convention, we can define custom metadata - so far so good. But its repetitive and prone to errors - in each input file we need to match the correct colour up to the category.

So what other methods do we have? Well metadata can be extracted from the file or path of the input file, and also based on a relative path set specific variables. So while putting the colour in the name of the file is a bit bonkers (see repetition and error point above), we could have our content arranged into folders - and on a matching path set an appropriate colour variable. This is workable, and only involves changes to our settings file, no code touched!

While the solution above works - I'm not totally in love with it. It requires a certain folder structure which is limiting. The crux of the task I'm trying to perform is creating new metadata based on existing metadata based on a predefined mapping - i.e. dev category posts should have the colour metadata as red, photo posts as blue etc.

Enter the pelican plugin. Plugins can be created to be called when certain signals are emitted during the reading-generation process, mutable objects are passed which can be modified and so changes are put back into the process. A really good real-world example of pelican plugin creation can be found here.

My plugin is registered to the article_generator_write_article signal which is called for each article just prior to it being written/output (an apt time to make some last minute metadata changes). The registered function is passed the generator object (unused for me) and the content object which defines the entire article - here I can read the metadata and create new object attributes (which is where the metadata ultimately ends up).

Thinking once again about what functionality should be stored where - keeping generic and specific apart: I opted to make my plugin call a user defined function which takes the content object as a mutable argument - and within the function the mapping can be performed and the new attributes in the content set. This function is defined in the pelican settings file as the mapping it contains is specific to the pelican project. I use a custom settings variable to pass a list of references to these functions to my plugin - the plugin then iterates through the list calling each of the functions when triggered.

# pelicanconf.py
# Content modification function
def category_to_colour(content):
  import  logging
  log = logging.getLogger(__name__)

  colour_map = {
    'Dev': 'red',
    'Productivity': 'orange',
    'Photos': 'blue',
    'Projects': 'yellow'
  }
  colour = 'red'
  category = str(content.metadata.get('category', None))

  if  category  in  colour_map:
    colour = colour_map[category]

  log.info(f'category_to_color: {category} -> {colour}')
  content.colour = colour

# Pass the functions to the plugin to iterate over on each article
ARTICLEINJECT_FUNCS = [category_to_colour]

On the theme/template side I had created four new CSS classes aptly named to set the background to that specific colour. I then utilised the Jinja default filter to look for the colour attribute in the content, but if it doesn't exist just use the default of 'red'. This means the theme still works even without the colour metadata set in either the content itself or using the remapping from the category using my custom plugin above.

<h1  class="{{ article.colour|default('red') }}">

Top ^