function isOneTimeDownloadSelected() { return el.expiry?.value === oneTimeRetentionKey; } function syncZipForRetention() { if (!el.allowZip) return; if (isOneTimeDownloadSelected()) { el.allowZip.checked = true; el.allowZip.disabled = true; } else if (!uploadLocked) { el.allowZip.disabled = false; } } function setBoxOptionsLocked(locked) { const controls = [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); el.optionsForm?.classList.toggle("is-locked", locked); controls.forEach((control) => { control.dataset.disabledReason = locked ? "Box Options are locked because this box was already created. Press Clear to start another upload." : ""; if (control.tagName === "INPUT" && !["checkbox", "radio", "file"].includes(control.type)) { control.readOnly = locked; } else { control.disabled = locked; } }); if (el.password) el.password.type = locked ? "password" : "text"; if (!locked) { syncZipForRetention(); syncApiKeyField(); } updateDisabledReasons(); } function updateDisabledReasons() { if (el.startButton) { let reason = ""; const policyMessage = apiKeyPolicyMessage(); if (!uploadsEnabled) reason = "Guest uploads are disabled."; else if (uploadLocked) reason = "This upload already started. Press Clear to create another box."; else if (policyMessage) reason = policyMessage; else if (hasQuotaError()) reason = "Over maximum upload size. Remove highlighted files or clear some files."; else if (!files.length) reason = "There are no files selected. Please select files to upload."; el.startButton.disabled = false; el.startButton.setAttribute("aria-disabled", reason ? "true" : "false"); el.startButton.dataset.disabledReason = reason; el.startButton.title = reason; } if (el.fileInput) { el.fileInput.dataset.disabledReason = uploadLocked ? "The current box is sealed after upload. Press Clear to start a new box." : (!uploadsEnabled ? "Guest uploads are disabled." : ""); } if (el.dropzone) { el.dropzone.dataset.disabledReason = uploadLocked ? "The current box is sealed after upload. Press Clear to start a new box." : (!uploadsEnabled ? "Guest uploads are disabled." : ""); } document.querySelectorAll('[data-action="start-upload"]').forEach((button) => { const reason = el.startButton?.dataset.disabledReason || ""; button.setAttribute("aria-disabled", reason ? "true" : "false"); button.dataset.disabledReason = reason; }); document.querySelectorAll('[data-action="browse"]').forEach((button) => { const reason = uploadLocked ? "The current box is sealed after upload. Press Clear to start a new box." : (!uploadsEnabled ? "Guest uploads are disabled." : ""); button.setAttribute("aria-disabled", reason ? "true" : "false"); button.dataset.disabledReason = reason; }); document.querySelectorAll('[data-action="copy-link"]').forEach((button) => { button.setAttribute("aria-disabled", shareUrl ? "false" : "true"); button.dataset.disabledReason = shareUrl ? "" : "There is no share URL yet. Start an upload first."; }); } function saveSettings() { const apiKey = el.apiKeyMode?.checked && validApiKey(el.apiKeyInput?.value || "") ? el.apiKeyInput.value.trim() : ""; const settings = { maxViews: el.maxViews?.value || "", allowPreview: Boolean(el.allowPreview?.checked), keepFilenames: Boolean(el.keepFilenames?.checked), privateBox: Boolean(el.privateBox?.checked), apiKeyMode: Boolean(el.apiKeyMode?.checked), apiKey, }; localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings)); } function loadSettings() { let settings = {}; try { settings = JSON.parse(localStorage.getItem(SETTINGS_KEY) || "{}"); } catch (_) {} if (el.maxViews) el.maxViews.value = settings.maxViews || ""; if (el.allowPreview) el.allowPreview.checked = settings.allowPreview !== false; if (el.keepFilenames) el.keepFilenames.checked = settings.keepFilenames !== false; if (el.privateBox) el.privateBox.checked = Boolean(settings.privateBox); if (el.apiKeyMode) el.apiKeyMode.checked = Boolean(settings.apiKeyMode); if (el.apiKeyInput) el.apiKeyInput.value = validApiKey(settings.apiKey || "") ? settings.apiKey : ""; syncZipForRetention(); syncApiKeyField(); saveSettings(); } function syncMenuChecks() { updateDisabledReasons(); } function syncApiKeyField() { const enabled = Boolean(el.apiKeyMode?.checked) && !uploadLocked; el.apiKeyRow?.classList.toggle("is-visible", Boolean(el.apiKeyMode?.checked)); if (!el.apiKeyMode?.checked) { clearTimeout(apiKeyTimer); apiKeyValidationRun += 1; resetAccountLimits(); updateLimitHint(); renderFiles(); } if (el.apiKeyInput) { el.apiKeyInput.disabled = !enabled; el.apiKeyInput.dataset.disabledReason = enabled ? "" : "Enable Use API key for larger quota before typing an API key."; } validateApiKeyField(); } function validateApiKeyField() { if (!el.apiKeyInput || !el.apiKeyState) return; clearTimeout(apiKeyTimer); const wrapper = el.apiKeyInput.closest(".api-key-field"); wrapper?.classList.remove("is-checking"); if (!el.apiKeyMode?.checked) { apiKeyValidationRun += 1; resetAccountLimits(); el.apiKeyState.textContent = ""; updateLimitHint(); renderFiles(); return; } const value = el.apiKeyInput.value.trim(); if (!value) { apiKeyValidationRun += 1; resetAccountLimits(); el.apiKeyState.textContent = "waiting"; updateLimitHint(); renderFiles(); saveSettings(); return; } if (!validApiKey(value)) { apiKeyValidationRun += 1; resetAccountLimits(); el.apiKeyInput.value = ""; el.apiKeyState.textContent = "invalid"; updateLimitHint(); renderFiles(); saveSettings(); showToast("Invalid API key removed. Paste a valid API key to save it.", "warning"); return; } const runID = apiKeyValidationRun + 1; apiKeyValidationRun = runID; el.apiKeyInput.disabled = true; wrapper?.classList.add("is-checking"); el.apiKeyState.textContent = "checking"; apiKeyTimer = setTimeout(async () => { try { const response = await fetch("/auth/me", { headers: { Authorization: `Bearer ${value}` }, }); let payload = {}; try { payload = await response.json(); } catch (_) {} if (runID !== apiKeyValidationRun) return; wrapper?.classList.remove("is-checking"); el.apiKeyInput.disabled = uploadLocked; if (!response.ok || !payload.user) { resetAccountLimits(); el.apiKeyInput.value = ""; el.apiKeyState.textContent = "invalid"; updateLimitHint(); renderFiles(); saveSettings(); showToast(payload.error || "API key was not accepted.", "warning"); return; } applyAccountLimits(payload.user); const policyMessage = apiKeyPolicyMessage(); const fileText = maxFileBytes ? formatBytes(maxFileBytes) : "unlimited"; const boxText = maxBoxBytes ? formatBytes(maxBoxBytes) : "unlimited"; el.apiKeyState.textContent = policyMessage ? "limited by policy" : "account limits applied"; updateLimitHint(); renderFiles(); saveSettings(); setStatus(`${payload.user.username || payload.user.email} limits: file ${fileText}, box ${boxText}`); if (policyMessage) showToast(policyMessage, "warning"); } catch (_) { if (runID !== apiKeyValidationRun) return; wrapper?.classList.remove("is-checking"); el.apiKeyInput.disabled = uploadLocked; resetAccountLimits(); updateLimitHint(); renderFiles(); el.apiKeyState.textContent = "check failed"; showToast("Could not check API key limits.", "warning"); } }, 650); } function validApiKey(value) { return /^[A-Za-z0-9._-]{12,}$/.test(String(value || "").trim()); } function slugify(value) { return String(value || "") .toLowerCase() .replace(/[^a-z0-9-]+/g, "-") .replace(/-+/g, "-") .replace(/^-|-$/g, "") .slice(0, 32); } function sanitizeSlugInput(value) { return String(value || "") .toLowerCase() .replace(/[^a-z0-9-]/g, "") .replace(/-+/g, "-") .slice(0, 32); } function syncSlugFromName(force = false) { if (!el.customSlug || !el.boxName) return; if (force || !el.customSlug.value || el.customSlug.dataset.auto === "true") { el.customSlug.value = slugify(el.boxName.value); el.customSlug.dataset.auto = "true"; } saveSettings(); updateTerminal(); } function randomPassword() { if (!el.password || uploadLocked) return; el.password.value = `${Math.random().toString(36).slice(2, 8)}-${Math.random().toString(36).slice(2, 6)}`; saveSettings(); updateTerminal(); setStatus("Generated a password"); } function randomBoxName() { if (!el.boxName || uploadLocked) return; const adjectives = ["Neon", "Turbo", "Quiet", "Cosmic", "Lucky", "Midnight", "Pixel", "Rapid"]; const nouns = ["Floppy Disk", "Archive Box", "Packet Portal", "Upload Folder", "Cache Drive", "Release Bundle"]; el.boxName.value = `${adjectives[Math.floor(Math.random() * adjectives.length)]} ${nouns[Math.floor(Math.random() * nouns.length)]}`; syncSlugFromName(true); setStatus("Generated a local box name"); }