From 0144bfc9cc6c616a00a8171f3950a75ec948427e Mon Sep 17 00:00:00 2001 From: jackyzha0 Date: Sun, 11 Apr 2021 10:27:27 -0700 Subject: base next refactor --- frontend/src/App.js | 5 +- frontend/src/components/PasteInfo.js | 6 +- frontend/src/components/hooks/shared.js | 74 ------------ frontend/src/components/hooks/useFetchPaste.js | 67 ----------- frontend/src/components/modals/PasteModal.js | 8 +- frontend/src/components/pages/NewPaste.js | 150 ------------------------ frontend/src/components/pages/Raw.js | 16 --- frontend/src/components/pages/ViewPaste.js | 65 ---------- frontend/src/components/renderers/Code.js | 2 +- frontend/src/components/renderers/InlineCode.js | 2 +- frontend/src/css/index.css | 150 ------------------------ frontend/src/http/shared.js | 74 ++++++++++++ frontend/src/http/useFetchPaste.js | 69 +++++++++++ frontend/src/pages/[hash].js | 65 ++++++++++ frontend/src/pages/_app.js | 23 ++++ frontend/src/pages/_document.js | 30 +++++ frontend/src/pages/index.js | 150 ++++++++++++++++++++++++ frontend/src/pages/raw/index.js | 14 +++ frontend/src/theme/GlobalStyle.js | 10 +- frontend/src/theme/ThemeProvider.js | 4 +- frontend/src/theme/style.css | 150 ++++++++++++++++++++++++ 21 files changed, 593 insertions(+), 541 deletions(-) delete mode 100644 frontend/src/components/hooks/shared.js delete mode 100644 frontend/src/components/hooks/useFetchPaste.js delete mode 100644 frontend/src/components/pages/NewPaste.js delete mode 100644 frontend/src/components/pages/Raw.js delete mode 100644 frontend/src/components/pages/ViewPaste.js delete mode 100644 frontend/src/css/index.css create mode 100644 frontend/src/http/shared.js create mode 100644 frontend/src/http/useFetchPaste.js create mode 100644 frontend/src/pages/[hash].js create mode 100644 frontend/src/pages/_app.js create mode 100644 frontend/src/pages/_document.js create mode 100644 frontend/src/pages/index.js create mode 100644 frontend/src/pages/raw/index.js create mode 100644 frontend/src/theme/style.css (limited to 'frontend/src') diff --git a/frontend/src/App.js b/frontend/src/App.js index 0a47baa..f00397f 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -13,10 +13,7 @@ import ThemeProvider from './theme/ThemeProvider' import GlobalStyle from './theme/GlobalStyle' import {Watermark} from "./components/Watermark"; -const Main = styled.div` - margin-top: 10vh; - padding: 0 20vw 30px 20vw; -` + const GetPasteWithParam = () => { let { hash } = useParams(); diff --git a/frontend/src/components/PasteInfo.js b/frontend/src/components/PasteInfo.js index 6ab5b19..2f912fb 100644 --- a/frontend/src/components/PasteInfo.js +++ b/frontend/src/components/PasteInfo.js @@ -1,8 +1,8 @@ import React from 'react'; import styled from 'styled-components' -import { useHistory } from 'react-router-dom'; import { Theme } from './Inputs' import {Button} from "./Common/Button"; +import {useRouter} from "next/router"; const Bold = styled.span` font-weight: 700 @@ -28,10 +28,10 @@ const Flex = styled.div` ` const PasteInfo = (props) => { - const history = useHistory(); + const router = useRouter() const redirRaw = () => { const redirUrl = `/raw/${props.hash}` - history.push(redirUrl); + router.push(redirUrl); } const renderable = () => { diff --git a/frontend/src/components/hooks/shared.js b/frontend/src/components/hooks/shared.js deleted file mode 100644 index 00d41e9..0000000 --- a/frontend/src/components/hooks/shared.js +++ /dev/null @@ -1,74 +0,0 @@ -import axios from 'axios'; - -// uncomment for local dev -// const base = `http://localhost:8080/api` -const base = `https://api.ctrl-v.app/api` -export function fetchPaste(hash, pass = "") { - const serverURL = `${base}/${hash}` - - if (pass === "") { - return axios.get(serverURL) - } else { - const bodyFormData = new FormData(); - bodyFormData.set('password', pass); - return axios({ - method: 'post', - url: `${base}/${hash}`, - data: bodyFormData, - headers: { 'Content-Type': 'multipart/form-data' }, - }) - } -} - -export function newPaste(paste) { - const {title, content, language, pass, expiry} = paste - const bodyFormData = new FormData(); - bodyFormData.set('title', title); - bodyFormData.set('content', content); - bodyFormData.set('language', language); - bodyFormData.set('password', pass); - bodyFormData.set('expiry', parseExpiry(expiry)); - - return axios({ - method: 'post', - url: base, - data: bodyFormData, - headers: { 'Content-Type': 'multipart/form-data' }, - }) -} - -export function parseExpiry(e) { - var cur = new Date(); - var inSeconds = 0 - switch (e) { - case '5 years': - inSeconds = 600 * 6 * 24 * 7 * 4 * 12 * 5 - break; - case '1 year': - inSeconds = 600 * 6 * 24 * 7 * 4 * 12 - break; - case '1 month': - inSeconds = 600 * 6 * 24 * 7 * 4 - break; - case '1 day': - inSeconds = 600 * 6 * 24 - break; - case '1 hour': - inSeconds = 600 * 6 - break; - case '10 min': - inSeconds = 600 - break; - case '1 week': - default: - inSeconds = 600 * 6 * 24 * 7 - break; - } - return new Date(cur.getTime() + inSeconds * 1000).toISOString(); -} - -export function fmtDateStr(dateString) { - const d = new Date(dateString) - const options = { hour: '2-digit', minute: '2-digit', year: 'numeric', month: 'long', day: 'numeric' } - return d.toLocaleDateString("en-US", options).toLocaleLowerCase() -} \ No newline at end of file diff --git a/frontend/src/components/hooks/useFetchPaste.js b/frontend/src/components/hooks/useFetchPaste.js deleted file mode 100644 index c394f5b..0000000 --- a/frontend/src/components/hooks/useFetchPaste.js +++ /dev/null @@ -1,67 +0,0 @@ -import {useEffect, useState} from 'react' -import {fetchPaste, fmtDateStr} from './shared' -import {LANGS} from "../renderers/Code"; - -export default (id) => { - const [loading, setLoading] = useState(true) - const [err, setErr] = useState() - const [requiresAuth, setRequiresAuth] = useState(false) - const [validPass, setValidPass] = useState(false) - const [result, setResult] = useState({ - title: 'fetching paste...', - content: '', - language: LANGS.detect, - expiry: '', - }) - - const handleErr = error => { - const resp = error.response - - // network err - if (!resp) { - setErr(error.toString()) - return - } - - // password protected - if (resp.status === 401) { - setRequiresAuth(true) - return - } - - // catch all - const errTxt = `${resp.status}: ${resp.data}` - setErr(errTxt) - } - - // callback to try verifying with password - const getWithPassword = (password, errorCallback) => { - fetchPaste(id, password) - .then(resp => { - setValidPass(true) - setStateFromData(resp.data) - }) - .catch(e => errorCallback(e.response.data)) - } - - - const setStateFromData = (data) => { - document.title = data.title - setResult({ - title: data.title, - content: data.content, - language: data.language, - expiry: fmtDateStr(data.expiry) - }) - } - - // initial fetch - useEffect(() => { - fetchPaste(id) - .then(resp => setStateFromData(resp.data)) - .catch(handleErr) - .finally(() => setLoading(false)) - }, [id]) - - return { loading, err, requiresAuth, validPass, getWithPassword, result } -} \ No newline at end of file diff --git a/frontend/src/components/modals/PasteModal.js b/frontend/src/components/modals/PasteModal.js index e7dbed2..a0fd309 100644 --- a/frontend/src/components/modals/PasteModal.js +++ b/frontend/src/components/modals/PasteModal.js @@ -1,21 +1,21 @@ import React from 'react'; import Modal from 'react-modal'; import {Form, ModalHeader, modalStyles} from './shared' -import { useHistory } from 'react-router-dom'; +import { useRouter } from 'next/router' import { Text } from '../Inputs' import { useClipboard } from 'use-clipboard-copy'; import {Button} from "../Common/Button"; const PasteModal = (props) => { - const history = useHistory(); - const fullURL = `${window.location.origin}/${props.hash}`; + const fullURL = `https://ctrl-v.app/${props.hash}`; const clipboard = useClipboard({ copiedTimeout: 3000 }); Modal.setAppElement('body'); + const router = useRouter() const redir = (e) => { e.preventDefault(); const redirUrl = `/${props.hash}` - history.push(redirUrl); + router.push(redirUrl); } return ( diff --git a/frontend/src/components/pages/NewPaste.js b/frontend/src/components/pages/NewPaste.js deleted file mode 100644 index 19161da..0000000 --- a/frontend/src/components/pages/NewPaste.js +++ /dev/null @@ -1,150 +0,0 @@ -import React, { useEffect, useState, useRef } from 'react'; -import { Text, Code } from '../Inputs' -import OptionsContainer from '../Options' -import Error from '../Err' -import PasteModal from '../modals/PasteModal' -import styled from 'styled-components' -import CodeRenderer from '../renderers/Code' -import Latex from '../renderers/Latex' -import Markdown from '../renderers/Markdown' -import {Button, SubmitButton} from "../Common/Button"; -import {newPaste} from "../hooks/shared"; - -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 = - - if (isPreview) { - var preview - switch (language) { - case 'latex': - preview = - - - - break - case 'markdown': - preview = - - - - break - default: - preview = - - } - - return ( - - - {pasteInput} - - - {preview} - - - ); - } else { - return ( - pasteInput - ); - } - } - - return ( -
- - {setTitle(e.target.value)}} - value={title} - autoFocus - maxLength="100" - id="titleInput" /> - {renderPreview()} - { setPass(e.target.value) }} - onLangChange={(e) => { setLanguage(e.target.value) }} - onExpiryChange={(e) => { setExpiry(e.target.value) }} /> -
- - {language !== 'detect' && } -
-
- - - ); -} - -export default NewPaste \ No newline at end of file diff --git a/frontend/src/components/pages/Raw.js b/frontend/src/components/pages/Raw.js deleted file mode 100644 index 23ef6bf..0000000 --- a/frontend/src/components/pages/Raw.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import styled from 'styled-components' -import {CodeLike} from "../Common/mixins"; -import useFetchPaste from "../hooks/useFetchPaste"; - -const RawText = styled.pre` - ${CodeLike} - padding: 0 1em; -` - -const Raw = ({hash}) => { - const { err, result } = useFetchPaste(hash) - return {result?.content || err} -} - -export default Raw \ No newline at end of file diff --git a/frontend/src/components/pages/ViewPaste.js b/frontend/src/components/pages/ViewPaste.js deleted file mode 100644 index bc61314..0000000 --- a/frontend/src/components/pages/ViewPaste.js +++ /dev/null @@ -1,65 +0,0 @@ -import React, { useEffect, useState, useRef } from 'react'; -import Error from '../Err'; -import { Text } from '../Inputs'; -import CodeRenderer from '../renderers/Code' -import PasteInfo from '../PasteInfo'; -import PasswordModal from '../modals/PasswordModal' -import RenderDispatch from '../renderers/RenderDispatch' -import useFetchPaste from "../hooks/useFetchPaste"; - -const ViewPaste = (props) => { - const { err, requiresAuth, validPass, getWithPassword, result } = useFetchPaste(props.hash) - const {content, language, expiry, title} = result ?? {} - const [theme, setTheme] = useState('atom'); - const [isRenderMode, setIsRenderMode] = useState(false); - const [enteredPass, setEnteredPass] = useState(''); - const ErrorLabelRef = useRef(null); - - if (err) { - ErrorLabelRef.current.showMessage(err, -1) - } - - useEffect(() => { - setIsRenderMode(language === 'latex' || language === 'markdown') - }, [language]) - - function getDisplay() { - return isRenderMode ? : - } - - return ( -
- setEnteredPass(e.target.value)} - validateCallback={getWithPassword} /> - - {getDisplay()} - setIsRenderMode(!isRenderMode)} - isRenderMode={isRenderMode} - onChange={(e) => setTheme(e.target.value)} - err={} - /> -
- ); -} - -export default ViewPaste \ No newline at end of file diff --git a/frontend/src/components/renderers/Code.js b/frontend/src/components/renderers/Code.js index 29531fc..85fd5ab 100644 --- a/frontend/src/components/renderers/Code.js +++ b/frontend/src/components/renderers/Code.js @@ -1,7 +1,7 @@ import React from 'react'; import SyntaxHighlighter from 'react-syntax-highlighter'; import virtualizedRenderer from 'react-syntax-highlighter-virtualized-renderer'; -import { atomOneLight, ascetic, atomOneDark, dracula, ocean } from 'react-syntax-highlighter/dist/esm/styles/hljs'; +import { atomOneLight, ascetic, atomOneDark, dracula, ocean } from 'react-syntax-highlighter/dist/cjs/styles/hljs'; import styled from 'styled-components' import {Border, CodeLike, DropShadow, Rounded} from "../Common/mixins"; diff --git a/frontend/src/components/renderers/InlineCode.js b/frontend/src/components/renderers/InlineCode.js index 44a3f58..2156503 100644 --- a/frontend/src/components/renderers/InlineCode.js +++ b/frontend/src/components/renderers/InlineCode.js @@ -1,6 +1,6 @@ import React from 'react'; import SyntaxHighlighter from 'react-syntax-highlighter'; -import { atomOneDark } from 'react-syntax-highlighter/dist/esm/styles/hljs'; +import { atomOneDark } from 'react-syntax-highlighter/dist/cjs/styles/hljs'; import { LANGS } from './Code' const MarkdownCodeRenderer = ({language, value}) => { diff --git a/frontend/src/css/index.css b/frontend/src/css/index.css deleted file mode 100644 index 0340813..0000000 --- a/frontend/src/css/index.css +++ /dev/null @@ -1,150 +0,0 @@ -@media all and (max-width: 1000px) { - .lt-content-column { - padding: 0 calc(5vw + 1em) 0 5vw !important; - } -} - -form { - width: 100%; -} - -textarea, input[type=text], input[type=password], .Dropdown-root { - width: 100%; - font-family: 'JetBrains Mono', monospace; - font-size: 0.8em; - padding: calc(0.8em - 1px); - border-radius: 3px; - border: 1px solid #565656; - outline: none; - margin: 1.7em 0; -} - -/* fix weird symbol height in render mode */ -.large-op { - transform: translateY(-0.55em); -} - -.small-op { - transform: translateY(-0.1em); -} - -code, pre { - background: #00000000; - font-family: 'JetBrains Mono', monospace; - padding: initial; - border-radius: 3px; - outline: none; -} - -.Dropdown-root { - cursor: pointer; -} - -.Dropdown-root:hover, .Dropdown-root.is-open { - opacity: 1; -} - -.Dropdown-root + label { - top: 0.5em; - opacity: 0; -} - -.Dropdown-root.is-open + label, .Dropdown-root:hover + label { - opacity: 1; - top: -0.1em; -} - -.Dropdown-placeholder { - width: 5.5em; -} - -.Dropdown-menu { - border-top: 1px solid #111111; - margin-top: 0.5em; - bottom: auto; -} - -.Dropdown-option { - margin-top: 0.5em; - transition: all 0.5s cubic-bezier(.25,.8,.25,1); -} - -.Dropdown-option:hover { - font-weight: 700; - opacity: 0.4; -} - -textarea, input[type=text], input[type=password], .Dropdown-root { - opacity: 0.5; - transition: opacity 0.5s cubic-bezier(.25,.8,.25,1); -} - -textarea:focus, input[type=text]:focus, input[type=password]:focus { - opacity: 1; -} - -input[type=password] { - font-weight: 700; -} - -textarea { - height: max(40vh, 100%); - resize: vertical; - min-height: 40vh; -} - -a { - color: #111111; -} - -input[type=submit], button[type=submit] { - font-family: 'JetBrains Mono', serif; - font-weight: 700; - color: #faf9f5; - background-color: #111111; - padding: 0.8em 2em; - margin: 2em 0; - outline: 0; -} - -button[type=button] { - font-family: 'JetBrains Mono', serif; - font-weight: 700; - width: 8em; - padding: calc(0.8em - 1px) 1.5em; - border-radius: 3px; - color: #111111; - background-color: #faf9f5; - border: 1px solid #565656; - outline: none; - margin: 2em 2em; -} - - -/* fixing markdown renderer */ -.md h3 { - font-weight: bold; -} - -.md hr { - border-top: 1px solid #000; - border-style: solid; -} - -.md code { - background: #00000008; - font-size: 0.8em; -} - -.md pre { - padding: 0.7em; - background: #00000008; -} - -.md pre > code { - background: none; -} - -.md table { - width: 100%; -} \ No newline at end of file diff --git a/frontend/src/http/shared.js b/frontend/src/http/shared.js new file mode 100644 index 0000000..00d41e9 --- /dev/null +++ b/frontend/src/http/shared.js @@ -0,0 +1,74 @@ +import axios from 'axios'; + +// uncomment for local dev +// const base = `http://localhost:8080/api` +const base = `https://api.ctrl-v.app/api` +export function fetchPaste(hash, pass = "") { + const serverURL = `${base}/${hash}` + + if (pass === "") { + return axios.get(serverURL) + } else { + const bodyFormData = new FormData(); + bodyFormData.set('password', pass); + return axios({ + method: 'post', + url: `${base}/${hash}`, + data: bodyFormData, + headers: { 'Content-Type': 'multipart/form-data' }, + }) + } +} + +export function newPaste(paste) { + const {title, content, language, pass, expiry} = paste + const bodyFormData = new FormData(); + bodyFormData.set('title', title); + bodyFormData.set('content', content); + bodyFormData.set('language', language); + bodyFormData.set('password', pass); + bodyFormData.set('expiry', parseExpiry(expiry)); + + return axios({ + method: 'post', + url: base, + data: bodyFormData, + headers: { 'Content-Type': 'multipart/form-data' }, + }) +} + +export function parseExpiry(e) { + var cur = new Date(); + var inSeconds = 0 + switch (e) { + case '5 years': + inSeconds = 600 * 6 * 24 * 7 * 4 * 12 * 5 + break; + case '1 year': + inSeconds = 600 * 6 * 24 * 7 * 4 * 12 + break; + case '1 month': + inSeconds = 600 * 6 * 24 * 7 * 4 + break; + case '1 day': + inSeconds = 600 * 6 * 24 + break; + case '1 hour': + inSeconds = 600 * 6 + break; + case '10 min': + inSeconds = 600 + break; + case '1 week': + default: + inSeconds = 600 * 6 * 24 * 7 + break; + } + return new Date(cur.getTime() + inSeconds * 1000).toISOString(); +} + +export function fmtDateStr(dateString) { + const d = new Date(dateString) + const options = { hour: '2-digit', minute: '2-digit', year: 'numeric', month: 'long', day: 'numeric' } + return d.toLocaleDateString("en-US", options).toLocaleLowerCase() +} \ No newline at end of file diff --git a/frontend/src/http/useFetchPaste.js b/frontend/src/http/useFetchPaste.js new file mode 100644 index 0000000..ba482f9 --- /dev/null +++ b/frontend/src/http/useFetchPaste.js @@ -0,0 +1,69 @@ +import {useEffect, useState} from 'react' +import {fetchPaste, fmtDateStr} from './shared' +import {LANGS} from "../components/renderers/Code"; + +const useFetchPaste = (id) => { + const [loading, setLoading] = useState(true) + const [err, setErr] = useState() + const [requiresAuth, setRequiresAuth] = useState(false) + const [validPass, setValidPass] = useState(false) + const [result, setResult] = useState({ + title: 'fetching paste...', + content: '', + language: LANGS.detect, + expiry: '', + }) + + const handleErr = error => { + const resp = error.response + + // network err + if (!resp) { + setErr(error.toString()) + return + } + + // password protected + if (resp.status === 401) { + setRequiresAuth(true) + return + } + + // catch all + const errTxt = `${resp.status}: ${resp.data}` + setErr(errTxt) + } + + // callback to try verifying with password + const getWithPassword = (password, errorCallback) => { + fetchPaste(id, password) + .then(resp => { + setValidPass(true) + setStateFromData(resp.data) + }) + .catch(e => errorCallback(e.response.data)) + } + + + const setStateFromData = (data) => { + document.title = data.title + setResult({ + title: data.title, + content: data.content, + language: data.language, + expiry: fmtDateStr(data.expiry) + }) + } + + // initial fetch + useEffect(() => { + fetchPaste(id) + .then(resp => setStateFromData(resp.data)) + .catch(handleErr) + .finally(() => setLoading(false)) + }, [id]) + + return { loading, err, requiresAuth, validPass, getWithPassword, result } +} + +export default useFetchPaste \ No newline at end of file diff --git a/frontend/src/pages/[hash].js b/frontend/src/pages/[hash].js new file mode 100644 index 0000000..27f808b --- /dev/null +++ b/frontend/src/pages/[hash].js @@ -0,0 +1,65 @@ +import React, { useEffect, useState, useRef } from 'react'; +import Error from '../components/Err'; +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 useFetchPaste from "../http/useFetchPaste"; + +const ViewPaste = (props) => { + const { err, requiresAuth, validPass, getWithPassword, result } = useFetchPaste(props.hash) + const {content, language, expiry, title} = result ?? {} + const [theme, setTheme] = useState('atom'); + const [isRenderMode, setIsRenderMode] = useState(false); + const [enteredPass, setEnteredPass] = useState(''); + const ErrorLabelRef = useRef(null); + + if (err) { + ErrorLabelRef.current.showMessage(err, -1) + } + + useEffect(() => { + setIsRenderMode(language === 'latex' || language === 'markdown') + }, [language]) + + function getDisplay() { + return isRenderMode ? : + } + + return ( +
+ setEnteredPass(e.target.value)} + validateCallback={getWithPassword} /> + + {getDisplay()} + setIsRenderMode(!isRenderMode)} + isRenderMode={isRenderMode} + onChange={(e) => setTheme(e.target.value)} + err={} + /> +
+ ); +} + +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..7e99f01 --- /dev/null +++ b/frontend/src/pages/_app.js @@ -0,0 +1,23 @@ +import React from 'react' +import ThemeProvider from "../theme/ThemeProvider"; +import GlobalStyle from "../theme/GlobalStyle"; +import '../theme/style.css'; +import {Watermark} from "../components/Watermark"; +import styled from "styled-components"; + +const Main = styled.div` + margin-top: 10vh; + padding: 0 20vw 30px 20vw; +` + +const App = ({ Component, pageProps }) => ( + + + +
+ +
+
+) + +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(), + }) + + 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..141ebac --- /dev/null +++ b/frontend/src/pages/index.js @@ -0,0 +1,150 @@ +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"; + +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 = + + if (isPreview) { + var preview + switch (language) { + case 'latex': + preview = + + + + break + case 'markdown': + preview = + + + + break + default: + preview = + + } + + return ( + + + {pasteInput} + + + {preview} + + + ); + } else { + return ( + pasteInput + ); + } + } + + return ( +
+ + {setTitle(e.target.value)}} + value={title} + autoFocus + maxLength="100" + id="titleInput" /> + {renderPreview()} + { setPass(e.target.value) }} + onLangChange={(e) => { setLanguage(e.target.value) }} + onExpiryChange={(e) => { setExpiry(e.target.value) }} /> +
+ + {language !== 'detect' && } +
+
+ + + ); +} + +export default NewPaste \ No newline at end of file diff --git a/frontend/src/pages/raw/index.js b/frontend/src/pages/raw/index.js new file mode 100644 index 0000000..86db3d4 --- /dev/null +++ b/frontend/src/pages/raw/index.js @@ -0,0 +1,14 @@ +import React from 'react'; +import useFetchPaste from "../../http/useFetchPaste"; +import { useRouter } from 'next/router' + + +export default (req, res) => { + const router = useRouter() + const { hash } = router.query + const { err, result } = useFetchPaste(hash) + res.statusCode = 200 + res.json({ + text: 'Hello World! This is the Next.js starter kit :D', + }) +} \ No newline at end of file diff --git a/frontend/src/theme/GlobalStyle.js b/frontend/src/theme/GlobalStyle.js index 9fe80a5..94e4b57 100644 --- a/frontend/src/theme/GlobalStyle.js +++ b/frontend/src/theme/GlobalStyle.js @@ -2,10 +2,10 @@ import { createGlobalStyle } from 'styled-components' export default createGlobalStyle` body { - margin: 0; - padding: 0; - background: ${(p) => p.theme.colors.background}; - font-family: 'JetBrains Mono', monospace; - color: ${(p) => p.theme.colors.text}; + margin: 0; + padding: 0; + background: ${(p) => p.theme.colors.background}; + font-family: 'JetBrains Mono', monospace; + color: ${(p) => p.theme.colors.text}; } ` \ No newline at end of file diff --git a/frontend/src/theme/ThemeProvider.js b/frontend/src/theme/ThemeProvider.js index d9edcb0..942bc40 100644 --- a/frontend/src/theme/ThemeProvider.js +++ b/frontend/src/theme/ThemeProvider.js @@ -10,4 +10,6 @@ const theme = { }, } -export default ({ children }) => {children} \ No newline at end of file +const Provider = ({ children }) => {children} + +export default Provider \ No newline at end of file diff --git a/frontend/src/theme/style.css b/frontend/src/theme/style.css new file mode 100644 index 0000000..10b955b --- /dev/null +++ b/frontend/src/theme/style.css @@ -0,0 +1,150 @@ +@media all and (max-width: 1000px) { + .lt-content-column { + padding: 0 calc(5vw + 1em) 0 5vw !important; + } +} + +form { + width: 100%; +} + +textarea, input[type=text], input[type=password], .Dropdown-root { + width: 100%; + font-family: 'JetBrains Mono', monospace; + font-size: 0.8em; + padding: calc(0.8em - 1px); + border-radius: 3px; + border: 1px solid #565656; + outline: none; + margin: 1.7em 0; +} + +/* fix weird symbol height in render mode */ +.large-op { + transform: translateY(-0.55em); +} + +.small-op { + transform: translateY(-0.1em); +} + +code, pre { + background: #00000000; + font-family: 'JetBrains Mono', monospace; + padding: initial; + border-radius: 3px; + outline: none; +} + +.Dropdown-root { + cursor: pointer; +} + +.Dropdown-root:hover, .Dropdown-root.is-open { + opacity: 1; +} + +.Dropdown-root + label { + top: 0.5em; + opacity: 0; +} + +.Dropdown-root.is-open + label, .Dropdown-root:hover + label { + opacity: 1; + top: -0.1em; +} + +.Dropdown-placeholder { + width: 5.5em; +} + +.Dropdown-menu { + border-top: 1px solid #111111; + margin-top: 0.5em; + bottom: auto; +} + +.Dropdown-option { + margin-top: 0.5em; + transition: all 0.5s cubic-bezier(.25,.8,.25,1); +} + +.Dropdown-option:hover { + font-weight: 700; + opacity: 0.4; +} + +textarea, input[type=text], input[type=password], .Dropdown-root { + opacity: 0.5; + transition: opacity 0.5s cubic-bezier(.25,.8,.25,1); +} + +textarea:focus, input[type=text]:focus, input[type=password]:focus { + opacity: 1; +} + +input[type=password] { + font-weight: 700; +} + +textarea { + height: max(40vh, 100%); + resize: vertical; + min-height: 40vh; +} + +a { + color: #111111; +} + +input[type=submit], button[type=submit] { + font-family: 'JetBrains Mono', serif; + font-weight: 700; + color: #faf9f5; + background-color: #111111; + padding: 0.8em 2em; + margin: 2em 0; + outline: 0; +} + +button[type=button] { + font-family: 'JetBrains Mono', serif; + font-weight: 700; + width: 8em; + padding: calc(0.8em - 1px) 1.5em; + border-radius: 3px; + color: #111111; + background-color: #faf9f5; + border: 1px solid #565656; + outline: none; + margin: 2em 2em; +} + + +/* fixing markdown renderer */ +.md h3 { + font-weight: bold; +} + +.md hr { + border-top: 1px solid #000; + border-style: solid; +} + +.md code { + background: #00000008; + font-size: 0.8em; +} + +.md pre { + padding: 0.7em; + background: #00000008; +} + +.md pre > code { + background: none; +} + +.md table { + width: 100%; +} \ No newline at end of file -- cgit v1.2.3