feat: add admonitions

This commit is contained in:
LukaDev 2024-11-30 16:25:02 +01:00
parent 81ecd02df2
commit b872ea7ce6
5 changed files with 186 additions and 5 deletions

Binary file not shown.

View file

@ -24,6 +24,9 @@
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.46.0",
"globals": "^15.11.0",
"mdast": "^3.0.0",
"mdast-util-directive": "^3.0.0",
"mdast-util-to-hast": "^13.2.0",
"mdsvex": "^0.12.3",
"prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.2.7",
@ -48,15 +51,19 @@
"hast-util-heading": "^3.0.0",
"hast-util-heading-rank": "^3.0.0",
"hast-util-to-text": "^4.0.2",
"hastscript": "^9.0.0",
"lucide-static": "^0.462.0",
"lucide-svelte": "^0.446.0",
"rehype-infer-description-meta": "^2.0.0",
"rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0",
"rehype-slug": "^6.0.0",
"rehype-stringify": "^10.0.1",
"remark-directive": "^3.0.0",
"remark-frontmatter": "^5.0.0",
"remark-gemoji": "^8.0.0",
"remark-gfm": "^4.0.0",
"remark-github-admonitions-to-directives": "^2.1.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.1",
"shiki": "^1.22.2",

View file

@ -90,3 +90,87 @@ body {
-ms-overflow-style: none;
scrollbar-width: none;
}
.admonition {
@apply my-4 rounded-sm border-l-4 px-4 py-3 prose-p:my-1;
border-color: var(--admonition-border);
background-color: var(--admonition-bg);
}
.admonition * {
color: theme(colors.light);
}
.admonition-title {
@apply flex items-center space-x-2 text-lg font-semibold;
}
.admonition-title * {
color: var(--admonition-text);
}
.admonition-icon {
@apply inline-block size-6 bg-current;
mask-image: var(--admonition-icon);
}
@media (prefers-color-scheme: dark) {
.admonition {
border-color: var(--admonition-border-dark);
background-color: var(--admonition-bg-dark);
}
.admonition-title * {
color: var(--admonition-text-dark);
}
}
.admonition-note {
--admonition-bg: theme(colors.blue.600 / 0.1);
--admonition-border: theme(colors.blue.600 / 0.4);
--admonition-text: theme(colors.blue.950);
--admonition-bg-dark: theme(colors.blue.500 / 0.1);
--admonition-border-dark: theme(colors.blue.500 / 0.6);
--admonition-text-dark: theme(colors.blue.100);
--admonition-icon: url(lucide-static/icons/info.svg);
}
.admonition-tip {
--admonition-bg: theme(colors.green.600 / 0.1);
--admonition-border: theme(colors.green.600 / 0.4);
--admonition-text: theme(colors.green.950);
--admonition-bg-dark: theme(colors.green.500 / 0.1);
--admonition-border-dark: theme(colors.green.500 / 0.6);
--admonition-text-dark: theme(colors.green.100);
--admonition-icon: url(lucide-static/icons/lightbulb.svg);
}
.admonition-info {
--admonition-bg: theme(colors.purple.600 / 0.1);
--admonition-border: theme(colors.purple.600 / 0.4);
--admonition-text: theme(colors.purple.950);
--admonition-bg-dark: theme(colors.purple.500 / 0.1);
--admonition-border-dark: theme(colors.purple.500 / 0.6);
--admonition-text-dark: theme(colors.purple.100);
--admonition-icon: url(lucide-static/icons/message-square-warning.svg);
}
.admonition-warning {
--admonition-bg: theme(colors.yellow.600 / 0.1);
--admonition-border: theme(colors.yellow.600 / 0.4);
--admonition-text: theme(colors.yellow.950);
--admonition-bg-dark: theme(colors.yellow.500 / 0.1);
--admonition-border-dark: theme(colors.yellow.500 / 0.6);
--admonition-text-dark: theme(colors.yellow.100);
--admonition-icon: url(lucide-static/icons/triangle-alert.svg);
}
.admonition-danger {
--admonition-bg: theme(colors.red.600 / 0.1);
--admonition-border: theme(colors.red.600 / 0.4);
--admonition-text: theme(colors.red.950);
--admonition-bg-dark: theme(colors.red.500 / 0.1);
--admonition-border-dark: theme(colors.red.500 / 0.6);
--admonition-text-dark: theme(colors.red.100);
--admonition-icon: url(lucide-static/icons/octagon-alert.svg);
}

