summaryrefslogtreecommitdiff
path: root/src/plugins/remark-admonitions.ts
diff options
context:
space:
mode:
authorDawid Rycerz <dawid@rycerz.xyz>2025-07-03 10:56:21 +0300
committerDawid Rycerz <dawid@rycerz.xyz>2025-07-03 10:56:21 +0300
commit456cf011b36de91c9936994b1fa45703adcd309b (patch)
tree8e60daf998f731ac50d100fa490eaecae1168042 /src/plugins/remark-admonitions.ts
Initial fork of chrismwilliams/astro-theme-cactus theme
Diffstat (limited to 'src/plugins/remark-admonitions.ts')
-rw-r--r--src/plugins/remark-admonitions.ts102
1 files changed, 102 insertions, 0 deletions
diff --git a/src/plugins/remark-admonitions.ts b/src/plugins/remark-admonitions.ts
new file mode 100644
index 0000000..89c8be9
--- /dev/null
+++ b/src/plugins/remark-admonitions.ts
@@ -0,0 +1,102 @@
+import type { AdmonitionType } from "@/types";
+import { type Properties, h as _h } from "hastscript";
+import type { Node, Paragraph as P, Parent, PhrasingContent, Root } from "mdast";
+import type { Directives, LeafDirective, TextDirective } from "mdast-util-directive";
+import { directiveToMarkdown } from "mdast-util-directive";
+import { toMarkdown } from "mdast-util-to-markdown";
+import { toString as mdastToString } from "mdast-util-to-string";
+import type { Plugin } from "unified";
+import { visit } from "unist-util-visit";
+
+// Supported admonition types
+const Admonitions = new Set<AdmonitionType>(["tip", "note", "important", "caution", "warning"]);
+
+/** Checks if a string is a supported admonition type. */
+function isAdmonition(s: string): s is AdmonitionType {
+ return Admonitions.has(s as AdmonitionType);
+}
+
+/** Checks if a node is a directive. */
+function isNodeDirective(node: Node): node is Directives {
+ return (
+ node.type === "containerDirective" ||
+ node.type === "leafDirective" ||
+ node.type === "textDirective"
+ );
+}
+
+/**
+ * From Astro Starlight:
+ * Transforms directives not supported back to original form as it can break user content and result in 'broken' output.
+ */
+function transformUnhandledDirective(
+ node: LeafDirective | TextDirective,
+ index: number,
+ parent: Parent,
+) {
+ const textNode = {
+ type: "text",
+ value: toMarkdown(node, { extensions: [directiveToMarkdown()] }),
+ } as const;
+ if (node.type === "textDirective") {
+ parent.children[index] = textNode;
+ } else {
+ parent.children[index] = {
+ children: [textNode],
+ type: "paragraph",
+ };
+ }
+}
+
+/** From Astro Starlight: Function that generates an mdast HTML tree ready for conversion to HTML by rehype. */
+// biome-ignore lint/suspicious/noExplicitAny: <explanation>
+function h(el: string, attrs: Properties = {}, children: any[] = []): P {
+ const { properties, tagName } = _h(el, attrs);
+ return {
+ children,
+ data: { hName: tagName, hProperties: properties },
+ type: "paragraph",
+ };
+}
+
+export const remarkAdmonitions: Plugin<[], Root> = () => (tree) => {
+ visit(tree, (node, index, parent) => {
+ if (!parent || index === undefined || !isNodeDirective(node)) return;
+ if (node.type === "textDirective" || node.type === "leafDirective") {
+ transformUnhandledDirective(node, index, parent);
+ return;
+ }
+
+ const admonitionType = node.name;
+ if (!isAdmonition(admonitionType)) return;
+
+ let title: string = admonitionType;
+ let titleNode: PhrasingContent[] = [{ type: "text", value: title }];
+
+ // Check if there's a custom title
+ const firstChild = node.children[0];
+ if (
+ firstChild?.type === "paragraph" &&
+ firstChild.data &&
+ "directiveLabel" in firstChild.data &&
+ firstChild.children.length > 0
+ ) {
+ titleNode = firstChild.children;
+ title = mdastToString(firstChild.children);
+ // The first paragraph contains a custom title, we can safely remove it.
+ node.children.splice(0, 1);
+ }
+
+ // Do not change prefix to AD, ADM, or similar, adblocks will block the content inside.
+ const admonition = h(
+ "aside",
+ { "aria-label": title, class: "admonition", "data-admonition-type": admonitionType },
+ [
+ h("p", { class: "admonition-title", "aria-hidden": "true" }, [...titleNode]),
+ h("div", { class: "admonition-content" }, node.children),
+ ],
+ );
+
+ parent.children[index] = admonition;
+ });
+};