@state-less/leap-frontend
Version:
A collection of open source fullstack services powered by React Server
269 lines (268 loc) • 12.7 kB
JavaScript
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { Table, TableBody, TableCell, TableHead, TableRow, Link, Box, } from '@mui/material';
import { Link as RouterLink, useLocation } from 'react-router-dom';
import { useContext, useEffect, useMemo, createElement } from 'react';
import { IconButton, List, ListItem, ListItemText } from '@mui/material';
import { ContentCopy } from '@mui/icons-material';
import copy from 'copy-to-clipboard';
import rehypeRaw from 'rehype-raw';
import rehypeHighlight from 'rehype-highlight';
import mermaid from 'mermaid';
import clsx from 'clsx';
import { atom, useAtom } from 'jotai';
import { v4 } from 'uuid';
import { Actions, stateContext } from '../provider/StateProvider.js';
import { wrapPromise } from '../lib/util/SSR.js';
const getChildText = (props) => {
const texts = props.children?.map?.((c) => {
if (typeof c === 'string')
return c;
if (c.type === 'code')
return c.props.children;
}) || [];
return texts.join('');
};
export const FetchSOAnswerById = (id, url) => async () => {
const res = await fetch(`https://api.stackexchange.com/2.3/answers/${id}?order=desc&sort=activity&site=stackoverflow&filter=!nNPvSNdWme&key=${encodeURIComponent('IQOO7kdoZ)ST9J0b)HCfww((')}&client_id=28299`);
const json = await res.json();
const answer = json?.items?.[0];
return `${quote(answer?.body)}\n<sub>- [${answer?.owner?.['display_name']}](${answer?.owner?.link}): ${url}</sub>`;
};
export const FetchTextContent = (url) => async () => {
const res = await fetch(url);
const text = await res.text();
return text;
};
const useFetchAtoms = {};
export const wrappedCache = {};
export const resultCache = typeof window !== 'undefined' ? window.__MD_STATE__ : null;
export const useFetch = (initialValue, fetchFn, cacheKey, { suspend }) => {
const key = useMemo(() => {
return cacheKey || v4();
}, [cacheKey]);
const resultValue = resultCache?.[cacheKey]
? resultCache?.[cacheKey]
: fetchFn === null
? initialValue
: null;
const atm = useFetchAtoms[key] ||
(useFetchAtoms[key] = atom({
loading: fetchFn === null ? 2 : resultCache?.[cacheKey] ? 3 : 0,
error: null,
result: resultValue,
promise: null,
}));
const [state, setState] = useAtom(atm);
useEffect(() => {
if (fetchFn)
return;
setState((state) => ({ ...state, result: initialValue }));
}, [initialValue, setState, fetchFn]);
useEffect(() => {
if (!fetchFn)
return;
if (state?.loading > 0)
return;
setState((state) => ({ ...state, loading: 1 }));
const promise = fetchFn();
promise.then(async (text) => {
setState((state) => ({ ...state, loading: 2, result: text }));
}, (e) => {
setState((state) => ({ ...state, loading: 3, result: null, error: e }));
});
}, [fetchFn, setState, state?.loading]);
if (suspend) {
if (fetchFn === null && resultValue) {
return {
result: resultValue,
loading: 3,
promise: null,
error: null,
};
}
if (!wrappedCache?.[cacheKey] && !resultCache?.[cacheKey]) {
const promise = fetchFn?.();
if (promise) {
wrappedCache[cacheKey] = wrapPromise(promise);
}
}
let cache = wrappedCache;
if (resultCache && resultCache[cacheKey]) {
const result = resultCache[cacheKey];
return {
result,
loading: 3,
promise: wrappedCache[cacheKey],
error: null,
};
}
try {
const result = wrappedCache[cacheKey]?.();
return {
result,
loading: 3,
promise: wrappedCache[cacheKey],
error: null,
};
}
catch (e) {
return { ...state, promise: wrappedCache[cacheKey] };
}
}
return state;
};
const quote = (str) => {
return str
.split('\n')
.map((line) => '> ' + line)
.join('\n');
};
mermaid.initialize({
startOnLoad: true,
theme: 'default',
securityLevel: 'loose',
fontFamily: 'Fira Code',
});
const Mermaid = (props) => {
useEffect(() => {
mermaid.contentLoaded();
}, []);
return _jsx("div", { className: "mermaid", children: props.children });
};
export const Markdown = ({ children, src, disablePadding, optimisticHeight = '0px', small = false, preview = false, center = true, landing = false, errorMD, fetchFn: userFetchFn, cacheKey = src, citeSrc, suspend, }) => {
const { dispatch } = useContext(stateContext);
const { hash } = useLocation();
let fetchFn = userFetchFn;
const fetchSrc = useMemo(() => FetchTextContent(src), [src]);
if (src && !fetchFn) {
fetchFn = fetchSrc;
}
else if (!src && !fetchFn) {
fetchFn = null;
}
let fetched;
const { loading, result, error, promise } = useFetch(children, fetchFn || null, cacheKey || '', { suspend });
if (promise && suspend && loading < 2) {
promise();
}
useEffect(() => {
if (loading > 1 && hash) {
setTimeout(() => {
document
.querySelector(hash)
?.scrollIntoView({ block: 'center', behavior: 'smooth' });
}, 50);
}
}, [loading, hash]);
const headingRenderer = useMemo(() => (props) => {
const { children } = props;
const text = typeof children === 'string' ? children : getChildText(props);
if (typeof text === 'string') {
const anchor = (text || '')
.toLowerCase()
.replace(/[^\w\s]/g, '') // Remove special characters
.replace(/\s+/g, '-'); // Replace spaces with hyphens
return createElement(props?.node?.tagName, { id: anchor || undefined }, children);
}
return createElement(props?.node?.tagName, {}, children);
}, []);
const components = useMemo(() => {
return {
h1: headingRenderer,
h2: headingRenderer,
h3: headingRenderer,
h4: headingRenderer,
h5: headingRenderer,
h6: headingRenderer,
ul: (props) => {
return (_jsx(List, { dense: true, disablePadding: true, children: props.children.map((child, i) => {
if (child === '\n')
return null;
return (_jsx(ListItem, { dense: true, sx: {
py: 0,
my: 1,
borderLeft: '1.5px solid',
borderLeftColor: 'info.main',
}, children: _jsx(ListItemText, { sx: { m: 0 }, primary: child?.props?.children || child }) }, i));
}) }));
},
pre: (props) => {
const language = (props?.children?.props?.className || '-bash').split('-')[1];
if (language === 'github') {
const url = props?.children?.props?.children;
return (_jsx(Markdown, { src: url, center: center, suspend: suspend, landing: landing, preview: preview, children: `Loading Markdown from Github: ${url}` }, url));
}
if (language === 'stackoverflow') {
const url = props?.children?.props?.children;
const id = url.split('/').at(-2);
return (_jsx(Markdown, { center: center, disablePadding: true, fetchFn: FetchSOAnswerById(id, url), cacheKey: id, preview: preview, suspend: suspend, landing: landing, children: `*See this Stackoverflow answer: [${url}](${url})` }, id));
}
if (language === 'mermaid') {
return _jsx(Mermaid, { children: props?.children?.props?.children });
}
const child = Array.isArray(props.children)
? props.children[0]
: props.children;
return (_jsxs(_Fragment, { children: [_jsx(Box, { sx: { width: '100%', display: 'flex' }, children: _jsx(IconButton, { "aria-label": "Copy markdown content.", sx: { ml: 'auto', mb: -7, color: 'white' }, onClick: () => {
copy(props?.children?.props.children);
dispatch({
type: Actions.SHOW_MESSAGE,
value: 'Copied to clipboard',
});
}, children: _jsx(ContentCopy, {}) }) }), _jsx("pre", { children: _jsx("code", { className: clsx('hljs', 'language-' + language), children: child?.props.children }) })] }));
},
a: (props) => {
return (_jsx(Link, { to: props.href, component: RouterLink, sx: { color: 'info.dark' }, onClick: () => {
document
.querySelector(hash)
?.scrollIntoView({ block: 'center', behavior: 'smooth' });
}, children: props.children }));
},
blockquote: (args) => {
return (_jsx(Box, { sx: {
borderLeft: '4px solid',
borderColor: 'primary.main',
}, children: _jsx("blockquote", { ...args }) }));
},
table: (props) => {
return (_jsxs(Table, { children: [_jsx(TableHead, { children: _jsx(TableRow, { children: props.children[0].props.children.props.children?.map((e) => {
return _jsx(TableCell, { children: e });
}) }) }), _jsx(TableBody, { children: [props.children[1].props.children].flat()?.map((row) => {
return (_jsx(TableRow, { children: row.props.children.map((e) => {
return _jsx(TableCell, { children: e }, e);
}) }));
}) })] }));
},
stackoverflow: (props) => {
const { url, children } = props;
if (typeof children === 'string') {
const trimmed = children.trim();
const id = url.split('/').at(-2);
return (_jsx(Markdown, { center: false, disablePadding: true, errorMD: trimmed, fetchFn: FetchSOAnswerById(id, url), cacheKey: id, preview: preview, children: `*See this Stackoverflow answer: [${url}](${url})` }, id));
}
},
github: (props) => {
const { url, children } = props;
const trimmed = typeof children === 'string' ? children.trim() : children;
return (_jsx(Markdown, { src: url, center: false, errorMD: trimmed, preview: preview, suspend: suspend, children: `Loading Markdown from Github: ${url}` }, url));
},
};
}, [dispatch, headingRenderer]);
const md = error ? errorMD : loading < 2 ? children : result;
const cited = src ? `<sub>\- ${src}</sub>` : md;
return (_jsxs("div", { children: [_jsxs("div", { className: clsx('markdown', {
center,
disablePadding,
preview,
landing,
}), style: {
minHeight: optimisticHeight,
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'start',
alignContent: 'center',
}, children: [_jsx("link", { rel: "stylesheet", href: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github-dark.min.css" }), _jsx(ReactMarkdown, { className: clsx({ 'markdown-small': small }), rehypePlugins: [rehypeRaw, rehypeHighlight], remarkPlugins: [remarkGfm], components: components, children: md })] }), src && citeSrc && (_jsx("div", { className: clsx('markdown', { disablePadding: true, preview: true }), children: _jsx(ReactMarkdown, { className: clsx({ 'markdown-small': small }), rehypePlugins: [rehypeRaw, rehypeHighlight], remarkPlugins: [remarkGfm], components: components, children: cited }) }))] }));
};