Files
x/static/index.html
Ian Gulliver f768568c3a PNG favicons
2024-12-04 16:33:37 -08:00

336 lines
13 KiB
HTML

<!doctype html>
<html>
<head>
<style>
:not(:defined) {
visibility: hidden;
}
body {
font: 12px var(--sl-font-mono);
display: flex;
flex-direction: column;
align-items: center;
}
sl-input {
margin-bottom: 10px;
}
sl-button {
margin-top: 10px;
}
sl-alert {
margin-top: 20px;
}
sl-tree {
margin-top: 20px;
}
sl-icon[name="type"] {
color: var(--sl-color-danger-500);
}
sl-icon[name="square"] {
color: var(--sl-color-warning-500);
cursor: pointer;
}
sl-icon[name="check-square"] {
color: var(--sl-color-success-500);
}
sl-icon[name="check-square-fill"] {
color: var(--sl-color-success-500);
}
</style>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAYAAABS3GwHAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAuIwAALiMBeKU/dgAAA7VpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDYuMC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+MTkyPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjE5MjwvZXhpZjpQaXhlbFhEaW1lbnNpb24+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+UGl4ZWxtYXRvciBQcm8gMy42LjEzPC94bXA6Q3JlYXRvclRvb2w+CiAgICAgICAgIDx4bXA6Q3JlYXRlRGF0ZT4yMDI0LTEyLTA0VDE2OjMwOjE5LTA4OjAwPC94bXA6Q3JlYXRlRGF0ZT4KICAgICAgICAgPHhtcDpNZXRhZGF0YURhdGU+MjAyNC0xMi0wNFQxNjozMToxNy0wODowMDwveG1wOk1ldGFkYXRhRGF0ZT4KICAgICAgICAgPHRpZmY6WFJlc29sdXRpb24+MzAwMDAwMC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6UmVzb2x1dGlvblVuaXQ+MjwvdGlmZjpSZXNvbHV0aW9uVW5pdD4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+MzAwMDAwMC8x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" />
<link
rel="stylesheet"
media="(prefers-color-scheme:light)"
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.18.0/cdn/themes/light.css"
/>
<link
rel="stylesheet"
media="(prefers-color-scheme:dark)"
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.18.0/cdn/themes/dark.css"
onload="document.documentElement.classList.add('sl-theme-dark');"
/>
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.18.0/cdn/shoelace-autoloader.js"></script>
<script>
function setInputIcon(val, icon) {
if (val.length > 0) {
icon.setAttribute('name', 'square');
} else {
icon.setAttribute('name', 'type');
}
}
function setInputIcons() {
setInputIcon(
document.getElementById('short').value,
document.getElementById('short-icon'),
);
setInputIcon(
document.getElementById('long').value,
document.getElementById('long-icon'),
);
}
function clearAlerts() {
document.getElementById('err').hide();
}
function error(err1, err2) {
clearAlerts();
document.getElementById('err1').innerText = err1;
document.getElementById('err2').innerText = err2;
document.getElementById('err').show();
}
async function setFromInputs() {
const short = document.getElementById('short').value;
const long = document.getElementById('long').value;
if (long == '') {
error('Unable to set', 'Long URL is required');
return;
}
document.getElementById('short-icon').setAttribute('name', 'check-square-fill');
document.getElementById('long-icon').setAttribute('name', 'check-square-fill');
await set(short, long);
}
async function set(short, long) {
if (short != '') {
setShortItem(short, null, 'check-square-fill');
}
const oldShort = document.getElementById('short').value;
const oldLong = document.getElementById('long').value;
let resp;
try {
resp = await fetch('./', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
short: short,
long: long,
}),
});
} catch (err) {
console.log(err);
setTimeout(async () => await set(short, long), 5000);
return;
}
if (resp.status !== 200) {
error('Failed to set', (await resp.json()).message);
return;
}
const data = await resp.json();
const newShort = data.short;
const newURL = data.url;
setShortItem(newShort, newURL, 'check-square');
// Only set the icons if we were actually setting from these inputs
if (document.getElementById('short').value == short && document.getElementById('long').value == long) {
document.getElementById('short-icon').setAttribute('name', 'check-square');
document.getElementById('long-icon').setAttribute('name', 'check-square');
}
// Only set the clipboard if the user didn't change the inputs
if (document.getElementById('short').value == oldShort && document.getElementById('long').value == oldLong) {
try {
await navigator.clipboard.writeText(newURL);
} catch (err) {
console.log(err);
}
}
const shorts = [];
for (const elem of document.getElementById('tree').children) {
const icon = elem.getElementsByTagName('sl-icon')[0];
if (icon.getAttribute('name') == 'check-square-fill' ||
icon.getAttribute('name') == 'check-square') {
shorts.push(elem.textContent);
}
}
try {
resp = await fetch('./', {
method: 'QUERY',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
shorts: shorts,
}),
});
} catch (err) {
console.log(err);
return;
}
for (const short of (await resp.json()).shorts) {
appendShortItem(short, long);
}
}
function setShortItem(short, url, icon) {
const tree = document.getElementById('tree');
for (const item of tree.children) {
if (item.textContent == short) {
tree.removeChild(item);
}
}
const item = document.createElement('sl-tree-item');
item.appendChild(document.createElement('sl-icon')).setAttribute('name', icon);
item.appendChild(document.createTextNode(short));
if (url != null) {
item.addEventListener('click', () => {
navigator.clipboard.writeText(url);
});
}
tree.insertBefore(item, tree.firstChild);
}
function appendShortItem(short, long) {
const tree = document.getElementById('tree');
for (const item of tree.children) {
if (item.textContent == short) {
return;
}
}
const item = document.createElement('sl-tree-item');
item.appendChild(document.createElement('sl-icon')).setAttribute('name', 'square');
item.appendChild(document.createTextNode(short));
item.addEventListener('click', () => {
set(short, long);
});
tree.appendChild(item);
}
document.addEventListener('DOMContentLoaded', async () => {
await Promise.all([
customElements.whenDefined('sl-input'),
customElements.whenDefined('sl-icon'),
customElements.whenDefined('sl-button'),
customElements.whenDefined('sl-alert'),
customElements.whenDefined('sl-tree'),
]);
let shortPaste = false;
document.getElementById('short').addEventListener('sl-input', async () => {
clearAlerts();
setInputIcons();
if (shortPaste) {
shortPaste = false;
await setFromInputs();
}
});
document.getElementById('short').addEventListener('keydown', async (e) => {
if (e.key === 'Enter') {
await setFromInputs();
}
});
document.getElementById('short').addEventListener('paste', async () => {
if (document.getElementById('long').value != '') {
shortPaste = true;
}
});
document.getElementById('short-icon').addEventListener('click', async () => {
await setFromInputs();
});
let longPaste = false;
document.getElementById('long').addEventListener('sl-input', async () => {
clearAlerts();
setInputIcons();
document.getElementById('tree').replaceChildren();
if (longPaste) {
longPaste = false;
await setFromInputs();
}
});
document.getElementById('long').addEventListener('keydown', async (e) => {
if (e.key === 'Enter') {
await setFromInputs();
}
});
document.getElementById('long').addEventListener('paste', () => {
if (document.getElementById('short').value != '') {
longPaste = true;
}
});
document.getElementById('long-icon').addEventListener('click', async () => {
await setFromInputs();
});
document.getElementById('set').addEventListener('click', async () => {
await setFromInputs();
});
document.getElementById('long').focus();
setInputIcons();
});
</script>
</head>
<body>
<div id="container" style="width: min(500px, calc(100vw - 10px))">
<sl-input id="short" value="{{ .path }}" label="{{ .host }}/">
<sl-icon id="short-icon" name="type" slot="suffix"></sl-icon>
</sl-input>
<sl-input id="long" value="{{ .long }}" label="⟶" type="url">
<sl-icon id="long-icon" name="type" slot="suffix"></sl-icon>
</sl-input>
<div style="text-align: center; margin-top: 10px;">
<sl-button variant="primary" id="set">Set</sl-button>
</div>
<sl-alert id="err" variant="danger">
<sl-icon slot="icon" name="exclamation-octagon"></sl-icon>
<strong id="err1"></strong><br />
<span id="err2"></span>
</sl-alert>
<sl-tree id="tree">
</sl-tree>
</div>
</body>
</html>