303 lines
9.9 KiB
TypeScript
303 lines
9.9 KiB
TypeScript
import React, { useState, useEffect, createElement } from 'react';
|
|
import { MDXProvider } from '@mdx-js/react';
|
|
import { compile } from '@mdx-js/mdx';
|
|
import * as runtime from 'react/jsx-runtime';
|
|
import { ComponentProps } from 'react';
|
|
|
|
interface CodeProps extends ComponentProps<'code'> {
|
|
className?: string;
|
|
children?: React.ReactNode;
|
|
}
|
|
|
|
interface BlockquoteProps extends ComponentProps<'blockquote'> { }
|
|
|
|
interface UlProps extends ComponentProps<'ul'> { }
|
|
interface OlProps extends ComponentProps<'ol'> { }
|
|
|
|
interface ListItemProps extends ComponentProps<'li'> { }
|
|
|
|
const components = {
|
|
h1: (props: ComponentProps<'h1'>) => <h1 style={{ color: '#2563eb' }} {...props} />,
|
|
h2: (props: ComponentProps<'h2'>) => <h2 style={{ color: '#1e40af' }} {...props} />,
|
|
p: (props: ComponentProps<'p'>) => <p style={{ fontSize: '16px', lineHeight: '1.6' }} {...props} />,
|
|
code: ({ className, children, ...props }: CodeProps) => (
|
|
<code
|
|
style={{
|
|
backgroundColor: '#f3f4f6',
|
|
padding: '2px 6px',
|
|
borderRadius: '4px',
|
|
fontFamily: 'monospace'
|
|
}}
|
|
{...props}
|
|
>
|
|
{children}
|
|
</code>
|
|
),
|
|
pre: (props: ComponentProps<'pre'>) => (
|
|
<pre
|
|
style={{
|
|
backgroundColor: '#1f2937',
|
|
color: '#f9fafb',
|
|
padding: '16px',
|
|
borderRadius: '8px',
|
|
overflow: 'auto',
|
|
fontFamily: 'monospace'
|
|
}}
|
|
{...props}
|
|
/>
|
|
),
|
|
blockquote: (props: BlockquoteProps) => (
|
|
<blockquote
|
|
style={{
|
|
borderLeft: '4px solid #e5e7eb',
|
|
paddingLeft: '16px',
|
|
margin: '16px 0',
|
|
fontStyle: 'italic'
|
|
}}
|
|
{...props}
|
|
/>
|
|
),
|
|
ul: (props: UlProps) => <ul style={{ paddingLeft: '20px' }} {...props} />,
|
|
ol: (props: OlProps) => <ol style={{ paddingLeft: '20px' }} {...props} />,
|
|
li: (props: ListItemProps) => <li style={{ marginBottom: '8px' }} {...props} />
|
|
};
|
|
|
|
const App = () => {
|
|
const [Content, setContent] = useState<React.ComponentType | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [reloading, setReloading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const loadMDX = async () => {
|
|
if (!loading) {
|
|
setReloading(true);
|
|
}
|
|
try {
|
|
const response = await fetch('/example.mdx?t=' + Date.now());
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
const text = await response.text();
|
|
|
|
const compiledCode = await compile(text, {
|
|
outputFormat: 'function-body',
|
|
development: import.meta.env.DEV,
|
|
});
|
|
|
|
const code = String(compiledCode);
|
|
console.log('Raw compiled code:', code);
|
|
|
|
try {
|
|
// Define basic HTML components for missing ones
|
|
const htmlComponents = {
|
|
h3: (props: any) => <h3 style={{ color: '#374151' }} {...props} />,
|
|
h4: (props: any) => <h4 style={{ color: '#4b5563' }} {...props} />,
|
|
h5: (props: any) => <h5 style={{ color: '#6b7280' }} {...props} />,
|
|
h6: (props: any) => <h6 style={{ color: '#9ca3af' }} {...props} />,
|
|
a: (props: any) => <a style={{ color: '#2563eb', textDecoration: 'underline' }} {...props} />,
|
|
img: (props: any) => <img style={{ maxWidth: '100%', height: 'auto' }} {...props} />,
|
|
hr: (props: any) => <hr style={{ border: '1px solid #e5e7eb', margin: '16px 0' }} {...props} />,
|
|
br: (props: any) => <br {...props} />,
|
|
em: (props: any) => <em style={{ fontStyle: 'italic' }} {...props} />,
|
|
strong: (props: any) => <strong style={{ fontWeight: 'bold' }} {...props} />,
|
|
table: (props: any) => <table style={{ width: '100%', borderCollapse: 'collapse', margin: '16px 0' }} {...props} />,
|
|
thead: (props: any) => <thead style={{ backgroundColor: '#f9fafb' }} {...props} />,
|
|
tbody: (props: any) => <tbody {...props} />,
|
|
tr: (props: any) => <tr style={{ borderBottom: '1px solid #e5e7eb' }} {...props} />,
|
|
th: (props: any) => <th style={{ padding: '12px', textAlign: 'left', fontWeight: 'bold' }} {...props} />,
|
|
td: (props: any) => <td style={{ padding: '12px' }} {...props} />,
|
|
div: (props: any) => <div {...props} />,
|
|
span: (props: any) => <span {...props} />,
|
|
};
|
|
|
|
// Merge all components
|
|
const allComponents = { ...components, ...htmlComponents };
|
|
|
|
const scope = {
|
|
React,
|
|
components: allComponents,
|
|
createElement,
|
|
Fragment: React.Fragment,
|
|
jsx: runtime.jsx,
|
|
jsxs: runtime.jsxs,
|
|
// Include all components directly in scope
|
|
h1: allComponents.h1,
|
|
h2: allComponents.h2,
|
|
h3: allComponents.h3,
|
|
h4: allComponents.h4,
|
|
h5: allComponents.h5,
|
|
h6: allComponents.h6,
|
|
p: allComponents.p,
|
|
a: allComponents.a,
|
|
img: allComponents.img,
|
|
ul: allComponents.ul,
|
|
ol: allComponents.ol,
|
|
li: allComponents.li,
|
|
blockquote: allComponents.blockquote,
|
|
hr: allComponents.hr,
|
|
br: allComponents.br,
|
|
em: allComponents.em,
|
|
strong: allComponents.strong,
|
|
code: allComponents.code,
|
|
pre: allComponents.pre,
|
|
table: allComponents.table,
|
|
thead: allComponents.thead,
|
|
tbody: allComponents.tbody,
|
|
tr: allComponents.tr,
|
|
th: allComponents.th,
|
|
td: allComponents.td,
|
|
div: allComponents.div,
|
|
span: allComponents.span,
|
|
};
|
|
|
|
const keys = Object.keys(scope);
|
|
const values = Object.values(scope);
|
|
|
|
// Try to evaluate the code using a more robust approach
|
|
try {
|
|
// First, try to execute as function body
|
|
const fn1 = new Function(
|
|
...keys,
|
|
`${code}; return MDXContent;`
|
|
);
|
|
const Component1 = fn1(...values);
|
|
const MDXComponent1 = Component1.default || Component1;
|
|
|
|
if (MDXComponent1) {
|
|
setContent(() => MDXComponent1);
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
} catch (e1) {
|
|
console.log('First evaluation method failed, trying second...');
|
|
}
|
|
|
|
try {
|
|
// Second, try to execute as function expression
|
|
const fn2 = new Function(
|
|
...keys,
|
|
`return ${code}`
|
|
);
|
|
const Component2 = fn2(...values);
|
|
const MDXComponent2 = Component2.default || Component2;
|
|
|
|
if (MDXComponent2) {
|
|
setContent(() => MDXComponent2);
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
} catch (e2) {
|
|
console.log('Second evaluation method failed, trying third...');
|
|
}
|
|
|
|
try {
|
|
// Third, try to execute directly with eval-like approach
|
|
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
|
|
const fn3 = new AsyncFunction(
|
|
...keys,
|
|
code
|
|
);
|
|
const result = await fn3(...values);
|
|
const MDXComponent3 = result.default || result;
|
|
|
|
if (MDXComponent3) {
|
|
setContent(() => MDXComponent3);
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
} catch (e3) {
|
|
console.log('Third evaluation method failed');
|
|
}
|
|
|
|
throw new Error('All evaluation methods failed');
|
|
} catch (evalError) {
|
|
console.error('Evaluation error:', evalError);
|
|
console.error('Compiled code sample:', code.substring(0, 500));
|
|
const errorMessage = evalError instanceof Error ? evalError.message : String(evalError);
|
|
throw new Error(`MDX evaluation failed: ${errorMessage}`);
|
|
}
|
|
} catch (err) {
|
|
console.error('Error loading MDX:', err);
|
|
setError(err instanceof Error ? err.message : String(err));
|
|
setLoading(false);
|
|
} finally {
|
|
setReloading(false);
|
|
}
|
|
};
|
|
|
|
loadMDX();
|
|
|
|
// Set up interval for auto-reloading during development
|
|
let intervalId: NodeJS.Timeout;
|
|
if (import.meta.env.DEV) {
|
|
intervalId = setInterval(loadMDX, 2000); // Check every 2 seconds
|
|
}
|
|
|
|
return () => {
|
|
if (intervalId) {
|
|
clearInterval(intervalId);
|
|
}
|
|
};
|
|
}, [loading]);
|
|
|
|
if (loading) {
|
|
return (
|
|
<div style={{ padding: '20px', textAlign: 'center' }}>
|
|
<h1>MDX Viewer</h1>
|
|
<p>Loading MDX content...</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div style={{ padding: '20px' }}>
|
|
<h1>MDX Viewer</h1>
|
|
<div style={{
|
|
backgroundColor: '#fef2f2',
|
|
border: '1px solid #fecaca',
|
|
borderRadius: '8px',
|
|
padding: '16px',
|
|
color: '#dc2626'
|
|
}}>
|
|
<h3>Error loading MDX:</h3>
|
|
<p>{error}</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<MDXProvider components={components}>
|
|
<div style={{
|
|
maxWidth: '800px',
|
|
margin: '0 auto',
|
|
padding: '20px',
|
|
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
|
|
}}>
|
|
<div style={{ marginBottom: '32px', textAlign: 'center', position: 'relative' }}>
|
|
<h1>MDX Viewer</h1>
|
|
{reloading && (
|
|
<div style={{
|
|
position: 'absolute',
|
|
top: '0',
|
|
right: '0',
|
|
backgroundColor: '#22c55e',
|
|
color: 'white',
|
|
padding: '4px 8px',
|
|
borderRadius: '4px',
|
|
fontSize: '12px',
|
|
fontWeight: 'bold'
|
|
}}>
|
|
🔄 Reloading...
|
|
</div>
|
|
)}
|
|
</div>
|
|
{Content && <Content />}
|
|
</div>
|
|
</MDXProvider>
|
|
);
|
|
};
|
|
|
|
export default App; |