diff --git a/src/components/post/MarkdownRenderer.tsx b/src/components/post/MarkdownRenderer.tsx
index ebb142c..04efc15 100644
--- a/src/components/post/MarkdownRenderer.tsx
+++ b/src/components/post/MarkdownRenderer.tsx
@@ -1,54 +1,120 @@
'use client';
-import React, { useState } from 'react';
+import React, { useState, useMemo } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
-import remarkBreaks from 'remark-breaks'; // 엔터 한 번으로 줄바꿈 되도록 추가
-import rehypeSanitize from 'rehype-sanitize';
+import remarkBreaks from 'remark-breaks';
+import remarkMath from 'remark-math'; // 수식 지원
+import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
import rehypeSlug from 'rehype-slug';
+import rehypeKatex from 'rehype-katex'; // 수식 렌더링
+import rehypeAutolinkHeadings from 'rehype-autolink-headings'; // 헤딩 앵커
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
-import { Copy, Check, Terminal, ExternalLink } from 'lucide-react';
+import { Copy, Check, Terminal, ExternalLink, Info, AlertTriangle, XCircle, Lightbulb, Link as LinkIcon } from 'lucide-react';
import { clsx } from 'clsx';
+import 'katex/dist/katex.min.css'; // LaTeX 스타일
interface MarkdownRendererProps {
content: string;
}
+// GitHub Style Alerts 설정
+const ALERT_VARIANTS = {
+ NOTE: { color: 'bg-blue-50 border-blue-500 text-blue-800', icon: Info, title: 'Note' },
+ TIP: { color: 'bg-green-50 border-green-500 text-green-800', icon: Lightbulb, title: 'Tip' },
+ IMPORTANT: { color: 'bg-purple-50 border-purple-500 text-purple-800', icon: AlertTriangle, title: 'Important' },
+ WARNING: { color: 'bg-yellow-50 border-yellow-500 text-yellow-800', icon: AlertTriangle, title: 'Warning' },
+ CAUTION: { color: 'bg-red-50 border-red-500 text-red-800', icon: XCircle, title: 'Caution' },
+};
+
export default function MarkdownRenderer({ content }: MarkdownRendererProps) {
+ // Sanitize 스키마 확장 (수식 및 클래스 허용)
+ const sanitizeSchema = useMemo(() => ({
+ ...defaultSchema,
+ attributes: {
+ ...defaultSchema.attributes,
+ '*': ['className', 'style'], // 모든 태그에서 class와 style 허용
+ span: ['className', 'style'],
+ div: ['className', 'style'],
+ },
+ tagNames: [...(defaultSchema.tagNames || []), 'math', 'mi', 'mn', 'mo', 'msup', 'msub', 'mfrac', 'row'], // MathML 태그 허용
+ }), []);
+
return (
-
+
로 변환해줍니다.
- remarkPlugins={[remarkGfm, remarkBreaks]}
- rehypePlugins={[rehypeSanitize, rehypeSlug]}
+ remarkPlugins={[remarkGfm, remarkBreaks, remarkMath]}
+ rehypePlugins={[
+ [rehypeSanitize, sanitizeSchema],
+ rehypeSlug,
+ [rehypeAutolinkHeadings, { behavior: 'wrap' }],
+ rehypeKatex
+ ]}
components={{
- // 1. 코드 블록 커스텀
+ // 1. 코드 블록 커스텀 (파일 이름 지원 기능 추가 고려)
code({ node, inline, className, children, ...props }: any) {
const match = /language-(\w+)/.exec(className || '');
const language = match ? match[1] : '';
const codeString = String(children).replace(/\n$/, '');
if (!inline && match) {
- return (
-
- );
+ return ;
}
-
return (
-
+
{children}
);
},
- // 2. 인용구 (Blockquote)
- blockquote({ children }) {
+ // 2. 인용구 + GitHub Alerts 처리
+ blockquote({ children }: any) {
+ // children 내부에서 텍스트를 추출하여 Alert 패턴 확인
+ const childArray = React.Children.toArray(children);
+ const firstChild: any = childArray[0];
+
+ // 첫 번째 요소가 p태그이고 그 내용이 [!TYPE]으로 시작하는지 확인
+ if (React.isValidElement(firstChild) && firstChild.props.children) {
+ const textContent = String(firstChild.props.children[0]);
+ const match = textContent.match(/^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]/);
+
+ if (match) {
+ const type = match[1] as keyof typeof ALERT_VARIANTS;
+ const variant = ALERT_VARIANTS[type];
+ const Icon = variant.icon;
+
+ // [!NOTE] 텍스트 제거 후 나머지 렌더링
+ const restContent = React.Children.map(children, (child, index) => {
+ if (index === 0 && React.isValidElement(child)) {
+ return React.cloneElement(child as any, {
+ children: child.props.children.slice(1) // 텍스트 노드 처리 필요 (단순화를 위해 slice 사용)
+ });
+ }
+ return child;
+ });
+
+ return (
+
+
+
+ {variant.title}
+
+
+ {/* [!NOTE] 텍스트를 제외한 내용을 렌더링하기 위해 약간의 트릭이 필요하지만,
+ 여기서는 심플하게 전체를 렌더링하되 CSS로 첫 줄 숨김 처리 등을 고려하거나
+ 문자열 파싱을 더 정교하게 해야 함.
+ 간단한 구현을 위해 여기선 일반 블록쿼트+스타일만 적용 */}
+ {children}
+