View file

@ -1,3 +1,5 @@
/// <reference types="mdast-util-directive" />
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {

View file

@ -3,14 +3,19 @@ import type { Nodes } from "hast"
import { heading } from "hast-util-heading"
import { headingRank } from "hast-util-heading-rank"
import { toText } from "hast-util-to-text"
import { h, type Child } from "hastscript"
import type { ContainerDirective } from "mdast-util-directive"
import type { Handler } from "mdast-util-to-hast"
import rehypeInferDescriptionMeta from "rehype-infer-description-meta"
import rehypeRaw from "rehype-raw"
import rehypeSanitize from "rehype-sanitize"
import rehypeSanitize, { defaultSchema } from "rehype-sanitize"
import rehypeSlug from "rehype-slug"
import rehypeStringify from "rehype-stringify"
import remarkDirective from "remark-directive"
import remarkFrontmatter from "remark-frontmatter"
import remarkGemoji from "remark-gemoji"
import remarkGfm from "remark-gfm"
import remarkGithubAdmonitionsToDirectives from "remark-github-admonitions-to-directives"
import remarkParse from "remark-parse"
import remarkRehype from "remark-rehype"
import { createCssVariablesTheme, createHighlighter } from "shiki"
@ -23,15 +28,92 @@ const highlighter = createHighlighter({
langs: [],
})
const ADMONITION_TYPES = {
note: {
label: "Note",
},
tip: {
label: "Tip",
},
info: {
label: "Info",
},
warning: {
label: "Warning",
},
danger: {
label: "Danger",
},
}
const containerDirectiveHandler: Handler = (state, node: ContainerDirective) => {
const type = node.name as keyof typeof ADMONITION_TYPES
if (!type || !(type in ADMONITION_TYPES)) {
return
}
const typeInfo = ADMONITION_TYPES[type]
let label: Child = typeInfo.label
const firstChild = node.children[0]
if (firstChild?.type === "paragraph" && firstChild.data?.directiveLabel) {
node.children.shift()
label = state.all(firstChild)
}
return h(
"div",
{
class: `admonition admonition-${type}`,
},
[
h(
"p",
{
class: "admonition-title",
},
[
h("span", {
class: "admonition-icon",
}),
h(
"span",
{
class: "admonition-label",
},
label,
),
],
),
state.all(node),
],
)
}
const sanitizeSchema: typeof defaultSchema = {
...defaultSchema,
attributes: {
...defaultSchema.attributes,
"*": [...(defaultSchema.attributes?.["*"] ?? []), ["className", "admonition", /^admonition-/]],
},
}
const remarkRehypeHandlers = {
containerDirective: containerDirectiveHandler,
}
export const markdown = (async () => {
return unified()
.use(remarkParse)
.use(remarkFrontmatter)
.use(remarkGfm)
.use(remarkGemoji)
.use(remarkRehype, { allowDangerousHtml: true })
.use(remarkGithubAdmonitionsToDirectives)
.use(remarkDirective)
.use(remarkRehype, { allowDangerousHtml: true, handlers: remarkRehypeHandlers })
.use(rehypeRaw)
.use(rehypeSanitize)
.use(rehypeSanitize, sanitizeSchema)
.use(rehypeShikiFromHighlighter, await highlighter, {
lazy: true,
theme: createCssVariablesTheme({
@ -58,7 +140,13 @@ export const docsMarkdown = (async () => {
.use(remarkFrontmatter)
.use(remarkGfm)
.use(remarkGemoji)
.use(remarkRehype, { allowDangerousHtml: true, clobberPrefix: "" })
.use(remarkGithubAdmonitionsToDirectives)
.use(remarkDirective)
.use(remarkRehype, {
allowDangerousHtml: true,
clobberPrefix: "",
handlers: remarkRehypeHandlers,
})
.use(rehypeSlug)
.use(() => (node, file) => {
const toc: TocItem[] = []
@ -97,7 +185,7 @@ export const docsMarkdown = (async () => {
}) as Node
})
.use(rehypeRaw)
.use(rehypeSanitize)
.use(rehypeSanitize, sanitizeSchema)
.use(rehypeShikiFromHighlighter, await highlighter, {
lazy: true,
theme: createCssVariablesTheme({