mirror of
https://github.com/sern-handler/bin
synced 2026-06-06 01:16:52 +00:00
feat: markdown
This commit is contained in:
@@ -17,6 +17,7 @@
|
|||||||
"@mantine/notifications": "^7.4.1",
|
"@mantine/notifications": "^7.4.1",
|
||||||
"@monaco-editor/react": "^4.6.0",
|
"@monaco-editor/react": "^4.6.0",
|
||||||
"@sentry/nextjs": "^7.93.0",
|
"@sentry/nextjs": "^7.93.0",
|
||||||
|
"@uiw/react-md-editor": "3.6.0",
|
||||||
"bottleneck": "^2.19.5",
|
"bottleneck": "^2.19.5",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
@@ -24,10 +25,14 @@
|
|||||||
"lru-cache": "^10.1.0",
|
"lru-cache": "^10.1.0",
|
||||||
"next": "14.0.4",
|
"next": "14.0.4",
|
||||||
"next-nprogress-bar": "^2.1.2",
|
"next-nprogress-bar": "^2.1.2",
|
||||||
|
"next-remove-imports": "^1.0.12",
|
||||||
"postgres": "^3.4.3",
|
"postgres": "^3.4.3",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-icons": "^5.0.1",
|
"react-icons": "^5.0.1",
|
||||||
|
"react-markdown": "^9.0.1",
|
||||||
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
|
"remark-gfm": "^4.0.0",
|
||||||
"util-utils": "^1.0.3",
|
"util-utils": "^1.0.3",
|
||||||
"zod": "^3.22.4",
|
"zod": "^3.22.4",
|
||||||
"zustand": "^4.4.7"
|
"zustand": "^4.4.7"
|
||||||
@@ -36,6 +41,7 @@
|
|||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
|
"@types/react-syntax-highlighter": "^15.5.11",
|
||||||
"drizzle-kit": "^0.20.12",
|
"drizzle-kit": "^0.20.12",
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
"eslint-config-next": "14.0.4",
|
"eslint-config-next": "14.0.4",
|
||||||
|
|||||||
86
src/app/md/page.tsx
Normal file
86
src/app/md/page.tsx
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import "@uiw/react-md-editor/markdown-editor.css";
|
||||||
|
import "@uiw/react-markdown-preview/markdown.css";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import styles from "@/components/CreateSnippet/index.module.css";
|
||||||
|
import {Button, Select, TextInput} from "@mantine/core";
|
||||||
|
import {createSnippet} from "@/components/CreateSnippet/createSnippet";
|
||||||
|
import {notifications} from "@mantine/notifications";
|
||||||
|
import {ICreateApiRequest} from "@/utils/types";
|
||||||
|
import {useUser} from "@clerk/nextjs";
|
||||||
|
import {useRouter} from "next-nprogress-bar";
|
||||||
|
|
||||||
|
const MDEditor = dynamic(
|
||||||
|
() => import("@uiw/react-md-editor"),
|
||||||
|
{ ssr: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const user = useUser().user!
|
||||||
|
const router = useRouter()
|
||||||
|
const [value, setValue] = useState("");
|
||||||
|
const [buttonLoading, setButtonLoading] = useState(false)
|
||||||
|
const [snippet, setSnippet] = useState<ICreateApiRequest>({
|
||||||
|
fileName: '',
|
||||||
|
description: '',
|
||||||
|
authorId: user.id,
|
||||||
|
lang: 'markdown',
|
||||||
|
code: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSnippet({ ...snippet, code: value })
|
||||||
|
}, [value]);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={styles.inputs}>
|
||||||
|
<TextInput
|
||||||
|
placeholder={'The file name of your text'}
|
||||||
|
style={{ flexGrow: .1 }}
|
||||||
|
onChange={(ev) => setSnippet({ ...snippet, fileName: ev.currentTarget.value })}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
placeholder={'Brief description of your text'}
|
||||||
|
style={{ flexGrow: 4 }}
|
||||||
|
onChange={(ev) => setSnippet({ ...snippet, description: ev.currentTarget.value })}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant={'filled'}
|
||||||
|
style={{ flexGrow: .1 }}
|
||||||
|
color={'blue'}
|
||||||
|
loading={buttonLoading}
|
||||||
|
onClick={() => {
|
||||||
|
setButtonLoading(true)
|
||||||
|
createSnippet(snippet).catch(e => {
|
||||||
|
if (e.message === 'fillFields') {
|
||||||
|
notifications.show({
|
||||||
|
title: 'Something went wrong',
|
||||||
|
message: 'Please fill in all the fields',
|
||||||
|
color: 'red',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}).then(id => {
|
||||||
|
if (id) {
|
||||||
|
notifications.show({
|
||||||
|
title: 'Done!',
|
||||||
|
message: 'Now redirecting to the snippet page...',
|
||||||
|
color: 'green',
|
||||||
|
})
|
||||||
|
router.push(`/s/${id}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setButtonLoading(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{/*
|
||||||
|
// @ts-ignore */}
|
||||||
|
<MDEditor value={value} onChange={e => setValue((e!))} height={'60vh'} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,3 +5,12 @@
|
|||||||
.snippetDescription p:last-of-type {
|
.snippetDescription p:last-of-type {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.markdownViewer {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 10px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
@@ -6,6 +6,11 @@ import {redirect} from "next/navigation";
|
|||||||
import {clerkClient} from "@clerk/nextjs";
|
import {clerkClient} from "@clerk/nextjs";
|
||||||
import MonacoEditor from "@/components/MonacoEditor/MonacoEditor";
|
import MonacoEditor from "@/components/MonacoEditor/MonacoEditor";
|
||||||
import {Metadata} from "next";
|
import {Metadata} from "next";
|
||||||
|
import ReactMarkdown from 'react-markdown'
|
||||||
|
import remarkGfm from 'remark-gfm'
|
||||||
|
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||||
|
import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
|
||||||
|
|
||||||
|
|
||||||
export default async function Page({ params }: { params: { id: string } }) {
|
export default async function Page({ params }: { params: { id: string } }) {
|
||||||
const query = await db.query.code.findFirst({
|
const query = await db.query.code.findFirst({
|
||||||
@@ -24,7 +29,41 @@ export default async function Page({ params }: { params: { id: string } }) {
|
|||||||
</div>
|
</div>
|
||||||
<p>Description: {query.description}</p>
|
<p>Description: {query.description}</p>
|
||||||
</div>
|
</div>
|
||||||
<MonacoEditor readOnly={true} defaultValue={query.code} defaultLanguage={query.lang} />
|
{
|
||||||
|
query.lang !== 'markdown' ?
|
||||||
|
<MonacoEditor readOnly={true} defaultValue={query.code} defaultLanguage={query.lang}/> :
|
||||||
|
<div className='markdownViewer'>
|
||||||
|
<ReactMarkdown remarkPlugins={[remarkGfm]} components={{
|
||||||
|
code({node, className, children, ...props}) {
|
||||||
|
const match = /language-(\w+)/.exec(className || '')
|
||||||
|
return match ? (
|
||||||
|
<SyntaxHighlighter
|
||||||
|
style={atomDark}
|
||||||
|
customStyle={{ backgroundColor: '#171717', outline: 'solid' }}
|
||||||
|
codeTagProps={{ className: 'codeHighlighter' }}
|
||||||
|
language={match[1]}
|
||||||
|
// eslint-disable-next-line react/no-children-prop
|
||||||
|
children={String(children).replace(/\n$/, '')}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<code className={className} {...props} style={{
|
||||||
|
fontSize: '1rem',
|
||||||
|
backgroundColor: '#171717',
|
||||||
|
outline: '3px solid #171717'
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</code>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
img(props) {
|
||||||
|
// eslint-disable-next-line jsx-a11y/alt-text
|
||||||
|
return <img {...props} style={{ maxWidth: '100%' }} />
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{query.code}
|
||||||
|
</ReactMarkdown>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {usePathname} from "next/navigation";
|
|||||||
};*/
|
};*/
|
||||||
const links = [
|
const links = [
|
||||||
{ link: '/', label: 'Create' },
|
{ link: '/', label: 'Create' },
|
||||||
|
{ link: '/md', label: 'Markdown' },
|
||||||
{ link: '/me', label: 'My snippets' },
|
{ link: '/me', label: 'My snippets' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ export const ZCreateApiRequest = z.object({
|
|||||||
fileName: z.string(),
|
fileName: z.string(),
|
||||||
description: z.string(),
|
description: z.string(),
|
||||||
authorId: z.string(),
|
authorId: z.string(),
|
||||||
lang: z.enum(['javascript', 'typescript']),
|
lang: z.enum(['javascript', 'typescript', 'markdown']),
|
||||||
code: z.string(),
|
code: z.string(),
|
||||||
});
|
});
|
||||||
export type ICreateApiRequest = z.infer<typeof ZCreateApiRequest>;
|
export type ICreateApiRequest = z.infer<typeof ZCreateApiRequest>;
|
||||||
Reference in New Issue
Block a user