feat: render tex (katex) and markdown
This commit is contained in:
parent
cfdcf05135
commit
950018673f
7 changed files with 1129 additions and 26 deletions
|
@ -52,22 +52,27 @@
|
|||
"exiftool-vendored": "^18.6.0",
|
||||
"fflate": "^0.7.4",
|
||||
"find-my-way": "^7.3.1",
|
||||
"katex": "^0.16.3",
|
||||
"minio": "^7.0.32",
|
||||
"ms": "canary",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"next": "^13.0.0",
|
||||
"otplib": "^12.0.1",
|
||||
"prisma": "^4.5.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"qrcode": "^1.5.1",
|
||||
"react": "^18.2.0",
|
||||
"react-chartjs-2": "^4.3.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-feather": "^2.0.10",
|
||||
"react-markdown": "^8.0.4",
|
||||
"recoil": "^0.7.6",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"sharp": "^0.31.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cookie": "^0.5.1",
|
||||
"@types/katex": "^0.14.0",
|
||||
"@types/minio": "^7.0.14",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/node": "^18.11.7",
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import { Button, Group, NumberInput, PasswordInput, Select, Tabs, Title, Tooltip } from '@mantine/core';
|
||||
import { Alert, Button, Card, Container, Group, Select, Tabs, Title } from '@mantine/core';
|
||||
import { useClipboard } from '@mantine/hooks';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { showNotification, updateNotification } from '@mantine/notifications';
|
||||
import { Prism } from '@mantine/prism';
|
||||
import CodeInput from 'components/CodeInput';
|
||||
import { ClockIcon, ImageIcon, TypeIcon, UploadIcon } from 'components/icons';
|
||||
import { ImageIcon, TypeIcon, UploadIcon } from 'components/icons';
|
||||
import KaTeX from 'components/render/KaTeX';
|
||||
import Markdown from 'components/render/Markdown';
|
||||
import PrismCode from 'components/render/PrismCode';
|
||||
import exts from 'lib/exts';
|
||||
import { userSelector } from 'lib/recoil/user';
|
||||
import { expireReadToDate } from 'lib/utils/client';
|
||||
import { Language } from 'prism-react-renderer';
|
||||
import { useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import showFilesModal from './showFilesModal';
|
||||
|
@ -24,6 +25,9 @@ export default function Text() {
|
|||
|
||||
const [options, setOpened, OptionsModal] = useUploadOptions();
|
||||
|
||||
const shouldRenderMarkdown = lang === 'md';
|
||||
const shouldRenderTex = lang === 'tex';
|
||||
|
||||
const handleUpload = async () => {
|
||||
const file = new File([value], 'text.' + lang);
|
||||
|
||||
|
@ -90,13 +94,26 @@ export default function Text() {
|
|||
</Tabs.Panel>
|
||||
|
||||
<Tabs.Panel mt='sm' value='preview'>
|
||||
<Prism
|
||||
sx={(t) => ({ height: '80vh', backgroundColor: t.colors.dark[8] })}
|
||||
withLineNumbers
|
||||
language={lang as Language}
|
||||
>
|
||||
{value}
|
||||
</Prism>
|
||||
{shouldRenderMarkdown || shouldRenderTex ? (
|
||||
<>
|
||||
<Alert color='blue' variant='outline' sx={{ width: '100%' }}>
|
||||
You are viewing a rendered version of your code
|
||||
</Alert>
|
||||
|
||||
<Container>
|
||||
<Card p='md' my='sm'>
|
||||
{shouldRenderMarkdown && <Markdown code={value} />}
|
||||
{shouldRenderTex && <KaTeX code={value} />}
|
||||
</Card>
|
||||
</Container>
|
||||
</>
|
||||
) : (
|
||||
<PrismCode
|
||||
sx={(t) => ({ height: '100vh', backgroundColor: t.colors.dark[8] })}
|
||||
code={value}
|
||||
ext={lang}
|
||||
/>
|
||||
)}
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
|
||||
|
|
51
src/components/render/KaTeX.tsx
Normal file
51
src/components/render/KaTeX.tsx
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { Alert } from '@mantine/core';
|
||||
import katex, { ParseError } from 'katex';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import 'katex/dist/katex.min.css';
|
||||
|
||||
const sanitize = (str: string) => {
|
||||
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
};
|
||||
|
||||
export default function KaTeX({ code, ...props }) {
|
||||
const [rendered, setRendered] = useState('');
|
||||
const [error, setError] = useState<JSX.Element>();
|
||||
|
||||
const renderError = (error: ParseError | TypeError) => {
|
||||
return (
|
||||
<Alert title={error.name} color='red'>
|
||||
{sanitize(error.message)}
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
const html = katex.renderToString(code, {
|
||||
displayMode: true,
|
||||
throwOnError: true,
|
||||
errorColor: '#f44336',
|
||||
});
|
||||
|
||||
setRendered(html);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
setError(renderError(e));
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}, [rendered, error, code]);
|
||||
|
||||
if (error) return error;
|
||||
|
||||
return (
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: rendered,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
31
src/components/render/Markdown.tsx
Normal file
31
src/components/render/Markdown.tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { Code } from '@mantine/core';
|
||||
import { Prism } from '@mantine/prism';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
|
||||
export default function Markdown({ code, ...props }) {
|
||||
return (
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{
|
||||
code({ node, inline, className, children, ...props }) {
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
return !inline && match ? (
|
||||
// @ts-ignore
|
||||
<Prism language={match[1]} {...props}>
|
||||
{String(children).replace(/\n$/, '')}
|
||||
</Prism>
|
||||
) : (
|
||||
<Code {...props}>{children}</Code>
|
||||
);
|
||||
},
|
||||
img({ node, ...props }) {
|
||||
return <img {...props} style={{ maxWidth: '100%' }} />;
|
||||
},
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{code}
|
||||
</ReactMarkdown>
|
||||
);
|
||||
}
|
53
src/components/render/PrismCode.tsx
Normal file
53
src/components/render/PrismCode.tsx
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { Prism } from '@mantine/prism';
|
||||
import { Prism as PrismLib } from 'prism-react-renderer';
|
||||
import exts from 'lib/exts';
|
||||
|
||||
(typeof window === 'undefined' ? global : window).Prism = PrismLib;
|
||||
|
||||
require('prismjs/components/prism-markdown');
|
||||
require('prismjs/components/prism-css');
|
||||
require('prismjs/components/prism-javascript');
|
||||
require('prismjs/components/prism-typescript');
|
||||
require('prismjs/components/prism-java');
|
||||
require('prismjs/components/prism-python');
|
||||
require('prismjs/components/prism-ruby');
|
||||
require('prismjs/components/prism-bash');
|
||||
require('prismjs/components/prism-php');
|
||||
require('prismjs/components/prism-perl');
|
||||
require('prismjs/components/prism-sql');
|
||||
require('prismjs/components/prism-xml-doc');
|
||||
require('prismjs/components/prism-yaml');
|
||||
require('prismjs/components/prism-c');
|
||||
require('prismjs/components/prism-cpp');
|
||||
require('prismjs/components/prism-csharp');
|
||||
require('prismjs/components/prism-go');
|
||||
require('prismjs/components/prism-docker');
|
||||
require('prismjs/components/prism-toml');
|
||||
require('prismjs/components/prism-ini');
|
||||
require('prismjs/components/prism-batch');
|
||||
require('prismjs/components/prism-latex');
|
||||
require('prismjs/components/prism-r');
|
||||
require('prismjs/components/prism-lua');
|
||||
require('prismjs/components/prism-powershell');
|
||||
require('prismjs/components/prism-rust');
|
||||
require('prismjs/components/prism-swift');
|
||||
require('prismjs/components/prism-scss');
|
||||
require('prismjs/components/prism-json');
|
||||
require('prismjs/components/prism-less');
|
||||
require('prismjs/components/prism-scala');
|
||||
require('prismjs/components/prism-kotlin');
|
||||
require('prismjs/components/prism-visual-basic');
|
||||
require('prismjs/components/prism-vim');
|
||||
|
||||
export default function PrismCode({ code, ext, ...props }) {
|
||||
return (
|
||||
<Prism
|
||||
sx={(t) => ({ height: '100vh', backgroundColor: t.colors.dark[8] })}
|
||||
withLineNumbers
|
||||
language={exts[ext]?.toLowerCase()}
|
||||
{...props}
|
||||
>
|
||||
{code}
|
||||
</Prism>
|
||||
);
|
||||
}
|
|
@ -1,27 +1,78 @@
|
|||
import { Prism } from '@mantine/prism';
|
||||
import { Box, Button, Card, Container } from '@mantine/core';
|
||||
import KaTeX from 'components/render/KaTeX';
|
||||
import Markdown from 'components/render/Markdown';
|
||||
import PrismCode from 'components/render/PrismCode';
|
||||
import config from 'lib/config';
|
||||
import exts from 'lib/exts';
|
||||
import prisma from 'lib/prisma';
|
||||
import { checkPassword } from 'lib/util';
|
||||
import { streamToString } from 'lib/utils/streams';
|
||||
import { GetServerSideProps } from 'next';
|
||||
import Head from 'next/head';
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function Code({ code, id, title }) {
|
||||
export default function Code({ code, id, title, render, renderType }) {
|
||||
const full_title = `${title} - Code (${id})`;
|
||||
|
||||
const [overrideRender, setOverrideRender] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{full_title}</title>
|
||||
</Head>
|
||||
{render && (
|
||||
<Box
|
||||
sx={(t) => ({
|
||||
backgroundColor: t.colorScheme === 'dark' ? t.colors.dark[6] : t.colors.gray[0],
|
||||
})}
|
||||
py={5}
|
||||
px='md'
|
||||
>
|
||||
{renderType === 'markdown' && (
|
||||
<span>
|
||||
You are {overrideRender ? 'not' : 'now'} viewing a rendered version of the markdown file.
|
||||
</span>
|
||||
)}
|
||||
{renderType === 'tex' && (
|
||||
<span>
|
||||
You are {overrideRender ? 'not' : 'now'} viewing a KaTeX rendering version of the tex file.
|
||||
</span>
|
||||
)}
|
||||
<Button mx='md' onClick={() => setOverrideRender(!overrideRender)} compact variant='light'>
|
||||
View {overrideRender ? 'rendered' : 'raw'}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Prism
|
||||
sx={(t) => ({ height: '100vh', backgroundColor: t.colors.dark[8] })}
|
||||
withLineNumbers
|
||||
language={exts[id.split('.').pop()]?.toLowerCase()}
|
||||
>
|
||||
{code}
|
||||
</Prism>
|
||||
{renderType === 'markdown' && !overrideRender && (
|
||||
<Container p='md'>
|
||||
<Markdown code={code} />
|
||||
</Container>
|
||||
)}
|
||||
|
||||
{renderType === 'tex' && !overrideRender && (
|
||||
<Container p='md'>
|
||||
<Card p={2}>
|
||||
<KaTeX code={code} />
|
||||
</Card>
|
||||
</Container>
|
||||
)}
|
||||
|
||||
{!render && (
|
||||
<PrismCode
|
||||
sx={(t) => ({ height: '100vh', backgroundColor: t.colors.dark[8] })}
|
||||
code={code}
|
||||
ext={id.split('.').pop()}
|
||||
/>
|
||||
)}
|
||||
|
||||
{render && overrideRender && (
|
||||
<PrismCode
|
||||
sx={(t) => ({ height: '100vh', backgroundColor: t.colors.dark[8] })}
|
||||
code={code}
|
||||
ext={id.split('.').pop()}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -56,11 +107,23 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
|
|||
|
||||
context.res.setHeader('Cache-Control', 'public, max-age=2628000, stale-while-revalidate=86400');
|
||||
|
||||
let renderType;
|
||||
|
||||
if (file.file.endsWith('.md')) {
|
||||
renderType = 'markdown';
|
||||
} else if (file.file.endsWith('.tex')) {
|
||||
renderType = 'tex';
|
||||
} else {
|
||||
renderType = null;
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
code: await streamToString(data),
|
||||
id: context.params.id as string,
|
||||
title: config.website.title,
|
||||
render: file.file.endsWith('.md') || file.file.endsWith('.tex'),
|
||||
renderType,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue