Simple web UI for shortlink creation (#15)

* add HTML interface (inlined into typescript) - with CSS and autocomplete hand-holding

* Some changes (listed below)

- Make UI optional and disabled by default
- Lint it in line with other code
- Import UI from a bundled HTML file
- Add UI to README
- Add support for random URL generation
- Show the URL in the output

---------

Co-authored-by: Erisa A <erisa@erisa.uk>
This commit is contained in:
infra 2023-04-29 20:43:20 -04:00 committed by GitHub
parent ad4ba3f6f0
commit 1bef54163c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 137 additions and 2 deletions

View file

@ -3,5 +3,13 @@
"semi": false, "semi": false,
"trailingComma": "all", "trailingComma": "all",
"useTabs": true, "useTabs": true,
"printWidth": 80 "printWidth": 80,
"overrides": [
{
"files": "src/*.html",
"options": {
"printWidth": 140
}
}
]
} }

View file

@ -27,6 +27,12 @@ For debuggging, you can use `yarn dev` (Which runs `wrangler dev --local`). To c
You can also debug on the edge with `wrangler dev`, though you will need to first configure a prepview namespace in `wrangler.toml` and add the `WORKERLINKS_SECRET` secret to the Worker. You can also debug on the edge with `wrangler dev`, though you will need to first configure a prepview namespace in `wrangler.toml` and add the `WORKERLINKS_SECRET` secret to the Worker.
## (Optional) User Interface
If `ENABLE_INDEX_FORM` is enabled in `wrangler.toml`, an optional UI form is available when visiting the Worker in a browser, allowing easy creation of links:
![](https://up.erisa.uk/firefox_qFWwv7NIqf.png)
## Usage ## Usage
Once deployed, interacting with the API should be rather simple. It's based on headers, specifically with the `Authorization` and `URL` headers. Once deployed, interacting with the API should be rather simple. It's based on headers, specifically with the `Authorization` and `URL` headers.

View file

@ -5,7 +5,7 @@
"author": "Erisa A", "author": "Erisa A",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"format": "prettier --write '**/*.{ts,js,css,json,md}'", "format": "prettier --write '**/*.{ts,js,css,json,md,html}'",
"deploy": "wrangler publish", "deploy": "wrangler publish",
"dev": "wrangler dev", "dev": "wrangler dev",
"addsecret": "wrangler secret put WORKERLINKS_SECRET", "addsecret": "wrangler secret put WORKERLINKS_SECRET",

106
src/form.html Normal file
View file

@ -0,0 +1,106 @@
<!DOCTYPE html>
<head>
<title>Shorten a URL</title>
<meta content="width=device-width,initial-scale=1" name="viewport" />
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
form {
margin: 0 auto;
max-width: 480px;
padding: 16px;
}
label {
display: block;
margin-bottom: 8px;
}
button[type='submit'] {
background-color: green;
color: #fff;
font-size: 1.5em;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
}
input {
width: 75%;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
padding: 8px;
}
input[type='submit'] {
background-color: #4caf50;
border: none;
color: #fff;
cursor: pointer;
font-size: 16px;
margin-top: 16px;
padding: 8px 16px;
border-radius: 4px;
}
input[type='submit']:hover {
background-color: #3e8e41;
}
#response {
margin-top: 16px;
font-size: 16px;
}
</style>
</head>
<body>
<form id="myForm" onsubmit="submitForm(event)">
<label for="value">Long URL:</label>
<input autocomplete="one-time-code" id="value" name="value" placeholder="https://google.com/search?q=how+does+google" /><br /><br />
<label for="key">Short Key for URL: (optional)</label>
<input autocomplete="one-time-code" id="key" name="key" placeholder="metagoogle" /><br /><br />
<label for="password">Password:</label>
<input autocomplete="password" id="password" name="password" type="password" placeholder="mysecret" /><br /><br />
<button type="submit">Submit</button><br /><br />
<div id="response"></div>
<br />
<span id="url"></span><a id="urlLink"></a>
</form>
<script>
function submitForm(event) {
event.preventDefault()
const key = document.getElementById('key').value
const value = document.getElementById('value').value
const password = document.getElementById('password').value
const xhr = new XMLHttpRequest()
xhr.open(key === '' ? 'POST' : 'PUT', window.location.href + key, true)
xhr.setRequestHeader('Url', value)
xhr.setRequestHeader('Authorization', password)
xhr.responseType = 'json'
xhr.onload = function () {
const response = document.getElementById('response')
response.textContent = 'Response: ' + xhr.response.message
const urlText = document.getElementById('url')
const urlLink = document.getElementById('urlLink')
if (xhr.response.shorturl) {
urlText.textContent = 'URL: '
urlLink.textContent = xhr.response.shorturl
urlLink.href = xhr.response.shorturl
} else {
urlText.textContent = ''
urlLink.textContent = ''
urlLink.href = ''
}
}
xhr.send()
}
</script>
</body>

4
src/html.d.ts vendored Normal file
View file

@ -0,0 +1,4 @@
declare module '*.html' {
const value: string
export default value
}

View file

@ -1,6 +1,9 @@
import { Context as HonoContext, Env as HonoEnv, Hono } from 'hono' import { Context as HonoContext, Env as HonoEnv, Hono } from 'hono'
import * as st from 'simple-runtypes' import * as st from 'simple-runtypes'
// html.d.ts tells typescript that this is a normal thing to do
import creationPageHtml from './form.html'
type Variables = { type Variables = {
path: string path: string
key: string key: string
@ -12,6 +15,7 @@ type Bindings = {
PLAUSIBLE_HOST?: string PLAUSIBLE_HOST?: string
KV: KVNamespace KV: KVNamespace
kv: KVNamespace kv: KVNamespace
ENABLE_INDEX_FORM: boolean
} }
const url = st.runtype((v) => { const url = st.runtype((v) => {
@ -107,6 +111,10 @@ app.get('/', async (c) => {
})), })),
}) })
} else { } else {
const urlResult = await c.env.KV.get(c.get('key'))
if (urlResult == null && c.env.ENABLE_INDEX_FORM) {
return c.html(creationPageHtml)
}
return handleGetHead(c) return handleGetHead(c)
} }
}) })

View file

@ -14,3 +14,6 @@ compatibility_date = "2023-03-05"
# Remove or comment out the route line if using workers_dev # Remove or comment out the route line if using workers_dev
workers_dev = false workers_dev = false
route = { pattern = "erisa.link/*", zone_name = "erisa.link" } route = { pattern = "erisa.link/*", zone_name = "erisa.link" }
[vars]
ENABLE_INDEX_FORM = true