aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--backend/api/routes.go7
-rw-r--r--backend/db/db.go9
-rw-r--r--frontend/package.json1
-rw-r--r--frontend/src/components/App.js60
-rw-r--r--frontend/src/components/Err.js18
-rw-r--r--frontend/src/components/Header.js20
-rw-r--r--frontend/src/components/Inputs.js6
-rw-r--r--frontend/src/components/NewPaste.js (renamed from frontend/src/components/PasteArea.js)46
-rw-r--r--frontend/src/components/PasteInfo.js29
-rw-r--r--frontend/src/components/ViewPaste.js103
-rw-r--r--frontend/src/components/decorators/CharLimit.js (renamed from frontend/src/components/CharLimit.js)0
-rw-r--r--frontend/src/components/decorators/FloatingLabel.js (renamed from frontend/src/components/FloatingLabel.js)0
-rw-r--r--frontend/src/css/index.css35
13 files changed, 286 insertions, 48 deletions
diff --git a/backend/api/routes.go b/backend/api/routes.go
index f8d2e4f..ff43714 100644
--- a/backend/api/routes.go
+++ b/backend/api/routes.go
@@ -38,7 +38,8 @@ func insertFunc(w http.ResponseWriter, r *http.Request) {
hash, err := db.New(ip, content, expiry, title, password)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
- fmt.Fprintf(w, "got err: %s", err.Error())
+ fmt.Fprintf(w, "%s", err.Error())
+ return
}
// if successful return paste hash
@@ -75,14 +76,14 @@ func handleGetPaste(w http.ResponseWriter, r *http.Request, parsedPassword strin
// if hash was not found
if err == cache.PasteNotFound {
w.WriteHeader(http.StatusNotFound)
- fmt.Fprintf(w, "got err: %s", err)
+ fmt.Fprintf(w, "%s", err)
return
}
// if paste is password protected
if err == cache.UserUnauthorized {
w.WriteHeader(http.StatusUnauthorized)
- fmt.Fprintf(w, "got err: %s", err)
+ fmt.Fprintf(w, "%s", err)
return
}
diff --git a/backend/db/db.go b/backend/db/db.go
index fba7bd9..4e58188 100644
--- a/backend/db/db.go
+++ b/backend/db/db.go
@@ -82,16 +82,11 @@ func New(ip, content, expiry, title, password string) (string, error) {
}
func checkLengths(title string, content string) error {
- errs := ""
if len(title) > TitleLimit {
- errs += fmt.Sprintf("title is longer than character limit of %d\n", TitleLimit)
+ return fmt.Errorf("title is longer than character limit of %d\n", TitleLimit)
}
if len(content) > ContentLimit {
- errs += fmt.Sprintf("content is longer than character limit of %d\n", ContentLimit)
- }
- // if any errors were found
- if errs != "" {
- return fmt.Errorf(errs)
+ return fmt.Errorf("content is longer than character limit of %d\n", ContentLimit)
}
return nil
diff --git a/frontend/package.json b/frontend/package.json
index d32e1cc..390e3f5 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -13,6 +13,7 @@
"react-dom": "^16.13.1",
"react-dropdown": "^1.7.0",
"react-modal": "^3.11.2",
+ "react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
"styled-components": "^5.1.0"
},
diff --git a/frontend/src/components/App.js b/frontend/src/components/App.js
index 17240f5..605903e 100644
--- a/frontend/src/components/App.js
+++ b/frontend/src/components/App.js
@@ -1,15 +1,61 @@
import React from 'react';
-import Header from './Header'
-import PasteArea from './PasteArea'
+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";
+
+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} />
+ );
+}
function App() {
return (
- <div className="lt-content-column">
- <Header />
- <PasteArea />
- <Footer />
- </div>
+ <Router>
+ <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>
+
+ <Switch>
+ <Route path="/:hash"
+ children={<GetPasteWithParam />}
+ />
+ <Route path="/">
+ <NewPaste />
+ </Route>
+ </Switch>
+
+ <Footer />
+ </div>
+ </Router>
);
}
diff --git a/frontend/src/components/Err.js b/frontend/src/components/Err.js
new file mode 100644
index 0000000..8562a74
--- /dev/null
+++ b/frontend/src/components/Err.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import styled from 'styled-components'
+
+const ErrMsg = styled.p`
+ display: inline;
+ font-weight: 700;
+ margin-left: 2em;
+ color: #ff3333
+`
+
+const Error = (props) => {
+ const msg = props.msg.toString().toLowerCase()
+ return (
+ <ErrMsg> {msg} </ErrMsg>
+ );
+}
+
+export default Error \ No newline at end of file
diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js
deleted file mode 100644
index a0c5ee8..0000000
--- a/frontend/src/components/Header.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import React from 'react';
-import styled from 'styled-components'
-
-const SpacedTitle = styled.div`
- margin-top: 10vh
-`
-
-const Header = () => {
- return (
- <SpacedTitle>
- <h1>
- <span role="img" aria-label="clipboard">📋&nbsp;</span>
- ctrl-v
- </h1>
- <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>
- </SpacedTitle>
- );
-}
-
-export default Header; \ No newline at end of file
diff --git a/frontend/src/components/Inputs.js b/frontend/src/components/Inputs.js
index 110d5bd..b61869f 100644
--- a/frontend/src/components/Inputs.js
+++ b/frontend/src/components/Inputs.js
@@ -1,7 +1,7 @@
import React from 'react';
-import CharLimit from './CharLimit'
+import CharLimit from './decorators/CharLimit'
import styled from 'styled-components'
-import FloatingLabel from './FloatingLabel'
+import FloatingLabel from './decorators/FloatingLabel'
import Dropdown from 'react-dropdown';
const CharLimitContainer = styled.div`
@@ -23,6 +23,7 @@ class TitleInput extends React.Component {
value={this.props.value} />
<input
name="title"
+ readOnly={this.props.readOnly}
className="lt-shadow"
placeholder="Title"
id={this.props.id}
@@ -49,6 +50,7 @@ class PasteInput extends React.Component {
value={this.props.content} />
<textarea
name="content"
+ readOnly={this.props.readOnly}
placeholder="Paste your text here"
value={this.props.content}
id={this.props.id}
diff --git a/frontend/src/components/PasteArea.js b/frontend/src/components/NewPaste.js
index bd08fc1..3bdd41a 100644
--- a/frontend/src/components/PasteArea.js
+++ b/frontend/src/components/NewPaste.js
@@ -2,21 +2,39 @@ import React from 'react';
import { TitleInput, PasteInput } from './Inputs'
import OptionsContainer from './Options'
import axios from 'axios';
+import { Redirect } from 'react-router-dom'
+import Error from './Err'
-class PasteArea extends React.Component {
+class NewPaste extends React.Component {
constructor(props) {
super(props);
this.state = {
title: '',
content: '',
pass: '',
- expiry: ''
+ expiry: '',
+ hash: '',
+ error: '',
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
+ newErr(msg, duration = 5000) {
+ this.setState({ error: msg })
+ setTimeout(() => {
+ this.setState({ error: '' })
+ }, duration);
+ }
+
+ renderRedirect = () => {
+ if (this.state.hash !== '') {
+ const redirUrl = `/${this.state.hash}`
+ return <Redirect to={redirUrl} />
+ }
+ }
+
componentDidUpdate() {
if (this.state.title === "") {
document.title = `ctrl-v`;
@@ -76,12 +94,20 @@ class PasteArea extends React.Component {
url: 'http://localhost:8080/api',
data: bodyFormData,
headers: { 'Content-Type': 'multipart/form-data' },
- }).then(function (response) {
- //handle success
- console.log(response);
- }).catch(function (response) {
- //handle error
- console.log(response);
+ }).then((response) => {
+ // on success, redir
+ this.setState({ hash: response.data.hash })
+ }).catch((error) => {
+ const resp = error.response
+
+ // some weird err
+ if (resp !== undefined) {
+ const errTxt = `${resp.statusText}: ${resp.data}`
+ this.newErr(errTxt)
+ } else {
+ // some weird err (e.g. network)
+ this.newErr(error)
+ }
});
event.preventDefault();
@@ -90,6 +116,7 @@ class PasteArea extends React.Component {
render() {
return (
<form onSubmit={this.handleSubmit}>
+ {this.renderRedirect()}
<TitleInput
onChange={this.handleChange}
value={this.state.title}
@@ -101,6 +128,7 @@ class PasteArea extends React.Component {
maxLength="100000"
id="pasteInput" />
<input className="lt-button lt-shadow lt-hover" type="submit" value="new paste" />
+ <Error msg={this.state.error} />
<OptionsContainer
pass={this.state.pass}
expiry={this.state.expiry}
@@ -110,4 +138,4 @@ class PasteArea extends React.Component {
}
}
-export default PasteArea \ No newline at end of file
+export default NewPaste \ No newline at end of file
diff --git a/frontend/src/components/PasteInfo.js b/frontend/src/components/PasteInfo.js
new file mode 100644
index 0000000..b27cd53
--- /dev/null
+++ b/frontend/src/components/PasteInfo.js
@@ -0,0 +1,29 @@
+import React from 'react';
+import styled from 'styled-components'
+
+const Bold = styled.span`
+ font-weight: 700
+`
+
+const FloatLeft = styled.p`
+ float: left;
+ display: inline-block;
+ margin: 0;
+`
+const FloatRight = styled.p`
+ float: right;
+ display: inline-block;
+ margin: 0;
+ margin-right: -1em;
+`
+
+const PasteInfo = (props) => {
+ return (
+ <div>
+ <FloatLeft><Bold>mode:&nbsp;</Bold>{props.mode}</FloatLeft>
+ <FloatRight><Bold>expires:&nbsp;</Bold>{props.expiry}</FloatRight>
+ </div>
+ );
+}
+
+export default PasteInfo \ No newline at end of file
diff --git a/frontend/src/components/ViewPaste.js b/frontend/src/components/ViewPaste.js
new file mode 100644
index 0000000..79b1840
--- /dev/null
+++ b/frontend/src/components/ViewPaste.js
@@ -0,0 +1,103 @@
+import React from 'react';
+import axios from 'axios';
+import Error from './Err';
+import { TitleInput, PasteInput } from './Inputs';
+import PasteInfo from './PasteInfo';
+
+const RENDER_MODES = Object.freeze({
+ RAW: 'raw text',
+ MD: 'markdown',
+ LATEX: 'latex',
+ CODE: 'code',
+})
+
+class ViewPaste extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ title: 'untitled paste',
+ content: '',
+ hasPass: false,
+ expiry: 'no expiry',
+ error: '',
+ mode: RENDER_MODES.RAW,
+ };
+ }
+
+ newErr(msg, duration = 5000) {
+ this.setState({ error: msg })
+
+ // if duration -1, dont clear
+ if (duration !== -1) {
+ setTimeout(() => {
+ this.setState({ error: '' })
+ }, duration);
+ }
+ }
+
+ drawRightMode() {
+ switch (this.state.mode) {
+ // TODO: add other renderers
+
+ // default render raw
+ case RENDER_MODES.RAW:
+ default:
+ return (<PasteInput
+ content={this.state.content}
+ id="pasteInput"
+ readOnly />);
+ }
+ }
+
+ render() {
+ return (
+ <div>
+ <TitleInput
+ value={this.state.title}
+ id="titleInput"
+ readOnly />
+
+ {this.drawRightMode()}
+
+ <PasteInfo
+ expiry={this.state.expiry}
+ mode={this.state.mode} />
+ <Error msg={this.state.error} />
+ </div>
+ );
+ }
+
+ 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()
+ }
+
+ componentDidMount() {
+ const serverURL = `http://localhost:8080/api/${this.props.hash}`
+
+ axios.get(serverURL)
+ .then((response) => {
+ const data = response.data
+ this.setState({
+ title: data.title,
+ content: data.content,
+ expiry: this.fmtDateStr(data.expiry),
+ })
+ }).catch((error) => {
+ const resp = error.response
+
+ // some weird err
+ if (resp !== undefined) {
+ const errTxt = `${resp.statusText}: ${resp.data}`
+ this.newErr(errTxt, -1)
+ } else {
+ // some weird err (e.g. network)
+ this.newErr(error, -1)
+ }
+ })
+ }
+}
+
+export default ViewPaste \ No newline at end of file
diff --git a/frontend/src/components/CharLimit.js b/frontend/src/components/decorators/CharLimit.js
index 623b378..623b378 100644
--- a/frontend/src/components/CharLimit.js
+++ b/frontend/src/components/decorators/CharLimit.js
diff --git a/frontend/src/components/FloatingLabel.js b/frontend/src/components/decorators/FloatingLabel.js
index e1fc0ba..e1fc0ba 100644
--- a/frontend/src/components/FloatingLabel.js
+++ b/frontend/src/components/decorators/FloatingLabel.js
diff --git a/frontend/src/css/index.css b/frontend/src/css/index.css
index 3d05fa0..3279aac 100644
--- a/frontend/src/css/index.css
+++ b/frontend/src/css/index.css
@@ -94,4 +94,39 @@ input[type=submit] {
padding: 0.8em 2em;
margin: 2em 0;
outline: 0;
+}
+
+ul {
+ list-style-type: none;
+ display: inline-block;
+}
+
+li {
+ display: inline-block;
+ margin: 0 1em;
+}
+
+.mainLogo {
+ 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%;
} \ No newline at end of file