diff options
| author | Jacky Zhao <[email protected]> | 2021-04-11 15:06:48 -0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2021-04-11 15:06:48 -0700 |
| commit | 82bda5ee85efbd2eae25427a839529d5e230eeaa (patch) | |
| tree | 1f7a88938fd6664a9a048503a5a78d010e3db1e2 /frontend/src/pages | |
| parent | Merge pull request #72 from jackyzha0/no-ip (diff) | |
| parent | readd preset height (diff) | |
| download | ctrl-v-82bda5ee85efbd2eae25427a839529d5e230eeaa.tar.xz ctrl-v-82bda5ee85efbd2eae25427a839529d5e230eeaa.zip | |
Merge pull request #74 from jackyzha0/next-refactor
Diffstat (limited to 'frontend/src/pages')
| -rw-r--r-- | frontend/src/pages/[hash].js | 81 | ||||
| -rw-r--r-- | frontend/src/pages/_app.js | 46 | ||||
| -rw-r--r-- | frontend/src/pages/_document.js | 30 | ||||
| -rw-r--r-- | frontend/src/pages/index.js | 163 | ||||
| -rw-r--r-- | frontend/src/pages/raw/[hash].js | 26 |
5 files changed, 346 insertions, 0 deletions
diff --git a/frontend/src/pages/[hash].js b/frontend/src/pages/[hash].js new file mode 100644 index 0000000..f281621 --- /dev/null +++ b/frontend/src/pages/[hash].js @@ -0,0 +1,81 @@ +import React, { useEffect, useState } from 'react'; +import { Text } from '../components/Inputs'; +import CodeRenderer from '../components/renderers/Code' +import PasteInfo from '../components/PasteInfo'; +import PasswordModal from '../components/modals/PasswordModal' +import RenderDispatch from '../components/renderers/RenderDispatch' +import {Watermark} from "../components/Watermark"; +import { useRouter } from 'next/router' +import resolvePaste from "../http/resolvePaste"; +import NextHead from "../components/NextHead"; + +export async function getServerSideProps(ctx) { + const data = await resolvePaste(ctx.params.hash) + return { props: { ...data } } +} + +const ViewPaste = ({data, unauthorized, error}) => { + const router = useRouter() + const { hash } = router.query + const [theme, setTheme] = useState('atom'); + const [isRenderMode, setIsRenderMode] = useState(false); + const [enteredPass, setEnteredPass] = useState(''); + const [correctPass, setCorrectPass] = useState(!unauthorized); + const [clientData, setClientData] = useState(data) + const {content, language, expiry, title} = clientData; + + const getWithPassword = (password, errorCallback) => { + resolvePaste(hash, password) + .then(resp => { + setCorrectPass(true) + setClientData(resp.data) + }) + .catch(e => errorCallback(e.response.data)) + } + + useEffect(() => { + setIsRenderMode(language === 'latex' || language === 'markdown') + }, [language]) + + function getDisplay() { + return isRenderMode ? <RenderDispatch + language={language} + content={content} + /> : <CodeRenderer + content={content} + lang={language} + theme={theme} + id="pasteInput" /> + } + + return ( + <div> + {!error && <NextHead data={data} />} + <PasswordModal + hasPass={unauthorized} + validPass={correctPass} + value={enteredPass} + onChange={(e) => setEnteredPass(e.target.value)} + validateCallback={getWithPassword} /> + <Text + label="title" + value={title} + id="titleInput" + readOnly /> + {getDisplay()} + <PasteInfo + hash={hash} + lang={language} + theme={theme} + expiry={expiry} + toggleRenderCallback={() => setIsRenderMode(!isRenderMode)} + isRenderMode={isRenderMode} + onChange={(e) => setTheme(e.target.value)} + err={unauthorized ? '' : error} + /> + <Watermark/> + </div> + ); +} + +export default ViewPaste
\ No newline at end of file diff --git a/frontend/src/pages/_app.js b/frontend/src/pages/_app.js new file mode 100644 index 0000000..115f47f --- /dev/null +++ b/frontend/src/pages/_app.js @@ -0,0 +1,46 @@ +import React from 'react' +import ThemeProvider from "../theme/ThemeProvider"; +import GlobalStyle from "../theme/GlobalStyle"; +import styled from "styled-components"; +import Head from "next/head"; + +const Main = styled.div` + margin-top: 10vh; + padding: 0 20vw 30px 20vw; +` + +const App = ({ Component, pageProps }) => ( + <ThemeProvider> + <GlobalStyle /> + <Head> + <meta charSet="utf-8"/> + <meta name="viewport" content="width=device-width, initial-scale=1"/> + <meta name="theme-color" content="#ffffff"/> + <meta + name="description" + content="a modern, open-source pastebin with latex and markdown rendering support" + /> + <link rel="icon" href="/favicon.png" /> + <link rel="preconnect" href="https://fonts.gstatic.com" /> + <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;700&display=swap" + rel="stylesheet" /> + <title>ctrl-v | a modern, open-source pastebin</title> + <script async src="https://www.googletagmanager.com/gtag/js?id=G-DE1TYY2F24" /> + <script + dangerouslySetInnerHTML={{ + __html: ` + window.dataLayer = window.dataLayer || []; + function gtag() {dataLayer.push(arguments);} + gtag('js', new Date()); + gtag('config', 'G-DE1TYY2F24'); + ` + }} + /> + </Head> + <Main id="appElement"> + <Component {...pageProps} /> + </Main> + </ThemeProvider> +) + +export default App
\ No newline at end of file diff --git a/frontend/src/pages/_document.js b/frontend/src/pages/_document.js new file mode 100644 index 0000000..0cbd6a3 --- /dev/null +++ b/frontend/src/pages/_document.js @@ -0,0 +1,30 @@ +import Document from 'next/document' +import { ServerStyleSheet } from 'styled-components' + +export default class StyledDocument extends Document { + static async getInitialProps(ctx) { + const sheet = new ServerStyleSheet() + const originalRenderPage = ctx.renderPage + + try { + ctx.renderPage = () => + originalRenderPage({ + enhanceApp: (App) => (props) => + sheet.collectStyles(<App {...props} />), + }) + + const initialProps = await Document.getInitialProps(ctx) + return { + ...initialProps, + styles: ( + <> + {initialProps.styles} + {sheet.getStyleElement()} + </> + ), + } + } finally { + sheet.seal() + } + } +}
\ No newline at end of file diff --git a/frontend/src/pages/index.js b/frontend/src/pages/index.js new file mode 100644 index 0000000..867a074 --- /dev/null +++ b/frontend/src/pages/index.js @@ -0,0 +1,163 @@ +import React, { useEffect, useState, useRef } from 'react'; +import { Text, Code } from '../components/Inputs' +import OptionsContainer from '../components/Options' +import Error from '../components/Err' +import PasteModal from '../components/modals/PasteModal' +import styled from 'styled-components' +import CodeRenderer from '../components/renderers/Code' +import Latex from '../components/renderers/Latex' +import Markdown from '../components/renderers/Markdown' +import {Button, SubmitButton} from "../components/Common/Button"; +import {newPaste} from "../http/shared"; +import {Watermark} from "../components/Watermark"; +import {Labelled} from "../components/decorators/Labelled"; + +const Container = styled.form` + width: 100%; +` + +const Flex = styled.div` + display: flex; + flex-direction: row; +` + +const FlexLeft = styled.div` + flex: 0 0 calc(50% - 1em - 2px); +` + +const FlexRight = styled.div` + flex: 0 0 50%; + max-width: calc(50% - 1em + 2px); + margin-left: 2em; +` + +const PreviewWrapper = styled.div` + margin: 2em; +` + +const NewPaste = () => { + const [title, setTitle] = useState(''); + const [content, setContent] = useState(''); + const [pass, setPass] = useState(''); + const [language, setLanguage] = useState('detect'); + const [expiry, setExpiry] = useState(''); + const [hash, setHash] = useState(''); + const [isPreview, setIsPreview] = useState(false); + const ErrorLabel = useRef(null); + + useEffect(() => { + document.title = title === "" ? `ctrl-v` : `ctrl-v | ${title}`; + }, [title]) + + function handleSubmit(e) { + e.preventDefault(); + + // prevent resubmission + if (!hash) { + newPaste({title, content, language, pass, expiry}) + .then(resp => {setHash(resp.data.hash)}) + .catch((error) => { + const resp = error.response + + // some weird err (e.g. network) + if (!resp) { + ErrorLabel.current.showMessage(error) + return + } + + // some weird err + const errTxt = `${resp.status}: ${resp.data}` + ErrorLabel.current.showMessage(errTxt) + }); + } + } + + function renderPreview() { + const pasteInput = <Code + setContentCallback={setContent} + content={content} + maxLength="100000" /> + + if (isPreview) { + var preview + switch (language) { + case 'latex': + preview = + <PreviewWrapper> + <Latex + content={content} /> + </PreviewWrapper> + break + case 'markdown': + preview = + <PreviewWrapper className='md' > + <Markdown + content={content} /> + </PreviewWrapper> + break + default: + preview = + <CodeRenderer + lang={language} + theme='atom' + content={content} /> + } + + return ( + <Flex> + <FlexLeft> + <Labelled label="content"> + {pasteInput} + </Labelled> + </FlexLeft> + <FlexRight className='preview' > + <Labelled label="preview"> + {preview} + </Labelled> + </FlexRight> + </Flex> + ); + } else { + return ( + <Labelled label="content"> + {pasteInput} + </Labelled> + ); + } + } + + return ( + <Container onSubmit={handleSubmit}> + <PasteModal hash={hash} /> + <Text + label="title" + onChange={(e) => {setTitle(e.target.value)}} + value={title} + autoFocus + maxLength="100" + id="titleInput" /> + {renderPreview()} + <OptionsContainer + pass={pass} + expiry={expiry} + lang={language} + onPassChange={(e) => { setPass(e.target.value) }} + onLangChange={(e) => { setLanguage(e.target.value) }} + onExpiryChange={(e) => { setExpiry(e.target.value) }} /> + <div> + <SubmitButton type="submit" value="new paste" /> + {language !== 'detect' && <Button + secondary + type="button" + onClick={() => setIsPreview(!isPreview)}> + preview + </Button>} + </div> + <br /> + <Error ref={ErrorLabel} /> + <Watermark/> + </Container> + ); +} + +export default NewPaste
\ No newline at end of file diff --git a/frontend/src/pages/raw/[hash].js b/frontend/src/pages/raw/[hash].js new file mode 100644 index 0000000..9edde36 --- /dev/null +++ b/frontend/src/pages/raw/[hash].js @@ -0,0 +1,26 @@ +import React from 'react'; +import resolvePaste from "../../http/resolvePaste"; +import {CodeLike} from "../../components/Common/mixins"; +import styled from 'styled-components' +import NextHead from "../../components/NextHead"; + +const RawText = styled.pre` + ${CodeLike} + padding: 0 1em; +` + +export async function getServerSideProps(ctx) { + const data = await resolvePaste(ctx.params.hash) + return { props: { ...data } } +} + +const Raw = ({error, data}) => { + return <> + {!error && <NextHead data={data} />} + <RawText> + {data?.content || error} + </RawText> + </> +} + +export default Raw
\ No newline at end of file |