feat(MarkdownEditor): enhance Markdown rendering with new plugins and components
- Integrated rehype-raw and rehype-slug for improved Markdown processing. - Added remark-toc for automatic table of contents generation. - Refactored Markdown components for better styling and functionality. - Updated package.json to include new dependencies for enhanced Markdown features.
This commit is contained in:
@@ -4,14 +4,274 @@ import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import React from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import rehypeHighlight from 'rehype-highlight';
|
||||
import remarkToc from 'remark-toc';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import rehypeSlug from 'rehype-slug';
|
||||
import rehypeSanitize from 'rehype-sanitize';
|
||||
import { Highlight, themes } from 'prism-react-renderer';
|
||||
import { Eye, EyeOff, Edit3, X, CheckSquare2 } from 'lucide-react';
|
||||
import { TagInput } from '@/components/ui/TagInput';
|
||||
import { TagDisplay } from '@/components/ui/TagDisplay';
|
||||
import { TaskSelectorWithData } from '@/components/shared/TaskSelectorWithData';
|
||||
import { MermaidRenderer } from '@/components/ui/MermaidRenderer';
|
||||
import { Tag, Task } from '@/lib/types';
|
||||
import type { Components } from 'react-markdown';
|
||||
|
||||
// Fonction pour générer les composants Markdown réutilisables
|
||||
const createMarkdownComponents = (
|
||||
isPreviewMode: boolean
|
||||
): Partial<Components> => ({
|
||||
// Titres avec tailles différentes selon le mode
|
||||
h1: ({ children }: { children?: React.ReactNode }) => (
|
||||
<h1
|
||||
className={
|
||||
isPreviewMode
|
||||
? 'text-4xl font-bold text-[var(--foreground)] mb-8 mt-10 first:mt-0 bg-gradient-to-r from-[var(--primary)] to-[var(--accent)] bg-clip-text text-transparent'
|
||||
: 'text-4xl font-bold text-[var(--foreground)] mb-8 mt-10 first:mt-0 bg-gradient-to-r from-[var(--primary)] to-[var(--accent)] bg-clip-text text-transparent'
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</h1>
|
||||
),
|
||||
h2: ({ children }: { children?: React.ReactNode }) => (
|
||||
<h2
|
||||
className={
|
||||
isPreviewMode
|
||||
? 'text-3xl font-bold text-[var(--foreground)] mb-6 mt-8 border-b border-[var(--border)]/30 pb-2'
|
||||
: 'text-3xl font-bold text-[var(--foreground)] mb-6 mt-8 border-b border-[var(--border)]/30 pb-2'
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</h2>
|
||||
),
|
||||
h3: ({ children }: { children?: React.ReactNode }) => (
|
||||
<h3
|
||||
className={
|
||||
isPreviewMode
|
||||
? 'text-2xl font-semibold text-[var(--foreground)] mb-4 mt-6'
|
||||
: 'text-2xl font-semibold text-[var(--foreground)] mb-4 mt-6'
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</h3>
|
||||
),
|
||||
h4: ({ children }: { children?: React.ReactNode }) => (
|
||||
<h4
|
||||
className={
|
||||
isPreviewMode
|
||||
? 'text-xl font-semibold text-[var(--foreground)] mb-3 mt-5'
|
||||
: 'text-xl font-semibold text-[var(--foreground)] mb-3 mt-5'
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</h4>
|
||||
),
|
||||
h5: ({ children }: { children?: React.ReactNode }) => (
|
||||
<h5
|
||||
className={
|
||||
isPreviewMode
|
||||
? 'text-lg font-semibold text-[var(--foreground)] mb-2 mt-4'
|
||||
: 'text-lg font-semibold text-[var(--foreground)] mb-2 mt-4'
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</h5>
|
||||
),
|
||||
h6: ({ children }: { children?: React.ReactNode }) => (
|
||||
<h6
|
||||
className={
|
||||
isPreviewMode
|
||||
? 'text-base font-semibold text-[var(--foreground)] mb-2 mt-3 text-[var(--muted-foreground)]'
|
||||
: 'text-base font-semibold text-[var(--foreground)] mb-2 mt-3 text-[var(--muted-foreground)]'
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</h6>
|
||||
),
|
||||
p: ({ children }: { children?: React.ReactNode }) => (
|
||||
<p className="text-[var(--foreground)] mb-4 leading-relaxed">{children}</p>
|
||||
),
|
||||
// Listes avec le même style partout
|
||||
ul: ({ children }: { children?: React.ReactNode }) => (
|
||||
<ul className="mb-0 pl-4">{children}</ul>
|
||||
),
|
||||
ol: ({ children }: { children?: React.ReactNode }) => (
|
||||
<ol className="mb-0 pl-4 list-decimal list-inside">{children}</ol>
|
||||
),
|
||||
li: ({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
}) => {
|
||||
const isTaskListItem = className?.includes('task-list-item');
|
||||
|
||||
if (isTaskListItem) {
|
||||
return (
|
||||
<li className="!m-0 py-0.5 text-[var(--foreground)]">{children}</li>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li className="!m-0 py-0.5 text-[var(--foreground)] flex items-start gap-2">
|
||||
<span className="w-1.5 h-1.5 bg-[var(--primary)] rounded-full mt-2 flex-shrink-0"></span>
|
||||
<span>{children}</span>
|
||||
</li>
|
||||
);
|
||||
},
|
||||
blockquote: ({ children }: { children?: React.ReactNode }) => (
|
||||
<blockquote className="border-l-4 border-[var(--primary)] pl-4 py-2 my-4 bg-[var(--card)]/20 backdrop-blur-sm rounded-r-lg">
|
||||
<div className="text-[var(--muted-foreground)] italic">{children}</div>
|
||||
</blockquote>
|
||||
),
|
||||
code: (({
|
||||
inline,
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: {
|
||||
inline?: boolean;
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
} & Record<string, unknown>) => {
|
||||
if (inline) {
|
||||
return (
|
||||
<code className="bg-[var(--card)]/60 px-2 py-1 rounded text-[var(--accent)] font-mono text-sm border border-[var(--border)]/40">
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
}) as Components['code'],
|
||||
pre: ({ children }: { children?: React.ReactNode }) => {
|
||||
let codeElement: string | null = null;
|
||||
let className = '';
|
||||
|
||||
if (React.isValidElement(children)) {
|
||||
const props = children.props as {
|
||||
children?: string;
|
||||
className?: string;
|
||||
};
|
||||
codeElement = props?.children || null;
|
||||
className = props?.className || '';
|
||||
}
|
||||
|
||||
const isMermaid =
|
||||
className.includes('language-mermaid') ||
|
||||
(typeof codeElement === 'string' &&
|
||||
(codeElement.trim().startsWith('graph') ||
|
||||
codeElement.trim().startsWith('flowchart') ||
|
||||
codeElement.trim().startsWith('sequenceDiagram') ||
|
||||
codeElement.trim().startsWith('gantt') ||
|
||||
codeElement.trim().startsWith('pie') ||
|
||||
codeElement.trim().startsWith('gitgraph') ||
|
||||
codeElement.trim().startsWith('journey') ||
|
||||
codeElement.trim().startsWith('stateDiagram') ||
|
||||
codeElement.trim().startsWith('classDiagram') ||
|
||||
codeElement.trim().startsWith('erDiagram') ||
|
||||
codeElement.trim().startsWith('mindmap') ||
|
||||
codeElement.trim().startsWith('timeline')));
|
||||
|
||||
if (isMermaid && typeof codeElement === 'string') {
|
||||
return (
|
||||
<div className="my-6">
|
||||
<MermaidRenderer chart={codeElement} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
const language = match ? match[1] : 'javascript';
|
||||
|
||||
return (
|
||||
<Highlight
|
||||
theme={themes.vsDark}
|
||||
code={codeElement || ''}
|
||||
language={language}
|
||||
>
|
||||
{({
|
||||
className: highlightClassName,
|
||||
style,
|
||||
tokens,
|
||||
getLineProps,
|
||||
getTokenProps,
|
||||
}) => {
|
||||
// Supprimer les lignes vides à la fin
|
||||
const filteredTokens = tokens.filter((line, i) => {
|
||||
// Garder toutes les lignes sauf la dernière si elle est vide
|
||||
if (i === tokens.length - 1) {
|
||||
return (
|
||||
line.length > 0 &&
|
||||
line.some((token) => token.content.trim() !== '')
|
||||
);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return (
|
||||
<pre
|
||||
className="bg-[var(--card)]/60 border border-[var(--border)]/60 rounded-lg p-4 overflow-x-auto backdrop-blur-sm font-mono text-sm mb-6"
|
||||
style={{
|
||||
...style,
|
||||
background: 'color-mix(in srgb, var(--card) 80%, transparent)',
|
||||
}}
|
||||
>
|
||||
<code className={highlightClassName}>
|
||||
{filteredTokens.map((line, i) => (
|
||||
<div key={i} {...getLineProps({ line })}>
|
||||
{line.map((token, key) => (
|
||||
<span key={key} {...getTokenProps({ token })} />
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</code>
|
||||
</pre>
|
||||
);
|
||||
}}
|
||||
</Highlight>
|
||||
);
|
||||
},
|
||||
a: ({ children, href }: { children?: React.ReactNode; href?: string }) => (
|
||||
<a
|
||||
href={href}
|
||||
className="text-[var(--primary)] hover:text-[var(--primary)]/80 underline decoration-[var(--primary)]/50 hover:decoration-[var(--primary)] transition-colors"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
table: ({ children }: { children?: React.ReactNode }) => (
|
||||
<div className="overflow-x-auto my-6">
|
||||
<table className="min-w-full border border-[var(--border)]/60 rounded-lg overflow-hidden">
|
||||
{children}
|
||||
</table>
|
||||
</div>
|
||||
),
|
||||
thead: ({ children }: { children?: React.ReactNode }) => (
|
||||
<thead className="bg-[var(--card)]/40">{children}</thead>
|
||||
),
|
||||
tbody: ({ children }: { children?: React.ReactNode }) => (
|
||||
<tbody className="divide-y divide-[var(--border)]/60">{children}</tbody>
|
||||
),
|
||||
tr: ({ children }: { children?: React.ReactNode }) => (
|
||||
<tr className="hover:bg-[var(--card)]/20 transition-colors">{children}</tr>
|
||||
),
|
||||
th: ({ children }: { children?: React.ReactNode }) => (
|
||||
<th className="px-4 py-2 text-left text-[var(--foreground)] font-semibold border-b border-[var(--border)]/60">
|
||||
{children}
|
||||
</th>
|
||||
),
|
||||
td: ({ children }: { children?: React.ReactNode }) => (
|
||||
<td className="px-4 py-2 text-[var(--foreground)]">{children}</td>
|
||||
),
|
||||
hr: () => <hr className="my-8 border-t-2 border-[var(--border)]/30" />,
|
||||
});
|
||||
|
||||
interface MarkdownEditorProps {
|
||||
value: string;
|
||||
@@ -444,161 +704,18 @@ export function MarkdownEditor({
|
||||
<div className="flex-1 overflow-auto p-6 bg-[var(--background)]/50 backdrop-blur-sm">
|
||||
<div className="prose prose-sm max-w-none prose-headings:text-[var(--foreground)] prose-p:text-[var(--foreground)] prose-strong:text-[var(--foreground)] prose-strong:font-bold prose-em:text-[var(--muted-foreground)] prose-code:text-[var(--accent)] prose-pre:bg-[var(--card)]/60 prose-pre:border prose-pre:border-[var(--border)]/60 prose-blockquote:border-[var(--primary)] prose-blockquote:text-[var(--muted-foreground)] prose-a:text-[var(--primary)] prose-table:border-[var(--border)]/60 prose-th:bg-[var(--card)]/40 prose-th:text-[var(--foreground)] prose-td:text-[var(--foreground)]">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
rehypePlugins={[rehypeHighlight, rehypeSanitize]}
|
||||
components={{
|
||||
// Custom styling for better integration
|
||||
h1: ({ children }) => (
|
||||
<h1 className="text-3xl font-bold text-[var(--foreground)] mb-6 mt-8 first:mt-0 bg-gradient-to-r from-[var(--primary)] to-[var(--accent)] bg-clip-text text-transparent">
|
||||
{children}
|
||||
</h1>
|
||||
),
|
||||
h2: ({ children }) => (
|
||||
<h2 className="text-2xl font-bold text-[var(--foreground)] mb-4 mt-6 border-b border-[var(--border)]/30 pb-2">
|
||||
{children}
|
||||
</h2>
|
||||
),
|
||||
h3: ({ children }) => (
|
||||
<h3 className="text-xl font-semibold text-[var(--foreground)] mb-3 mt-5 flex items-center gap-2">
|
||||
<span className="w-2 h-2 bg-[var(--primary)] rounded-full"></span>
|
||||
{children}
|
||||
</h3>
|
||||
),
|
||||
h4: ({ children }) => (
|
||||
<h4 className="text-lg font-semibold text-[var(--foreground)] mb-2 mt-4">
|
||||
{children}
|
||||
</h4>
|
||||
),
|
||||
h5: ({ children }) => (
|
||||
<h5 className="text-base font-semibold text-[var(--foreground)] mb-2 mt-3">
|
||||
{children}
|
||||
</h5>
|
||||
),
|
||||
h6: ({ children }) => (
|
||||
<h6 className="text-sm font-semibold text-[var(--foreground)] mb-2 mt-3 text-[var(--muted-foreground)]">
|
||||
{children}
|
||||
</h6>
|
||||
),
|
||||
p: ({ children }) => (
|
||||
<p className="text-[var(--foreground)] mb-4 leading-relaxed">
|
||||
{children}
|
||||
</p>
|
||||
),
|
||||
ul: ({ children }) => (
|
||||
<ul className="mb-4 space-y-2">{children}</ul>
|
||||
),
|
||||
ol: ({ children }) => (
|
||||
<ol className="mb-4 space-y-2 list-decimal list-inside">
|
||||
{children}
|
||||
</ol>
|
||||
),
|
||||
li: ({ children }) => (
|
||||
<li className="text-[var(--foreground)] flex items-start gap-2">
|
||||
<span className="w-1.5 h-1.5 bg-[var(--primary)] rounded-full mt-2 flex-shrink-0"></span>
|
||||
<span>{children}</span>
|
||||
</li>
|
||||
),
|
||||
blockquote: ({ children }) => (
|
||||
<blockquote className="border-l-4 border-[var(--primary)] pl-4 py-2 my-4 bg-[var(--card)]/20 backdrop-blur-sm rounded-r-lg">
|
||||
<div className="text-[var(--muted-foreground)] italic">
|
||||
{children}
|
||||
</div>
|
||||
</blockquote>
|
||||
),
|
||||
code: ({ children, className }) => {
|
||||
const isInline = !className;
|
||||
if (isInline) {
|
||||
return (
|
||||
<code className="bg-[var(--card)]/60 px-2 py-1 rounded text-[var(--accent)] font-mono text-sm border border-[var(--border)]/40">
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
return <code className={className}>{children}</code>;
|
||||
},
|
||||
pre: ({ children }) => {
|
||||
// Check if this is a mermaid code block
|
||||
let codeElement: string | null = null;
|
||||
let className = '';
|
||||
|
||||
if (React.isValidElement(children)) {
|
||||
const props = children.props as {
|
||||
children?: string;
|
||||
className?: string;
|
||||
};
|
||||
codeElement = props?.children || null;
|
||||
className = props?.className || '';
|
||||
}
|
||||
|
||||
const isMermaid =
|
||||
className.includes('language-mermaid') ||
|
||||
(typeof codeElement === 'string' &&
|
||||
(codeElement.trim().startsWith('graph') ||
|
||||
codeElement.trim().startsWith('flowchart') ||
|
||||
codeElement.trim().startsWith('sequenceDiagram') ||
|
||||
codeElement.trim().startsWith('gantt') ||
|
||||
codeElement.trim().startsWith('pie') ||
|
||||
codeElement.trim().startsWith('gitgraph') ||
|
||||
codeElement.trim().startsWith('journey') ||
|
||||
codeElement.trim().startsWith('stateDiagram') ||
|
||||
codeElement.trim().startsWith('classDiagram') ||
|
||||
codeElement.trim().startsWith('erDiagram') ||
|
||||
codeElement.trim().startsWith('mindmap') ||
|
||||
codeElement.trim().startsWith('timeline')));
|
||||
|
||||
if (isMermaid && typeof codeElement === 'string') {
|
||||
return (
|
||||
<div className="my-6">
|
||||
<MermaidRenderer chart={codeElement} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<pre className="bg-[var(--card)]/60 border border-[var(--border)]/60 rounded-lg p-4 overflow-x-auto backdrop-blur-sm">
|
||||
{children}
|
||||
</pre>
|
||||
);
|
||||
},
|
||||
a: ({ children, href }) => (
|
||||
<a
|
||||
href={href}
|
||||
className="text-[var(--primary)] hover:text-[var(--primary)]/80 underline decoration-[var(--primary)]/50 hover:decoration-[var(--primary)] transition-colors"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
table: ({ children }) => (
|
||||
<div className="overflow-x-auto mb-6 rounded-lg border border-[var(--border)]/60">
|
||||
<table className="min-w-full">{children}</table>
|
||||
</div>
|
||||
),
|
||||
th: ({ children }) => (
|
||||
<th className="border-b border-[var(--border)]/60 bg-[var(--card)]/40 px-4 py-3 text-left font-semibold text-[var(--foreground)] backdrop-blur-sm">
|
||||
{children}
|
||||
</th>
|
||||
),
|
||||
td: ({ children }) => (
|
||||
<td className="border-b border-[var(--border)]/30 px-4 py-3 text-[var(--foreground)]">
|
||||
{children}
|
||||
</td>
|
||||
),
|
||||
hr: () => (
|
||||
<hr className="my-8 border-0 h-px bg-gradient-to-r from-transparent via-[var(--border)] to-transparent" />
|
||||
),
|
||||
strong: ({ children }) => (
|
||||
<strong className="font-bold text-[var(--foreground)]">
|
||||
{children}
|
||||
</strong>
|
||||
),
|
||||
em: ({ children }) => (
|
||||
<em className="italic text-[var(--muted-foreground)]">
|
||||
{children}
|
||||
</em>
|
||||
),
|
||||
}}
|
||||
remarkPlugins={[
|
||||
remarkGfm,
|
||||
[
|
||||
remarkToc,
|
||||
{
|
||||
heading: '(table[ -]of[ -])?contents?|toc|sommaire',
|
||||
tight: true,
|
||||
},
|
||||
],
|
||||
]}
|
||||
rehypePlugins={[rehypeRaw, rehypeSlug, rehypeSanitize]}
|
||||
components={createMarkdownComponents(true)}
|
||||
>
|
||||
{value || "*Commencez à écrire pour voir l'aperçu...*"}
|
||||
</ReactMarkdown>
|
||||
@@ -670,163 +787,18 @@ export function MarkdownEditor({
|
||||
<div className="flex-1 overflow-auto p-6 bg-[var(--background)]/50 backdrop-blur-sm">
|
||||
<div className="prose prose-sm max-w-none prose-headings:text-[var(--foreground)] prose-p:text-[var(--foreground)] prose-strong:text-[var(--foreground)] prose-strong:font-bold prose-em:text-[var(--muted-foreground)] prose-code:text-[var(--accent)] prose-pre:bg-[var(--card)]/60 prose-pre:border prose-pre:border-[var(--border)]/60 prose-blockquote:border-[var(--primary)] prose-blockquote:text-[var(--muted-foreground)] prose-a:text-[var(--primary)] prose-table:border-[var(--border)]/60 prose-th:bg-[var(--card)]/40 prose-th:text-[var(--foreground)] prose-td:text-[var(--foreground)]">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
rehypePlugins={[rehypeHighlight, rehypeSanitize]}
|
||||
components={{
|
||||
// Custom styling for better integration
|
||||
h1: ({ children }) => (
|
||||
<h1 className="text-3xl font-bold text-[var(--foreground)] mb-6 mt-8 first:mt-0 bg-gradient-to-r from-[var(--primary)] to-[var(--accent)] bg-clip-text text-transparent">
|
||||
{children}
|
||||
</h1>
|
||||
),
|
||||
h2: ({ children }) => (
|
||||
<h2 className="text-2xl font-bold text-[var(--foreground)] mb-4 mt-6 border-b border-[var(--border)]/30 pb-2">
|
||||
{children}
|
||||
</h2>
|
||||
),
|
||||
h3: ({ children }) => (
|
||||
<h3 className="text-xl font-semibold text-[var(--foreground)] mb-3 mt-5 flex items-center gap-2">
|
||||
<span className="w-2 h-2 bg-[var(--primary)] rounded-full"></span>
|
||||
{children}
|
||||
</h3>
|
||||
),
|
||||
h4: ({ children }) => (
|
||||
<h4 className="text-lg font-semibold text-[var(--foreground)] mb-2 mt-4">
|
||||
{children}
|
||||
</h4>
|
||||
),
|
||||
h5: ({ children }) => (
|
||||
<h5 className="text-base font-semibold text-[var(--foreground)] mb-2 mt-3">
|
||||
{children}
|
||||
</h5>
|
||||
),
|
||||
h6: ({ children }) => (
|
||||
<h6 className="text-sm font-semibold text-[var(--foreground)] mb-2 mt-3 text-[var(--muted-foreground)]">
|
||||
{children}
|
||||
</h6>
|
||||
),
|
||||
p: ({ children }) => (
|
||||
<p className="text-[var(--foreground)] mb-4 leading-relaxed">
|
||||
{children}
|
||||
</p>
|
||||
),
|
||||
ul: ({ children }) => (
|
||||
<ul className="mb-4 space-y-2">{children}</ul>
|
||||
),
|
||||
ol: ({ children }) => (
|
||||
<ol className="mb-4 space-y-2 list-decimal list-inside">
|
||||
{children}
|
||||
</ol>
|
||||
),
|
||||
li: ({ children }) => (
|
||||
<li className="text-[var(--foreground)] flex items-start gap-2">
|
||||
<span className="w-1.5 h-1.5 bg-[var(--primary)] rounded-full mt-2 flex-shrink-0"></span>
|
||||
<span>{children}</span>
|
||||
</li>
|
||||
),
|
||||
blockquote: ({ children }) => (
|
||||
<blockquote className="border-l-4 border-[var(--primary)] pl-4 py-2 my-4 bg-[var(--card)]/20 backdrop-blur-sm rounded-r-lg">
|
||||
<div className="text-[var(--muted-foreground)] italic">
|
||||
{children}
|
||||
</div>
|
||||
</blockquote>
|
||||
),
|
||||
code: ({ children, className }) => {
|
||||
const isInline = !className;
|
||||
if (isInline) {
|
||||
return (
|
||||
<code className="bg-[var(--card)]/60 px-2 py-1 rounded text-[var(--accent)] font-mono text-sm border border-[var(--border)]/40">
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
return <code className={className}>{children}</code>;
|
||||
},
|
||||
pre: ({ children }) => {
|
||||
// Check if this is a mermaid code block
|
||||
let codeElement: string | null = null;
|
||||
let className = '';
|
||||
|
||||
if (React.isValidElement(children)) {
|
||||
const props = children.props as {
|
||||
children?: string;
|
||||
className?: string;
|
||||
};
|
||||
codeElement = props?.children || null;
|
||||
className = props?.className || '';
|
||||
}
|
||||
|
||||
const isMermaid =
|
||||
className.includes('language-mermaid') ||
|
||||
(typeof codeElement === 'string' &&
|
||||
(codeElement.trim().startsWith('graph') ||
|
||||
codeElement.trim().startsWith('flowchart') ||
|
||||
codeElement
|
||||
.trim()
|
||||
.startsWith('sequenceDiagram') ||
|
||||
codeElement.trim().startsWith('gantt') ||
|
||||
codeElement.trim().startsWith('pie') ||
|
||||
codeElement.trim().startsWith('gitgraph') ||
|
||||
codeElement.trim().startsWith('journey') ||
|
||||
codeElement.trim().startsWith('stateDiagram') ||
|
||||
codeElement.trim().startsWith('classDiagram') ||
|
||||
codeElement.trim().startsWith('erDiagram') ||
|
||||
codeElement.trim().startsWith('mindmap') ||
|
||||
codeElement.trim().startsWith('timeline')));
|
||||
|
||||
if (isMermaid && typeof codeElement === 'string') {
|
||||
return (
|
||||
<div className="my-6">
|
||||
<MermaidRenderer chart={codeElement} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<pre className="bg-[var(--card)]/60 border border-[var(--border)]/60 rounded-lg p-4 overflow-x-auto backdrop-blur-sm">
|
||||
{children}
|
||||
</pre>
|
||||
);
|
||||
},
|
||||
a: ({ children, href }) => (
|
||||
<a
|
||||
href={href}
|
||||
className="text-[var(--primary)] hover:text-[var(--primary)]/80 underline decoration-[var(--primary)]/50 hover:decoration-[var(--primary)] transition-colors"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
table: ({ children }) => (
|
||||
<div className="overflow-x-auto mb-6 rounded-lg border border-[var(--border)]/60">
|
||||
<table className="min-w-full">{children}</table>
|
||||
</div>
|
||||
),
|
||||
th: ({ children }) => (
|
||||
<th className="border-b border-[var(--border)]/60 bg-[var(--card)]/40 px-4 py-3 text-left font-semibold text-[var(--foreground)] backdrop-blur-sm">
|
||||
{children}
|
||||
</th>
|
||||
),
|
||||
td: ({ children }) => (
|
||||
<td className="border-b border-[var(--border)]/30 px-4 py-3 text-[var(--foreground)]">
|
||||
{children}
|
||||
</td>
|
||||
),
|
||||
hr: () => (
|
||||
<hr className="my-8 border-0 h-px bg-gradient-to-r from-transparent via-[var(--border)] to-transparent" />
|
||||
),
|
||||
strong: ({ children }) => (
|
||||
<strong className="font-bold text-[var(--foreground)]">
|
||||
{children}
|
||||
</strong>
|
||||
),
|
||||
em: ({ children }) => (
|
||||
<em className="italic text-[var(--muted-foreground)]">
|
||||
{children}
|
||||
</em>
|
||||
),
|
||||
}}
|
||||
remarkPlugins={[
|
||||
remarkGfm,
|
||||
[
|
||||
remarkToc,
|
||||
{
|
||||
heading: '(table[ -]of[ -])?contents?|toc|sommaire',
|
||||
tight: true,
|
||||
},
|
||||
],
|
||||
]}
|
||||
rehypePlugins={[rehypeRaw, rehypeSlug, rehypeSanitize]}
|
||||
components={createMarkdownComponents(false)}
|
||||
>
|
||||
{value || "*Commencez à écrire pour voir l'aperçu...*"}
|
||||
</ReactMarkdown>
|
||||
|
||||
Reference in New Issue
Block a user