Skip to content

programming

Better Footnotes in Ghost with Obsidian

Because text is limited to the domain of one direction ^ftmvri, adding breaks in the tedium for the reader is critical for engagement. Footnotes do a great job of this.[1] In addition, footnotes play the important role of allowing the writer to convey information that is akin to world building (basically the way "meanwhile" is used on TV shows) without having to use other symbols like "--" or ";" to symbolize a change in pace.

This doesn't mean footnotes are easy -- for the writer or the reader.

In books, the reader has to make the conscious decision to stop reading to go to the bottom of the document. This runs the risk of disrupting flow or even worse, causing the reader to stop all together. Alternatively, the reader can read all the footnotes at once after finishing a piece, but this sucks too. Most often, readers will be tired after consuming a whole article/blog/chapter, and footnotes just feel like more work.

For the writer, there is a choice to be made about whether to put something in a footnote or find a way to fit that information into the overall word budget of the piece. Not at all pleasant!

So are we just shit out of luck?

Fear not friends, if you too have a footer fetish, there is a light at the end of the tunnel!

Thanks to the web, we can use CSS and JS to hide information that sits right in the middle[2] of the flow of the piece. Not only that, but we can put images in footnotes,[3] or even full quotes and links![4] And best yet, all the footnotes are hidden behind cute ellipsis, and don't take up real estate at the bottom of the post!

Enough preamble, let's add some footnotes!

In Obsidian

Firstly, in Obsidian you're going to want to install the Footnote Shortcut plugin. This will allow you to create footnotes with a single hotkey (I mapped mine to alt-shift-f).

Next, if you publish to Ghost[5] you're going to want to use the Obsidian Ghost Plugin. This plugin converts markdown to HTML with markdown-it.[6] Specifically, and this is important so pay attention(!) you are going to want to use my fork here: bramses/obsidian-ghost-publish. This fork has the code you will need to run the footnote logic[7] as well as including other nice updates (The Obsidian Ghost Plugin).

That's all you really need on the Obsidian side of things.[8] Let's take a look at what we need to do in Ghost.

In Ghost

The core of the footnote experience is littlefoot.js. It handles pretty much everything, so all we need to do is make it play nicely with Ghost.[9]

Most importantly, you need to be able to edit the theme in Ghost, specifically gulp.js and default.hbs, so if you don't have a custom theme going, this tutorial will be harder to complete. This tutorial will walk you through a deep dive of how to do this, but in short:

  1. Inside your theme, run npm install --save littlefoot
  2. add littlefoot to gulpfile.js, mine looks like the code below:
function js(done) {
    pump([
        src([
            'node_modules/@tryghost/shared-theme-assets/assets/js/v1/lib/**/*.js',
            'node_modules/@tryghost/shared-theme-assets/assets/js/v1/main.js',
            'assets/js/lib/*.js',
            'assets/js/main.js',
            'node_modules/littlefoot/dist/littlefoot.js' // https://aspirethemes.com/blog/ghost-theme-load-scripts
        ], {sourcemaps: true}),
        concat('main.min.js'),
        uglify(),
        dest('assets/built/', {sourcemaps: '.'}),
        livereload()
    ], handleError(done));
}

  1. in screen.css add the following line to the imports:
@import "../../node_modules/littlefoot/dist/littlefoot.css";
  1. In default.hbs add the following script to the footer, right above </body>:
<script>
  littlefoot.littlefoot({
    activateOnHover: true,
  }) // Pass any littlefoot settings here.
</script>
  1. Run yarn zip to zip your theme
  2. Upload the zip to your theme in Ghost

That's it! Congrats on your new, sexy footnotes![10]


If you like what you read, please hit that Subscribe button in the corner to join other thinkers in this community! I send out multiple think pieces or Maps of Content a week as well as many notes that can only be seen by subscribing to the RSS feed above.


  1. or do they?…ok yeah they do

  2. Everything, everything'll be just fine
    Everything, everything'll be alright

  3. https://bram-adams.ghost.io/content/images/2023/02/leda.jpeg
    leda.jpeg
  4. Jimmy Eat World - The Middle (Official Music Video) - YouTube

  5. which I safely assume you do since you're reading this tutorial

  6. and the markdown-it-footnote plugin, of course!

  7. Ghost needs to upload footnotes as an HTML card or the API swallows the markup for some annoying reason

  8. One more thing. You probably want to install Linter and enable footnote re-indexing to make sure footnotes stay in order

  9. easier said than done…

  10. I hope we got off on the right foot!

A P5 + Replit + Ghost Embed Test

I made this a while ago, back in my tech art era.

To make the embed work use /embed and copy a public repl with the URL parameters: &lite=1&outputonly=1.

Hit the play button!

Inbox Processing in Flow State with Alfred

Problem

Every time I want to get into flow mode, I feel like I need music to get going. Especially when processing Obsidian notes, which requires a clear mind to place things and occasionally jump into a deep writing exercise.

