2026-04-30 11:05:56 +03:00
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 = "" ;
2026-05-04 02:27:36 +03:00
const policyMessage = apiKeyPolicyMessage ( ) ;
2026-04-30 11:05:56 +03:00
if ( ! uploadsEnabled ) reason = "Guest uploads are disabled." ;
else if ( uploadLocked ) reason = "This upload already started. Press Clear to create another box." ;
2026-05-04 02:27:36 +03:00
else if ( policyMessage ) reason = policyMessage ;
2026-04-30 11:05:56 +03:00
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 ) ) ;
2026-05-04 02:27:36 +03:00
if ( ! el . apiKeyMode ? . checked ) {
clearTimeout ( apiKeyTimer ) ;
apiKeyValidationRun += 1 ;
resetAccountLimits ( ) ;
updateLimitHint ( ) ;
renderFiles ( ) ;
}
2026-04-30 11:05:56 +03:00
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 ) {
2026-05-04 02:27:36 +03:00
apiKeyValidationRun += 1 ;
resetAccountLimits ( ) ;
2026-04-30 11:05:56 +03:00
el . apiKeyState . textContent = "" ;
2026-05-04 02:27:36 +03:00
updateLimitHint ( ) ;
renderFiles ( ) ;
2026-04-30 11:05:56 +03:00
return ;
}
const value = el . apiKeyInput . value . trim ( ) ;
if ( ! value ) {
2026-05-04 02:27:36 +03:00
apiKeyValidationRun += 1 ;
resetAccountLimits ( ) ;
2026-04-30 11:05:56 +03:00
el . apiKeyState . textContent = "waiting" ;
2026-05-04 02:27:36 +03:00
updateLimitHint ( ) ;
renderFiles ( ) ;
2026-04-30 11:05:56 +03:00
saveSettings ( ) ;
return ;
}
2026-05-04 02:27:36 +03:00
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 ;
2026-04-30 11:05:56 +03:00
el . apiKeyInput . disabled = true ;
wrapper ? . classList . add ( "is-checking" ) ;
el . apiKeyState . textContent = "checking" ;
2026-05-04 02:27:36 +03:00
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 ( ) ;
2026-04-30 11:05:56 +03:00
saveSettings ( ) ;
2026-05-04 02:27:36 +03:00
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" ) ;
2026-04-30 11:05:56 +03:00
}
} , 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" ) ;
}