Skip to content

Instantly share code, notes, and snippets.

@wooorm
Last active June 27, 2024 15:02
Show Gist options
  • Save wooorm/e5347ee97569bf6f7a079357c419cf7e to your computer and use it in GitHub Desktop.
Save wooorm/e5347ee97569bf6f7a079357c419cf7e to your computer and use it in GitHub Desktop.
remark-abbr-start
import fs from "node:fs/promises";
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
import myCustomRemarkAbbrPlugin from "./index.js";
const file = await unified()
.use(remarkParse)
.use(myCustomRemarkAbbrPlugin)
.use(remarkRehype)
.use(rehypeStringify)
.process(await fs.readFile("example.md"));
console.log(file);

This is an abbreviation: HTML.

*[HTML]: HyperText Markup Language

import { visit } from "unist-util-visit";
import { findAndReplace } from "mdast-util-find-and-replace";
import escapeStringRegexp from "escape-string-regexp";
// This plugin does two things:
// a) First, it tells micromark and friends how to parse abbreviation definitions.
// b) Much later, it replaces abbreviations use, in the AST, with custom nodes.
/** @type {import('unified').Plugin<[], import('mdast').Root>} */
export default function myCustomRemarkAbbr() {
const data = this.data();
// Register things that deal with parsing.
// Micromark extensions define how to parse syntax.
// This is a micromark extension.
// It would look like <https://github.com/micromark/micromark/blob/main/packages/micromark-core-commonmark/dev/lib/definition.js>.
add("micromarkExtensions", myCustomMicromarkAbbrExtension);
// This is a `mdast-util-from-markdown` extension.
// `mdast-util-from-markdown` extensions define how to handle the resulting tokens/events from micromark extensions and build nodes from them.
// It would look like <https://github.com/syntax-tree/mdast-util-math/blob/4670ae469c47b53d9661e7a116a869493f4b1800/index.js#L24>.
// But instead of math, it would handle the tokens that are added by your custom micromark extension.
// The rest of the codes expects the resulting nodes from your extension to look as follows:
// `{type: 'my-abbr-definition-node-type', id: string, title: string}`.
add("fromMarkdownExtensions", myCustomMdastUtilFromMarkdownAbbrExtension);
// To do: if you need to compile back to markdown again, you need to define how to turn nodes back into a string of markdown.
// If you need this, it would look like <https://github.com/syntax-tree/mdast-util-math/blob/4670ae469c47b53d9661e7a116a869493f4b1800/index.js#L125>.
// But instead of math, it would handle the nodes that are created by your `mdast-util-from-markdown` extension.
// add("toMarkdownExtensions", myCustomMicromarkAbbrToMarkdownExtension);
// This a) finds all defined abbr definitions, b) replaces all abbr uses with what is defined.
return function transform(tree) {
/** @type {Record<string, string>} */
const definitionToTitle = Object.create(null);
visit(tree, "my-abbr-definition-node-type", (node) => {
// To do: prefer the first of duplicates?
definitionToTitle[node.id] = node.title;
});
// Create a regex to look for ID use.
let search = new RegExp(
Object.keys(definitionToTitle).map(escapeStringRegexp).join("|"),
"g"
);
// Replace ID use with custom abbr use nodes.
findAndReplace(tree, search, (/** @type {string} */ $0) => {
let title = definitionToTitle;
return {
type: "my-abbr-use-node-type",
value: $0,
// We are in markdown, but we want to define how our custom nodes get turned into HTML.
// For more on this, see: <https://github.com/remarkjs/remark-rehype#what-is-this>.
// For more on these fields, see: <https://github.com/syntax-tree/mdast-util-to-hast#fields-on-nodes>.
data: {
hTagName: "abbr",
hProperties: {
title,
},
},
};
});
};
/**
* @param {string} field
* @param {unknown} value
*/
function add(field, value) {
const list = /** @type {unknown[]} */ (
// Other extensions
data[field] ? data[field] : (data[field] = [])
);
list.push(value);
}
}
{
"name": "example",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"rehype-stringify": "^9.0.3",
"remark-parse": "^10.0.1",
"remark-rehype": "^10.1.0",
"unified": "^10.1.2"
},
"dependencies": {
"@types/mdast": "^3.0.10",
"escape-string-regexp": "^5.0.0",
"mdast-util-find-and-replace": "^2.2.1",
"unist-util-visit": "^4.1.0"
}
}
@wooorm
Copy link
Author

wooorm commented Jan 16, 2023

I’m not sure how you found this gist, it was I think privately helping someone with a ton of legacy markdown.
I recommend going with directives if you can!

@talatkuyuk
Copy link

Thank you very much for quick reply. I will check it out.

@slorber
Copy link

slorber commented Jun 27, 2024

Also found it on Google 😄

In case it's helpful to anyone, here are a few tips if you want to implement this thanks to remark-directive:
facebook/docusaurus#10242 (reply in thread)

@wooorm
Copy link
Author

wooorm commented Jun 27, 2024

Oh no! 😅
Anyway, good answer, slorber!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment