Updated some dev dependencies.

---

Gulp will now build CSS/JS files during development into dist-dev
directory, to prevent IDE's Git from unnecessarily building diff's.

Added dist-dev to ignore files.

---

The entire config fille will now be passed to Nunjuck templates for ease
of access of config values.

Root domain for use in Nunjuck templates will now be parsed from config.

Better page titles.

Updated help message for "Uploads history order" option in
homepage's config tab.

Added "Load images for preview" option to homepage's config tab.
Setting this to false will now prevent image uploads from loading
themselves for previews.

Uploads' original names in homepage's uploads history are now
selectable.

Min/max length for user/pass are now enforced in auth's front-end.

Improved performance of album public pages.
Their generated HTML pages will now be cached into memory.
Unfortunately, No-JS version of their pages will be cached separately,
so each album may take up to double the memory space.

File names in thumbnails no longer have their full URLs as tooltips.
I saw no point in that behavior.

Added video icons.
Homepage's uploads history will now display video icons for videos.

"View thumbnail" button in Dashboard is now renamed to "Show preview".
Their icons will also be changed depending on their file types.

Added max length for albums' title & description.
These will be enforced both in front-end and back-end.
Existing albums that have surpassed the limits will not be enforced.

A few other small improvements.
This commit is contained in:
Bobby Wibowo 2019-09-17 11:13:41 +07:00
parent 21f39dff9d
commit 9e9b0d4439
No known key found for this signature in database
GPG Key ID: 51C3A1E1E22D26CF
50 changed files with 429 additions and 267 deletions

View File

@ -1,4 +1,5 @@
**/*.min.js
dist/js/*
dist/*
dist-dev/*
public/libs/*
src/libs/*

3
.gitignore vendored
View File

@ -51,6 +51,9 @@ package-lock.json
# Custom pages directory
/pages/custom
# Dist dev
/dist-dev
# User files
.DS_Store
.nvmrc

View File

@ -1,3 +1,4 @@
dist/css/*
dist/*
dist-dev/*
public/libs/*
src/libs/*

View File

@ -10,6 +10,11 @@ const logger = require('./../logger')
const db = require('knex')(config.database)
const self = {
// Don't forget to update max length of text inputs in
// home.js & dashboard.js when changing these values
titleMaxLength: 280,
descMaxLength: 4000,
onHold: new Set()
}
@ -109,7 +114,7 @@ self.create = async (req, res, next) => {
if (!user) return
const name = typeof req.body.name === 'string'
? utils.escape(req.body.name.trim())
? utils.escape(req.body.name.trim().substring(0, self.titleMaxLength))
: ''
if (!name)
@ -140,7 +145,7 @@ self.create = async (req, res, next) => {
download: (req.body.download === false || req.body.download === 0) ? 0 : 1,
public: (req.body.public === false || req.body.public === 0) ? 0 : 1,
description: typeof req.body.description === 'string'
? utils.escape(req.body.description.trim())
? utils.escape(req.body.description.trim().substring(0, self.descMaxLength))
: ''
})
utils.invalidateStatsCache('albums')
@ -159,7 +164,7 @@ self.delete = async (req, res, next) => {
const id = req.body.id
const purge = req.body.purge
if (id === undefined || id === '')
if (!Number.isFinite(id))
return res.json({ success: false, description: 'No album specified.' })
try {
@ -184,7 +189,7 @@ self.delete = async (req, res, next) => {
userid: user.id
})
.update('enabled', 0)
utils.invalidateStatsCache('albums')
utils.invalidateAlbumsCache([id])
const identifier = await db.table('albums')
.select('identifier')
@ -215,7 +220,7 @@ self.edit = async (req, res, next) => {
return res.json({ success: false, description: 'No album specified.' })
const name = typeof req.body.name === 'string'
? utils.escape(req.body.name.trim())
? utils.escape(req.body.name.trim().substring(0, self.titleMaxLength))
: ''
if (!name)
@ -245,13 +250,14 @@ self.edit = async (req, res, next) => {
})
.update({
name,
editedAt: Math.floor(Date.now() / 1000),
download: Boolean(req.body.download),
public: Boolean(req.body.public),
description: typeof req.body.description === 'string'
? utils.escape(req.body.description.trim())
? utils.escape(req.body.description.trim().substring(0, self.descMaxLength))
: ''
})
utils.invalidateStatsCache('albums')
utils.invalidateAlbumsCache([id])
if (!req.body.requestLink)
return res.json({ success: true, name })

View File

@ -1,4 +1,3 @@
const { promisify } = require('util')
const bcrypt = require('bcrypt')
const randomstring = require('randomstring')
const perms = require('./permissionController')
@ -8,11 +7,27 @@ const config = require('./../config')
const logger = require('./../logger')
const db = require('knex')(config.database)
// Don't forget to update min/max length of text inputs in auth.njk
// when changing these values.
const self = {
compare: promisify(bcrypt.compare),
hash: promisify(bcrypt.hash)
user: {
min: 4,
max: 32
},
pass: {
min: 6,
// Should not be more than 72 characters
// https://github.com/kelektiv/node.bcrypt.js#security-issues-and-concerns
max: 64,
// Length of randomized password
// when resetting passwordthrough Dashboard's Manage Users.
rand: 16
}
}
// https://github.com/kelektiv/node.bcrypt.js#a-note-on-rounds
const saltRounds = 10
self.verify = async (req, res, next) => {
const username = typeof req.body.username === 'string'
? req.body.username.trim()
@ -37,7 +52,7 @@ self.verify = async (req, res, next) => {
if (user.enabled === false || user.enabled === 0)
return res.json({ success: false, description: 'This account has been disabled.' })
const result = await self.compare(password, user.password)
const result = await bcrypt.compare(password, user.password)
if (result === false)
return res.json({ success: false, description: 'Wrong password.' })
else
@ -55,14 +70,14 @@ self.register = async (req, res, next) => {
const username = typeof req.body.username === 'string'
? req.body.username.trim()
: ''
if (username.length < 4 || username.length > 32)
return res.json({ success: false, description: 'Username must have 4-32 characters.' })
if (username.length < self.user.min || username.length > self.user.max)
return res.json({ success: false, description: `Username must have ${self.user.min}-${self.user.max} characters.` })
const password = typeof req.body.password === 'string'
? req.body.password.trim()
: ''
if (password.length < 6 || password.length > 64)
return res.json({ success: false, description: 'Password must have 6-64 characters.' })
if (password.length < self.pass.min || password.length > self.pass.max)
return res.json({ success: false, description: `Password must have ${self.pass.min}-${self.pass.max} characters.` })
try {
const user = await db.table('users')
@ -72,7 +87,7 @@ self.register = async (req, res, next) => {
if (user)
return res.json({ success: false, description: 'Username already exists.' })
const hash = await self.hash(password, 10)
const hash = await bcrypt.hash(password, saltRounds)
const token = await tokens.generateUniqueToken()
if (!token)
@ -103,11 +118,11 @@ self.changePassword = async (req, res, next) => {
const password = typeof req.body.password === 'string'
? req.body.password.trim()
: ''
if (password.length < 6 || password.length > 64)
return res.json({ success: false, description: 'Password must have 6-64 characters.' })
if (password.length < self.pass.min || password.length > self.pass.max)
return res.json({ success: false, description: `Password must have ${self.pass.min}-${self.pass.max} characters.` })
try {
const hash = await self.hash(password, 10)
const hash = await bcrypt.hash(password, saltRounds)
await db.table('users')
.where('id', user.id)
@ -144,8 +159,11 @@ self.editUser = async (req, res, next) => {
if (req.body.username !== undefined) {
update.username = String(req.body.username).trim()
if (update.username.length < 4 || update.username.length > 32)
return res.json({ success: false, description: 'Username must have 4-32 characters.' })
if (update.username.length < self.user.min || update.username.length > self.user.max)
return res.json({
success: false,
description: `Username must have ${self.user.min}-${self.user.max} characters.`
})
}
if (req.body.enabled !== undefined)
@ -159,8 +177,8 @@ self.editUser = async (req, res, next) => {
let password
if (req.body.resetPassword) {
password = randomstring.generate(16)
update.password = await self.hash(password, 10)
password = randomstring.generate(self.pass.rand)
update.password = await bcrypt.hash(password, saltRounds)
}
await db.table('users')

View File

@ -33,7 +33,9 @@ self.thumbPlaceholder = path.resolve(config.uploads.generateThumbs.placeholder |
self.logs = path.resolve(config.logsFolder)
self.customPages = path.resolve('pages/custom')
self.dist = path.resolve('dist')
self.dist = process.env.NODE_ENV === 'development'
? path.resolve('dist-dev')
: path.resolve('dist')
self.public = path.resolve('public')
self.errorRoot = path.resolve(config.errorPages.rootDir)

View File

@ -51,8 +51,18 @@ const initChunks = async uuid => {
}
const executeMulter = multer({
// Guide: https://github.com/expressjs/multer#limits
limits: {
fileSize: maxSizeBytes
fileSize: maxSizeBytes,
// Maximum number of non-file fields.
// Dropzone.js will add 6 extra fields for chunked uploads.
// We don't use them for anything else.
fields: 6,
// Maximum number of file fields.
// Chunked uploads still need to provide only 1 file field.
// Otherwise, only one of the files will end up being properly stored,
// and that will also be as a chunk.
files: 20
},
fileFilter (req, file, cb) {
file.extname = utils.extname(file.originalname)
@ -101,7 +111,8 @@ const executeMulter = multer({
return cb(null, name)
}
})
}).array('files[]')
}).array('files[]', {
})
self.isExtensionFiltered = extname => {
// If empty extension needs to be filtered
@ -621,10 +632,12 @@ self.storeFilesToDb = async (req, res, user, infoMap) => {
utils.invalidateStatsCache('uploads')
// Update albums' timestamp
if (authorizedIds.length)
if (authorizedIds.length) {
await db.table('albums')
.whereIn('id', authorizedIds)
.update('editedAt', Math.floor(Date.now() / 1000))
utils.invalidateAlbumsCache(authorizedIds)
}
}
return files.concat(exists)

View File

@ -25,7 +25,9 @@ const self = {
imageExts: ['.webp', '.jpg', '.jpeg', '.gif', '.png', '.tiff', '.tif', '.svg'],
videoExts: ['.webm', '.mp4', '.wmv', '.avi', '.mov', '.mkv'],
ffprobe: promisify(ffmpeg.ffprobe)
ffprobe: promisify(ffmpeg.ffprobe),
albumsCache: {}
}
const statsCache = {
@ -57,7 +59,7 @@ const statsCache = {
}
}
const cloudflareAuth = config.cloudflare.apiKey && config.cloudflare.email && config.cloudflare.zoneId
const cloudflareAuth = config.cloudflare && config.cloudflare.apiKey && config.cloudflare.email && config.cloudflare.zoneId
self.mayGenerateThumb = extname => {
return (config.uploads.generateThumbs.image && self.imageExts.includes(extname)) ||
@ -504,6 +506,14 @@ self.bulkDeleteExpired = async (dryrun) => {
return result
}
self.invalidateAlbumsCache = albumids => {
for (const albumid of albumids) {
delete self.albumsCache[albumid]
delete self.albumsCache[`${albumid}-nojs`]
}
self.invalidateStatsCache('albums')
}
self.invalidateStatsCache = type => {
if (!['albums', 'users', 'uploads'].includes(type)) return
statsCache[type].invalidatedAt = Date.now()
@ -660,6 +670,8 @@ self.stats = async (req, res, next) => {
stats.uploads = statsCache.uploads.cache
} else {
statsCache.uploads.generating = true
statsCache.uploads.generatedAt = Date.now()
stats.uploads = {
_types: {
number: ['total', 'images', 'videos', 'others']
@ -700,7 +712,6 @@ self.stats = async (req, res, next) => {
// Update cache
statsCache.uploads.cache = stats.uploads
statsCache.uploads.generatedAt = Date.now()
statsCache.uploads.generating = false
}
@ -711,6 +722,8 @@ self.stats = async (req, res, next) => {
stats.users = statsCache.users.cache
} else {
statsCache.users.generating = true
statsCache.users.generatedAt = Date.now()
stats.users = {
_types: {
number: ['total', 'disabled']
@ -742,7 +755,6 @@ self.stats = async (req, res, next) => {
// Update cache
statsCache.users.cache = stats.users
statsCache.users.generatedAt = Date.now()
statsCache.users.generating = false
}
@ -753,6 +765,8 @@ self.stats = async (req, res, next) => {
stats.albums = statsCache.albums.cache
} else {
statsCache.albums.generating = true
statsCache.albums.generatedAt = Date.now()
stats.albums = {
_types: {
number: ['total', 'active', 'downloadable', 'public', 'generatedZip']
@ -789,7 +803,6 @@ self.stats = async (req, res, next) => {
// Update cache
statsCache.albums.cache = stats.albums
statsCache.albums.generatedAt = Date.now()
statsCache.albums.generating = false
}

View File

@ -1,2 +1,2 @@
body{-webkit-animation:none;animation:none}#dashboard{-webkit-animation:fadeInOpacity .5s;animation:fadeInOpacity .5s}.section{background:none}.menu-list a{color:#3794d2}.menu-list a:hover{color:#60a8dc;background-color:#4d4d4d}.menu-list a.is-active{color:#eff0f1;background-color:#3794d2}.menu-list a[disabled]{color:#7a7a7a;cursor:not-allowed}.menu-list a[disabled]:hover{background:none}ul#albumsContainer{border-left:0;padding-left:0}ul#albumsContainer li{border-left:1px solid #898b8d;padding-left:.75em}#page.fade-in,ul#albumsContainer li{-webkit-animation:fadeInOpacity .5s;animation:fadeInOpacity .5s}.pagination{margin-bottom:1.25rem}.pagination a:not([disabled]){color:#eff0f1;border-color:#4d4d4d;background-color:#31363b}a.pagination-link:not(.is-current):hover,a.pagination-next:not([disabled]):hover,a.pagination-previous:not([disabled]):hover{color:#eff0f1;border-color:#60a8dc;background-color:#31363b}a.pagination-link.is-current{background-color:#3794d2;border-color:#3794d2}a.pagination-link.is-current:hover{border-color:#60a8dc}li[data-action=page-ellipsis]{cursor:pointer}.label{color:#bdc3c7}.menu-list li ul{border-left-color:#898b8d}.image-container .checkbox{position:absolute;top:12px;left:12px}.no-touch .image-container .checkbox{opacity:.5}.no-touch .image-container .controls,.no-touch .image-container .details{opacity:0}.no-touch .image-container:hover .checkbox,.no-touch .image-container:hover .controls,.no-touch .image-container:hover .details{opacity:1}#page{min-width:0}.table{color:#bdc3c7;background-color:#31363b;font-size:.75rem}.table.is-striped tbody tr:nth-child(2n),.table tr:hover{background:none}.table.is-striped tbody tr:hover,.table.is-striped tbody tr:nth-child(2n):hover,.tag{background-color:#4d4d4d}.table td,.table th{border:0;white-space:nowrap}.table th{color:#eff0f1;height:2.25em}.table thead td,.table thead th{color:#eff0f1;background-color:#ff3860}.table .cell-indent{padding-left:2.25em}.is-linethrough{text-decoration:line-through}#menu.is-loading li a{cursor:progress}#statistics tr :nth-child(2){min-width:50%}.expirydate{color:#bdc3c7}
body{-webkit-animation:none;animation:none}#dashboard{-webkit-animation:fadeInOpacity .5s;animation:fadeInOpacity .5s}.section{background:none}.menu-list a{color:#3794d2}.menu-list a:hover{color:#60a8dc;background-color:#4d4d4d}.menu-list a.is-active{color:#eff0f1;background-color:#3794d2}.menu-list a[disabled]{color:#7a7a7a;cursor:not-allowed}.menu-list a[disabled]:hover{background:none}.menu-list a.is-loading:after{-webkit-animation:spinAround .5s linear infinite;animation:spinAround .5s linear infinite;border-radius:290486px;border-color:transparent transparent #dbdbdb #dbdbdb;border-style:solid;border-width:2px;content:"";display:block;height:1em;width:1em;right:.5em;top:calc(50% - .5em);position:absolute!important}ul#albumsContainer{border-left:0;padding-left:0}ul#albumsContainer li{border-left:1px solid #898b8d;padding-left:.75em}#page.fade-in,ul#albumsContainer li{-webkit-animation:fadeInOpacity .5s;animation:fadeInOpacity .5s}.pagination{margin-bottom:1.25rem}.pagination a:not([disabled]){color:#eff0f1;border-color:#4d4d4d;background-color:#31363b}a.pagination-link:not(.is-current):hover,a.pagination-next:not([disabled]):hover,a.pagination-previous:not([disabled]):hover{color:#eff0f1;border-color:#60a8dc;background-color:#31363b}a.pagination-link.is-current{background-color:#3794d2;border-color:#3794d2}a.pagination-link.is-current:hover{border-color:#60a8dc}li[data-action=page-ellipsis]{cursor:pointer}.label{color:#bdc3c7}.menu-list li ul{border-left-color:#898b8d}.image-container .checkbox{position:absolute;top:12px;left:12px}.no-touch .image-container .checkbox{opacity:.5}.no-touch .image-container .controls,.no-touch .image-container .details{opacity:0}.no-touch .image-container:hover .checkbox,.no-touch .image-container:hover .controls,.no-touch .image-container:hover .details{opacity:1}#page{min-width:0}.table{color:#bdc3c7;background-color:#31363b;font-size:.75rem}.table.is-striped tbody tr:nth-child(2n),.table tr:hover{background:none}.table.is-striped tbody tr:hover,.table.is-striped tbody tr:nth-child(2n):hover,.tag{background-color:#4d4d4d}.table td,.table th{border:0;white-space:nowrap}.table th{color:#eff0f1;height:2.25em}.table thead td,.table thead th{color:#eff0f1;background-color:#ff3860}.table .cell-indent{padding-left:2.25em}.is-linethrough{text-decoration:line-through}#menu.is-loading .menu-list a{cursor:progress}#statistics tr :nth-child(2){min-width:50%}.expirydate{color:#bdc3c7}
/*# sourceMappingURL=dashboard.css.map */

