* Replaced all instances of getElementById and getElementsByClassName
with querySelector or querySelectorAll.

* Updated utilsController.js to stop disabling
no-async-promise-executor eslint rule.

* Removed unused lines in dashboard.njk.

* Refactored maxFileSize to maxSize in home.{css,js,njk}.

* Updated ClamAV codes in lolisafe.js. No more pinging.
Since querying version will also check connection anyway.

* Option "Upload to album" in homepage is now selectable.
Selecting this option will restore the uploader to not associate files
with an album.

* Fixed uploader to properly respect server's max file size.
Also updated error message of file size to use MB instead of MiB.

* Creating an album from homepage will automatically select the album.

* Updated Dropzone.js to v5.5.0.

* Bumped v1 & v3 version strings.

* Various other small fixes.
This commit is contained in:
Bobby Wibowo 2019-08-20 09:16:34 +07:00
parent 14d69bf1c1
commit 3a398721b5
No known key found for this signature in database
GPG Key ID: 51C3A1E1E22D26CF
15 changed files with 182 additions and 194 deletions

View File

@ -16,8 +16,8 @@ const uploadsDir = path.join(__dirname, '..', config.uploads.folder)
const chunkedUploads = Boolean(config.uploads.chunkSize) const chunkedUploads = Boolean(config.uploads.chunkSize)
const chunksDir = path.join(uploadsDir, 'chunks') const chunksDir = path.join(uploadsDir, 'chunks')
const maxSize = config.uploads.maxSize const maxSize = config.uploads.maxSize
const maxSizeBytes = parseInt(maxSize) * 1000 * 1000 const maxSizeBytes = parseInt(maxSize) * 1e6
const urlMaxSizeBytes = parseInt(config.uploads.urlMaxSize) * 1000 * 1000 const urlMaxSizeBytes = parseInt(config.uploads.urlMaxSize) * 1e6
const storage = multer.diskStorage({ const storage = multer.diskStorage({
destination (req, file, cb) { destination (req, file, cb) {
@ -181,11 +181,12 @@ uploadsController.actuallyUpload = async (req, res, user, albumid) => {
upload(req, res, async error => { upload(req, res, async error => {
if (error) { if (error) {
const expected = [ // Suppress error logging for errors with these codes
const suppress = [
'LIMIT_FILE_SIZE', 'LIMIT_FILE_SIZE',
'LIMIT_UNEXPECTED_FILE' 'LIMIT_UNEXPECTED_FILE'
] ]
if (expected.includes(error.code)) return erred(error.toString()) if (error.code && suppress.includes(error.code)) return erred(error.toString())
return erred(error) return erred(error)
} }
@ -597,7 +598,7 @@ uploadsController.scanFiles = (req, infoMap) => {
return `Threat found: ${result.virus}${result.lastIteration ? '' : ', and maybe more'}.` return `Threat found: ${result.virus}${result.lastIteration ? '' : ', and maybe more'}.`
}).catch(error => { }).catch(error => {
console.error(`ClamAV: ${error.toString()}.`) console.error(`ClamAV: ${error.toString()}.`)
return `ClamAV: ${error.code !== undefined ? `${error.code} , p` : 'P'}lease contact the site owner.` return `ClamAV: ${error.code !== undefined ? `${error.code}, p` : 'P'}lease contact the site owner.`
}) })
} }

View File

@ -298,71 +298,70 @@ utilsController.bulkDeleteFiles = async (field, values, user, set) => {
const failed = [] const failed = []
const ismoderator = perms.is(user, 'moderator') const ismoderator = perms.is(user, 'moderator')
await Promise.all(chunks.map((chunk, index) => { await Promise.all(chunks.map((chunk, index) => {
// It's much too dirty to code the function below without async function const job = async () => {
// eslint-disable-next-line no-async-promise-executor try {
return new Promise(async (resolve, reject) => { const files = await db.table('files')
const files = await db.table('files') .whereIn(field, chunk)
.whereIn(field, chunk) .where(function () {
.where(function () { if (!ismoderator)
if (!ismoderator) this.where('userid', user.id)
this.where('userid', user.id)
})
.catch(reject)
// Push files that could not be found in DB
failed.push.apply(failed, chunk.filter(v => !files.find(file => file[field] === v)))
// Delete all found files physically
const deletedFiles = []
await Promise.all(files.map(file =>
utilsController.deleteFile(file.name)
.then(() => deletedFiles.push(file))
.catch(error => {
failed.push(file[field])
console.error(error)
}) })
))
if (!deletedFiles.length) // Push files that could not be found in DB
return resolve() failed.push.apply(failed, chunk.filter(v => !files.find(file => file[field] === v)))
// Delete all found files from database // Delete all found files physically
const deletedFromDb = await db.table('files') const deletedFiles = []
.whereIn('id', deletedFiles.map(file => file.id)) await Promise.all(files.map(file =>
.del() utilsController.deleteFile(file.name)
.catch(reject) .then(() => deletedFiles.push(file))
.catch(error => {
failed.push(file[field])
console.error(error)
})
))
if (set) if (!deletedFiles.length)
deletedFiles.forEach(file => { return true
const identifier = file.name.split('.')[0]
set.delete(identifier)
// console.log(`Removed ${identifier} from identifiers cache (bulkDeleteFiles)`)
})
// Update albums if necessary // Delete all found files from database
if (deletedFromDb) { const deletedFromDb = await db.table('files')
const albumids = [] .whereIn('id', deletedFiles.map(file => file.id))
deletedFiles.forEach(file => { .del()
if (file.albumid && !albumids.includes(file.albumid))
albumids.push(file.albumid) if (set)
}) deletedFiles.forEach(file => {
await db.table('albums') const identifier = file.name.split('.')[0]
.whereIn('id', albumids) set.delete(identifier)
.update('editedAt', Math.floor(Date.now() / 1000)) // console.log(`Removed ${identifier} from identifiers cache (bulkDeleteFiles)`)
.catch(console.error) })
// Update albums if necessary
if (deletedFromDb) {
const albumids = []
deletedFiles.forEach(file => {
if (file.albumid && !albumids.includes(file.albumid))
albumids.push(file.albumid)
})
await db.table('albums')
.whereIn('id', albumids)
.update('editedAt', Math.floor(Date.now() / 1000))
.catch(console.error)
}
// Purge Cloudflare's cache if necessary
if (config.cloudflare.purgeCache)
utilsController.purgeCloudflareCache(deletedFiles.map(file => file.name), true, true)
.then(results => {
for (const result of results)
if (result.errors.length)
result.errors.forEach(error => console.error(`CF: ${error}`))
})
} catch (error) {
console.error(error)
} }
}
// Purge Cloudflare's cache if necessary return new Promise(resolve => job().then(() => resolve()))
if (config.cloudflare.purgeCache)
utilsController.purgeCloudflareCache(deletedFiles.map(file => file.name), true, true)
.then(results => {
for (const result of results)
if (result.errors.length)
result.errors.forEach(error => console.error(`CF: ${error}`))
})
return resolve()
}).catch(console.error)
})) }))
return failed return failed
} }

View File

@ -166,22 +166,23 @@ const start = async () => {
const scan = config.uploads.scan const scan = config.uploads.scan
if (scan && scan.enabled) { if (scan && scan.enabled) {
const created = await new Promise(async (resolve, reject) => { const createScanner = async () => {
if (!scan.ip || !scan.port) try {
return reject(new Error('clamd IP or port is missing')) if (!scan.ip || !scan.port)
throw new Error('clamd IP or port is missing')
const ping = await clamd.ping(scan.ip, scan.port).catch(reject) const version = await clamd.version(scan.ip, scan.port)
if (!ping) console.log(`${scan.ip}:${scan.port} ${version}`)
return reject(new Error('Could not ping clamd'))
const version = await clamd.version(scan.ip, scan.port).catch(reject) const scanner = clamd.createScanner(scan.ip, scan.port)
console.log(`${scan.ip}:${scan.port} ${version}`) safe.set('clam-scanner', scanner)
return true
const scanner = clamd.createScanner(scan.ip, scan.port) } catch (error) {
safe.set('clam-scanner', scanner) console.error(`ClamAV: ${error.toString()}`)
return resolve(true) return false
}).catch(error => console.error(error.toString())) }
if (!created) return process.exit(1) }
if (!await createScanner()) return process.exit(1)
} }
if (config.uploads.cacheFileIdentifiers) { if (config.uploads.cacheFileIdentifiers) {

View File

@ -33,7 +33,7 @@
display: none; display: none;
} }
#maxFileSize { #maxSize {
font-size: 1rem; font-size: 1rem;
} }

View File

@ -21,7 +21,7 @@ page.getPrettyBytes = function (num, si) {
} }
window.onload = function () { window.onload = function () {
const elements = document.getElementsByClassName('file-size') const elements = document.querySelectorAll('.file-size')
for (let i = 0; i < elements.length; i++) for (let i = 0; i < elements.length; i++)
elements[i].innerHTML = page.getPrettyBytes(parseInt(elements[i].innerHTML.replace(/\s*B$/i, ''))) elements[i].innerHTML = page.getPrettyBytes(parseInt(elements[i].innerHTML.replace(/\s*B$/i, '')))

View File

@ -56,19 +56,19 @@ page.verify = function () {
window.onload = function () { window.onload = function () {
page.verify() page.verify()
page.user = document.getElementById('user') page.user = document.querySelector('#user')
page.pass = document.getElementById('pass') page.pass = document.querySelector('#pass')
// Prevent default form's submit action // Prevent default form's submit action
document.getElementById('authForm').addEventListener('submit', function (event) { document.querySelector('#authForm').addEventListener('submit', function (event) {
event.preventDefault() event.preventDefault()
}) })
document.getElementById('loginBtn').addEventListener('click', function () { document.querySelector('#loginBtn').addEventListener('click', function () {
page.do('login') page.do('login')
}) })
document.getElementById('registerBtn').addEventListener('click', function () { document.querySelector('#registerBtn').addEventListener('click', function () {
page.do('register') page.do('register')
}) })
} }

View File

@ -115,14 +115,13 @@ page.verifyToken = function (token, reloadOnError) {
} }
page.prepareDashboard = function () { page.prepareDashboard = function () {
page.dom = document.getElementById('page') page.dom = document.querySelector('#page')
page.dom.addEventListener('click', page.domClick, true) page.dom.addEventListener('click', page.domClick, true)
// document.getElementById('auth').style.display = 'none' document.querySelector('#dashboard').style.display = 'block'
document.getElementById('dashboard').style.display = 'block'
if (page.permissions.moderator) { if (page.permissions.moderator) {
const itemManageUploads = document.getElementById('itemManageUploads') const itemManageUploads = document.querySelector('#itemManageUploads')
itemManageUploads.removeAttribute('disabled') itemManageUploads.removeAttribute('disabled')
itemManageUploads.addEventListener('click', function () { itemManageUploads.addEventListener('click', function () {
page.setActiveMenu(this) page.setActiveMenu(this)
@ -131,14 +130,14 @@ page.prepareDashboard = function () {
} }
if (page.permissions.admin) { if (page.permissions.admin) {
const itemServerStats = document.getElementById('itemServerStats') const itemServerStats = document.querySelector('#itemServerStats')
itemServerStats.removeAttribute('disabled') itemServerStats.removeAttribute('disabled')
itemServerStats.addEventListener('click', function () { itemServerStats.addEventListener('click', function () {
page.setActiveMenu(this) page.setActiveMenu(this)
page.getServerStats() page.getServerStats()
}) })
const itemManageUsers = document.getElementById('itemManageUsers') const itemManageUsers = document.querySelector('#itemManageUsers')
itemManageUsers.removeAttribute('disabled') itemManageUsers.removeAttribute('disabled')
itemManageUsers.addEventListener('click', function () { itemManageUsers.addEventListener('click', function () {
page.setActiveMenu(this) page.setActiveMenu(this)
@ -146,37 +145,37 @@ page.prepareDashboard = function () {
}) })
} }
document.getElementById('itemUploads').addEventListener('click', function () { document.querySelector('#itemUploads').addEventListener('click', function () {
page.setActiveMenu(this) page.setActiveMenu(this)
page.getUploads({ all: false }) page.getUploads({ all: false })
}) })
document.getElementById('itemDeleteByNames').addEventListener('click', function () { document.querySelector('#itemDeleteByNames').addEventListener('click', function () {
page.setActiveMenu(this) page.setActiveMenu(this)
page.deleteByNames() page.deleteByNames()
}) })
document.getElementById('itemManageGallery').addEventListener('click', function () { document.querySelector('#itemManageGallery').addEventListener('click', function () {
page.setActiveMenu(this) page.setActiveMenu(this)
page.getAlbums() page.getAlbums()
}) })
document.getElementById('itemFileLength').addEventListener('click', function () { document.querySelector('#itemFileLength').addEventListener('click', function () {
page.setActiveMenu(this) page.setActiveMenu(this)
page.changeFileLength() page.changeFileLength()
}) })
document.getElementById('itemTokens').addEventListener('click', function () { document.querySelector('#itemTokens').addEventListener('click', function () {
page.setActiveMenu(this) page.setActiveMenu(this)
page.changeToken() page.changeToken()
}) })
document.getElementById('itemPassword').addEventListener('click', function () { document.querySelector('#itemPassword').addEventListener('click', function () {
page.setActiveMenu(this) page.setActiveMenu(this)
page.changePassword() page.changePassword()
}) })
const logoutBtn = document.getElementById('itemLogout') const logoutBtn = document.querySelector('#itemLogout')
logoutBtn.addEventListener('click', function () { logoutBtn.addEventListener('click', function () {
page.logout() page.logout()
}) })
@ -315,11 +314,12 @@ page.switchPage = function (action, element) {
case 'page-goto': case 'page-goto':
views.pageNum = parseInt(element.dataset.goto) views.pageNum = parseInt(element.dataset.goto)
return func(views, element) return func(views, element)
case 'jump-to-page': case 'jump-to-page': {
const jumpToPage = parseInt(document.getElementById('jumpToPage').value) const jumpToPage = parseInt(document.querySelector('#jumpToPage').value)
views.pageNum = isNaN(jumpToPage) ? 0 : (jumpToPage - 1) views.pageNum = isNaN(jumpToPage) ? 0 : (jumpToPage - 1)
if (views.pageNum < 0) views.pageNum = 0 if (views.pageNum < 0) views.pageNum = 0
return func(views, element) return func(views, element)
}
} }
} }
@ -480,7 +480,7 @@ page.getUploads = function ({ pageNum, album, all, filters } = {}, element) {
` `
page.fadeIn() page.fadeIn()
const table = document.getElementById('table') const table = document.querySelector('#table')
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
const upload = files[i] const upload = files[i]
@ -553,7 +553,7 @@ page.getUploads = function ({ pageNum, album, all, filters } = {}, element) {
` `
page.fadeIn() page.fadeIn()
const table = document.getElementById('table') const table = document.querySelector('#table')
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
const upload = files[i] const upload = files[i]
@ -597,7 +597,7 @@ page.getUploads = function ({ pageNum, album, all, filters } = {}, element) {
} }
if (allSelected && files.length) { if (allSelected && files.length) {
const selectAll = document.getElementById('selectAll') const selectAll = document.querySelector('#selectAll')
if (selectAll) selectAll.checked = true if (selectAll) selectAll.checked = true
} }
@ -771,7 +771,7 @@ page.clearSelection = function () {
localStorage[lsKeys.selected[page.currentView]] = '[]' localStorage[lsKeys.selected[page.currentView]] = '[]'
page.selected[page.currentView] = [] page.selected[page.currentView] = []
const selectAll = document.getElementById('selectAll') const selectAll = document.querySelector('#selectAll')
if (selectAll) selectAll.checked = false if (selectAll) selectAll.checked = false
return swal('Cleared selection!', `Unselected ${count} ${suffix}.`, 'success') return swal('Cleared selection!', `Unselected ${count} ${suffix}.`, 'success')
@ -809,14 +809,14 @@ page.filtersHelp = function (element) {
} }
page.filterUploads = function (element) { page.filterUploads = function (element) {
const filters = document.getElementById('filters').value const filters = document.querySelector('#filters').value
page.getUploads({ all: true, filters }, element) page.getUploads({ all: true, filters }, element)
} }
page.viewUserUploads = function (id) { page.viewUserUploads = function (id) {
const user = page.cache.users[id] const user = page.cache.users[id]
if (!user) return if (!user) return
page.setActiveMenu(document.getElementById('itemManageUploads')) page.setActiveMenu(document.querySelector('#itemManageUploads'))
page.getUploads({ all: true, filters: `user:${user.username.replace(/ /g, '\\ ')}` }) page.getUploads({ all: true, filters: `user:${user.username.replace(/ /g, '\\ ')}` })
} }
@ -944,7 +944,7 @@ page.deleteByNames = function () {
} }
page.deleteFileByNames = function () { page.deleteFileByNames = function () {
const names = document.getElementById('names').value const names = document.querySelector('#names').value
.split(/\r?\n/) .split(/\r?\n/)
.filter(function (n) { .filter(function (n) {
return n.trim().length return n.trim().length
@ -986,7 +986,7 @@ page.deleteFileByNames = function () {
if (bulkdelete.data.failed && bulkdelete.data.failed.length) if (bulkdelete.data.failed && bulkdelete.data.failed.length)
deleted -= bulkdelete.data.failed.length deleted -= bulkdelete.data.failed.length
document.getElementById('names').value = bulkdelete.data.failed.join('\n') document.querySelector('#names').value = bulkdelete.data.failed.join('\n')
swal('Deleted!', `${deleted} file${deleted === 1 ? ' has' : 's have'} been deleted.`, 'success') swal('Deleted!', `${deleted} file${deleted === 1 ? ' has' : 's have'} been deleted.`, 'success')
}).catch(function (error) { }).catch(function (error) {
console.log(error) console.log(error)
@ -1058,7 +1058,7 @@ page.addFilesToAlbum = function (ids, callback) {
}).then(function (choose) { }).then(function (choose) {
if (!choose) return if (!choose) return
const albumid = parseInt(document.getElementById('swalAlbum').value) const albumid = parseInt(document.querySelector('#swalAlbum').value)
if (isNaN(albumid)) if (isNaN(albumid))
return swal('An error occurred!', 'You did not choose an album.', 'error') return swal('An error occurred!', 'You did not choose an album.', 'error')
@ -1107,7 +1107,7 @@ page.addFilesToAlbum = function (ids, callback) {
return return
} }
const select = document.getElementById('swalAlbum') const select = document.querySelector('#swalAlbum')
// If the prompt was replaced, the container would be missing // If the prompt was replaced, the container would be missing
if (!select) return if (!select) return
select.innerHTML += list.data.albums select.innerHTML += list.data.albums
@ -1180,7 +1180,7 @@ page.getAlbums = function () {
page.fadeIn() page.fadeIn()
const homeDomain = response.data.homeDomain const homeDomain = response.data.homeDomain
const table = document.getElementById('table') const table = document.querySelector('#table')
for (let i = 0; i < response.data.albums.length; i++) { for (let i = 0; i < response.data.albums.length; i++) {
const album = response.data.albums[i] const album = response.data.albums[i]
@ -1292,11 +1292,11 @@ page.editAlbum = function (id) {
axios.post('api/albums/edit', { axios.post('api/albums/edit', {
id, id,
name: document.getElementById('swalName').value, name: document.querySelector('#swalName').value,
description: document.getElementById('swalDescription').value, description: document.querySelector('#swalDescription').value,
download: document.getElementById('swalDownload').checked, download: document.querySelector('#swalDownload').checked,
public: document.getElementById('swalPublic').checked, public: document.querySelector('#swalPublic').checked,
requestLink: document.getElementById('swalRequestLink').checked requestLink: document.querySelector('#swalRequestLink').checked
}).then(function (response) { }).then(function (response) {
if (!response) return if (!response) return
@ -1370,8 +1370,8 @@ page.submitAlbum = function (element) {
page.isLoading(element, true) page.isLoading(element, true)
axios.post('api/albums', { axios.post('api/albums', {
name: document.getElementById('albumName').value, name: document.querySelector('#albumName').value,
description: document.getElementById('albumDescription').value description: document.querySelector('#albumDescription').value
}).then(function (response) { }).then(function (response) {
if (!response) return if (!response) return
@ -1405,7 +1405,7 @@ page.getAlbumsSidebar = function () {
return swal('An error occurred!', response.data.description, 'error') return swal('An error occurred!', response.data.description, 'error')
} }
const albumsContainer = document.getElementById('albumsContainer') const albumsContainer = document.querySelector('#albumsContainer')
albumsContainer.innerHTML = '' albumsContainer.innerHTML = ''
if (response.data.albums === undefined) return if (response.data.albums === undefined) return
@ -1473,8 +1473,8 @@ page.changeFileLength = function () {
` `
page.fadeIn() page.fadeIn()
document.getElementById('setFileLength').addEventListener('click', function () { document.querySelector('#setFileLength').addEventListener('click', function () {
page.setFileLength(document.getElementById('fileLength').value, this) page.setFileLength(document.querySelector('#fileLength').value, this)
}) })
}).catch(function (error) { }).catch(function (error) {
console.log(error) console.log(error)
@ -1604,9 +1604,9 @@ page.changePassword = function () {
` `
page.fadeIn() page.fadeIn()
document.getElementById('sendChangePassword').addEventListener('click', function () { document.querySelector('#sendChangePassword').addEventListener('click', function () {
if (document.getElementById('password').value === document.getElementById('passwordConfirm').value) if (document.querySelector('#password').value === document.querySelector('#passwordConfirm').value)
page.sendNewPassword(document.getElementById('password').value, this) page.sendNewPassword(document.querySelector('#password').value, this)
else else
swal({ swal({
title: 'Password mismatch!', title: 'Password mismatch!',
@ -1644,7 +1644,7 @@ page.sendNewPassword = function (pass, element) {
} }
page.setActiveMenu = function (activeItem) { page.setActiveMenu = function (activeItem) {
const menu = document.getElementById('menu') const menu = document.querySelector('#menu')
const items = menu.getElementsByTagName('a') const items = menu.getElementsByTagName('a')
for (let i = 0; i < items.length; i++) for (let i = 0; i < items.length; i++)
items[i].classList.remove('is-active') items[i].classList.remove('is-active')
@ -1753,7 +1753,7 @@ page.getUsers = function ({ pageNum } = {}, element) {
` `
page.fadeIn() page.fadeIn()
const table = document.getElementById('table') const table = document.querySelector('#table')
for (let i = 0; i < response.data.users.length; i++) { for (let i = 0; i < response.data.users.length; i++) {
const user = response.data.users[i] const user = response.data.users[i]
@ -1810,12 +1810,11 @@ page.getUsers = function ({ pageNum } = {}, element) {
` `
table.appendChild(tr) table.appendChild(tr)
// page.checkboxes.users = Array.from(table.getElementsByClassName('checkbox'))
page.checkboxes.users = Array.from(table.querySelectorAll('.checkbox[data-action="select"]')) page.checkboxes.users = Array.from(table.querySelectorAll('.checkbox[data-action="select"]'))
} }
if (allSelected && response.data.users.length) { if (allSelected && response.data.users.length) {
const selectAll = document.getElementById('selectAll') const selectAll = document.querySelector('#selectAll')
if (selectAll) selectAll.checked = true if (selectAll) selectAll.checked = true
} }
@ -1888,10 +1887,10 @@ page.editUser = function (id) {
axios.post('api/users/edit', { axios.post('api/users/edit', {
id, id,
username: document.getElementById('swalUsername').value, username: document.querySelector('#swalUsername').value,
group: document.getElementById('swalGroup').value, group: document.querySelector('#swalGroup').value,
enabled: document.getElementById('swalEnabled').checked, enabled: document.querySelector('#swalEnabled').checked,
resetPassword: document.getElementById('swalResetPassword').checked resetPassword: document.querySelector('#swalResetPassword').checked
}).then(function (response) { }).then(function (response) {
if (!response) return if (!response) return

View File

@ -7,7 +7,7 @@ const page = {
// configs from api/check // configs from api/check
private: null, private: null,
enableUserAccounts: null, enableUserAccounts: null,
maxFileSize: null, maxSize: null,
chunkSize: null, chunkSize: null,
// store album id that will be used with upload requests // store album id that will be used with upload requests
@ -27,12 +27,12 @@ page.checkIfPublic = function () {
axios.get('api/check').then(function (response) { axios.get('api/check').then(function (response) {
page.private = response.data.private page.private = response.data.private
page.enableUserAccounts = response.data.enableUserAccounts page.enableUserAccounts = response.data.enableUserAccounts
page.maxFileSize = response.data.maxFileSize page.maxSize = response.data.maxSize
page.chunkSize = response.data.chunkSize page.chunkSize = response.data.chunkSize
page.preparePage() page.preparePage()
}).catch(function (error) { }).catch(function (error) {
console.log(error) console.log(error)
const button = document.getElementById('loginToUpload') const button = document.querySelector('#loginToUpload')
button.classList.remove('is-loading') button.classList.remove('is-loading')
button.innerText = 'Error occurred. Reload the page?' button.innerText = 'Error occurred. Reload the page?'
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error') return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
@ -44,7 +44,7 @@ page.preparePage = function () {
if (page.token) { if (page.token) {
return page.verifyToken(page.token, true) return page.verifyToken(page.token, true)
} else { } else {
const button = document.getElementById('loginToUpload') const button = document.querySelector('#loginToUpload')
button.href = 'auth' button.href = 'auth'
button.classList.remove('is-loading') button.classList.remove('is-loading')
@ -84,7 +84,7 @@ page.verifyToken = function (token, reloadOnError) {
page.prepareUpload = function () { page.prepareUpload = function () {
// I think this fits best here because we need to check for a valid token before we can get the albums // I think this fits best here because we need to check for a valid token before we can get the albums
if (page.token) { if (page.token) {
page.albumSelect = document.getElementById('albumSelect') page.albumSelect = document.querySelector('#albumSelect')
page.albumSelect.addEventListener('change', function () { page.albumSelect.addEventListener('change', function () {
page.album = parseInt(page.albumSelect.value) page.album = parseInt(page.albumSelect.value)
@ -93,14 +93,14 @@ page.prepareUpload = function () {
page.prepareAlbums() page.prepareAlbums()
// Display the album selection // Display the album selection
document.getElementById('albumDiv').style.display = 'flex' document.querySelector('#albumDiv').style.display = 'flex'
} }
document.getElementById('maxFileSize').innerHTML = `Maximum upload size per file is ${page.maxFileSize}` document.querySelector('#maxSize').innerHTML = `Maximum upload size per file is ${page.maxSize}`
document.getElementById('loginToUpload').style.display = 'none' document.querySelector('#loginToUpload').style.display = 'none'
if (!page.token && page.enableUserAccounts) if (!page.token && page.enableUserAccounts)
document.getElementById('loginLinkText').innerHTML = 'Create an account and keep track of your uploads' document.querySelector('#loginLinkText').innerHTML = 'Create an account and keep track of your uploads'
const previewNode = document.querySelector('#tpl') const previewNode = document.querySelector('#tpl')
page.previewTemplate = previewNode.innerHTML page.previewTemplate = previewNode.innerHTML
@ -108,7 +108,7 @@ page.prepareUpload = function () {
page.prepareDropzone() page.prepareDropzone()
const tabs = document.getElementById('tabs') const tabs = document.querySelector('#tabs')
if (tabs) { if (tabs) {
tabs.style.display = 'flex' tabs.style.display = 'flex'
const items = tabs.getElementsByTagName('li') const items = tabs.getElementsByTagName('li')
@ -117,12 +117,12 @@ page.prepareUpload = function () {
page.setActiveTab(this.dataset.id) page.setActiveTab(this.dataset.id)
}) })
document.getElementById('uploadUrls').addEventListener('click', function () { document.querySelector('#uploadUrls').addEventListener('click', function () {
page.uploadUrls(this) page.uploadUrls(this)
}) })
page.setActiveTab('tab-files') page.setActiveTab('tab-files')
} else { } else {
document.getElementById('tab-files').style.display = 'block' document.querySelector('#tab-files').style.display = 'block'
} }
} }
@ -130,7 +130,6 @@ page.prepareAlbums = function () {
const option = document.createElement('option') const option = document.createElement('option')
option.value = '' option.value = ''
option.innerHTML = 'Upload to album' option.innerHTML = 'Upload to album'
option.disabled = true
option.selected = true option.selected = true
page.albumSelect.appendChild(option) page.albumSelect.appendChild(option)
@ -164,7 +163,7 @@ page.prepareAlbums = function () {
} }
page.setActiveTab = function (activeId) { page.setActiveTab = function (activeId) {
const items = document.getElementById('tabs').getElementsByTagName('li') const items = document.querySelector('#tabs').getElementsByTagName('li')
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
const tabId = items[i].dataset.id const tabId = items[i].dataset.id
if (tabId === activeId) { if (tabId === activeId) {
@ -178,7 +177,7 @@ page.setActiveTab = function (activeId) {
} }
page.prepareDropzone = function () { page.prepareDropzone = function () {
const tabDiv = document.getElementById('tab-files') const tabDiv = document.querySelector('#tab-files')
const div = document.createElement('div') const div = document.createElement('div')
div.className = 'control is-expanded' div.className = 'control is-expanded'
div.innerHTML = ` div.innerHTML = `
@ -191,28 +190,31 @@ page.prepareDropzone = function () {
` `
tabDiv.querySelector('.dz-container').appendChild(div) tabDiv.querySelector('.dz-container').appendChild(div)
const maxSize = parseInt(page.maxSize)
const maxSizeBytes = maxSize * 1e6
const previewsContainer = tabDiv.querySelector('#tab-files .field.uploads') const previewsContainer = tabDiv.querySelector('#tab-files .field.uploads')
page.dropzone = new Dropzone('#dropzone', { page.dropzone = new Dropzone('#dropzone', {
url: 'api/upload', url: 'api/upload',
paramName: 'files[]', paramName: 'files[]',
maxFilesize: parseInt(page.maxFileSize), maxFilesize: maxSizeBytes / 1024 / 1024, // this option expects MiB
parallelUploads: 2, parallelUploads: 2,
uploadMultiple: false, uploadMultiple: false,
previewsContainer, previewsContainer,
previewTemplate: page.previewTemplate, previewTemplate: page.previewTemplate,
createImageThumbnails: false, createImageThumbnails: false,
maxFiles: 1000,
autoProcessQueue: true, autoProcessQueue: true,
headers: { token: page.token }, headers: { token: page.token },
chunking: Boolean(page.chunkSize), chunking: Boolean(page.chunkSize),
chunkSize: parseInt(page.chunkSize) * 1000000, // 1000000 B = 1 MB, chunkSize: (parseInt(page.chunkSize) * 1e6), // the option below expects Bytes
parallelChunkUploads: false, // when set to true, sometimes it often hangs with hundreds of parallel uploads parallelChunkUploads: false, // when set to true, it often hangs with hundreds of parallel uploads
chunksUploaded (file, done) { chunksUploaded (file, done) {
file.previewElement.querySelector('.progress').setAttribute('value', 100) file.previewElement.querySelector('.progress').setAttribute('value', 100)
file.previewElement.querySelector('.progress').innerHTML = '100%' file.previewElement.querySelector('.progress').innerHTML = '100%'
return axios.post('api/upload/finishchunks', { return axios.post('api/upload/finishchunks', {
// The API supports an array of multiple files // This API supports an array of multiple files
files: [{ files: [{
uuid: file.upload.uuid, uuid: file.upload.uuid,
original: file.name, original: file.name,
@ -245,7 +247,7 @@ page.prepareDropzone = function () {
}) })
page.dropzone.on('addedfile', function (file) { page.dropzone.on('addedfile', function (file) {
tabDiv.getElementsByClassName('uploads')[0].style.display = 'block' tabDiv.querySelector('.uploads').style.display = 'block'
file.previewElement.querySelector('.name').innerHTML = file.name file.previewElement.querySelector('.name').innerHTML = file.name
}) })
@ -257,6 +259,8 @@ page.prepareDropzone = function () {
// Update the total progress bar // Update the total progress bar
page.dropzone.on('uploadprogress', function (file, progress) { page.dropzone.on('uploadprogress', function (file, progress) {
// For some reason, chunked uploads fire 100% progress event
// for each chunk's successful uploads
if (file.upload.chunked && progress === 100) return if (file.upload.chunked && progress === 100) return
file.previewElement.querySelector('.progress').setAttribute('value', progress) file.previewElement.querySelector('.progress').setAttribute('value', progress)
file.previewElement.querySelector('.progress').innerHTML = `${progress}%` file.previewElement.querySelector('.progress').innerHTML = `${progress}%`
@ -265,7 +269,6 @@ page.prepareDropzone = function () {
page.dropzone.on('success', function (file, response) { page.dropzone.on('success', function (file, response) {
if (!response) return if (!response) return
file.previewElement.querySelector('.progress').style.display = 'none' file.previewElement.querySelector('.progress').style.display = 'none'
// file.previewElement.querySelector('.name').innerHTML = file.name
if (response.success === false) if (response.success === false)
file.previewElement.querySelector('.error').innerHTML = response.description file.previewElement.querySelector('.error').innerHTML = response.description
@ -275,6 +278,9 @@ page.prepareDropzone = function () {
}) })
page.dropzone.on('error', function (file, error) { page.dropzone.on('error', function (file, error) {
if ((typeof error === 'string' && /^File is too big/.test(error)) ||
error.description === 'MulterError: File too large')
error = `File too large (${(file.size / 1e6).toFixed(2)}MB).`
page.updateTemplateIcon(file.previewElement, 'icon-block') page.updateTemplateIcon(file.previewElement, 'icon-block')
file.previewElement.querySelector('.progress').style.display = 'none' file.previewElement.querySelector('.progress').style.display = 'none'
file.previewElement.querySelector('.name').innerHTML = file.name file.previewElement.querySelector('.name').innerHTML = file.name
@ -285,7 +291,7 @@ page.prepareDropzone = function () {
} }
page.uploadUrls = function (button) { page.uploadUrls = function (button) {
const tabDiv = document.getElementById('tab-urls') const tabDiv = document.querySelector('#tab-urls')
if (!tabDiv) return if (!tabDiv) return
if (button.classList.contains('is-loading')) return if (button.classList.contains('is-loading')) return
@ -298,19 +304,19 @@ page.uploadUrls = function (button) {
function run () { function run () {
const albumid = page.album const albumid = page.album
const previewsContainer = tabDiv.getElementsByClassName('uploads')[0] const previewsContainer = tabDiv.querySelector('.uploads')
const urls = document.getElementById('urls').value const urls = document.querySelector('#urls').value
.split(/\r?\n/) .split(/\r?\n/)
.filter(function (url) { .filter(function (url) {
return url.trim().length return url.trim().length
}) })
document.getElementById('urls').value = urls.join('\n') document.querySelector('#urls').value = urls.join('\n')
if (!urls.length) if (!urls.length)
// eslint-disable-next-line prefer-promise-reject-errors // eslint-disable-next-line prefer-promise-reject-errors
return done('You have not entered any URLs.') return done('You have not entered any URLs.')
tabDiv.getElementsByClassName('uploads')[0].style.display = 'block' tabDiv.querySelector('.uploads').style.display = 'block'
const files = urls.map(function (url) { const files = urls.map(function (url) {
const previewTemplate = document.createElement('template') const previewTemplate = document.createElement('template')
previewTemplate.innerHTML = page.previewTemplate.trim() previewTemplate.innerHTML = page.previewTemplate.trim()
@ -373,14 +379,14 @@ page.updateTemplate = function (file, response) {
const a = file.previewElement.querySelector('.link > a') const a = file.previewElement.querySelector('.link > a')
const clipboard = file.previewElement.querySelector('.clipboard-mobile > .clipboard-js') const clipboard = file.previewElement.querySelector('.clipboard-mobile > .clipboard-js')
a.href = a.innerHTML = clipboard.dataset['clipboardText'] = response.url a.href = a.innerHTML = clipboard.dataset.clipboardText = response.url
clipboard.parentElement.style.display = 'block' clipboard.parentElement.style.display = 'block'
const exec = /.[\w]+(\?|$)/.exec(response.url) const exec = /.[\w]+(\?|$)/.exec(response.url)
if (exec && exec[0] && page.imageExtensions.includes(exec[0].toLowerCase())) { if (exec && exec[0] && page.imageExtensions.includes(exec[0].toLowerCase())) {
const img = file.previewElement.querySelector('img') const img = file.previewElement.querySelector('img')
img.setAttribute('alt', response.name || '') img.setAttribute('alt', response.name || '')
img.dataset['src'] = response.url img.dataset.src = response.url
img.style.display = '' img.style.display = ''
img.onerror = function () { img.onerror = function () {
// Hide image elements that fail to load // Hide image elements that fail to load
@ -438,12 +444,12 @@ page.createAlbum = function () {
}).then(function (value) { }).then(function (value) {
if (!value) return if (!value) return
const name = document.getElementById('swalName').value const name = document.querySelector('#swalName').value
axios.post('api/albums', { axios.post('api/albums', {
name, name,
description: document.getElementById('swalDescription').value, description: document.querySelector('#swalDescription').value,
download: document.getElementById('swalDownload').checked, download: document.querySelector('#swalDownload').checked,
public: document.getElementById('swalPublic').checked public: document.querySelector('#swalPublic').checked
}, { }, {
headers: { headers: {
token: page.token token: page.token
@ -453,9 +459,10 @@ page.createAlbum = function () {
return swal('An error occurred!', response.data.description, 'error') return swal('An error occurred!', response.data.description, 'error')
const option = document.createElement('option') const option = document.createElement('option')
page.albumSelect.appendChild(option)
option.value = response.data.id option.value = response.data.id
option.innerHTML = name option.innerHTML = name
page.albumSelect.appendChild(option) option.selected = true
swal('Woohoo!', 'Album was created successfully', 'success') swal('Woohoo!', 'Album was created successfully', 'success')
}).catch(function (error) { }).catch(function (error) {
@ -497,7 +504,7 @@ window.onload = function () {
elements_selector: '.field.uploads img' elements_selector: '.field.uploads img'
}) })
document.getElementById('createAlbum').addEventListener('click', function () { document.querySelector('#createAlbum').addEventListener('click', function () {
page.createAlbum() page.createAlbum()
}) })
} }

View File

@ -93,7 +93,7 @@ page.doRenderSwal = function () {
} }
page.getRenderVersion = function () { page.getRenderVersion = function () {
const renderScript = document.getElementById('renderScript') const renderScript = document.querySelector('#renderScript')
if (renderScript && renderScript.dataset.version) if (renderScript && renderScript.dataset.version)
return `?v=${renderScript.dataset.version}` return `?v=${renderScript.dataset.version}`
return '' return ''

View File

@ -4,7 +4,7 @@ page.prepareShareX = function () {
if (!page.token) return if (!page.token) return
const origin = (location.hostname + location.pathname).replace(/\/(dashboard)?$/, '') const origin = (location.hostname + location.pathname).replace(/\/(dashboard)?$/, '')
const originClean = origin.replace(/\//g, '_') const originClean = origin.replace(/\//g, '_')
const sharexElement = document.getElementById('ShareX') const sharexElement = document.querySelector('#ShareX')
const sharexFile = `{ const sharexFile = `{
"Name": "${originClean}", "Name": "${originClean}",
"DestinationType": "ImageUploader, FileUploader", "DestinationType": "ImageUploader, FileUploader",

File diff suppressed because one or more lines are too long

View File

@ -10,7 +10,7 @@ routes.get('/check', (req, res, next) => {
return res.json({ return res.json({
private: config.private, private: config.private,
enableUserAccounts: config.enableUserAccounts, enableUserAccounts: config.enableUserAccounts,
maxFileSize: config.uploads.maxSize, maxSize: config.uploads.maxSize,
chunkSize: config.uploads.chunkSize chunkSize: config.uploads.chunkSize
}) })
}) })

View File

@ -16,9 +16,9 @@
v3: CSS and JS files (libs such as bulma, lazyload, etc). v3: CSS and JS files (libs such as bulma, lazyload, etc).
v4: Renders in /public/render/* directories (to be used by render.js). v4: Renders in /public/render/* directories (to be used by render.js).
#} #}
{% set v1 = "S3TAWpPeFS" %} {% set v1 = "f0nYw5J15T" %}
{% set v2 = "hiboQUzAzp" %} {% set v2 = "hiboQUzAzp" %}
{% set v3 = "RpD2narcvz" %} {% set v3 = "f0nYw5J15T" %}
{% set v4 = "S3TAWpPeFS" %} {% set v4 = "S3TAWpPeFS" %}
{# {#

View File

@ -19,25 +19,6 @@
{% block content %} {% block content %}
{{ super() }} {{ super() }}
{#
<!-- This isn't even being used apparently -->
<section id="auth" class="hero is-light is-fullheight">
<div class="hero-body">
<div class="container">
<h1 class="title">
Admin dashboard
</h1>
<h2 class="subtitle">
<p class="control has-addons">
<input id="token" class="input is-danger" type="text" placeholder="Your admin token">
<a id="tokenSubmit" class="button is-danger is-outlined">Check</a>
</p>
</h2>
</div>
</div>
</section>
#}
<section id="dashboard" class="section"> <section id="dashboard" class="section">
<div id="panel" class="container"> <div id="panel" class="container">
<h1 class="title"> <h1 class="title">

View File

@ -38,7 +38,7 @@
<h1 class="title">{{ globals.name }}</h1> <h1 class="title">{{ globals.name }}</h1>
<h2 class="subtitle">{{ globals.home_subtitle | safe }}</h2> <h2 class="subtitle">{{ globals.home_subtitle | safe }}</h2>
<h3 id="maxFileSize" class="subtitle"></h3> <h3 id="maxSize" class="subtitle"></h3>
<div class="columns is-gapless"> <div class="columns is-gapless">
<div class="column is-hidden-mobile"></div> <div class="column is-hidden-mobile"></div>