Fixes
This commit is contained in:
8 files changed
+123
-110
No files matched your search
+27
-11
@@ -310,15 +310,6 @@ button:hover {
|
||||
color: #923e31;
|
||||
}
|
||||
|
||||
.link-button {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
color: var(--accent);
|
||||
text-decoration: underline;
|
||||
text-align: left;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: wait;
|
||||
@@ -376,8 +367,33 @@ hr {
|
||||
color: #7f3022;
|
||||
}
|
||||
|
||||
.result, .preview {
|
||||
margin-top: 1.25rem;
|
||||
dialog.result {
|
||||
box-sizing: border-box;
|
||||
width: min(36rem, calc(100% - 2rem));
|
||||
max-height: calc(100vh - 2rem);
|
||||
box-shadow: 0 1.5rem 5rem rgb(20 35 20 / 20%);
|
||||
}
|
||||
|
||||
dialog::backdrop {
|
||||
background: rgb(20 35 20 / 45%);
|
||||
backdrop-filter: blur(3px);
|
||||
}
|
||||
|
||||
.result h2 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
|
||||
.result #share-url {
|
||||
font-family: ui-monospace, monospace;
|
||||
}
|
||||
|
||||
.result-actions {
|
||||
justify-content: space-between;
|
||||
margin-block: 1.5rem;
|
||||
}
|
||||
|
||||
.result [data-error="true"] {
|
||||
color: #7f3022;
|
||||
}
|
||||
|
||||
.result p:last-child {
|
||||
|
||||
+29
-75
@@ -6,12 +6,13 @@ let notes = [];
|
||||
let editing = null;
|
||||
|
||||
function notify(message, error = false) {
|
||||
$("status").textContent = message;
|
||||
$("status").dataset.error = String(error);
|
||||
$("status").hidden = false;
|
||||
const target = $("share-result").open ? $("result-feedback") : $("status");
|
||||
target.textContent = message;
|
||||
target.dataset.error = String(error);
|
||||
target.hidden = false;
|
||||
}
|
||||
|
||||
async function api(path, { method = "GET", body, accept = "text/plain", includeHeaders = false } = {}, auth = authorization) {
|
||||
async function api(path, { method = "GET", body, accept = "text/plain" } = {}, auth = authorization) {
|
||||
const headers = { Accept: accept };
|
||||
if (auth) headers.Authorization = auth;
|
||||
const response = await fetch(path, { method, body, headers, credentials: "omit", cache: "no-store" });
|
||||
@@ -26,8 +27,7 @@ async function api(path, { method = "GET", body, accept = "text/plain", includeH
|
||||
};
|
||||
throw new Error(messages[response.status] || `The request failed (${response.status}). Please try again.`);
|
||||
}
|
||||
const data = accept === "application/json" ? await response.json() : await response.text();
|
||||
return includeHeaders ? { data, headers: response.headers } : data;
|
||||
return accept === "application/json" ? response.json() : response.text();
|
||||
}
|
||||
|
||||
async function busy(button, action) {
|
||||
@@ -96,7 +96,10 @@ function renderNotes() {
|
||||
for (const note of filtered) {
|
||||
const row = document.createElement("tr");
|
||||
const name = cell(row, "");
|
||||
name.append(action(note.id, () => openEntry(note), "link-button"));
|
||||
const link = document.createElement("a");
|
||||
link.href = sharePath(note.id, isUrl(note));
|
||||
link.textContent = note.id;
|
||||
name.append(link);
|
||||
const type = document.createElement("small");
|
||||
type.textContent = isUrl(note) ? "Short link" : note.language || "Plain text";
|
||||
name.append(type);
|
||||
@@ -154,7 +157,7 @@ function resetEditor() {
|
||||
editing = null;
|
||||
$("language").querySelector("[data-current]")?.remove();
|
||||
$("editor-form").reset();
|
||||
$("share-result").hidden = true;
|
||||
$("share-result").close();
|
||||
updateEditor();
|
||||
}
|
||||
|
||||
@@ -199,53 +202,6 @@ function reference(value, url) {
|
||||
return { id, url };
|
||||
}
|
||||
|
||||
async function openPaste(id) {
|
||||
$("preview").replaceChildren();
|
||||
$("preview").hidden = true;
|
||||
const highlighted = $("open-format").value === "html";
|
||||
const { data: content, headers } = await api(sharePath(id, false), {
|
||||
accept: highlighted ? "text/html" : "text/plain",
|
||||
includeHeaders: true,
|
||||
});
|
||||
const counter = document.createElement("p");
|
||||
counter.className = "badge view-counter";
|
||||
counter.textContent = `Views: ${headers.get("x-paste-views")} / ${headers.get("x-paste-max-views") ?? "∞"}`;
|
||||
$("preview").append(counter);
|
||||
if (highlighted) {
|
||||
const page = new DOMParser().parseFromString(content, "text/html");
|
||||
const code = page.querySelector("main pre");
|
||||
if (!code) throw new Error("The server returned an unexpected preview.");
|
||||
$("preview").append(document.importNode(code, true));
|
||||
} else {
|
||||
const pre = document.createElement("pre");
|
||||
pre.textContent = content;
|
||||
$("preview").append(pre);
|
||||
}
|
||||
$("preview").hidden = false;
|
||||
await loadNotes();
|
||||
notify(`Opened ${id}. This read counts as a view.`);
|
||||
}
|
||||
|
||||
async function openEntry(note) {
|
||||
$("open-id").value = note.id;
|
||||
$("open-kind").value = isUrl(note) ? "url" : "note";
|
||||
updateOpen();
|
||||
showPanel("open");
|
||||
if (isUrl(note)) {
|
||||
window.open(sharePath(note.id, true), "_blank", "noopener,noreferrer");
|
||||
notify("The short link opens in a new tab. Refresh your collection to update its view count.");
|
||||
} else {
|
||||
await openPaste(note.id);
|
||||
}
|
||||
}
|
||||
|
||||
function updateOpen() {
|
||||
const url = $("open-kind").value === "url";
|
||||
$("format-field").hidden = url;
|
||||
$("open-submit").textContent = url ? "Follow short link ↗" : "Open paste";
|
||||
$("preview").hidden = true;
|
||||
}
|
||||
|
||||
$("login-form").addEventListener("submit", event => {
|
||||
event.preventDefault();
|
||||
busy(event.submitter, async () => {
|
||||
@@ -272,8 +228,6 @@ $("logout").addEventListener("click", () => {
|
||||
$("login-form").reset();
|
||||
$("logout").hidden = true;
|
||||
$("session-label").textContent = "Anonymous";
|
||||
$("preview").replaceChildren();
|
||||
$("preview").hidden = true;
|
||||
resetEditor();
|
||||
renderNotes();
|
||||
notify("Signed out.");
|
||||
@@ -282,6 +236,7 @@ $("logout").addEventListener("click", () => {
|
||||
$("editor-form").addEventListener("submit", event => {
|
||||
event.preventDefault();
|
||||
busy(event.submitter, async () => {
|
||||
const current = editing;
|
||||
const url = $("kind").value === "url";
|
||||
const body = new URLSearchParams();
|
||||
body.set(url ? "href" : "content", $("content").value);
|
||||
@@ -290,22 +245,27 @@ $("editor-form").addEventListener("submit", event => {
|
||||
if ($("max-views").value !== "") body.set("max_views", $("max-views").value);
|
||||
const requested = $("custom-id").value.trim();
|
||||
let path = url ? "/to" : $("once").checked ? "/once" : "";
|
||||
if (editing) path = sharePath(editing.id, url);
|
||||
if (current) path = sharePath(current.id, url);
|
||||
else path = requested ? `${path}/${encodeURIComponent(requested)}` : path || "/";
|
||||
const result = await api(path, { method: editing ? "PUT" : "POST", body });
|
||||
const id = editing ? editing.id : result;
|
||||
const result = await api(path, { method: current ? "PUT" : "POST", body });
|
||||
const id = current ? current.id : result;
|
||||
const link = new URL(sharePath(id, url), location.origin).href;
|
||||
$("share-url").value = link;
|
||||
$("share-link").href = link;
|
||||
$("result-title").textContent = editing ? "Changes saved" : "Your share is ready";
|
||||
$("share-result").hidden = false;
|
||||
$("result-title").textContent = current ? "Changes saved" : "Your share is ready";
|
||||
const message = `${current ? "Updated" : "Created"} ${id}.${requested && requested !== id ? " The requested ID was unavailable, so a new one was generated." : ""}`;
|
||||
$("result-feedback").textContent = message;
|
||||
$("result-feedback").dataset.error = "false";
|
||||
$("result-feedback").hidden = false;
|
||||
$("share-result").showModal();
|
||||
$("share-url").focus();
|
||||
$("share-url").select();
|
||||
await loadNotes();
|
||||
if (editing) {
|
||||
if (current && editing === current) {
|
||||
editing = notes.find(note => note.id === id) || editing;
|
||||
$("expires").value = "";
|
||||
updateEditor();
|
||||
}
|
||||
notify(`${editing ? "Updated" : "Created"} ${id}.${requested && requested !== id ? " The requested ID was unavailable, so a new one was generated." : ""}`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -322,17 +282,12 @@ $("copy-url").addEventListener("click", event => busy(event.currentTarget, async
|
||||
|
||||
$("open-form").addEventListener("submit", event => {
|
||||
event.preventDefault();
|
||||
busy(event.submitter, async () => {
|
||||
try {
|
||||
const ref = reference($("open-id").value, $("open-kind").value === "url");
|
||||
$("open-kind").value = ref.url ? "url" : "note";
|
||||
updateOpen();
|
||||
if (ref.url) {
|
||||
window.open(sharePath(ref.id, true), "_blank", "noopener,noreferrer");
|
||||
notify("The short link opens in a new tab. Following it counts as a view.");
|
||||
} else {
|
||||
await openPaste(ref.id);
|
||||
}
|
||||
});
|
||||
location.assign(sharePath(ref.id, ref.url));
|
||||
} catch (error) {
|
||||
notify(error.message, true);
|
||||
}
|
||||
});
|
||||
|
||||
for (const link of document.querySelectorAll("[data-panel]")) {
|
||||
@@ -344,7 +299,6 @@ for (const link of document.querySelectorAll("[data-panel]")) {
|
||||
$("kind").addEventListener("change", updateEditor);
|
||||
$("once").addEventListener("change", updateEditor);
|
||||
$("new-note").addEventListener("click", resetEditor);
|
||||
$("open-kind").addEventListener("change", updateOpen);
|
||||
$("search").addEventListener("input", renderNotes);
|
||||
$("refresh").addEventListener("click", event => busy(event.currentTarget, loadNotes));
|
||||
window.addEventListener("hashchange", () => showPanel(location.hash.slice(1)));
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
"use strict";
|
||||
|
||||
const copy = document.getElementById("copy-code");
|
||||
const status = document.getElementById("copy-status");
|
||||
const fallback = document.getElementById("raw-code");
|
||||
|
||||
copy.addEventListener("click", async () => {
|
||||
const bytes = Uint8Array.from(atob(copy.dataset.raw), character => character.charCodeAt(0));
|
||||
const content = new TextDecoder("utf-8", { ignoreBOM: true }).decode(bytes);
|
||||
copy.disabled = true;
|
||||
try {
|
||||
await navigator.clipboard.writeText(content);
|
||||
fallback.hidden = true;
|
||||
} catch {
|
||||
fallback.value = content;
|
||||
fallback.hidden = false;
|
||||
fallback.focus();
|
||||
fallback.select();
|
||||
} finally {
|
||||
copy.disabled = false;
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user