193 lines
7.6 KiB
JavaScript
193 lines
7.6 KiB
JavaScript
|
|
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 = "";
|
||
|
|
if (!uploadsEnabled) reason = "Guest uploads are disabled.";
|
||
|
|
else if (uploadLocked) reason = "This upload already started. Press Clear to create another box.";
|
||
|
|
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.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) {
|
||
|
|
el.apiKeyState.textContent = "";
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const value = el.apiKeyInput.value.trim();
|
||
|
|
if (!value) {
|
||
|
|
el.apiKeyState.textContent = "waiting";
|
||
|
|
saveSettings();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
el.apiKeyInput.disabled = true;
|
||
|
|
wrapper?.classList.add("is-checking");
|
||
|
|
el.apiKeyState.textContent = "checking";
|
||
|
|
apiKeyTimer = setTimeout(() => {
|
||
|
|
wrapper?.classList.remove("is-checking");
|
||
|
|
el.apiKeyInput.disabled = uploadLocked;
|
||
|
|
if (validApiKey(value)) {
|
||
|
|
el.apiKeyState.textContent = "saved locally";
|
||
|
|
saveSettings();
|
||
|
|
} else {
|
||
|
|
el.apiKeyInput.value = "";
|
||
|
|
el.apiKeyState.textContent = "invalid";
|
||
|
|
saveSettings();
|
||
|
|
showToast("Invalid API key removed. Paste a valid API key to save it.", "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");
|
||
|
|
}
|