Initial commit

This commit is contained in:
KazooTTT
2025-02-05 14:07:58 +08:00
committed by GitHub
commit ea645368e9
85 changed files with 11210 additions and 0 deletions

View File

@ -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;
});
};

View File

@ -0,0 +1,11 @@
import { toString as mdastToString } from "mdast-util-to-string";
import getReadingTime from "reading-time";
export function remarkReadingTime() {
// @ts-expect-error:next-line
return (tree, { data }) => {
const textOnPage = mdastToString(tree);
const readingTime = getReadingTime(textOnPage);
data.astro.frontmatter.readingTime = readingTime.text;
};
}