Skip to content

Instantly share code, notes, and snippets.

@spjpgrd
Last active August 14, 2026 01:43
Show Gist options
  • Select an option

  • Save spjpgrd/7722ecb6f1680f8111a1d07d70ca9a71 to your computer and use it in GitHub Desktop.

Select an option

Save spjpgrd/7722ecb6f1680f8111a1d07d70ca9a71 to your computer and use it in GitHub Desktop.
Sätteri HAST plugin for changing footnote and reference IDs
/**
* I really didn't like the default unsightly #user-content-fnref IDs and links
* This changes it to footnote-{label} and id-{label}
* <sup><a href="#footnote-1" id="id-1" data-footnote-ref>1</a></sup>
* ...
* <li id="footnote-1"><p>Yada yada note text <a href="#id-1" data-footnote-backref>↩︎</a></p></li>
*/
import { defineHastPlugin } from 'satteri'
function str(value: unknown): string | undefined {
return typeof value === 'string' ? value : undefined
}
function hasProp(props: Record<string, unknown>, ...keys: string[]): boolean {
return keys.some((k) => props[k] !== undefined)
}
const KNOWN_ID_PREFIXES = ['user-content-fnref-', 'user-content-fn-', 'fnref-']
function stripKnownPrefix(id: string): string {
for (const prefix of KNOWN_ID_PREFIXES) {
if (id.startsWith(prefix)) return id.slice(prefix.length)
}
return id
}
const satteriChangeFootnoteIds = defineHastPlugin({
name: 'strip-footnote-ids',
element: {
filter: ['a', 'li'],
visit(node, ctx) {
const props = (node.properties ?? {}) as Record<string, unknown>
if (hasProp(props, 'dataFootnoteRef', 'data-footnote-ref')) {
const id = str(props.id)
const href = str(props.href)
if (id) {
const label = stripKnownPrefix(id)
ctx.setProperty(node, 'id', `id-${label}`)
}
if (href?.startsWith('#')) {
const label = stripKnownPrefix(href.slice(1))
ctx.setProperty(node, 'href', `#footnote-${label}`)
}
return
}
if (hasProp(props, 'dataFootnoteBackref', 'data-footnote-backref')) {
const href = str(props.href)
if (href?.startsWith('#')) {
const label = stripKnownPrefix(href.slice(1))
ctx.setProperty(node, 'href', `#id-${label}`)
}
return
}
if (node.tagName === 'li') {
const parent = ctx.parent(node) // <ol>
const grandparent =
parent && parent.type === 'element' ? ctx.parent(parent) : undefined // <section>
const gpProps = (
grandparent && grandparent.type === 'element'
? (grandparent.properties ?? {})
: {}
) as Record<string, unknown>
const isFootnoteList = hasProp(
gpProps,
'dataFootnotes',
'data-footnotes',
)
if (isFootnoteList) {
const id = str(props.id)
if (id) {
const label = stripKnownPrefix(id)
ctx.setProperty(node, 'id', `footnote-${label}`)
}
}
}
},
},
})
export default satteriChangeFootnoteIds
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment