Rearrangement

This commit is contained in:
SinTan1729
2023-04-02 16:53:55 -05:00
parent d9f7e9537d
commit 0e97516759
15 changed files with 1275 additions and 98 deletions

26
actix/resources/404.html Normal file
View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<title>Error 404</title>
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<style>
#quote {
text-indent: 4em;
}
</style>
<body style="text-align: center;">
<h1>Error 404!</h1>
<div style="display: inline-block; text-align:left;">
<p>You step in the stream,</p>
<p>but the water has moved on.</p>
<p>The page is not here.</p>
<p id="quote"> — Cass Whittington</p>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

117
actix/resources/index.html Normal file
View File

@@ -0,0 +1,117 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Simply Shorten</title>
<meta name="description" content="A simple selfhosted URL shortener with no unnecessary features.">
<meta name="keywords" content="url shortener, link shortener, self hosted, open source">
<link rel="icon" type="image/x-icon" href="assets/favicon.ico" sizes="any">
<link rel="icon" type="image/svg+xml" href="assets/favicon.svg">
<link rel="icon" type="image/png" href="assets/favicon-32.png" sizes="32x32">
<link rel="icon" type="image/png" href="assets/favicon-196.png" sizes="196x196">
<link rel="stylesheet" href="https://unpkg.com/purecss@1.0.1/build/pure-min.css"
integrity="sha384-oAOxQR6DkCoMliIh8yFnu25d7Eq/PHS21PClpwjOTeU2jRSq11vu66rf90/cZr47" crossorigin="anonymous">
<script src="js/main.js"></script>
<style>
.container {
max-width: 950px;
margin: 20px auto auto;
}
table {
width: 100%;
}
table tr td div {
max-height: 75px;
line-height: 25px;
word-wrap: break-word;
max-width: 575px;
overflow: auto;
}
td[name="deleteBtn"] {
text-align: center;
}
td[name="deleteBtn"] button {
border-radius: 50%;
border-style: solid;
cursor: pointer;
background-color: transparent;
}
input {
width: 65%;
}
form input[name="shortUrl"] {
text-transform: lowercase;
}
form input[name="shortUrl"]::placeholder {
text-transform: none;
}
div[name="gitlab"] {
position: absolute;
right: 0.5%;
bottom: 0.5%;
}
</style>
</head>
<body>
<div class="container">
<form class="pure-form pure-form-aligned" name="new-url-form">
<fieldset>
<legend style="font-size: 32px;"><img src="assets/favicon-32.png" width="26px" alt="logo"> Simply
Shorten</legend>
<div class="pure-control-group">
<label for="longUrl">Long URL</label>
<input type="url" name="longUrl" id="longUrl" placeholder="Please enter a valid URL"
onblur="addProtocol(this)" required />
</div>
<div class=" pure-control-group">
<label for="shortUrl">Short URL (optional)</label>
<input type="text" name="shortUrl" id="shortUrl" placeholder="Only a-z, 0-9, - and _ are allowed"
pattern="[A-Za-z0-9_-]+" />
</div>
<div class="pure-controls">
<button class="pure-button pure-button-primary">Shorten!</button>
</div>
</fieldset>
</form>
<table class="pure-table" style="visibility: hidden;">
<caption style="font-size: 22px; text-align: left; font-style: normal;">Active links</caption>
<br>
<thead>
<tr>
<td id="short-url-header">Short URL (click to copy)</td>
<td>Long URL</td>
<td>Hits</td>
<td name="deleteBtn">&times;</td>
</tr>
</thead>
<tbody id="url-table">
</tbody>
</table>
</div>
<div name="gitlab">
<a href="https://gitlab.com/SinTan1729/simply-shorten" target="_blank" rel="noopener noreferrer">Source Code</a>
</div>
</body>
</html>

166
actix/resources/js/main.js Normal file
View File

