This commit is contained in:
30
package-lock.json
generated
30
package-lock.json
generated
@@ -29,6 +29,7 @@
|
|||||||
"react-syntax-highlighter": "^16.1.0",
|
"react-syntax-highlighter": "^16.1.0",
|
||||||
"rehype-sanitize": "^6.0.0",
|
"rehype-sanitize": "^6.0.0",
|
||||||
"rehype-slug": "^6.0.0",
|
"rehype-slug": "^6.0.0",
|
||||||
|
"remark-breaks": "^4.0.0",
|
||||||
"remark-gfm": "^4.0.1",
|
"remark-gfm": "^4.0.1",
|
||||||
"yarn": "^1.22.22",
|
"yarn": "^1.22.22",
|
||||||
"zustand": "^5.0.9"
|
"zustand": "^5.0.9"
|
||||||
@@ -6275,6 +6276,20 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mdast-util-newline-to-break": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mdast-util-newline-to-break/-/mdast-util-newline-to-break-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/mdast": "^4.0.0",
|
||||||
|
"mdast-util-find-and-replace": "^3.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mdast-util-phrasing": {
|
"node_modules/mdast-util-phrasing": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
|
||||||
@@ -7970,6 +7985,21 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/remark-breaks": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/mdast": "^4.0.0",
|
||||||
|
"mdast-util-newline-to-break": "^2.0.0",
|
||||||
|
"unified": "^11.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/remark-gfm": {
|
"node_modules/remark-gfm": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
"react-syntax-highlighter": "^16.1.0",
|
"react-syntax-highlighter": "^16.1.0",
|
||||||
"rehype-sanitize": "^6.0.0",
|
"rehype-sanitize": "^6.0.0",
|
||||||
"rehype-slug": "^6.0.0",
|
"rehype-slug": "^6.0.0",
|
||||||
|
"remark-breaks": "^4.0.0",
|
||||||
"remark-gfm": "^4.0.1",
|
"remark-gfm": "^4.0.1",
|
||||||
"yarn": "^1.22.22",
|
"yarn": "^1.22.22",
|
||||||
"zustand": "^5.0.9"
|
"zustand": "^5.0.9"
|
||||||
|
|||||||
@@ -3,12 +3,13 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
|
import remarkBreaks from 'remark-breaks'; // 엔터 한 번으로 줄바꿈 되도록 추가
|
||||||
import rehypeSanitize from 'rehype-sanitize';
|
import rehypeSanitize from 'rehype-sanitize';
|
||||||
|
import rehypeSlug from 'rehype-slug';
|
||||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||||
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||||
import { Copy, Check, Terminal, ExternalLink } from 'lucide-react';
|
import { Copy, Check, Terminal, ExternalLink } from 'lucide-react';
|
||||||
import { clsx } from 'clsx';
|
import { clsx } from 'clsx';
|
||||||
import rehypeSlug from 'rehype-slug';
|
|
||||||
|
|
||||||
interface MarkdownRendererProps {
|
interface MarkdownRendererProps {
|
||||||
content: string;
|
content: string;
|
||||||
@@ -18,7 +19,8 @@ export default function MarkdownRenderer({ content }: MarkdownRendererProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="markdown-content">
|
<div className="markdown-content">
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
remarkPlugins={[remarkGfm]}
|
// remarkBreaks: 일반 텍스트에서 엔터 한 번을 <br/>로 변환해줍니다.
|
||||||
|
remarkPlugins={[remarkGfm, remarkBreaks]}
|
||||||
rehypePlugins={[rehypeSanitize, rehypeSlug]}
|
rehypePlugins={[rehypeSanitize, rehypeSlug]}
|
||||||
components={{
|
components={{
|
||||||
// 1. 코드 블록 커스텀
|
// 1. 코드 블록 커스텀
|
||||||
@@ -43,7 +45,7 @@ export default function MarkdownRenderer({ content }: MarkdownRendererProps) {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
// 2. 인용구
|
// 2. 인용구 (Blockquote)
|
||||||
blockquote({ children }) {
|
blockquote({ children }) {
|
||||||
return (
|
return (
|
||||||
<blockquote className="border-l-4 border-blue-500 bg-blue-50 pl-4 py-3 my-6 text-gray-700 rounded-r-lg italic shadow-sm">
|
<blockquote className="border-l-4 border-blue-500 bg-blue-50 pl-4 py-3 my-6 text-gray-700 rounded-r-lg italic shadow-sm">
|
||||||
@@ -82,29 +84,27 @@ export default function MarkdownRenderer({ content }: MarkdownRendererProps) {
|
|||||||
return <thead className="text-xs text-gray-700 uppercase bg-gray-50 border-b border-gray-200">{children}</thead>;
|
return <thead className="text-xs text-gray-700 uppercase bg-gray-50 border-b border-gray-200">{children}</thead>;
|
||||||
},
|
},
|
||||||
th({ children }) {
|
th({ children }) {
|
||||||
return <th className="px-6 py-3 font-bold text-gray-900">{children}</th>;
|
return <th className="px-6 py-3 font-bold text-gray-900 whitespace-nowrap">{children}</th>;
|
||||||
},
|
},
|
||||||
td({ children }) {
|
td({ children }) {
|
||||||
return <td className="px-6 py-4 border-b border-gray-100 whitespace-pre-wrap">{children}</td>;
|
return <td className="px-6 py-4 border-b border-gray-100 whitespace-pre-wrap">{children}</td>;
|
||||||
},
|
},
|
||||||
|
|
||||||
// 5. 이미지 (비율 유지 및 중앙 정렬)
|
// 5. 이미지 (figure 태그 사용으로 시멘틱 개선)
|
||||||
img({ src, alt }) {
|
img({ src, alt }) {
|
||||||
return (
|
return (
|
||||||
// 🛠️ [Fix] flex-col 추가: 이미지와 캡션을 세로로 정렬
|
<figure className="block my-8 flex flex-col items-center justify-center">
|
||||||
// items-center 추가: 가로축 중앙 정렬
|
|
||||||
<span className="block my-8 flex flex-col items-center justify-center">
|
|
||||||
<img
|
<img
|
||||||
src={src}
|
src={src}
|
||||||
alt={alt}
|
alt={alt}
|
||||||
className="rounded-xl shadow-lg border border-gray-100 max-w-full h-auto max-h-[700px] mx-auto hover:scale-[1.01] transition-transform duration-300"
|
className="rounded-xl shadow-lg border border-gray-100 max-w-full h-auto max-h-[700px] mx-auto hover:scale-[1.01] transition-transform duration-300 object-contain"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
onError={(e) => {
|
onError={(e) => {
|
||||||
e.currentTarget.style.display = 'none';
|
e.currentTarget.style.display = 'none';
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{alt && <span className="block text-center text-sm text-gray-400 mt-2 w-full">{alt}</span>}
|
{alt && <figcaption className="text-center text-sm text-gray-400 mt-2 w-full">{alt}</figcaption>}
|
||||||
</span>
|
</figure>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -123,19 +123,24 @@ export default function MarkdownRenderer({ content }: MarkdownRendererProps) {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
li({ children }) {
|
li({ children }) {
|
||||||
return <li className="pl-1">{children}</li>;
|
// p 태그가 내부에 생길 경우 margin 상쇄를 위해 items-start 등 조정
|
||||||
|
return <li className="pl-1 leading-relaxed">{children}</li>;
|
||||||
},
|
},
|
||||||
|
|
||||||
// 7. 헤딩 스타일 (🛠️ 수정: ...props를 전달해야 id가 붙어서 목차 이동이 작동함)
|
// 7. 헤딩 스타일
|
||||||
h1({ children, ...props }: any) {
|
h1({ children, ...props }: any) {
|
||||||
return <h1 className="text-3xl font-extrabold mt-12 mb-6 pb-4 border-b border-gray-100 text-gray-900" {...props}>{children}</h1>;
|
return <h1 className="text-3xl font-extrabold mt-12 mb-6 pb-4 border-b border-gray-100 text-gray-900 scroll-mt-20" {...props}>{children}</h1>;
|
||||||
},
|
},
|
||||||
h2({ children, ...props }: any) {
|
h2({ children, ...props }: any) {
|
||||||
return <h2 className="text-2xl font-bold mt-10 mb-5 pb-2 text-gray-800" {...props}>{children}</h2>;
|
return <h2 className="text-2xl font-bold mt-10 mb-5 pb-2 text-gray-800 scroll-mt-20" {...props}>{children}</h2>;
|
||||||
},
|
},
|
||||||
h3({ children, ...props }: any) {
|
h3({ children, ...props }: any) {
|
||||||
return <h3 className="text-xl font-bold mt-8 mb-4 text-gray-800 flex items-center gap-2 before:content-[''] before:w-1.5 before:h-6 before:bg-blue-500 before:rounded-full before:mr-1" {...props}>{children}</h3>;
|
return <h3 className="text-xl font-bold mt-8 mb-4 text-gray-800 flex items-center gap-2 before:content-[''] before:w-1.5 before:h-6 before:bg-blue-500 before:rounded-full before:mr-1 scroll-mt-20" {...props}>{children}</h3>;
|
||||||
},
|
},
|
||||||
|
// p 태그 스타일 추가 (일반 텍스트 가독성)
|
||||||
|
p({ children }) {
|
||||||
|
return <p className="mb-4 leading-7 text-gray-700">{children}</p>;
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
|
|||||||
17
yarn.lock
17
yarn.lock
@@ -2796,6 +2796,14 @@ mdast-util-mdxjs-esm@^2.0.0:
|
|||||||
mdast-util-from-markdown "^2.0.0"
|
mdast-util-from-markdown "^2.0.0"
|
||||||
mdast-util-to-markdown "^2.0.0"
|
mdast-util-to-markdown "^2.0.0"
|
||||||
|
|
||||||
|
mdast-util-newline-to-break@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.npmjs.org/mdast-util-newline-to-break/-/mdast-util-newline-to-break-2.0.0.tgz"
|
||||||
|
integrity sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==
|
||||||
|
dependencies:
|
||||||
|
"@types/mdast" "^4.0.0"
|
||||||
|
mdast-util-find-and-replace "^3.0.0"
|
||||||
|
|
||||||
mdast-util-phrasing@^4.0.0:
|
mdast-util-phrasing@^4.0.0:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz"
|
resolved "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz"
|
||||||
@@ -3689,6 +3697,15 @@ rehype@~13.0.0:
|
|||||||
rehype-stringify "^10.0.0"
|
rehype-stringify "^10.0.0"
|
||||||
unified "^11.0.0"
|
unified "^11.0.0"
|
||||||
|
|
||||||
|
remark-breaks@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz"
|
||||||
|
integrity sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/mdast" "^4.0.0"
|
||||||
|
mdast-util-newline-to-break "^2.0.0"
|
||||||
|
unified "^11.0.0"
|
||||||
|
|
||||||
remark-gfm@^4.0.1, remark-gfm@~4.0.0:
|
remark-gfm@^4.0.1, remark-gfm@~4.0.0:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz"
|
resolved "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz"
|
||||||
|
|||||||
Reference in New Issue
Block a user