I'll Do It Myself: A Substack Plugin For Syntax Highlighting

First I submitted a request to Substack’s support team (who are very hard to get a hold of!), and then I published an open letter about the lack of syntax highlighting on the platform. But the request has blown to the wayside.

A few days ago, I got this comment on my open letter.

I thought it was an exaggeration. It would probably be, like, a dozen or so at least, right?

Then I had an errant thought last night—I could create a bookmarklet that injects style into Substack newsletters! This would be a pretty low-tech way to circumvent the platform’s styles.

But… I use the Arc browser and Arc doesn’t support bookmarks. (They just have “tab pinning”, which I love, but doesn’t allow for bookmarklets. And they do have “Arc boosts” which are meant to serve the same purpose, but then I wouldn’t be able to share it with you all!).

Making of the Substack Syntax Highlighting Extension

So, I wrote a bookmarklet.

I decided that adding syntax highlighting based on programming languages to the code blocks would be a bit too much to solve (at least with this first version), so I put my sights on the inline code style.

My first iteration just looked like this:

javascript:(function() {
  const linkColor = getComputedStyle(document.querySelector('a'))?.color || '#1a0dab';
  const style = document.createElement('style');
  style.innerHTML = `
    code:not(pre code) {
      background-color: #f9f9f9 !important;
      padding: 0.2em 0.4em;
      border-radius: 4px;
      font-family: monospace;
      font-size: 0.95em;
      border: 1px solid ${linkColor};
    }
  `;
  document.head.appendChild(style);
})();

If I used Chrome, or something else with bookmarks, I could just paste this in a bookmark, and press that while on a Substack newsletter and it would inject the style into the inline code examples (like this!).

But since I use Arc, I needed to write a Chromium extension.

Luckily, someone (Peter Legierski) made a “Bookmarklet-to-Extension” tool!

NOTE: As of my writing this, the tool needs upgrading from manifest v2 to v3, but that should happen pretty soon.

So I pasted the bookmarklet in, and voila!

I just had to take that folder, unzip it, and upload it to my extensions.

Me pressing CMD + T and typing “extensions” which lets me quickly access the Manage Extensions page in the Arc browser
Developer mode is hidden in the top-right corner — you have to turn this on to add your own extensions
Click “Load unpacked” to upload the extension folder

And now I have my extension!

Substack Inline Code Styler is now in action — You can make changes locally and click the refresh button to quickly iterate on functionality.

More Iteration

So, it turns out I didn’t really want a bookmarklet. I want this to run every time I open a Substack newsletter—I don’t want to have to press a button to inject the style.

So, I had to iterate a bit on what the Bookmarklet-to-Extension tool gave me.

The tool gives you a structure like:

bookmarklet2extension/
├── background.js
├── bookmarklet.js
├── icon-128.png
├── icon-16.png
├── icon-48.png
└── manifest.json

I ended up removing the bookmarklet.js file and creating an inject.js file that holds all of the complexity.

This is the manifest.json I ended up going with:

{
  "manifest_version": 2,
  "name": "Substack Inline Code Styler",
  "version": "1.0",
  "description": "Automatically styles inline code on Substack posts.",
  "background": {
    "scripts": ["background.js"],
    "persistent": false
  },
  "permissions": ["tabs", "*://*.substack.com/*"],
  "icons": {
    "16": "icon-16.png",
    "48": "icon-48.png",
    "128": "icon-128.png"
  }
}
NOTE: I’ve given permission to any substack.com domains, but this notably won’t work if people are using custom domains for their newsletter.

And then I rewrote the background.js file to run automatically (instead of on the extension click event), as well as reference inject.js.

chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
  if (
    changeInfo.status === "complete" &&
    tab.url &&
    tab.url.includes("substack.com")
  ) {
    chrome.tabs.executeScript(tabId, { file: "inject.js" });
  }
});

inject.js

If you want to see the full source code for inject.js, check out the extension repo. It’s a little too long to include here.

Highlights:

  • Adds a 1px border to the inline code style

    • Sets the border color to match the primary link color used in the Substack post

  • Adds a custom background color to the inline code style

    • This background is derived from the post’s original background color:

    • It calculates the luminance of the original background

    • If the background is bright, it darkens it slightly

    • If it’s dark, it brightens it slightly

    • The adjustment factor is 1.05 for both cases

Screenshots

Here is a before and after of my Django + HTMX tutorial:

Before:

After:

And here is a “dark mode” example from Simon Willison’s blog that I opened in from the Substack web app. (This way of viewing newsletters introduces different HTML and CSS classes, which our browser extension has to target).

Before:

After:

Conclusion, What’s Next?

The source code for this extension can be found here. If you want, try cloning it and adding it to your browser.

This was really just a proof-of-concept. I don’t plan on publishing this to the extension store.

But I am interested in the technical limits of this approach. Could it be used to inject actual programming-language-specific syntax highlighting to code blocks? Probably?


Thanks for your time,

Nick