Last active
August 14, 2026 01:43
-
-
Save spjpgrd/7722ecb6f1680f8111a1d07d70ca9a71 to your computer and use it in GitHub Desktop.
Sätteri HAST plugin for changing footnote and reference IDs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * 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