Add GitHub-style callout support
Implement proper GitHub-style callouts with beautiful styling: Features: - Custom rehype plugin to transform > [!NOTE] syntax - Support for 5 callout types: * NOTE (blue, 📝) * TIP (green, 💡) * IMPORTANT (purple, ❗) * WARNING (orange, ⚠️) * CAUTION (red, 🚨) - Gradient backgrounds with accent colors - Full dark mode support - Converts existing emoji callouts to proper format Files: - lib/rehype-callouts.ts: Custom plugin for parsing - contentlayer.config.ts: Add plugin to pipeline - styles/globals.css: Beautiful styling for all types - content/: Convert 2 emoji callouts to [!TIP] format 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2
content
2
content
Submodule content updated: c105c2a142...a859f93327
@@ -4,6 +4,7 @@ import rehypeSlug from 'rehype-slug';
|
||||
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import rehypePrettyCode from 'rehype-pretty-code';
|
||||
import { rehypeCallouts } from './lib/rehype-callouts';
|
||||
|
||||
export const Post = defineDocumentType(() => ({
|
||||
name: 'Post',
|
||||
@@ -97,6 +98,7 @@ export default makeSource({
|
||||
keepBackground: false,
|
||||
},
|
||||
],
|
||||
rehypeCallouts,
|
||||
rehypeSlug,
|
||||
[rehypeAutolinkHeadings, { behavior: 'wrap' }],
|
||||
/**
|
||||
|
||||
81
lib/rehype-callouts.ts
Normal file
81
lib/rehype-callouts.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { visit } from 'unist-util-visit';
|
||||
|
||||
/**
|
||||
* Rehype plugin to transform GitHub-style blockquote alerts
|
||||
* Transforms: > [!NOTE] into styled callout boxes
|
||||
*/
|
||||
export function rehypeCallouts() {
|
||||
return (tree: any) => {
|
||||
visit(tree, 'element', (node, index, parent) => {
|
||||
// Only process blockquotes
|
||||
if (node.tagName !== 'blockquote') return;
|
||||
|
||||
// Check if first child is a paragraph
|
||||
if (!node.children || node.children.length === 0) return;
|
||||
const firstChild = node.children[0];
|
||||
if (firstChild.tagName !== 'p') return;
|
||||
|
||||
// Check if paragraph starts with [!TYPE]
|
||||
if (!firstChild.children || firstChild.children.length === 0) return;
|
||||
const firstText = firstChild.children[0];
|
||||
if (firstText.type !== 'text') return;
|
||||
|
||||
const match = firstText.value.match(/^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*/i);
|
||||
if (!match) return;
|
||||
|
||||
const type = match[0].replace(/^\[!|\]\s*/g, '').toLowerCase();
|
||||
|
||||
// Remove the [!TYPE] marker from the text
|
||||
firstText.value = firstText.value.replace(match[0], '');
|
||||
|
||||
// If the text node is now empty, remove it
|
||||
if (!firstText.value.trim()) {
|
||||
firstChild.children.shift();
|
||||
}
|
||||
|
||||
// Add callout data attributes and classes
|
||||
node.properties = node.properties || {};
|
||||
node.properties.className = ['callout', `callout-${type}`];
|
||||
node.properties['data-callout'] = type;
|
||||
|
||||
// Add icon element at the beginning
|
||||
const iconMap: Record<string, string> = {
|
||||
note: '📝',
|
||||
tip: '💡',
|
||||
important: '❗',
|
||||
warning: '⚠️',
|
||||
caution: '🚨',
|
||||
};
|
||||
|
||||
const icon = {
|
||||
type: 'element',
|
||||
tagName: 'div',
|
||||
properties: { className: ['callout-icon'] },
|
||||
children: [{ type: 'text', value: iconMap[type] || '📝' }],
|
||||
};
|
||||
|
||||
const title = {
|
||||
type: 'element',
|
||||
tagName: 'div',
|
||||
properties: { className: ['callout-title'] },
|
||||
children: [{ type: 'text', value: type.toUpperCase() }],
|
||||
};
|
||||
|
||||
const header = {
|
||||
type: 'element',
|
||||
tagName: 'div',
|
||||
properties: { className: ['callout-header'] },
|
||||
children: [icon, title],
|
||||
};
|
||||
|
||||
const content = {
|
||||
type: 'element',
|
||||
tagName: 'div',
|
||||
properties: { className: ['callout-content'] },
|
||||
children: node.children,
|
||||
};
|
||||
|
||||
node.children = [header, content];
|
||||
});
|
||||
};
|
||||
}
|
||||
57
package-lock.json
generated
57
package-lock.json
generated
@@ -28,6 +28,7 @@
|
||||
"rehype-autolink-headings": "^7.1.0",
|
||||
"rehype-pretty-code": "^0.14.1",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"remark-directive": "^4.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"shiki": "^3.15.0",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
@@ -7355,6 +7356,27 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-directive": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz",
|
||||
"integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^4.0.0",
|
||||
"@types/unist": "^3.0.0",
|
||||
"ccount": "^2.0.0",
|
||||
"devlop": "^1.0.0",
|
||||
"mdast-util-from-markdown": "^2.0.0",
|
||||
"mdast-util-to-markdown": "^2.0.0",
|
||||
"parse-entities": "^4.0.0",
|
||||
"stringify-entities": "^4.0.0",
|
||||
"unist-util-visit-parents": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-find-and-replace": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz",
|
||||
@@ -7804,6 +7826,25 @@
|
||||
"micromark-util-types": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-extension-directive": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz",
|
||||
"integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"devlop": "^1.0.0",
|
||||
"micromark-factory-space": "^2.0.0",
|
||||
"micromark-factory-whitespace": "^2.0.0",
|
||||
"micromark-util-character": "^2.0.0",
|
||||
"micromark-util-symbol": "^2.0.0",
|
||||
"micromark-util-types": "^2.0.0",
|
||||
"parse-entities": "^4.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-extension-frontmatter": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz",
|
||||
@@ -9712,6 +9753,22 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/remark-directive": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-4.0.0.tgz",
|
||||
"integrity": "sha512-7sxn4RfF1o3izevPV1DheyGDD6X4c9hrGpfdUpm7uC++dqrnJxIZVkk7CoKqcLm0VUMAuOol7Mno3m6g8cfMuA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^4.0.0",
|
||||
"mdast-util-directive": "^3.0.0",
|
||||
"micromark-extension-directive": "^4.0.0",
|
||||
"unified": "^11.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/remark-frontmatter": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz",
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"rehype-autolink-headings": "^7.1.0",
|
||||
"rehype-pretty-code": "^0.14.1",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"remark-directive": "^4.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"shiki": "^3.15.0",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
|
||||
@@ -426,3 +426,111 @@ body {
|
||||
.prose [data-rehype-pretty-code-title] + pre {
|
||||
@apply mt-0 rounded-t-none;
|
||||
}
|
||||
|
||||
/* GitHub-style Callouts/Alerts */
|
||||
.prose .callout {
|
||||
@apply my-6 rounded-lg border-l-4 p-4 shadow-sm;
|
||||
background: linear-gradient(135deg, var(--callout-bg-start), var(--callout-bg-end));
|
||||
}
|
||||
|
||||
.prose .callout-header {
|
||||
@apply mb-3 flex items-center gap-2;
|
||||
}
|
||||
|
||||
.prose .callout-icon {
|
||||
@apply text-2xl;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.prose .callout-title {
|
||||
@apply text-sm font-bold uppercase tracking-wider;
|
||||
color: var(--callout-title-color);
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.prose .callout-content {
|
||||
@apply text-sm leading-relaxed;
|
||||
}
|
||||
|
||||
.prose .callout-content > *:first-child {
|
||||
@apply mt-0;
|
||||
}
|
||||
|
||||
.prose .callout-content > *:last-child {
|
||||
@apply mb-0;
|
||||
}
|
||||
|
||||
/* NOTE - Blue */
|
||||
.prose .callout-note {
|
||||
--callout-bg-start: rgba(59, 130, 246, 0.08);
|
||||
--callout-bg-end: rgba(59, 130, 246, 0.04);
|
||||
--callout-title-color: #2563eb;
|
||||
@apply border-blue-500;
|
||||
}
|
||||
|
||||
.dark .prose .callout-note {
|
||||
--callout-bg-start: rgba(96, 165, 250, 0.12);
|
||||
--callout-bg-end: rgba(96, 165, 250, 0.06);
|
||||
--callout-title-color: #93c5fd;
|
||||
@apply border-blue-400;
|
||||
}
|
||||
|
||||
/* TIP - Green */
|
||||
.prose .callout-tip {
|
||||
--callout-bg-start: rgba(34, 197, 94, 0.08);
|
||||
--callout-bg-end: rgba(34, 197, 94, 0.04);
|
||||
--callout-title-color: #16a34a;
|
||||
@apply border-green-500;
|
||||
}
|
||||
|
||||
.dark .prose .callout-tip {
|
||||
--callout-bg-start: rgba(74, 222, 128, 0.12);
|
||||
--callout-bg-end: rgba(74, 222, 128, 0.06);
|
||||
--callout-title-color: #86efac;
|
||||
@apply border-green-400;
|
||||
}
|
||||
|
||||
/* IMPORTANT - Purple */
|
||||
.prose .callout-important {
|
||||
--callout-bg-start: rgba(168, 85, 247, 0.08);
|
||||
--callout-bg-end: rgba(168, 85, 247, 0.04);
|
||||
--callout-title-color: #9333ea;
|
||||
@apply border-purple-500;
|
||||
}
|
||||
|
||||
.dark .prose .callout-important {
|
||||
--callout-bg-start: rgba(192, 132, 252, 0.12);
|
||||
--callout-bg-end: rgba(192, 132, 252, 0.06);
|
||||
--callout-title-color: #c084fc;
|
||||
@apply border-purple-400;
|
||||
}
|
||||
|
||||
/* WARNING - Orange/Yellow */
|
||||
.prose .callout-warning {
|
||||
--callout-bg-start: rgba(251, 191, 36, 0.08);
|
||||
--callout-bg-end: rgba(251, 191, 36, 0.04);
|
||||
--callout-title-color: #d97706;
|
||||
@apply border-yellow-500;
|
||||
}
|
||||
|
||||
.dark .prose .callout-warning {
|
||||
--callout-bg-start: rgba(253, 224, 71, 0.12);
|
||||
--callout-bg-end: rgba(253, 224, 71, 0.06);
|
||||
--callout-title-color: #fde047;
|
||||
@apply border-yellow-400;
|
||||
}
|
||||
|
||||
/* CAUTION - Red */
|
||||
.prose .callout-caution {
|
||||
--callout-bg-start: rgba(239, 68, 68, 0.08);
|
||||
--callout-bg-end: rgba(239, 68, 68, 0.04);
|
||||
--callout-title-color: #dc2626;
|
||||
@apply border-red-500;
|
||||
}
|
||||
|
||||
.dark .prose .callout-caution {
|
||||
--callout-bg-start: rgba(248, 113, 113, 0.12);
|
||||
--callout-bg-end: rgba(248, 113, 113, 0.06);
|
||||
--callout-title-color: #fca5a5;
|
||||
@apply border-red-400;
|
||||
}
|
||||
Reference in New Issue
Block a user