aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--frontend/package.json3
-rw-r--r--frontend/public/index.html4
-rw-r--r--frontend/src/App.js58
-rw-r--r--frontend/src/components/App.js80
-rw-r--r--frontend/src/components/Common/Button.js32
-rw-r--r--frontend/src/components/Common/Input.js9
-rw-r--r--frontend/src/components/Common/mixins.js52
-rw-r--r--frontend/src/components/Footer.js9
-rw-r--r--frontend/src/components/Inputs.js200
-rw-r--r--frontend/src/components/Inputs/Code.js54
-rw-r--r--frontend/src/components/Inputs/Dropdown.js120
-rw-r--r--frontend/src/components/Inputs/Password.js14
-rw-r--r--frontend/src/components/Inputs/Text.js25
-rw-r--r--frontend/src/components/Inputs/index.js6
-rw-r--r--frontend/src/components/NewPaste.js33
-rw-r--r--frontend/src/components/Options.js13
-rw-r--r--frontend/src/components/PasteInfo.js30
-rw-r--r--frontend/src/components/ViewPaste.js40
-rw-r--r--frontend/src/components/Watermark.js32
-rw-r--r--frontend/src/components/decorators/CharLimit.js20
-rw-r--r--frontend/src/components/decorators/FloatingLabel.js32
-rw-r--r--frontend/src/components/decorators/Labelled.js30
-rw-r--r--frontend/src/components/hooks/useFetchPaste.js5
-rw-r--r--frontend/src/components/modals/PasswordModal.js41
-rw-r--r--frontend/src/components/modals/PasteModal.js66
-rw-r--r--frontend/src/components/modals/shared.js18
-rw-r--r--frontend/src/components/renderers/Code.js84
-rw-r--r--frontend/src/components/renderers/Raw.js6
-rw-r--r--frontend/src/components/renderers/RenderDispatch.js9
-rw-r--r--frontend/src/css/index.css41
-rw-r--r--frontend/src/index.js3
-rw-r--r--frontend/src/theme/GlobalStyle.js11
-rw-r--r--frontend/src/theme/ThemeProvider.js13
-rw-r--r--frontend/yarn.lock86
34 files changed, 691 insertions, 588 deletions
diff --git a/frontend/package.json b/frontend/package.json
index 376660a..4fc1fe7 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -10,7 +10,6 @@
"d3-scale": "^3.2.1",
"elliptic": "^6.5.3",
"file-saver": "^2.0.2",
- "indent-textarea": "^2.0.2",
"rc-slider": "^9.2.4",
"react": "^16.13.1",
"react-component-export-image": "^0.1.4",
@@ -23,7 +22,9 @@
"react-render-html": "^0.6.0",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
+ "react-simple-code-editor": "^0.11.0",
"react-syntax-highlighter": "^12.2.1",
+ "react-syntax-highlighter-virtualized-renderer": "^1.1.0",
"serialize-javascript": "^4.0.0",
"styled-components": "^5.1.0",
"use-clipboard-copy": "^0.1.2"
diff --git a/frontend/public/index.html b/frontend/public/index.html
index 0a92619..8ca24a5 100644
--- a/frontend/public/index.html
+++ b/frontend/public/index.html
@@ -9,8 +9,8 @@
content="a modern, open-source pastebin with latex and markdown rendering support"
/>
<link rel="icon" href="icon.png">
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/jackyzha0/[email protected]/src/lite.css">
- <link href="https://fonts.googleapis.com/css2?family=Lora:wght@400;600&family=Roboto+Mono&display=swap" rel="stylesheet">
+ <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>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-DE1TYY2F24"></script>
diff --git a/frontend/src/App.js b/frontend/src/App.js
new file mode 100644
index 0000000..a8da469
--- /dev/null
+++ b/frontend/src/App.js
@@ -0,0 +1,58 @@
+import React from 'react';
+import NewPaste from './components/NewPaste'
+import ViewPaste from './components/ViewPaste'
+import styled from 'styled-components'
+import {
+ BrowserRouter as Router,
+ Switch,
+ Route,
+ useParams
+} from "react-router-dom";
+import Raw from './components/renderers/Raw'
+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();
+ return <ViewPaste hash = {hash} />;
+}
+
+const GetRawWithParam = () => {
+ let { hash } = useParams();
+ return <Raw hash={hash} />;
+}
+
+const App = () => {
+ return (
+ <ThemeProvider>
+ <GlobalStyle />
+ <Router>
+ <Switch>
+ <Route path="/raw/:hash"><GetRawWithParam /></Route>
+ <Route>
+ <Watermark/>
+ <Main id="appElement">
+ <Switch>
+ <Route path="/:hash">
+ <GetPasteWithParam />
+ </Route>
+ <Route path="/">
+ <NewPaste />
+ </Route>
+ </Switch>
+ </Main>
+ </Route>
+ </Switch>
+ </Router>
+ </ThemeProvider>
+ );
+}
+
+
+export default App;
diff --git a/frontend/src/components/App.js b/frontend/src/components/App.js
deleted file mode 100644
index 543cd46..0000000
--- a/frontend/src/components/App.js
+++ /dev/null
@@ -1,80 +0,0 @@
-import React from 'react';
-import NewPaste from './NewPaste'
-import ViewPaste from './ViewPaste'
-import Footer from './Footer'
-import styled from 'styled-components'
-import {
- BrowserRouter as Router,
- Switch,
- Route,
- Link,
- useParams
-} from "react-router-dom";
-import Raw from './renderers/Raw'
-
-const SpacedTitle = styled.div`
- margin-top: 10vh
-`
-
-const Desc = () => {
- return (
- <h3>a modern, <a href="https://github.com/jackyzha0/ctrl-v" target="_blank" rel="noopener noreferrer">open-source</a> pastebin with latex and markdown rendering support</h3>
- );
-}
-
-const GetPasteWithParam = () => {
- let { hash } = useParams();
-
- return (
- <ViewPaste hash = {hash} />
- );
-}
-
-const GetRawWithParam = () => {
- let { hash } = useParams();
-
- return (
- <Raw hash={hash} />
- );
-}
-
-const App = () => {
- return (
- <Router>
- <Switch>
- <Route path="/raw/:hash"
- children={<GetRawWithParam />}
- />
- <Route>
- <div className="lt-content-column">
- <SpacedTitle>
- <nav>
- <h1 className="mainLogo">
- <span role="img" aria-label="clipboard">📋&nbsp;</span>
- <Link to="/">ctrl-v</Link>
- </h1>
- <Desc />
- </nav>
- </SpacedTitle>
-
- <main id="appElement">
- <Switch>
- <Route path="/:hash"
- children={<GetPasteWithParam />}
- />
- <Route path="/">
- <NewPaste />
- </Route>
- </Switch>
- </main>
-
- <Footer />
- </div>
- </Route>
- </Switch>
- </Router>
- );
-}
-
-
-export default App;
diff --git a/frontend/src/components/Common/Button.js b/frontend/src/components/Common/Button.js
new file mode 100644
index 0000000..59e148b
--- /dev/null
+++ b/frontend/src/components/Common/Button.js
@@ -0,0 +1,32 @@
+import styled, {css} from 'styled-components'
+import {Border, ButtonLike, DropShadow, Rounded} from "./mixins";
+
+const Base = css`
+ ${DropShadow}
+ ${Rounded}
+ ${ButtonLike}
+ margin-right: 2em;
+ height: calc(16px + 1.6em);
+ cursor: pointer;
+`
+
+const Primary = css`
+ ${Base};
+ border: none;
+ color: ${p => p.theme.colors.background};
+ background-color: ${p => p.theme.colors.text};
+`
+const Secondary = css`
+ ${Base};
+ ${Border};
+ color: ${p => p.theme.colors.text};
+ background-color: ${p => p.theme.colors.background};
+`
+
+export const Button = styled.button`
+ ${p => p.secondary ? css`${Secondary}` : css`${Primary}` }
+`
+
+export const SubmitButton = styled.input`
+ ${Primary}
+` \ No newline at end of file
diff --git a/frontend/src/components/Common/Input.js b/frontend/src/components/Common/Input.js
new file mode 100644
index 0000000..e000cfb
--- /dev/null
+++ b/frontend/src/components/Common/Input.js
@@ -0,0 +1,9 @@
+import styled from 'styled-components'
+import {Border, DropShadow, InputLike, Rounded} from "./mixins";
+
+export const Input = styled.input`
+ ${Border}
+ ${Rounded}
+ ${DropShadow}
+ ${InputLike}
+` \ No newline at end of file
diff --git a/frontend/src/components/Common/mixins.js b/frontend/src/components/Common/mixins.js
new file mode 100644
index 0000000..ff2759f
--- /dev/null
+++ b/frontend/src/components/Common/mixins.js
@@ -0,0 +1,52 @@
+import {css} from 'styled-components';
+
+export const DropShadow = css`
+ box-shadow: 0 14px 28px rgba(27, 33, 48,0.06), 0 10px 10px rgba(27, 33, 48,0.02);
+`
+
+export const Hover = css`
+ opacity: 0.5;
+ transition: all 0.5s cubic-bezier(.25,.8,.25,1);
+
+ & ~ pre {
+ transition: all 0.5s cubic-bezier(.25,.8,.25,1);
+ opacity: 0.5;
+ }
+
+ &:focus, &:hover, &:focus span, &:focus ~ pre {
+ opacity: 1;
+ }
+`
+
+export const Rounded = css`
+ border-radius: 3px;
+`
+
+export const Border = css`
+ border: 1px solid ${p => p.theme.colors.border};
+`
+
+export const InputLike = css`
+ ${Hover}
+ font-family: 'JetBrains Mono', monospace;
+ width: 100%;
+ font-size: 0.8em;
+ padding: 0.6em;
+ outline: none;
+ margin: 1.7em 0;
+`
+
+export const CodeLike = css`
+ font-family: JetBrains Mono !important;
+ font-size: 13px !important;
+ line-height: 1.6em !important;
+ white-space: pre-wrap;
+`
+
+export const ButtonLike = css`
+ font-family: 'JetBrains Mono', serif;
+ font-weight: 700;
+ padding: 0.6em 1.5em;
+ margin: 2em 0;
+ outline: 0;
+` \ No newline at end of file
diff --git a/frontend/src/components/Footer.js b/frontend/src/components/Footer.js
index c7878e5..4afc7ba 100644
--- a/frontend/src/components/Footer.js
+++ b/frontend/src/components/Footer.js
@@ -2,7 +2,12 @@ import React from 'react';
import styled from 'styled-components'
const SpacedFooter = styled.div`
- margin: 2em 0;
+ & > p {
+ font-size: 0.8em;
+ }
+ & a {
+ color: ${p => p.theme.colors.text};
+ }
`
const Link = (props) => {
@@ -14,7 +19,7 @@ const Link = (props) => {
const Footer = () => {
return (
<SpacedFooter>
- © 2020 &mdash; <Link link="https://jzhao.xyz/" name="jacky" />, <Link link="https://ryanmehri.tech/" name="ryan" />
+ <p>(c) 2020 // <Link link="https://jzhao.xyz/" name="jacky" />, <Link link="https://ryanmehri.tech/" name="ryan" /></p>
</SpacedFooter>
);
}
diff --git a/frontend/src/components/Inputs.js b/frontend/src/components/Inputs.js
deleted file mode 100644
index 3adad92..0000000
--- a/frontend/src/components/Inputs.js
+++ /dev/null
@@ -1,200 +0,0 @@
-import React, {useEffect, useRef} from 'react';
-import CharLimit from './decorators/CharLimit'
-import styled from 'styled-components'
-import FloatingLabel from './decorators/FloatingLabel'
-import Dropdown from 'react-dropdown';
-import { LANGS, THEMES } from './renderers/Code';
-import * as indentation from 'indent-textarea';
-
-const RelPositioning = styled.div`
- position: relative;
- height: calc(100% - 4em);
-`
-
-const FlexChild = styled.div`
- display: block;
- margin-left: 2em;
-`
-
-const TitleInput = (props) => {
- return (
- <RelPositioning>
- <FloatingLabel
- label="title"
- id={props.id}
- value={props.value} />
- <input
- name="title"
- readOnly={props.readOnly}
- className="lt-shadow"
- placeholder="Title"
- id={props.id}
- type="text"
- autoFocus
- autoComplete="off"
- onChange={props.onChange}
- value={props.value} />
- <CharLimit
- content={props.value}
- maxLength={props.maxLength} />
- </RelPositioning>
- );
-}
-
-const PasteInput = ({content, ...props}) => {
- const textInput = useRef(null);
-
- useEffect(() => {
- indentation.watch(textInput.current);
- }, [textInput])
-
- return (
- <RelPositioning>
- <FloatingLabel
- label="content"
- id={props.id}
- value={content} />
- <textarea
- name="content"
- readOnly={props.readOnly}
- placeholder="Paste your text here"
- value={content}
- id={props.id}
- ref={textInput}
- required
- onChange={props.onChange}
- className="lt-shadow" />
- <CharLimit
- content={content}
- maxLength={props.maxLength} />
- </RelPositioning>
- );
-}
-
-const PassInput = (props) => {
- return (
- <FlexChild>
- <RelPositioning>
- <FloatingLabel
- label="password"
- id={props.id}
- value={props.value} />
- <input
- name="pass"
- className="lt-shadow"
- placeholder="password"
- type="password"
- autoComplete="off"
- onChange={props.onChange}
- value={props.value}
- id={props.id} />
- </RelPositioning>
- </FlexChild>
- );
-}
-
-const GenericDropdown = (props) => {
- function _onSelect(option) {
- props.onChange({
- target: {
- name: props.label,
- value: option.label
- }
- });
- }
-
- return (
- <FlexChild>
- <Dropdown
- options={props.options}
- onChange={_onSelect}
- value={props.value}
- placeholder={props.placeholder}
- id={props.id} />
- <FloatingLabel
- label={props.label}
- id={props.id}
- value={props.value} />
- </FlexChild>
- );
-}
-
-const ExpiryInput = (props) => {
- const options = [
- '5 years',
- '1 year',
- '1 month',
- '1 week',
- '1 day',
- '1 hour',
- '10 min',
- ];
-
- return (
- <GenericDropdown
- {...props}
- options={options}
- placeholder='1 week'
- label='expiry'
- />
- );
-}
-
-const LangInput = (props) => {
- const options = Object.entries(LANGS).map((key, _) => {
- return {
- 'value': key[1],
- 'label': key[0]
- }
- })
-
- return (
- <GenericDropdown
- {...props}
- options={options}
- placeholder={LANGS.raw}
- label='language'
- />
- );
-}
-
-const ThemeInput = (props) => {
- const options = Object.entries(THEMES).map((key, _) => {
- return {
- 'value': key[1],
- 'label': key[0]
- }
- })
-
- return (
- <GenericDropdown
- {...props}
- options={options}
- placeholder={'atom'}
- label='theme'
- />
- );
-}
-
-const PasteURLInput = ({id, fullURL}) => {
- return (
- <FlexChild>
- <RelPositioning>
- <FloatingLabel
- label="url"
- id={id}
- value={id} />
- <input
- name="paste_url"
- className="lt-shadow"
- type="text"
- readOnly
- autoFocus
- id={id}
- value={fullURL} />
- </RelPositioning>
- </FlexChild>
- );
-}
-
-export { TitleInput, PasteInput, PassInput, ExpiryInput, PasteURLInput, LangInput, ThemeInput } \ No newline at end of file
diff --git a/frontend/src/components/Inputs/Code.js b/frontend/src/components/Inputs/Code.js
new file mode 100644
index 0000000..adb1536
--- /dev/null
+++ b/frontend/src/components/Inputs/Code.js
@@ -0,0 +1,54 @@
+import React from "react";
+import styled from 'styled-components'
+import CharLimit from "../decorators/CharLimit";
+import Editor from 'react-simple-code-editor';
+import {Highlighter} from "../renderers/Code";
+import {CodeLike, Hover} from "../Common/mixins";
+
+const Wrapper = styled.div`
+ position: relative;
+`
+const EditorWrapper = styled(Editor)`
+ overflow: visible !important;
+
+ & > * {
+ padding: 0 !important;
+ width: 100%;
+ }
+
+ & pre, & code, & > textarea {
+ ${CodeLike}
+ min-height: 40vh;
+ }
+
+ & pre {
+ z-index: -1 !important;
+ }
+
+ & > textarea {
+ ${Hover}
+ padding: 0.6em !important;
+ outline: none !important;
+ }
+`
+
+export const Code = ({content, id, readOnly, setContentCallback, ...props}) => {
+ return (
+ <Wrapper>
+ <EditorWrapper
+ name="content"
+ readOnly={readOnly}
+ placeholder="Paste your text here"
+ value={content}
+ id={id}
+ required
+ highlight={code => <Highlighter theme="atom">{code}</Highlighter> }
+ onValueChange={code => setContentCallback(code)}
+ padding={15}
+ />
+ <CharLimit
+ content={content}
+ maxLength={props.maxLength} />
+ </Wrapper>
+ );
+} \ No newline at end of file
diff --git a/frontend/src/components/Inputs/Dropdown.js b/frontend/src/components/Inputs/Dropdown.js
new file mode 100644
index 0000000..9fde6ed
--- /dev/null
+++ b/frontend/src/components/Inputs/Dropdown.js
@@ -0,0 +1,120 @@
+import Dropdown from "react-dropdown";
+import React from "react";
+import styled from 'styled-components';
+import {LANGS, THEMES} from "../renderers/Code";
+import {Labelled} from "../decorators/Labelled";
+import {Border, DropShadow, InputLike, Rounded} from "../Common/mixins";
+
+const StyledDropdown = styled(Dropdown)`
+ ${Border}
+ ${Rounded}
+ ${DropShadow}
+ ${InputLike}
+ cursor: pointer;
+
+ & .Dropdown-root {
+ cursor: pointer;
+
+ &:hover, &.is-open {
+ opacity: 1;
+ }
+ }
+
+ & .Dropdown-placeholder {
+ width: 5.5em;
+ }
+
+ & .Dropdown-menu {
+ border-top: 1px solid ${p => p.theme.colors.text};
+ margin-top: 0.5em;
+ bottom: auto;
+ }
+
+ & .Dropdown-option {
+ margin-top: 0.5em;
+ transition: all 0.5s cubic-bezier(.25,.8,.25,1);
+
+ &:hover {
+ font-weight: 700;
+ opacity: 0.4;
+ }
+ }
+`
+
+const GenericDropdown = (props) => {
+ function _onSelect(option) {
+ props.onChange({
+ target: {
+ name: props.label,
+ value: option.label
+ }
+ });
+ }
+
+ return (
+ <Labelled
+ label={props.label}
+ id={props.id}
+ value={props.value}>
+ <StyledDropdown
+ options={props.options}
+ onChange={_onSelect}
+ value={props.value}
+ placeholder={props.placeholder}
+ id={props.id} />
+ </Labelled>
+ );
+}
+
+export const Expiry = (props) => {
+ const options = [
+ '5 years',
+ '1 year',
+ '1 month',
+ '1 week',
+ '1 day',
+ '1 hour',
+ '10 min',
+ ];
+
+ return (
+ <GenericDropdown
+ {...props}
+ options={options}
+ placeholder='1 week'
+ label='expiry'
+ />
+ );
+}
+
+export const Language = (props) => {
+ const options = Object.entries(LANGS).map((key, _) => ({
+ 'value': key[1],
+ 'label': key[0]
+ }))
+
+ return (
+ <GenericDropdown
+ {...props}
+ options={options}
+ placeholder='detect'
+ label='language'
+ />
+ );
+}
+
+export const Theme = (props) => {
+ const options = Object.entries(THEMES).map((key) => ({
+ 'value': key[1],
+ 'label': key[0]
+ }))
+
+ return (
+ <GenericDropdown
+ {...props}
+ options={options}
+ placeholder='atom'
+ label='theme'
+ />
+ );
+} \ No newline at end of file
diff --git a/frontend/src/components/Inputs/Password.js b/frontend/src/components/Inputs/Password.js
new file mode 100644
index 0000000..099aae4
--- /dev/null
+++ b/frontend/src/components/Inputs/Password.js
@@ -0,0 +1,14 @@
+import React from "react";
+import {Labelled} from "../decorators/Labelled";
+import {Input} from "../Common/Input";
+
+export const Password = (props) => <Labelled label="password">
+ <Input
+ name="pass"
+ placeholder={props.placeholder ?? "add password"}
+ type="password"
+ autoComplete="off"
+ onChange={props.onChange}
+ value={props.value}
+ id={props.id} />
+</Labelled> \ No newline at end of file
diff --git a/frontend/src/components/Inputs/Text.js b/frontend/src/components/Inputs/Text.js
new file mode 100644
index 0000000..600e653
--- /dev/null
+++ b/frontend/src/components/Inputs/Text.js
@@ -0,0 +1,25 @@
+import CharLimit from "../decorators/CharLimit";
+import React from "react";
+import {Labelled} from "../decorators/Labelled";
+import {Input} from "../Common/Input";
+
+export const Text = React.forwardRef(({label, id, readOnly, onChange, value, maxLength, autoFocus}, ref) => {
+ return (
+ <Labelled label={label} value={value}>
+ <Input
+ ref={ref}
+ name={label}
+ readOnly={readOnly}
+ placeholder="Title"
+ id={id}
+ type="text"
+ autoFocus={autoFocus}
+ autoComplete="off"
+ onChange={onChange}
+ value={value} />
+ <CharLimit
+ content={value}
+ maxLength={maxLength} />
+ </Labelled>
+ );
+}) \ No newline at end of file
diff --git a/frontend/src/components/Inputs/index.js b/frontend/src/components/Inputs/index.js
new file mode 100644
index 0000000..b16deb3
--- /dev/null
+++ b/frontend/src/components/Inputs/index.js
@@ -0,0 +1,6 @@
+import {Code} from './Code';
+import {Expiry, Language, Theme} from "./Dropdown";
+import {Password} from "./Password";
+import {Text} from "./Text";
+
+export {Code, Expiry, Language, Theme, Password, Text}; \ No newline at end of file
diff --git a/frontend/src/components/NewPaste.js b/frontend/src/components/NewPaste.js
index ff945a0..b322351 100644
--- a/frontend/src/components/NewPaste.js
+++ b/frontend/src/components/NewPaste.js
@@ -1,20 +1,14 @@
import React, { useEffect, useState, useRef } from 'react';
-import { TitleInput, PasteInput } from './Inputs'
+import { Text, Code } from './Inputs'
import OptionsContainer from './Options'
import Error from './Err'
import { PostNewPaste } from '../helpers/httpHelper'
import PasteModal from './modals/PasteModal'
-import { LANGS } from './renderers/Code'
import styled from 'styled-components'
import CodeRenderer from './renderers/Code'
import Latex from './renderers/Latex'
import Markdown from './renderers/Markdown'
-
-const Button = styled.button`
- margin-right: 0 !important;
- margin-left: 2em !important;
- height: calc(16px + 1.6em + 2px);
-`
+import {Button, SubmitButton} from "./Common/Button";
const Flex = styled.div`
display: flex;
@@ -39,7 +33,7 @@ const NewPaste = () => {
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const [pass, setPass] = useState('');
- const [language, setLanguage] = useState(LANGS.raw);
+ const [language, setLanguage] = useState('detect');
const [expiry, setExpiry] = useState('');
const [hash, setHash] = useState('');
const [isPreview, setIsPreview] = useState(false);
@@ -78,11 +72,10 @@ const NewPaste = () => {
}
function renderPreview() {
- const pasteInput = <PasteInput
- onChange={(e) => { setContent(e.target.value) }}
+ const pasteInput = <Code
+ setContentCallback={setContent}
content={content}
- maxLength="100000"
- id="pasteInput" />
+ maxLength="100000" />
if (isPreview) {
var preview
@@ -129,9 +122,11 @@ const NewPaste = () => {
return (
<form onSubmit={handleSubmit}>
<PasteModal hash={hash} />
- <TitleInput
+ <Text
+ label="title"
onChange={(e) => {setTitle(e.target.value)}}
value={title}
+ autoFocus
maxLength="100"
id="titleInput" />
{renderPreview()}
@@ -142,13 +137,15 @@ const NewPaste = () => {
onPassChange={(e) => { setPass(e.target.value) }}
onLangChange={(e) => { setLanguage(e.target.value) }}
onExpiryChange={(e) => { setExpiry(e.target.value) }} />
- <input className="lt-button lt-shadow lt-hover" type="submit" value="new paste" />
- <Button
- className="lt-shadow lt-hover"
+ <div>
+ <SubmitButton type="submit" value="new paste" />
+ {language !== 'detect' && <Button
+ secondary
type="button"
onClick={() => setIsPreview(!isPreview)}>
preview
- </Button>
+ </Button>}
+ </div>
<br />
<Error ref={ErrorLabel} />
</form>
diff --git a/frontend/src/components/Options.js b/frontend/src/components/Options.js
index 09f92f3..97d3127 100644
--- a/frontend/src/components/Options.js
+++ b/frontend/src/components/Options.js
@@ -1,31 +1,34 @@
import React from 'react';
import styled from 'styled-components'
-import { PassInput, ExpiryInput, LangInput } from './Inputs'
+import { Password, Expiry, Language } from './Inputs'
const Flex = styled.div`
float: right;
display: flex;
flex-direction: row;
transform: translateY(0.2em);
+
+ & > *:not(:first-child) {
+ margin-left: 2em;
+ }
@media (max-width: 850px) {
float: none !important;
- transform: translateX(-2em);
}
`
const OptionsContainer = ({pass, lang, expiry, onPassChange, onLangChange, onExpiryChange}) => {
return (
<Flex>
- <PassInput
+ <Password
value={pass}
onChange={onPassChange}
id="passwordInput" />
- <LangInput
+ <Language
value={lang}
onChange={onLangChange}
id="langInput" />
- <ExpiryInput
+ <Expiry
value={expiry}
onChange={onExpiryChange}
id="expiryInput" />
diff --git a/frontend/src/components/PasteInfo.js b/frontend/src/components/PasteInfo.js
index bab7e23..6ab5b19 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 { ThemeInput } from './Inputs'
-import { exportComponentAsPNG } from "react-component-export-image";
+import { Theme } from './Inputs'
+import {Button} from "./Common/Button";
const Bold = styled.span`
font-weight: 700
@@ -13,10 +13,7 @@ const StyledDiv = styled.div`
margin: 2em 0;
`
-const Button = styled.button`
- margin-right: 0 !important;
- margin-left: 2em !important;
- height: calc(16px + 1.6em);
+const ShiftedButton = styled(Button)`
margin-top: 1.6em !important;
`
@@ -28,7 +25,6 @@ const Flex = styled.div`
float: right;
display: flex;
flex-direction: row;
- transform: translateY(0.2em);
`
const PasteInfo = (props) => {
@@ -42,12 +38,12 @@ const PasteInfo = (props) => {
const buttonTxt = props.isRenderMode ? 'text' : 'render'
if (props.lang === 'latex' || props.lang === 'markdown') {
return (
- <Button
- className="lt-shadow lt-hover"
+ <ShiftedButton
+ secondary
type="button"
onClick={props.toggleRenderCallback}>
{buttonTxt}
- </Button>
+ </ShiftedButton>
);
}
}
@@ -55,20 +51,14 @@ const PasteInfo = (props) => {
return (
<div>
<Flex>
- <Button
- className="lt-shadow lt-hover"
+ <ShiftedButton
+ secondary
type="button"
onClick={redirRaw}>
view raw
- </Button>
- <Button
- className="lt-shadow lt-hover"
- type="button"
- onClick={() => exportComponentAsPNG(props.compref, `paste-${props.hash}.png`)}>
- save png
- </Button>
+ </ShiftedButton>
{renderable()}
- <ThemeInput
+ <Theme
value={props.theme}
onChange={props.onChange}
id="themeInput" />
diff --git a/frontend/src/components/ViewPaste.js b/frontend/src/components/ViewPaste.js
index 5f9a962..9dd281c 100644
--- a/frontend/src/components/ViewPaste.js
+++ b/frontend/src/components/ViewPaste.js
@@ -1,13 +1,12 @@
import React, { useEffect, useState, useRef } from 'react';
import Error from './Err';
-import { TitleInput } from './Inputs';
+import { Text } from './Inputs';
import CodeRenderer from './renderers/Code'
import PasteInfo from './PasteInfo';
import PasswordModal from './modals/PasswordModal'
import { FetchPaste, FetchPasswordPaste } from '../helpers/httpHelper'
import { LANGS } from './renderers/Code'
import RenderDispatch from './renderers/RenderDispatch'
-import MetaTags from 'react-meta-tags';
function fmtDateStr(dateString) {
const d = new Date(dateString)
@@ -24,14 +23,13 @@ const ViewPaste = (props) => {
const [expiry, setExpiry] = useState('');
const [theme, setTheme] = useState('atom');
const [isRenderMode, setIsRenderMode] = useState(false);
- const [language, setLanguage] = useState(LANGS.raw);
+ const [language, setLanguage] = useState(LANGS.detect);
useEffect(() => {
setIsRenderMode(language === 'latex' || language === 'markdown')
}, [language])
const ErrorLabelRef = useRef(null);
- const ComponentRef = useRef(null);
function validatePass(pass, onErrorCallBack) {
FetchPasswordPaste(props.hash, pass)
@@ -98,39 +96,26 @@ const ViewPaste = (props) => {
}, [props.hash])
function getDisplay() {
- if (isRenderMode) {
- return (
- <RenderDispatch
- language={language}
- content={content}
- ref={ComponentRef}
- />
- )
- } else {
- return (
- <CodeRenderer
- content={content}
- lang={language}
- theme={theme}
- ref={ComponentRef}
- id="pasteInput" />
- )
- }
+ return isRenderMode ? <RenderDispatch
+ language={language}
+ content={content}
+ /> : <CodeRenderer
+ content={content}
+ lang={language}
+ theme={theme}
+ id="pasteInput" />
}
return (
<div>
- <MetaTags>
- <meta name="description" content={`${language}, expires ${expiry}. hosted on ctrl-v`} />
- <meta property="og:title" content={title} />
- </MetaTags>
<PasswordModal
hasPass={hasPass}
validPass={validPass}
value={enteredPass}
onChange={(e) => setEnteredPass(e.target.value)}
validateCallback={validatePass} />
- <TitleInput
+ <Text
+ label="title"
value={title}
id="titleInput"
readOnly />
@@ -143,7 +128,6 @@ const ViewPaste = (props) => {
toggleRenderCallback={() => setIsRenderMode(!isRenderMode)}
isRenderMode={isRenderMode}
onChange={(e) => setTheme(e.target.value)}
- compref={ComponentRef}
err={<Error ref={ErrorLabelRef} />}
/>
</div>
diff --git a/frontend/src/components/Watermark.js b/frontend/src/components/Watermark.js
new file mode 100644
index 0000000..2f6c8c6
--- /dev/null
+++ b/frontend/src/components/Watermark.js
@@ -0,0 +1,32 @@
+import styled from "styled-components";
+import React from "react";
+import Footer from "./Footer";
+
+const Logo = styled.div`
+ position: fixed;
+ bottom: 1em;
+ left: 2em;
+ opacity: 0.3;
+ transition: opacity 0.5s cubic-bezier(.25,.8,.25,1);
+
+ &:hover {
+ opacity: 1;
+ }
+
+ & > h1 {
+ font-size: 50px;
+ margin: 0 0;
+
+ & > a {
+ text-decoration: none;
+ position: relative;
+ color: ${p => p.theme.colors.text};
+ }
+ }
+`
+export const Watermark = () => <Logo>
+ <h1>
+ <a href="https://github.com/jackyzha0/ctrl-v">ctrl-v</a>
+ </h1>
+ <Footer />
+</Logo> \ No newline at end of file
diff --git a/frontend/src/components/decorators/CharLimit.js b/frontend/src/components/decorators/CharLimit.js
index 5a6fdca..3d1d981 100644
--- a/frontend/src/components/decorators/CharLimit.js
+++ b/frontend/src/components/decorators/CharLimit.js
@@ -1,34 +1,24 @@
import React from 'react';
-import styled, { css } from 'styled-components'
+import styled from 'styled-components'
// show char limit if length > half of max
const Chars = styled.p`
color: #11111100;
- font-family: 'Roboto Mono', monospace;
position: absolute;
font-size: 0.8em;
writing-mode: vertical-rl;
top: 50%;
- transform: translate(5em, -50%);
+ transform: translate(4em, -50%);
right: 0;
transition: all 0.5s cubic-bezier(.25,.8,.25,1);
- ${props =>
- ((props.content.length / props.maxLength) > 0.5) &&
- css`
- color: #111111;
- `};
-
- ${props =>
- ((props.content.length / props.maxLength) > 1) &&
- css`
- color: #ee1111;
- `};
+ ${p => ((p.content.length / p.maxLength) > 0.5) && ` color: ${p.theme.colors.text}; `};
+ ${p => ((p.content.length / p.maxLength) > 1) && ` color: ${p.theme.colors.error}; `};
`;
const CharLimit = (props) => {
return (
- <Chars {...props} >{props.maxLength - props.content.length}/{props.maxLength}</Chars>
+ <Chars {...props}>{props.maxLength - props.content.length}/{props.maxLength}</Chars>
);
}
diff --git a/frontend/src/components/decorators/FloatingLabel.js b/frontend/src/components/decorators/FloatingLabel.js
deleted file mode 100644
index ef56b44..0000000
--- a/frontend/src/components/decorators/FloatingLabel.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import React from 'react';
-import styled, { css } from 'styled-components'
-
-const StyledLabel = styled.label`
- position: absolute;
- top: 0.5em;
- font-weight: 700;
- font-size: 1em;
- opacity: 0;
- transition: all 0.5s cubic-bezier(.25,.8,.25,1);
-
- ${props =>
- (props.value.length > 0) &&
- css`
- top: -0.1em;
- opacity: 1
- `};
-`
-
-const FloatingLabel = (props) => {
- return (
- <StyledLabel
- label={props.label}
- value={props.value}
- className={props.id}
- htmlFor={props.id}>
- {props.label}
- </StyledLabel>
- );
-}
-
-export default FloatingLabel \ No newline at end of file
diff --git a/frontend/src/components/decorators/Labelled.js b/frontend/src/components/decorators/Labelled.js
new file mode 100644
index 0000000..0d7fe39
--- /dev/null
+++ b/frontend/src/components/decorators/Labelled.js
@@ -0,0 +1,30 @@
+import styled from "styled-components";
+import React from "react";
+
+const StyledLabel = styled.label`
+ position: relative;
+ & > span {
+ position: absolute;
+ transform: translateY(-0.2em);
+ font-weight: 700;
+ font-size: 1em;
+ opacity: 0.5;
+ transition: opacity 0.5s cubic-bezier(.25,.8,.25,1);
+ }
+
+ &:hover > span {
+ opacity: 1;
+ }
+`
+
+const FloatingLabel = (props) => <StyledLabel label={props.label}>
+ {props.children}
+</StyledLabel>
+
+
+export const Labelled = ({label, value, children}) => <div>
+ <FloatingLabel label={label} value={value} >
+ <span>{label}</span>
+ {children}
+ </FloatingLabel>
+</div> \ No newline at end of file
diff --git a/frontend/src/components/hooks/useFetchPaste.js b/frontend/src/components/hooks/useFetchPaste.js
new file mode 100644
index 0000000..a61ca1e
--- /dev/null
+++ b/frontend/src/components/hooks/useFetchPaste.js
@@ -0,0 +1,5 @@
+import React from 'react'
+
+export default () => {
+
+} \ No newline at end of file
diff --git a/frontend/src/components/modals/PasswordModal.js b/frontend/src/components/modals/PasswordModal.js
index bf373cc..a829091 100644
--- a/frontend/src/components/modals/PasswordModal.js
+++ b/frontend/src/components/modals/PasswordModal.js
@@ -1,19 +1,9 @@
import React, { useRef } from 'react';
import Modal from 'react-modal';
-import { PassInput } from '../Inputs'
-import { RightPad, LeftPad, ModalHeader, Padding } from './shared'
+import { Password } from '../Inputs'
+import {ModalHeader, Padding, modalStyles, Form} from './shared'
import Error from '../Err';
-
-const modalStyles = {
- content: {
- top: '50%',
- left: '50%',
- transform: 'translate(-50%, -50%)',
- width: '400px',
- height: '250px',
- border: '1px solid #11111188'
- }
-};
+import {SubmitButton} from "../Common/Button";
const PasswordModal = (props) => {
const ErrorLabel = useRef(null);
@@ -31,21 +21,16 @@ const PasswordModal = (props) => {
style={modalStyles}
contentLabel="enter paste password"
>
- <form onSubmit={submitPassword}>
- <LeftPad>
- <ModalHeader><span role="img" aria-label="warning">🚧&nbsp;</span>err: password protected</ModalHeader>
- </LeftPad>
- <RightPad>
- <PassInput
- value={props.value}
- onChange={props.onChange} />
- </RightPad>
- <LeftPad>
- <input className="lt-button lt-shadow lt-hover" type="submit" value="continue" />
- <Padding />
- <Error ref={ErrorLabel} />
- </LeftPad>
- </form>
+ <Form onSubmit={submitPassword}>
+ <ModalHeader><span role="img" aria-label="warning">🚧&nbsp;</span>err: password protected</ModalHeader>
+ <Password
+ placeholder="hunter2"
+ value={props.value}
+ onChange={props.onChange} />
+ <SubmitButton type="submit" value="continue" />
+ <Padding />
+ <Error ref={ErrorLabel} />
+ </Form>
</Modal>
);
}
diff --git a/frontend/src/components/modals/PasteModal.js b/frontend/src/components/modals/PasteModal.js
index 7b28abb..e7dbed2 100644
--- a/frontend/src/components/modals/PasteModal.js
+++ b/frontend/src/components/modals/PasteModal.js
@@ -1,20 +1,10 @@
import React from 'react';
import Modal from 'react-modal';
-import { LeftPad, ModalHeader, RightPad } from './shared'
+import {Form, ModalHeader, modalStyles} from './shared'
import { useHistory } from 'react-router-dom';
-import { PasteURLInput } from '../Inputs'
+import { Text } from '../Inputs'
import { useClipboard } from 'use-clipboard-copy';
-
-const modalStyles = {
- content: {
- top: '50%',
- left: '50%',
- transform: 'translate(-50%, -50%)',
- width: '500px',
- height: '250px',
- border: '1px solid #11111188'
- }
-};
+import {Button} from "../Common/Button";
const PasteModal = (props) => {
const history = useHistory();
@@ -34,35 +24,27 @@ const PasteModal = (props) => {
style={modalStyles}
contentLabel="paste created"
>
- <form onSubmit={redir}>
- <LeftPad>
- <ModalHeader><span role="img" aria-label="success">📎&nbsp;</span>paste created</ModalHeader>
- </LeftPad>
- <RightPad>
- <PasteURLInput
- id="paste_modal"
- fullURL={fullURL} />
- <input
- hidden
- type="text"
- value={fullURL}
- readOnly
- ref={clipboard.target} />
- </RightPad>
- <LeftPad>
- <button
- className="lt-button lt-shadow lt-hover"
- type="submit">
- view
- </button>
- <button
- className="lt-button lt-shadow lt-hover"
- type="button"
- onClick={clipboard.copy}>
- {clipboard.copied ? 'copied' : 'copy url'}
- </button>
- </LeftPad>
- </form>
+ <Form onSubmit={redir}>
+ <ModalHeader>
+ <span role="img" aria-label="success">📎&nbsp;</span>paste created
+ </ModalHeader>
+ <Text
+ label="url"
+ type="text"
+ value={fullURL}
+ readOnly
+ ref={clipboard.target} />
+ <Button
+ type="submit">
+ go to paste
+ </Button>
+ <Button
+ secondary
+ type="button"
+ onClick={clipboard.copy}>
+ {clipboard.copied ? 'copied' : 'copy url'}
+ </Button>
+ </Form>
</Modal>
);
}
diff --git a/frontend/src/components/modals/shared.js b/frontend/src/components/modals/shared.js
index d63be06..0336818 100644
--- a/frontend/src/components/modals/shared.js
+++ b/frontend/src/components/modals/shared.js
@@ -1,11 +1,19 @@
import styled from 'styled-components'
-export const RightPad = styled.div`
- margin-right: 3em;
-`
+export const modalStyles = {
+ content: {
+ top: '50%',
+ left: '50%',
+ transform: 'translate(-50%, -50%)',
+ width: '500px',
+ height: '250px',
+ border: '1px solid #11111188',
+ },
+};
-export const LeftPad = styled.div`
- margin-left: 2em;
+export const Form = styled.form`
+ margin: 2em;
+ margin-right: 3em;
`
export const ModalHeader = styled.h3`
diff --git a/frontend/src/components/renderers/Code.js b/frontend/src/components/renderers/Code.js
index a312c51..4ab1175 100644
--- a/frontend/src/components/renderers/Code.js
+++ b/frontend/src/components/renderers/Code.js
@@ -1,7 +1,9 @@
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 styled from 'styled-components'
+import {Border, CodeLike, DropShadow, Rounded} from "../Common/mixins";
export const THEMES = Object.freeze({
'atom': atomOneLight,
@@ -12,66 +14,50 @@ export const THEMES = Object.freeze({
})
export const LANGS = Object.freeze({
- 'bash': 'bash',
- 'c': 'c',
- 'c++': 'cpp',
- 'c#': 'cs',
- 'css': 'css',
- 'docker': 'dockerfile',
- 'go': 'go',
- 'haskell': 'haskell',
- 'html': 'html',
- 'java': 'java',
- 'js': 'javascript',
- 'jsx': 'jsx',
'latex': 'latex',
- 'lisp': 'lisp',
- 'makefile': 'makefile',
'markdown': 'markdown',
- 'php': 'php',
- 'python': 'python',
- 'raw': 'text',
- 'ruby': 'ruby',
- 'scala': 'scala',
- 'yaml': 'yaml'
+ 'detect': 'text',
})
-const StyledPre = styled.pre`
- width: 100%;
- font-size: 0.8em;
- min-height: 1.2em;
- border-radius: 3px !important;
- border: 1px solid #565656 !important;
- padding: calc(0.8em - 1px) !important;
- outline: none;
+export const StyledPre = styled.pre`
+ ${Rounded};
+ ${Border};
+ ${DropShadow};
+ width: calc(100%);
+ padding: calc(0.6em - 1px) !important;
margin: 1.7em 0;
+ position: relative;
+ outline: none;
+
+ & code {
+ ${CodeLike}
+ }
- & code:first-child {
+ & code:first-child:not(:only-of-type) {
margin-right: 10px !important;
border-radius: 0 !important;
border-right: 1px solid #11111155 !important;
}
`
-const CodeRenderer = React.forwardRef((props, ref) => {
-
- const Pre = (props) => {
- return (
- <StyledPre {...props} ref={ref} />
- );
- }
-
- return (
- <div className="lt-shadow">
- <SyntaxHighlighter
- language={LANGS[props.lang]}
- style={THEMES[props.theme]}
- showLineNumbers
- PreTag={Pre}>
- {props.content}
- </SyntaxHighlighter>
- </div>
- );
-});
+export const Highlighter = ({language, lineNumbers, theme, pre = StyledPre, children}) => <SyntaxHighlighter
+ language={LANGS[language]}
+ style={THEMES[theme]}
+ showLineNumbers={lineNumbers}
+ PreTag={pre}>
+ {children}
+</SyntaxHighlighter>
+
+const CodeRenderer = (props) => {
+ return (<Highlighter
+ lineNumbers={true}
+ language={props.lang}
+ theme={props.theme}
+ renderer={virtualizedRenderer()}
+ pre={StyledPre}
+ >
+ {props.content}
+ </Highlighter>)
+};
export default CodeRenderer \ No newline at end of file
diff --git a/frontend/src/components/renderers/Raw.js b/frontend/src/components/renderers/Raw.js
index d4dc830..182bfff 100644
--- a/frontend/src/components/renderers/Raw.js
+++ b/frontend/src/components/renderers/Raw.js
@@ -1,12 +1,10 @@
import React, { useState, useEffect } from 'react';
import styled from 'styled-components'
import { FetchPaste } from '../../helpers/httpHelper'
+import {CodeLike} from "../Common/mixins";
const RawText = styled.pre`
- word-wrap: break-word;
- white-space: pre-wrap;
- line-height: initial;
- font-size: 0.8em;
+ ${CodeLike}
padding: 0 1em;
`
diff --git a/frontend/src/components/renderers/RenderDispatch.js b/frontend/src/components/renderers/RenderDispatch.js
index 3f1c87b..365a822 100644
--- a/frontend/src/components/renderers/RenderDispatch.js
+++ b/frontend/src/components/renderers/RenderDispatch.js
@@ -8,16 +8,16 @@ const RenderWrapper = styled.div`
padding: 1em;
`
-const RenderDispatch = React.forwardRef((props, ref) => {
+const RenderDispatch = (props) => {
switch (props.language) {
case 'latex':
return (
- <RenderWrapper ref={ref}>
+ <RenderWrapper>
<Latex content={props.content} />
</RenderWrapper>)
case 'markdown':
return (
- <RenderWrapper ref={ref} className="md" >
+ <RenderWrapper className="md" >
<Markdown content={props.content} />
</RenderWrapper>)
default:
@@ -26,9 +26,8 @@ const RenderDispatch = React.forwardRef((props, ref) => {
content={props.content}
lang={props.language}
theme={props.theme}
- ref={ref}
id="pasteInput" />)
}
-});
+};
export default RenderDispatch \ No newline at end of file
diff --git a/frontend/src/css/index.css b/frontend/src/css/index.css
index fcda1a3..0340813 100644
--- a/frontend/src/css/index.css
+++ b/frontend/src/css/index.css
@@ -4,20 +4,13 @@
}
}
-body {
- margin: 0;
- padding: 0;
- background-color: #faf9f5;
- font-family: 'Lora', serif;
-}
-
form {
width: 100%;
}
textarea, input[type=text], input[type=password], .Dropdown-root {
width: 100%;
- font-family: 'Roboto Mono', monospace;
+ font-family: 'JetBrains Mono', monospace;
font-size: 0.8em;
padding: calc(0.8em - 1px);
border-radius: 3px;
@@ -37,7 +30,7 @@ textarea, input[type=text], input[type=password], .Dropdown-root {
code, pre {
background: #00000000;
- font-family: 'Roboto Mono', monospace;
+ font-family: 'JetBrains Mono', monospace;
padding: initial;
border-radius: 3px;
outline: none;
@@ -105,7 +98,7 @@ a {
}
input[type=submit], button[type=submit] {
- font-family: 'Lora', serif;
+ font-family: 'JetBrains Mono', serif;
font-weight: 700;
color: #faf9f5;
background-color: #111111;
@@ -115,7 +108,7 @@ input[type=submit], button[type=submit] {
}
button[type=button] {
- font-family: 'Lora', serif;
+ font-family: 'JetBrains Mono', serif;
font-weight: 700;
width: 8em;
padding: calc(0.8em - 1px) 1.5em;
@@ -127,32 +120,6 @@ button[type=button] {
margin: 2em 2em;
}
-.mainLogo {
- font-size: 50px;
- margin: 0 0;
- display: inline-block;
-}
-
-.mainLogo a {
- text-decoration: none;
- position: relative;
- margin: 3px;
-}
-
-.mainLogo a::after {
- content: "";
- position: absolute;
- left: 0;
- bottom: 0;
- height: 4px;
- background-color: #111111;
- width: 0;
- transition: width 0.25s ease;
-}
-
-.mainLogo:hover a::after {
- width: 100%;
-}
/* fixing markdown renderer */
.md h3 {
diff --git a/frontend/src/index.js b/frontend/src/index.js
index 437fe03..7173ce5 100644
--- a/frontend/src/index.js
+++ b/frontend/src/index.js
@@ -1,7 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
-import './css/index.css';
-import App from './components/App';
+import App from './App';
ReactDOM.render(
<React.StrictMode>
diff --git a/frontend/src/theme/GlobalStyle.js b/frontend/src/theme/GlobalStyle.js
new file mode 100644
index 0000000..9fe80a5
--- /dev/null
+++ b/frontend/src/theme/GlobalStyle.js
@@ -0,0 +1,11 @@
+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};
+ }
+` \ No newline at end of file
diff --git a/frontend/src/theme/ThemeProvider.js b/frontend/src/theme/ThemeProvider.js
new file mode 100644
index 0000000..d9edcb0
--- /dev/null
+++ b/frontend/src/theme/ThemeProvider.js
@@ -0,0 +1,13 @@
+import React from 'react'
+import { ThemeProvider } from 'styled-components'
+
+const theme = {
+ colors: {
+ background: '#faf9f5',
+ border: '#565656',
+ text: '#111111',
+ error: '#ee1111',
+ },
+}
+
+export default ({ children }) => <ThemeProvider theme={theme}>{children}</ThemeProvider> \ No newline at end of file
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index 2289530..27f2ab3 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -1081,6 +1081,13 @@
dependencies:
regenerator-runtime "^0.13.4"
+"@babel/runtime@^7.8.7":
+ version "7.13.9"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.9.tgz#97dbe2116e2630c489f22e0656decd60aaa1fcee"
+ integrity sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
"@babel/template@^7.10.1", "@babel/template@^7.4.0", "@babel/template@^7.8.6":
version "7.10.1"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811"
@@ -2444,7 +2451,7 @@ babel-preset-react-app@^9.1.2:
babel-plugin-macros "2.8.0"
babel-plugin-transform-react-remove-prop-types "0.4.24"
[email protected], babel-runtime@^6.26.0:
[email protected], babel-runtime@^6.18.0, babel-runtime@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4=
@@ -3104,6 +3111,11 @@ clone-deep@^4.0.1:
kind-of "^6.0.2"
shallow-clone "^3.0.0"
+clsx@^1.0.4:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
+ integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
+
co@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
@@ -3709,6 +3721,11 @@ csstype@^2.2.0:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.10.tgz#e63af50e66d7c266edb6b32909cfd0aabe03928b"
integrity sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==
+csstype@^3.0.2:
+ version "3.0.7"
+ resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.7.tgz#2a5fb75e1015e84dd15692f71e89a1450290950b"
+ integrity sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g==
+
cyclist@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
@@ -4013,6 +4030,14 @@ dom-converter@^0.2:
dependencies:
utila "~0.4"
+dom-helpers@^5.1.3:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.0.tgz#57fd054c5f8f34c52a3eeffdb7e7e93cd357d95b"
+ integrity sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ==
+ dependencies:
+ "@babel/runtime" "^7.8.7"
+ csstype "^3.0.2"
+
dom-serializer@0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
@@ -5361,6 +5386,11 @@ hex-color-regex@^1.1.0:
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
+highlight.js@~9.12.0:
+ version "9.12.0"
+ resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e"
+ integrity sha1-5tnb5Xy+/mB1HwKvM2GVhwyQwB4=
+
highlight.js@~9.15.0, highlight.js@~9.15.1:
version "9.15.10"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.15.10.tgz#7b18ed75c90348c045eef9ed08ca1319a2219ad2"
@@ -5681,13 +5711,6 @@ indent-string@^4.0.0:
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
-indent-textarea@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/indent-textarea/-/indent-textarea-2.0.2.tgz#9fc142cb0cf40c1b0320558c017eddb8dd494174"
- integrity sha512-2E/WQXpCOd3lLJoLPk8QIArLfflZNoGD6udendhbRpRpA+nkIYSNexsY9fpSCjw4poqKfuP51d9ASYucFdjm4Q==
- dependencies:
- insert-text-textarea "^2.0.0"
-
indexes-of@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
@@ -5764,11 +5787,6 @@ inquirer@^7.0.0:
strip-ansi "^6.0.0"
through "^2.3.6"
-insert-text-textarea@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/insert-text-textarea/-/insert-text-textarea-2.0.1.tgz#ac3ccbb05b27a1280ca9ea62349c5ef65ba7d4a0"
- integrity sha512-I4PC8aD1HjD61lqPiAxauv1r6Tc0vPk3BGauZw/oQl+1YnKhStzHOIG0K7peKZgZ7XG0N/OmbR8KMZ5wE0n1tA==
-
internal-ip@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907"
@@ -7075,6 +7093,14 @@ [email protected]:
fault "^1.0.2"
highlight.js "~9.15.0"
+lowlight@~1.9.1:
+ version "1.9.2"
+ resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.9.2.tgz#0b9127e3cec2c3021b7795dd81005c709a42fdd1"
+ integrity sha512-Ek18ElVCf/wF/jEm1b92gTnigh94CtBNWiZ2ad+vTgW7cTmQxUY3I98BjHK68gZAJEWmybGBZgx9qv3QxLQB/Q==
+ dependencies:
+ fault "^1.0.2"
+ highlight.js "~9.12.0"
+
lru-cache@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
@@ -9464,6 +9490,19 @@ [email protected]:
optionalDependencies:
fsevents "2.1.2"
+react-simple-code-editor@^0.11.0:
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/react-simple-code-editor/-/react-simple-code-editor-0.11.0.tgz#bb57c7c29b570f2ab229872599eac184f5bc673c"
+ integrity sha512-xGfX7wAzspl113ocfKQAR8lWPhavGWHL3xSzNLeseDRHysT+jzRBi/ExdUqevSMos+7ZtdfeuBOXtgk9HTwsrw==
+
+react-syntax-highlighter-virtualized-renderer@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/react-syntax-highlighter-virtualized-renderer/-/react-syntax-highlighter-virtualized-renderer-1.1.0.tgz#7536d9f18f9cce736fac15031a891b8cbaabe90b"
+ integrity sha1-dTbZ8Y+cznNvrBUDGokbjLqr6Qs=
+ dependencies:
+ react-syntax-highlighter "^5.1.2"
+ react-virtualized "^9.3.0"
+
react-syntax-highlighter@^12.2.1:
version "12.2.1"
resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-12.2.1.tgz#14d78352da1c1c3f93c6698b70ec7c706b83493e"
@@ -9475,6 +9514,27 @@ react-syntax-highlighter@^12.2.1:
prismjs "^1.8.4"
refractor "^2.4.1"
+react-syntax-highlighter@^5.1.2:
+ version "5.8.0"
+ resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-5.8.0.tgz#a220c010fd0641751d93532509ba7159cc3a4383"
+ integrity sha512-+FolT9NhFBqE4SsZDelSzsYJJS/JCnQqo4+GxLrFPoML548uvr8f4Eh5nnd5o6ERKFW7ryiygOX9SPnxdnlpkg==
+ dependencies:
+ babel-runtime "^6.18.0"
+ highlight.js "~9.12.0"
+ lowlight "~1.9.1"
+
+react-virtualized@^9.3.0:
+ version "9.22.3"
+ resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.22.3.tgz#f430f16beb0a42db420dbd4d340403c0de334421"
+ integrity sha512-MKovKMxWTcwPSxE1kK1HcheQTWfuCxAuBoSTf2gwyMM21NdX/PXUhnoP8Uc5dRKd+nKm8v41R36OellhdCpkrw==
+ dependencies:
+ "@babel/runtime" "^7.7.2"
+ clsx "^1.0.4"
+ dom-helpers "^5.1.3"
+ loose-envify "^1.4.0"
+ prop-types "^15.7.2"
+ react-lifecycles-compat "^3.0.4"
+
react@^16.12.0, react@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"