fix: bugs in general
This commit is contained in:
@@ -109,7 +109,7 @@ textarea {
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: repeating-linear-gradient(45deg, #c0c0c0 0 2px, #b5b5b5 2px 4px);
|
||||
background: repeating-linear-gradient(45deg, #808080 0 2px, #8f8f8f 2px 4px);
|
||||
border-top: 1px solid #808080;
|
||||
border-left: 1px solid #808080;
|
||||
border-right: 1px solid #ffffff;
|
||||
@@ -126,8 +126,57 @@ textarea {
|
||||
box-shadow: inset -1px -1px 0 #808080, inset 1px 1px 0 #dfdfdf;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button:single-button {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
background-color: #c0c0c0;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 7px 7px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button:single-button:vertical:decrement {
|
||||
background-image: linear-gradient(45deg, transparent 50%, #000000 50%), linear-gradient(135deg, #000000 50%, transparent 50%);
|
||||
background-position: 5px 6px, 8px 6px;
|
||||
background-size: 4px 4px, 4px 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button:single-button:vertical:increment {
|
||||
background-image: linear-gradient(225deg, transparent 50%, #000000 50%), linear-gradient(315deg, #000000 50%, transparent 50%);
|
||||
background-position: 5px 7px, 8px 7px;
|
||||
background-size: 4px 4px, 4px 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button:single-button:horizontal:decrement {
|
||||
background-image: linear-gradient(135deg, transparent 50%, #000000 50%), linear-gradient(45deg, #000000 50%, transparent 50%);
|
||||
background-position: 6px 5px, 6px 8px;
|
||||
background-size: 4px 4px, 4px 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button:single-button:horizontal:increment {
|
||||
background-image: linear-gradient(315deg, transparent 50%, #000000 50%), linear-gradient(225deg, #000000 50%, transparent 50%);
|
||||
background-position: 7px 5px, 7px 8px;
|
||||
background-size: 4px 4px, 4px 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover,
|
||||
::-webkit-scrollbar-button:single-button:hover {
|
||||
background-color: #d0d0d0;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:active,
|
||||
::-webkit-scrollbar-button:single-button:active {
|
||||
border-top-color: #000000;
|
||||
border-left-color: #000000;
|
||||
border-right-color: #ffffff;
|
||||
border-bottom-color: #ffffff;
|
||||
box-shadow: inset -1px -1px 0 #dfdfdf, inset 1px 1px 0 #808080;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background: #c0c0c0;
|
||||
border-top: 1px solid #808080;
|
||||
border-left: 1px solid #808080;
|
||||
}
|
||||
|
||||
.win98-button {
|
||||
@@ -231,10 +280,12 @@ textarea:disabled {
|
||||
.popup-body li { margin: 0 0 4px; }
|
||||
.popup-body .code-block {
|
||||
margin: 6px 0 10px;
|
||||
padding: 8px 8px 22px;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
padding: 8px;
|
||||
color: #00ff66;
|
||||
background: #000000;
|
||||
border: 0;
|
||||
@@ -243,13 +294,20 @@ textarea:disabled {
|
||||
line-height: 15px;
|
||||
white-space: pre;
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
cursor: text;
|
||||
box-sizing: border-box;
|
||||
contain: layout paint;
|
||||
}
|
||||
|
||||
.popup-body .code-block::after {
|
||||
content: "\A";
|
||||
white-space: pre;
|
||||
.popup-body .code-block code {
|
||||
display: inline-block;
|
||||
min-width: 100%;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
white-space: inherit;
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
}
|
||||
|
||||
.copy-fallback-text {
|
||||
|
||||
@@ -284,6 +284,14 @@ body.fit-window .box-window {
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.preview-frame.is-text code {
|
||||
display: inline-block;
|
||||
min-width: 100%;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
white-space: inherit;
|
||||
}
|
||||
|
||||
.box-empty {
|
||||
margin: 0;
|
||||
padding: 12px;
|
||||
|
||||
@@ -775,7 +775,7 @@ body.fit-window .desktop-wrap {
|
||||
font-family: 'MonoCraft', 'PixelOperatorMono', 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 16px;
|
||||
white-space: pre;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.terminal-box::after {
|
||||
@@ -910,10 +910,12 @@ body.fit-window .desktop-wrap {
|
||||
.popup-body li { margin: 0 0 4px; }
|
||||
.popup-body .code-block {
|
||||
margin: 6px 0 10px;
|
||||
padding: 8px;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
padding: 8px;
|
||||
color: #00ff66;
|
||||
background: #000000;
|
||||
border: 0;
|
||||
@@ -921,7 +923,10 @@ body.fit-window .desktop-wrap {
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
white-space: pre;
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
box-sizing: border-box;
|
||||
contain: layout paint;
|
||||
}
|
||||
|
||||
.popup-window.is-about-popup .popup-body {
|
||||
@@ -1064,13 +1069,18 @@ body.fit-window .desktop-wrap {
|
||||
|
||||
.popup-body .code-block {
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
cursor: text;
|
||||
padding-bottom: 22px;
|
||||
}
|
||||
|
||||
.popup-body .code-block::after {
|
||||
content: "\A";
|
||||
white-space: pre;
|
||||
.popup-body .code-block code {
|
||||
display: inline-block;
|
||||
min-width: 100%;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
white-space: inherit;
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
}
|
||||
|
||||
.kbd {
|
||||
|
||||
@@ -13,6 +13,7 @@ const toast = document.querySelector("#toast");
|
||||
const zipOnly = boxPanel && boxPanel.dataset.zipOnly === "true";
|
||||
|
||||
let contextFile = null;
|
||||
let lastStatusSignature = "";
|
||||
|
||||
function htmlEscape(value) {
|
||||
return String(value || "")
|
||||
@@ -149,7 +150,7 @@ async function previewFile(item) {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error("Preview failed");
|
||||
const text = await response.text();
|
||||
openPopup(`${data.name} preview`, `<code class="code-block preview-frame is-text">${htmlEscape(text.slice(0, 120000))}</code>`, { preview: true });
|
||||
openPopup(`${data.name} preview`, `<pre class="code-block preview-frame is-text"><code>${htmlEscape(text.slice(0, 120000))}</code></pre>`, { preview: true });
|
||||
} catch (_) {
|
||||
showToast("The browser could not load a text preview.", "error");
|
||||
}
|
||||
@@ -214,9 +215,13 @@ async function refreshBoxStatus() {
|
||||
|
||||
const boxID = boxPanel.dataset.boxId;
|
||||
const response = await fetch(`/box/${boxID}/status`);
|
||||
if (!response.ok) return true;
|
||||
if (!response.ok) return { changed: false, hasLoadingFiles: true };
|
||||
|
||||
const result = await response.json();
|
||||
const signature = statusSignature(result);
|
||||
const changed = signature !== lastStatusSignature;
|
||||
lastStatusSignature = signature;
|
||||
|
||||
if (boxExpiryMeta && typeof result.expires_at === "string") {
|
||||
boxExpiryMeta.dataset.expiresAt = result.expires_at;
|
||||
updateExpiryCountdown();
|
||||
@@ -228,11 +233,73 @@ async function refreshBoxStatus() {
|
||||
boxStatus.textContent = `${completeCount}/${result.files.length} ready`;
|
||||
}
|
||||
|
||||
return result.files.some((file) => {
|
||||
const hasLoadingFiles = result.files.some((file) => {
|
||||
const isUploading = file.status === "pending" || file.status === "uploading";
|
||||
const isWaitingForThumbnail = file.status === "complete" && !file.thumbnail_status && !file.thumbnail_path;
|
||||
return isUploading || isWaitingForThumbnail || file.thumbnail_status === "processing";
|
||||
});
|
||||
|
||||
return { changed, hasLoadingFiles };
|
||||
}
|
||||
|
||||
function statusSignature(result) {
|
||||
const files = Array.isArray(result.files) ? result.files : [];
|
||||
return JSON.stringify({
|
||||
expiresAt: result.expires_at || "",
|
||||
files: files.map((file) => ({
|
||||
id: file.id,
|
||||
status: file.status,
|
||||
size: file.size,
|
||||
thumbnailPath: file.thumbnail_path || "",
|
||||
thumbnailStatus: file.thumbnail_status || "",
|
||||
downloadPath: file.download_path || "",
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
function pollingStages(baseMS) {
|
||||
return [
|
||||
{ interval: baseMS, attempts: 10 },
|
||||
{ interval: baseMS * 2, attempts: 20 },
|
||||
{ interval: baseMS * 10, attempts: 100 },
|
||||
];
|
||||
}
|
||||
|
||||
function startStagedPolling(baseMS) {
|
||||
const stages = pollingStages(baseMS);
|
||||
let stageIndex = 0;
|
||||
let attemptsInStage = 0;
|
||||
let stopped = false;
|
||||
|
||||
const tick = async () => {
|
||||
if (stopped) return;
|
||||
const stage = stages[stageIndex];
|
||||
try {
|
||||
const result = await refreshBoxStatus();
|
||||
if (result.changed) {
|
||||
stageIndex = 0;
|
||||
attemptsInStage = 0;
|
||||
} else {
|
||||
attemptsInStage += 1;
|
||||
if (attemptsInStage >= stage.attempts) {
|
||||
stageIndex += 1;
|
||||
attemptsInStage = 0;
|
||||
if (stageIndex >= stages.length) {
|
||||
stopped = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
attemptsInStage += 1;
|
||||
}
|
||||
|
||||
if (!stopped) {
|
||||
window.setTimeout(tick, stages[stageIndex].interval);
|
||||
}
|
||||
};
|
||||
|
||||
window.setTimeout(tick, stages[0].interval);
|
||||
}
|
||||
|
||||
document.addEventListener("click", (event) => {
|
||||
@@ -296,12 +363,5 @@ setInterval(updateExpiryCountdown, 1000);
|
||||
|
||||
if (boxPanel) {
|
||||
const pollMS = Number.parseInt(boxPanel.dataset.pollMs, 10) || 5000;
|
||||
const timer = setInterval(async () => {
|
||||
try {
|
||||
const hasLoadingFiles = await refreshBoxStatus();
|
||||
if (!hasLoadingFiles) clearInterval(timer);
|
||||
} catch (_) {
|
||||
// Keep polling through temporary network/server hiccups.
|
||||
}
|
||||
}, pollMS);
|
||||
startStagedPolling(pollMS);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
<h3>Upload with cURL</h3>
|
||||
<p>WarpBox accepts normal multipart form uploads through the compatibility endpoint:</p>
|
||||
<code class="code-block">curl \
|
||||
<h3>Upload from a terminal</h3>
|
||||
<p>WarpBox accepts normal multipart uploads at <code>/upload</code>. The server returns JSON with a <code>box_url</code> you can open or share.</p>
|
||||
<pre class="code-block"><code>curl \
|
||||
-F 'files=@./my-file.zip' \
|
||||
-F 'retention=1h' \
|
||||
{{ origin }}/upload
|
||||
</code>
|
||||
<h4>Browser flow</h4>
|
||||
<p>The browser uses the manifest API: it creates a box, uploads each file, and marks failed uploads so the download page does not wait forever.</p>
|
||||
<h4>Make a WarpBox executable</h4>
|
||||
<p>Save this as <code>warpbox</code>, make it executable, and put it somewhere on your <code>PATH</code>.</p>
|
||||
<code class="code-block">#!/usr/bin/env bash
|
||||
</code></pre>
|
||||
|
||||
<h4>Reusable shell wrapper</h4>
|
||||
<p>This version is small, portable, and works well as a personal <code>warpbox</code> command.</p>
|
||||
<pre class="code-block"><code>#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$#" -lt 1 ]; then
|
||||
@@ -25,9 +24,39 @@ for file in "$@"; do
|
||||
args+=(-F "files=@${file}")
|
||||
done
|
||||
|
||||
curl "${args[@]}" "${endpoint}"
|
||||
</code>
|
||||
<code class="code-block">chmod +x ./warpbox
|
||||
curl --fail-with-body "${args[@]}" "${endpoint}"
|
||||
</code></pre>
|
||||
|
||||
<h4>Install it</h4>
|
||||
<p>Put the wrapper somewhere on your <code>PATH</code>, then call it with one or more files.</p>
|
||||
<pre class="code-block"><code>chmod +x ./warpbox
|
||||
sudo install -m 755 ./warpbox /usr/local/bin/warpbox
|
||||
warpbox ./my-file.zip
|
||||
</code>
|
||||
warpbox ./photo.png ./archive.zip
|
||||
</code></pre>
|
||||
|
||||
<h4>Print only the share URL</h4>
|
||||
<p>If <code>jq</code> is installed, this variant extracts the returned link and expands it to a full URL.</p>
|
||||
<pre class="code-block"><code>warpbox() {
|
||||
local endpoint="${WARPBOX_URL:-{{ origin }}}/upload"
|
||||
local retention="${WARPBOX_RETENTION:-1h}"
|
||||
local args=(-F "retention=${retention}")
|
||||
|
||||
for file in "$@"; do
|
||||
args+=(-F "files=@${file}")
|
||||
done
|
||||
|
||||
curl --fail-with-body -sS "${args[@]}" "${endpoint}" |
|
||||
jq -r --arg origin "${WARPBOX_URL:-{{ origin }}}" '"\($origin)\(.box_url)"'
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<h4>Add password or retention</h4>
|
||||
<p>You can keep the wrapper simple and pass fixed options through environment variables or extra form fields.</p>
|
||||
<pre class="code-block"><code>WARPBOX_RETENTION=24h warpbox ./release.tar.gz
|
||||
|
||||
curl \
|
||||
-F 'files=@./private.zip' \
|
||||
-F 'retention=1h' \
|
||||
-F 'password=correct-horse-battery-staple' \
|
||||
{{ origin }}/upload
|
||||
</code></pre>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<h3>Upload examples</h3>
|
||||
<h4>Basic CLI upload</h4>
|
||||
<code class="code-block">curl \
|
||||
<pre class="code-block"><code>curl \
|
||||
-F 'files=@./photo.png' \
|
||||
-F 'retention=24h' \
|
||||
{{ origin }}/upload
|
||||
</code>
|
||||
</code></pre>
|
||||
<h4>Multiple files with password</h4>
|
||||
<code class="code-block">curl \
|
||||
<pre class="code-block"><code>curl \
|
||||
-F 'files=@./one.png' \
|
||||
-F 'files=@./two.zip' \
|
||||
-F 'retention=1h' \
|
||||
-F 'password=secret-pass' \
|
||||
{{ origin }}/upload
|
||||
</code>
|
||||
</code></pre>
|
||||
<h4>Go</h4>
|
||||
<code class="code-block">package main
|
||||
<pre class="code-block"><code>package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -48,9 +48,9 @@ func main() {
|
||||
out, _ := io.ReadAll(resp.Body)
|
||||
fmt.Println(string(out))
|
||||
}
|
||||
</code>
|
||||
</code></pre>
|
||||
<h4>Java 11+ HttpClient</h4>
|
||||
<code class="code-block">import java.net.URI;
|
||||
<pre class="code-block"><code>import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
@@ -84,9 +84,9 @@ public class UploadWarpBox {
|
||||
System.out.println(response.body());
|
||||
}
|
||||
}
|
||||
</code>
|
||||
</code></pre>
|
||||
<h4>JavaScript Node.js</h4>
|
||||
<code class="code-block">import { openAsBlob } from 'node:fs';
|
||||
<pre class="code-block"><code>import { openAsBlob } from 'node:fs';
|
||||
|
||||
const file = await openAsBlob('./photo.png');
|
||||
const form = new FormData();
|
||||
@@ -99,4 +99,4 @@ const res = await fetch('{{ origin }}/upload', {
|
||||
});
|
||||
|
||||
console.log(await res.text());
|
||||
</code>
|
||||
</code></pre>
|
||||
|
||||
Reference in New Issue
Block a user