@@ -0,0 +1,166 @@
const getSiteUrl = async () => await fetch("/api/site")
.then(res => res.text())
.then(text => {
if (text == "unset") {
return window.location.host;
}
else {
return text;
}
});
const refreshData = async () => {
let data = await fetch("/api/all").then(res => res.text());
data = data
.split("\n")
.filter(line => line !== "")
.map(line => line.split(","))
.map(arr => ({
short: arr[0],
long: arr[1],
hits: arr[2]
}));
displayData(data);
};
const displayData = async (data) => {
let site = await getSiteUrl();
table_box = document.querySelector(".pure-table");
if (data.length == 0) {
table_box.style.visibility = "hidden";
}
else {
const table = document.querySelector("#url-table");
if (!window.isSecureContext) {
const shortUrlHeader = document.getElementById("short-url-header");
shortUrlHeader.innerHTML = "Short URL (right click and copy)";
}
table_box.style.visibility = "visible";
table.innerHTML = ''; // Clear
data.forEach(tr => table.appendChild(TR(tr, site)));
}
};
const showAlert = async (text, col) => {
document.getElementById("alertBox")?.remove();
const controls = document.querySelector(".pure-controls");
const alertBox = document.createElement("p");
alertBox.setAttribute("id", "alertBox");
alertBox.setAttribute("style", `color:${col}`);
alertBox.innerHTML = text;
controls.appendChild(alertBox);
};
const TR = (row, site) => {
const tr = document.createElement("tr");
const longTD = TD(A_LONG(row.long));
var shortTD = null;
if (window.isSecureContext) {
shortTD = TD(A_SHORT(row.short, site));
}
else {
shortTD = TD(A_SHORT_INSECURE(row.short, site));
}
const hitsTD = TD(row.hits);
const btn = deleteButton(row.short);
tr.appendChild(shortTD);
tr.appendChild(longTD);
tr.appendChild(hitsTD);
tr.appendChild(btn);
return tr;
};
const copyShortUrl = async (link) => {
const site = await getSiteUrl();
try {
navigator.clipboard.writeText(`${site}/${link}`);
showAlert(`Short URL ${link} was copied to clipboard!`, "green");
} catch (e) {
console.log(e);
showAlert("Could not copy short URL to clipboard, please do it manually.", "red");
}
};
const addProtocol = (input) => {
var url = input.value.trim();
if (url != "" && !~url.indexOf("://") && !~url.indexOf("magnet:")) {
url = "https://" + url;
}
input.value = url;
return input
}
const A_LONG = (s) => `<a href='${s}'>${s}</a>`;
const A_SHORT = (s, t) => `<a href="javascript:copyShortUrl('${s}');">${s}</a>`;
const A_SHORT_INSECURE = (s, t) => `<a href="${t}/${s}">${s}</a>`;
const deleteButton = (shortUrl) => {
const td = document.createElement("td");
const btn = document.createElement("button");
btn.innerHTML = "&times;";
btn.onclick = e => {
e.preventDefault();
if (confirm("Do you want to delete the entry " + shortUrl + "?")) {
document.getElementById("alertBox")?.remove();
fetch(`/api/${shortUrl}`, {
method: "DELETE"
}).then(_ => refreshData());
}
};
td.setAttribute("name", "deleteBtn");
td.appendChild(btn);
return td;
};
const TD = (s) => {
const td = document.createElement("td");
const div = document.createElement("div");
div.innerHTML = s;
td.appendChild(div);
return td;
};
const submitForm = () => {
const form = document.forms.namedItem("new-url-form");
const longUrl = form.elements["longUrl"];
const shortUrl = form.elements["shortUrl"];
const url = `/api/new`;
fetch(url, {
method: "POST",
body: `${longUrl.value};${shortUrl.value}`
})
.then(res => {
if (!res.ok) {
showAlert("Short URL is not valid or it's already in use!", "red");
return "error";
}
else {
return res.text();
}
}).then(text => {
if (text != "error") {
copyShortUrl(text);
longUrl.value = "";
shortUrl.value = "";
refreshData();
}
});
};
(async () => {
await refreshData();
const form = document.forms.namedItem("new-url-form");
form.onsubmit = e => {
e.preventDefault();
submitForm();
}
})();