import { useParse } from 'myst-util-to-react'; import { VFile } from 'vfile'; import type { VFileMessage } from 'vfile-message'; import yaml from 'js-yaml'; import type { References } from '@curvenote/site-common'; import type { NodeRenderer } from 'myst-util-to-react'; import React, { useEffect, useRef, useState } from 'react'; import classnames from 'classnames'; import ExclamationIcon from '@heroicons/react/outline/ExclamationIcon'; import ExclamationCircleIcon from '@heroicons/react/outline/ExclamationCircleIcon'; import InformationCircleIcon from '@heroicons/react/outline/InformationCircleIcon'; import { CopyIcon } from './components/CopyIcon'; import { CodeBlock } from './code'; import { ReferencesProvider } from '@curvenote/ui-providers'; async function parse(text: string) { // Ensure that any imports from myst are async and scoped to this function const { MyST, unified, visit } = await import('mystjs'); const { mathPlugin, footnotesPlugin, keysPlugin, basicTransformationsPlugin, enumerateTargetsPlugin, resolveReferencesPlugin, ReferenceState, getFrontmatter, } = await import('myst-transforms'); const { default: mystToTex } = await import('myst-to-tex'); const myst = new MyST(); const mdast = myst.parse(text); // For the mdast that we show, duplicate, strip positions and dump to yaml const mdastPre = JSON.parse(JSON.stringify(mdast)); visit(mdastPre, (n) => delete n.position); const mdastString = yaml.dump(mdastPre); const htmlString = myst.renderMdast(mdastPre); const file = new VFile(); const references = { cite: { order: [], data: {} }, footnotes: {}, }; const { frontmatter } = getFrontmatter(mdast, { removeYaml: true, removeHeading: false }); const state = new ReferenceState({ numbering: frontmatter.numbering, file }); unified() .use(basicTransformationsPlugin) .use(mathPlugin, { macros: frontmatter?.math ?? {} }) // This must happen before enumeration, as it can add labels .use(enumerateTargetsPlugin, { state }) .use(footnotesPlugin, { references }) .use(keysPlugin) .use(resolveReferencesPlugin, { state }) .runSync(mdast as any, file); const tex = unified() .use(mystToTex) .stringify(mdast as any).result as string; const content = useParse(mdast as any); return { yaml: mdastString, references: { ...references, article: mdast } as References, html: htmlString, tex, content, warnings: file.messages, }; } export function MySTRenderer({ value }: { value: string }) { const area = useRef(null); const [text, setText] = useState(value.trim()); const [references, setReferences] = useState({}); const [mdastYaml, setYaml] = useState('Loading...'); const [html, setHtml] = useState('Loading...'); const [tex, setTex] = useState('Loading...'); const [warnings, setWarnings] = useState([]); const [content, setContent] = useState(

{value}

); const [previewType, setPreviewType] = useState('DEMO'); useEffect(() => { const ref = { current: true }; parse(text).then((result) => { if (!ref.current) return; setYaml(result.yaml); setReferences(result.references); setHtml(result.html); setTex(result.tex); setContent(result.content); setWarnings(result.warnings); }); return () => { ref.current = false; }; }, [text]); useEffect(() => { if (!area.current) return; area.current.style.height = 'auto'; // for the scroll area in the next step! area.current.style.height = `${area.current.scrollHeight}px`; }, [text]); return (
{/* The `exclude-from-outline` class is excluded from the document outline */}
{['DEMO', 'AST', 'HTML', 'LaTeX'].map((show) => ( ))}
{previewType === 'DEMO' && ( {content} )} {previewType === 'AST' && } {previewType === 'HTML' && } {previewType === 'LaTeX' && }
{previewType === 'DEMO' && warnings.length > 0 && (
{warnings.map((m) => (
{m.fatal === true && } {m.fatal === false && } {m.fatal === null && } {m.ruleId || m.source}: {m.message}
))}
)}
); } const MystNodeRenderer: NodeRenderer = (node) => { return ; }; const MYST_RENDERERS = { myst: MystNodeRenderer, }; export default MYST_RENDERERS;