惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threat Research - Cisco Blogs
H
Hacker News: Front Page
IT之家
IT之家
I
Intezer
GbyAI
GbyAI
MongoDB | Blog
MongoDB | Blog
博客园_首页
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
S
SegmentFault 最新的问题
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Threatpost
Cisco Talos Blog
Cisco Talos Blog
C
Check Point Blog
P
Proofpoint News Feed
P
Privacy International News Feed
有赞技术团队
有赞技术团队
T
Tailwind CSS Blog
Scott Helme
Scott Helme
U
Unit 42
J
Java Code Geeks
W
WeLiveSecurity
H
Hackread – Cybersecurity News, Data Breaches, AI and More
C
CERT Recently Published Vulnerability Notes
小众软件
小众软件
The Hacker News
The Hacker News
L
LINUX DO - 热门话题
博客园 - 【当耐特】
G
Google Developers Blog
Latest news
Latest news
AWS News Blog
AWS News Blog
NISL@THU
NISL@THU
S
Secure Thoughts
P
Proofpoint News Feed
L
Lohrmann on Cybersecurity
F
Full Disclosure
S
Securelist
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Engineering at Meta
Engineering at Meta
Security Archives - TechRepublic
Security Archives - TechRepublic
人人都是产品经理
人人都是产品经理
T
Tor Project blog
Recent Announcements
Recent Announcements
Security Latest
Security Latest
N
News | PayPal Newsroom
A
About on SuperTechFans
Hugging Face - Blog
Hugging Face - Blog
Y
Y Combinator Blog
大猫的无限游戏
大猫的无限游戏
博客园 - Franky
T
The Blog of Author Tim Ferriss

Pierce Freeman

A browser for agents | Pierce Freeman The grey market of podcast appearances The way I travel | Pierce Freeman Fixing slow AWS uploads | Pierce Freeman Local tools should still use vaults We solved scratch content first Starting a podcast in 2025 Being late but still being early Automating our home video imports Adding my parents to tailscale A deep dive on agent sandboxes Language servers for AI | Pierce Freeman My simple home podcast studio We need centralized infrastructure | Pierce Freeman Coercing agents to follow conventions using AST validation My unified theory of social selling My personal backup strategy | Pierce Freeman July updates to the homelab How the KV Cache works httpx is the right way to do web requests in Python Reputation is becoming everything | Pierce Freeman Building a (kind of) invisible mac app Updated knowledge in language models Making an ascii animation | Pierce Freeman How speculative decoding works | Pierce Freeman Under the hood of Claude Code Doing things because they're easy, not hard Speeding up sideeffects with JIT in mountaineer Firehot for hot reloading in Python Misadventures in Python hot reloading How text diffusion works | Pierce Freeman The tenacity of modern LLMs The ergonomics of rails | Pierce Freeman How language servers work | Pierce Freeman Just add eggs | Pierce Freeman Unfortunately SEO still matters | Pierce Freeman The futility of human-only web requirements Setting up Input Leap | Pierce Freeman Checking in on Waymo | Pierce Freeman The react revolution | Pierce Freeman Speeding up many small transfers to a unifi nas Quick notes on swift libraries AI engineering is a different animal San Francisco | Pierce Freeman Debugging a mountaineer rendering segfault Local network config on macOS Building our home network | Pierce Freeman Introducing Envelope.dev | Pierce Freeman Legacy code and AI copilots Typehinting from day-zero | Pierce Freeman Generating database migrations with acyclic graphs Lofoten | Pierce Freeman Mountaineer v0.1: Webapps in Python and React Constraining LLM Outputs | Pierce Freeman Passthrough above all | Pierce Freeman Accuracy in kudos | Pierce Freeman How quick we are to adapt The curious case of LM repetition Costa Rica | Pierce Freeman Debugging chrome extensions with system-level logging Speeding up runpod | Pierce Freeman Parsing Common Crawl in a day for $60 An era of rich CLI All or nothing with remote work The Next 10 Years | Pierce Freeman Adding wheels to flash-attention | Pierce Freeman LLMs as interdisciplinary agents | Pierce Freeman New Zealand | Pierce Freeman Representations in autoregressive models | Pierce Freeman Let's talk about Siri | Pierce Freeman Minimum viable public infrastructure | Pierce Freeman Reasoning vs. Memorization in LLMs Automatically migrate enums in alembic Greater sequence lengths will set us free On learning to ski | Pierce Freeman Dolomites | Pierce Freeman Using grpc with node and typescript Opportunity years | Pierce Freeman Buzzword peaks and valleys | Pierce Freeman Buenos Aires | Pierce Freeman Network routing interaction on MacOS Independent work: November recap | Pierce Freeman Debugging slow pytorch training performance The provenance of copy and paste Debugging tips for neural network training Patagonia | Pierce Freeman Santiago | Pierce Freeman My 2022 digital travel kit AWS vs GCP - GPU Availability V2 Independent work: October recap | Pierce Freeman Planning Patagonia | Pierce Freeman Relationship modeling | Pierce Freeman The power of status updates A new chapter | Pierce Freeman Give my library a coffee shop AWS vs GCP - GPU Availability V1 Switzerland | Pierce Freeman Headfull browsers beat headless | Pierce Freeman Webcrawling tradeoffs | Pierce Freeman Copenhagen | Pierce Freeman
Inline footnotes with html templates
2023-12-17 · via Pierce Freeman

![Screenshot of inline footnotes](/notes-images/inline_footnotes/Screenshot%202023-12-17%20at%204.22.36 PM.png)

I couldn’t write without footnotes. Or at least - I couldn't write enjoyably without them. They let you sneak in anecdotes, additional context, and maybe even a joke or two. They're the love of my writing life.

