diff --git a/src/views/home.html b/src/views/home.html
index 5615503..a50d7cc 100644
--- a/src/views/home.html
+++ b/src/views/home.html
@@ -8,11 +8,11 @@ SYNOPSIS
you can also paste or drag files to this page
DESCRIPTION
- works better with text
+ works better with text and images
limited to 20MiB
everything are encrypted
decryption only on browser
- no data or logs are collected
+ no data or logs never collected
EXAMPLES
$ curl <%- BASE_URL %> --data-binary @debug.log
diff --git a/static/home.mjs b/static/home.mjs
index 428c753..d26c82c 100644
--- a/static/home.mjs
+++ b/static/home.mjs
@@ -34,12 +34,14 @@ const create = async (data) => {
codeElement.classList.add("loading");
try {
+ const body =
+ data instanceof File
+ ? await data.arrayBuffer()
+ : new TextEncoder().encode(data);
+
const response = await fetch("/", {
method: "POST",
- headers: {
- "Content-Type": "application/x-www-form-urlencoded",
- },
- body: data,
+ body,
});
const url = await response.text();
diff --git a/static/styles.css b/static/styles.css
index 7f84811..d77134b 100644
--- a/static/styles.css
+++ b/static/styles.css
@@ -29,7 +29,9 @@ body,
code,
pre,
samp {
- font: 100 0.8125rem/1.25rem "JetBrains Mono", monospace;
+ font:
+ 100 0.8125rem/1.25rem "JetBrains Mono",
+ monospace;
}
code,
@@ -44,6 +46,14 @@ pre {
position: relative;
}
+pre.center {
+ display: grid;
+ place-content: center;
+ min-height: 100vh;
+ min-height: 100dvh;
+ padding: 1em;
+}
+
samp {
display: inline-block;
white-space: pre-line;
@@ -55,17 +65,31 @@ a {
text-decoration: none;
}
-img,
-video {
+img {
display: block;
margin: auto;
width: 100%;
max-width: fit-content;
+ object-fit: contain;
+ max-height: calc(100vh - 2em);
+ max-height: calc(100dvh - 2em);
+}
+
+/* Image zoom on click */
+pre.can-zoom img {
+ cursor: zoom-in;
+}
+
+pre.zoomed img {
+ cursor: zoom-out;
+ width: auto;
+ max-width: none;
+ max-height: none;
}
-video {
- max-height: 100vh;
- max-height: 100dvh;
+pre.zoomed {
+ overflow: auto;
+ place-content: safe center;
}
/* noscript hides loading */
diff --git a/static/view.mjs b/static/view.mjs
index 7c8c77f..8d93a3a 100644
--- a/static/view.mjs
+++ b/static/view.mjs
@@ -61,6 +61,24 @@ const selectLines = (preElement) => {
setTimeout(() => markElement.scrollIntoView(), 200);
};
+const detectContentType = (buffer) => {
+ // Magic bytes for common image formats (https://en.wikipedia.org/wiki/Magic_number_(programming))
+ const signatures = [
+ { mime: "image/png", bytes: [0x89, 0x50, 0x4e, 0x47] },
+ { mime: "image/jpeg", bytes: [0xff, 0xd8, 0xff] },
+ { mime: "image/gif", bytes: [0x47, 0x49, 0x46, 0x38] },
+ { mime: "image/webp", bytes: [0x52, 0x49, 0x46, 0x46] },
+ ];
+
+ const view = new Uint8Array(buffer);
+ for (const { mime, bytes } of signatures) {
+ if (view.length < bytes.length) continue;
+ if (bytes.every((b, i) => view[i] === b)) return mime;
+ }
+
+ return "text/plain";
+};
+
addEventListener("DOMContentLoaded", async () => {
// delete noscript tag
document.querySelector("noscript").remove();
@@ -78,8 +96,29 @@ addEventListener("DOMContentLoaded", async () => {
const iv = new Uint8Array(response.headers.get("X-IV")?.split(","));
const cipherText = await response.arrayBuffer();
const buffer = await decrypt(iv, k, cipherText);
-
- childElement.textContent = new TextDecoder().decode(buffer);
+ const type = detectContentType(buffer);
+
+ if (type.startsWith("image/")) {
+ preElement.classList.add("center");
+ const blob = new Blob([buffer], { type });
+ const img = document.createElement("img");
+ img.src = URL.createObjectURL(blob);
+ img.addEventListener("load", () => {
+ // Check if image is larger than its displayed size (needs zoom)
+ if (
+ img.naturalWidth > img.clientWidth ||
+ img.naturalHeight > img.clientHeight
+ ) {
+ preElement.classList.add("can-zoom");
+ img.addEventListener("click", () =>
+ preElement.classList.toggle("zoomed"),
+ );
+ }
+ });
+ childElement = img;
+ } else {
+ childElement.textContent = new TextDecoder().decode(buffer);
+ }
} catch (err) {
console.error(err);
const mappedMessages = new Map([[404, "not found 🙈"]]);
@@ -91,6 +130,9 @@ addEventListener("DOMContentLoaded", async () => {
document.body.appendChild(preElement);
}
+ // Skip highlighting for images
+ if (childElement.tagName === "IMG") return;
+
// force highlight.js instead of detect (default)
if (ext) {
hljs.configure({ languages: [ext] });