diff options
| author | Melonai <einebeere@gmail.com> | 2020-05-20 21:16:34 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-05-20 21:16:34 +0200 |
| commit | e9f542ddc8b8230418b1e6fc1656677453ea5a10 (patch) | |
| tree | efe84fba111c308370a89fba61dd9e8548a01085 /client | |
| parent | a00a8a867cae381982c7b8b77f07836ab4a504ed (diff) | |
| parent | 58abd266b0b5ec37c5d7beea37abc2babd7d504a (diff) | |
| download | shorest-0.2.0.tar.zst shorest-0.2.0.zip | |
Merge pull request #1 from Melonai/react-port 0.2.0
React port
Diffstat (limited to 'client')
27 files changed, 622 insertions, 269 deletions
diff --git a/client/index.html b/client/index.html deleted file mode 100644 index 07478ee..0000000 --- a/client/index.html +++ /dev/null @@ -1,37 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="UTF-8"> - <title>Shorest</title> - <link rel="stylesheet" href="static/main.css"> - <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> - <script src="https://cdnjs.cloudflare.com/ajax/libs/validate.js/0.13.1/validate.min.js"></script> - <script src="static/main.js"></script> -</head> -<body> -<div class="active" id="main"> - <div class="title"> - <a><b>sho.rest</b><br></a> - <a>Made with ❤ by <b>Mel</b></a> - </div> - <form id="form"> - <div class="input-group" id="form-group"> - <div class="input-container" id="left" style="border-right: none;"> - <a class="input-field-text">https://</a> - <input class="input-field" id="url" required> - </div> - <div class="button-container"> - <input type="submit" value class="button" id="btn"> - </div> - </div> - </form> - <div id="responses"></div> -</div> -<template id="response-template"> - <div class="response-container"> - <div class="title response-text"></div> - <a class="copy-text"><strong>Copy Link</strong></a> - </div> -</template> -</body> -</html> \ No newline at end of file diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..0650abf --- /dev/null +++ b/client/package.json @@ -0,0 +1,35 @@ +{ + "name": "shorest", + "version": "0.1.0", + "private": true, + "homepage": "/client/", + "dependencies": { + "axios": "^0.19.2", + "clipboard-copy": "^3.1.0", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-scripts": "3.4.1", + "shortid": "^2.2.15", + "validator": "^13.0.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": "react-app" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/client/public/android-chrome-192x192.png b/client/public/android-chrome-192x192.png new file mode 100644 index 0000000..501c9fa --- /dev/null +++ b/client/public/android-chrome-192x192.png Binary files differdiff --git a/client/public/android-chrome-512x512.png b/client/public/android-chrome-512x512.png new file mode 100644 index 0000000..d264939 --- /dev/null +++ b/client/public/android-chrome-512x512.png Binary files differdiff --git a/client/public/apple-touch-icon.png b/client/public/apple-touch-icon.png new file mode 100644 index 0000000..249732c --- /dev/null +++ b/client/public/apple-touch-icon.png Binary files differdiff --git a/client/public/favicon-16x16.png b/client/public/favicon-16x16.png new file mode 100644 index 0000000..3ceab15 --- /dev/null +++ b/client/public/favicon-16x16.png Binary files differdiff --git a/client/public/favicon-32x32.png b/client/public/favicon-32x32.png new file mode 100644 index 0000000..652c714 --- /dev/null +++ b/client/public/favicon-32x32.png Binary files differdiff --git a/client/public/favicon.ico b/client/public/favicon.ico new file mode 100644 index 0000000..9d96bed --- /dev/null +++ b/client/public/favicon.ico Binary files differdiff --git a/client/public/index.html b/client/public/index.html new file mode 100644 index 0000000..f8ac79c --- /dev/null +++ b/client/public/index.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <meta + name="description" + content="A simple URL shortener!" + /> + <link rel="apple-touch-icon" sizes="180x180" href="%PUBLIC_URL%/apple-touch-icon.png" /> + <link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/favicon-32x32.png" /> + <link rel="icon" type="image/png" sizes="16x16" href="%PUBLIC_URL%/favicon-16x16.png" /> + <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> + <link rel="mask-icon" href="%PUBLIC_URL%/safari-pinned-tab.svg" color="#5bbad5" /> + <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> + <meta name="msapplication-TileColor" content="#727272" /> + <meta name="theme-color" content="#ffffff" /> + <title>sho.rest</title> + </head> + <body> + <noscript>You need to enable JavaScript to run this app.</noscript> + <div id="root"></div> + </body> +</html> diff --git a/client/public/manifest.json b/client/public/manifest.json new file mode 100644 index 0000000..37e0ec8 --- /dev/null +++ b/client/public/manifest.json @@ -0,0 +1,19 @@ +{ + "name": "sho.rest", + "short_name": "sho.rest", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/client/public/mstile-150x150.png b/client/public/mstile-150x150.png new file mode 100644 index 0000000..acb3438 --- /dev/null +++ b/client/public/mstile-150x150.png Binary files differdiff --git a/client/public/robots.txt b/client/public/robots.txt new file mode 100644 index 0000000..e9e57dc --- /dev/null +++ b/client/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/client/public/safari-pinned-tab.svg b/client/public/safari-pinned-tab.svg new file mode 100644 index 0000000..2b7a7b4 --- /dev/null +++ b/client/public/safari-pinned-tab.svg @@ -0,0 +1 @@ +<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="1266.667" height="1266.667" viewBox="0 0 950.000000 950.000000"><path d="M.3 340.2C.6 713.1.1 682.2 6.1 712c10.2 50.8 36.2 101.5 72.1 140.7 48.4 53 112.1 85.8 185.3 95.5 14.7 1.9 408.3 1.9 423 0 63.9-8.5 120.4-34.4 166.2-76.4 53-48.4 85.8-112.1 95.5-185.3 1.9-14.7 1.9-408.3 0-423-9.7-73.2-42.5-136.9-95.5-185.3C813.5 42.3 762.8 16.3 712 6.1 682.2.1 713.1.6 340.2.3L0 0l.3 340.2zM669.5 84.1C773.8 93 857 176.2 865.9 280.5c1.5 16.4 1.5 372.6 0 389C857 773.8 773.8 857 669.5 865.9c-16.4 1.5-372.6 1.5-389 0C176.2 857 93 773.8 84.1 669.5c-.7-7.9-1.1-109.9-1.1-299.3V83h287.3c189.3 0 291.3.4 299.2 1.1z"/><path d="M449.4 267.6c-21.3 2.9-45.7 10.8-61.9 20-28.1 16.1-45.8 38.2-53.3 66.4-2.1 8.1-2.5 11.7-2.5 24.5 0 16.8 1.5 25.8 6.5 38.4 11.2 28.4 36.6 52 75.1 70.1 12.7 5.9 33 13.7 60.2 23 47.9 16.5 63 26.2 69.7 44.8 3 8.4 3 24.6-.1 32.5-5.7 14.6-18.9 24.2-39 28.2-11.9 2.5-41.1 1.7-52.6-1.4-19.6-5.2-34-15.8-40.8-30.2-3.9-8.3-6.7-19.3-6.7-26.8 0-8.9 4-8.1-43.3-8.1h-42l.6 11.2c2.1 37.7 21.3 70.6 54.3 92.9 9.1 6.1 28.9 15.8 39.4 19.3 9.8 3.2 21.4 6.2 32.5 8.2 11.9 2.2 51.5 3.1 65.7 1.5 54.3-6.2 93.4-29.7 110.3-66.4 11.6-25.1 12-61.4.9-88.7-10.9-27.1-35-49.6-71.9-67.5-15.7-7.6-28-12.3-52-20-34-11-53.9-19.7-66.5-29.2-7-5.2-13.3-14.2-15-21.3-1.7-7-.8-18.1 2-25.5 3-7.9 12.8-17.8 22-22.2 12.1-5.8 17.5-6.8 38.5-6.8 17.1 0 19.7.2 26.5 2.3 16.2 5.1 27.7 14.1 33.9 26.6 3.4 7 6.1 17.5 6.1 24.3v4.3h85.1l-.6-7.8c-2.1-26.4-9.8-47-24.6-66.2-20.8-26.9-54.4-44.7-95.2-50.5-12.9-1.9-47.5-1.8-61.3.1z"/></svg> \ No newline at end of file diff --git a/client/src/App.css b/client/src/App.css new file mode 100644 index 0000000..07c14c2 --- /dev/null +++ b/client/src/App.css @@ -0,0 +1,189 @@ +.title { + padding-left: 30px; + margin-bottom: 10px; + line-height: 1.42857143; + color: #E0E0E0; +} + +.response-text { + margin-bottom: 0; +} + +.copy-text { + color: #E0E0E0; + padding-right: 30px; + text-align: right; + -webkit-touch-callout: none !important; + -webkit-user-select: none !important; + -moz-user-select: none !important; + -ms-user-select: none !important; + user-select: none !important; +} + +.input-group { + position: relative; + display: table; + border-color: #E0E0E0; + border-collapse: separate; + transition: border-color 1s; +} + +.response-container { + display: flex; + align-items: center; + border: 2px solid #E0E0E0; + margin: 15px 30px 0 30px; + height: 14vh; + justify-content: space-between; + border-radius: 0 5vh 5vh 5vh; +} + +.input-field { + position: relative; + z-index: 2; + margin-bottom: 0; + text-indent: 0; + color: #727272; + background-color: #fff; + background-image: none; + box-sizing: border-box; + border: none; + outline: none; + height: 100%; + width: 100%; +} + +.input-field-text { + margin-left: 30px; + box-sizing: border-box; + color: #E0E0E0; +} + +.input-container { + z-index: 2; + width: 100%; + float: left; + display: flex; + align-items: center; + position: relative; + padding: 0; + border-radius: 5vh 0 0 5vh; + border: 2px solid; + border-color: inherit; + margin: 0; + box-sizing: border-box; + height: 10vh; +} + +.button-container { + display: table-cell; + position: relative; + font-size: 0; + white-space: nowrap; + width: 1%; + height: 0; + vertical-align: middle; + border-color: inherit; +} + +.button { + z-index: 2; + margin-left: -1px; + display: inline-block; + margin-bottom: 0; + font-weight: 400; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + background-image: none; + background-color: #fff; + color: #727272; + padding: 4px 5vw; + height: 10vh; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + border-radius: 0 5vh 5vh 0; + border: 2px solid; + border-left-style: none; + transition: color 1s; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +strong { + font-weight: normal; + color: #727272; +} + +a { + text-decoration: none; +} + +.disabled { + border-color: #ffbcbc; + color: #ffbcbc; +} + +.border-r-none { + border-right: none; +} + +#btn { + border-color: inherit; +} + +@-webkit-keyframes scale { + 0% { + -webkit-transform: scale(1); + transform: scale(1); + opacity: 1; } + 45% { + -webkit-transform: scale(0.1); + transform: scale(0.1); + opacity: 0.2; } + 80% { + -webkit-transform: scale(1); + transform: scale(1); + opacity: 1; } } + +@keyframes scale { + 0% { + -webkit-transform: scale(1); + transform: scale(1); + opacity: 1; } + 45% { + -webkit-transform: scale(0.1); + transform: scale(0.1); + opacity: 0.2; } + 80% { + -webkit-transform: scale(1); + transform: scale(1); + opacity: 1; } } + +.ball-pulse > div:nth-child(1) { + -webkit-animation: scale 0.75s -0.24s infinite cubic-bezier(0.2, 0.68, 0.18, 1.08); + animation: scale 0.75s -0.48s infinite cubic-bezier(0.2, 0.68, 0.18, 1.08); } + +.ball-pulse > div:nth-child(2) { + -webkit-animation: scale 0.75s -0.12s infinite cubic-bezier(0.2, 0.68, 0.18, 1.08); + animation: scale 0.75s -0.36s infinite cubic-bezier(0.2, 0.68, 0.18, 1.08); } + +.ball-pulse > div:nth-child(3) { + -webkit-animation: scale 0.75s 0s infinite cubic-bezier(0.2, 0.68, 0.18, 1.08); + animation: scale 0.75s -0.24s infinite cubic-bezier(0.2, 0.68, 0.18, 1.08); } + +.ball-pulse > div { + background-color: #727272; + width: 6px; + height: 6px; + margin: 5px; + border-radius: 100%; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + display: inline-block; } \ No newline at end of file diff --git a/client/src/App.js b/client/src/App.js new file mode 100644 index 0000000..135a037 --- /dev/null +++ b/client/src/App.js @@ -0,0 +1,25 @@ +import React, {useState} from 'react'; +import './App.css'; +import Title from './Components/Title'; +import Form from './Components/Form'; +import ResponseContainer from './Components/ResponseContainer'; +import shortid from 'shortid'; + +function App() { + const [requests, setRequests] = useState([]); + + const addRequest = (newRequest) => { + const newRequests = [{url: newRequest, key: shortid.generate()}, ...requests]; + setRequests(newRequests.slice(0, 2)); + } + + return ( + <div> + <Title/> + <Form addRequest={addRequest}/> + <ResponseContainer requests={requests}/> + </div> + ); +} + +export default App; diff --git a/client/src/Components/Button.js b/client/src/Components/Button.js new file mode 100644 index 0000000..46c0c27 --- /dev/null +++ b/client/src/Components/Button.js @@ -0,0 +1,11 @@ +import React from 'react'; + +function Button(props) { + return ( + <div className="button-container"> + <input type="submit" value={props.valid ? "→" : ""} className="button" id="btn" onClick={props.submit}/> + </div> + ) +} + +export default Button; \ No newline at end of file diff --git a/client/src/Components/CopyButton.js b/client/src/Components/CopyButton.js new file mode 100644 index 0000000..0ae8e83 --- /dev/null +++ b/client/src/Components/CopyButton.js @@ -0,0 +1,24 @@ +import React, {useState} from 'react'; +import copy from 'clipboard-copy'; + +function CopyButton(props) { + const [copied, setCopied] = useState(false); + + const handleClick = async () => { + await copy("https://sho.rest/" + props.hash); + setCopied(true); + }; + + let content; + if (copied) { + content = <span>Link Copied!</span>; + } else { + content = <strong>Copy Link</strong>; + } + + return ( + <span className="copy-text" onClick={handleClick}>{content}</span> + ) +} + +export default CopyButton; \ No newline at end of file diff --git a/client/src/Components/Form.js b/client/src/Components/Form.js new file mode 100644 index 0000000..4d10f98 --- /dev/null +++ b/client/src/Components/Form.js @@ -0,0 +1,33 @@ +import React, {useState} from 'react'; +import Button from './Button'; +import isURL from "validator/lib/isURL"; + +function Form(props) { + const [state, setState] = useState({value: '', valid: false}); + + const handleSubmit = () => { + if (state.valid) { + props.addRequest(state.value); + } + }; + + const handleChange = (e) => { + const userInput = e.target.value; + const valid = isURL('https://' + userInput); + setState({value: userInput, valid: valid}); + }; + + return ( + <form id="form" onSubmit={(e) => e.preventDefault()}> + <div className={"input-group" + (state.valid ? "" : " disabled")}> + <div className={"input-container" + (state.valid ? "" : " border-r-none")}> + <span className="input-field-text">https://</span> + <input className="input-field" required onChange={handleChange}/> + </div> + <Button valid={state.valid} submit={handleSubmit}/> + </div> + </form> + ) +} + +export default Form; \ No newline at end of file diff --git a/client/src/Components/Loader.js b/client/src/Components/Loader.js new file mode 100644 index 0000000..3f4c47f --- /dev/null +++ b/client/src/Components/Loader.js @@ -0,0 +1,13 @@ +import React from 'react'; + +function Loader() { + return ( + <div className="ball-pulse"> + <div/> + <div/> + <div/> + </div> + ) +} + +export default Loader; \ No newline at end of file diff --git a/client/src/Components/Response.js b/client/src/Components/Response.js new file mode 100644 index 0000000..83c6ff1 --- /dev/null +++ b/client/src/Components/Response.js @@ -0,0 +1,50 @@ +import React, {useEffect, useState} from 'react'; +import axios from "axios"; +import Loader from "./Loader"; +import CopyButton from "./CopyButton"; + +function Response(props){ + const CancelToken = axios.CancelToken; + const [requestState, setRequestState] = useState({loading: true, cancel: CancelToken.source()}); + + useEffect(() => { + axios.post('/', {url: "https://" + props.url}, {cancelToken: requestState.cancel.token}) + .then((r) => { + setRequestState({loading: false, hash: r.data.hash, cancel: requestState.cancel}); + }).catch((e) => { + if (!axios.isCancel(e)) { + setRequestState({loading: false, error: true, cancel: requestState.cancel}); + } + }); + + return () => { + requestState.cancel.cancel(); + }; + }, [props.url, requestState.cancel]) + + let text; + if (!requestState.loading) { + if (!requestState.error) { + if (props.url.length < 20) { + text = + <span>The short link for <strong>{props.url}</strong> is<br/><strong>sho.rest/{requestState.hash}</strong></span>; + } else { + text = + <span>The short link for your URL is<br/><strong>sho.rest/{requestState.hash}</strong></span>; + } + } else { + text = <span>There was an error.</span> + } + } else { + text = <Loader/> + } + + return ( + <div className={"response-container" + (requestState.error ? " disabled" : "")}> + <div className={"title response-text" + (requestState.error ? " disabled" : "")}>{text}</div> + {requestState.error || requestState.loading ? "" : <CopyButton hash={requestState.hash}/>} + </div> + ) +} + +export default Response; \ No newline at end of file diff --git a/client/src/Components/ResponseContainer.js b/client/src/Components/ResponseContainer.js new file mode 100644 index 0000000..8fad4bd --- /dev/null +++ b/client/src/Components/ResponseContainer.js @@ -0,0 +1,12 @@ +import React from 'react'; +import Response from "./Response"; + +function ResponseContainer(props){ + const responseContent = props.requests.map((r) => <Response key={r.key} url={r.url}/>); + + return ( + responseContent + ) +} + +export default ResponseContainer; \ No newline at end of file diff --git a/client/src/Components/Title.js b/client/src/Components/Title.js new file mode 100644 index 0000000..8ea96a9 --- /dev/null +++ b/client/src/Components/Title.js @@ -0,0 +1,12 @@ +import React from 'react'; + +function Title() { + return ( + <div className="title"> + <span><b>sho.rest</b><br/></span> + <span>Made with ❤ by <b>Mel</b></span> + </div> + ) +} + +export default Title; \ No newline at end of file diff --git a/client/src/index.css b/client/src/index.css new file mode 100644 index 0000000..d007edc --- /dev/null +++ b/client/src/index.css @@ -0,0 +1,13 @@ +html * { + font-family: Roboto, sans-serif; + font-size: 3vmin; +} + +#root { + justify-content: center; + position: absolute; + width: 100%; + box-sizing: border-box; + top: 40%; + padding: 0 50px 0 50px; +} \ No newline at end of file diff --git a/client/src/index.js b/client/src/index.js new file mode 100644 index 0000000..f5185c1 --- /dev/null +++ b/client/src/index.js @@ -0,0 +1,17 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import './index.css'; +import App from './App'; +import * as serviceWorker from './serviceWorker'; + +ReactDOM.render( + <React.StrictMode> + <App /> + </React.StrictMode>, + document.getElementById('root') +); + +// If you want your app to work offline and load faster, you can change +// unregister() to register() below. Note this comes with some pitfalls. +// Learn more about service workers: https://bit.ly/CRA-PWA +serviceWorker.unregister(); diff --git a/client/src/serviceWorker.js b/client/src/serviceWorker.js new file mode 100644 index 0000000..b04b771 --- /dev/null +++ b/client/src/serviceWorker.js @@ -0,0 +1,141 @@ +// This optional code is used to register a service worker. +// register() is not called by default. + +// This lets the app load faster on subsequent visits in production, and gives +// it offline capabilities. However, it also means that developers (and users) +// will only see deployed updates on subsequent visits to a page, after all the +// existing tabs open on the page have been closed, since previously cached +// resources are updated in the background. + +// To learn more about the benefits of this model and instructions on how to +// opt-in, read https://bit.ly/CRA-PWA + +const isLocalhost = Boolean( + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.0/8 are considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) +); + +export function register(config) { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebook/create-react-app/issues/2374 + return; + } + + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + + if (isLocalhost) { + // This is running on localhost. Let's check if a service worker still exists or not. + checkValidServiceWorker(swUrl, config); + + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit https://bit.ly/CRA-PWA' + ); + }); + } else { + // Is not localhost. Just register service worker + registerValidSW(swUrl, config); + } + }); + } +} + +function registerValidSW(swUrl, config) { + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + if (installingWorker == null) { + return; + } + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the updated precached content has been fetched, + // but the previous service worker will still serve the older + // content until all client tabs are closed. + console.log( + 'New content is available and will be used when all ' + + 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' + ); + + // Execute callback + if (config && config.onUpdate) { + config.onUpdate(registration); + } + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + + // Execute callback + if (config && config.onSuccess) { + config.onSuccess(registration); + } + } + } + }; + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); +} + +function checkValidServiceWorker(swUrl, config) { + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl, { + headers: { 'Service-Worker': 'script' }, + }) + .then(response => { + // Ensure service worker exists, and that we really are getting a JS file. + const contentType = response.headers.get('content-type'); + if ( + response.status === 404 || + (contentType != null && contentType.indexOf('javascript') === -1) + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then(registration => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl, config); + } + }) + .catch(() => { + console.log( + 'No internet connection found. App is running in offline mode.' + ); + }); +} + +export function unregister() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready + .then(registration => { + registration.unregister(); + }) + .catch(error => { + console.error(error.message); + }); + } +} diff --git a/client/static/main.css b/client/static/main.css deleted file mode 100644 index e24db3a..0000000 --- a/client/static/main.css +++ /dev/null @@ -1,141 +0,0 @@ -html * { - font-family: Roboto, sans-serif; - font-size: 3vmin; -} - -.active { - justify-content: center; - position: absolute; - width: 100%; - box-sizing: border-box; - top: 40%; - padding: 0 50px 0 50px; -} - -.title { - padding-left: 30px; - margin-bottom: 10px; - line-height: 1.42857143; - color: #E0E0E0; -} - -.response-text { - margin-bottom: 0; -} - -.copy-text { - color: #E0E0E0; - padding-right: 30px; - text-align: right; - -webkit-touch-callout: none !important; - -webkit-user-select: none !important; - -moz-user-select: none !important; - -ms-user-select: none !important; - user-select: none !important; -} - -.input-group { - position: relative; - display: table; - border-color: #E0E0E0; - border-collapse: separate; - transition: border-color 1s; -} - -.response-container { - display: flex; - align-items: center; - border: 2px solid #E0E0E0; - margin: 15px 30px 0 30px; - height: 14vh; - justify-content: space-between; - border-radius: 0 5vh 5vh 5vh; -} - -.input-field { - position: relative; - z-index: 2; - margin-bottom: 0; - text-indent: 0; - color: #727272; - background-color: #fff; - background-image: none; - box-sizing: border-box; - border: none; - outline: none; - height: 100%; - width: 100%; -} - -.input-field-text { - margin-left: 30px; - box-sizing: border-box; - color: #E0E0E0; -} - -.input-container { - z-index: 2; - width: 100%; - float: left; - display: flex; - align-items: center; - position: relative; - padding: 0; - border-radius: 5vh 0 0 5vh; - border: 2px solid; - border-color: inherit; - margin: 0; - box-sizing: border-box; - height: 10vh; -} - -.button-container { - display: table-cell; - position: relative; - font-size: 0; - white-space: nowrap; - width: 1%; - height: 0; - vertical-align: middle; - border-color: inherit; -} - -.button { - z-index: 2; - margin-left: -1px; - display: inline-block; - margin-bottom: 0; - font-weight: 400; - text-align: center; - white-space: nowrap; - vertical-align: middle; - -ms-touch-action: manipulation; - touch-action: manipulation; - cursor: pointer; - background-image: none; - background-color: #fff; - color: #727272; - padding: 4px 5vw; - height: 10vh; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - border-radius: 0 5vh 5vh 0; - border: 2px solid; - border-color: inherit; - border-left: none; - transition: color 1s; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; -} - -strong { - font-weight: normal; - color: #727272; -} - -a { - text-decoration: none; -} \ No newline at end of file diff --git a/client/static/main.js b/client/static/main.js deleted file mode 100644 index 5c8f452..0000000 --- a/client/static/main.js +++ /dev/null @@ -1,91 +0,0 @@ -$(document).ready(function() { - const FORM = $('#form'); - const URL_FIELD = $('#url'); - - FORM.on('submit', onFormSubmit); - FORM.attr("novalidate",true); - URL_FIELD.on({'input': inputUpdate, 'paste': pasteTrim}); - - function onFormSubmit() { - if (validateURL(URL_FIELD.val())) { - const data = JSON.stringify({'url': 'https://' + URL_FIELD.val()}); - $.ajax('/', {method: 'POST', data: data, contentType: 'application/json'}).then(onSuccess); - } - return false; - } - - function onSuccess(response) { - const responseDiv = $('#response-template')[0].content.querySelector('div'); - const node = document.importNode(responseDiv, true); - let text; - if (URL_FIELD.val().length < 20 ) { - text = 'The short link for <strong>' + URL_FIELD.val() + '</strong> is<br><strong>sho.rest/' + response.hash + '</strong>'; - } else { - text = 'The short link for your URL is<br><strong>sho.rest/' + response.hash + '</strong>'; - } - node.querySelector('.response-text').innerHTML = text; - $(node).find('.copy-text').on('click', copyClick); - $('#responses')[0].prepend(node); - } - - function inputUpdate() { - const visible = validateURL(URL_FIELD.val()) - if (!FORM[0].hasAttribute('disabled') === visible) return; - - const valuesDisabled = {borderColor: '#FFBCBC', borderRight: 'none', buttonValue: '', buttonValueColor: '#FFFFFF'}; - const valuesEnabled = {borderColor: '#E0E0E0', borderRight: '', buttonValue: '→', buttonValueColor: '#727272'}; - - const btn = $('#btn'); - const left = $('#left'); - const formGroup = $('#form-group'); - - let values; - if (visible) { - values = valuesEnabled; - FORM.removeAttr('disabled'); - } else { - values = valuesDisabled; - FORM[0].setAttribute('disabled', ''); - } - - formGroup.css('border-color', values.borderColor); - left.css('border-right', values.borderRight); - btn.css('color', values.buttonValueColor); - btn.val(values.buttonValue); - } - - function pasteTrim() { - const pattern = /^https?:\/\//; - setTimeout(() => { - URL_FIELD.val(URL_FIELD.val().replace(pattern, '')); - inputUpdate(); - }, 0); - } - - inputUpdate(); -}); - -function copyClick(event) { - const target = $(event.target); - if (target.hasClass('copied')) return; - const copyText = target.closest('.copy-text'); - const previousCopied = $('.copied'); - - previousCopied.removeClass('copied'); - previousCopied.html('<strong>Copy Link</strong>'); - copyText.html('Link Copied!'); - copyText.addClass('copied'); - - const link = copyText.parent().find('.response-text strong').last(); - - const range = document.createRange(); - range.selectNode(link[0]); - window.getSelection().removeAllRanges(); - window.getSelection().addRange(range); - document.execCommand('copy'); - window.getSelection().removeAllRanges(); -} - -function validateURL(url) { - return !validate({website: 'https://' + url}, {website: {url: true}}); -} \ No newline at end of file |