For that reason, I wanted to get them closer to the content itself. By default Markdown and Markdown parsers will render footnotes at the end of the article. For instance:

| This is a blog post    |
| with a footenote. [^1] |
| Hear me roar.          |

[^1]: And if they don't show up at all, well, there I can't really help you.
[^2]: If you have a fully unresponsive site where words are locked into their line width regardless of the overall browser setting, you can pre-calculate the offsets on the server side. Otherwise if you have different configuration for mobile and desktop, or just otherwise want to allow some word wrapping to happen client side, it will always come back to JS.

My goal was to instead have them look more like:

| This is a blog post | | with a footenote. 1 | | footnote | | Hear me roar. |


Okay. Where's the tech design here?

1. Footnotes should display next to their reference tags.
1. They should resize and change position dynamically if the text wraps or box model changes.
1. If the window is too small, they should remain at the end.

The end product looks like this. If they don't show up inline, you might need to resize your browser.[^1]

The only way to accomplish this is with a bit of Javascript.[^2] Since this site uses vanilla html and Javascript only, we can add a small `<script>` to the page that accomplishes exactly that.

First we style our post contents to have two grid columns: one for the content and one for the footnotes.

```html
<div class="grid grid-cols-12">
	<div class="col-span-12 lg:col-span-8 px-4" id="content-raw">
	</div>
	<div class="hidden lg:block lg:col-span-4 relative" id="footnotes-inline">
	</div>
</div>

We use media queries to show the footnotes on lg screens and above, and hide them otherwise. We then style the inline footnote as we want. This is where the <template> tag comes in. Templates aren't rendered into the DOM and need to be instantiated in Javascript before use.

<div class="hidden lg:block lg:col-span-4 relative" id="footnotes-inline">
	<template id="footnote-template">
	<div class="text-slate-400 prose border-l-4 pl-2">
	{CONTENT}
	</div>
	</template>
</div>

So let's actually write this thing. Our approach will be to scan the post for all instances of footnotes and then calculate where it falls on the page. These particular class names mirror what the python Markdown package outputs into html.

const contentRaw = document.getElementById("content-raw");
const footnotes = document.getElementById("footnotes-inline");
const footnoteTemplate = document.getElementById("footnote-template");

document.querySelectorAll('.footnote-ref').forEach(callout => {
	// Based on the a tag link, get the actual footnote
	const footnoteId = callout.getAttribute("href").replace("#", "");
	const footnoteOriginal = document.getElementById(footnoteId);

	// Get the position relative to the parent container and scroll
	const calloutRect = callout.getBoundingClientRect();
	const parentRect = contentRaw.getBoundingClientRect();
	const top = calloutRect.top - parentRect.top;

	// New footnote div
	let footnote = document.createElement("div");
	footnote.style.position = "absolute";
	footnote.style.top = top + "px";
	footnote.innerHTML = footnoteTemplate.innerHTML.replace("{CONTENT}", footnoteOriginal.innerHTML);

	footnotes.appendChild(footnote);

	// Find where this has rendered to present a cap on the y-axis for subsequent notes
	const footnoteRect = footnote.getBoundingClientRect();
	const footnoteTop = footnoteRect.top - parentRect.top;
	const footnoteBottom = footnoteTop + footnoteRect.height;
});

This should mostly work already. But there are a few gotchas:

  • This will only position the inline footnote once at load time. If you resize the page or have dynamic elements that change the article as users read it, the footnotes will lose their anchor. They'll be lost, floating in space alone.
  • If two footnote references are close to one another in the original article, they might overlap when we render them on the sidebar.

We fix this by updating the footnotes whenever the page changes size. You could use the legacy window.onresize callback for doing this, but there are settings in which the content has changed even when the window has not. The ResizeObserver is purpose built for this case. We install the hook for our content to get notifications anytime the actual content causes a reflow. We also add some minmax logic to ensure that footnotes aren't allowed to overlap one another.

const contentRaw = document.getElementById("content-raw");
const footnotes = document.getElementById("footnotes-inline");
const footnoteTemplate = document.getElementById("footnote-template");

const footnoteSpacePadding = 20;

function updateFootnotes() {
    let maxY = 0;

    // Clear existing footnotes to avoid duplicates
    footnotes.innerHTML = '';

    document.querySelectorAll('.footnote-ref').forEach(callout => {
        const footnoteId = callout.getAttribute("href").replace("#", "");
        const footnoteOriginal = document.getElementById(footnoteId);

        const calloutRect = callout.getBoundingClientRect();
        const parentRect = contentRaw.getBoundingClientRect();
        const top = Math.max(maxY + footnoteSpacePadding, calloutRect.top - parentRect.top);

        let footnote = document.createElement("div");
        footnote.style.position = "absolute";
        footnote.style.top = top + "px";
        footnote.innerHTML = footnoteTemplate.innerHTML.replace("{CONTENT}", footnoteOriginal.innerHTML);

        footnotes.appendChild(footnote);

        const footnoteRect = footnote.getBoundingClientRect();
        const footnoteTop = footnoteRect.top - parentRect.top;
        const footnoteBottom = footnoteTop + footnoteRect.height;
        maxY = footnoteBottom;
    });
}

// Create a new ResizeObserver
const resizeObserver = new ResizeObserver(entries => {
    for (let entry of entries) {
        // Check if the contentRaw is being observed and resized
        if (entry.target === contentRaw) {
            updateFootnotes();
        }
    }
});

// Start observing the contentRaw element
resizeObserver.observe(contentRaw);

// Call the function on initial load
updateFootnotes();

And that's it. Inline footnotes with vanilla html. Coming soon to a personal blog near you.

  1. And if they don't show up at all, well, there I can't really help you. ↩