const SETTINGS_KEY = "warpbox.upload.settings.v1"; const el = { form: document.querySelector("#upload-form"), fileInput: document.querySelector("#file-upload"), dropSurface: document.querySelector("#drop-surface"), dropzone: document.querySelector("#dropzone"), fileList: document.querySelector("#file-list"), queueLabel: document.querySelector("#queue-label"), queueSize: document.querySelector("#queue-size"), limitHint: document.querySelector("#limit-hint"), boxSpaceText: document.querySelector("#box-space-text"), boxSpaceBar: document.querySelector("#box-space-bar"), overallBar: document.querySelector("#overall-bar"), overallPercent: document.querySelector("#overall-percent"), shareLink: document.querySelector("#share-link"), copyButton: document.querySelector("#copy-button"), startButton: document.querySelector("#start-button"), statusText: document.querySelector("#status-text"), toast: document.querySelector("#toast"), terminal: document.querySelector("#terminal-box"), copyCurlButton: document.querySelector("#copy-curl-button"), docPopup: document.querySelector("#doc-popup"), modalBackdrop: document.querySelector("#modal-backdrop"), docPopupTitle: document.querySelector("#doc-popup-title"), docPopupBody: document.querySelector("#doc-popup-body"), docPopupClose: document.querySelector("#doc-popup-close"), expiry: document.querySelector("#expiry-select"), password: document.querySelector("#password-input"), optionsForm: document.querySelector("#box-options-form"), maxViews: document.querySelector("#max-views"), boxName: document.querySelector("#box-name"), customSlug: document.querySelector("#custom-slug"), downloadPage: document.querySelector("#download-page"), allowZip: document.querySelector("#allow-zip"), allowPreview: document.querySelector("#allow-preview"), keepFilenames: document.querySelector("#keep-filenames"), privateBox: document.querySelector("#private-box"), apiKeyMode: document.querySelector("#api-key-mode"), apiKeyInput: document.querySelector("#api-key-input"), apiKeyRow: document.querySelector("#api-key-row"), apiKeyState: document.querySelector("#api-key-state"), }; const uploadsEnabled = el.form?.dataset.uploadsEnabled === "true"; const defaultRetention = el.form?.dataset.defaultRetention || "10s"; const maxFileBytes = numberFromDataset(el.form?.dataset.maxFileBytes); const maxBoxBytes = numberFromDataset(el.form?.dataset.maxBoxBytes); const oneTimeRetentionKey = "one-time"; let files = []; let shareUrl = ""; let uploadLocked = false; let statusTimer = null; let pendingDuplicateFiles = []; let apiKeyTimer = null; let completedImpactKeys = new Set(); let overallImpactDone = false; function numberFromDataset(value) { const number = Number.parseInt(value || "0", 10); return Number.isFinite(number) && number > 0 ? number : 0; } function formatBytes(bytes) { if (!bytes) return "0 B"; const units = ["B", "KB", "MB", "GB", "TB"]; let value = bytes; let unit = 0; while (value >= 1024 && unit < units.length - 1) { value /= 1024; unit += 1; } return `${value.toFixed(value >= 10 || unit === 0 ? 0 : 1)} ${units[unit]}`; } function htmlEscape(value) { return String(value) .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function shellQuote(value) { return `'${String(value).replaceAll("'", "'\\''")}'`; } function totalBytes() { return files.reduce((sum, item) => sum + item.file.size, 0); } function uploadedBytes() { return files.reduce((sum, item) => sum + item.loaded, 0); } function overallProgress() { const total = totalBytes(); return total ? Math.round((uploadedBytes() / total) * 100) : 0; } function oversizedFiles() { return maxFileBytes ? files.filter((item) => item.file.size > maxFileBytes) : []; } function isOverBoxQuota() { return maxBoxBytes ? totalBytes() > maxBoxBytes : false; } function hasQuotaError() { return isOverBoxQuota() || oversizedFiles().length > 0; } function normalizedFileName(name) { return String(name || "").trim().toLowerCase(); } function splitNameForIncrement(name) { const value = String(name || "file"); const dot = value.lastIndexOf("."); if (dot > 0 && dot < value.length - 1) return [value.slice(0, dot), value.slice(dot)]; return [value, ""]; } function nextIncrementedFileName(name, usedNames) { const [base, ext] = splitNameForIncrement(name); let index = 2; let candidate = `${base} (${index})${ext}`; while (usedNames.has(normalizedFileName(candidate))) { index += 1; candidate = `${base} (${index})${ext}`; } usedNames.add(normalizedFileName(candidate)); return candidate; } function makeQueuedFile(file, displayName = file.name) { return { file, displayName, loaded: 0, uploaded: false, failed: false, error: "", row: null, boxID: "", boxFile: null, previewURL: file.type?.startsWith("image/") ? URL.createObjectURL(file) : "", }; } function iconForFile(file) { const filename = file.name || ""; const mimeType = file.type || ""; const extension = filename.includes(".") ? filename.slice(filename.lastIndexOf(".")).toLowerCase() : ""; if (extension === ".exe") return "/static/img/icons/Program Files Icons - PNG/MSONSEXT.DLL_14_6-0.png"; if (mimeType.startsWith("image/")) return "/static/img/sprites/bitmap.png"; if (mimeType.startsWith("video/") || mimeType.startsWith("audio/")) return "/static/img/icons/netshow_notransm-1.png"; if (mimeType.startsWith("text/") || extension === ".md") return "/static/img/sprites/notepad_file-1.png"; if (mimeType.includes("zip") || mimeType.includes("compressed") || [".rar", ".7z", ".tar", ".gz"].includes(extension)) return "/static/img/icons/Windows Icons - PNG/zipfldr.dll_14_101-0.png"; if ([".ttf", ".otf", ".woff", ".woff2"].includes(extension)) return "/static/img/sprites/font.png"; if (extension === ".pdf") return "/static/img/sprites/journal.png"; if ([".html", ".css", ".js"].includes(extension)) return "/static/img/sprites/frame_web-0.png"; return "/static/img/icons/Windows Icons - PNG/ole2.dll_14_DEFICON.png"; } function setStatus(message) { if (el.statusText) el.statusText.textContent = message; } function showToast(message, type = "info") { window.WarpBoxUI.toast(message, type, { target: el.toast }); } function closeMenus() { document.querySelectorAll(".menu-item.is-open").forEach((node) => { node.classList.remove("is-open"); node.querySelector(".menu-button")?.setAttribute("aria-expanded", "false"); }); } function disabledReasonFor(target) { const control = target.closest("[data-disabled-reason], button, input, select, textarea, .upload-dropzone, .option-check, .option-row"); if (!control) return ""; if (control.classList.contains("option-check") || control.classList.contains("option-row")) { const nested = control.querySelector("input, select, textarea"); if (nested?.disabled || nested?.readOnly || nested?.getAttribute("aria-disabled") === "true") { return nested.dataset.disabledReason || "This option is disabled right now."; } } if (control.classList.contains("upload-dropzone") && uploadLocked) { return control.dataset.disabledReason || "The current box is sealed after upload. Press Clear to start a new box."; } if (control.disabled || control.readOnly || control.getAttribute("aria-disabled") === "true") { return control.dataset.disabledReason || control.title || "This control is disabled right now."; } return ""; } function announceDisabledReason(event) { const reason = disabledReasonFor(event.target); if (!reason) return false; event.preventDefault(); event.stopPropagation(); closeMenus(); showToast(reason, "warning"); setStatus(reason); return true; } function stopStatusAnimation() { if (statusTimer) { clearInterval(statusTimer); statusTimer = null; } } function animateUploadStatus(getPrefix) { let dotCount = 0; stopStatusAnimation(); statusTimer = setInterval(() => { dotCount = (dotCount % 3) + 1; setStatus(`${getPrefix()} Uploading${".".repeat(dotCount)}`); }, 350); } function setShareUrl(url) { shareUrl = url ? new URL(url, window.location.origin).toString() : ""; if (!el.shareLink || !el.copyButton) return; el.shareLink.textContent = shareUrl || "Not created yet"; el.shareLink.href = shareUrl || "#"; el.shareLink.title = shareUrl; el.shareLink.classList.toggle("is-empty", !shareUrl); el.shareLink.setAttribute("aria-disabled", shareUrl ? "false" : "true"); el.copyButton.disabled = false; el.copyButton.setAttribute("aria-disabled", shareUrl ? "false" : "true"); el.copyButton.dataset.disabledReason = shareUrl ? "" : "There is no share URL yet. Start an upload first."; updateDisabledReasons(); updateTerminal(); updateCurrentStep(); } function setOverallProgress(percent) { const clamped = Math.max(0, Math.min(100, percent)); const display = `${Math.round(clamped)}%`; if (el.overallBar) el.overallBar.style.width = display; if (el.overallPercent) el.overallPercent.textContent = display; } function flashProgressBar(bar) { if (!bar) return; bar.classList.remove("just-completed"); void bar.offsetWidth; bar.classList.add("just-completed"); setTimeout(() => bar.classList.remove("just-completed"), 620); } function setRowProgress(item, percent) { const bar = item.row?.querySelector(".upload-progress-bar"); if (bar) bar.style.width = `${Math.max(0, Math.min(100, percent))}%`; } function updateCurrentStep() { const hasFiles = files.length > 0; const allDone = hasFiles && files.every((item) => item.uploaded); el.dropzone?.classList.toggle("is-current-step", uploadsEnabled && !hasFiles && !uploadLocked); el.startButton?.classList.toggle("is-current-step", uploadsEnabled && hasFiles && !allDone && !uploadLocked && !hasQuotaError()); document.querySelector(".upload-result")?.classList.toggle("is-current-step", allDone && Boolean(shareUrl)); } function quotaWarningMessage(incoming = []) { const combined = [...files, ...incoming]; const tooBig = maxFileBytes ? combined.filter((item) => item.file.size > maxFileBytes) : []; const total = combined.reduce((sum, item) => sum + item.file.size, 0); if (tooBig.length) { const list = tooBig.slice(0, 4).map((item) => `${item.displayName} (${formatBytes(item.file.size)})`).join(", "); const more = tooBig.length > 4 ? ` and ${tooBig.length - 4} more` : ""; return `These files are over the single-file limit of ${formatBytes(maxFileBytes)}: ${list}${more}. Remove them before uploading.`; } if (maxBoxBytes && total > maxBoxBytes) { return `This box is ${formatBytes(total - maxBoxBytes)} over the ${formatBytes(maxBoxBytes)} limit. Remove some files before uploading.`; } return ""; } function updateLimitHint() { if (!el.limitHint) return; const parts = []; if (maxBoxBytes) parts.push(`Max box: ${formatBytes(maxBoxBytes)}`); if (maxFileBytes) parts.push(`max file: ${formatBytes(maxFileBytes)}`); parts.push("links expire automatically"); el.limitHint.textContent = parts.join(" · "); } function updateQuota() { const used = totalBytes(); const limitText = maxBoxBytes ? ` / ${formatBytes(maxBoxBytes)}` : ""; const overQuota = isOverBoxQuota(); const overFile = oversizedFiles().length > 0; const percent = maxBoxBytes ? Math.min(100, Math.round((used / maxBoxBytes) * 100)) : 0; document.querySelector(".upload-quota")?.classList.toggle("is-quota-warning", overQuota || overFile); if (el.boxSpaceText) el.boxSpaceText.textContent = `${formatBytes(used)}${limitText}${overQuota ? " - over quota" : ""}`; if (el.boxSpaceBar) { el.boxSpaceBar.style.width = `${percent}%`; el.boxSpaceBar.classList.toggle("is-over-quota", overQuota || overFile); } } function updateQueueSummary() { const count = files.length; if (el.queueLabel) el.queueLabel.textContent = count ? `${count} file${count === 1 ? "" : "s"} selected` : "No files selected"; if (el.queueSize) el.queueSize.textContent = `${formatBytes(totalBytes())} total`; } function updateOverallProgress() { const uploadedCount = files.filter((item) => item.uploaded).length; const percent = overallProgress(); setOverallProgress(percent >= 100 && uploadedCount < files.length ? 99 : percent); if (percent >= 100 && files.length && !overallImpactDone) { overallImpactDone = true; flashProgressBar(el.overallBar); } } function createFileRow(item, index) { const row = document.createElement("div"); row.className = "upload-file-row"; row.dataset.index = String(index); row.classList.toggle("has-thumbnail", Boolean(item.previewURL)); row.classList.toggle("is-too-large", maxFileBytes > 0 && item.file.size > maxFileBytes); row.classList.toggle("is-working", item.loaded > 0 && !item.uploaded && !item.failed); row.classList.toggle("is-uploaded", item.uploaded); row.classList.toggle("is-failed", item.failed); row.title = item.error || ""; const icon = document.createElement("img"); icon.className = "upload-file-icon"; icon.src = item.previewURL || iconForFile(item.file); icon.alt = ""; icon.setAttribute("aria-hidden", "true"); const name = document.createElement("span"); name.className = "upload-file-name"; name.textContent = item.displayName; name.title = item.displayName; const size = document.createElement("span"); size.className = "upload-file-size"; size.textContent = formatBytes(item.file.size); const remove = document.createElement("button"); remove.className = "win98-button upload-file-remove"; remove.type = "button"; remove.textContent = "×"; remove.dataset.remove = String(index); remove.title = uploadLocked ? "This file cannot be removed because this upload box was already created." : "Remove file"; remove.disabled = false; remove.setAttribute("aria-disabled", uploadLocked ? "true" : "false"); remove.dataset.disabledReason = uploadLocked ? "Files cannot be removed after the box is created. Press Clear to start another upload." : ""; const progress = document.createElement("span"); progress.className = "upload-progress"; progress.setAttribute("aria-label", `Upload progress ${Math.round(item.file.size ? (item.loaded / item.file.size) * 100 : 0)} percent`); const progressBar = document.createElement("span"); progressBar.className = "upload-progress-bar"; progressBar.style.width = `${item.uploaded ? 100 : item.failed ? 100 : Math.max(0, Math.min(100, item.file.size ? (item.loaded / item.file.size) * 100 : 0))}%`; progress.append(progressBar); row.append(icon, name, size, remove, progress); item.row = row; return row; } function renderFiles() { if (!el.fileList) return; el.fileList.replaceChildren(); if (!files.length) { const empty = document.createElement("p"); empty.className = "upload-empty-state"; empty.textContent = uploadsEnabled ? "No files in the box yet. Drop files here, use File > Add files, or click the dropzone." : "Guest uploads are disabled."; el.fileList.append(empty); } else { const fragment = document.createDocumentFragment(); files.forEach((item, index) => fragment.append(createFileRow(item, index))); el.fileList.append(fragment); } updateQueueSummary(); updateQuota(); updateOverallProgress(); updateTerminal(); updateDisabledReasons(); updateCurrentStep(); } function duplicateFileReport(incoming = []) { const used = new Set(files.map((item) => normalizedFileName(item.displayName))); const duplicates = []; const unique = []; incoming.forEach((item) => { const key = normalizedFileName(item.displayName); if (used.has(key)) { duplicates.push(item); return; } used.add(key); unique.push(item); }); return { unique, duplicates }; } function addFiles(fileList) { if (!uploadsEnabled) { showToast("Guest uploads are disabled.", "warning"); return; } if (uploadLocked) { showToast("This box is sealed. Clear it to create a fresh upload.", "warning"); return; } const incoming = Array.from(fileList || []).map((file) => makeQueuedFile(file)); if (!incoming.length) return; const { unique, duplicates } = duplicateFileReport(incoming); if (unique.length) { files.push(...unique); setShareUrl(""); renderFiles(); const warning = quotaWarningMessage(); if (warning) showWarningDialog("Quota warning", warning); } if (duplicates.length) showDuplicateDialog(duplicates); if (unique.length) setStatus(`${unique.length} file${unique.length === 1 ? "" : "s"} added to queue`); if (duplicates.length && !unique.length) setStatus(`${duplicates.length} duplicate file${duplicates.length === 1 ? "" : "s"} need your choice`); } function showDuplicateDialog(duplicates) { pendingDuplicateFiles = duplicates; const list = duplicates.map((item) => `
Single-file limit exceeded. Remove these files before uploading.
"); parts.push(`Box quota exceeded. Current total is ${formatBytes(totalBytes())}. The limit is ${formatBytes(maxBoxBytes)}. Remove ${formatBytes(totalBytes() - maxBoxBytes)} or more.
`); } if (!parts.length) parts.push(`${htmlEscape(message)}
`); return parts.join(""); } function showWarningDialog(title, message) { showTemplatePopup(title, "warning", { title: htmlEscape(title), content: quotaWarningHtml(message), }); } function openPopup(title, html, about = false) { window.WarpBoxUI.openPopup(title, html, { about, popup: el.docPopup, title: el.docPopupTitle, body: el.docPopupBody, backdrop: el.modalBackdrop, }); } function closeDoc() { window.WarpBoxUI.closePopup({ popup: el.docPopup, backdrop: el.modalBackdrop }); } async function showTemplatePopup(title, templateName, data = {}, about = false) { try { const html = await window.WBPopups.renderTemplate(templateName, data); openPopup(title, html, about); } catch (error) { showToast(error.message || `Could not load ${title}.`, "error"); } } function popupTemplateData(name) { const data = { origin: window.location.origin }; if (name !== "dailyQuota") return data; return { ...data, boxLimit: maxBoxBytes ? formatBytes(maxBoxBytes) : "No configured limit", boxPercent: maxBoxBytes ? Math.min(100, Math.round((totalBytes() / maxBoxBytes) * 100)) : 0, fileLimit: maxFileBytes ? formatBytes(maxFileBytes) : "No configured limit", filePercent: oversizedFiles().length ? 100 : 0, }; } async function openDoc(name) { try { const doc = await window.WBPopups.renderDoc(name, popupTemplateData(name)); if (!doc) return; openPopup(doc.title, doc.html, doc.about); setStatus(`${doc.title} opened`); } catch (error) { showToast(error.message || "Could not load help window.", "error"); } } document.addEventListener("click", (event) => { if (announceDisabledReason(event)) return; const menuButton = event.target.closest(".menu-button"); if (menuButton) { const item = menuButton.closest(".menu-item"); const isOpen = item.classList.contains("is-open"); closeMenus(); item.classList.toggle("is-open", !isOpen); menuButton.setAttribute("aria-expanded", String(!isOpen)); return; } const action = event.target.closest("[data-action]")?.dataset.action; if (action) { closeMenus(); if (action === "browse") el.fileInput?.click(); if (action === "start-upload") startUpload(); if (action === "copy-link") copyText("Share URL", shareUrl, shareUrl); if (action === "clear") confirmClearQueue(); if (action === "toggle-delete-once" && el.expiry?.querySelector(`option[value="${oneTimeRetentionKey}"]`)) { el.expiry.value = isOneTimeDownloadSelected() ? defaultRetention : oneTimeRetentionKey; syncZipForRetention(); saveSettings(); syncMenuChecks(); updateTerminal(); } if (action === "random-password") randomPassword(); if (action === "random-box-name") randomBoxName(); if (action === "clear-password" && el.password && !uploadLocked) { el.password.value = ""; saveSettings(); updateTerminal(); } if (action === "toggle-page" && el.downloadPage && !uploadLocked) { el.downloadPage.checked = !el.downloadPage.checked; saveSettings(); syncMenuChecks(); } if (action === "help" || action === "side-help") openDoc("faq"); if (action === "coming-soon") showToast("Coming Soon, not implemented just yet."); if (action === "fake-close") showToast("Close button denied. The upload window is staying open.", "warning"); if (action === "minimize") showToast("Minimize requested. WarpBox stays visible so your queue is safe."); if (action === "toggle-fit") { document.body.classList.toggle("fit-window"); showToast("Maximize requested. The pixel rectangle feels important now."); } if (action === "side-close") showToast("Box Options refuses to leave. Settings stay visible."); if (action === "side-help") showToast("Terminal help opened. Copy the command and feed it files."); if (action === "side-folder-close") showToast("The folder window saw that click and chose denial."); return; } const doc = event.target.closest("[data-doc]")?.dataset.doc; if (doc) { openDoc(doc); return; } const remove = event.target.closest("[data-remove]"); if (remove) { removeFile(Number(remove.dataset.remove)); return; } if (event.target.id === "duplicate-append") appendPendingDuplicates(); if (event.target.id === "duplicate-skip") { pendingDuplicateFiles = []; closeDoc(); showToast("Duplicate files skipped."); } if (event.target.id === "confirm-clear-yes") { closeDoc(); clearQueue(); } if (event.target.id === "confirm-clear-no" || event.target.id === "fallback-close") closeDoc(); if (!event.target.closest(".menu-item")) { closeMenus(); } }); document.addEventListener("mousedown", (event) => { announceDisabledReason(event); }, true); document.querySelectorAll(".menu-item").forEach((item) => { item.addEventListener("mouseenter", () => { if (!document.querySelector(".menu-item.is-open")) return; closeMenus(); item.classList.add("is-open"); item.querySelector(".menu-button")?.setAttribute("aria-expanded", "true"); }); }); el.fileInput?.addEventListener("change", () => addFiles(el.fileInput.files)); [el.dropSurface, el.dropzone].filter(Boolean).forEach((target) => { target.addEventListener("dragover", (event) => { event.preventDefault(); el.dropzone?.classList.add("is-dragging"); }); target.addEventListener("dragleave", () => el.dropzone?.classList.remove("is-dragging")); target.addEventListener("drop", (event) => { event.preventDefault(); el.dropzone?.classList.remove("is-dragging"); addFiles(event.dataTransfer.files); }); }); el.dropzone?.addEventListener("keydown", (event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); el.fileInput?.click(); } }); el.form?.addEventListener("submit", (event) => { event.preventDefault(); startUpload(); }); el.copyButton?.addEventListener("click", () => copyText("Share URL", shareUrl, shareUrl)); el.copyCurlButton?.addEventListener("click", () => copyText("cURL command", getCurlCommand({ full: true }))); el.docPopupClose?.addEventListener("click", closeDoc); el.modalBackdrop?.addEventListener("click", closeDoc); el.maxViews?.addEventListener("wheel", (event) => { if (el.maxViews.disabled || el.maxViews.readOnly) return; event.preventDefault(); const delta = event.deltaY < 0 ? 1 : -1; const modifier = event.ctrlKey && event.shiftKey ? 50 : event.shiftKey ? 15 : event.ctrlKey ? 5 : 1; const min = Number.parseInt(el.maxViews.min || "1", 10); const max = Number.parseInt(el.maxViews.max || "9999", 10); const current = Number.parseInt(el.maxViews.value || String(min), 10); el.maxViews.value = String(Math.max(min, Math.min(max, current + (delta * modifier)))); saveSettings(); updateTerminal(); }); el.apiKeyInput?.addEventListener("keydown", (event) => { const allowed = event.ctrlKey || event.metaKey || event.altKey || [ "Tab", "Shift", "Control", "Alt", "Meta", "Escape", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End", "PageUp", "PageDown", ].includes(event.key); if (allowed) return; event.preventDefault(); showToast("Only pasting the API key is supported.", "warning"); setStatus("Only pasting the API key is supported"); }); el.apiKeyInput?.addEventListener("paste", () => { setTimeout(validateApiKeyField, 0); }); [el.expiry, el.password, el.maxViews, el.boxName, el.customSlug, el.downloadPage, el.allowZip, el.allowPreview, el.keepFilenames, el.privateBox, el.apiKeyMode, el.apiKeyInput].filter(Boolean).forEach((control) => { control.addEventListener("input", () => { if (control === el.boxName) syncSlugFromName(); if (control === el.customSlug) { const clean = sanitizeSlugInput(el.customSlug.value); if (el.customSlug.value !== clean) el.customSlug.value = clean; el.customSlug.dataset.auto = "false"; } if (control === el.apiKeyInput) validateApiKeyField(); saveSettings(); updateTerminal(); }); control.addEventListener("change", () => { if (control === el.expiry) syncZipForRetention(); if (control === el.apiKeyMode) syncApiKeyField(); saveSettings(); syncMenuChecks(); updateTerminal(); }); }); document.addEventListener("keydown", (event) => { if (event.key === "Escape") { closeDoc(); closeMenus(); } if (event.key === "F1") { event.preventDefault(); openDoc("faq"); } if (event.ctrlKey && !event.shiftKey && !event.altKey) { const key = event.key.toLowerCase(); if (key === "o") { event.preventDefault(); el.fileInput?.click(); } if (key === "u") { event.preventDefault(); startUpload(); } if (key === "k") { event.preventDefault(); copyText("cURL command", getCurlCommand({ full: true })); } if (key === "l") { event.preventDefault(); copyText("Share URL", shareUrl, shareUrl); } } }); window.addEventListener("beforeunload", () => { files.forEach((item) => { if (item.previewURL) URL.revokeObjectURL(item.previewURL); }); }); loadSettings(); updateLimitHint(); syncMenuChecks(); renderFiles(); updateTerminal();