Simple Custom Styling Notebooks in JupyterLab & RetroLab… Or Not… Again…

Pondering an earlier fragment on Previewing Richly Formatted Jupyter Book Style Content Authored Using MyST-md, a JupyterLab/RetroLab extension that allows you to render rich MyST markdown content blocks, it struck me that I should be able to extend it to provide a way of displaying customised single cell markdown exercise blocks, if nothing else.

After all, how hard could it be?

The JupyterLab extension that adds the functionality is the executablebooks/jupyterlab-myst extension, which seems in turn to rely on executablebooks/markdown-it-docutils, a plugin for markdown-it (whatever that is…). You can find a demo of what the docutils plugin supports here.

The repo is full of developer voodoo files, although the Getting Started section does tell you what you need to be able to type in order to build things (node required (good luck…); running the provided commands will download the internet and install whatever other node packages appear to be required):

Looking at the source TypeScript code in src/directives directory the admonitions.ts, adding a new admonition type seems simple enough:

// ...
export class Tip extends BaseAdmonition {
  public title = "Tip"
  public kind = "tip"

export class Warning extends BaseAdmonition {
  public title = "Warning"
  public kind = "warning"

export const admonitions = {
  admonition: Admonition,
  attention: Attention,
  caution: Caution,
  danger: Danger,
  error: Error,
  important: Important,
  hint: Hint,
  note: Note,
  seealso: SeeAlso,
  tip: Tip,
  warning: Warning

The colour theme and the icon for the styled admonition block are set by a simple bit of CSS:

$admonitions: (
  // Each of these has a reST directives for it.
  "caution": #ff9100 "spark",
  "warning": #ff9100 "warning",
  "danger": #ff5252 "spark",
  "attention": #ff5252 "warning",
  "error": #ff5252 "failure",
  "hint": #00c852 "question",
  "important": #00bfa5 "flame",
  "note": #00b0ff "pencil",
  "seealso": #448aff "info",
  "tip": #00c852 "info",
  "admonition-todo": #808080 "pencil"

So adding a new admonition type looks relatively straighforward, albeit constraining you to just custom styling the top bar and icon for the special content block. I reckon I should be able to at least build the markdown-it plugin. But what then? How can I use it?

This plugin is mentioned by name in several places in the jupyterlab-myst extension, so if I can find a way of distributing the plugin, for example, by publishing my own distribution via npm using a new, unique package name, then I should be able to reference this, instead of the original, in a fork of the jupyterlab-myst extension installed into my own Jupyter environment? At the cost of having to fork and install my own version of jupyterlab-myst, of course. And compared to the trivial way in which we can create a really simple Pyhton package to extend Sphinx to provide us with similar, custom admonition blocks.

A simpler way might be to use the executablebooks/markdown-it-plugin-template (from which the markdown-it-docutils plugin was created) and create a separate plugin that I can import directly into jupyterlab-myst; but there is a ton of stuff in the executablebooks/markdown-it-docutils extension that the parser seems to be required, and it seems much easier to just add 5 lines of code to something that already works. Were it not for the issue of actually getting those tweaks into my running Jupyter environment, of course.

And, of course, this still isn’t ideal, because I don’t want to just tweak the colour scheme the header of the custom block: I want to make changes to the background of all of it so that it looks like the exercise block provided by executablebooks/sphinx-exercise, for example:

The styling for the “erroneous” directive is a bit closer to what we want, but this is hard coded for “erroneous” classed cells.

And in the way that the styles are defined for the custom admonitions, they are templated to only tweak the header colour and icon in an easily customisable way.

I wonder if the simplest way is to just appropriate one of the custom admonition classes I am not likely to use (eg the attention class) and manually hack some overrides for that class into the CSS file?

But of course, to add a custom CSS file to JupyterLab, you have to do what? Create a dummy extension that just includes a CSS file? Because of course, JupyterLab doesn’t support custom.css.

UPDATE: here’s a trick for getting a custom CSS file into the JupyterLab environment via a dummy, otherwise empty, extension:

Is there anything about JupyterLab that is not really hostile to simple end-user customisation by end-user developers?! I really do HATE IT!

Author: Tony Hirst

I'm a Senior Lecturer at The Open University, with an interest in #opendata policy and practice, as well as general web tinkering...

%d bloggers like this: