mirror of
https://github.com/BobbyWibowo/lolisafe.git
synced 2025-02-20 12:19:03 +00:00
Updates
* A bunch of refactors in public JS files (home.js, dashboard.js, etcetera). * Added lazyload to home page (for thumbs of uploaded images), dashboard (for thumbs view) and albums' public link. Albums' public link will silently fallback to loading all thumbs at once if JavaScript is disabled. * A bunch of others code improvements. Honestly I'm too lazy to track all the changes.
This commit is contained in:
parent
c51365adb5
commit
61e1896945
@ -163,13 +163,12 @@ albumsController.delete = async (req, res, next) => {
|
||||
|
||||
// Unlink zip archive of the album if it exists
|
||||
const zipPath = path.join(zipsDir, `${identifier}.zip`)
|
||||
return fs.access(zipPath, error => {
|
||||
if (error) { return res.json({ success: true, failedids }) }
|
||||
fs.unlink(zipPath, error => {
|
||||
if (!error) { return res.json({ success: true, failedids }) }
|
||||
fs.unlink(zipPath, error => {
|
||||
if (error && error.code !== 'ENOENT') {
|
||||
console.log(error)
|
||||
res.json({ success: false, description: error.toString(), failedids })
|
||||
})
|
||||
return res.json({ success: false, description: error.toString(), failedids })
|
||||
}
|
||||
res.json({ success: true, failedids })
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -246,7 +246,9 @@ uploadsController.actuallyFinishChunks = async (req, res, user, albumid) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunkPath = path.join(uuidDir, chunkName)
|
||||
fs.unlink(chunkPath, error => {
|
||||
if (error) { return reject(error) }
|
||||
if (error && error.code !== 'ENOENT') {
|
||||
return reject(error)
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
@ -366,7 +368,7 @@ uploadsController.writeFilesToDb = (req, res, user, infoMap) => {
|
||||
timestamp: Math.floor(Date.now() / 1000)
|
||||
})
|
||||
} else {
|
||||
utils.deleteFile(info.data.filename).then(() => {}).catch(error => console.log(error))
|
||||
utils.deleteFile(info.data.filename).catch(console.error)
|
||||
existingFiles.push(dbFile)
|
||||
}
|
||||
|
||||
@ -447,6 +449,7 @@ uploadsController.delete = async (req, res) => {
|
||||
// TODO: Wrap utils.bulkDeleteFilesByIds() instead
|
||||
const user = await utils.authorize(req, res)
|
||||
if (!user) { return }
|
||||
|
||||
const id = req.body.id
|
||||
if (id === undefined || id === '') {
|
||||
return res.json({ success: false, description: 'No file specified.' })
|
||||
@ -461,21 +464,17 @@ uploadsController.delete = async (req, res) => {
|
||||
})
|
||||
.first()
|
||||
|
||||
try {
|
||||
await utils.deleteFile(file.name).catch(error => {
|
||||
// ENOENT is missing file, for whatever reason, then just delete from db anyways
|
||||
if (error.code !== 'ENOENT') { throw error }
|
||||
})
|
||||
await db.table('files')
|
||||
.where('id', id)
|
||||
.del()
|
||||
if (file.albumid) {
|
||||
await db.table('albums')
|
||||
.where('id', file.albumid)
|
||||
.update('editedAt', Math.floor(Date.now() / 1000))
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
const deleteFile = await utils.deleteFile(file.name).catch(console.error)
|
||||
if (!deleteFile) { return }
|
||||
|
||||
await db.table('files')
|
||||
.where('id', id)
|
||||
.del()
|
||||
|
||||
if (file.albumid) {
|
||||
await db.table('albums')
|
||||
.where('id', file.albumid)
|
||||
.update('editedAt', Math.floor(Date.now() / 1000))
|
||||
}
|
||||
|
||||
return res.json({ success: true })
|
||||
|
@ -8,9 +8,17 @@ const path = require('path')
|
||||
const units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||
|
||||
const utilsController = {}
|
||||
const uploadsDir = path.join(__dirname, '..', config.uploads.folder)
|
||||
const thumbsDir = path.join(uploadsDir, 'thumbs')
|
||||
|
||||
utilsController.imageExtensions = ['.webp', '.jpg', '.jpeg', '.bmp', '.gif', '.png']
|
||||
utilsController.videoExtensions = ['.webm', '.mp4', '.wmv', '.avi', '.mov', '.mkv']
|
||||
|
||||
utilsController.mayGenerateThumb = extname => {
|
||||
return (config.uploads.generateThumbnails.image && utilsController.imageExtensions.includes(extname)) ||
|
||||
(config.uploads.generateThumbnails.video && utilsController.videoExtensions.includes(extname))
|
||||
}
|
||||
|
||||
utilsController.getPrettyDate = date => {
|
||||
return date.getFullYear() + '-' +
|
||||
(date.getMonth() + 1) + '-' +
|
||||
@ -48,72 +56,58 @@ utilsController.authorize = async (req, res) => {
|
||||
|
||||
const user = await db.table('users').where('token', token).first()
|
||||
if (user) { return user }
|
||||
|
||||
res.status(401).json({ success: false, description: 'Invalid token.' })
|
||||
}
|
||||
|
||||
utilsController.generateThumbs = (file, basedomain) => {
|
||||
const ext = path.extname(file.name).toLowerCase()
|
||||
const isVideoExt = utilsController.videoExtensions.includes(ext)
|
||||
const isImageExt = utilsController.imageExtensions.includes(ext)
|
||||
const extname = path.extname(file.name).toLowerCase()
|
||||
if (!utilsController.mayGenerateThumb(extname)) { return }
|
||||
|
||||
if ((!isVideoExt && !isImageExt) ||
|
||||
(isVideoExt && config.uploads.generateThumbnails.video !== true) ||
|
||||
(isImageExt && config.uploads.generateThumbnails.image !== true)) {
|
||||
return
|
||||
}
|
||||
|
||||
const thumbname = path.join(__dirname, '..', config.uploads.folder, 'thumbs', file.name.slice(0, -ext.length) + '.png')
|
||||
const thumbname = path.join(thumbsDir, file.name.slice(0, -extname.length) + '.png')
|
||||
fs.access(thumbname, error => {
|
||||
if (error && error.code === 'ENOENT') {
|
||||
if (isVideoExt) {
|
||||
ffmpeg(path.join(__dirname, '..', config.uploads.folder, file.name))
|
||||
.thumbnail({
|
||||
timestamps: ['1%'],
|
||||
filename: '%b.png',
|
||||
folder: path.join(__dirname, '..', config.uploads.folder, 'thumbs'),
|
||||
size: '200x?'
|
||||
})
|
||||
.on('error', error => console.log('Error - ', error.message))
|
||||
} else if (isImageExt) {
|
||||
const size = {
|
||||
width: 200,
|
||||
height: 200
|
||||
}
|
||||
gm(path.join(__dirname, '..', config.uploads.folder, file.name))
|
||||
.resize(size.width, size.height + '>')
|
||||
.gravity('Center')
|
||||
.extent(size.width, size.height)
|
||||
.background('transparent')
|
||||
.write(thumbname, error => {
|
||||
if (error) { console.log('Error - ', error) }
|
||||
})
|
||||
}
|
||||
// Only make thumbnail if it does not exist (ENOENT)
|
||||
if (!error || error.code !== 'ENOENT') { return }
|
||||
|
||||
// If image extension
|
||||
if (utilsController.imageExtensions.includes(extname)) {
|
||||
const size = { width: 200, height: 200 }
|
||||
return gm(path.join(__dirname, '..', config.uploads.folder, file.name))
|
||||
.resize(size.width, size.height + '>')
|
||||
.gravity('Center')
|
||||
.extent(size.width, size.height)
|
||||
.background('transparent')
|
||||
.write(thumbname, error => {
|
||||
if (error) { console.log('Error - ', error) }
|
||||
})
|
||||
}
|
||||
|
||||
// Otherwise video extension
|
||||
ffmpeg(path.join(__dirname, '..', config.uploads.folder, file.name))
|
||||
.thumbnail({
|
||||
timestamps: ['1%'],
|
||||
filename: '%b.png',
|
||||
folder: path.join(__dirname, '..', config.uploads.folder, 'thumbs'),
|
||||
size: '200x?'
|
||||
})
|
||||
.on('error', error => console.log('Error - ', error.message))
|
||||
})
|
||||
}
|
||||
|
||||
utilsController.deleteFile = file => {
|
||||
const ext = path.extname(file).toLowerCase()
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.stat(path.join(__dirname, '..', config.uploads.folder, file), (error, stats) => {
|
||||
if (error) { return reject(error) }
|
||||
fs.unlink(path.join(__dirname, '..', config.uploads.folder, file), error => {
|
||||
if (error) { return reject(error) }
|
||||
if (!utilsController.imageExtensions.includes(ext) && !utilsController.videoExtensions.includes(ext)) {
|
||||
return resolve()
|
||||
}
|
||||
file = file.substr(0, file.lastIndexOf('.')) + '.png'
|
||||
fs.stat(path.join(__dirname, '..', config.uploads.folder, 'thumbs/', file), (error, stats) => {
|
||||
if (error) {
|
||||
if (error.code !== 'ENOENT') { console.log(error) }
|
||||
return resolve()
|
||||
}
|
||||
fs.unlink(path.join(__dirname, '..', config.uploads.folder, 'thumbs/', file), error => {
|
||||
if (error) { return reject(error) }
|
||||
return resolve()
|
||||
})
|
||||
const extname = path.extname(file).toLowerCase()
|
||||
return fs.unlink(path.join(uploadsDir, file), error => {
|
||||
if (error && error.code !== 'ENOENT') { return reject(error) }
|
||||
|
||||
if (utilsController.imageExtensions.includes(extname) || utilsController.videoExtensions.includes(extname)) {
|
||||
const thumb = file.substr(0, file.lastIndexOf('.')) + '.png'
|
||||
return fs.unlink(path.join(thumbsDir, thumb), error => {
|
||||
if (error && error.code !== 'ENOENT') { return reject(error) }
|
||||
resolve(true)
|
||||
})
|
||||
})
|
||||
}
|
||||
resolve(true)
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -136,13 +130,10 @@ utilsController.bulkDeleteFilesByIds = async (ids, user) => {
|
||||
await Promise.all(files.map(file => {
|
||||
return new Promise(async resolve => {
|
||||
const deleteFile = await utilsController.deleteFile(file.name)
|
||||
.then(() => true)
|
||||
.catch(error => {
|
||||
if (error.code === 'ENOENT') { return true }
|
||||
console.log(error)
|
||||
failedids.push(file.id)
|
||||
})
|
||||
|
||||
if (!deleteFile) { return resolve() }
|
||||
|
||||
await db.table('files')
|
||||
|
@ -140,10 +140,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
img[data-dz-thumbnail] {
|
||||
#uploads progress {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
#uploads img {
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 1rem;
|
||||
color: #bdc3c7;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.link > a {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.clipboard-mobile {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
7
public/js/album.js
Normal file
7
public/js/album.js
Normal file
@ -0,0 +1,7 @@
|
||||
/* global LazyLoad */
|
||||
|
||||
const page = {}
|
||||
|
||||
window.onload = () => {
|
||||
page.lazyLoad = new LazyLoad()
|
||||
}
|
@ -1,62 +1,63 @@
|
||||
/* global swal, axios */
|
||||
|
||||
const page = {}
|
||||
const page = {
|
||||
// user token
|
||||
token: localStorage.token
|
||||
}
|
||||
|
||||
page.do = dest => {
|
||||
page.do = async dest => {
|
||||
const user = document.getElementById('user').value
|
||||
const pass = document.getElementById('pass').value
|
||||
|
||||
if (user === undefined || user === null || user === '') {
|
||||
return swal('Error', 'You need to specify a username', 'error')
|
||||
}
|
||||
if (pass === undefined || pass === null || pass === '') {
|
||||
if (!user) {
|
||||
return swal('Error', 'You need to specify a username', 'error')
|
||||
}
|
||||
|
||||
axios.post('api/' + dest, {
|
||||
if (!pass) {
|
||||
return swal('Error', 'You need to specify a username', 'error')
|
||||
}
|
||||
|
||||
const response = await axios.post(`api/${dest}`, {
|
||||
username: user,
|
||||
password: pass
|
||||
})
|
||||
.then(response => {
|
||||
if (response.data.success === false) {
|
||||
return swal('Error', response.data.description, 'error')
|
||||
}
|
||||
|
||||
localStorage.token = response.data.token
|
||||
window.location = 'dashboard'
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
})
|
||||
if (!response) { return }
|
||||
|
||||
if (response.data.success === false) {
|
||||
return swal('Error', response.data.description, 'error')
|
||||
}
|
||||
|
||||
localStorage.token = response.data.token
|
||||
window.location = 'dashboard'
|
||||
}
|
||||
|
||||
page.onkeypress = function (event, element) {
|
||||
page.onkeypress = (event, element) => {
|
||||
event = event || window.event
|
||||
if (!event) { return }
|
||||
if (event.keyCode === 13 || event.which === 13) {
|
||||
return this.do('login')
|
||||
}
|
||||
if (event.keyCode === 13 || event.which === 13) { page.do('login') }
|
||||
}
|
||||
|
||||
page.verify = () => {
|
||||
page.token = localStorage.token
|
||||
if (page.token === undefined) { return }
|
||||
page.verify = async () => {
|
||||
if (!page.token) { return }
|
||||
|
||||
axios.post('api/tokens/verify', {
|
||||
const response = await axios.post('api/tokens/verify', {
|
||||
token: page.token
|
||||
})
|
||||
.then(response => {
|
||||
if (response.data.success === false) {
|
||||
return swal('Error', response.data.description, 'error')
|
||||
}
|
||||
|
||||
window.location = 'dashboard'
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
})
|
||||
if (!response) { return }
|
||||
|
||||
if (response.data.success === false) {
|
||||
return swal('Error', response.data.description, 'error')
|
||||
}
|
||||
|
||||
window.location = 'dashboard'
|
||||
}
|
||||
|
||||
window.onload = () => {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,28 +1,34 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* global swal, axios, Dropzone, ClipboardJS */
|
||||
/* global swal, axios, Dropzone, ClipboardJS, LazyLoad */
|
||||
|
||||
const upload = {
|
||||
private: undefined,
|
||||
enableUserAccounts: undefined,
|
||||
const page = {
|
||||
// user token
|
||||
token: localStorage.token,
|
||||
maxFileSize: undefined,
|
||||
chunkedUploads: undefined,
|
||||
// Add the album let to the upload so we can store the album id in there
|
||||
album: undefined,
|
||||
dropzone: undefined,
|
||||
clipboardJS: undefined
|
||||
|
||||
// configs from api/check
|
||||
private: null,
|
||||
enableUserAccounts: null,
|
||||
maxFileSize: null,
|
||||
chunkedUploads: null, // chunked uploads config
|
||||
|
||||
// store album id that will be used with upload requests
|
||||
album: null,
|
||||
|
||||
dropzone: null,
|
||||
clipboardJS: null,
|
||||
lazyLoad: null,
|
||||
lazyLoadTimestamp: null
|
||||
}
|
||||
|
||||
const imageExtensions = ['.webp', '.jpg', '.jpeg', '.bmp', '.gif', '.png']
|
||||
|
||||
upload.checkIfPublic = () => {
|
||||
page.checkIfPublic = () => {
|
||||
axios.get('api/check')
|
||||
.then(response => {
|
||||
upload.private = response.data.private
|
||||
upload.enableUserAccounts = response.data.enableUserAccounts
|
||||
upload.maxFileSize = response.data.maxFileSize
|
||||
upload.chunkedUploads = response.data.chunkedUploads
|
||||
upload.preparePage()
|
||||
page.private = response.data.private
|
||||
page.enableUserAccounts = response.data.enableUserAccounts
|
||||
page.maxFileSize = response.data.maxFileSize
|
||||
page.chunkedUploads = response.data.chunkedUploads
|
||||
page.preparePage()
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
@ -33,27 +39,27 @@ upload.checkIfPublic = () => {
|
||||
})
|
||||
}
|
||||
|
||||
upload.preparePage = () => {
|
||||
if (upload.private) {
|
||||
if (upload.token) {
|
||||
return upload.verifyToken(upload.token, true)
|
||||
page.preparePage = () => {
|
||||
if (page.private) {
|
||||
if (page.token) {
|
||||
return page.verifyToken(page.token, true)
|
||||
} else {
|
||||
const button = document.getElementById('loginToUpload')
|
||||
button.href = 'auth'
|
||||
button.className = button.className.replace(' is-loading', '')
|
||||
|
||||
if (upload.enableUserAccounts) {
|
||||
button.innerText = 'Anonymous upload is disabled. Log in to upload.'
|
||||
if (page.enableUserAccounts) {
|
||||
button.innerText = 'Anonymous upload is disabled. Log in to page.'
|
||||
} else {
|
||||
button.innerText = 'Running in private mode. Log in to upload.'
|
||||
button.innerText = 'Running in private mode. Log in to page.'
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return upload.prepareUpload()
|
||||
return page.prepareUpload()
|
||||
}
|
||||
}
|
||||
|
||||
upload.verifyToken = (token, reloadOnError) => {
|
||||
page.verifyToken = (token, reloadOnError) => {
|
||||
if (reloadOnError === undefined) { reloadOnError = false }
|
||||
|
||||
axios.post('api/tokens/verify', { token })
|
||||
@ -73,8 +79,8 @@ upload.verifyToken = (token, reloadOnError) => {
|
||||
}
|
||||
|
||||
localStorage.token = token
|
||||
upload.token = token
|
||||
return upload.prepareUpload()
|
||||
page.token = token
|
||||
return page.prepareUpload()
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
@ -82,16 +88,16 @@ upload.verifyToken = (token, reloadOnError) => {
|
||||
})
|
||||
}
|
||||
|
||||
upload.prepareUpload = () => {
|
||||
page.prepareUpload = () => {
|
||||
// I think this fits best here because we need to check for a valid token before we can get the albums
|
||||
if (upload.token) {
|
||||
if (page.token) {
|
||||
const select = document.getElementById('albumSelect')
|
||||
|
||||
select.addEventListener('change', () => {
|
||||
upload.album = parseInt(select.value)
|
||||
page.album = parseInt(select.value)
|
||||
})
|
||||
|
||||
axios.get('api/albums', { headers: { token: upload.token } })
|
||||
axios.get('api/albums', { headers: { token: page.token } })
|
||||
.then(res => {
|
||||
const albums = res.data.albums
|
||||
|
||||
@ -126,38 +132,38 @@ upload.prepareUpload = () => {
|
||||
`
|
||||
div.style.display = 'flex'
|
||||
|
||||
document.getElementById('maxFileSize').innerHTML = `Maximum upload size per file is ${upload.maxFileSize}`
|
||||
document.getElementById('maxFileSize').innerHTML = `Maximum upload size per file is ${page.maxFileSize}`
|
||||
document.getElementById('loginToUpload').style.display = 'none'
|
||||
|
||||
if (upload.token === undefined && upload.enableUserAccounts) {
|
||||
if (!page.token && page.enableUserAccounts) {
|
||||
document.getElementById('loginLinkText').innerHTML = 'Create an account and keep track of your uploads'
|
||||
}
|
||||
|
||||
document.getElementById('uploadContainer').appendChild(div)
|
||||
|
||||
upload.prepareDropzone()
|
||||
page.prepareDropzone()
|
||||
}
|
||||
|
||||
upload.prepareDropzone = () => {
|
||||
page.prepareDropzone = () => {
|
||||
const previewNode = document.querySelector('#template')
|
||||
previewNode.id = ''
|
||||
const previewTemplate = previewNode.parentNode.innerHTML
|
||||
previewNode.parentNode.removeChild(previewNode)
|
||||
|
||||
upload.dropzone = new Dropzone('div#dropzone', {
|
||||
page.dropzone = new Dropzone('#dropzone', {
|
||||
url: 'api/upload',
|
||||
paramName: 'files[]',
|
||||
maxFilesize: parseInt(upload.maxFileSize),
|
||||
maxFilesize: parseInt(page.maxFileSize),
|
||||
parallelUploads: 2,
|
||||
uploadMultiple: false,
|
||||
previewsContainer: 'div#uploads',
|
||||
previewsContainer: '#uploads',
|
||||
previewTemplate,
|
||||
createImageThumbnails: false,
|
||||
maxFiles: 1000,
|
||||
autoProcessQueue: true,
|
||||
headers: { token: upload.token },
|
||||
chunking: upload.chunkedUploads.enabled,
|
||||
chunkSize: parseInt(upload.chunkedUploads.chunkSize) * 1000000, // 1000000 B = 1 MB,
|
||||
headers: { token: page.token },
|
||||
chunking: page.chunkedUploads.enabled,
|
||||
chunkSize: parseInt(page.chunkedUploads.chunkSize) * 1000000, // 1000000 B = 1 MB,
|
||||
parallelChunkUploads: false, // when set to true, sometimes it often hangs with hundreds of parallel uploads
|
||||
chunksUploaded: async (file, done) => {
|
||||
file.previewElement.querySelector('.progress').setAttribute('value', 100)
|
||||
@ -167,19 +173,17 @@ upload.prepareDropzone = () => {
|
||||
const response = await axios.post(
|
||||
'api/upload/finishchunks',
|
||||
{
|
||||
files: [
|
||||
{
|
||||
uuid: file.upload.uuid,
|
||||
original: file.name,
|
||||
size: file.size,
|
||||
type: file.type,
|
||||
count: file.upload.totalChunkCount,
|
||||
albumid: upload.album
|
||||
}
|
||||
]
|
||||
files: [{
|
||||
uuid: file.upload.uuid,
|
||||
original: file.name,
|
||||
size: file.size,
|
||||
type: file.type,
|
||||
count: file.upload.totalChunkCount,
|
||||
albumid: page.album
|
||||
}]
|
||||
},
|
||||
{
|
||||
headers: { token: upload.token }
|
||||
headers: { token: page.token }
|
||||
})
|
||||
.then(response => response.data)
|
||||
.catch(error => {
|
||||
@ -195,32 +199,31 @@ upload.prepareDropzone = () => {
|
||||
file.previewTemplate.querySelector('.error').innerHTML = response.description
|
||||
}
|
||||
|
||||
if (response.files && response.files[0] && response.files[0].url) {
|
||||
upload.appendLink(file, response.files[0].url)
|
||||
upload.showThumbnail(file, response.files[0].url)
|
||||
if (response.files && response.files[0]) {
|
||||
page.updateTemplate(file, response.files[0])
|
||||
}
|
||||
return done()
|
||||
}
|
||||
})
|
||||
|
||||
upload.dropzone.on('addedfile', file => {
|
||||
page.dropzone.on('addedfile', file => {
|
||||
document.getElementById('uploads').style.display = 'block'
|
||||
})
|
||||
|
||||
// Add the selected albumid, if an album is selected, as a header
|
||||
upload.dropzone.on('sending', (file, xhr, formData) => {
|
||||
page.dropzone.on('sending', (file, xhr, formData) => {
|
||||
if (file.upload.chunked) { return }
|
||||
if (upload.album) { xhr.setRequestHeader('albumid', upload.album) }
|
||||
if (page.album) { xhr.setRequestHeader('albumid', page.album) }
|
||||
})
|
||||
|
||||
// Update the total progress bar
|
||||
upload.dropzone.on('uploadprogress', (file, progress, bytesSent) => {
|
||||
page.dropzone.on('uploadprogress', (file, progress, bytesSent) => {
|
||||
if (file.upload.chunked && progress === 100) { return }
|
||||
file.previewElement.querySelector('.progress').setAttribute('value', progress)
|
||||
file.previewElement.querySelector('.progress').innerHTML = `${progress}%`
|
||||
})
|
||||
|
||||
upload.dropzone.on('success', (file, response) => {
|
||||
page.dropzone.on('success', (file, response) => {
|
||||
if (!response) { return }
|
||||
file.previewTemplate.querySelector('.progress').style.display = 'none'
|
||||
|
||||
@ -228,37 +231,42 @@ upload.prepareDropzone = () => {
|
||||
file.previewTemplate.querySelector('.error').innerHTML = response.description
|
||||
}
|
||||
|
||||
if (response.files && response.files[0] && response.files[0].url) {
|
||||
upload.appendLink(file, response.files[0].url)
|
||||
upload.showThumbnail(file, response.files[0].url)
|
||||
if (response.files && response.files[0]) {
|
||||
page.updateTemplate(file, response.files[0])
|
||||
}
|
||||
})
|
||||
|
||||
upload.dropzone.on('error', (file, error) => {
|
||||
page.dropzone.on('error', (file, error) => {
|
||||
file.previewTemplate.querySelector('.progress').style.display = 'none'
|
||||
file.previewTemplate.querySelector('.error').innerHTML = error
|
||||
})
|
||||
|
||||
upload.prepareShareX()
|
||||
page.prepareShareX()
|
||||
}
|
||||
|
||||
upload.appendLink = (file, url) => {
|
||||
page.updateTemplate = (file, response) => {
|
||||
if (!response.url) { return }
|
||||
|
||||
const a = file.previewTemplate.querySelector('.link > a')
|
||||
const clipboard = file.previewTemplate.querySelector('.clipboard-mobile > .clipboard-js')
|
||||
a.href = a.innerHTML = clipboard.dataset['clipboardText'] = response.url
|
||||
clipboard.parentElement.style.display = 'block'
|
||||
|
||||
a.href = a.innerHTML = clipboard.dataset['clipboardText'] = url
|
||||
a.parentElement.style = clipboard.parentElement.style = ''
|
||||
}
|
||||
const name = file.previewTemplate.querySelector('.name')
|
||||
name.innerHTML = file.name
|
||||
|
||||
upload.showThumbnail = (file, url) => {
|
||||
const exec = /.[\w]+(\?|$)/.exec(url)
|
||||
const exec = /.[\w]+(\?|$)/.exec(response.url)
|
||||
if (exec && exec[0] && imageExtensions.includes(exec[0].toLowerCase())) {
|
||||
upload.dropzone.emit('thumbnail', file, url)
|
||||
const img = file.previewTemplate.querySelector('img')
|
||||
img.setAttribute('alt', response.name || '')
|
||||
img.dataset['src'] = response.url
|
||||
img.onerror = function () { this.style.display = 'none' } // hide webp in firefox and ie
|
||||
page.lazyLoad.update(file.previewTemplate.querySelectorAll('img'))
|
||||
}
|
||||
}
|
||||
|
||||
upload.prepareShareX = () => {
|
||||
if (upload.token) {
|
||||
page.prepareShareX = () => {
|
||||
if (page.token) {
|
||||
const sharexElement = document.getElementById('ShareX')
|
||||
const sharexFile =
|
||||
'{\r\n' +
|
||||
@ -268,7 +276,7 @@ upload.prepareShareX = () => {
|
||||
` "RequestURL": "${location.origin}/api/upload",\r\n` +
|
||||
' "FileFormName": "files[]",\r\n' +
|
||||
' "Headers": {\r\n' +
|
||||
` "token": "${upload.token}"\r\n` +
|
||||
` "token": "${page.token}"\r\n` +
|
||||
' },\r\n' +
|
||||
' "ResponseType": "Text",\r\n' +
|
||||
' "URL": "$json:files[0].url$",\r\n' +
|
||||
@ -291,22 +299,34 @@ window.addEventListener('paste', event => {
|
||||
const file = new File([blob], `pasted-image.${blob.type.match(/(?:[^/]*\/)([^;]*)/)[1]}`)
|
||||
file.type = blob.type
|
||||
console.log(file)
|
||||
upload.dropzone.addFile(file)
|
||||
page.dropzone.addFile(file)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
page.lazyLoadUpdate = () => {
|
||||
// console.log(page.lazyLoadTimestamp)
|
||||
if (!page.lazyLoadTimestamp) { page.lazyLoadTimestamp = 0 }
|
||||
if (Date.now() - page.lazyLoadTimestamp >= 500) {
|
||||
console.log('update() called')
|
||||
page.lazyLoad.update()
|
||||
page.lazyLoadTimestamp = Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = () => {
|
||||
upload.checkIfPublic()
|
||||
page.checkIfPublic()
|
||||
|
||||
upload.clipboardJS = new ClipboardJS('.clipboard-js')
|
||||
page.clipboardJS = new ClipboardJS('.clipboard-js')
|
||||
|
||||
upload.clipboardJS.on('success', () => {
|
||||
page.clipboardJS.on('success', () => {
|
||||
return swal('Copied!', 'The link has been copied to clipboard.', 'success')
|
||||
})
|
||||
|
||||
upload.clipboardJS.on('error', event => {
|
||||
page.clipboardJS.on('error', event => {
|
||||
console.error(event)
|
||||
return swal('An error occurred!', 'There was an error when trying to copy the link to clipboard, please check the console for more information.', 'error')
|
||||
})
|
||||
|
||||
page.lazyLoad = new LazyLoad()
|
||||
}
|
||||
|
21
public/libs/lazyload/LICENSE
Normal file
21
public/libs/lazyload/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2015 Andrea Verlicchi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
1
public/libs/lazyload/lazyload.min.js
vendored
Executable file
1
public/libs/lazyload/lazyload.min.js
vendored
Executable file
@ -0,0 +1 @@
|
||||
/*! lazyload v10.5.2 | Copyright (c) 2015 Andrea Verlicchi */var _extends=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};!function(e,t){"object"===("undefined"==typeof exports?"undefined":_typeof(exports))&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.LazyLoad=t()}(this,function(){"use strict";var e=function(e){var t={elements_selector:"img",container:document,threshold:300,data_src:"src",data_srcset:"srcset",class_loading:"loading",class_loaded:"loaded",class_error:"error",callback_load:null,callback_error:null,callback_set:null,callback_enter:null};return _extends({},t,e)},t=function(e,t){return e.getAttribute("data-"+t)},n=function(e,t,n){return e.setAttribute("data-"+t,n)},r=function(e){return e.filter(function(e){return!t(e,"was-processed")})},s=function(e,t){var n,r=new e(t);try{n=new CustomEvent("LazyLoad::Initialized",{detail:{instance:r}})}catch(e){(n=document.createEvent("CustomEvent")).initCustomEvent("LazyLoad::Initialized",!1,!1,{instance:r})}window.dispatchEvent(n)},o=function(e,n){var r=n.data_srcset,s=e.parentNode;if(s&&"PICTURE"===s.tagName)for(var o,a=0;o=s.children[a];a+=1)if("SOURCE"===o.tagName){var i=t(o,r);i&&o.setAttribute("srcset",i)}},a=function(e,n){var r=n.data_src,s=n.data_srcset,a=e.tagName,i=t(e,r);if("IMG"===a){o(e,n);var c=t(e,s);return c&&e.setAttribute("srcset",c),void(i&&e.setAttribute("src",i))}"IFRAME"!==a?i&&(e.style.backgroundImage='url("'+i+'")'):i&&e.setAttribute("src",i)},i="undefined"!=typeof window,c=i&&"IntersectionObserver"in window,l=i&&"classList"in document.createElement("p"),u=function(e,t){l?e.classList.add(t):e.className+=(e.className?" ":"")+t},d=function(e,t){l?e.classList.remove(t):e.className=e.className.replace(new RegExp("(^|\\s+)"+t+"(\\s+|$)")," ").replace(/^\s+/,"").replace(/\s+$/,"")},f=function(e,t){e&&e(t)},_=function(e,t,n){e.removeEventListener("load",t),e.removeEventListener("error",n)},v=function(e,t){var n=function n(s){m(s,!0,t),_(e,n,r)},r=function r(s){m(s,!1,t),_(e,n,r)};e.addEventListener("load",n),e.addEventListener("error",r)},m=function(e,t,n){var r=e.target;d(r,n.class_loading),u(r,t?n.class_loaded:n.class_error),f(t?n.callback_load:n.callback_error,r)},b=function(e,t){f(t.callback_enter,e),["IMG","IFRAME"].indexOf(e.tagName)>-1&&(v(e,t),u(e,t.class_loading)),a(e,t),n(e,"was-processed",!0),f(t.callback_set,e)},p=function(e){return e.isIntersecting||e.intersectionRatio>0},h=function(t,n){this._settings=e(t),this._setObserver(),this.update(n)};h.prototype={_setObserver:function(){var e=this;if(c){var t=this._settings,n={root:t.container===document?null:t.container,rootMargin:t.threshold+"px"};this._observer=new IntersectionObserver(function(t){t.forEach(function(t){if(p(t)){var n=t.target;b(n,e._settings),e._observer.unobserve(n)}}),e._elements=r(e._elements)},n)}},update:function(e){var t=this,n=this._settings,s=e||n.container.querySelectorAll(n.elements_selector);this._elements=r(Array.prototype.slice.call(s)),this._observer?this._elements.forEach(function(e){t._observer.observe(e)}):(this._elements.forEach(function(e){b(e,n)}),this._elements=r(this._elements))},destroy:function(){var e=this;this._observer&&(r(this._elements).forEach(function(t){e._observer.unobserve(t)}),this._observer=null),this._elements=null,this._settings=null}};var y=window.lazyLoadOptions;return i&&y&&function(e,t){if(t.length)for(var n,r=0;n=t[r];r+=1)s(e,n);else s(e,t)}(h,y),h});
|
@ -42,25 +42,17 @@ routes.get('/a/:identifier', async (req, res, next) => {
|
||||
for (const file of files) {
|
||||
file.file = `${basedomain}/${file.name}`
|
||||
file.size = utils.getPrettyBytes(parseInt(file.size))
|
||||
file.extname = path.extname(file.name).toLowerCase()
|
||||
|
||||
const extname = path.extname(file.name).toLowerCase()
|
||||
if ((config.uploads.generateThumbnails.image && utils.imageExtensions.includes(extname)) || (config.uploads.generateThumbnails.video && utils.videoExtensions.includes(extname))) {
|
||||
file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -extname.length)}.png`
|
||||
if (!utils.mayGenerateThumb(file.extname)) { continue }
|
||||
file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -file.extname.length)}.png`
|
||||
|
||||
/*
|
||||
If thumbnail for album is still not set, do it.
|
||||
A potential improvement would be to let the user upload a specific image as an album cover
|
||||
since embedding the first image could potentially result in nsfw content when pasting links.
|
||||
*/
|
||||
|
||||
if (thumb === '') {
|
||||
thumb = file.thumb
|
||||
}
|
||||
|
||||
file.thumb = `<img alt="${file.name}" src="${file.thumb}"/>`
|
||||
} else {
|
||||
file.thumb = `<h1 class="title">${extname || 'N/A'}</h1>`
|
||||
}
|
||||
/*
|
||||
If thumbnail for album is still not set, do it.
|
||||
A potential improvement would be to let the user upload a specific image as an album cover
|
||||
since embedding the first image could potentially result in nsfw content when pasting links.
|
||||
*/
|
||||
if (thumb === '') { thumb = file.thumb }
|
||||
}
|
||||
|
||||
return res.render('album', {
|
||||
|
@ -12,7 +12,7 @@
|
||||
v1: CSS and JS files.
|
||||
v2: Images and config files (manifest.json, browserconfig.xml, etcetera).
|
||||
#}
|
||||
{% set v1 = "vixJhF5oKZ" %}
|
||||
{% set v1 = "aP7qgfND6s" %}
|
||||
{% set v2 = "MSEpgpfFIQ" %}
|
||||
|
||||
{#
|
||||
|
@ -7,6 +7,11 @@
|
||||
<link rel="stylesheet" type="text/css" href="../css/album.css?v={{ globals.v1 }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script type="text/javascript" src="../libs/lazyload/lazyload.min.js?v={{ globals.v1 }}"></script>
|
||||
<script type="text/javascript" src="../js/album.js?v={{ globals.v1 }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block opengraph %}
|
||||
<!-- Open Graph tags -->
|
||||
<meta property="og:type" content="website" />
|
||||
@ -65,7 +70,19 @@
|
||||
<div id="table" class="columns is-multiline is-mobile is-centered has-text-centered">
|
||||
{% for file in files %}
|
||||
<div class="image-container column is-narrow">
|
||||
<a class="image" href="{{ file.file }}" target="_blank" rel="noopener">{{ file.thumb | safe }}</a>
|
||||
<a class="image" href="{{ file.file }}" target="_blank" rel="noopener">
|
||||
{% if file.thumb -%}
|
||||
<img alt="{{ file.name }}" data-src="{{ file.thumb }}">
|
||||
{#-
|
||||
This will kinda increase the overall page size,
|
||||
but this will still benefit users with JavaScript enabled by lazyloading images,
|
||||
and not causing those who have JavaScript disabled be unable to view the images.
|
||||
#}
|
||||
<noscript><img alt="{{ file.name }}" src="{{ file.thumb }}" style="display: none"></noscript>
|
||||
{%- else -%}
|
||||
<h1 class="title">{{ file.extname | default('N/A') }}</h1>
|
||||
{%- endif %}
|
||||
</a>
|
||||
<div class="details">
|
||||
<p><span class="name" title="{{ file.file }}">{{ file.name }}</span></p>
|
||||
<p>{{ file.size }}</p>
|
||||
@ -82,4 +99,12 @@
|
||||
{%- endif %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{# Hide lazyload img tags and show noscript img tags #}
|
||||
<noscript>
|
||||
<style>
|
||||
img[data-src] { display: none; }
|
||||
img[src] { display: block !important; }
|
||||
</style>
|
||||
</noscript>
|
||||
{% endblock %}
|
||||
|
@ -12,6 +12,7 @@
|
||||
<script type="text/javascript" src="libs/sweetalert/sweetalert.min.js?v={{ globals.v1 }}"></script>
|
||||
<script type="text/javascript" src="libs/axios/axios.min.js?v={{ globals.v1 }}"></script>
|
||||
<script type="text/javascript" src="libs/clipboard.js/clipboard.min.js?v={{ globals.v1 }}"></script>
|
||||
<script type="text/javascript" src="libs/lazyload/lazyload.min.js?v={{ globals.v1 }}"></script>
|
||||
<script type="text/javascript" src="js/dashboard.js?v={{ globals.v1 }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
@ -52,13 +53,13 @@
|
||||
<a href=".">Frontpage</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="itemUploads" onclick="panel.getUploads()">Uploads</a>
|
||||
<a id="itemUploads" onclick="page.getUploads()">Uploads</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="menu-label">Albums</p>
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<a id="itemManageGallery" onclick="panel.getAlbums()">Manage your albums</a>
|
||||
<a id="itemManageGallery" onclick="page.getAlbums()">Manage your albums</a>
|
||||
</li>
|
||||
<li>
|
||||
<ul id="albumsContainer"></ul>
|
||||
@ -67,16 +68,16 @@
|
||||
<p class="menu-label">Administration</p>
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<a id="itemFileLength" onclick="panel.changeFileLength()">File name length</a>
|
||||
<a id="itemFileLength" onclick="page.changeFileLength()">File name length</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="itemTokens" onclick="panel.changeToken()">Manage your token</a>
|
||||
<a id="itemTokens" onclick="page.changeToken()">Manage your token</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="itemPassword" onclick="panel.changePassword()">Change your password</a>
|
||||
<a id="itemPassword" onclick="page.changePassword()">Change your password</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="itemLogout" onclick="panel.logout()">Logout</a>
|
||||
<a id="itemLogout" onclick="page.logout()">Logout</a>
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
@ -89,13 +90,13 @@
|
||||
</section>
|
||||
|
||||
<div id="modal" class="modal">
|
||||
<div class="modal-background" onclick="panel.closeModal()"></div>
|
||||
<div class="modal-background" onclick="page.closeModal()"></div>
|
||||
<div class="modal-content">
|
||||
<figure class="image">
|
||||
<img id="modalImage" alt="modal-image">
|
||||
</figure>
|
||||
</div>
|
||||
<button class="modal-close is-large" aria-label="close" onclick="panel.closeModal()"></button>
|
||||
<button class="modal-close is-large" aria-label="close" onclick="page.closeModal()"></button>
|
||||
</div>
|
||||
|
||||
{% include "_partial/noscript.njk" %}
|
||||
|
@ -13,6 +13,7 @@
|
||||
<script type="text/javascript" src="libs/dropzone/dropzone.min.js?v={{ globals.v1 }}"></script>
|
||||
<script type="text/javascript" src="libs/axios/axios.min.js?v={{ globals.v1 }}"></script>
|
||||
<script type="text/javascript" src="libs/clipboard.js/clipboard.min.js?v={{ globals.v1 }}"></script>
|
||||
<script type="text/javascript" src="libs/lazyload/lazyload.min.js?v={{ globals.v1 }}"></script>
|
||||
<script type="text/javascript" src="js/home.js?v={{ globals.v1 }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
@ -51,18 +52,19 @@
|
||||
<div class="column is-hidden-mobile"></div>
|
||||
<div class="column">
|
||||
<progress class="progress is-small is-danger" value="0" max="100"></progress>
|
||||
<img class="is-unselectable" data-dz-thumbnail>
|
||||
<img class="is-unselectable">
|
||||
<p class="name is-unselectable"></p>
|
||||
<p class="error"></p>
|
||||
<p class="link" style="display: none">
|
||||
<p class="link">
|
||||
<a target="_blank" rel="noopener"></a>
|
||||
</p>
|
||||
<p class="clipboard-mobile is-hidden-desktop" style="display: none">
|
||||
<a class="button is-info is-outlined clipboard-js" style="display: flex">
|
||||
<span class="icon">
|
||||
<i class="icon-clipboard-1"></i>
|
||||
</span>
|
||||
<span>Copy link to clipboard</span>
|
||||
</a>
|
||||
<span class="icon">
|
||||
<i class="icon-clipboard-1"></i>
|
||||
</span>
|
||||
<span>Copy link to clipboard</span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="column is-hidden-mobile"></div>
|
||||
|
Loading…
Reference in New Issue
Block a user