View File

@ -1 +1 @@
{"version":3,"sources":["css/dashboard.css"],"names":[],"mappings":"AAAA,KACE,sBAAc,CAAd,cACF,CAEA,WACE,mCAA4B,CAA5B,2BACF,CAEA,SACE,eACF,CAEA,aACE,aACF,CAEA,mBACE,aAAc,CACd,wBACF,CAEA,uBACE,aAAc,CACd,wBACF,CAEA,uBACE,aAAc,CACd,kBACF,CAEA,6BACE,eACF,CAEA,mBACE,aAAc,CACd,cACF,CAEA,sBACE,6BAA8B,CAC9B,kBAEF,CAEA,oCAHE,mCAA4B,CAA5B,2BAKF,CAEA,YACE,qBACF,CAEA,8BACE,aAAc,CACd,oBAAqB,CACrB,wBACF,CAEA,6HAGE,aAAc,CACd,oBAAqB,CACrB,wBACF,CAEA,6BACE,wBAAyB,CACzB,oBACF,CAEA,mCACE,oBACF,CAEA,8BACE,cACF,CAEA,OACE,aACF,CAEA,iBACE,yBACF,CAEA,2BACE,iBAAkB,CAClB,QAAS,CACT,SACF,CAEA,qCACE,UACF,CAEA,yEAEE,SACF,CAEA,gIAGE,SACF,CAEA,MAEE,WACF,CAEA,OACE,aAAc,CACd,wBAAyB,CACzB,gBACF,CAEA,yDAEE,eACF,CAEA,qFAGE,wBACF,CAEA,oBAEE,QAAS,CACT,kBACF,CAEA,UACE,aAAc,CACd,aACF,CAEA,gCAEE,aAAc,CACd,wBACF,CAEA,oBACE,mBACF,CAEA,gBACE,4BACF,CAEA,sBACE,eACF,CAEA,6BACE,aACF,CAEA,YACE,aACF","file":"dashboard.css","sourcesContent":["body {\n animation: none\n}\n\n#dashboard {\n animation: fadeInOpacity 0.5s\n}\n\n.section {\n background: none\n}\n\n.menu-list a {\n color: #3794d2\n}\n\n.menu-list a:hover {\n color: #60a8dc;\n background-color: #4d4d4d\n}\n\n.menu-list a.is-active {\n color: #eff0f1;\n background-color: #3794d2\n}\n\n.menu-list a[disabled] {\n color: #7a7a7a;\n cursor: not-allowed\n}\n\n.menu-list a[disabled]:hover {\n background: none\n}\n\nul#albumsContainer {\n border-left: 0;\n padding-left: 0\n}\n\nul#albumsContainer li {\n border-left: 1px solid #898b8d;\n padding-left: 0.75em;\n animation: fadeInOpacity 0.5s\n}\n\n#page.fade-in {\n animation: fadeInOpacity 0.5s\n}\n\n.pagination {\n margin-bottom: 1.25rem\n}\n\n.pagination a:not([disabled]) {\n color: #eff0f1;\n border-color: #4d4d4d;\n background-color: #31363b\n}\n\na.pagination-link:not(.is-current):hover,\na.pagination-next:not([disabled]):hover,\na.pagination-previous:not([disabled]):hover {\n color: #eff0f1;\n border-color: #60a8dc;\n background-color: #31363b\n}\n\na.pagination-link.is-current {\n background-color: #3794d2;\n border-color: #3794d2\n}\n\na.pagination-link.is-current:hover {\n border-color: #60a8dc\n}\n\nli[data-action=\"page-ellipsis\"] {\n cursor: pointer\n}\n\n.label {\n color: #bdc3c7\n}\n\n.menu-list li ul {\n border-left-color: #898b8d\n}\n\n.image-container .checkbox {\n position: absolute;\n top: 12px;\n left: 12px\n}\n\n.no-touch .image-container .checkbox {\n opacity: 0.5\n}\n\n.no-touch .image-container .controls,\n.no-touch .image-container .details {\n opacity: 0\n}\n\n.no-touch .image-container:hover .checkbox,\n.no-touch .image-container:hover .controls,\n.no-touch .image-container:hover .details {\n opacity: 1\n}\n\n#page {\n /* fix overflow issue with flex */\n min-width: 0\n}\n\n.table {\n color: #bdc3c7;\n background-color: #31363b;\n font-size: 0.75rem\n}\n\n.table tr:hover,\n.table.is-striped tbody tr:nth-child(2n) {\n background: none\n}\n\n.table.is-striped tbody tr:hover,\n.table.is-striped tbody tr:nth-child(2n):hover,\n.tag {\n background-color: #4d4d4d\n}\n\n.table td,\n.table th {\n border: 0;\n white-space: nowrap\n}\n\n.table th {\n color: #eff0f1;\n height: 2.25em\n}\n\n.table thead td,\n.table thead th {\n color: #eff0f1;\n background-color: #ff3860\n}\n\n.table .cell-indent {\n padding-left: 2.25em\n}\n\n.is-linethrough {\n text-decoration: line-through\n}\n\n#menu.is-loading li a {\n cursor: progress\n}\n\n#statistics tr *:nth-child(2) {\n min-width: 50%\n}\n\n.expirydate {\n color: #bdc3c7\n}\n"]}
{"version":3,"sources":["css/dashboard.css"],"names":[],"mappings":"AAAA,KACE,sBAAc,CAAd,cACF,CAEA,WACE,mCAA4B,CAA5B,2BACF,CAEA,SACE,eACF,CAEA,aACE,aACF,CAEA,mBACE,aAAc,CACd,wBACF,CAEA,uBACE,aAAc,CACd,wBACF,CAEA,uBACE,aAAc,CACd,kBACF,CAEA,6BACE,eACF,CAEA,8BACE,gDAA0C,CAA1C,wCAA0C,CAE1C,sBAAuB,CAEvB,oDAA6B,CAA7B,kBAA6B,CAA7B,gBAA6B,CAC7B,UAAW,CACX,aAAc,CACd,UAAW,CACX,SAAU,CACV,UAA2B,CAC3B,oBAA0B,CAC1B,2BACF,CAEA,mBACE,aAAc,CACd,cACF,CAEA,sBACE,6BAA8B,CAC9B,kBAEF,CAEA,oCAHE,mCAA4B,CAA5B,2BAKF,CAEA,YACE,qBACF,CAEA,8BACE,aAAc,CACd,oBAAqB,CACrB,wBACF,CAEA,6HAGE,aAAc,CACd,oBAAqB,CACrB,wBACF,CAEA,6BACE,wBAAyB,CACzB,oBACF,CAEA,mCACE,oBACF,CAEA,8BACE,cACF,CAEA,OACE,aACF,CAEA,iBACE,yBACF,CAEA,2BACE,iBAAkB,CAClB,QAAS,CACT,SACF,CAEA,qCACE,UACF,CAEA,yEAEE,SACF,CAEA,gIAGE,SACF,CAEA,MAEE,WACF,CAEA,OACE,aAAc,CACd,wBAAyB,CACzB,gBACF,CAEA,yDAEE,eACF,CAEA,qFAGE,wBACF,CAEA,oBAEE,QAAS,CACT,kBACF,CAEA,UACE,aAAc,CACd,aACF,CAEA,gCAEE,aAAc,CACd,wBACF,CAEA,oBACE,mBACF,CAEA,gBACE,4BACF,CAEA,8BACE,eACF,CAEA,6BACE,aACF,CAEA,YACE,aACF","file":"dashboard.css","sourcesContent":["body {\n animation: none\n}\n\n#dashboard {\n animation: fadeInOpacity 0.5s\n}\n\n.section {\n background: none\n}\n\n.menu-list a {\n color: #3794d2\n}\n\n.menu-list a:hover {\n color: #60a8dc;\n background-color: #4d4d4d\n}\n\n.menu-list a.is-active {\n color: #eff0f1;\n background-color: #3794d2\n}\n\n.menu-list a[disabled] {\n color: #7a7a7a;\n cursor: not-allowed\n}\n\n.menu-list a[disabled]:hover {\n background: none\n}\n\n.menu-list a.is-loading::after {\n animation: spinAround 0.5s infinite linear;\n border: 2px solid #dbdbdb;\n border-radius: 290486px;\n border-right-color: transparent;\n border-top-color: transparent;\n content: \"\";\n display: block;\n height: 1em;\n width: 1em;\n right: calc(0% + (1em / 2));\n top: calc(50% - (1em / 2));\n position: absolute !important\n}\n\nul#albumsContainer {\n border-left: 0;\n padding-left: 0\n}\n\nul#albumsContainer li {\n border-left: 1px solid #898b8d;\n padding-left: 0.75em;\n animation: fadeInOpacity 0.5s\n}\n\n#page.fade-in {\n animation: fadeInOpacity 0.5s\n}\n\n.pagination {\n margin-bottom: 1.25rem\n}\n\n.pagination a:not([disabled]) {\n color: #eff0f1;\n border-color: #4d4d4d;\n background-color: #31363b\n}\n\na.pagination-link:not(.is-current):hover,\na.pagination-next:not([disabled]):hover,\na.pagination-previous:not([disabled]):hover {\n color: #eff0f1;\n border-color: #60a8dc;\n background-color: #31363b\n}\n\na.pagination-link.is-current {\n background-color: #3794d2;\n border-color: #3794d2\n}\n\na.pagination-link.is-current:hover {\n border-color: #60a8dc\n}\n\nli[data-action=\"page-ellipsis\"] {\n cursor: pointer\n}\n\n.label {\n color: #bdc3c7\n}\n\n.menu-list li ul {\n border-left-color: #898b8d\n}\n\n.image-container .checkbox {\n position: absolute;\n top: 12px;\n left: 12px\n}\n\n.no-touch .image-container .checkbox {\n opacity: 0.5\n}\n\n.no-touch .image-container .controls,\n.no-touch .image-container .details {\n opacity: 0\n}\n\n.no-touch .image-container:hover .checkbox,\n.no-touch .image-container:hover .controls,\n.no-touch .image-container:hover .details {\n opacity: 1\n}\n\n#page {\n /* fix overflow issue with flex */\n min-width: 0\n}\n\n.table {\n color: #bdc3c7;\n background-color: #31363b;\n font-size: 0.75rem\n}\n\n.table tr:hover,\n.table.is-striped tbody tr:nth-child(2n) {\n background: none\n}\n\n.table.is-striped tbody tr:hover,\n.table.is-striped tbody tr:nth-child(2n):hover,\n.tag {\n background-color: #4d4d4d\n}\n\n.table td,\n.table th {\n border: 0;\n white-space: nowrap\n}\n\n.table th {\n color: #eff0f1;\n height: 2.25em\n}\n\n.table thead td,\n.table thead th {\n color: #eff0f1;\n background-color: #ff3860\n}\n\n.table .cell-indent {\n padding-left: 2.25em\n}\n\n.is-linethrough {\n text-decoration: line-through\n}\n\n#menu.is-loading .menu-list a {\n cursor: progress\n}\n\n#statistics tr *:nth-child(2) {\n min-width: 50%\n}\n\n.expirydate {\n color: #bdc3c7\n}\n"]}

2
dist/css/style.css vendored
View File

