Add basic support for callouts

This commit is contained in:
Endeavorance 2025-06-06 11:50:03 -04:00
parent b280b29df6
commit 5687f3b02b
5 changed files with 265 additions and 4 deletions

View file

@ -6,7 +6,7 @@
"dependencies": { "dependencies": {
"@biomejs/biome": "^1.9.4", "@biomejs/biome": "^1.9.4",
"@endeavorance/emdy": "^1.0.0", "@endeavorance/emdy": "^1.0.0",
"marked": "^15.0.12", "rehype-callouts": "^2.1.0",
"rehype-raw": "^7.0.0", "rehype-raw": "^7.0.0",
"rehype-slug": "^6.0.0", "rehype-slug": "^6.0.0",
"rehype-stringify": "^10.0.1", "rehype-stringify": "^10.0.1",
@ -90,10 +90,14 @@
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
"hast-util-from-html": ["hast-util-from-html@2.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.1.0", "hast-util-from-parse5": "^8.0.0", "parse5": "^7.0.0", "vfile": "^6.0.0", "vfile-message": "^4.0.0" } }, "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw=="],
"hast-util-from-parse5": ["hast-util-from-parse5@8.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "hastscript": "^9.0.0", "property-information": "^7.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" } }, "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg=="], "hast-util-from-parse5": ["hast-util-from-parse5@8.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "hastscript": "^9.0.0", "property-information": "^7.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" } }, "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg=="],
"hast-util-heading-rank": ["hast-util-heading-rank@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA=="], "hast-util-heading-rank": ["hast-util-heading-rank@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA=="],
"hast-util-is-element": ["hast-util-is-element@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g=="],
"hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="], "hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="],
"hast-util-raw": ["hast-util-raw@9.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-from-parse5": "^8.0.0", "hast-util-to-parse5": "^8.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "parse5": "^7.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="], "hast-util-raw": ["hast-util-raw@9.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-from-parse5": "^8.0.0", "hast-util-to-parse5": "^8.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "parse5": "^7.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="],
@ -116,8 +120,6 @@
"markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
"marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="],
"mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="],
"mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="],
@ -204,6 +206,8 @@
"property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
"rehype-callouts": ["rehype-callouts@2.1.0", "", { "dependencies": { "@types/hast": "^3.0.4", "hast-util-from-html": "^2.0.3", "hast-util-is-element": "^3.0.0", "hastscript": "^9.0.1", "unist-util-visit": "^5.0.0" } }, "sha512-Als/wlYpXg2Rs0p/yofrkSH6IQzn1xh6cvBC5BHy5JVT3lGlzUvVT8wOyVqCEgf8Eun6cQ3f47rLLoaiyLkP0w=="],
"rehype-raw": ["rehype-raw@7.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="], "rehype-raw": ["rehype-raw@7.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="],
"rehype-slug": ["rehype-slug@6.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "github-slugger": "^2.0.0", "hast-util-heading-rank": "^3.0.0", "hast-util-to-string": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A=="], "rehype-slug": ["rehype-slug@6.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "github-slugger": "^2.0.0", "hast-util-heading-rank": "^3.0.0", "hast-util-to-string": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A=="],

View file

@ -8,3 +8,12 @@ It has a list!
- See? - See?
- Fancy! - Fancy!
How about a callout?
> [!info] This is an info callout
> Here's some more info
> etc
> [!warning]- This is collapsible
> And how!

View file

