mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-05 23:44:22 +00:00
137 lines
4.0 KiB
TypeScript
137 lines
4.0 KiB
TypeScript
import React, { useState, useEffect, useMemo, type ReactNode } from 'react';
|
|
import { unified } from 'unified';
|
|
import remarkParse from 'remark-parse';
|
|
import remarkMath from 'remark-math';
|
|
import remarkRehype from 'remark-rehype';
|
|
import rehypeMathjax from 'rehype-mathjax';
|
|
import rehypeReact, { type Options } from 'rehype-react';
|
|
import rehypeHighlight from 'rehype-highlight';
|
|
import * as prod from 'react/jsx-runtime';
|
|
import { notifications } from '@mantine/notifications';
|
|
import { remarkWikiLinks } from '../../utils/remarkWikiLinks';
|
|
import { useWorkspace } from '../../hooks/useWorkspace';
|
|
import { useHighlightTheme } from '../../hooks/useHighlightTheme';
|
|
|
|
interface MarkdownPreviewProps {
|
|
content: string;
|
|
handleFileSelect: (filePath: string | null) => Promise<void>;
|
|
}
|
|
|
|
interface MarkdownImageProps {
|
|
src: string;
|
|
alt?: string;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
interface MarkdownLinkProps {
|
|
href: string;
|
|
children: ReactNode;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
const MarkdownPreview: React.FC<MarkdownPreviewProps> = ({
|
|
content,
|
|
handleFileSelect,
|
|
}) => {
|
|
const [processedContent, setProcessedContent] = useState<ReactNode | null>(
|
|
null
|
|
);
|
|
const baseUrl = window.API_BASE_URL;
|
|
const { currentWorkspace, colorScheme } = useWorkspace();
|
|
|
|
// Use the highlight theme hook
|
|
useHighlightTheme(colorScheme === 'auto' ? 'light' : colorScheme);
|
|
|
|
const processor = useMemo(() => {
|
|
const handleLinkClick = (
|
|
e: React.MouseEvent<HTMLAnchorElement>,
|
|
href: string
|
|
): void => {
|
|
e.preventDefault();
|
|
|
|
if (href.startsWith(`${baseUrl}/internal/`)) {
|
|
// For existing files, extract the path and directly select it
|
|
const [filePath] = decodeURIComponent(
|
|
href.replace(`${baseUrl}/internal/`, '')
|
|
).split('#');
|
|
if (filePath) {
|
|
void handleFileSelect(filePath);
|
|
}
|
|
} else if (href.startsWith(`${baseUrl}/notfound/`)) {
|
|
// For non-existent files, show a notification
|
|
const fileName = decodeURIComponent(
|
|
href.replace(`${baseUrl}/notfound/`, '')
|
|
);
|
|
notifications.show({
|
|
title: 'File Not Found',
|
|
message: `The file "${fileName}" does not exist.`,
|
|
color: 'red',
|
|
});
|
|
}
|
|
};
|
|
// Only create the processor if we have a workspace name
|
|
if (!currentWorkspace?.name) {
|
|
return unified();
|
|
}
|
|
|
|
return unified()
|
|
.use(remarkParse)
|
|
.use(remarkWikiLinks, currentWorkspace.name)
|
|
.use(remarkMath)
|
|
.use(remarkRehype)
|
|
.use(rehypeMathjax)
|
|
.use(rehypeHighlight)
|
|
.use(rehypeReact, {
|
|
jsx: prod.jsx,
|
|
jsxs: prod.jsxs,
|
|
Fragment: prod.Fragment,
|
|
development: false,
|
|
elementAttributeNameCase: 'react',
|
|
stylePropertyNameCase: 'dom',
|
|
components: {
|
|
img: ({ src, alt, ...props }: MarkdownImageProps) => (
|
|
<img
|
|
src={src}
|
|
alt={alt || ''}
|
|
onError={(event) => {
|
|
console.error('Failed to load image:', event.currentTarget.src);
|
|
event.currentTarget.alt = 'Failed to load image';
|
|
}}
|
|
{...props}
|
|
/>
|
|
),
|
|
a: ({ href, children, ...props }: MarkdownLinkProps) => (
|
|
<a href={href} onClick={(e) => handleLinkClick(e, href)} {...props}>
|
|
{children}
|
|
</a>
|
|
),
|
|
},
|
|
} as Options);
|
|
}, [currentWorkspace?.name, baseUrl, handleFileSelect]);
|
|
|
|
useEffect(() => {
|
|
const processContent = async (): Promise<void> => {
|
|
if (!currentWorkspace) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const result = await processor.process(content);
|
|
setProcessedContent(result.result as ReactNode);
|
|
} catch (error) {
|
|
console.error('Error processing markdown:', error);
|
|
}
|
|
};
|
|
|
|
void processContent();
|
|
}, [content, processor, currentWorkspace]);
|
|
|
|
return (
|
|
<div className="markdown-preview" data-testid="markdown-preview">
|
|
{processedContent}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default MarkdownPreview;
|