mirror of
https://github.com/BobbyWibowo/lolisafe.git
synced 2025-01-18 17:21:33 +00:00
Manage albums admin page, and more!
Resolves #194. Added pagination for Manage your albums page. Albums sidebar will now only list 9 albums at most. Use Manage your albums page to view the rest. Albums in the list will now have View uploads button after all. Delete album button for albums renamed to Disable album. Since techincally the server would've always been disabling the albums instead of deleting them. It was something upstream dev's decided, and I haven't bothered changing its behavior. I'll work on actual Delete album feature some other days. As the title says, added Manage albums admin page. Viewing uploads of an album will hook into albumid: filter key. I'll work on filter and bulk operations some other days. Updated styling for disabled albums and users. Instead of havine a line through them, they will be greyed out. Disable public page of albums will still use line through however. Links to album's disabled public page are now clickable. Added a new button styling is-dangerish. It'll be orange. Renamed /api/albums/delete to /api/albums/disable. For backwards compatibility, /api/albums/delete will still work but automatically re-routed to /api/albums/disable. /api/uploads/list will no longer print SQLite errors for moderators or higher when encountering them. It was originally used to inform moderators of non-existing colum names when used for sorting. But on one of the recent commits, I had added a check for allowed colum names. Improved some caching in dashboard page. Added new entries to cookie policy. Some other small things. Bumped v1 version string and rebuilt client assets.
This commit is contained in:
parent
255a5992b5
commit
5e5d5c5647
@ -4,6 +4,7 @@ const path = require('path')
|
||||
const randomstring = require('randomstring')
|
||||
const Zip = require('jszip')
|
||||
const paths = require('./pathsController')
|
||||
const perms = require('./permissionController')
|
||||
const utils = require('./utilsController')
|
||||
const config = require('./../config')
|
||||
const logger = require('./../logger')
|
||||
@ -76,39 +77,102 @@ self.list = async (req, res, next) => {
|
||||
const user = await utils.authorize(req, res)
|
||||
if (!user) return
|
||||
|
||||
let fields = ['id', 'name']
|
||||
if (req.params.sidebar === undefined)
|
||||
fields = fields.concat(['timestamp', 'identifier', 'editedAt', 'download', 'public', 'description'])
|
||||
const all = req.headers.all === '1'
|
||||
const sidebar = req.headers.sidebar
|
||||
const ismoderator = perms.is(user, 'moderator')
|
||||
if (all && !ismoderator)
|
||||
return res.status(403).end()
|
||||
|
||||
const albums = await db.table('albums')
|
||||
.select(fields)
|
||||
.where({
|
||||
enabled: 1,
|
||||
userid: user.id
|
||||
})
|
||||
|
||||
if (req.params.sidebar !== undefined)
|
||||
return res.json({ success: true, albums })
|
||||
|
||||
const albumids = {}
|
||||
for (const album of albums) {
|
||||
album.download = album.download !== 0
|
||||
album.public = album.public !== 0
|
||||
album.files = 0
|
||||
// Map by IDs
|
||||
albumids[album.id] = album
|
||||
const filter = function () {
|
||||
if (!all)
|
||||
this.where({
|
||||
enabled: 1,
|
||||
userid: user.id
|
||||
})
|
||||
}
|
||||
|
||||
const files = await db.table('files')
|
||||
.whereIn('albumid', Object.keys(albumids))
|
||||
.select('albumid')
|
||||
try {
|
||||
// Query albums count for pagination
|
||||
const count = await db.table('albums')
|
||||
.where(filter)
|
||||
.count('id as count')
|
||||
.then(rows => rows[0].count)
|
||||
if (!count)
|
||||
return res.json({ success: true, albums: [], count })
|
||||
|
||||
// Increment files count
|
||||
for (const file of files)
|
||||
if (albumids[file.albumid])
|
||||
albumids[file.albumid].files++
|
||||
let fields = ['id', 'name']
|
||||
|
||||
return res.json({ success: true, albums, homeDomain })
|
||||
let albums
|
||||
if (sidebar) {
|
||||
albums = await db.table('albums')
|
||||
.where(filter)
|
||||
.limit(9)
|
||||
.select(fields)
|
||||
|
||||
return res.json({ success: true, albums, count })
|
||||
} else {
|
||||
let offset = Number(req.params.page)
|
||||
if (isNaN(offset)) offset = 0
|
||||
else if (offset < 0) offset = Math.max(0, Math.ceil(count / 25) + offset)
|
||||
|
||||
fields = fields.concat(['identifier', 'enabled', 'timestamp', 'editedAt', 'download', 'public', 'description'])
|
||||
if (all)
|
||||
fields.push('userid')
|
||||
|
||||
albums = await db.table('albums')
|
||||
.where(filter)
|
||||
.limit(25)
|
||||
.offset(25 * offset)
|
||||
.select(fields)
|
||||
}
|
||||
|
||||
const albumids = {}
|
||||
for (const album of albums) {
|
||||
album.download = album.download !== 0
|
||||
album.public = album.public !== 0
|
||||
album.uploads = 0
|
||||
|
||||
// Map by IDs
|
||||
albumids[album.id] = album
|
||||
}
|
||||
|
||||
const uploads = await db.table('files')
|
||||
.whereIn('albumid', Object.keys(albumids))
|
||||
.select('albumid')
|
||||
|
||||
for (const upload of uploads)
|
||||
if (albumids[upload.albumid])
|
||||
albumids[upload.albumid].uploads++
|
||||
|
||||
// If we are not listing all albums, send response
|
||||
if (!all)
|
||||
return res.json({ success: true, albums, count, homeDomain })
|
||||
|
||||
// Otherwise proceed to querying usernames
|
||||
const userids = albums
|
||||
.map(album => album.userid)
|
||||
.filter((v, i, a) => {
|
||||
return v !== null && v !== undefined && v !== '' && a.indexOf(v) === i
|
||||
})
|
||||
|
||||
// If there are no albums attached to a registered user, send response
|
||||
if (userids.length === 0)
|
||||
return res.json({ success: true, albums, count, homeDomain })
|
||||
|
||||
// Query usernames of user IDs from currently selected files
|
||||
const usersTable = await db.table('users')
|
||||
.whereIn('id', userids)
|
||||
.select('id', 'username')
|
||||
|
||||
const users = {}
|
||||
for (const user of usersTable)
|
||||
users[user.id] = user.username
|
||||
|
||||
return res.json({ success: true, albums, count, users, homeDomain })
|
||||
} catch (error) {
|
||||
logger.error(error)
|
||||
return res.status(500).json({ success: false, description: 'An unexpected error occurred. Try again?' })
|
||||
}
|
||||
}
|
||||
|
||||
self.create = async (req, res, next) => {
|
||||
@ -161,6 +225,11 @@ self.create = async (req, res, next) => {
|
||||
}
|
||||
|
||||
self.delete = async (req, res, next) => {
|
||||
// Map /delete requests to /disable route
|
||||
return self.disable(req, res, next)
|
||||
}
|
||||
|
||||
self.disable = async (req, res, next) => {
|
||||
const user = await utils.authorize(req, res)
|
||||
if (!user) return
|
||||
|
||||
@ -218,6 +287,8 @@ self.edit = async (req, res, next) => {
|
||||
const user = await utils.authorize(req, res)
|
||||
if (!user) return
|
||||
|
||||
const ismoderator = perms.is(user, 'moderator')
|
||||
|
||||
const id = parseInt(req.body.id)
|
||||
if (isNaN(id))
|
||||
return res.json({ success: false, description: 'No album specified.' })
|
||||
@ -229,13 +300,19 @@ self.edit = async (req, res, next) => {
|
||||
if (!name)
|
||||
return res.json({ success: false, description: 'No name specified.' })
|
||||
|
||||
const filter = function () {
|
||||
this.where('id', id)
|
||||
|
||||
if (!ismoderator)
|
||||
this.andWhere({
|
||||
enabled: 1,
|
||||
userid: user.id
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const album = await db.table('albums')
|
||||
.where({
|
||||
id,
|
||||
userid: user.id,
|
||||
enabled: 1
|
||||
})
|
||||
.where(filter)
|
||||
.first()
|
||||
|
||||
if (!album)
|
||||
@ -256,14 +333,14 @@ self.edit = async (req, res, next) => {
|
||||
: ''
|
||||
}
|
||||
|
||||
if (ismoderator)
|
||||
update.enabled = Boolean(req.body.enabled)
|
||||
|
||||
if (req.body.requestLink)
|
||||
update.identifier = await self.getUniqueRandomName()
|
||||
|
||||
await db.table('albums')
|
||||
.where({
|
||||
id,
|
||||
userid: user.id
|
||||
})
|
||||
.where(filter)
|
||||
.update(update)
|
||||
utils.invalidateAlbumsCache([id])
|
||||
|
||||
|
@ -1332,23 +1332,8 @@ self.list = async (req, res) => {
|
||||
|
||||
return res.json({ success: true, files, count, users, albums, basedomain })
|
||||
} catch (error) {
|
||||
// If moderator, capture SQLITE_ERROR and use its error message for the response's description
|
||||
let errorString
|
||||
if (ismoderator && error.code === 'SQLITE_ERROR') {
|
||||
const match = error.message.match(/SQLITE_ERROR: .*$/)
|
||||
errorString = match && match[0]
|
||||
}
|
||||
|
||||
// If not proper SQLITE_ERROR, log to console
|
||||
if (!errorString) {
|
||||
logger.error(error)
|
||||
res.status(500) // Use 500 status code
|
||||
}
|
||||
|
||||
return res.json({
|
||||
success: false,
|
||||
description: errorString || 'An unexpected error occurred. Try again?'
|
||||
})
|
||||
logger.error(error)
|
||||
return res.status(500).json({ success: false, description: 'An unexpected error occurred. Try again?' })
|
||||
}
|
||||
}
|
||||
|
||||
|
2
dist/css/style.css
vendored
2
dist/css/style.css
vendored
@ -1,2 +1,2 @@
|
||||
html{background-color:#000;overflow-y:auto}body{color:#eff0f1;-webkit-animation:fadeInOpacity .5s;animation:fadeInOpacity .5s}@-webkit-keyframes fadeInOpacity{0%{opacity:0}to{opacity:1}}@keyframes fadeInOpacity{0%{opacity:0}to{opacity:1}}a{color:#209cee}a:hover{color:#67c3ff}hr{background-color:#585858}.message-body code,code{background-color:#000;border-radius:5px;font-size:1rem}.subtitle,.subtitle strong{color:#bdc3c7}.subtitle.is-brighter,.subtitle.is-brighter strong,.title{color:#eff0f1}.input,.select select,.textarea{color:#eff0f1;border-color:#585858;background-color:#000}.input::-moz-placeholder,.textarea::-moz-placeholder{color:#bdc3c7}.input::-webkit-input-placeholder,.textarea::-webkit-input-placeholder{color:#bdc3c7}.input:-moz-placeholder,.textarea:-moz-placeholder{color:#bdc3c7}.input:-ms-input-placeholder,.textarea:-ms-input-placeholder{color:#bdc3c7}.input.is-active,.input.is-focused,.input:active,.input:focus,.input:not([disabled]):hover,.select fieldset:not([disabled]) select:hover,.select select:not([disabled]):hover,.textarea.is-active,.textarea.is-focused,.textarea:active,.textarea:focus,.textarea:not([disabled]):hover,fieldset:not([disabled]) .input:hover,fieldset:not([disabled]) .select select:hover,fieldset:not([disabled]) .textarea:hover{border-color:#209cee}.input[disabled],.select fieldset[disabled] select,.select select[disabled],.textarea[disabled],fieldset[disabled] .input,fieldset[disabled] .select select,fieldset[disabled] .textarea{border-color:#585858;background-color:#2f2f2f}.label{color:#eff0f1;font-weight:400}.help{color:#bdc3c7}.progress{background-color:#585858}.button.is-info.is-hovered [class*=" icon-"]:before,.button.is-info.is-hovered [class^=icon-]:before,.button.is-info:hover [class*=" icon-"]:before,.button.is-info:hover [class^=icon-]:before{fill:#fff}.button.is-wrappable{white-space:break-spaces;min-height:2.25em;height:auto}.checkbox:hover,.radio:hover{color:#7f8c8d}.select:not(.is-multiple):not(.is-loading):after,.select:not(.is-multiple):not(.is-loading):hover:after{border-color:#eff0f1}.select select[disabled]:hover,fieldset[disabled] .select select:hover{border-color:#585858}.message{background-color:#2f2f2f}.message-body{color:#eff0f1;border:0}.table{color:#bdc3c7;background-color:#000}.table.is-narrow{font-size:.75rem}.table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#2f2f2f}.table td,.table th{white-space:nowrap;vertical-align:middle;border-bottom:1px solid #585858}.table th{color:#eff0f1;height:2.25em;font-weight:400}.table th.capitalize{text-transform:capitalize}.table thead td,.table thead th{color:#eff0f1;background-color:#383838;border-bottom:0;height:31px}.table tbody tr:last-child td,.table tbody tr:last-child th{border-bottom-width:1px}.table .cell-indent{padding-left:2.25em}.cc-window{font-family:inherit!important}.cc-link{padding:0!important}.section.has-extra-bottom-padding{padding-bottom:6.5rem}a.floating-home-button{display:flex;position:fixed;right:1.5rem;bottom:1.5rem;border-radius:100%;background-color:#209cee;color:#fff;width:3.5rem;height:3.5rem;justify-content:center;align-items:center;transition:background-color .25s}a.floating-home-button:hover{background-color:#67c3ff;color:#fff}a.floating-home-button>.icon{margin-top:-2px}.hero.is-fullheight>.hero-body{min-height:100vh;height:100%}.hero.is-fullheight>.hero-body>.container{width:100%}
|
||||
html{background-color:#000;overflow-y:auto}body{color:#eff0f1;-webkit-animation:fadeInOpacity .5s;animation:fadeInOpacity .5s}@-webkit-keyframes fadeInOpacity{0%{opacity:0}to{opacity:1}}@keyframes fadeInOpacity{0%{opacity:0}to{opacity:1}}a{color:#209cee}a:hover{color:#67c3ff}hr{background-color:#585858}.message-body code,code{background-color:#000;border-radius:5px;font-size:1rem}.subtitle,.subtitle strong{color:#bdc3c7}.subtitle.is-brighter,.subtitle.is-brighter strong,.title{color:#eff0f1}.input,.select select,.textarea{color:#eff0f1;border-color:#585858;background-color:#000}.input::-moz-placeholder,.textarea::-moz-placeholder{color:#bdc3c7}.input::-webkit-input-placeholder,.textarea::-webkit-input-placeholder{color:#bdc3c7}.input:-moz-placeholder,.textarea:-moz-placeholder{color:#bdc3c7}.input:-ms-input-placeholder,.textarea:-ms-input-placeholder{color:#bdc3c7}.input.is-active,.input.is-focused,.input:active,.input:focus,.input:not([disabled]):hover,.select fieldset:not([disabled]) select:hover,.select select:not([disabled]):hover,.textarea.is-active,.textarea.is-focused,.textarea:active,.textarea:focus,.textarea:not([disabled]):hover,fieldset:not([disabled]) .input:hover,fieldset:not([disabled]) .select select:hover,fieldset:not([disabled]) .textarea:hover{border-color:#209cee}.input[disabled],.select fieldset[disabled] select,.select select[disabled],.textarea[disabled],fieldset[disabled] .input,fieldset[disabled] .select select,fieldset[disabled] .textarea{border-color:#585858;background-color:#2f2f2f}.label{color:#eff0f1;font-weight:400}.help{color:#bdc3c7}.progress{background-color:#585858}.button.is-info.is-hovered [class*=" icon-"]:before,.button.is-info.is-hovered [class^=icon-]:before,.button.is-info:hover [class*=" icon-"]:before,.button.is-info:hover [class^=icon-]:before{fill:#fff}.button.is-dangerish{background-color:#ff7043;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-dangerish.is-hovered,.button.is-dangerish:not([disabled]):hover{background-color:#ff8a65;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-dangerish.is-active,.button.is-dangerish:not([disabled]):active{background-color:#ff5722;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-dangerish.is-outlined{background-color:transparent;border-color:#ff7043;color:#ff7043}.button.is-dangerish.is-outlined.is-focused,.button.is-dangerish.is-outlined.is-hovered,.button.is-dangerish.is-outlined:not([disabled]):focus,.button.is-dangerish.is-outlined:not([disabled]):hover{background-color:#ff7043;border-color:#ff7043;color:rgba(0,0,0,.7)}.button.is-wrappable{white-space:break-spaces;min-height:2.25em;height:auto}.checkbox:hover,.radio:hover{color:#7f8c8d}.select:not(.is-multiple):not(.is-loading):after,.select:not(.is-multiple):not(.is-loading):hover:after{border-color:#eff0f1}.select select[disabled]:hover,fieldset[disabled] .select select:hover{border-color:#585858}.message{background-color:#2f2f2f}.message-body{color:#eff0f1;border:0}.table{color:#bdc3c7;background-color:#000}.table.is-narrow{font-size:.75rem}.table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#2f2f2f}.table td,.table th{white-space:nowrap;vertical-align:middle;border-bottom:1px solid #585858}.table th{color:#eff0f1;height:2.25em;font-weight:400}.table th.capitalize{text-transform:capitalize}.table thead td,.table thead th{color:#eff0f1;background-color:#383838;border-bottom:0;height:31px}.table tbody tr:last-child td,.table tbody tr:last-child th{border-bottom-width:1px}.table .cell-indent{padding-left:2.25em}.cc-window{font-family:inherit!important}.cc-link{padding:0!important}.section.has-extra-bottom-padding{padding-bottom:6.5rem}a.floating-home-button{display:flex;position:fixed;right:1.5rem;bottom:1.5rem;border-radius:100%;background-color:#209cee;color:#fff;width:3.5rem;height:3.5rem;justify-content:center;align-items:center;transition:background-color .25s}a.floating-home-button:hover{background-color:#67c3ff;color:#fff}a.floating-home-button>.icon{margin-top:-2px}.hero.is-fullheight>.hero-body{min-height:100vh;height:100%}.hero.is-fullheight>.hero-body>.container{width:100%}
|
||||
/*# sourceMappingURL=style.css.map */
|
||||
|
2
dist/css/style.css.map
vendored
2
dist/css/style.css.map
vendored
File diff suppressed because one or more lines are too long
2
dist/js/dashboard.js
vendored
2
dist/js/dashboard.js
vendored
File diff suppressed because one or more lines are too long
2
dist/js/dashboard.js.map
vendored
2
dist/js/dashboard.js.map
vendored
File diff suppressed because one or more lines are too long
@ -35,10 +35,11 @@ routes.get('/album/zip/:identifier', (req, res, next) => albumsController.genera
|
||||
routes.get('/album/:id', (req, res, next) => uploadController.list(req, res, next))
|
||||
routes.get('/album/:id/:page', (req, res, next) => uploadController.list(req, res, next))
|
||||
routes.get('/albums', (req, res, next) => albumsController.list(req, res, next))
|
||||
routes.get('/albums/:sidebar', (req, res, next) => albumsController.list(req, res, next))
|
||||
routes.get('/albums/:page', (req, res, next) => albumsController.list(req, res, next))
|
||||
routes.post('/albums', (req, res, next) => albumsController.create(req, res, next))
|
||||
routes.post('/albums/addfiles', (req, res, next) => albumsController.addFiles(req, res, next))
|
||||
routes.post('/albums/delete', (req, res, next) => albumsController.delete(req, res, next))
|
||||
routes.post('/albums/disable', (req, res, next) => albumsController.disable(req, res, next))
|
||||
routes.post('/albums/edit', (req, res, next) => albumsController.edit(req, res, next))
|
||||
routes.post('/albums/rename', (req, res, next) => albumsController.rename(req, res, next))
|
||||
routes.get('/albums/test', (req, res, next) => albumsController.test(req, res, next))
|
||||
|
@ -141,6 +141,41 @@ fieldset[disabled] .textarea {
|
||||
fill: #fff
|
||||
}
|
||||
|
||||
.button.is-dangerish {
|
||||
background-color: #ff7043;
|
||||
border-color: transparent;
|
||||
color: rgba(0, 0, 0, 0.7)
|
||||
}
|
||||
|
||||
.button.is-dangerish.is-hovered,
|
||||
.button.is-dangerish:not([disabled]):hover {
|
||||
background-color: #ff8a65;
|
||||
border-color: transparent;
|
||||
color: rgba(0, 0, 0, 0.7)
|
||||
}
|
||||
|
||||
.button.is-dangerish.is-active,
|
||||
.button.is-dangerish:not([disabled]):active {
|
||||
background-color: #ff5722;
|
||||
border-color: transparent;
|
||||
color: rgba(0, 0, 0, 0.7)
|
||||
}
|
||||
|
||||
.button.is-dangerish.is-outlined {
|
||||
background-color: transparent;
|
||||
border-color: #ff7043;
|
||||
color: #ff7043
|
||||
}
|
||||
|
||||
.button.is-dangerish.is-outlined.is-focused,
|
||||
.button.is-dangerish.is-outlined.is-hovered,
|
||||
.button.is-dangerish.is-outlined:not([disabled]):focus,
|
||||
.button.is-dangerish.is-outlined:not([disabled]):hover {
|
||||
background-color: #ff7043;
|
||||
border-color: #ff7043;
|
||||
color: rgba(0, 0, 0, 0.7)
|
||||
}
|
||||
|
||||
.button.is-wrappable {
|
||||
white-space: break-spaces;
|
||||
min-height: 2.25em;
|
||||
|
@ -9,6 +9,8 @@ const lsKeys = {
|
||||
selected: {
|
||||
uploads: 'selectedUploads',
|
||||
uploadsAll: 'selectedUploadsAll',
|
||||
albums: 'selectedAlbums',
|
||||
albumsAll: 'selectedAlbumsAll',
|
||||
users: 'selectedUsers'
|
||||
}
|
||||
}
|
||||
@ -32,52 +34,53 @@ const page = {
|
||||
|
||||
currentView: null,
|
||||
views: {
|
||||
// config of uploads view
|
||||
// params of uploads view
|
||||
uploads: {
|
||||
type: localStorage[lsKeys.viewType.uploads],
|
||||
album: null, // album's id
|
||||
pageNum: null // page num
|
||||
pageNum: null
|
||||
},
|
||||
// config of uploads view (all)
|
||||
// params of uploads view (all)
|
||||
uploadsAll: {
|
||||
type: localStorage[lsKeys.viewType.uploadsAll],
|
||||
filters: null, // uploads' filters
|
||||
pageNum: null, // page num
|
||||
filters: null,
|
||||
pageNum: null,
|
||||
all: true
|
||||
},
|
||||
// config of users view
|
||||
// params of albums view
|
||||
albums: {
|
||||
filters: null,
|
||||
pageNum: null
|
||||
},
|
||||
// params of albums view (all)
|
||||
albumsAll: {
|
||||
filters: null,
|
||||
pageNum: null,
|
||||
all: true
|
||||
},
|
||||
// params of users view
|
||||
users: {
|
||||
filters: null, // users' filters
|
||||
filters: null,
|
||||
pageNum: null
|
||||
}
|
||||
},
|
||||
|
||||
// id of selected items (shared across pages and will be synced with localStorage)
|
||||
// ids of selected items (shared across pages and will be synced with localStorage)
|
||||
selected: {
|
||||
uploads: [],
|
||||
uploadsAll: [],
|
||||
albums: [],
|
||||
albumsAll: [],
|
||||
users: []
|
||||
},
|
||||
checkboxes: {
|
||||
uploads: [],
|
||||
uploadsAll: [],
|
||||
users: []
|
||||
},
|
||||
lastSelected: {
|
||||
upload: null,
|
||||
uploadsAll: null,
|
||||
user: null
|
||||
},
|
||||
checkboxes: [],
|
||||
lastSelected: [],
|
||||
|
||||
// select album dom for dialogs/modals
|
||||
selectAlbumContainer: null,
|
||||
|
||||
// cache for dialogs/modals
|
||||
cache: {
|
||||
uploads: {},
|
||||
albums: {},
|
||||
users: {}
|
||||
},
|
||||
cache: {},
|
||||
|
||||
clipboardJS: null,
|
||||
lazyLoad: null,
|
||||
@ -208,11 +211,12 @@ page.prepareDashboard = () => {
|
||||
const itemMenus = [
|
||||
{ selector: '#itemUploads', onclick: page.getUploads },
|
||||
{ selector: '#itemDeleteUploadsByNames', onclick: page.deleteUploadsByNames },
|
||||
{ selector: '#itemManageAlbums', onclick: page.getAlbums },
|
||||
{ selector: '#itemManageYourAlbums', onclick: page.getAlbums },
|
||||
{ selector: '#itemManageToken', onclick: page.changeToken },
|
||||
{ selector: '#itemChangePassword', onclick: page.changePassword },
|
||||
{ selector: '#itemLogout', onclick: page.logout },
|
||||
{ selector: '#itemManageUploads', onclick: page.getUploads, params: { all: true }, group: 'moderator' },
|
||||
{ selector: '#itemManageAlbums', onclick: page.getAlbums, params: { all: true }, group: 'moderator' },
|
||||
{ selector: '#itemStatistics', onclick: page.getStatistics, group: 'admin' },
|
||||
{ selector: '#itemManageUsers', onclick: page.getUsers, group: 'admin' }
|
||||
]
|
||||
@ -357,8 +361,10 @@ page.domClick = event => {
|
||||
return page.submitAlbum(element)
|
||||
case 'edit-album':
|
||||
return page.editAlbum(id)
|
||||
case 'delete-album':
|
||||
return page.deleteAlbum(id)
|
||||
case 'disable-album':
|
||||
return page.disableAlbum(id)
|
||||
case 'view-album-uploads':
|
||||
return page.viewAlbumUploads(id, element)
|
||||
// Manage users
|
||||
case 'create-user':
|
||||
return page.createUser()
|
||||
@ -370,12 +376,6 @@ page.domClick = event => {
|
||||
return page.deleteUser(id)
|
||||
case 'view-user-uploads':
|
||||
return page.viewUserUploads(id, element)
|
||||
/* // WIP
|
||||
case 'user-filters-help':
|
||||
return page.userFiltersHelp(element)
|
||||
case 'filter-users':
|
||||
return page.filterUsers(element)
|
||||
*/
|
||||
// Others
|
||||
case 'get-new-token':
|
||||
return page.getNewToken(element)
|
||||
@ -416,6 +416,30 @@ page.fadeAndScroll = disableFading => {
|
||||
})
|
||||
}
|
||||
|
||||
page.getByView = (view, get) => {
|
||||
switch (view) {
|
||||
case 'uploads':
|
||||
case 'uploadsAll':
|
||||
return {
|
||||
type: 'uploads',
|
||||
func: page.getUploads
|
||||
}[get]
|
||||
case 'albums':
|
||||
case 'albumsAll':
|
||||
return {
|
||||
type: 'albums',
|
||||
func: page.getAlbums
|
||||
}[get]
|
||||
case 'users':
|
||||
return {
|
||||
type: 'users',
|
||||
func: page.getUsers
|
||||
}[get]
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
page.switchPage = (action, element) => {
|
||||
if (page.isSomethingLoading)
|
||||
return page.warnSomethingLoading()
|
||||
@ -425,7 +449,7 @@ page.switchPage = (action, element) => {
|
||||
trigger: element
|
||||
})
|
||||
|
||||
const func = page.currentView === 'users' ? page.getUsers : page.getUploads
|
||||
const func = page.getByView(page.currentView, 'func')
|
||||
|
||||
switch (action) {
|
||||
case 'page-prev':
|
||||
@ -508,11 +532,13 @@ page.getUploads = (params = {}) => {
|
||||
}
|
||||
|
||||
page.currentView = params.all ? 'uploadsAll' : 'uploads'
|
||||
page.cache.uploads = {}
|
||||
page.cache = {}
|
||||
|
||||
const albums = response.data.albums
|
||||
const users = response.data.users
|
||||
const basedomain = response.data.basedomain
|
||||
|
||||
if (params.pageNum < 0) params.pageNum = Math.max(0, pages + params.pageNum)
|
||||
const pagination = page.paginate(response.data.count, 25, params.pageNum)
|
||||
|
||||
const filter = `
|
||||
@ -520,7 +546,7 @@ page.getUploads = (params = {}) => {
|
||||
<form class="prevent-default">
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<input id="filters" class="input is-small" type="text" placeholder="Filters" value="${page.escape(params.filters || '')}">
|
||||
<input id="filters" class="input is-small" type="text" placeholder="Filter uploads" value="${page.escape(params.filters || '')}">
|
||||
</div>
|
||||
<div class="control">
|
||||
<button type="button" class="button is-small is-primary is-outlined" title="Help?" data-action="upload-filters-help"${params.all ? ' data-all="true"' : ''}>
|
||||
@ -633,7 +659,7 @@ page.getUploads = (params = {}) => {
|
||||
files[i].type = 'video'
|
||||
|
||||
// Cache bare minimum data for thumbnails viewer
|
||||
page.cache.uploads[files[i].id] = {
|
||||
page.cache[files[i].id] = {
|
||||
name: files[i].name,
|
||||
thumb: files[i].thumb,
|
||||
original: files[i].file,
|
||||
@ -723,7 +749,7 @@ page.getUploads = (params = {}) => {
|
||||
`
|
||||
|
||||
table.appendChild(div)
|
||||
page.checkboxes[page.currentView] = table.querySelectorAll('.checkbox[data-action="select"]')
|
||||
page.checkboxes = table.querySelectorAll('.checkbox[data-action="select"]')
|
||||
}
|
||||
} else {
|
||||
const allAlbums = params.all && params.filters && params.filters.includes('albumid:')
|
||||
@ -765,7 +791,7 @@ page.getUploads = (params = {}) => {
|
||||
<td class="controls"><input type="checkbox" class="checkbox" title="Select" data-index="${i}" data-action="select"${upload.selected ? ' checked' : ''}></td>
|
||||
<th><a href="${upload.file}" target="_blank" title="${upload.file}">${upload.name}</a></th>
|
||||
${params.album === undefined ? `<th>${upload.appendix}</th>` : ''}
|
||||
${allAlbums ? `<th>${files[i].albumid ? (albums[files[i].albumid] || '') : ''}</th>` : ''}
|
||||
${allAlbums ? `<th>${upload.albumid ? (albums[upload.albumid] || '') : ''}</th>` : ''}
|
||||
<td>${upload.prettyBytes}</td>
|
||||
${params.all ? `<td>${upload.ip || ''}</td>` : ''}
|
||||
<td>${upload.prettyDate}</td>
|
||||
@ -796,7 +822,7 @@ page.getUploads = (params = {}) => {
|
||||
`
|
||||
|
||||
table.appendChild(tr)
|
||||
page.checkboxes[page.currentView] = table.querySelectorAll('.checkbox[data-action="select"]')
|
||||
page.checkboxes = table.querySelectorAll('.checkbox[data-action="select"]')
|
||||
}
|
||||
}
|
||||
|
||||
@ -830,8 +856,13 @@ page.setUploadsView = (view, element) => {
|
||||
if (page.isSomethingLoading)
|
||||
return page.warnSomethingLoading()
|
||||
|
||||
localStorage[lsKeys.viewType[page.currentView]] = view
|
||||
page.views[page.currentView].type = view
|
||||
if (view === 'list') {
|
||||
delete localStorage[lsKeys.viewType[page.currentView]]
|
||||
page.views[page.currentView].type = undefined
|
||||
} else {
|
||||
localStorage[lsKeys.viewType[page.currentView]] = view
|
||||
page.views[page.currentView].type = view
|
||||
}
|
||||
|
||||
// eslint-disable-next-line compat/compat
|
||||
page.getUploads(Object.assign(page.views[page.currentView], {
|
||||
@ -840,7 +871,7 @@ page.setUploadsView = (view, element) => {
|
||||
}
|
||||
|
||||
page.displayPreview = id => {
|
||||
const file = page.cache.uploads[id]
|
||||
const file = page.cache[id]
|
||||
if (!file.thumb) return
|
||||
|
||||
const div = document.createElement('div')
|
||||
@ -927,12 +958,12 @@ page.displayPreview = id => {
|
||||
}
|
||||
|
||||
page.selectAll = element => {
|
||||
for (let i = 0; i < page.checkboxes[page.currentView].length; i++) {
|
||||
const id = page.getItemID(page.checkboxes[page.currentView][i])
|
||||
for (let i = 0; i < page.checkboxes.length; i++) {
|
||||
const id = page.getItemID(page.checkboxes[i])
|
||||
if (isNaN(id)) continue
|
||||
if (page.checkboxes[page.currentView][i].checked !== element.checked) {
|
||||
page.checkboxes[page.currentView][i].checked = element.checked
|
||||
if (page.checkboxes[page.currentView][i].checked)
|
||||
if (page.checkboxes[i].checked !== element.checked) {
|
||||
page.checkboxes[i].checked = element.checked
|
||||
if (page.checkboxes[i].checked)
|
||||
page.selected[page.currentView].push(id)
|
||||
else
|
||||
page.selected[page.currentView].splice(page.selected[page.currentView].indexOf(id), 1)
|
||||
@ -955,12 +986,12 @@ page.selectInBetween = (element, lastElement) => {
|
||||
if (distance < 2)
|
||||
return
|
||||
|
||||
for (let i = 0; i < page.checkboxes[page.currentView].length; i++)
|
||||
for (let i = 0; i < page.checkboxes.length; i++)
|
||||
if ((thisIndex > lastIndex && i > lastIndex && i < thisIndex) ||
|
||||
(thisIndex < lastIndex && i > thisIndex && i < lastIndex)) {
|
||||
// Check or uncheck depending on the state of the initial checkbox
|
||||
const checked = page.checkboxes[page.currentView][i].checked = lastElement.checked
|
||||
const id = page.getItemID(page.checkboxes[page.currentView][i])
|
||||
const checked = page.checkboxes[i].checked = lastElement.checked
|
||||
const id = page.getItemID(page.checkboxes[i])
|
||||
if (!page.selected[page.currentView].includes(id) && checked)
|
||||
page.selected[page.currentView].push(id)
|
||||
else if (page.selected[page.currentView].includes(id) && !checked)
|
||||
@ -972,13 +1003,12 @@ page.select = (element, event) => {
|
||||
const id = page.getItemID(element)
|
||||
if (isNaN(id)) return
|
||||
|
||||
const lastSelected = page.lastSelected[page.currentView]
|
||||
if (event.shiftKey && lastSelected) {
|
||||
page.selectInBetween(element, lastSelected)
|
||||
if (event.shiftKey && page.lastSelected) {
|
||||
page.selectInBetween(element, page.lastSelected)
|
||||
// Check or uncheck depending on the state of the initial checkbox
|
||||
element.checked = lastSelected.checked
|
||||
element.checked = page.lastSelected.checked
|
||||
} else {
|
||||
page.lastSelected[page.currentView] = element
|
||||
page.lastSelected = element
|
||||
}
|
||||
|
||||
if (!page.selected[page.currentView].includes(id) && element.checked)
|
||||
@ -995,7 +1025,7 @@ page.select = (element, event) => {
|
||||
|
||||
page.clearSelection = () => {
|
||||
const selected = page.selected[page.currentView]
|
||||
const type = page.currentView === 'users' ? 'users' : 'uploads'
|
||||
const type = page.getByView(page.currentView, 'type')
|
||||
const count = selected.length
|
||||
if (!count)
|
||||
return swal('An error occurred!', `You have not selected any ${type}.`, 'error')
|
||||
@ -1008,7 +1038,7 @@ page.clearSelection = () => {
|
||||
}).then(proceed => {
|
||||
if (!proceed) return
|
||||
|
||||
const checkboxes = page.checkboxes[page.currentView]
|
||||
const checkboxes = page.checkboxes
|
||||
for (let i = 0; i < checkboxes.length; i++)
|
||||
if (checkboxes[i].checked)
|
||||
checkboxes[i].checked = false
|
||||
@ -1134,7 +1164,7 @@ page.filterUploads = element => {
|
||||
}
|
||||
|
||||
page.viewUserUploads = (id, element) => {
|
||||
const user = page.cache.users[id]
|
||||
const user = page.cache[id]
|
||||
if (!user) return
|
||||
element.classList.add('is-loading')
|
||||
// Wrap username in quotes if it contains whitespaces
|
||||
@ -1148,6 +1178,20 @@ page.viewUserUploads = (id, element) => {
|
||||
})
|
||||
}
|
||||
|
||||
page.viewAlbumUploads = (id, element) => {
|
||||
if (!page.cache[id]) return
|
||||
element.classList.add('is-loading')
|
||||
// eslint-disable-next-line compat/compat
|
||||
const all = page.currentView === 'albumsAll' && page.permissions.moderator
|
||||
page.getUploads({
|
||||
all,
|
||||
filters: `albumid:${id}`,
|
||||
trigger: all
|
||||
? document.querySelector('#itemManageUploads')
|
||||
: document.querySelector('#itemUploads')
|
||||
})
|
||||
}
|
||||
|
||||
page.deleteUpload = id => {
|
||||
page.postBulkDeleteUploads({
|
||||
all: page.currentView === 'uploadsAll',
|
||||
@ -1462,9 +1506,24 @@ page.addUploadsToAlbum = (ids, callback) => {
|
||||
}
|
||||
|
||||
page.getAlbums = (params = {}) => {
|
||||
if (params && params.all && !page.permissions.moderator)
|
||||
return swal('An error occurred!', 'You cannot do this!', 'error')
|
||||
|
||||
if (page.isSomethingLoading)
|
||||
return page.warnSomethingLoading()
|
||||
|
||||
page.updateTrigger(params.trigger, 'loading')
|
||||
|
||||
axios.get('api/albums').then(response => {
|
||||
if (typeof params.pageNum !== 'number')
|
||||
params.pageNum = 0
|
||||
|
||||
const headers = {}
|
||||
|
||||
if (params.all)
|
||||
headers.all = '1'
|
||||
|
||||
const url = `api/albums/${params.pageNum}`
|
||||
axios.get(url, { headers }).then(response => {
|
||||
if (!response) return
|
||||
|
||||
if (response.data.success === false)
|
||||
@ -1475,9 +1534,115 @@ page.getAlbums = (params = {}) => {
|
||||
return swal('An error occurred!', response.data.description, 'error')
|
||||
}
|
||||
|
||||
page.cache.albums = {}
|
||||
const pages = Math.ceil(response.data.count / 25)
|
||||
const albums = response.data.albums
|
||||
if (params.pageNum && (albums.length === 0))
|
||||
if (params.autoPage) {
|
||||
params.pageNum = pages - 1
|
||||
return page.getAlbums(params)
|
||||
} else {
|
||||
page.updateTrigger(params.trigger)
|
||||
return swal('An error occurred!', `There are no more albums to populate page ${params.pageNum + 1}.`, 'error')
|
||||
}
|
||||
|
||||
page.dom.innerHTML = `
|
||||
page.currentView = params.all ? 'albumsAll' : 'albums'
|
||||
page.cache = {}
|
||||
|
||||
const users = response.data.users
|
||||
const homeDomain = response.data.homeDomain
|
||||
|
||||
if (params.pageNum < 0) params.pageNum = Math.max(0, pages + params.pageNum)
|
||||
const pagination = page.paginate(response.data.count, 25, params.pageNum)
|
||||
|
||||
const filter = `
|
||||
<div class="column">
|
||||
<form class="prevent-default">
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<input id="filters" class="input is-small" type="text" placeholder="Filter albums (WIP)" value="${page.escape(params.filters || '')}" disabled>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button type="button" class="button is-small is-primary is-outlined" title="Help? (WIP)" data-action="album-filters-help" disabled>
|
||||
<span class="icon">
|
||||
<i class="icon-help-circled"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button type="submit" class="button is-small is-info is-outlined" title="Filter albums (WIP)" data-action="filter-albums" disabled>
|
||||
<span class="icon">
|
||||
<i class="icon-filter"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
`
|
||||
const extraControls = `
|
||||
<div class="columns">
|
||||
${filter}
|
||||
<div class="column is-one-quarter">
|
||||
<form class="prevent-default">
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<input id="jumpToPage" class="input is-small" type="number" min="1" max="${pages}" value="${params.pageNum + 1}"${pages === 1 ? ' disabled' : ''}>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button type="submit" class="button is-small is-info is-outlined" title="Jump to page" data-action="jump-to-page">
|
||||
<span class="icon">
|
||||
<i class="icon-paper-plane"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
const controls = `
|
||||
<div class="columns">
|
||||
<div class="column is-hidden-mobile"></div>
|
||||
<div class="column bulk-operations has-text-right">
|
||||
<a class="button is-small is-info is-outlined" title="Clear selection" data-action="clear-selection">
|
||||
<span class="icon">
|
||||
<i class="icon-cancel"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a class="button is-small is-dangerish is-outlined" title="Bulk disable (WIP)" data-action="bulk-disable-albums" disabled>
|
||||
<span class="icon">
|
||||
<i class="icon-trash"></i>
|
||||
</span>
|
||||
${!params.all ? '<span>Bulk disable</span>' : ''}
|
||||
</a>
|
||||
${params.all
|
||||
? `<a class="button is-small is-danger is-outlined" title="Bulk delete (WIP)" data-action="bulk-delete-albums" disabled>
|
||||
<span class="icon">
|
||||
<i class="icon-trash"></i>
|
||||
</span>
|
||||
<span>Bulk delete</span>
|
||||
</a>`
|
||||
: ''}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
// Do some string replacements for bottom controls
|
||||
const bottomFiltersId = 'bFilters'
|
||||
const bottomJumpId = 'bJumpToPage'
|
||||
const bottomExtraControls = extraControls
|
||||
.replace(/id="filters"/, `id="${bottomFiltersId}"`)
|
||||
.replace(/(data-action="filter-uploads")/, `$1 data-filtersid="${bottomFiltersId}"`)
|
||||
.replace(/id="jumpToPage"/, `id="${bottomJumpId}"`)
|
||||
.replace(/(data-action="jump-to-page")/g, `$1 data-jumpid="${bottomJumpId}"`)
|
||||
const bottomPagination = pagination
|
||||
.replace(/(data-action="page-ellipsis")/g, `$1 data-jumpid="${bottomJumpId}"`)
|
||||
|
||||
// Whether there are any unselected items
|
||||
let unselected = false
|
||||
|
||||
const createNewAlbum = `
|
||||
<h2 class="subtitle">Create new album</h2>
|
||||
<form class="prevent-default">
|
||||
<div class="field">
|
||||
@ -1504,14 +1669,22 @@ page.getAlbums = (params = {}) => {
|
||||
</div>
|
||||
</form>
|
||||
<hr>
|
||||
<h2 class="subtitle">List of albums</h2>
|
||||
`
|
||||
|
||||
page.dom.innerHTML = `
|
||||
${!params.all ? createNewAlbum : ''}
|
||||
${pagination}
|
||||
${extraControls}
|
||||
${controls}
|
||||
<div class="table-container">
|
||||
<table class="table is-narrow is-fullwidth is-hoverable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input id="selectAll" class="checkbox" type="checkbox" title="Select all" data-action="select-all"></th>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Files</th>
|
||||
${params.all ? '<th>User</th>' : ''}
|
||||
<th>Uploads</th>
|
||||
<th>Created at</th>
|
||||
<th>Public link</th>
|
||||
<th></th>
|
||||
@ -1521,38 +1694,54 @@ page.getAlbums = (params = {}) => {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${controls}
|
||||
${bottomExtraControls}
|
||||
${bottomPagination}
|
||||
`
|
||||
|
||||
const homeDomain = response.data.homeDomain
|
||||
const table = document.querySelector('#table')
|
||||
|
||||
for (let i = 0; i < response.data.albums.length; i++) {
|
||||
const album = response.data.albums[i]
|
||||
for (let i = 0; i < albums.length; i++) {
|
||||
const album = albums[i]
|
||||
const albumUrl = `${homeDomain}/a/${album.identifier}`
|
||||
|
||||
const selected = page.selected[page.currentView].includes(album.id)
|
||||
if (!selected) unselected = true
|
||||
|
||||
// Prettify
|
||||
album.prettyDate = page.getPrettyDate(new Date(album.timestamp * 1000))
|
||||
|
||||
page.cache.albums[album.id] = {
|
||||
// Server-side explicitly expect this value to consider an album as disabled
|
||||
const enabled = album.enabled !== 0
|
||||
page.cache[album.id] = {
|
||||
name: album.name,
|
||||
download: album.download,
|
||||
public: album.public,
|
||||
description: album.description
|
||||
description: album.description,
|
||||
enabled
|
||||
}
|
||||
|
||||
const tr = document.createElement('tr')
|
||||
tr.dataset.id = album.id
|
||||
tr.innerHTML = `
|
||||
<td class="controls"><input type="checkbox" class="checkbox" title="Select" data-index="${i}" data-action="select"${selected ? ' checked' : ''}></td>
|
||||
<th>${album.id}</th>
|
||||
<th>${album.name}</th>
|
||||
<th>${album.files}</th>
|
||||
<th${enabled ? '' : ' class="has-text-grey"'}>${album.name}</td>
|
||||
${params.all ? `<th>${album.userid ? (users[album.userid] || '') : ''}</th>` : ''}
|
||||
<th>${album.uploads}</th>
|
||||
<td>${album.prettyDate}</td>
|
||||
<td><a ${album.public ? `href="${albumUrl}"` : 'class="is-linethrough"'} target="_blank">${albumUrl}</a></td>
|
||||
<td><a ${album.public ? '' : 'class="is-linethrough" '}href="${albumUrl}" target="_blank">${albumUrl}</a></td>
|
||||
<td class="has-text-right" data-id="${album.id}">
|
||||
<a class="button is-small is-primary is-outlined" title="Edit album" data-action="edit-album">
|
||||
<span class="icon is-small">
|
||||
<i class="icon-pencil"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a class="button is-small is-info is-outlined" title="${album.uploads ? 'View uploads' : 'Album doesn\'t have uploads'}" data-action="view-album-uploads" ${album.uploads ? '' : 'disabled'}>
|
||||
<span class="icon">
|
||||
<i class="icon-docs"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a class="button is-small is-info is-outlined clipboard-js" title="Copy link to clipboard" ${album.public ? `data-clipboard-text="${albumUrl}"` : 'disabled'}>
|
||||
<span class="icon is-small">
|
||||
<i class="icon-clipboard"></i>
|
||||
@ -1563,7 +1752,7 @@ page.getAlbums = (params = {}) => {
|
||||
<i class="icon-download"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a class="button is-small is-danger is-outlined" title="Delete album" data-action="delete-album">
|
||||
<a class="button is-small is-dangerish is-outlined" title="Disable album" data-action="disable-album">
|
||||
<span class="icon is-small">
|
||||
<i class="icon-trash"></i>
|
||||
</span>
|
||||
@ -1572,9 +1761,21 @@ page.getAlbums = (params = {}) => {
|
||||
`
|
||||
|
||||
table.appendChild(tr)
|
||||
page.checkboxes = table.querySelectorAll('.checkbox[data-action="select"]')
|
||||
}
|
||||
|
||||
const selectAll = document.querySelector('#selectAll')
|
||||
if (selectAll && !unselected) {
|
||||
selectAll.checked = true
|
||||
selectAll.title = 'Unselect all'
|
||||
}
|
||||
|
||||
page.fadeAndScroll()
|
||||
page.updateTrigger(params.trigger, 'active')
|
||||
|
||||
if (page.currentView === 'albumsAll')
|
||||
page.views[page.currentView].filters = params.filters
|
||||
page.views[page.currentView].pageNum = albums.length ? params.pageNum : 0
|
||||
}).catch(error => {
|
||||
page.updateTrigger(params.trigger)
|
||||
page.onAxiosError(error)
|
||||
@ -1582,7 +1783,7 @@ page.getAlbums = (params = {}) => {
|
||||
}
|
||||
|
||||
page.editAlbum = id => {
|
||||
const album = page.cache.albums[id]
|
||||
const album = page.cache[id]
|
||||
if (!album) return
|
||||
|
||||
const div = document.createElement('div')
|
||||
@ -1599,6 +1800,16 @@ page.editAlbum = id => {
|
||||
</div>
|
||||
<p class="help">Max length is ${page.albumDescMaxLength} characters.</p>
|
||||
</div>
|
||||
${page.currentView === 'albumsAll' && page.permissions.moderator
|
||||
? `<div class="field">
|
||||
<div class="control">
|
||||
<label class="checkbox">
|
||||
<input id="swalEnabled" type="checkbox" ${album.enabled ? 'checked' : ''}>
|
||||
Enabled
|
||||
</label>
|
||||
</div>
|
||||
</div>`
|
||||
: ''}
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<label class="checkbox">
|
||||
@ -1638,14 +1849,19 @@ page.editAlbum = id => {
|
||||
}).then(value => {
|
||||
if (!value) return
|
||||
|
||||
axios.post('api/albums/edit', {
|
||||
const post = {
|
||||
id,
|
||||
name: document.querySelector('#swalName').value.trim(),
|
||||
description: document.querySelector('#swalDescription').value.trim(),
|
||||
download: document.querySelector('#swalDownload').checked,
|
||||
public: document.querySelector('#swalPublic').checked,
|
||||
requestLink: document.querySelector('#swalRequestLink').checked
|
||||
}).then(response => {
|
||||
}
|
||||
|
||||
if (page.currentView === 'albumsAll' && page.permissions.moderator)
|
||||
post.enabled = document.querySelector('#swalEnabled').checked
|
||||
|
||||
axios.post('api/albums/edit', post).then(response => {
|
||||
if (!response) return
|
||||
|
||||
if (response.data.success === false)
|
||||
@ -1656,35 +1872,39 @@ page.editAlbum = id => {
|
||||
}
|
||||
|
||||
if (response.data.identifier)
|
||||
swal('Success!', `Your album's new identifier is: ${response.data.identifier}.`, 'success')
|
||||
swal('Success!', `The album's new identifier is: ${response.data.identifier}.`, 'success')
|
||||
else if (response.data.name !== album.name)
|
||||
swal('Success!', `Your album was renamed to: ${response.data.name}.`, 'success')
|
||||
swal('Success!', `The album was renamed to: ${response.data.name}.`, 'success')
|
||||
else
|
||||
swal('Success!', 'Your album was edited!', 'success', {
|
||||
swal('Success!', 'The album was edited.', 'success', {
|
||||
buttons: false,
|
||||
timer: 1500
|
||||
})
|
||||
|
||||
page.getAlbumsSidebar()
|
||||
page.getAlbums()
|
||||
// Reload albums list
|
||||
// eslint-disable-next-line compat/compat
|
||||
page.getAlbums(Object.assign(page.views[page.currentView], {
|
||||
autoPage: true
|
||||
}))
|
||||
}).catch(page.onAxiosError)
|
||||
})
|
||||
}
|
||||
|
||||
page.deleteAlbum = id => {
|
||||
page.disableAlbum = id => {
|
||||
swal({
|
||||
title: 'Are you sure?',
|
||||
text: 'This won\'t delete your uploads, only the album!',
|
||||
text: 'This won\'t delete the uploads associated with the album!',
|
||||
icon: 'warning',
|
||||
dangerMode: true,
|
||||
buttons: {
|
||||
cancel: true,
|
||||
confirm: {
|
||||
text: 'Yes, delete it!',
|
||||
text: 'Yes, disable it!',
|
||||
closeModal: false
|
||||
},
|
||||
purge: {
|
||||
text: 'Umm, delete the uploads too please?',
|
||||
text: 'Umm, delete the uploads too, please?',
|
||||
value: 'purge',
|
||||
className: 'swal-button--danger',
|
||||
closeModal: false
|
||||
@ -1693,7 +1913,7 @@ page.deleteAlbum = id => {
|
||||
}).then(proceed => {
|
||||
if (!proceed) return
|
||||
|
||||
axios.post('api/albums/delete', {
|
||||
axios.post('api/albums/disable', {
|
||||
id,
|
||||
purge: proceed === 'purge'
|
||||
}).then(response => {
|
||||
@ -1710,12 +1930,17 @@ page.deleteAlbum = id => {
|
||||
return swal('An error occurred!', response.data.description, 'error')
|
||||
}
|
||||
|
||||
swal('Deleted!', 'Your album has been deleted.', 'success', {
|
||||
swal('Deleted!', 'Your album has been disabled.', 'success', {
|
||||
buttons: false,
|
||||
timer: 1500
|
||||
})
|
||||
|
||||
page.getAlbumsSidebar()
|
||||
page.getAlbums()
|
||||
// Reload albums list
|
||||
// eslint-disable-next-line compat/compat
|
||||
page.getAlbums(Object.assign(page.views[page.currentView], {
|
||||
autoPage: true
|
||||
}))
|
||||
}).catch(page.onAxiosError)
|
||||
})
|
||||
}
|
||||
@ -1742,7 +1967,9 @@ page.submitAlbum = element => {
|
||||
timer: 1500
|
||||
})
|
||||
page.getAlbumsSidebar()
|
||||
page.getAlbums()
|
||||
page.getAlbums({
|
||||
pageNum: -1
|
||||
})
|
||||
}).catch(error => {
|
||||
page.updateTrigger(element)
|
||||
page.onAxiosError(error)
|
||||
@ -1750,7 +1977,7 @@ page.submitAlbum = element => {
|
||||
}
|
||||
|
||||
page.getAlbumsSidebar = () => {
|
||||
axios.get('api/albums/sidebar').then(response => {
|
||||
axios.get('api/albums', { headers: { sidebar: '1' } }).then(response => {
|
||||
if (!response) return
|
||||
|
||||
if (response.data.success === false)
|
||||
@ -1760,6 +1987,8 @@ page.getAlbumsSidebar = () => {
|
||||
return swal('An error occurred!', response.data.description, 'error')
|
||||
}
|
||||
|
||||
const albums = response.data.albums
|
||||
const count = response.data.count
|
||||
const albumsContainer = document.querySelector('#albumsContainer')
|
||||
|
||||
// Clear albums sidebar if necessary
|
||||
@ -1770,16 +1999,16 @@ page.getAlbumsSidebar = () => {
|
||||
albumsContainer.innerHTML = ''
|
||||
}
|
||||
|
||||
if (response.data.albums === undefined)
|
||||
if (albums === undefined)
|
||||
return
|
||||
|
||||
for (let i = 0; i < response.data.albums.length; i++) {
|
||||
const album = response.data.albums[i]
|
||||
for (let i = 0; i < albums.length; i++) {
|
||||
const album = albums[i]
|
||||
const li = document.createElement('li')
|
||||
const a = document.createElement('a')
|
||||
a.id = album.id
|
||||
a.innerHTML = album.name
|
||||
a.className = 'is-relative'
|
||||
a.innerHTML = album.name
|
||||
|
||||
a.addEventListener('click', event => {
|
||||
page.getUploads({
|
||||
@ -1792,6 +2021,23 @@ page.getAlbumsSidebar = () => {
|
||||
li.appendChild(a)
|
||||
albumsContainer.appendChild(li)
|
||||
}
|
||||
|
||||
if (count > albums.length) {
|
||||
const li = document.createElement('li')
|
||||
const a = document.createElement('a')
|
||||
a.className = 'is-relative'
|
||||
a.innerHTML = '...'
|
||||
a.title = `You have ${count} albums, but the sidebar can only list your first ${albums.length} albums.`
|
||||
|
||||
a.addEventListener('click', event => {
|
||||
page.getAlbums({
|
||||
trigger: document.querySelector('#itemManageYourAlbums')
|
||||
})
|
||||
})
|
||||
|
||||
li.appendChild(a)
|
||||
albumsContainer.appendChild(li)
|
||||
}
|
||||
}).catch(page.onAxiosError)
|
||||
}
|
||||
|
||||
@ -1939,7 +2185,7 @@ page.getUsers = (params = {}) => {
|
||||
|
||||
page.updateTrigger(params.trigger, 'loading')
|
||||
|
||||
if (params.pageNum === undefined)
|
||||
if (typeof params.pageNum !== 'number')
|
||||
params.pageNum = 0
|
||||
|
||||
const url = `api/users/${params.pageNum}`
|
||||
@ -1953,7 +2199,8 @@ page.getUsers = (params = {}) => {
|
||||
}
|
||||
|
||||
const pages = Math.ceil(response.data.count / 25)
|
||||
if (params.pageNum && (response.data.users.length === 0))
|
||||
const users = response.data.users
|
||||
if (params.pageNum && (users.length === 0))
|
||||
if (params.autoPage) {
|
||||
params.pageNum = pages - 1
|
||||
return page.getUsers(params)
|
||||
@ -1963,7 +2210,7 @@ page.getUsers = (params = {}) => {
|
||||
}
|
||||
|
||||
page.currentView = 'users'
|
||||
page.cache.users = {}
|
||||
page.cache = {}
|
||||
|
||||
if (params.pageNum < 0) params.pageNum = Math.max(0, pages + params.pageNum)
|
||||
const pagination = page.paginate(response.data.count, 25, params.pageNum)
|
||||
@ -1973,7 +2220,7 @@ page.getUsers = (params = {}) => {
|
||||
<form class="prevent-default">
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<input id="filters" class="input is-small" type="text" placeholder="Filters (WIP)" value="${page.escape(params.filters || '')}" disabled>
|
||||
<input id="filters" class="input is-small" type="text" placeholder="Filter users (WIP)" value="${page.escape(params.filters || '')}" disabled>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button type="button" class="button is-small is-primary is-outlined" title="Help? (WIP)" data-action="user-filters-help" disabled>
|
||||
@ -2031,7 +2278,7 @@ page.getUsers = (params = {}) => {
|
||||
<i class="icon-cancel"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a class="button is-small is-warning is-outlined" title="Bulk disable (WIP)" data-action="bulk-disable-users" disabled>
|
||||
<a class="button is-small is-dangerish is-outlined" title="Bulk disable (WIP)" data-action="bulk-disable-users" disabled>
|
||||
<span class="icon">
|
||||
<i class="icon-hammer"></i>
|
||||
</span>
|
||||
@ -2089,9 +2336,9 @@ page.getUsers = (params = {}) => {
|
||||
|
||||
const table = document.querySelector('#table')
|
||||
|
||||
for (let i = 0; i < response.data.users.length; i++) {
|
||||
const user = response.data.users[i]
|
||||
const selected = page.selected.users.includes(user.id)
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
const user = users[i]
|
||||
const selected = page.selected[page.currentView].includes(user.id)
|
||||
if (!selected) unselected = true
|
||||
|
||||
let displayGroup = null
|
||||
@ -2103,7 +2350,7 @@ page.getUsers = (params = {}) => {
|
||||
|
||||
// Server-side explicitly expects either of these two values to consider a user as disabled
|
||||
const enabled = user.enabled !== false && user.enabled !== 0
|
||||
page.cache.users[user.id] = {
|
||||
page.cache[user.id] = {
|
||||
username: user.username,
|
||||
groups: user.groups,
|
||||
enabled,
|
||||
@ -2121,7 +2368,7 @@ page.getUsers = (params = {}) => {
|
||||
tr.dataset.id = user.id
|
||||
tr.innerHTML = `
|
||||
<td class="controls"><input type="checkbox" class="checkbox" title="Select" data-index="${i}" data-action="select"${selected ? ' checked' : ''}></td>
|
||||
<th${enabled ? '' : ' class="is-linethrough"'}>${user.username}</td>
|
||||
<th${enabled ? '' : ' class="has-text-grey"'}>${user.username}</td>
|
||||
<th>${user.uploads}</th>
|
||||
<td>${page.getPrettyBytes(user.usage)}</td>
|
||||
<td>${displayGroup}</td>
|
||||
@ -2138,7 +2385,7 @@ page.getUsers = (params = {}) => {
|
||||
<i class="icon-docs"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a class="button is-small is-warning is-outlined" title="${enabled ? 'Disable user' : 'User is disabled'}" data-action="disable-user" ${enabled ? '' : 'disabled'}>
|
||||
<a class="button is-small is-dangerish is-outlined" title="${enabled ? 'Disable user' : 'User is disabled'}" data-action="disable-user" ${enabled ? '' : 'disabled'}>
|
||||
<span class="icon">
|
||||
<i class="icon-hammer"></i>
|
||||
</span>
|
||||
@ -2152,7 +2399,7 @@ page.getUsers = (params = {}) => {
|
||||
`
|
||||
|
||||
table.appendChild(tr)
|
||||
page.checkboxes.users = table.querySelectorAll('.checkbox[data-action="select"]')
|
||||
page.checkboxes = table.querySelectorAll('.checkbox[data-action="select"]')
|
||||
}
|
||||
|
||||
const selectAll = document.querySelector('#selectAll')
|
||||
@ -2164,7 +2411,7 @@ page.getUsers = (params = {}) => {
|
||||
page.fadeAndScroll()
|
||||
page.updateTrigger(params.trigger, 'active')
|
||||
|
||||
page.views.users.pageNum = response.data.users.length ? params.pageNum : 0
|
||||
page.views[page.currentView].pageNum = users.length ? params.pageNum : 0
|
||||
}).catch(error => {
|
||||
page.updateTrigger(params.trigger)
|
||||
page.onAxiosError(error)
|
||||
@ -2252,7 +2499,7 @@ page.createUser = () => {
|
||||
}
|
||||
|
||||
page.editUser = id => {
|
||||
const user = page.cache.users[id]
|
||||
const user = page.cache[id]
|
||||
if (!user) return
|
||||
|
||||
const groupOptions = Object.keys(page.permissions).map((g, i, a) => {
|
||||
@ -2366,12 +2613,12 @@ page.editUser = id => {
|
||||
}
|
||||
|
||||
page.disableUser = id => {
|
||||
const user = page.cache.users[id]
|
||||
const user = page.cache[id]
|
||||
if (!user || !user.enabled) return
|
||||
|
||||
const content = document.createElement('div')
|
||||
content.innerHTML = `
|
||||
<p>You will be disabling a user named <b>${page.cache.users[id].username}</b>.</p>
|
||||
<p>You will be disabling a user named <b>${page.cache[id].username}</b>.</p>
|
||||
<p>Their files will remain.</p>
|
||||
`
|
||||
|
||||
@ -2399,7 +2646,7 @@ page.disableUser = id => {
|
||||
else
|
||||
return swal('An error occurred!', response.data.description, 'error')
|
||||
|
||||
swal('Success!', `${page.cache.users[id].username} has been disabled.`, 'success', {
|
||||
swal('Success!', `${page.cache[id].username} has been disabled.`, 'success', {
|
||||
buttons: false,
|
||||
timer: 1500
|
||||
})
|
||||
@ -2409,12 +2656,12 @@ page.disableUser = id => {
|
||||
}
|
||||
|
||||
page.deleteUser = id => {
|
||||
const user = page.cache.users[id]
|
||||
const user = page.cache[id]
|
||||
if (!user) return
|
||||
|
||||
const content = document.createElement('div')
|
||||
content.innerHTML = `
|
||||
<p>You will be deleting a user named <b>${page.cache.users[id].username}</b>.<p>
|
||||
<p>You will be deleting a user named <b>${page.cache[id].username}</b>.<p>
|
||||
<p>Their files will remain, unless you choose otherwise.</p>
|
||||
`
|
||||
|
||||
@ -2458,7 +2705,7 @@ page.deleteUser = id => {
|
||||
return swal('An error occurred!', response.data.description, 'error')
|
||||
}
|
||||
|
||||
swal('Success!', `${page.cache.users[id].username} has been deleted.`, 'success', {
|
||||
swal('Success!', `${page.cache[id].username} has been deleted.`, 'success', {
|
||||
buttons: false,
|
||||
timer: 1500
|
||||
})
|
||||
@ -2647,7 +2894,7 @@ window.onload = () => {
|
||||
if (!('ontouchstart' in document.documentElement))
|
||||
document.documentElement.classList.add('no-touch')
|
||||
|
||||
const selectedKeys = ['uploads', 'uploadsAll', 'users']
|
||||
const selectedKeys = ['uploads', 'uploadsAll', 'albums', 'albumsAll', 'users']
|
||||
for (let i = 0; i < selectedKeys.length; i++) {
|
||||
const ls = localStorage[lsKeys.selected[selectedKeys[i]]]
|
||||
if (ls) page.selected[selectedKeys[i]] = JSON.parse(ls)
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"1": "1590695426",
|
||||
"1": "1590986654",
|
||||
"2": "1589010026",
|
||||
"3": "1581416390",
|
||||
"4": "1581416390",
|
||||
|
@ -54,7 +54,7 @@
|
||||
<h2 class='subtitle is-brighter'>What information do we know about you?</h2>
|
||||
<article class="message">
|
||||
<div class="message-body">
|
||||
We don’t request or require you to provide personal information to access our web site.<br>
|
||||
We don’t request or require you to provide personal information to access our website.<br>
|
||||
However, we may receive your IP address and user agent automatically.
|
||||
</div>
|
||||
</article>
|
||||
@ -124,22 +124,34 @@
|
||||
<td>Personalization</td>
|
||||
<td>LocalStorage</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>selectedAlbums</th>
|
||||
<td>{{ globals.root_domain }}</td>
|
||||
<td>Personalization</td>
|
||||
<td>LocalStorage</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>selectedAlbumsAll</th>
|
||||
<td>{{ globals.root_domain }}</td>
|
||||
<td>Personalization</td>
|
||||
<td>LocalStorage</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>selectedUploads</th>
|
||||
<td>{{ globals.root_domain }}</td>
|
||||
<td>Necessary</td>
|
||||
<td>Personalization</td>
|
||||
<td>LocalStorage</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>selectedUploadsAll</th>
|
||||
<td>{{ globals.root_domain }}</td>
|
||||
<td>Necessary</td>
|
||||
<td>Personalization</td>
|
||||
<td>LocalStorage</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>selectedUsers</th>
|
||||
<td>{{ globals.root_domain }}</td>
|
||||
<td>Necessary</td>
|
||||
<td>Personalization</td>
|
||||
<td>LocalStorage</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -175,18 +187,21 @@
|
||||
<tr>
|
||||
<th>viewTypeUploads</th>
|
||||
<td>{{ globals.root_domain }}</td>
|
||||
<td>Necessary</td>
|
||||
<td>Personalization</td>
|
||||
<td>LocalStorage</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>viewTypeUploadsAll</th>
|
||||
<td>{{ globals.root_domain }}</td>
|
||||
<td>Necessary</td>
|
||||
<td>Personalization</td>
|
||||
<td>LocalStorage</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
Only Analytics keys will be set on first page load regardless of your consent.<br>
|
||||
The rest will only be set as you use features of our website.<br>
|
||||
However, all Personalization keys should delete themselves once you set back their default values.
|
||||
</div>
|
||||
</article>
|
||||
|
||||
|
@ -58,7 +58,7 @@
|
||||
<p class="menu-label">Albums</p>
|
||||
<ul class="menu-list is-unselectable">
|
||||
<li>
|
||||
<a id="itemManageAlbums" class="is-relative">Manage your albums</a>
|
||||
<a id="itemManageYourAlbums" class="is-relative">Manage your albums</a>
|
||||
</li>
|
||||
<li>
|
||||
<ul id="albumsContainer"></ul>
|
||||
@ -72,6 +72,9 @@
|
||||
<li>
|
||||
<a id="itemManageUploads" class="is-relative is-hidden">Manage uploads</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="itemManageAlbums" class="is-relative is-hidden">Manage albums</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="itemManageUsers" class="is-relative is-hidden">Manage users</a>
|
||||
</li>
|
||||
|
@ -275,7 +275,7 @@
|
||||
The logs contain each visitor's IP address.<br>
|
||||
This is required by our automated system, that checks logs in real time to detect and punish abuse, such as DDoS attempts, scanners, and so on.<br>
|
||||
Logs are rotated daily, and the server will only keep the last 10 logs.<br>
|
||||
So you can be reassured that the logs will only be kept for <strong>10 days at most</strong>.
|
||||
So you can be assured that the logs will only be kept for <strong>10 days at most</strong>.
|
||||
</div>
|
||||
</article>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user