Solution

Create a Alfred workflow that does the following:

  1. Open Obsidian up to the BHOV Inbox note (/Computed/To Process)
  2. Use Alfred Spotify Mini to play a random song or a playlist
  3. Run ztl to activate

Spotify

https://bram-adams.ghost.io/content/images/2023/02/alfred-spotify-and-obsidian.png
alfred spotify and obsidian.png

Obsidian

https://bram-adams.ghost.io/content/images/2023/02/obsidian-uri-alfred.png
obsidian uri alfred.png

Instructions

Use Obsidian Advanced URI to open a note (can be downloaded from Obsidian plugin store) and uses Spotify Mini Alfred plugin (https://alfred-spotify-mini-player.com) to play a song.

Requires:
- set up Spotify Alfred
- Obsidian
- Obsidian Advanced URI plugin

GitHub: https://github.com/bramses/process-notes-alfred/tree/main

I Programmed a YouTube Clipper

Just to capture this goated moment on a Kripp vid

Are you on the guest list? x 3

It captures the current time from a YouTube share link and adds 5 seconds to the end time, subtracts 5 seconds from the start time.

Get it here ⬇️
GitHub - bramses/ytclip-10s

Impetus

https://bram-adams.ghost.io/content/images/2023/02/alfred-ytc-5-Screenshot-2023-02-07-00-15-21.png
alfred ytc 5 Screenshot 2023-02-07 00-15-21.png

Alfred

I then wrapped it in an Alfred script to run whenever I type ytc

https://bram-adams.ghost.io/content/images/2023/02/alfred-ytc-1-Screenshot-2023-02-07-00-11-34.png
alfred ytc 1 Screenshot 2023-02-07 00-11-34.png
https://bram-adams.ghost.io/content/images/2023/02/alfred-ytc-2-Screenshot-2023-02-07-00-11-40.png
alfred ytc 2 Screenshot 2023-02-07 00-11-40.png
https://bram-adams.ghost.io/content/images/2023/02/alfred-ytc-3-Screenshot-2023-02-07-00-11-48.png
alfred ytc 3 Screenshot 2023-02-07 00-11-48.png
https://bram-adams.ghost.io/content/images/2023/02/alfred-ytc-4-Screenshot-2023-02-07-00-11-55.png
alfred ytc 4 Screenshot 2023-02-07 00-11-55.png

AI, Regex, Twitter, Blockquotes, Readwise, Ghost

This may sound like a laundry list of random buzzword items -- but in reality these are all stakeholders to complete one seemingly simple task: converting blockquotes from Readwise format into Ghost Twitter Card format.

First, I'd like to go on record stating that I hate writing regex. It feels like writing cursive during middle school, with all the weird symbols and arbitrary rules abound.

you cant just have the b like that, bro.
why not?
it'll get confused with the f.
…what?
https://bram-adams.ghost.io/content/images/2023/02/cursive.png
cursive.png

Instead of sticking to the regular game of conditionals like everyone else, Regex decides to be "unique" and force the programmer to create the whole damn matcher in one go: if you don't match even a single character, you're shit out of luck.

Even with the power of AI on my side, I struggled for hours to make this simple regex. The rules I wanted:

  1. Match lines that start with '>'
  2. Reject any blockquotes that don't end with a Twitter link
  3. Swallow the ENTIRE blockquote and replace it with the correct Ghost HTML.

It was #3 that really fucked me. Tweets came in a lot of different styles like:


// multiline blockquote with multiple URLs

<figure class="kg-card kg-embed-card"><div class="twitter-tweet twitter-tweet-rendered"><iframe src="https://twitter.com/tayroga/status/1621335910808948736?ref_src=twsrc%5Etfw" width="550" height="550" frameborder="0" scrolling="no" allowfullscreen="true" style="border: none; max-width: 100%; min-width: 100%;"></iframe></div></figure>

// multiline blockquote -- no URLs

<figure class="kg-card kg-embed-card"><div class="twitter-tweet twitter-tweet-rendered"><iframe src="https://twitter.com/BrianNorgard/status/1621348809627541505?ref_src=twsrc%5Etfw" width="550" height="550" frameborder="0" scrolling="no" allowfullscreen="true" style="border: none; max-width: 100%; min-width: 100%;"></iframe></div></figure>


// single line with Twitter video embedded

<figure class="kg-card kg-embed-card"><div class="twitter-tweet twitter-tweet-rendered"><iframe src="https://twitter.com/Money__Doug/status/1620546196560551936?ref_src=twsrc%5Etfw" width="550" height="550" frameborder="0" scrolling="no" allowfullscreen="true" style="border: none; max-width: 100%; min-width: 100%;"></iframe></div></figure>

ChatGPT + Codex to the Rescue (kinda)

I started humbly in JS with this comment to Copilot:

// take the url from view tweet format and replace the entire blockquote with a tweet embed iframe

That created something that successfully extracted the tweet URL, but it failed miserably at eating the rest of the blockquote.

ChatGPT didn't fare much better. It got pretty obsessed with isolating the status ID from the string despite my pleading:

https://bram-adams.ghost.io/content/images/2023/02/chatgpt-regex-1.png
chatgpt regex 1.png
https://bram-adams.ghost.io/content/images/2023/02/chatgpt-regex-2.png
chatgpt regex 2.png
https://bram-adams.ghost.io/content/images/2023/02/chatgpt-regex-3.png
chatgpt regex 3.png
https://bram-adams.ghost.io/content/images/2023/02/chatgpt-regex-4.png
chatgpt regex 4.png

also i did not know that ChatGPT did images, whaddya know?

Eventually ChatGPT gave me something usable:

/^>.*\n.*\(https:\/\/twitter.com\/.*\/status\/(\d+)\)/s

which worked…kinda.

Regexr is the 🐐

At the end of the day, I had to use my boring soggy, water-logged ape brain to solve the Regex debacle. I played with placement of * and + for what felt like eternity in Regexr, and finally, blindly, stumbled upon the right answer.

It turns out I still had to hack it by artifically inserting a newline in front of View Tweet which defeats the purpose, but whatever, fuck it.

Full code below:

// take the url from view tweet format and replace the entire blockquote with a tweet embed iframe

// add a new line before every 
([View Tweet]

data.content = data.content.replace(/\(\[View Tweet\]/gm, "\n
([View Tweet]");

  

data.content = data.content.replace(

/(^>.*\n.*)*\(https:\/\/twitter.com\/(.*)\/status\/(\d+)\)\)/gm,

(match: any, p1: string, p2: string, p3: string) => {

console.log("p1", p1);

console.log("p2", p2);

  
  

const url = `https://twitter.com/${p2}/status/${p3}?ref_src=twsrc%5Etfw`;

return `<figure class="kg-card kg-embed-card"><div class="twitter-tweet twitter-tweet-rendered"><iframe src="${url}" width="550" height="550" frameborder="0" scrolling="no" allowfullscreen="true" style="border: none; max-width: 100%; min-width: 100%;"></iframe></div></figure>`;

}

)

All I can truly say is the creators regexr deserve a Nobel Peace Prize. They've single handedly prevented so many wars caused out of sheer frustration from dealing with the eccentricities of Regex.

To celebrate, here's a tweet, straight from Readwise in Obsidian, delivered hand-picked into Ghost.

And to you, Regex?

If there's a hell, I'll see you there.

Redundancy in Software

An experimental evaluation of the assumption of independence in multiversion programming – John Knight and Nancy Leveson Behind this dry title lies something very interesting. I first heard about this paper from Ralph Johnson in a newsgroup discussion about program correctness. It turns out that one of the avenues that engineers in other disciplines take to make their products stronger – redundancy – doesn’t really work in software. Multi-version programming was the idea that you could decrease faults in critical systems by handing the spec to several teams, having them develop the software independently, and then having the systems run in parallel. A monitoring process verifies their results and if there is any discrepancy it picks the most common result. Sounds like it should work, right? Well..

I've noticed when I spend time adding redundancy in my codebases, I build defenses on "the wrong side of the house". I (and I imagine most devs) create tests that cover the safe path, and the thing that ends up breaking is hardly what was expected. Perhaps thats the curse of not operating in the world of physics, where the rules are deadly, but clear.

100R Out Here Making Code Art Real

Orca is an esoteric programming language, designed to create procedural sequencers in which each letter of the alphabet is an operation, where lowercase letters operate on bang, uppercase letters operate each frame.
The application is not a synthesiser, but a flexible livecoding environment capable of sending MIDI, OSC & UDP to your audio interface, like Ableton, Renoise, VCV Rack or SuperCollider.

I've spent many years fighting my engineering background to try and become an artist, and 100R has cracked the code. LITERALLY. They realize that code can be beautiful, if you allow it to be. I wonder if Simon Sarris would see the intimacy in Orca . This programming style also reminds me of Porter Robinson's work.

https://bram-adams.ghost.io/content/images/2023/01/orca-2d.png
orca 2d.png
https://bram-adams.ghost.io/content/images/2023/01/porter-nurture-cover.png
porter nurture cover.png

Sometimes You Just Need to Start Cutting Shit

Sometimes you have to stop sharpening the saw, and just start cutting shit
Some people tend to jump into problems and just start writing code. Other people tend to want to research and research and get caught in analysis paralysis. In those cases, set a deadline for yourself and just start exploring solutions. You’ll quickly learn more as you start solving the problem, and that will lead you to iterate into a better solution. (View Highlight)

This is a style I hope to integrate more in 2023. I have no trouble doing this in my fields of expertise, it is where I am a novice that I struggle to try the iterative route of working, specifically in music production/more fine art type things like taking beautiful pictures and videos.