@ -1,2 +1,2 @@
html{background-color:#232629;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:#3794d2}a:hover{color:#60a8dc}hr{background-color:#898b8d}.message-body code,code{background-color:#222528;border-radius:5px}.title{color:#eff0f1}.subtitle,.subtitle strong{color:#bdc3c7}.input::-moz-placeholder,.textarea::-moz-placeholder{color:#7f8c8d}.input::-webkit-input-placeholder,.textarea::-webkit-input-placeholder{color:#7f8c8d}.input:-moz-placeholder,.textarea:-moz-placeholder{color:#7f8c8d}.input:-ms-input-placeholder,.textarea:-ms-input-placeholder{color:#7f8c8d}.input.is-active,.input.is-focused,.input:active,.input:focus,.textarea.is-active,.textarea.is-focused,.textarea:active,.textarea:focus{border-color:#3794d2}.table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#4d4d4d}.table td,.table th{vertical-align:middle}.help{color:#7f8c8d}.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}.checkbox:hover,.radio:hover{color:#7f8c8d}.message{background-color:#31363b}.message-body{color:#eff0f1;border:0;box-shadow:0 20px 60px rgba(10,10,10,.05),0 5px 10px rgba(10,10,10,.1),0 1px 1px rgba(10,10,10,.2)}.menu-list a.is-loading:after{-webkit-animation:spinAround .5s linear infinite;animation:spinAround .5s linear infinite;border-radius:290486px;border-color:transparent transparent #dbdbdb #dbdbdb;border-style:solid;border-width:2px;content:"";display:block;height:1em;width:1em;right:.5em;top:calc(50% - .5em);position:absolute!important}.hero.is-fullheight>.hero-body{min-height:100vh;height:100%}.hero.is-fullheight>.hero-body>.container{width:100%}
html{background-color:#232629;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:#3794d2}a:hover{color:#60a8dc}hr{background-color:#898b8d}.message-body code,code{background-color:#222528;border-radius:5px}.title{color:#eff0f1}.subtitle,.subtitle strong{color:#bdc3c7}.input::-moz-placeholder,.textarea::-moz-placeholder{color:#7f8c8d}.input::-webkit-input-placeholder,.textarea::-webkit-input-placeholder{color:#7f8c8d}.input:-moz-placeholder,.textarea:-moz-placeholder{color:#7f8c8d}.input:-ms-input-placeholder,.textarea:-ms-input-placeholder{color:#7f8c8d}.input.is-active,.input.is-focused,.input:active,.input:focus,.textarea.is-active,.textarea.is-focused,.textarea:active,.textarea:focus{border-color:#3794d2}.table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#4d4d4d}.table td,.table th{vertical-align:middle}.help{color:#7f8c8d}.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}.checkbox:hover,.radio:hover{color:#7f8c8d}.message{background-color:#31363b}.message-body{color:#eff0f1;border:0;box-shadow:0 20px 60px rgba(10,10,10,.05),0 5px 10px rgba(10,10,10,.1),0 1px 1px rgba(10,10,10,.2)}.hero.is-fullheight>.hero-body{min-height:100vh;height:100%}.hero.is-fullheight>.hero-body>.container{width:100%}
/*# sourceMappingURL=style.css.map */

View File

@ -1 +1 @@
{"version":3,"sources":["css/style.css"],"names":[],"mappings":"AAAA,KACE,wBAAyB,CACzB,eACF,CAEA,KACE,aAAc,CACd,mCAA4B,CAA5B,2BACF,CAEA,iCACE,GACE,SACF,CAEA,GACE,SACF,CACF,CAEA,yBACE,GACE,SACF,CAEA,GACE,SACF,CACF,CAEA,EACE,aACF,CAEA,QACE,aACF,CAEA,GACE,wBACF,CAEA,wBAEE,wBAAyB,CACzB,iBACF,CAEA,OACE,aACF,CAMA,2BACE,aACF,CAEA,qDAEE,aACF,CAEA,uEAEE,aACF,CAEA,mDAEE,aACF,CAEA,6DAEE,aACF,CAEA,wIAQE,oBACF,CAEA,qDACE,wBACF,CAEA,oBAEE,qBACF,CAEA,MACE,aACF,CAEA,gMAIE,SACF,CAEA,6BAEE,aACF,CAEA,SACE,wBACF,CAEA,cACE,aAAc,CACd,QAAS,CACT,kGACF,CAEA,8BACE,gDAA0C,CAA1C,wCAA0C,CAE1C,sBAAuB,CAEvB,oDAA6B,CAA7B,kBAA6B,CAA7B,gBAA6B,CAC7B,UAAW,CACX,aAAc,CACd,UAAW,CACX,SAAU,CACV,UAA2B,CAC3B,oBAA0B,CAC1B,2BACF,CAGA,+BACE,gBAAiB,CACjB,WACF,CAGA,0CACE,UACF","file":"style.css","sourcesContent":["html {\n background-color: #232629;\n overflow-y: auto\n}\n\nbody {\n color: #eff0f1;\n animation: fadeInOpacity 0.5s\n}\n\n@-webkit-keyframes fadeInOpacity {\n 0% {\n opacity: 0\n }\n\n 100% {\n opacity: 1\n }\n}\n\n@keyframes fadeInOpacity {\n 0% {\n opacity: 0\n }\n\n 100% {\n opacity: 1\n }\n}\n\na {\n color: #3794d2\n}\n\na:hover {\n color: #60a8dc\n}\n\nhr {\n background-color: #898b8d\n}\n\ncode,\n.message-body code {\n background-color: #222528;\n border-radius: 5px\n}\n\n.title {\n color: #eff0f1\n}\n\n.subtitle {\n color: #bdc3c7\n}\n\n.subtitle strong {\n color: #bdc3c7\n}\n\n.input::-moz-placeholder,\n.textarea::-moz-placeholder {\n color: #7f8c8d\n}\n\n.input::-webkit-input-placeholder,\n.textarea::-webkit-input-placeholder {\n color: #7f8c8d\n}\n\n.input:-moz-placeholder,\n.textarea:-moz-placeholder {\n color: #7f8c8d\n}\n\n.input:-ms-input-placeholder,\n.textarea:-ms-input-placeholder {\n color: #7f8c8d\n}\n\n.input.is-active,\n.input.is-focused,\n.input:active,\n.input:focus,\n.textarea.is-active,\n.textarea.is-focused,\n.textarea:active,\n.textarea:focus {\n border-color: #3794d2\n}\n\n.table.is-hoverable tbody tr:not(.is-selected):hover {\n background-color: #4d4d4d\n}\n\n.table td,\n.table th {\n vertical-align: middle\n}\n\n.help {\n color: #7f8c8d\n}\n\n.button.is-info.is-hovered [class^=\"icon-\"]::before,\n.button.is-info.is-hovered [class*=\" icon-\"]::before,\n.button.is-info:hover [class^=\"icon-\"]::before,\n.button.is-info:hover [class*=\" icon-\"]::before {\n fill: #fff\n}\n\n.checkbox:hover,\n.radio:hover {\n color: #7f8c8d\n}\n\n.message {\n background-color: #31363b\n}\n\n.message-body {\n color: #eff0f1;\n border: 0;\n box-shadow: 0 20px 60px rgba(10, 10, 10, 0.05), 0 5px 10px rgba(10, 10, 10, 0.1), 0 1px 1px rgba(10, 10, 10, 0.2)\n}\n\n.menu-list a.is-loading::after {\n animation: spinAround 0.5s infinite linear;\n border: 2px solid #dbdbdb;\n border-radius: 290486px;\n border-right-color: transparent;\n border-top-color: transparent;\n content: \"\";\n display: block;\n height: 1em;\n width: 1em;\n right: calc(0% + (1em / 2));\n top: calc(50% - (1em / 2));\n position: absolute !important\n}\n\n/* https://github.com/philipwalton/flexbugs#flexbug-3 */\n.hero.is-fullheight > .hero-body {\n min-height: 100vh;\n height: 100%\n}\n\n/* https://github.com/philipwalton/flexbugs#flexbug-2 */\n.hero.is-fullheight > .hero-body > .container {\n width: 100%\n}\n"]}
{"version":3,"sources":["css/style.css"],"names":[],"mappings":"AAAA,KACE,wBAAyB,CACzB,eACF,CAEA,KACE,aAAc,CACd,mCAA4B,CAA5B,2BACF,CAEA,iCACE,GACE,SACF,CAEA,GACE,SACF,CACF,CAEA,yBACE,GACE,SACF,CAEA,GACE,SACF,CACF,CAEA,EACE,aACF,CAEA,QACE,aACF,CAEA,GACE,wBACF,CAEA,wBAEE,wBAAyB,CACzB,iBACF,CAEA,OACE,aACF,CAMA,2BACE,aACF,CAEA,qDAEE,aACF,CAEA,uEAEE,aACF,CAEA,mDAEE,aACF,CAEA,6DAEE,aACF,CAEA,wIAQE,oBACF,CAEA,qDACE,wBACF,CAEA,oBAEE,qBACF,CAEA,MACE,aACF,CAEA,gMAIE,SACF,CAEA,6BAEE,aACF,CAEA,SACE,wBACF,CAEA,cACE,aAAc,CACd,QAAS,CACT,kGACF,CAGA,+BACE,gBAAiB,CACjB,WACF,CAGA,0CACE,UACF","file":"style.css","sourcesContent":["html {\n background-color: #232629;\n overflow-y: auto\n}\n\nbody {\n color: #eff0f1;\n animation: fadeInOpacity 0.5s\n}\n\n@-webkit-keyframes fadeInOpacity {\n 0% {\n opacity: 0\n }\n\n 100% {\n opacity: 1\n }\n}\n\n@keyframes fadeInOpacity {\n 0% {\n opacity: 0\n }\n\n 100% {\n opacity: 1\n }\n}\n\na {\n color: #3794d2\n}\n\na:hover {\n color: #60a8dc\n}\n\nhr {\n background-color: #898b8d\n}\n\ncode,\n.message-body code {\n background-color: #222528;\n border-radius: 5px\n}\n\n.title {\n color: #eff0f1\n}\n\n.subtitle {\n color: #bdc3c7\n}\n\n.subtitle strong {\n color: #bdc3c7\n}\n\n.input::-moz-placeholder,\n.textarea::-moz-placeholder {\n color: #7f8c8d\n}\n\n.input::-webkit-input-placeholder,\n.textarea::-webkit-input-placeholder {\n color: #7f8c8d\n}\n\n.input:-moz-placeholder,\n.textarea:-moz-placeholder {\n color: #7f8c8d\n}\n\n.input:-ms-input-placeholder,\n.textarea:-ms-input-placeholder {\n color: #7f8c8d\n}\n\n.input.is-active,\n.input.is-focused,\n.input:active,\n.input:focus,\n.textarea.is-active,\n.textarea.is-focused,\n.textarea:active,\n.textarea:focus {\n border-color: #3794d2\n}\n\n.table.is-hoverable tbody tr:not(.is-selected):hover {\n background-color: #4d4d4d\n}\n\n.table td,\n.table th {\n vertical-align: middle\n}\n\n.help {\n color: #7f8c8d\n}\n\n.button.is-info.is-hovered [class^=\"icon-\"]::before,\n.button.is-info.is-hovered [class*=\" icon-\"]::before,\n.button.is-info:hover [class^=\"icon-\"]::before,\n.button.is-info:hover [class*=\" icon-\"]::before {\n fill: #fff\n}\n\n.checkbox:hover,\n.radio:hover {\n color: #7f8c8d\n}\n\n.message {\n background-color: #31363b\n}\n\n.message-body {\n color: #eff0f1;\n border: 0;\n box-shadow: 0 20px 60px rgba(10, 10, 10, 0.05), 0 5px 10px rgba(10, 10, 10, 0.1), 0 1px 1px rgba(10, 10, 10, 0.2)\n}\n\n/* https://github.com/philipwalton/flexbugs#flexbug-3 */\n.hero.is-fullheight > .hero-body {\n min-height: 100vh;\n height: 100%\n}\n\n/* https://github.com/philipwalton/flexbugs#flexbug-2 */\n.hero.is-fullheight > .hero-body > .container {\n width: 100%\n}\n"]}

2
dist/css/thumbs.css vendored
View File

@ -1,2 +1,2 @@
.image-container{display:flex;width:224px;height:224px;margin:9px;padding:12px;background-color:#31363b;overflow:hidden;align-items:center;box-shadow:0 20px 60px rgba(10,10,10,.05),0 5px 10px rgba(10,10,10,.1),0 1px 1px rgba(10,10,10,.2)}.image-container .title{font-weight:400;word-break:break-all}.image-container .image{display:flex;height:100%;width:100%;align-items:center;justify-content:center}.image-container .image img{max-height:100%;max-width:100%;height:auto;width:auto}.image-container .controls{display:flex;position:absolute;top:12px;right:12px}.image-container .controls .button{border-radius:0}.image-container .controls .button:not(:active):not(:hover){color:#fff;background-color:rgba(49,54,59,.75)}.image-container .details{position:absolute;left:12px;bottom:12px;right:12px;background-color:rgba(49,54,59,.75);color:#eff0f1;padding:3px;font-size:.75rem}.image-container .details p{display:block;text-overflow:ellipsis;overflow:hidden}.image-container .details p span{font-weight:700}
.image-container{flex:none;position:relative;width:224px;height:224px;margin:.75rem;padding:12px;background-color:#31363b;overflow:hidden;align-items:center;box-shadow:0 20px 60px rgba(10,10,10,.05),0 5px 10px rgba(10,10,10,.1),0 1px 1px rgba(10,10,10,.2)}.image-container .title{font-weight:400;word-break:break-all}.image-container .image{display:flex;height:100%;width:100%;align-items:center;justify-content:center}.image-container .image img{max-height:100%;max-width:100%;height:auto;width:auto}.image-container .controls{display:flex;position:absolute;top:12px;right:12px}.image-container .controls .button{border-radius:0}.image-container .controls .button:not(:active):not(:hover){color:#fff;background-color:rgba(49,54,59,.75)}.image-container .details{position:absolute;left:12px;bottom:12px;right:12px;background-color:rgba(49,54,59,.75);color:#eff0f1;padding:3px;font-size:.75rem}.image-container .details p{display:block;text-overflow:ellipsis;overflow:hidden}.image-container .details p span{font-weight:700}
/*# sourceMappingURL=thumbs.css.map */

View File

@ -1 +1 @@
{"version":3,"sources":["css/thumbs.css"],"names":[],"mappings":"AAAA,iBACE,YAAa,CACb,WAAY,CACZ,YAAa,CACb,UAAW,CACX,YAAa,CACb,wBAAyB,CACzB,eAAgB,CAChB,kBAAmB,CACnB,kGACF,CAEA,wBACE,eAAmB,CACnB,oBACF,CAEA,wBACE,YAAa,CACb,WAAY,CACZ,UAAW,CACX,kBAAmB,CACnB,sBACF,CAEA,4BACE,eAAgB,CAChB,cAAe,CACf,WAAY,CACZ,UACF,CAEA,2BACE,YAAa,CACb,iBAAkB,CAClB,QAAS,CACT,UACF,CAEA,mCACE,eACF,CAEA,4DACE,UAAW,CACX,mCACF,CAEA,0BACE,iBAAkB,CAClB,SAAU,CACV,WAAY,CACZ,UAAW,CACX,mCAAwC,CACxC,aAAc,CACd,WAAY,CACZ,gBACF,CAEA,4BACE,aAAc,CACd,sBAAuB,CACvB,eACF,CAEA,iCACE,eACF","file":"thumbs.css","sourcesContent":[".image-container {\n display: flex;\n width: 224px;\n height: 224px;\n margin: 9px;\n padding: 12px;\n background-color: #31363b;\n overflow: hidden;\n align-items: center;\n box-shadow: 0 20px 60px rgba(10, 10, 10, 0.05), 0 5px 10px rgba(10, 10, 10, 0.1), 0 1px 1px rgba(10, 10, 10, 0.2)\n}\n\n.image-container .title {\n font-weight: normal;\n word-break: break-all\n}\n\n.image-container .image {\n display: flex;\n height: 100%;\n width: 100%;\n align-items: center;\n justify-content: center\n}\n\n.image-container .image img {\n max-height: 100%;\n max-width: 100%;\n height: auto;\n width: auto\n}\n\n.image-container .controls {\n display: flex;\n position: absolute;\n top: 12px;\n right: 12px\n}\n\n.image-container .controls .button {\n border-radius: 0\n}\n\n.image-container .controls .button:not(:active):not(:hover) {\n color: #fff;\n background-color: rgba(49, 54, 59, 0.75)\n}\n\n.image-container .details {\n position: absolute;\n left: 12px;\n bottom: 12px;\n right: 12px;\n background-color: rgba(49, 54, 59, 0.75);\n color: #eff0f1;\n padding: 3px;\n font-size: 0.75rem\n}\n\n.image-container .details p {\n display: block;\n text-overflow: ellipsis;\n overflow: hidden\n}\n\n.image-container .details p span {\n font-weight: bold\n}\n"]}
{"version":3,"sources":["css/thumbs.css"],"names":[],"mappings":"AAAA,iBACE,SAAU,CACV,iBAAkB,CAClB,WAAY,CACZ,YAAa,CACb,aAAe,CACf,YAAa,CACb,wBAAyB,CACzB,eAAgB,CAChB,kBAAmB,CACnB,kGACF,CAEA,wBACE,eAAmB,CACnB,oBACF,CAEA,wBACE,YAAa,CACb,WAAY,CACZ,UAAW,CACX,kBAAmB,CACnB,sBACF,CAEA,4BACE,eAAgB,CAChB,cAAe,CACf,WAAY,CACZ,UACF,CAEA,2BACE,YAAa,CACb,iBAAkB,CAClB,QAAS,CACT,UACF,CAEA,mCACE,eACF,CAEA,4DACE,UAAW,CACX,mCACF,CAEA,0BACE,iBAAkB,CAClB,SAAU,CACV,WAAY,CACZ,UAAW,CACX,mCAAwC,CACxC,aAAc,CACd,WAAY,CACZ,gBACF,CAEA,4BACE,aAAc,CACd,sBAAuB,CACvB,eACF,CAEA,iCACE,eACF","file":"thumbs.css","sourcesContent":[".image-container {\n flex: none;\n position: relative;\n width: 224px;\n height: 224px;\n margin: 0.75rem;\n padding: 12px;\n background-color: #31363b;\n overflow: hidden;\n align-items: center;\n box-shadow: 0 20px 60px rgba(10, 10, 10, 0.05), 0 5px 10px rgba(10, 10, 10, 0.1), 0 1px 1px rgba(10, 10, 10, 0.2)\n}\n\n.image-container .title {\n font-weight: normal;\n word-break: break-all\n}\n\n.image-container .image {\n display: flex;\n height: 100%;\n width: 100%;\n align-items: center;\n justify-content: center\n}\n\n.image-container .image img {\n max-height: 100%;\n max-width: 100%;\n height: auto;\n width: auto\n}\n\n.image-container .controls {\n display: flex;\n position: absolute;\n top: 12px;\n right: 12px\n}\n\n.image-container .controls .button {\n border-radius: 0\n}\n\n.image-container .controls .button:not(:active):not(:hover) {\n color: #fff;\n background-color: rgba(49, 54, 59, 0.75)\n}\n\n.image-container .details {\n position: absolute;\n left: 12px;\n bottom: 12px;\n right: 12px;\n background-color: rgba(49, 54, 59, 0.75);\n color: #eff0f1;\n padding: 3px;\n font-size: 0.75rem\n}\n\n.image-container .details p {\n display: block;\n text-overflow: ellipsis;\n overflow: hidden\n}\n\n.image-container .details p span {\n font-weight: bold\n}\n"]}

2
dist/js/auth.js vendored
View File

@ -1,2 +1,2 @@
var lsKeys={token:"token"},page={token:localStorage[lsKeys.token],user:null,pass:null,do:function(e,r){var o=page.user.value.trim();if(!o)return swal("An error occurred!","You need to specify a username.","error");var t=page.pass.value.trim();if(!t)return swal("An error occurred!","You need to specify a password.","error");r.classList.add("is-loading"),axios.post("api/"+e,{username:o,password:t}).then((function(o){if(!1===o.data.success)return r.classList.remove("is-loading"),swal("Unable to "+e+"!",o.data.description,"error");localStorage.token=o.data.token,window.location="dashboard"})).catch((function(e){return console.error(e),r.classList.remove("is-loading"),swal("An error occurred!","There was an error with the request, please check the console for more information.","error")}))},verify:function(){page.token&&axios.post("api/tokens/verify",{token:page.token}).then((function(e){if(!1===e.data.success)return swal("An error occurred!",e.data.description,"error");window.location="dashboard"})).catch((function(e){console.error(e);var r=e.response.data&&e.response.data.description?e.response.data.description:"There was an error with the request, please check the console for more information.";return swal(e.response.status+" "+e.response.statusText,r,"error")}))}};window.onload=function(){page.verify(),page.user=document.querySelector("#user"),page.pass=document.querySelector("#pass"),document.querySelector("#authForm").addEventListener("submit",(function(e){e.preventDefault()})),document.querySelector("#loginBtn").addEventListener("click",(function(e){page.do("login",e.currentTarget)})),document.querySelector("#registerBtn").addEventListener("click",(function(e){page.do("register",e.currentTarget)}))};
var lsKeys={token:"token"},page={token:localStorage[lsKeys.token],user:null,pass:null,do:function(e,r){var o=page.user.value.trim();if(!o)return swal("An error occurred!","You need to specify a username.","error");var t=page.pass.value.trim();if(!t)return swal("An error occurred!","You need to specify a password.","error");r.classList.add("is-loading"),axios.post("api/"+e,{username:o,password:t}).then((function(o){if(!1===o.data.success)return r.classList.remove("is-loading"),swal("Unable to "+e+"!",o.data.description,"error");localStorage.token=o.data.token,window.location="dashboard"})).catch((function(e){return console.error(e),r.classList.remove("is-loading"),swal("An error occurred!","There was an error with the request, please check the console for more information.","error")}))},verify:function(){page.token&&axios.post("api/tokens/verify",{token:page.token}).then((function(e){if(!1===e.data.success)return swal("An error occurred!",e.data.description,"error");window.location="dashboard"})).catch((function(e){console.error(e);var r=e.response.data&&e.response.data.description?e.response.data.description:"There was an error with the request, please check the console for more information.";return swal(e.response.status+" "+e.response.statusText,r,"error")}))}};window.onload=function(){page.verify(),page.user=document.querySelector("#user"),page.pass=document.querySelector("#pass");var e=document.querySelector("#authForm");e.addEventListener("submit",(function(e){e.preventDefault()})),document.querySelector("#loginBtn").addEventListener("click",(function(r){e.checkValidity()&&page.do("login",r.currentTarget)})),document.querySelector("#registerBtn").addEventListener("click",(function(r){e.checkValidity()&&page.do("register",r.currentTarget)}))};
//# sourceMappingURL=auth.js.map

2
dist/js/auth.js.map vendored
View File

@ -1 +1 @@
{"version":3,"sources":["auth.js"],"names":["const","lsKeys","token","page","localStorage","user","pass","do","dest","trigger","value","trim","swal","classList","add","axios","post","username","password","then","response","data","success","remove","description","window","location","catch","error","console","verify","onload","document","querySelector","addEventListener","event","preventDefault","currentTarget"],"mappings":"AAEAA,IAAMC,OAAS,CACbC,MAAO,SAGHC,KAAO,CAEXD,MAAOE,aAAaH,OAAOC,OAG3BG,KAAM,KACNC,KAAM,KAGRC,GAAO,SAAIC,EAAMC,GACfT,IAAMK,EAAOF,KAAKE,KAAKK,MAAMC,OAC7B,IAAKN,EACH,OAAOO,KAAK,qBAAsB,kCAAmC,SAEvEZ,IAAMM,EAAOH,KAAKG,KAAKI,MAAMC,OAC7B,IAAKL,EACH,OAAOM,KAAK,qBAAsB,kCAAmC,SAEvEH,EAAQI,UAAUC,IAAI,cACtBC,MAAMC,KAAK,OAAOR,EAAQ,CACxBS,SAAUZ,EACVa,SAAUZ,IACTa,MAAI,SAACC,GACN,IAA8B,IAA1BA,EAASC,KAAKC,QAEhB,OADAb,EAAQI,UAAUU,OAAO,cAClBX,KAAK,aAAaJ,EAAI,IAAKY,EAASC,KAAKG,YAAa,SAG/DpB,aAAaF,MAAQkB,EAASC,KAAKnB,MACnCuB,OAAOC,SAAW,eACjBC,OAAK,SAACC,GAGP,OAFAC,QAAQD,MAAMA,GACdnB,EAAQI,UAAUU,OAAO,cAClBX,KAAK,qBAAsB,sFAAuF,aAI7HkB,OAAW,WACJ3B,KAAKD,OAEVa,MAAMC,KAAK,oBAAqB,CAC9Bd,MAAOC,KAAKD,QACXiB,MAAI,SAACC,GACN,IAA8B,IAA1BA,EAASC,KAAKC,QAChB,OAAOV,KAAK,qBAAsBQ,EAASC,KAAKG,YAAa,SAE/DC,OAAOC,SAAW,eACjBC,OAAK,SAACC,GACPC,QAAQD,MAAMA,GACd5B,IAAMwB,EAAcI,EAAMR,SAASC,MAAQO,EAAMR,SAASC,KAAKG,YAC3DI,EAAMR,SAASC,KAAKG,YACpB,sFACJ,OAAOZ,KAAQgB,EAAMR,SAAS,OAAM,IAAIQ,EAAMR,SAAmB,WAAII,EAAa,cAItFC,OAAOM,OAAM,WACX5B,KAAK2B,SAEL3B,KAAKE,KAAO2B,SAASC,cAAc,SACnC9B,KAAKG,KAAO0B,SAASC,cAAc,SAGnCD,SAASC,cAAc,aAAaC,iBAAiB,UAAQ,SAAEC,GAC7DA,EAAMC,oBAGRJ,SAASC,cAAc,aAAaC,iBAAiB,SAAO,SAAEC,GAC5DhC,KAAKI,GAAG,QAAS4B,EAAME,kBAGzBL,SAASC,cAAc,gBAAgBC,iBAAiB,SAAO,SAAEC,GAC/DhC,KAAKI,GAAG,WAAY4B,EAAME","file":"auth.js","sourcesContent":["/* global swal, axios */\n\nconst lsKeys = {\n token: 'token'\n}\n\nconst page = {\n // user token\n token: localStorage[lsKeys.token],\n\n // HTML elements\n user: null,\n pass: null\n}\n\npage.do = (dest, trigger) => {\n const user = page.user.value.trim()\n if (!user)\n return swal('An error occurred!', 'You need to specify a username.', 'error')\n\n const pass = page.pass.value.trim()\n if (!pass)\n return swal('An error occurred!', 'You need to specify a password.', 'error')\n\n trigger.classList.add('is-loading')\n axios.post(`api/${dest}`, {\n username: user,\n password: pass\n }).then(response => {\n if (response.data.success === false) {\n trigger.classList.remove('is-loading')\n return swal(`Unable to ${dest}!`, response.data.description, 'error')\n }\n\n localStorage.token = response.data.token\n window.location = 'dashboard'\n }).catch(error => {\n console.error(error)\n trigger.classList.remove('is-loading')\n return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')\n })\n}\n\npage.verify = () => {\n if (!page.token) return\n\n axios.post('api/tokens/verify', {\n token: page.token\n }).then(response => {\n if (response.data.success === false)\n return swal('An error occurred!', response.data.description, 'error')\n\n window.location = 'dashboard'\n }).catch(error => {\n console.error(error)\n const description = error.response.data && error.response.data.description\n ? error.response.data.description\n : 'There was an error with the request, please check the console for more information.'\n return swal(`${error.response.status} ${error.response.statusText}`, description, 'error')\n })\n}\n\nwindow.onload = () => {\n page.verify()\n\n page.user = document.querySelector('#user')\n page.pass = document.querySelector('#pass')\n\n // Prevent default form's submit action\n document.querySelector('#authForm').addEventListener('submit', event => {\n event.preventDefault()\n })\n\n document.querySelector('#loginBtn').addEventListener('click', event => {\n page.do('login', event.currentTarget)\n })\n\n document.querySelector('#registerBtn').addEventListener('click', event => {\n page.do('register', event.currentTarget)\n })\n}\n"]}
{"version":3,"sources":["auth.js"],"names":["const","lsKeys","token","page","localStorage","user","pass","do","dest","trigger","value","trim","swal","classList","add","axios","post","username","password","then","response","data","success","remove","description","window","location","catch","error","console","verify","onload","document","querySelector","form","addEventListener","event","preventDefault","checkValidity","currentTarget"],"mappings":"AAEAA,IAAMC,OAAS,CACbC,MAAO,SAGHC,KAAO,CAEXD,MAAOE,aAAaH,OAAOC,OAG3BG,KAAM,KACNC,KAAM,KAGRC,GAAO,SAAIC,EAAMC,GACfT,IAAMK,EAAOF,KAAKE,KAAKK,MAAMC,OAC7B,IAAKN,EACH,OAAOO,KAAK,qBAAsB,kCAAmC,SAEvEZ,IAAMM,EAAOH,KAAKG,KAAKI,MAAMC,OAC7B,IAAKL,EACH,OAAOM,KAAK,qBAAsB,kCAAmC,SAEvEH,EAAQI,UAAUC,IAAI,cACtBC,MAAMC,KAAK,OAAOR,EAAQ,CACxBS,SAAUZ,EACVa,SAAUZ,IACTa,MAAI,SAACC,GACN,IAA8B,IAA1BA,EAASC,KAAKC,QAEhB,OADAb,EAAQI,UAAUU,OAAO,cAClBX,KAAK,aAAaJ,EAAI,IAAKY,EAASC,KAAKG,YAAa,SAG/DpB,aAAaF,MAAQkB,EAASC,KAAKnB,MACnCuB,OAAOC,SAAW,eACjBC,OAAK,SAACC,GAGP,OAFAC,QAAQD,MAAMA,GACdnB,EAAQI,UAAUU,OAAO,cAClBX,KAAK,qBAAsB,sFAAuF,aAI7HkB,OAAW,WACJ3B,KAAKD,OAEVa,MAAMC,KAAK,oBAAqB,CAC9Bd,MAAOC,KAAKD,QACXiB,MAAI,SAACC,GACN,IAA8B,IAA1BA,EAASC,KAAKC,QAChB,OAAOV,KAAK,qBAAsBQ,EAASC,KAAKG,YAAa,SAE/DC,OAAOC,SAAW,eACjBC,OAAK,SAACC,GACPC,QAAQD,MAAMA,GACd5B,IAAMwB,EAAcI,EAAMR,SAASC,MAAQO,EAAMR,SAASC,KAAKG,YAC3DI,EAAMR,SAASC,KAAKG,YACpB,sFACJ,OAAOZ,KAAQgB,EAAMR,SAAS,OAAM,IAAIQ,EAAMR,SAAmB,WAAII,EAAa,cAItFC,OAAOM,OAAM,WACX5B,KAAK2B,SAEL3B,KAAKE,KAAO2B,SAASC,cAAc,SACnC9B,KAAKG,KAAO0B,SAASC,cAAc,SAGnCjC,IAAMkC,EAAOF,SAASC,cAAc,aACpCC,EAAKC,iBAAiB,UAAQ,SAAEC,GAC9BA,EAAMC,oBAGRL,SAASC,cAAc,aAAaE,iBAAiB,SAAO,SAAEC,GACvDF,EAAKI,iBACVnC,KAAKI,GAAG,QAAS6B,EAAMG,kBAGzBP,SAASC,cAAc,gBAAgBE,iBAAiB,SAAO,SAAEC,GAC1DF,EAAKI,iBACVnC,KAAKI,GAAG,WAAY6B,EAAMG","file":"auth.js","sourcesContent":["/* global swal, axios */\n\nconst lsKeys = {\n token: 'token'\n}\n\nconst page = {\n // user token\n token: localStorage[lsKeys.token],\n\n // HTML elements\n user: null,\n pass: null\n}\n\npage.do = (dest, trigger) => {\n const user = page.user.value.trim()\n if (!user)\n return swal('An error occurred!', 'You need to specify a username.', 'error')\n\n const pass = page.pass.value.trim()\n if (!pass)\n return swal('An error occurred!', 'You need to specify a password.', 'error')\n\n trigger.classList.add('is-loading')\n axios.post(`api/${dest}`, {\n username: user,\n password: pass\n }).then(response => {\n if (response.data.success === false) {\n trigger.classList.remove('is-loading')\n return swal(`Unable to ${dest}!`, response.data.description, 'error')\n }\n\n localStorage.token = response.data.token\n window.location = 'dashboard'\n }).catch(error => {\n console.error(error)\n trigger.classList.remove('is-loading')\n return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')\n })\n}\n\npage.verify = () => {\n if (!page.token) return\n\n axios.post('api/tokens/verify', {\n token: page.token\n }).then(response => {\n if (response.data.success === false)\n return swal('An error occurred!', response.data.description, 'error')\n\n window.location = 'dashboard'\n }).catch(error => {\n console.error(error)\n const description = error.response.data && error.response.data.description\n ? error.response.data.description\n : 'There was an error with the request, please check the console for more information.'\n return swal(`${error.response.status} ${error.response.statusText}`, description, 'error')\n })\n}\n\nwindow.onload = () => {\n page.verify()\n\n page.user = document.querySelector('#user')\n page.pass = document.querySelector('#pass')\n\n // Prevent default form's submit action\n const form = document.querySelector('#authForm')\n form.addEventListener('submit', event => {\n event.preventDefault()\n })\n\n document.querySelector('#loginBtn').addEventListener('click', event => {\n if (!form.checkValidity()) return\n page.do('login', event.currentTarget)\n })\n\n document.querySelector('#registerBtn').addEventListener('click', event => {\n if (!form.checkValidity()) return\n page.do('register', event.currentTarget)\n })\n}\n"]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dist/js/home.js vendored

File diff suppressed because one or more lines are too long

2
dist/js/home.js.map vendored

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
@font-face{font-family:fontello;src:url(fontello.eot?fFS2CGH95j);src:url(fontello.eot?fFS2CGH95j#iefix) format("embedded-opentype"),url(fontello.woff2?fFS2CGH95j) format("woff2"),url(fontello.woff?fFS2CGH95j) format("woff"),url(fontello.ttf?fFS2CGH95j) format("truetype"),url(fontello.svg?fFS2CGH95j#fontello) format("svg");font-weight:400;font-style:normal}[class*=" icon-"]:before,[class^=icon-]:before{font-family:fontello;font-style:normal;font-weight:400;speak:none;display:inline-block;text-decoration:inherit;width:1em;margin-right:.2em;text-align:center;font-feature-settings:normal;font-variant:normal;text-transform:none;margin-left:.2em;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-2x:before{font-size:2rem}.icon-archive:before{content:"\e800"}.icon-sharex:before{content:"\e801"}.icon-picture:before{content:"\e802"}.icon-th-list:before{content:"\e803"}.icon-trash:before{content:"\e804"}.icon-cancel:before{content:"\e805"}.icon-arrows-cw:before{content:"\e806"}.icon-plus:before{content:"\e807"}.icon-clipboard:before{content:"\e808"}.icon-login:before{content:"\e809"}.icon-home:before{content:"\e80a"}.icon-gauge:before{content:"\e80b"}.icon-help-circled:before{content:"\e80d"}.icon-github-circled:before{content:"\e80e"}.icon-pencil:before{content:"\e80f"}.icon-terminal:before{content:"\e810"}.icon-hammer:before{content:"\e811"}.icon-block:before{content:"\e812"}.icon-link:before{content:"\e813"}.icon-cog-alt:before{content:"\e814"}.icon-floppy:before{content:"\e815"}.icon-user-plus:before{content:"\e816"}.icon-privatebin:before{content:"\e817"}.icon-upload-cloud:before{content:"\e819"}.icon-th-large:before{content:"\e81a"}.icon-download:before{content:"\e81b"}.icon-gatsby:before{content:"\e81c"}.icon-filter:before{content:"\f0b0"}.icon-docs:before{content:"\f0c5"}.icon-doc-inv:before{content:"\f15b"}.icon-paper-plane:before{content:"\f1d8"}.icon-chrome:before{content:"\f268"}.icon-firefox:before{content:"\f269"}
@font-face{font-family:fontello;src:url(fontello.eot?iDzQ0dov5j);src:url(fontello.eot?iDzQ0dov5j#iefix) format("embedded-opentype"),url(fontello.woff2?iDzQ0dov5j) format("woff2"),url(fontello.woff?iDzQ0dov5j) format("woff"),url(fontello.ttf?iDzQ0dov5j) format("truetype"),url(fontello.svg?iDzQ0dov5j#fontello) format("svg");font-weight:400;font-style:normal}[class*=" icon-"]:before,[class^=icon-]:before{font-family:fontello;font-style:normal;font-weight:400;speak:none;display:inline-block;text-decoration:inherit;width:1em;margin-right:.2em;text-align:center;font-feature-settings:normal;font-variant:normal;text-transform:none;margin-left:.2em;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-2x:before{font-size:2rem}.icon-archive:before{content:"\e800"}.icon-sharex:before{content:"\e801"}.icon-picture:before{content:"\e802"}.icon-th-list:before{content:"\e803"}.icon-trash:before{content:"\e804"}.icon-cancel:before{content:"\e805"}.icon-arrows-cw:before{content:"\e806"}.icon-plus:before{content:"\e807"}.icon-clipboard:before{content:"\e808"}.icon-login:before{content:"\e809"}.icon-home:before{content:"\e80a"}.icon-gauge:before{content:"\e80b"}.icon-video:before{content:"\e80c"}.icon-help-circled:before{content:"\e80d"}.icon-github-circled:before{content:"\e80e"}.icon-pencil:before{content:"\e80f"}.icon-terminal:before{content:"\e810"}.icon-hammer:before{content:"\e811"}.icon-block:before{content:"\e812"}.icon-link:before{content:"\e813"}.icon-cog-alt:before{content:"\e814"}.icon-floppy:before{content:"\e815"}.icon-user-plus:before{content:"\e816"}.icon-privatebin:before{content:"\e817"}.icon-upload-cloud:before{content:"\e819"}.icon-th-large:before{content:"\e81a"}.icon-download:before{content:"\e81b"}.icon-gatsby:before{content:"\e81c"}.icon-filter:before{content:"\f0b0"}.icon-docs:before{content:"\f0c5"}.icon-doc-inv:before{content:"\f15b"}.icon-paper-plane:before{content:"\f1d8"}.icon-chrome:before{content:"\f268"}.icon-firefox:before{content:"\f269"}
/*# sourceMappingURL=fontello.css.map */

View File

@ -1 +1 @@
{"version":3,"sources":["libs/fontello/fontello.css"],"names":[],"mappings":"AAAA,WACE,oBAAuB,CACvB,gCAAmC,CACnC,kQAKuD,CACvD,eAAmB,CACnB,iBACF,CAaA,+CAEE,oBAAuB,CACvB,iBAAkB,CAClB,eAAmB,CACnB,UAAW,CACX,oBAAqB,CACrB,uBAAwB,CACxB,SAAU,CACV,iBAAmB,CACnB,iBAAkB,CAIlB,4BAAoB,CAApB,mBAAoB,CACpB,mBAAoB,CAOpB,gBAAkB,CAMlB,kCAAmC,CACnC,iCAIF,CAEA,gBACE,cACF,CAEA,qBAAwB,eAAiB,CACzC,oBAAuB,eAAiB,CACxC,qBAAwB,eAAiB,CACzC,qBAAwB,eAAiB,CACzC,mBAAsB,eAAiB,CACvC,oBAAuB,eAAiB,CACxC,uBAA0B,eAAiB,CAC3C,kBAAqB,eAAiB,CACtC,uBAA0B,eAAiB,CAC3C,mBAAsB,eAAiB,CACvC,kBAAqB,eAAiB,CACtC,mBAAsB,eAAiB,CACvC,0BAA6B,eAAiB,CAC9C,4BAA+B,eAAiB,CAChD,oBAAuB,eAAiB,CACxC,sBAAyB,eAAiB,CAC1C,oBAAuB,eAAiB,CACxC,mBAAsB,eAAiB,CACvC,kBAAqB,eAAiB,CACtC,qBAAwB,eAAiB,CACzC,oBAAuB,eAAiB,CACxC,uBAA0B,eAAiB,CAC3C,wBAA2B,eAAiB,CAC5C,0BAA6B,eAAiB,CAC9C,sBAAyB,eAAiB,CAC1C,sBAAyB,eAAiB,CAC1C,oBAAuB,eAAiB,CACxC,oBAAuB,eAAiB,CACxC,kBAAqB,eAAiB,CACtC,qBAAwB,eAAiB,CACzC,yBAA4B,eAAiB,CAC7C,oBAAuB,eAAiB,CACxC,qBAAwB,eAAiB","file":"fontello.css","sourcesContent":["@font-face {\n font-family: 'fontello';\n src: url('fontello.eot?fFS2CGH95j');\n src:\n url('fontello.eot?fFS2CGH95j#iefix') format('embedded-opentype'),\n url('fontello.woff2?fFS2CGH95j') format('woff2'),\n url('fontello.woff?fFS2CGH95j') format('woff'),\n url('fontello.ttf?fFS2CGH95j') format('truetype'),\n url('fontello.svg?fFS2CGH95j#fontello') format('svg');\n font-weight: normal;\n font-style: normal\n}\n\n/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */\n/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */\n/*\n@media screen and (-webkit-min-device-pixel-ratio:0) {\n @font-face {\n font-family: 'fontello';\n src: url('fontello.svg?fFS2CGH95j#fontello') format('svg');\n }\n}\n*/\n\n[class^=\"icon-\"]::before,\n[class*=\" icon-\"]::before {\n font-family: \"fontello\";\n font-style: normal;\n font-weight: normal;\n speak: none;\n display: inline-block;\n text-decoration: inherit;\n width: 1em;\n margin-right: 0.2em;\n text-align: center;\n /* opacity: .8; */\n\n /* For safety - reset parent styles, that can break glyph codes */\n font-variant: normal;\n text-transform: none;\n\n /* fix buttons height, for twitter bootstrap */\n /* line-height: 1em; */\n\n /* Animation center compensation - margins should be symmetric */\n /* remove if not needed */\n margin-left: 0.2em;\n\n /* you can be more comfortable with increased icons size */\n /* font-size: 120%; */\n\n /* Font smoothing. That was taken from TWBS */\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n\n /* Uncomment for 3D effect */\n /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */\n}\n\n.icon-2x::before {\n font-size: 2rem\n}\n\n.icon-archive::before { content: '\\e800' } /* '' */\n.icon-sharex::before { content: '\\e801' } /* '' */\n.icon-picture::before { content: '\\e802' } /* '' */\n.icon-th-list::before { content: '\\e803' } /* '' */\n.icon-trash::before { content: '\\e804' } /* '' */\n.icon-cancel::before { content: '\\e805' } /* '' */\n.icon-arrows-cw::before { content: '\\e806' } /* '' */\n.icon-plus::before { content: '\\e807' } /* '' */\n.icon-clipboard::before { content: '\\e808' } /* '' */\n.icon-login::before { content: '\\e809' } /* '' */\n.icon-home::before { content: '\\e80a' } /* '' */\n.icon-gauge::before { content: '\\e80b' } /* '' */\n.icon-help-circled::before { content: '\\e80d' } /* '' */\n.icon-github-circled::before { content: '\\e80e' } /* '' */\n.icon-pencil::before { content: '\\e80f' } /* '' */\n.icon-terminal::before { content: '\\e810' } /* '' */\n.icon-hammer::before { content: '\\e811' } /* '' */\n.icon-block::before { content: '\\e812' } /* '' */\n.icon-link::before { content: '\\e813' } /* '' */\n.icon-cog-alt::before { content: '\\e814' } /* '' */\n.icon-floppy::before { content: '\\e815' } /* '' */\n.icon-user-plus::before { content: '\\e816' } /* '' */\n.icon-privatebin::before { content: '\\e817' } /* '' */\n.icon-upload-cloud::before { content: '\\e819' } /* '' */\n.icon-th-large::before { content: '\\e81a' } /* '' */\n.icon-download::before { content: '\\e81b' } /* '' */\n.icon-gatsby::before { content: '\\e81c' } /* '' */\n.icon-filter::before { content: '\\f0b0' } /* '' */\n.icon-docs::before { content: '\\f0c5' } /* '' */\n.icon-doc-inv::before { content: '\\f15b' } /* '' */\n.icon-paper-plane::before { content: '\\f1d8' } /* '' */\n.icon-chrome::before { content: '\\f268' } /* '' */\n.icon-firefox::before { content: '\\f269' } /* '' */\n"]}
{"version":3,"sources":["libs/fontello/fontello.css"],"names":[],"mappings":"AAAA,WACE,oBAAuB,CACvB,gCAAmC,CACnC,kQAKuD,CACvD,eAAmB,CACnB,iBACF,CAaA,+CAEE,oBAAuB,CACvB,iBAAkB,CAClB,eAAmB,CACnB,UAAW,CACX,oBAAqB,CACrB,uBAAwB,CACxB,SAAU,CACV,iBAAmB,CACnB,iBAAkB,CAIlB,4BAAoB,CAApB,mBAAoB,CACpB,mBAAoB,CAOpB,gBAAkB,CAMlB,kCAAmC,CACnC,iCAIF,CAEA,gBACE,cACF,CAEA,qBAAwB,eAAiB,CACzC,oBAAuB,eAAiB,CACxC,qBAAwB,eAAiB,CACzC,qBAAwB,eAAiB,CACzC,mBAAsB,eAAiB,CACvC,oBAAuB,eAAiB,CACxC,uBAA0B,eAAiB,CAC3C,kBAAqB,eAAiB,CACtC,uBAA0B,eAAiB,CAC3C,mBAAsB,eAAiB,CACvC,kBAAqB,eAAiB,CACtC,mBAAsB,eAAiB,CACvC,mBAAqB,eAAkB,CACvC,0BAA6B,eAAiB,CAC9C,4BAA+B,eAAiB,CAChD,oBAAuB,eAAiB,CACxC,sBAAyB,eAAiB,CAC1C,oBAAuB,eAAiB,CACxC,mBAAsB,eAAiB,CACvC,kBAAqB,eAAiB,CACtC,qBAAwB,eAAiB,CACzC,oBAAuB,eAAiB,CACxC,uBAA0B,eAAiB,CAC3C,wBAA2B,eAAiB,CAC5C,0BAA6B,eAAiB,CAC9C,sBAAyB,eAAiB,CAC1C,sBAAyB,eAAiB,CAC1C,oBAAuB,eAAiB,CACxC,oBAAuB,eAAiB,CACxC,kBAAqB,eAAiB,CACtC,qBAAwB,eAAiB,CACzC,yBAA4B,eAAiB,CAC7C,oBAAuB,eAAiB,CACxC,qBAAwB,eAAiB","file":"fontello.css","sourcesContent":["@font-face {\n font-family: 'fontello';\n src: url('fontello.eot?iDzQ0dov5j');\n src:\n url('fontello.eot?iDzQ0dov5j#iefix') format('embedded-opentype'),\n url('fontello.woff2?iDzQ0dov5j') format('woff2'),\n url('fontello.woff?iDzQ0dov5j') format('woff'),\n url('fontello.ttf?iDzQ0dov5j') format('truetype'),\n url('fontello.svg?iDzQ0dov5j#fontello') format('svg');\n font-weight: normal;\n font-style: normal\n}\n\n/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */\n/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */\n/*\n@media screen and (-webkit-min-device-pixel-ratio:0) {\n @font-face {\n font-family: 'fontello';\n src: url('fontello.svg?iDzQ0dov5j#fontello') format('svg');\n }\n}\n*/\n\n[class^=\"icon-\"]::before,\n[class*=\" icon-\"]::before {\n font-family: \"fontello\";\n font-style: normal;\n font-weight: normal;\n speak: none;\n display: inline-block;\n text-decoration: inherit;\n width: 1em;\n margin-right: 0.2em;\n text-align: center;\n /* opacity: .8; */\n\n /* For safety - reset parent styles, that can break glyph codes */\n font-variant: normal;\n text-transform: none;\n\n /* fix buttons height, for twitter bootstrap */\n /* line-height: 1em; */\n\n /* Animation center compensation - margins should be symmetric */\n /* remove if not needed */\n margin-left: 0.2em;\n\n /* you can be more comfortable with increased icons size */\n /* font-size: 120%; */\n\n /* Font smoothing. That was taken from TWBS */\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n\n /* Uncomment for 3D effect */\n /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */\n}\n\n.icon-2x::before {\n font-size: 2rem\n}\n\n.icon-archive::before { content: '\\e800' } /* '' */\n.icon-sharex::before { content: '\\e801' } /* '' */\n.icon-picture::before { content: '\\e802' } /* '' */\n.icon-th-list::before { content: '\\e803' } /* '' */\n.icon-trash::before { content: '\\e804' } /* '' */\n.icon-cancel::before { content: '\\e805' } /* '' */\n.icon-arrows-cw::before { content: '\\e806' } /* '' */\n.icon-plus::before { content: '\\e807' } /* '' */\n.icon-clipboard::before { content: '\\e808' } /* '' */\n.icon-login::before { content: '\\e809' } /* '' */\n.icon-home::before { content: '\\e80a' } /* '' */\n.icon-gauge::before { content: '\\e80b' } /* '' */\n.icon-video:before { content: '\\e80c'; } /* '' */\n.icon-help-circled::before { content: '\\e80d' } /* '' */\n.icon-github-circled::before { content: '\\e80e' } /* '' */\n.icon-pencil::before { content: '\\e80f' } /* '' */\n.icon-terminal::before { content: '\\e810' } /* '' */\n.icon-hammer::before { content: '\\e811' } /* '' */\n.icon-block::before { content: '\\e812' } /* '' */\n.icon-link::before { content: '\\e813' } /* '' */\n.icon-cog-alt::before { content: '\\e814' } /* '' */\n.icon-floppy::before { content: '\\e815' } /* '' */\n.icon-user-plus::before { content: '\\e816' } /* '' */\n.icon-privatebin::before { content: '\\e817' } /* '' */\n.icon-upload-cloud::before { content: '\\e819' } /* '' */\n.icon-th-large::before { content: '\\e81a' } /* '' */\n.icon-download::before { content: '\\e81b' } /* '' */\n.icon-gatsby::before { content: '\\e81c' } /* '' */\n.icon-filter::before { content: '\\f0b0' } /* '' */\n.icon-docs::before { content: '\\f0c5' } /* '' */\n.icon-doc-inv::before { content: '\\f15b' } /* '' */\n.icon-paper-plane::before { content: '\\f1d8' } /* '' */\n.icon-chrome::before { content: '\\f268' } /* '' */\n.icon-firefox::before { content: '\\f269' } /* '' */\n"]}

View File

@ -11,6 +11,13 @@ const sourcemaps = require('gulp-sourcemaps')
const stylelint = require('gulp-stylelint')
const terser = require('gulp-terser')
// Put built files for development on a Git-ignored directory.
// This will prevent IDE's Git from unnecessarily
// building diff's during development.
const dist = process.env.NODE_ENV === 'development'
? './dist-dev'
: './dist'
/** TASKS: LINT */
gulp.task('lint:js', () => {
@ -34,21 +41,21 @@ gulp.task('lint', gulp.parallel('lint:js', 'lint:css'))
gulp.task('clean:css', () => {
return del([
'./dist/**/*.css',
'./dist/**/*.css.map'
`${dist}/**/*.css`,
`${dist}/**/*.css.map`
])
})
gulp.task('clean:js', () => {
return del([
'./dist/**/*.js',
'./dist/**/*.js.map'
`${dist}/**/*.js`,
`${dist}/**/*.js.map`
])
})
gulp.task('clean:rest', () => {
return del([
'./dist/*'
`${dist}/*`
])
})
@ -69,7 +76,7 @@ gulp.task('build:css', () => {
.pipe(sourcemaps.init())
.pipe(postcss(plugins))
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('./dist'))
.pipe(gulp.dest(dist))
})
gulp.task('build:js', () => {
@ -79,7 +86,7 @@ gulp.task('build:js', () => {
// Minify on production
.pipe(gulpif(process.env.NODE_ENV !== 'development', terser()))
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('./dist'))
.pipe(gulp.dest(dist))
})
gulp.task('build', gulp.parallel('build:css', 'build:js'))

View File

@ -117,25 +117,9 @@ safe.use('/api', api)
if (!await paths.access(customPage).catch(() => true))
safe.get(`/${page === 'home' ? '' : page}`, (req, res, next) => res.sendFile(customPage))
else if (page === 'home')
safe.get('/', (req, res, next) => res.render('home', {
maxSize: parseInt(config.uploads.maxSize),
urlMaxSize: parseInt(config.uploads.urlMaxSize),
urlDisclaimerMessage: config.uploads.urlDisclaimerMessage,
urlExtensionsFilterMode: config.uploads.urlExtensionsFilterMode,
urlExtensionsFilter: config.uploads.urlExtensionsFilter,
temporaryUploadAges: Array.isArray(config.uploads.temporaryUploadAges) &&
config.uploads.temporaryUploadAges.length,
gitHash: utils.gitHash
}))
else if (page === 'faq')
safe.get('/faq', (req, res, next) => res.render('faq', {
whitelist: config.extensionsFilterMode === 'whitelist',
extensionsFilter: config.extensionsFilter,
noJsMaxSize: parseInt(config.cloudflare.noJsMaxSize) < parseInt(config.uploads.maxSize),
chunkSize: parseInt(config.uploads.chunkSize)
}))
safe.get('/', (req, res, next) => res.render(page, { config, gitHash: utils.gitHash }))
else
safe.get(`/${page}`, (req, res, next) => res.render(page))
safe.get(`/${page}`, (req, res, next) => res.render(page, { config }))
}
// Error pages

View File

@ -68,6 +68,6 @@
"gulp-terser": "^1.2.0",
"postcss-preset-env": "^6.7.0",
"stylelint": "^10.1.0",
"stylelint-config-standard": "^18.3.0"
"stylelint-config-standard": "^19.0.0"
}
}

View File

@ -235,6 +235,12 @@
"search": [
"gatsby"
]
},
{
"uid": "31accb20e8819b200c297df608e68830",
"css": "video",
"code": 59404,
"src": "elusive"
}
]
}

Binary file not shown.

View File

@ -30,6 +30,8 @@
<glyph glyph-name="gauge" unicode="&#xe80b;" d="M214 207q0 30-21 51t-50 21-51-21-21-51 21-50 51-21 50 21 21 50z m107 250q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m239-268l57 213q3 14-5 27t-21 16-27-3-17-22l-56-213q-33-3-60-25t-35-55q-11-43 11-81t66-50 81 11 50 66q9 33-4 65t-40 51z m369 18q0 30-21 51t-51 21-50-21-21-51 21-50 50-21 51 21 21 50z m-358 357q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m250-107q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m179-250q0-145-79-269-10-17-30-17h-782q-20 0-30 17-79 123-79 269 0 102 40 194t106 160 160 107 194 39 194-39 160-107 106-160 40-194z" horiz-adv-x="1000" />
<glyph glyph-name="video" unicode="&#xe80c;" d="M0-29l0 758 1000 0 0-758-1000 0z m123 123l754 0 0 512-754 0 0-512z m266 82l0 340 293-170z" horiz-adv-x="1000" />
<glyph glyph-name="help-circled" unicode="&#xe80d;" d="M500 82v107q0 8-5 13t-13 5h-107q-8 0-13-5t-5-13v-107q0-8 5-13t13-5h107q8 0 13 5t5 13z m143 375q0 49-31 91t-77 65-95 23q-136 0-207-119-9-13 4-24l74-55q4-4 10-4 9 0 14 7 30 38 48 51 19 14 48 14 27 0 48-15t21-33q0-21-11-34t-38-25q-35-15-65-48t-29-70v-20q0-8 5-13t13-5h107q8 0 13 5t5 13q0 10 12 27t30 28q18 10 28 16t25 19 25 27 16 34 7 45z m214-107q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
<glyph glyph-name="github-circled" unicode="&#xe80e;" d="M429 779q116 0 215-58t156-156 57-215q0-140-82-252t-211-155q-15-3-22 4t-7 17q0 1 0 43t0 75q0 54-29 79 32 3 57 10t53 22 45 37 30 58 11 84q0 67-44 115 21 51-4 114-16 5-46-6t-51-25l-21-13q-52 15-107 15t-108-15q-8 6-23 15t-47 22-47 7q-25-63-5-114-44-48-44-115 0-47 12-83t29-59 45-37 52-22 57-10q-21-20-27-58-12-5-25-8t-32-3-36 12-31 35q-11 18-27 29t-28 14l-11 1q-12 0-16-2t-3-7 5-8 7-6l4-3q12-6 24-21t18-29l6-13q7-21 24-34t37-17 39-3 31 1l13 3q0-22 0-50t1-30q0-10-8-17t-22-4q-129 43-211 155t-82 252q0 117 58 215t155 156 216 58z m-267-616q2 4-3 7-6 1-8-1-1-4 4-7 5-3 7 1z m18-19q4 3-1 9-6 5-9 2-4-3 1-9 5-6 9-2z m16-25q6 4 0 11-4 7-9 3-5-3 0-10t9-4z m24-23q4 4-2 10-7 7-11 2-5-5 2-11 6-6 11-1z m32-14q1 6-8 9-8 2-10-4t7-9q8-3 11 4z m35-3q0 7-10 6-9 0-9-6 0-7 10-6 9 0 9 6z m32 5q-1 7-10 5-9-1-8-8t10-4 8 7z" horiz-adv-x="857.1" />

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -5,8 +5,6 @@ const utils = require('./../controllers/utilsController')
const config = require('./../config')
const db = require('knex')(config.database)
const homeDomain = config.homeDomain || config.domain
routes.get('/a/:identifier', async (req, res, next) => {
const identifier = req.params.identifier
if (identifier === undefined)
@ -20,6 +18,7 @@ routes.get('/a/:identifier', async (req, res, next) => {
identifier,
enabled: 1
})
.select('id', 'name', 'identifier', 'editedAt', 'download', 'public', 'description')
.first()
if (!album)
@ -30,44 +29,70 @@ routes.get('/a/:identifier', async (req, res, next) => {
description: 'This album is not available for public.'
})
const nojs = req.query.nojs !== undefined
// Cache ID - we initialize a separate cache for No-JS version
const cacheid = nojs ? `${album.id}-nojs` : album.id
if (!utils.albumsCache[cacheid])
utils.albumsCache[cacheid] = {
cache: null,
generating: false,
// Cache will actually be deleted after the album has been updated,
// so storing this timestamp may be redundant, but just in case.
generatedAt: 0
}
if (!utils.albumsCache[cacheid].cache && utils.albumsCache[cacheid].generating)
return res.json({
success: false,
description: 'This album is still generating its public page.'
})
else if ((album.editedAt < utils.albumsCache[cacheid].generatedAt) || utils.albumsCache[cacheid].generating)
return res.send(utils.albumsCache[cacheid].cache)
// Use current timestamp to make sure cache is invalidated
// when an album is edited during this generation process.
utils.albumsCache[cacheid].generating = true
utils.albumsCache[cacheid].generatedAt = Math.floor(Date.now() / 1000)
const files = await db.table('files')
.select('name', 'size')
.where('albumid', album.id)
.orderBy('id', 'DESC')
let thumb = ''
const basedomain = config.domain
album.thumb = ''
album.totalSize = 0
let totalSize = 0
for (const file of files) {
file.file = `${basedomain}/${file.name}`
file.extname = path.extname(file.name).toLowerCase()
album.totalSize += parseInt(file.size)
file.extname = path.extname(file.name)
if (utils.mayGenerateThumb(file.extname)) {
file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -file.extname.length)}.png`
/*
If thumbnail for album is still not set, set it to current file's full URL.
A potential improvement would be to let the user set a specific image as an album cover.
*/
if (thumb === '') thumb = file.file
file.thumb = `thumbs/${file.name.slice(0, -file.extname.length)}.png`
// If thumbnail for album is still not set, set it to current file's full URL.
// A potential improvement would be to let the user set a specific image as an album cover.
if (!album.thumb) album.thumb = file.name
}
totalSize += parseInt(file.size)
}
return res.render('album', {
title: album.name,
description: album.description ? album.description.replace(/\n/g, '<br>') : null,
count: files.length,
thumb,
files,
identifier,
generateZips: config.uploads.generateZips,
downloadLink: album.download === 0
? null
: `${homeDomain}/api/album/zip/${album.identifier}?v=${album.editedAt}`,
editedAt: album.editedAt,
url: `${homeDomain}/a/${album.identifier}`,
totalSize,
nojs: req.query.nojs !== undefined
album.description = album.description
? album.description.replace(/\n/g, '<br>')
: null
album.downloadLink = album.download === 0
? null
: `api/album/zip/${album.identifier}?v=${album.editedAt}`
album.url = `a/${album.identifier}`
return res.render('album', { config, album, files, nojs }, (error, html) => {
utils.albumsCache[cacheid].cache = error ? null : html
utils.albumsCache[cacheid].generating = false
// Express should already send error to the next handler
if (error) return
return res.send(utils.albumsCache[cacheid].cache)
})
})

View File

@ -3,39 +3,21 @@ const uploadController = require('./../controllers/uploadController')
const utils = require('./../controllers/utilsController')
const config = require('./../config')
const renderOptions = {
uploadDisabled: false,
maxFileSize: parseInt(config.cloudflare.noJsMaxSize || config.uploads.maxSize)
}
if (config.private)
if (config.enableUserAccounts) {
renderOptions.uploadDisabled = 'Anonymous upload is disabled.'
} else {
renderOptions.uploadDisabled = 'Running in private mode.'
}
routes.get('/nojs', async (req, res, next) => {
const options = { renderOptions }
options.gitHash = utils.gitHash
return res.render('nojs', options)
return res.render('nojs', { config, gitHash: utils.gitHash })
})
routes.post('/nojs', (req, res, next) => {
res._json = res.json
res.json = (...args) => {
const result = args[0]
const options = { renderOptions }
options.gitHash = utils.utils
options.errorMessage = result.success ? '' : (result.description || 'An unexpected error occurred.')
options.files = result.files || [{}]
return res.render('nojs', options)
return res.render('nojs', {
config,
gitHash: utils.gitHash,
errorMessage: result.success ? '' : (result.description || 'An unexpected error occurred.'),
files: result.files || [{}]
})
}
return uploadController.upload(req, res, next)
})

View File

@ -33,6 +33,21 @@ body {
background: none
}
.menu-list a.is-loading::after {
animation: spinAround 0.5s infinite linear;
border: 2px solid #dbdbdb;
border-radius: 290486px;
border-right-color: transparent;
border-top-color: transparent;
content: "";
display: block;
height: 1em;
width: 1em;
right: calc(0% + (1em / 2));
top: calc(50% - (1em / 2));
position: absolute !important
}
ul#albumsContainer {
border-left: 0;
padding-left: 0
@ -155,7 +170,7 @@ li[data-action="page-ellipsis"] {
text-decoration: line-through
}
#menu.is-loading li a {
#menu.is-loading .menu-list a {
cursor: progress
}

View File

@ -124,21 +124,6 @@ code,
box-shadow: 0 20px 60px rgba(10, 10, 10, 0.05), 0 5px 10px rgba(10, 10, 10, 0.1), 0 1px 1px rgba(10, 10, 10, 0.2)
}
.menu-list a.is-loading::after {
animation: spinAround 0.5s infinite linear;
border: 2px solid #dbdbdb;
border-radius: 290486px;
border-right-color: transparent;
border-top-color: transparent;
content: "";
display: block;
height: 1em;
width: 1em;
right: calc(0% + (1em / 2));
top: calc(50% - (1em / 2));
position: absolute !important
}
/* https://github.com/philipwalton/flexbugs#flexbug-3 */
.hero.is-fullheight > .hero-body {
min-height: 100vh;

View File

@ -1,8 +1,9 @@
.image-container {
display: flex;
flex: none;
position: relative;
width: 224px;
height: 224px;
margin: 9px;
margin: 0.75rem;
padding: 12px;
background-color: #31363b;
overflow: hidden;

View File

@ -67,15 +67,18 @@ window.onload = () => {
page.pass = document.querySelector('#pass')
// Prevent default form's submit action
document.querySelector('#authForm').addEventListener('submit', event => {
const form = document.querySelector('#authForm')
form.addEventListener('submit', event => {
event.preventDefault()
})
document.querySelector('#loginBtn').addEventListener('click', event => {
if (!form.checkValidity()) return
page.do('login', event.currentTarget)
})
document.querySelector('#registerBtn').addEventListener('click', event => {
if (!form.checkValidity()) return
page.do('register', event.currentTarget)
})
}

View File

@ -84,7 +84,10 @@ const page = {
videoExts: ['.webm', '.mp4', '.wmv', '.avi', '.mov', '.mkv'],
isTriggerLoading: null,
fadingIn: null
fadingIn: null,
albumTitleMaxLength: 280,
albumDescMaxLength: 4000
}
page.preparePage = () => {
@ -270,8 +273,8 @@ page.domClick = event => {
return page.deleteUpload(id)
case 'bulk-delete-uploads':
return page.bulkDeleteUploads()
case 'display-thumbnail':
return page.displayThumbnail(id)
case 'display-preview':
return page.displayPreview(id)
case 'submit-album':
return page.submitAlbum(element)
case 'edit-album':
@ -495,11 +498,21 @@ page.getUploads = (params = {}) => {
if (files[i].thumb)
files[i].thumb = `${basedomain}/${files[i].thumb}`
// Determine types
files[i].type = 'other'
const exec = /.[\w]+(\?|$)/.exec(files[i].file)
const extname = exec && exec[0] ? exec[0].toLowerCase() : null
if (page.imageExts.includes(extname))
files[i].type = 'picture'
else if (page.videoExts.includes(extname))
files[i].type = 'video'
// Cache bare minimum data for thumbnails viewer
page.cache.uploads[files[i].id] = {
name: files[i].name,
thumb: files[i].thumb,
original: files[i].file
original: files[i].file,
type: files[i].type
}
// Prettify
@ -542,7 +555,7 @@ page.getUploads = (params = {}) => {
for (let i = 0; i < files.length; i++) {
const upload = files[i]
const div = document.createElement('div')
div.className = 'image-container column is-narrow is-relative'
div.className = 'image-container column'
div.dataset.id = upload.id
if (upload.thumb !== undefined)
@ -554,9 +567,9 @@ page.getUploads = (params = {}) => {
<input type="checkbox" class="checkbox" title="Select" data-index="${i}" data-action="select"${upload.selected ? ' checked' : ''}>
<div class="controls">
${upload.thumb ? `
<a class="button is-small is-primary" title="View thumbnail" data-action="display-thumbnail">
<a class="button is-small is-primary" title="Display preview" data-action="display-preview">
<span class="icon">
<i class="icon-picture"></i>
<i class="${upload.type !== 'other' ? `icon-${upload.type}` : 'icon-doc-inv'}"></i>
</span>
</a>` : ''}
<a class="button is-small is-info clipboard-js" title="Copy link to clipboard" data-clipboard-text="${upload.file}">
@ -576,7 +589,7 @@ page.getUploads = (params = {}) => {
</a>
</div>
<div class="details">
<p><span class="name" title="${upload.file}">${upload.name}</span></p>
<p><span class="name">${upload.name}</span></p>
<p>${upload.appendix ? `<span>${upload.appendix}</span> ` : ''}${upload.prettyBytes}</p>
${hasExpiryDateColumn && upload.prettyExpiryDate ? `
<p class="expirydate">EXP: ${upload.prettyExpiryDate}</p>` : ''}
@ -629,9 +642,9 @@ page.getUploads = (params = {}) => {
<td>${upload.prettyDate}</td>
${hasExpiryDateColumn ? `<td>${upload.prettyExpiryDate || '-'}</td>` : ''}
<td class="controls has-text-right">
<a class="button is-small is-primary" title="${upload.thumb ? 'View thumbnail' : 'File doesn\'t have thumbnail'}" data-action="display-thumbnail"${upload.thumb ? '' : ' disabled'}>
<a class="button is-small is-primary" title="${upload.thumb ? 'Display preview' : 'File can\'t be previewed'}" data-action="display-preview"${upload.thumb ? '' : ' disabled'}>
<span class="icon">
<i class="icon-picture"></i>
<i class="${upload.type !== 'other' ? `icon-${upload.type}` : 'icon-doc-inv'}"></i>
</span>
</a>
<a class="button is-small is-info clipboard-js" title="Copy link to clipboard" data-clipboard-text="${upload.file}">
@ -688,7 +701,7 @@ page.setUploadsView = (view, element) => {
}, page.views[page.currentView]))
}
page.displayThumbnail = id => {
page.displayPreview = id => {
const file = page.cache.uploads[id]
if (!file.thumb) return
@ -1254,13 +1267,15 @@ page.getAlbums = (params = {}) => {
<form class="prevent-default">
<div class="field">
<div class="control">
<input id="albumName" class="input" type="text" placeholder="Name">
<input id="albumName" class="input" type="text" placeholder="Name" maxlength="${page.albumTitleMaxLength}">
</div>
<p class="help">Max length is ${page.albumTitleMaxLength} characters.</p>
</div>
<div class="field">
<div class="control">
<textarea id="albumDescription" class="textarea" placeholder="Description" rows="1"></textarea>
<textarea id="albumDescription" class="textarea" placeholder="Description" rows="1" maxlength="${page.albumDescMaxLength}"></textarea>
</div>
<p class="help">Max length is ${page.albumDescMaxLength} characters.</p>
</div>
<div class="field">
<div class="control">
@ -1360,13 +1375,15 @@ page.editAlbum = id => {
div.innerHTML = `
<div class="field">
<div class="controls">
<input id="swalName" class="input" type="text" placeholder="Name" value="${album.name || ''}">
<input id="swalName" class="input" type="text" placeholder="Name" maxlength="${page.albumTitleMaxLength}" value="${(album.name || '').substring(0, page.albumTitleMaxLength)}">
</div>
<p class="help">Max length is ${page.albumTitleMaxLength} characters.</p>
</div>
<div class="field">
<div class="control">
<textarea id="swalDescription" class="textarea" placeholder="Description" rows="2">${album.description || ''}</textarea>
<textarea id="swalDescription" class="textarea" placeholder="Description" rows="2" maxlength="${page.albumDescMaxLength}">${(album.description || '').substring(0, page.albumDescMaxLength)}</textarea>
</div>
<p class="help">Max length is ${page.albumDescMaxLength} characters.</p>
</div>
<div class="field">
<div class="control">
@ -1488,8 +1505,8 @@ page.deleteAlbum = id => {
page.submitAlbum = element => {
page.updateTrigger(element, 'loading')
axios.post('api/albums', {
name: document.querySelector('#albumName').value,
description: document.querySelector('#albumDescription').value
name: document.querySelector('#albumName').value.trim(),
description: document.querySelector('#albumDescription').value.trim()
}).then(response => {
if (!response) return

View File

@ -5,6 +5,7 @@ const lsKeys = {
chunkSize: 'chunkSize',
parallelUploads: 'parallelUploads',
uploadsHistoryOrder: 'uploadsHistoryOrder',
previewImages: 'previewImages',
fileLength: 'fileLength',
uploadAge: 'uploadAge'
}
@ -25,7 +26,7 @@ const page = {
album: null,
parallelUploads: null,
uploadsHistoryOrder: null,
previewImages: null,
fileLength: null,
uploadAge: null,
@ -42,7 +43,13 @@ const page = {
clipboardJS: null,
lazyLoad: null,
imageExtensions: ['.webp', '.jpg', '.jpeg', '.bmp', '.gif', '.png', '.svg']
// Include BMP for uploads preview only, cause the real images will be used
// Sharp isn't capable of making their thumbnails for dashboard and album public pages
imageExts: ['.webp', '.jpg', '.jpeg', '.bmp', '.gif', '.png', '.tiff', '.tif', '.svg'],
videoExts: ['.webm', '.mp4', '.wmv', '.avi', '.mov', '.mkv'],
albumTitleMaxLength: 280,
albumDescMaxLength: 4000
}
// Error handler for all API requests on init
@ -471,25 +478,34 @@ page.updateTemplate = (file, response) => {
clipboard.parentElement.classList.remove('is-hidden')
const exec = /.[\w]+(\?|$)/.exec(response.url)
if (exec && exec[0] && page.imageExtensions.includes(exec[0].toLowerCase())) {
const img = file.previewElement.querySelector('img')
img.setAttribute('alt', response.name || '')
img.dataset.src = response.url
img.classList.remove('is-hidden')
img.onerror = event => {
// Hide image elements that fail to load
// Consequently include WEBP in browsers that do not have WEBP support (e.i. IE)
event.currentTarget.classList.add('is-hidden')
const extname = exec && exec[0]
? exec[0].toLowerCase()
: null
if (page.imageExts.includes(extname))
if (page.previewImages) {
const img = file.previewElement.querySelector('img')
img.setAttribute('alt', response.name || '')
img.dataset.src = response.url
img.classList.remove('is-hidden')
img.onerror = event => {
// Hide image elements that fail to load
// Consequently include WEBP in browsers that do not have WEBP support (e.i. IE)
event.currentTarget.classList.add('is-hidden')
page.updateTemplateIcon(file.previewElement, 'icon-picture')
}
page.lazyLoad.update(file.previewElement.querySelectorAll('img'))
} else {
page.updateTemplateIcon(file.previewElement, 'icon-picture')
}
page.lazyLoad.update(file.previewElement.querySelectorAll('img'))
} else {
else if (page.videoExts.includes(extname))
page.updateTemplateIcon(file.previewElement, 'icon-video')
else
page.updateTemplateIcon(file.previewElement, 'icon-doc-inv')
}
if (response.expirydate) {
const expiryDate = file.previewElement.querySelector('.expiry-date')
expiryDate.innerHTML = `Expiry date: ${page.getPrettyDate(new Date(response.expirydate * 1000))}`
expiryDate.innerHTML = `EXP: ${page.getPrettyDate(new Date(response.expirydate * 1000))}`
expiryDate.classList.remove('is-hidden')
}
}
@ -499,13 +515,15 @@ page.createAlbum = () => {
div.innerHTML = `
<div class="field">
<div class="controls">
<input id="swalName" class="input" type="text" placeholder="Name">
<input id="swalName" class="input" type="text" placeholder="Name" maxlength="${page.albumTitleMaxLength}">
</div>
<p class="help">Max length is ${page.albumTitleMaxLength} characters.</p>
</div>
<div class="field">
<div class="control">
<textarea id="swalDescription" class="textarea" placeholder="Description" rows="2"></textarea>
<textarea id="swalDescription" class="textarea" placeholder="Description" rows="2" maxlength="${page.albumDescMaxLength}"></textarea>
</div>
<p class="help">Max length is ${page.albumDescMaxLength} characters.</p>
</div>
<div class="field">
<div class="control">
@ -665,11 +683,13 @@ page.prepareUploadConfig = () => {
uploadFields[i].classList.add('is-reversed')
}
page.previewImages = localStorage[lsKeys.previewImages] !== '0'
document.querySelector('#saveConfig').addEventListener('click', () => {
if (!form.checkValidity())
return
const prefKeys = ['siBytes', 'uploadsHistoryOrder', 'uploadAge']
const prefKeys = ['siBytes', 'uploadsHistoryOrder', 'previewImages', 'uploadAge']
for (let i = 0; i < prefKeys.length; i++) {
const value = form.elements[prefKeys[i]].value
if (value !== 'default' && value !== fallback[prefKeys[i]])

View File

@ -1,12 +1,12 @@
@font-face {
font-family: 'fontello';
src: url('fontello.eot?fFS2CGH95j');
src: url('fontello.eot?iDzQ0dov5j');
src:
url('fontello.eot?fFS2CGH95j#iefix') format('embedded-opentype'),
url('fontello.woff2?fFS2CGH95j') format('woff2'),
url('fontello.woff?fFS2CGH95j') format('woff'),
url('fontello.ttf?fFS2CGH95j') format('truetype'),
url('fontello.svg?fFS2CGH95j#fontello') format('svg');
url('fontello.eot?iDzQ0dov5j#iefix') format('embedded-opentype'),
url('fontello.woff2?iDzQ0dov5j') format('woff2'),
url('fontello.woff?iDzQ0dov5j') format('woff'),
url('fontello.ttf?iDzQ0dov5j') format('truetype'),
url('fontello.svg?iDzQ0dov5j#fontello') format('svg');
font-weight: normal;
font-style: normal
}
@ -17,7 +17,7 @@
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'fontello';
src: url('fontello.svg?fFS2CGH95j#fontello') format('svg');
src: url('fontello.svg?iDzQ0dov5j#fontello') format('svg');
}
}
*/
@ -73,6 +73,7 @@
.icon-login::before { content: '\e809' } /* '' */
.icon-home::before { content: '\e80a' } /* '' */
.icon-gauge::before { content: '\e80b' } /* '' */
.icon-video:before { content: '\e80c'; } /* '' */
.icon-help-circled::before { content: '\e80d' } /* '' */
.icon-github-circled::before { content: '\e80e' } /* '' */
.icon-pencil::before { content: '\e80f' } /* '' */

View File

@ -2,7 +2,7 @@
Normal priority:
* [ ] Improve performance of album public pages, and maybe paginate them.
* [x] Improve performance of album public pages, ~~and maybe paginate them~~.
* [x] Use [native lazy-load tag](https://web.dev/native-lazy-loading) on nojs album pages.
* [ ] Use incremental version numbering instead of randomized strings.
* [ ] Use versioning in APIs, somehow.

View File

@ -1,5 +1,4 @@
{% set name = "safe.fiery.me" %}
{% set root = "https://safe.fiery.me" %}
{% set motto = "A small safe worth protecting." %}
{% set description = "A pomf-like file uploading service that doesn't suck." %}
{% set keywords = "upload,lolisafe,file,images,hosting,bobby,fiery" %}
@ -16,9 +15,9 @@
v3: CSS and JS files (libs such as bulma, lazyload, etc).
v4: Renders in /public/render/* directories (to be used by render.js).
#}
{% set v1 = "GqfmYYUziT" %}
{% set v1 = "iDzQ0dov5j" %}
{% set v2 = "hiboQUzAzp" %}
{% set v3 = "GqfmYYUziT" %}
{% set v3 = "iDzQ0dov5j" %}
{% set v4 = "S3TAWpPeFS" %}
{#

View File

@ -1,4 +1,8 @@
{% import '_globals.njk' as globals %}
{# Set root domain here to inherit values from config file #}
{% set root = config.homeDomain or config.domain %}
<!DOCTYPE html>
<html lang="en">
<head>
@ -8,7 +12,11 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>{{ title | default(globals.name + ' &#8211; ' + globals.motto) | safe }}</title>
{% if title -%}
<title>{{ title + ' | ' + globals.name }}</title>
{%- else -%}
<title>{{ globals.name + ' ' + globals.motto }}</title>
{%- endif %}
{% block stylesheets %}
<!-- Stylesheets -->
@ -18,36 +26,36 @@
{% block opengraph %}
<!-- Open Graph tags -->
<meta property="og:url" content="{{ globals.root }}" />
<meta property="og:url" content="{{ root }}" />
<meta property="og:type" content="website" />
<meta property="og:title" content="{{ globals.name }} &#8211; {{ globals.motto }}" />
<meta property="og:title" content="{{ globals.name }} {{ globals.motto }}" />
<meta property="og:description" content="{{ globals.description }}" />
<meta property="og:image" content="{{ globals.root }}/icons/600px.png?v={{ globals.v2 }}" />
<meta property="og:image" content="{{ root }}/icons/600px.png?v={{ globals.v2 }}" />
<meta property="og:image:width" content="600" />
<meta property="og:image:height" content="600" />
<meta property="og:image" content="{{ globals.root }}/images/fb_share.png?v={{ globals.v2 }}" />
<meta property="og:image" content="{{ root }}/images/fb_share.png?v={{ globals.v2 }}" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:locale" content="en_US" />
<!-- Twitter Card tags -->
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="{{ globals.name }} &#8211; {{ globals.motto }}">
<meta name="twitter:title" content="{{ globals.name }} {{ globals.motto }}">
<meta name="twitter:description" content="{{ globals.description }}">
<meta name="twitter:image" content="{{ globals.root }}/icons/600px.png?v={{ globals.v2 }}">
<meta name="twitter:image" content="{{ root }}/icons/600px.png?v={{ globals.v2 }}">
{% endblock %}
<!-- Icons, configs, etcetera -->
<link rel="icon" href="{{ globals.root }}/icons/32pxr.png?v={{ globals.v2 }}" sizes="32x32">
<link rel="icon" href="{{ globals.root }}/icons/96pxr.png?v={{ globals.v2 }}" sizes="96x96">
<link rel="apple-touch-icon" href="{{ globals.root }}/icons/120px.png?v={{ globals.v2 }}" sizes="120x120">
<link rel="apple-touch-icon" href="{{ globals.root }}/icons/152px.png?v={{ globals.v2 }}" sizes="152x152">
<link rel="apple-touch-icon" href="{{ globals.root }}/icons/167px.png?v={{ globals.v2 }}" sizes="167x167">
<link rel="apple-touch-icon" href="{{ globals.root }}/icons/180px.png?v={{ globals.v2 }}" sizes="180x180">
<link rel="manifest" href="{{ globals.root }}/icons/manifest.json?v={{ globals.v2 }}">
<link rel="icon" href="{{ root }}/icons/32pxr.png?v={{ globals.v2 }}" sizes="32x32">
<link rel="icon" href="{{ root }}/icons/96pxr.png?v={{ globals.v2 }}" sizes="96x96">
<link rel="apple-touch-icon" href="{{ root }}/icons/120px.png?v={{ globals.v2 }}" sizes="120x120">
<link rel="apple-touch-icon" href="{{ root }}/icons/152px.png?v={{ globals.v2 }}" sizes="152x152">
<link rel="apple-touch-icon" href="{{ root }}/icons/167px.png?v={{ globals.v2 }}" sizes="167x167">
<link rel="apple-touch-icon" href="{{ root }}/icons/180px.png?v={{ globals.v2 }}" sizes="180x180">
<link rel="manifest" href="{{ root }}/icons/manifest.json?v={{ globals.v2 }}">
<meta name="apple-mobile-web-app-title" content="{{ globals.name }}">
<meta name="application-name" content="{{ globals.name }}">
<meta name="msapplication-config" content="{{ globals.root }}/icons/browserconfig.xml?v={{ globals.v2 }}">
<meta name="msapplication-config" content="{{ root }}/icons/browserconfig.xml?v={{ globals.v2 }}">
<meta name="theme-color" content="#232629">
{% block endmeta %}{% endblock %}
</head>

View File

@ -1,5 +1,10 @@
{% set title = album.name %}
{% extends "_layout.njk" %}
{% set fileRoot = config.domain %}
{% set generateZips = config.uploads.generateZips %}
{% set usingCdn = config.cloudflare and config.cloudflare.purgeCache %}
{% block stylesheets %}
<!-- Stylesheets -->
<link rel="stylesheet" href="../libs/bulma/bulma.min.css?v={{ globals.v3 }}">
@ -20,17 +25,17 @@
{% block opengraph %}
<!-- Open Graph tags -->
<meta property="og:type" content="website" />
<meta property="og:title" content="{{ title | safe }} &#8211; {{ count }} files" />
<meta property="og:url" content="{{ url }}" />
<meta property="og:description" content="{{ description | safe }}" />
<meta property="og:image" content="{{ thumb }}" />
<meta property="og:title" content="{{ album.name | safe }} &#8211; {{ files.length }} files" />
<meta property="og:url" content="{{ root }}/{{ album.url }}" />
<meta property="og:description" content="{{ album.description }}" />
<meta property="og:image" content="{{ fileRoot }}/{{ album.thumb }}" />
<meta property="og:locale" content="en_US" />
<!-- Twitter Card tags -->
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="{{ title | safe }} &#8211; {{ count }} files">
<meta name="twitter:description" content="{{ description | safe }}">
<meta name="twitter:image" content="{{ thumb }}">
<meta name="twitter:title" content="{{ album.name | safe }} &#8211; {{ files.length }} files">
<meta name="twitter:description" content="{{ album.description }}">
<meta name="twitter:image" content="{{ fileRoot }}/{{ album.thumb }}">
{% endblock %}
{% block content %}
@ -41,21 +46,27 @@
<div class="level-left">
<div class="level-item">
<h1 id="title" class="title">
{{ title | safe }}
{{ album.name | safe }}
</h1>
</div>
<div class="level-item">
<h1 id="count" class="subtitle">
{{ count }} files (<span class="file-size">{{ totalSize }} B</span>)
{{ files.length }} files (<span class="file-size">{{ album.totalSize }} B</span>)
</h1>
</div>
</div>
{% if generateZips and files.length -%}
{% if generateZips -%}
<div class="level-right">
<p class="level-item">
{% if downloadLink -%}
<a class="button is-primary is-outlined" title="Be aware that album archive may be cached by CDN" href="{{ downloadLink }}">Download album</a>
{% if not files.length -%}
<a class="button is-primary is-outlined" title="There are no files in the album" disabled>Download album</a>
{%- elif album.downloadLink -%}
{%- if usingCDN -%}
<a class="button is-primary is-outlined" title="Be aware that album archive may be cached by CDN" href="../{{ album.downloadLink }}">Download album</a>
{%- else -%}
<a class="button is-primary is-outlined" href="../{{ album.downloadLink }}">Download album</a>
{%- endif -%}
{%- else -%}
<a class="button is-primary is-outlined" title="The album's owner has chosen to disable download" disabled>Download disabled</a>
{%- endif %}
@ -64,9 +75,9 @@
{%- endif %}
</nav>
{% if description -%}
{% if album.description -%}
<h2 class="subtitle description">
{{ description | safe }}
{{ album.description | safe }}
</h2>
{%- endif %}
<hr>
@ -75,7 +86,7 @@
<article class="message">
<div class="message-body">
<p>You are viewing No-JS version of this album, so file size will be displayed in bytes.</p>
<p>Please <a href="{{ url }}">click here</a> if you want to view its regular version.</p>
<p>Please <a href="../{{ album.url }}">click here</a> if you want to view its regular version.</p>
</div>
</article>
{%- endif %}
@ -83,20 +94,20 @@
{% if files.length -%}
<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 is-relative">
<a class="image" href="{{ file.file }}" target="_blank" rel="noopener">
<div class="image-container column">
<a class="image" href="{{ fileRoot }}/{{ file.name }}" target="_blank" rel="noopener">
{% if file.thumb -%}
{% if nojs -%}
<img alt="{{ file.name }}" src="{{ file.thumb }}" width="200" height="200" loading="lazy">
<img alt="{{ file.name }}" src="{{ fileRoot }}/{{ file.thumb }}" width="200" height="200" loading="lazy">
{%- else -%}
<img alt="{{ file.name }}" data-src="{{ file.thumb }}">
<img alt="{{ file.name }}" data-src="{{ fileRoot }}/{{ file.thumb }}">
{%- endif %}
{%- 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><span class="name">{{ file.name }}</span></p>
<p class="file-size">{{ file.size }} B</p>
</div>
</div>
@ -119,7 +130,7 @@
<div class="hero-body">
<div class="container has-text-centered">
<p>You have JavaScript disabled, but this page requires JavaScript to function.</p>
<p>Please <a href="{{ url }}?nojs">click here</a> if you want to view its No-JS version.</p>
<p>Please <a href="../{{ album.url }}?nojs">click here</a> if you want to view its No-JS version.</p>
</div>
</div>
</section>

View File

@ -1,3 +1,4 @@
{% set title = "Auth" %}
{% extends "_layout.njk" %}
{% block stylesheets %}
@ -30,12 +31,12 @@
<form id="authForm">
<div class="field">
<div class="control">
<input id="user" name="user" class="input" type="text" placeholder="Your username">
<input id="user" name="user" class="input" type="text" placeholder="Your username" minlength="4" maxlength="32">
</div>
</div>
<div class="field">
<div class="control">
<input id="pass" name="pass" class="input" type="password" placeholder="Your password">
<input id="pass" name="pass" class="input" type="password" placeholder="Your password" minlength="6" maxlength="64">
</div>
</div>
<div class="field is-grouped is-grouped-right">

View File

@ -1,3 +1,4 @@
{% set title = "Dashboard" %}
{% extends "_layout.njk" %}
{% block stylesheets %}

View File

@ -1,5 +1,11 @@
{% set title = "FAQ" %}
{% extends "_layout.njk" %}
{% set noJsMaxSize = config.cloudflare.noJsMaxSize | int %}
{% set chunkSize = config.uploads.chunkSize | int %}
{% set extensionsFilterMode = config.extensionsFilterMode %}
{% set extensionsFilter = config.extensionsFilter %}
{% macro extensions(obj) %}
{% set space = joiner(' ') %}
{% for id, val in obj -%}
@ -114,7 +120,7 @@
<article class="message">
<div class="message-body">
{% if extensionsFilter.length -%}
{%- if whitelist -%}
{%- if extensionsFilterMode === 'whitelist' -%}
We only support the following extensions:
{%- else -%}
We support any file extensions, except the following:

View File

@ -1,5 +1,12 @@
{% extends "_layout.njk" %}
{% set maxSizeInt = config.maxSize | int %}
{% set urlMaxSizeInt = config.urlMaxSize | int %}
{% set urlDisclaimerMessage = config.uploads.urlDisclaimerMessage %}
{% set urlExtensionsFilterMode = config.uploads.urlExtensionsFilterMode %}
{% set urlExtensionsFilter = config.uploads.urlExtensionsFilter %}
{% set temporaryUploadAges = config.uploads.temporaryUploadAges %}
{% block endmeta %}
{{ super() }}
{% if globals.google_site_verification %}
@ -39,7 +46,7 @@
<h2 class="subtitle">{{ globals.home_subtitle | safe }}</h2>
<h3 id="maxSize" class="subtitle">
Maximum upload size per file is <span>{{ maxSize }} MB</span>
Maximum upload size per file is <span>{{ maxSizeInt }} MB</span>
</h3>
<div class="columns is-gapless">
@ -95,8 +102,8 @@
<textarea id="urls" class="textarea" rows="2"></textarea>
</div>
<p class="help">
{% if urlMaxSize !== maxSize -%}
Maximum file size per URL is <span id="urlMaxSize">{{ urlMaxSize }} MB</span>.
{% if urlMaxSizeInt !== maxSizeInt -%}
Maximum file size per URL is <span id="urlMaxSize">{{ urlMaxSizeInt }} MB</span>.
{%- endif %}
{% if urlExtensionsFilter.length and (urlExtensionsFilterMode === 'blacklist') -%}
@ -144,7 +151,7 @@
</div>
<p class="help"></p>
</div>
{%- if temporaryUploadAges %}
{%- if temporaryUploadAges and temporaryUploadAges.length %}
<div id="uploadAgeDiv" class="field is-hidden">
<label class="label">Upload age</label>
<div class="control">
@ -179,7 +186,19 @@
</select>
</div>
</div>
<p class="help">Newer files on top only works in <a href="https://caniuse.com/#feat=mdn-css_properties_flex-direction" target="_blank" rel="noopener">these browsers</a>.</p>
<p class="help">Newer files on top will use <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/flex-direction#Accessibility_Concerns" target="_blank" rel="noopener">a CSS technique</a>. Trying to select their texts manually from top to bottom will end up selecting the texts from bottom to top instead.</p>
</div>
<div class="field">
<label class="label">Load images for preview</label>
<div class="control">
<div class="select is-fullwidth">
<select id="previewImages">
<option value="default">Yes (default)</option>
<option value="0">No</option>
</select>
</div>
</div>
<p class="help">By default, uploaded images will be loaded as their previews.</p>
</div>
<div class="field">
<p class="control">
@ -205,7 +224,7 @@
<div class="field">
<i class="icon is-hidden"></i>
<img class="is-unselectable is-hidden">
<p class="name is-unselectable"></p>
<p class="name"></p>
<progress class="progress is-small is-danger" max="100" value="0"></progress>
<p class="error"></p>
<p class="link">

View File

@ -1,5 +1,17 @@
{% set title = "No-JS uploader" %}
{% extends "_layout.njk" %}
{% set private = config.private %}
{% set disabledMessage -%}
{%- if config.enableUserAccounts -%}
Anonymous upload is disabled.
{%- else -%}
Running in private mode.
{%- endif %}
{%- endset %}
{% set maxSizeInt = config.maxSize | int %}
{% set noJsMaxSizeInt = config.cloudflare.noJsMaxSize | int %}
{% block stylesheets %}
{{ super() }}
<link rel="stylesheet" href="css/home.css?v={{ globals.v1 }}">
@ -17,15 +29,15 @@
<h2 class="subtitle">{{ globals.home_subtitle | safe }}</h2>
<h3 class="subtitle" id="maxSize">
Maximum total size per upload attempt is {{ renderOptions.maxFileSize }} MB
Maximum total size per upload attempt is {{ noJsMaxSizeInt or maxSizeInt }} MB
</h3>
<div class="columns is-gapless">
<div class="column is-hidden-mobile"></div>
<div class="column">
{% if renderOptions.uploadDisabled -%}
{% if config.private -%}
<a class="button is-danger is-flex" href="auth">
{{ renderOptions.uploadDisabled }}
{{ disabledMessage }}
</a>
{%- else -%}
<form id="form" class="field" action="" method="post" enctype="multipart/form-data">

View File

@ -915,9 +915,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0"
caniuse-db@^1.0.30000977:
version "1.0.30000994"
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000994.tgz#891d94864a8b4a49cae58a9b4a93c5b538667794"
integrity sha512-7KjfAAhO0qJOs92z8lMWkcRA2ig7Ewv5SQSAy+dik8MFQCDSua+j4RbPFnGrXuOSFe/3RhmGr+68DxKZrbJQGg==
version "1.0.30000995"
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000995.tgz#8e0557e822dab88fbbb21358d3ed395cb8efc21d"
integrity sha512-25ew/vPIVU0g/OjeZay2IfcljWAmNVy1TSmeoozFrJzEOqnka0ZSusJFS+4iGZKVIJ4RHOZB4NyilpwNcsh8tA==
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000980, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30000989:
version "1.0.30000989"
@ -1808,9 +1808,9 @@ ee-first@1.1.1:
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
electron-to-chromium@^1.3.247:
version "1.3.258"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.258.tgz#829b03be37424099b91aefb6815e8801bf30b509"
integrity sha512-rkPYrgFU7k/8ngjHYvzOZ44OQQ1GeIRIQnhGv00RkSlQXEnJKsGonQppbEEWHuuxZegpMao+WZmYraWQJQJMMg==
version "1.3.260"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.260.tgz#ffd686b4810bab0e1a428e7af5f08c21fe7c1fa2"
integrity sha512-wGt+OivF1C1MPwaSv3LJ96ebNbLAWlx3HndivDDWqwIVSQxmhL17Y/YmwUdEMtS/bPyommELt47Dct0/VZNQBQ==
emoji-regex@^7.0.1:
version "7.0.3"
@ -2518,11 +2518,11 @@ fs-constants@^1.0.0:
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
fs-minipass@^1.2.5:
version "1.2.6"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07"
integrity sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==
version "1.2.7"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7"
integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==
dependencies:
minipass "^2.2.1"
minipass "^2.6.0"
fs-mkdirp-stream@^1.0.0:
version "1.0.0"
@ -4218,10 +4218,10 @@ minimist@^1.2.0:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
minipass@^2.2.1, minipass@^2.3.5:
version "2.5.1"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.5.1.tgz#cf435a9bf9408796ca3a3525a8b851464279c9b8"
integrity sha512-dmpSnLJtNQioZFI5HfQ55Ad0DzzsMAb+HfokwRTNXwEQjepbTkl5mtIlSVxGIkOkxlpX7wIn5ET/oAd9fZ/Y/Q==
minipass@^2.2.1, minipass@^2.3.5, minipass@^2.6.0:
version "2.6.2"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.6.2.tgz#c3075a22680b3b1479bae5915904cb1eba50f5c0"
integrity sha512-38Jwdc8AttUDaQAIRX8Iaw3QoCDWjAwKMGeGDF9JUi9QCPMjH5qAQg/hdO8o1nC7Nmh1/CqzMg5FQPEKuKwznQ==
dependencies:
safe-buffer "^5.1.2"
yallist "^3.0.0"
@ -4398,9 +4398,9 @@ node-pre-gyp@^0.11.0:
tar "^4"
node-releases@^1.1.29:
version "1.1.30"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.30.tgz#35eebf129c63baeb6d8ddeda3c35b05abfd37f7f"
integrity sha512-BHcr1g6NeUH12IL+X3Flvs4IOnl1TL0JczUhEZjDE+FXXPQcVCNr8NEPb01zqGxzhTpdyJL5GXemaCW7aw6Khw==
version "1.1.32"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.32.tgz#485b35c1bf9b4d8baa105d782f8ca731e518276e"
integrity sha512-VhVknkitq8dqtWoluagsGPn3dxTvN9fwgR59fV3D7sLBHe0JfDramsMI8n8mY//ccq/Kkrf8ZRHRpsyVZ3qw1A==
dependencies:
semver "^5.3.0"
@ -6681,17 +6681,17 @@ stylehacks@^4.0.0:
postcss "^7.0.0"
postcss-selector-parser "^3.0.0"
stylelint-config-recommended@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-2.2.0.tgz#46ab139db4a0e7151fd5f94af155512886c96d3f"
integrity sha512-bZ+d4RiNEfmoR74KZtCKmsABdBJr4iXRiCso+6LtMJPw5rd/KnxUWTxht7TbafrTJK1YRjNgnN0iVZaJfc3xJA==
stylelint-config-recommended@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-3.0.0.tgz#e0e547434016c5539fe2650afd58049a2fd1d657"
integrity sha512-F6yTRuc06xr1h5Qw/ykb2LuFynJ2IxkKfCMf+1xqPffkxh0S09Zc902XCffcsw/XMFq/OzQ1w54fLIDtmRNHnQ==
stylelint-config-standard@^18.3.0:
version "18.3.0"
resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-18.3.0.tgz#a2a1b788d2cf876c013feaff8ae276117a1befa7"
integrity sha512-Tdc/TFeddjjy64LvjPau9SsfVRexmTFqUhnMBrzz07J4p2dVQtmpncRF/o8yZn8ugA3Ut43E6o1GtjX80TFytw==
stylelint-config-standard@^19.0.0:
version "19.0.0"
resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-19.0.0.tgz#66f0cf13f33b8a9e34965881493b38fc1313693a"
integrity sha512-VvcODsL1PryzpYteWZo2YaA5vU/pWfjqBpOvmeA8iB2MteZ/ZhI1O4hnrWMidsS4vmEJpKtjdhLdfGJmmZm6Cg==
dependencies:
stylelint-config-recommended "^2.2.0"
stylelint-config-recommended "^3.0.0"
stylelint@^10.1.0:
version "10.1.0"