Commit 66234ae
Changed files (5)
src
plugins
styles
src/plugins/remark-github-blockquote.ts
@@ -0,0 +1,153 @@
+import type { RemarkPlugin } from '@astrojs/markdown-remark';
+import type { BlockContent, Data, DefinitionContent } from 'mdast';
+import type { Node } from 'unist';
+import { visit } from 'unist-util-visit';
+
+type TextFilter = string | RegExp | ((title: string) => string);
+type ClassNames = string | string[];
+type ClassNameMap = ClassNames | ((title: string) => ClassNames);
+
+export interface Config {
+ titleFilter: TextFilter[];
+ titleTextMap: (title: string) => {
+ displayTitle: (string | Node)[];
+ checkedTitle: string;
+ };
+ dataMaps: {
+ title: (data: Data) => Data;
+ block: (data: Data) => Data;
+ };
+ classNameMaps: {
+ title: ClassNameMap;
+ block: ClassNameMap;
+ };
+}
+
+const defaultConfig: Config = {
+ titleFilter: [
+ /\[!NOTE(:.*?)?\]/,
+ /\[!TIP(:.*?)?\]/,
+ /\[!IMPORTANT(:.*?)?\]/,
+ /\[!CAUTION(:.*?)?\]/,
+ /\[!WARNING(:.*?)?\]/,
+ ],
+ titleTextMap: (title) => {
+ const [type, text] = title.substring(2, title.length - 1).split(':');
+ let icon: string;
+ switch (type) {
+ case 'NOTE':
+ icon = 'material-symbols--info-outline-rounded';
+ break;
+ case 'TIP':
+ icon = 'ic--outline-tips-and-updates';
+ break;
+ case 'IMPORTANT':
+ icon = 'material-symbols--chat-info-outline-rounded';
+ break;
+ case 'CAUTION':
+ icon = 'mdi--alert-octagon-outline';
+ break;
+ case 'WARNING':
+ icon = 'material-symbols--warning-rounded';
+ break;
+ default:
+ icon = 'material-symbols--info-outline-rounded';
+ break;
+ }
+ return {
+ displayTitle: [
+ {
+ type: 'element',
+ data: { hName: 'span', hProperties: { className: `icon-[${icon}]` } },
+ } as Node,
+ text || type,
+ ],
+ checkedTitle: type,
+ };
+ },
+ dataMaps: {
+ title: (data) => data,
+ block: (data) => data,
+ },
+ classNameMaps: {
+ title: 'admonition-title',
+ block: (title) => `admonition admonition-${title.toLowerCase()}`,
+ },
+};
+
+function nameFilter(fliters: TextFilter[]): (title: string) => boolean {
+ return (title) => {
+ for (const filter of fliters) {
+ if (typeof filter == 'string') {
+ if (title.startsWith(filter)) return true;
+ } else if (filter instanceof RegExp) {
+ if (filter.test(title)) return true;
+ } else if (typeof filter == 'function') {
+ if (filter(title)) return true;
+ }
+ }
+ return false;
+ };
+}
+
+function classNameMap(gen: ClassNameMap): (title: string) => string {
+ return (title) => {
+ const classNames = typeof gen == 'function' ? gen(title) : gen;
+ return typeof classNames == 'object' ? classNames.join(' ') : classNames;
+ };
+}
+
+export const remarkGithubBlockquote: RemarkPlugin<Partial<Config>[]> = function (...params) {
+ const providedConfig = params.reduce((a, b) => ({ ...a, ...b }), {});
+ const config = { ...defaultConfig, ...providedConfig };
+ return (tree) => {
+ visit(tree, (node) => {
+ if (node.type != 'blockquote') return;
+ const blockquote = node;
+ if (blockquote.children[0]?.type != 'paragraph') return;
+ const paragraph = blockquote.children[0];
+ if (paragraph.children[0]?.type != 'text') return;
+ const text = paragraph.children[0];
+ let title;
+ const titleEnd = text.value.indexOf('\n');
+ if (titleEnd < 0) {
+ if (paragraph.children.length > 1) {
+ if (paragraph.children.at(1)?.type == 'break') {
+ paragraph.children.splice(1, 1);
+ } else {
+ return;
+ }
+ }
+ title = text.value;
+ if (!nameFilter(config.titleFilter)(title)) return;
+ paragraph.children.shift();
+ } else {
+ const textBody = text.value.substring(titleEnd + 1);
+ title = text.value.substring(0, titleEnd);
+ const m = /[ \t\v\f\r]+$/.exec(title);
+ if (m) {
+ title = title.substring(0, title.length - m[0].length);
+ }
+ if (!nameFilter(config.titleFilter)(title)) return;
+ text.value = textBody;
+ }
+ const { displayTitle, checkedTitle } = config.titleTextMap(title);
+ const paragraphTitleTexts = displayTitle.map((text) =>
+ typeof text == 'string' ? { type: 'text', value: text } : text
+ );
+ const paragraphTitle = {
+ type: 'paragraph',
+ children: [...paragraphTitleTexts],
+ data: config.dataMaps.title({
+ hProperties: { className: classNameMap(config.classNameMaps.title)(checkedTitle) },
+ }),
+ } as BlockContent | DefinitionContent;
+ blockquote.children.unshift(paragraphTitle);
+ blockquote.data = config.dataMaps.block({
+ ...blockquote.data,
+ hProperties: { className: classNameMap(config.classNameMaps.block)(checkedTitle) },
+ hName: 'div',
+ });
+ });
+ };
+};
src/styles/global.css
@@ -6,6 +6,8 @@
dark --prefersdark;
}
+@plugin "@iconify/tailwind4";
+
@plugin 'daisyui/theme' {
name: 'light';
src/styles/markdown.css
@@ -240,7 +240,7 @@ article {
@apply my-4 rounded-sm border-l-4;
.admonition-title {
- font-weight: bold;
+ @apply inline-flex items-center gap-1 text-lg font-bold;
}
&.admonition-note {
astro.config.mjs
@@ -2,6 +2,7 @@
import { CDN } from './src/constants/cdn.mjs';
import { rehypeWrapTables } from './src/plugins/rehype-wrap-tables.ts';
import { remarkExcerpt } from './src/plugins/remark-excerpt.ts';
+import { remarkGithubBlockquote } from './src/plugins/remark-github-blockquote.ts';
// import { remarkHeadingShift } from './src/plugins/remark-heading-shift.ts';
import { remarkImageProcess } from './src/plugins/remark-image-process.ts';
import { remarkReadingTime } from './src/plugins/remark-reading-time.ts';
@@ -14,7 +15,6 @@ import pagefind from 'astro-pagefind';
import { defineConfig } from 'astro/config';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
import rehypeMathJaxCHtml from 'rehype-mathjax/chtml';
-import remarkGithubBlockQuote from 'remark-github-beta-blockquote-admonitions';
import remarkMath from 'remark-math';
// https://astro.build/config
@@ -43,17 +43,7 @@ export default defineConfig({
remarkReadingTime,
remarkExcerpt,
remarkImageProcess,
- // @ts-expect-error - types are not up to date
- [
- remarkGithubBlockQuote,
- {
- classNameMaps: {
- block: (/** @type {string} */ title) =>
- `admonition admonition-${title.toLowerCase()}`,
- title: 'admonition-title',
- },
- },
- ],
+ remarkGithubBlockquote,
],
rehypePlugins: [
rehypeHeadingIds,
package.json
@@ -17,6 +17,7 @@
"@astrojs/mdx": "^4.0.8",
"@astrojs/rss": "^4.0.11",
"@astrojs/sitemap": "^3.2.1",
+ "@iconify-json/ic": "^1.2.2",
"@iconify-json/material-symbols": "^1.2.14",
"@iconify-json/mdi": "^1.2.3",
"@tailwindcss/vite": "^4.0.6",
@@ -48,11 +49,13 @@
"@astrojs/check": "^0.9.4",
"@astrojs/ts-plugin": "^1.10.4",
"@eslint/js": "^9.20.0",
+ "@iconify/tailwind4": "^1.0.3",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/hast": "^3.0.4",
"@types/markdown-it": "^14.1.2",
"@types/mdast": "^4.0.4",
"@types/sanitize-html": "^2.13.0",
+ "@types/unist": "^3.0.3",
"@typescript-eslint/parser": "^8.24.0",
"astro-eslint-parser": "^1.2.1",
"eslint": "^9.20.0",