@ -3,7 +3,7 @@
"module": "index.ts", "module": "index.ts",
"type": "module", "type": "module",
"private": true, "private": true,
"version": "0.1.0", "version": "0.2.0",
"devDependencies": { "devDependencies": {
"@types/bun": "latest" "@types/bun": "latest"
}, },
@ -13,6 +13,7 @@
"dependencies": { "dependencies": {
"@biomejs/biome": "^1.9.4", "@biomejs/biome": "^1.9.4",
"@endeavorance/emdy": "^1.0.0", "@endeavorance/emdy": "^1.0.0",
"rehype-callouts": "^2.1.0",
"rehype-raw": "^7.0.0", "rehype-raw": "^7.0.0",
"rehype-slug": "^6.0.0", "rehype-slug": "^6.0.0",
"rehype-stringify": "^10.0.1", "rehype-stringify": "^10.0.1",

View file

@ -113,3 +113,248 @@ th {
background: var(--color-bg); background: var(--color-bg);
font-weight: 500; font-weight: 500;
} }
/* Callout styles, from https://raw.githubusercontent.com/lin-stephanie/rehype-callouts/refs/heads/main/src/themes/obsidian/index.css */
[data-callout="note"] {
--rc-color-light: var(--callout-note-color-light, rgb(8, 109, 221));
--rc-color-dark: var(--callout-note-color-dark, rgb(2, 122, 255));
}
[data-callout="abstract"] {
--rc-color-light: var(--callout-abstract-color-light, rgb(0, 191, 188));
--rc-color-dark: var(--callout-abstract-color-dark, rgb(83, 223, 221));
}
[data-callout="summary"] {
--rc-color-light: var(--callout-summary-color-light, rgb(0, 191, 188));
--rc-color-dark: var(--callout-summary-color-dark, rgb(83, 223, 221));
}
[data-callout="tldr"] {
--rc-color-light: var(--callout-tldr-color-light, rgb(0, 191, 188));
--rc-color-dark: var(--callout-tldr-color-dark, rgb(83, 223, 221));
}
[data-callout="info"] {
--rc-color-light: var(--callout-info-color-light, rgb(8, 109, 221));
--rc-color-dark: var(--callout-info-color-dark, rgb(2, 122, 255));
}
[data-callout="todo"] {
--rc-color-light: var(--callout-todo-color-light, rgb(8, 109, 221));
--rc-color-dark: var(--callout-todo-color-dark, rgb(2, 122, 255));
}
[data-callout="tip"] {
--rc-color-light: var(--callout-tip-color-light, rgb(0, 191, 188));
--rc-color-dark: var(--callout-tip-color-dark, rgb(83, 223, 221));
}
[data-callout="hint"] {
--rc-color-light: var(--callout-hint-color-light, rgb(0, 191, 188));
--rc-color-dark: var(--callout-hint-color-dark, rgb(83, 223, 221));
}
[data-callout="important"] {
--rc-color-light: var(--callout-important-color-light, rgb(0, 191, 188));
--rc-color-dark: var(--callout-important-color-dark, rgb(83, 223, 221));
}
[data-callout="success"] {
--rc-color-light: var(--callout-success-color-light, rgb(8, 185, 78));
--rc-color-dark: var(--callout-success-color-dark, rgb(68, 207, 110));
}
[data-callout="check"] {
--rc-color-light: var(--callout-check-color-light, rgb(8, 185, 78));
--rc-color-dark: var(--callout-check-color-dark, rgb(68, 207, 110));
}
[data-callout="done"] {
--rc-color-light: var(--callout-done-color-light, rgb(8, 185, 78));
--rc-color-dark: var(--callout-done-color-dark, rgb(68, 207, 110));
}
[data-callout="question"] {
--rc-color-light: var(--callout-question-color-light, rgb(236, 117, 0));
--rc-color-dark: var(--callout-question-color-dark, rgb(233, 151, 63));
}
[data-callout="help"] {
--rc-color-light: var(--callout-help-color-light, rgb(236, 117, 0));
--rc-color-dark: var(--callout-help-color-dark, rgb(233, 151, 63));
}
[data-callout="faq"] {
--rc-color-light: var(--callout-faq-color-light, rgb(236, 117, 0));
--rc-color-dark: var(--callout-faq-color-dark, rgb(233, 151, 63));
}
[data-callout="warning"] {
--rc-color-light: var(--callout-warning-color-light, rgb(236, 117, 0));
--rc-color-dark: var(--callout-warning-color-dark, rgb(233, 151, 63));
}
[data-callout="attention"] {
--rc-color-light: var(--callout-attention-color-light, rgb(236, 117, 0));
--rc-color-dark: var(--callout-attention-color-dark, rgb(233, 151, 63));
}
[data-callout="caution"] {
--rc-color-light: var(--callout-caution-color-light, rgb(236, 117, 0));
--rc-color-dark: var(--callout-caution-color-dark, rgb(233, 151, 63));
}
[data-callout="failure"] {
--rc-color-light: var(--callout-failure-color-light, rgb(233, 49, 71));
--rc-color-dark: var(--callout-failure-color-dark, rgb(251, 70, 76));
}
[data-callout="missing"] {
--rc-color-light: var(--callout-missing-color-light, rgb(233, 49, 71));
--rc-color-dark: var(--callout-missing-color-dark, rgb(251, 70, 76));
}
[data-callout="fail"] {
--rc-color-light: var(--callout-fail-color-light, rgb(233, 49, 71));
--rc-color-dark: var(--callout-fail-color-dark, rgb(251, 70, 76));
}
[data-callout="danger"] {
--rc-color-light: var(--callout-danger-color-light, rgb(233, 49, 71));
--rc-color-dark: var(--callout-danger-color-dark, rgb(251, 70, 76));
}
[data-callout="error"] {
--rc-color-light: var(--callout-error-color-light, rgb(233, 49, 71));
--rc-color-dark: var(--callout-error-color-dark, rgb(251, 70, 76));
}
[data-callout="bug"] {
--rc-color-light: var(--callout-bug-color-light, rgb(233, 49, 71));
--rc-color-dark: var(--callout-bug-color-dark, rgb(251, 70, 76));
}
[data-callout="example"] {
--rc-color-light: var(--callout-example-color-light, rgb(120, 82, 238));
--rc-color-dark: var(--callout-example-color-dark, rgb(168, 130, 255));
}
[data-callout="quote"] {
--rc-color-light: var(--callout-quote-color-light, rgb(158, 158, 158));
--rc-color-dark: var(--callout-quote-color-dark, rgb(158, 158, 158));
}
[data-callout="cite"] {
--rc-color-light: var(--callout-cite-color-light, rgb(158, 158, 158));
--rc-color-dark: var(--callout-cite-color-dark, rgb(158, 158, 158));
}
.callout {
--rc-color-default: #888;
overflow: hidden;
width: 100%;
padding: 12px 12px 12px 24px;
border-radius: 4px;
margin: 1em 0;
line-height: 1.3;
mix-blend-mode: darken;
background-color: rgb(
from var(--rc-color-light, var(--rc-color-default)) r g b /
0.1
);
}
.dark .callout {
mix-blend-mode: lighten;
background-color: rgb(
from var(--rc-color-dark, var(--rc-color-default)) r g b /
0.1
);
}
.callout-title {
display: flex;
align-items: flex-start;
gap: 4px;
color: var(--rc-color-light, var(--rc-color-default));
font-size: inherit;
}
.dark .callout-title {
color: var(--rc-color-dark, var(--rc-color-default));
}
.callout-title::-webkit-details-marker {
display: none;
}
.callout-title-icon {
display: flex;
flex: 0 0 auto;
align-items: center;
}
.callout-title-text {
color: inherit;
font-weight: 600;
}
.callout-content {
overflow-x: auto;
padding: 0;
background-color: transparent;
}
.callout[data-collapsible="true"] .callout-title {
cursor: pointer;
}
.callout[data-collapsible="true"] .callout-fold-icon {
display: flex;
align-items: center;
padding-inline-end: 8px;
}
.callout[data-collapsible="true"] > .callout-title .callout-fold-icon svg {
transform: rotate(-90deg);
transition: transform 100ms ease-in-out;
}
.callout[data-collapsible="true"][open]
> .callout-title
.callout-fold-icon
svg {
transform: none;
}
.callout-title-icon::after,
.callout-fold-icon::after {
content: "\200B";
}
.callout-title-icon svg,
.callout-fold-icon svg {
width: 18px;
height: 18px;
}
@media (prefers-color-scheme: dark) {
.callout {
mix-blend-mode: lighten;
background-color: rgb(
from var(--rc-color-dark, var(--rc-color-default)) r g b /
0.1
);
}
.callout-title {
color: var(--rc-color-dark, var(--rc-color-default));
}
}

View file

@ -5,6 +5,7 @@ import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype"; import remarkRehype from "remark-rehype";
import rehypeSlug from "rehype-slug"; import rehypeSlug from "rehype-slug";
import { unified } from "unified"; import { unified } from "unified";
import rehypeCallouts from "rehype-callouts";
export async function parseMarkdown(markdown: string): Promise<string> { export async function parseMarkdown(markdown: string): Promise<string> {
const file = await unified() const file = await unified()
@ -13,6 +14,7 @@ export async function parseMarkdown(markdown: string): Promise<string> {
.use(remarkRehype, { allowDangerousHtml: true }) .use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeRaw) .use(rehypeRaw)
.use(rehypeSlug) .use(rehypeSlug)
.use(rehypeCallouts)
.use(rehypeStringify) .use(rehypeStringify)
.process(markdown); .process(markdown);