From 456cf011b36de91c9936994b1fa45703adcd309b Mon Sep 17 00:00:00 2001 From: Dawid Rycerz Date: Thu, 3 Jul 2025 10:56:21 +0300 Subject: Initial fork of chrismwilliams/astro-theme-cactus theme --- src/plugins/remark-admonitions.ts | 102 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 src/plugins/remark-admonitions.ts (limited to 'src/plugins/remark-admonitions.ts') 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(["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: +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; + }); +}; -- cgit v1.2.3