diff options
Diffstat (limited to 'frontend')
| -rw-r--r-- | frontend/package.json | 1 | ||||
| -rw-r--r-- | frontend/src/components/App.js | 60 | ||||
| -rw-r--r-- | frontend/src/components/Err.js | 18 | ||||
| -rw-r--r-- | frontend/src/components/Header.js | 20 | ||||
| -rw-r--r-- | frontend/src/components/Inputs.js | 6 | ||||
| -rw-r--r-- | frontend/src/components/NewPaste.js (renamed from frontend/src/components/PasteArea.js) | 46 | ||||
| -rw-r--r-- | frontend/src/components/PasteInfo.js | 29 | ||||
| -rw-r--r-- | frontend/src/components/ViewPaste.js | 103 | ||||
| -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.css | 35 |
11 files changed, 280 insertions, 38 deletions
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">📋 </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">📋 </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: </Bold>{props.mode}</FloatLeft> + <FloatRight><Bold>expires: </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 |