Upgraded dependencies.

Stop adding cache-control header to album zip files unless
config.cacheControl is enabled.

Updated CSS files.
Moved thumbnail-related styling to thumbs.css.
Various other fixes & improvements.

Moved render.js from public/js to public/js/s.

Removed sharex.js in favor of public/js/s/utils.js.
Moved getPrettyDate() & getPrettyBytes() to that JS file as well.

Added lsKeys global variable wherever applicable.
Previously the idea was only used in dashboard.js.

Added No-JS version of album public pages.
You'll only have to add ?nojs to the URL.
Viewing the regular version with JS disabled will show a notice with
a link to the No-JS version of the particular album.
Overall page size of the regular version will now be lower as well,
since there'll be no need to add noscript tag for each thumbs.

No longer show Administrator section to non-admin in the dashboard.
Moderators will ONLY be able to see manage users menu as well.

Simplified FAQ wherever applicable.
Added a new FAQ about bug report or feature request.

Updated link for Firefox extension.
Also pushed Firefox link before Chrome, cause I like it more.

Added browser settings menu to dashboard.
This allows you to choose file size unit (kilobyte vs kibibyte) for that
specific browser.
The preference will be used on homepage, dashboard and album pages.
This also allows you to set chunk size and maximum parallel uploads
for the homepage uploader.

All menu links in the dashboard will now scroll to the content once
loaded.
Previously it would only do so with manage uploads/users when
switching pages.

Refactored all instances of for-in & for-of loop from browser JS files.
For the sake of uniformity, for now.
This commit is contained in:
Bobby Wibowo 2019-09-02 02:23:16 +07:00
parent b77d4b7c65
commit 05b905bc9b
No known key found for this signature in database
GPG Key ID: 51C3A1E1E22D26CF
21 changed files with 544 additions and 473 deletions

View File

@ -321,11 +321,11 @@ albumsController.get = async (req, res, next) => {
albumsController.generateZip = async (req, res, next) => {
const versionString = parseInt(req.query.v)
const download = (filePath, fileName) => {
const headers = { 'Access-Control-Allow-Origin': '*' }
if (versionString > 0)
// Cache-Control header is useful when using CDN (max-age: 30 days)
const headers = {}
if (config.cacheControl && versionString > 0) {
headers['Access-Control-Allow-Origin'] = '*'
headers['Cache-Control'] = 'public, max-age=2592000, must-revalidate, proxy-revalidate, immutable, stale-while-revalidate=86400, stale-if-error=604800'
}
return res.download(filePath, fileName, { headers })
}
@ -351,7 +351,7 @@ albumsController.generateZip = async (req, res, next) => {
else if (album.download === 0)
return res.json({ success: false, description: 'Download for this album is disabled.' })
if ((!versionString || versionString <= 0) && album.editedAt)
if ((isNaN(versionString) || versionString <= 0) && album.editedAt)
return res.redirect(`${album.identifier}?v=${album.editedAt}`)
if (album.zipGeneratedAt > album.editedAt) {

View File

@ -29,9 +29,9 @@
"express": "^4.17.1",
"express-rate-limit": "^5.0.0",
"fluent-ffmpeg": "^2.1.2",
"helmet": "^3.20.0",
"helmet": "^3.20.1",
"jszip": "^3.2.2",
"knex": "^0.19.2",
"knex": "^0.19.3",
"multer": "^1.4.2",
"node-fetch": "^2.6.0",
"nunjucks": "^3.2.0",
@ -42,10 +42,10 @@
"sqlite3": "^4.1.0"
},
"devDependencies": {
"eslint": "^6.2.2",
"eslint-config-standard": "^14.0.1",
"eslint": "^6.3.0",
"eslint-config-standard": "^14.1.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-node": "^9.1.0",
"eslint-plugin-node": "^9.2.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1"
}

View File

@ -2,99 +2,8 @@
background: none;
}
.message {
background-color: #31363b;
}
.message-body {
color: #eff0f1;
border: 0;
-webkit-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);
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);
}
@media screen and (max-width: 768px) {
.description {
text-align: center;
}
}
/*
This is the same sheets used in dashboard.css, minus the on-hover ones.
I should probably put this in a file named _thumbs.css, remove the ones in dashboard.css,
and use it for both album and dashboard (which means dashboard will have to load an extra css).
But oh well, that's something for the future me to think further about.
*/
.image-container {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
width: 200px;
height: 200px;
margin: 9px;
background-color: #31363b;
overflow: hidden;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
position: relative;
-webkit-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);
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);
}
.image-container .title {
font-weight: normal;
}
.image-container .image {
overflow: hidden;
max-width: 100%;
max-height: 100%;
width: 100%;
}
.image-container .file-checkbox {
position: absolute;
top: .75rem;
left: .75rem;
}
.image-container .controls {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
position: absolute;
top: .75rem;
right: .75rem;
}
.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: .75rem;
bottom: .75rem;
right: .75rem;
background-color: rgba(49, 54, 59, .75);
color: #eff0f1;
padding: .25rem;
font-size: .75rem;
}
.image-container .details p {
display: block;
text-overflow: ellipsis;
overflow: hidden;
}
.image-container .details p span {
font-weight: 800;
}

View File

@ -94,96 +94,12 @@ li[data-action="page-ellipsis"] {
border-left-color: #898b8d;
}
.image-container {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
width: 200px;
height: 200px;
margin: 9px;
background-color: #31363b;
overflow: hidden;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
position: relative;
-webkit-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);
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);
}
.image-container .title {
font-weight: normal;
word-break: break-all;
}
.image-container .image {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
height: 100%;
width: 100%;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
.image-container .image img {
max-height: 100%;
max-width: 100%;
height: auto;
width: auto;
}
.image-container .checkbox {
position: absolute;
top: .75rem;
left: .75rem;
}
.image-container .controls {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
position: absolute;
top: .75rem;
right: .75rem;
}
.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: .75rem;
bottom: .75rem;
right: .75rem;
background-color: rgba(49, 54, 59, .75);
color: #eff0f1;
padding: .25rem;
font-size: .75rem;
}
.image-container .details p {
display: block;
text-overflow: ellipsis;
overflow: hidden;
}
.image-container .details p span {
font-weight: 800;
}
/* Make extra info appear on hover only on non-touch devices */
.no-touch .image-container .checkbox {
opacity: .5;
-webkit-transition: opacity .25s;

View File

@ -106,14 +106,14 @@
}
}
.uploads {
padding-top: 1rem;
margin-bottom: 1rem;
}
.uploads>div {
-webkit-animation: fadeInOpacity .5s;
animation: fadeInOpacity .5s;
margin: 1rem;
}
.uploads>div:first-child {
margin-top: 1.5rem;
}
.uploads.nojs {
@ -211,3 +211,29 @@
display: inline-block;
word-break: break-all;
}
#urlMaxSize {
font-weight: bold;
}
.render {
position: fixed;
right: 0;
bottom: 0;
font-size: 1rem;
color: #bdc3c7;
cursor: pointer;
}
.render.button {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
right: 1%;
opacity: .25;
-webkit-transition: opacity .25s;
transition: opacity .25s;
}
.render.button:hover {
opacity: 1;
}

View File

@ -103,30 +103,24 @@ hr {
color: #fff;
}
.checkbox:hover,
.radio:hover {
color: #7f8c8d;
}
.progress.is-breeze:indeterminate {
background-image: linear-gradient(to right,#60a8dc 30%,#eff0f1 30%);
}
.render {
position: fixed;
right: 0;
bottom: 0;
font-size: 1rem;
color: #bdc3c7;
cursor: pointer;
.message {
background-color: #31363b;
}
.render.button {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
right: 1%;
opacity: .25;
-webkit-transition: opacity .25s;
transition: opacity .25s;
}
.render.button:hover {
opacity: 1;
.message-body {
color: #eff0f1;
border: 0;
-webkit-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);
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);
}
@-webkit-keyframes fadeInOpacity {

81
public/css/thumbs.css Normal file
View File

@ -0,0 +1,81 @@
.image-container {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
width: 200px;
height: 200px;
margin: 9px;
background-color: #31363b;
overflow: hidden;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
position: relative;
-webkit-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);
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);
}
.image-container .title {
font-weight: normal;
word-break: break-all;
}
.image-container .image {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
height: 100%;
width: 100%;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
.image-container .image img {
max-height: 100%;
max-width: 100%;
height: auto;
width: auto;
}
.image-container .controls {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
position: absolute;
top: .75rem;
right: .75rem;
}
.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: .75rem;
bottom: .75rem;
right: .75rem;
background-color: rgba(49, 54, 59, .75);
color: #eff0f1;
padding: .25rem;
font-size: .75rem;
}
.image-container .details p {
display: block;
text-overflow: ellipsis;
overflow: hidden;
}
.image-container .details p span {
font-weight: bold;
}

View File

@ -1,25 +1,12 @@
/* global LazyLoad */
// eslint-disable-next-line no-unused-vars
const lsKeys = {}
const page = {
lazyLoad: null
}
page.getPrettyBytes = function (num, si) {
// MIT License
// Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
if (!Number.isFinite(num)) return num
const neg = num < 0 ? '-' : ''
const scale = si ? 1000 : 1024
if (neg) num = -num
if (num < scale) return `${neg}${num} B`
const exponent = Math.min(Math.floor(Math.log10(num) / 3), 8) // 8 is count of KMGTPEZY
const numStr = Number((num / Math.pow(scale, exponent)).toPrecision(3))
const pre = (si ? 'kMGTPEZY' : 'KMGTPEZY').charAt(exponent - 1) + (si ? '' : 'i')
return `${neg}${numStr} ${pre}B`
}
window.onload = function () {
const elements = document.querySelectorAll('.file-size')
for (let i = 0; i < elements.length; i++)

View File

@ -1,8 +1,12 @@
/* global swal, axios */
const lsKeys = {
token: 'token'
}
const page = {
// user token
token: localStorage.token,
token: localStorage[lsKeys.token],
// HTML elements
user: null,

View File

@ -1,6 +1,5 @@
/* global swal, axios, ClipboardJS, LazyLoad */
// keys for localStorage
const lsKeys = {
token: 'token',
viewType: {
@ -11,7 +10,9 @@ const lsKeys = {
uploads: 'selectedUploads',
uploadsAll: 'selectedUploadsAll',
users: 'selectedUsers'
}
},
chunkSize: 'chunkSize',
parallelUploads: 'parallelUploads'
}
const page = {
@ -127,8 +128,9 @@ page.prepareDashboard = function () {
document.querySelector('#dashboard').style.display = 'block'
if (page.permissions.moderator) {
document.querySelector('#itemLabelAdmin').style.display = 'block'
document.querySelector('#itemListAdmin').style.display = 'block'
const itemManageUploads = document.querySelector('#itemManageUploads')
itemManageUploads.removeAttribute('disabled')
itemManageUploads.addEventListener('click', function () {
page.setActiveMenu(this)
page.getUploads({ all: true })
@ -137,18 +139,19 @@ page.prepareDashboard = function () {
if (page.permissions.admin) {
const itemServerStats = document.querySelector('#itemServerStats')
itemServerStats.removeAttribute('disabled')
itemServerStats.addEventListener('click', function () {
page.setActiveMenu(this)
page.getServerStats()
})
const itemManageUsers = document.querySelector('#itemManageUsers')
itemManageUsers.removeAttribute('disabled')
itemManageUsers.addEventListener('click', function () {
page.setActiveMenu(this)
page.getUsers()
})
} else {
document.querySelector('#itemServerStats').style.display = 'none'
document.querySelector('#itemManageUsers').style.display = 'none'
}
document.querySelector('#itemUploads').addEventListener('click', function () {
@ -166,6 +169,11 @@ page.prepareDashboard = function () {
page.getAlbums()
})
document.querySelector('#itemBrowserSettings').addEventListener('click', function () {
page.setActiveMenu(this)
page.browserSettings()
})
document.querySelector('#itemFileLength').addEventListener('click', function () {
page.setActiveMenu(this)
page.changeFileLength()
@ -286,7 +294,7 @@ page.isLoading = function (element, state) {
element.classList.remove('is-loading')
}
page.fadeIn = function (content) {
page.fadeAndScroll = function (content) {
if (page.fadingIn) {
clearTimeout(page.fadingIn)
page.dom.classList.remove('fade-in')
@ -295,10 +303,11 @@ page.fadeIn = function (content) {
page.fadingIn = setTimeout(function () {
page.dom.classList.remove('fade-in')
}, 500)
page.dom.scrollIntoView(true)
}
page.switchPage = function (action, element) {
const views = { scroll: true }
const views = {}
let func = null
if (page.currentView === 'users') {
@ -338,7 +347,7 @@ page.focusJumpToPage = function () {
element.select()
}
page.getUploads = function ({ pageNum, album, all, filters, scroll } = {}, element) {
page.getUploads = function ({ pageNum, album, all, filters } = {}, element) {
if (element) page.isLoading(element, true)
if ((all || filters) && !page.permissions.moderator)
@ -497,7 +506,7 @@ page.getUploads = function ({ pageNum, album, all, filters, scroll } = {}, eleme
<hr>
${pagination}
`
page.fadeIn()
page.fadeAndScroll()
const table = document.querySelector('#table')
@ -570,7 +579,7 @@ page.getUploads = function ({ pageNum, album, all, filters, scroll } = {}, eleme
<hr>
${pagination}
`
page.fadeIn()
page.fadeAndScroll()
const table = document.querySelector('#table')
@ -615,8 +624,6 @@ page.getUploads = function ({ pageNum, album, all, filters, scroll } = {}, eleme
}
}
if (scroll) page.dom.scrollIntoView(true)
if (allSelected && files.length) {
const selectAll = document.querySelector('#selectAll')
if (selectAll) selectAll.checked = true
@ -961,7 +968,7 @@ page.deleteByNames = function () {
</div>
</div>
`
page.fadeIn()
page.fadeAndScroll()
}
page.deleteFileByNames = function () {
@ -1200,7 +1207,7 @@ page.getAlbums = function () {
</table>
</div>
`
page.fadeIn()
page.fadeAndScroll()
const homeDomain = response.data.homeDomain
const table = document.querySelector('#table')
@ -1458,6 +1465,111 @@ page.getAlbum = function (album) {
page.getUploads({ album: album.id })
}
page.browserSettings = function () {
const selectionMap = { uploads: 'Selected uploads' }
if (page.permissions.moderator)
selectionMap.uploadsAll = 'Selected uploads (manager)'
if (page.permissions.admin)
selectionMap.users = 'Selected users'
let selectionSection = ''
const keys = Object.keys(selectionMap)
for (let i = 0; i < keys.length; i++)
selectionSection += `
<p>${selectionMap[keys[i]]}: ${page.selected[keys[i]].length}
`
const maxChunkSize = 95
const siBytes = localStorage[lsKeys.siBytes] !== '0'
page.dom.innerHTML = `
<h2 class="subtitle">Browser settings</h2>
<article class="message has-text-left">
<div class="message-body">
${selectionSection}
</div>
</article>
<article class="message has-text-left">
<div class="message-body">
<form class="prevent-default" id="browserSettingsForm">
<div class="field">
<label class="label">File size unit</label>
<div class="control">
<label class="radio">
<input type="radio" name="siBytes" value="default"${siBytes ? ' checked' : ''}>
1 Kilobyte = 1 kB = 1000 B (default)
</label>
</div>
<div>
<label class="radio">
<input type="radio" name="siBytes" value="0"${siBytes ? '' : ' checked'}>
1 Kibibyte = 1 KiB = 1024 B
</label>
</div>
</div>
<div class="field">
<label class="label">Upload chunk size (MB)</label>
<div class="control">
<input class="input" type="number" name="chunkSize" min="0" max="${maxChunkSize}" step="5" value="${localStorage[lsKeys.chunkSize] || '0'}">
</div>
<p class="help">Default is 0, which means to use server's setting. Max is ${maxChunkSize} MB.</p>
</div>
<div class="field">
<label class="label">Parallel uploads</label>
<div class="control">
<input class="input" type="number" name="parallelUploads" min="0" value="${localStorage[lsKeys.parallelUploads] || '0'}">
</div>
<p class="help">Default is 0, which means to use hard-coded Dropzone setting.</p>
</div>
<div class="field is-grouped">
<p class="control">
<button type="submit" id="saveBrowserSettings" class="button is-breeze">
Save
</button>
</p>
</div>
</form>
</div>
</article>
`
page.fadeAndScroll()
document.querySelector('#saveBrowserSettings').addEventListener('click', function () {
const form = document.querySelector('#browserSettingsForm')
const prefKeys = ['siBytes']
for (let i = 0; i < prefKeys.length; i++) {
const value = form.elements[prefKeys[i]].value
if (value !== '0')
localStorage.removeItem(lsKeys[prefKeys[i]])
else
localStorage[lsKeys[prefKeys[i]]] = value
}
const numKeys = ['chunkSize', 'parallelUploads']
for (let i = 0; i < numKeys.length; i++) {
const parsed = parseInt(form.elements[numKeys[i]].value)
let value = isNaN(parsed) ? 0 : Math.max(parsed, 0)
if (numKeys[i] === 'chunkSize') value = Math.min(value, maxChunkSize)
value = Math.min(value, Number.MAX_SAFE_INTEGER)
if (value > 0)
localStorage[lsKeys[numKeys[i]]] = value
else
localStorage.removeItem(lsKeys[numKeys[i]])
}
swal({
title: 'Woohoo!',
text: 'Browser settings saved.',
icon: 'success'
}).then(function () {
page.browserSettings()
})
})
}
page.changeFileLength = function () {
axios.get('api/filelength/config').then(function (response) {
if (response.data.success === false)
@ -1496,7 +1608,7 @@ page.changeFileLength = function () {
</div>
</form>
`
page.fadeIn()
page.fadeAndScroll()
document.querySelector('#setFileLength').addEventListener('click', function () {
page.setFileLength(document.querySelector('#fileLength').value, this)
@ -1564,7 +1676,7 @@ page.changeToken = function () {
</div>
</div>
`
page.fadeIn()
page.fadeAndScroll()
}).catch(function (error) {
console.log(error)
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
@ -1629,7 +1741,7 @@ page.changePassword = function () {
</div>
</form>
`
page.fadeIn()
page.fadeAndScroll()
document.querySelector('#sendChangePassword').addEventListener('click', function () {
if (document.querySelector('#password').value === document.querySelector('#passwordConfirm').value)
@ -1679,7 +1791,7 @@ page.setActiveMenu = function (activeItem) {
activeItem.classList.add('is-active')
}
page.getUsers = function ({ pageNum, scroll } = {}, element) {
page.getUsers = function ({ pageNum } = {}, element) {
if (element) page.isLoading(element, true)
if (pageNum === undefined) pageNum = 0
@ -1780,7 +1892,7 @@ page.getUsers = function ({ pageNum, scroll } = {}, element) {
<hr>
${pagination}
`
page.fadeIn()
page.fadeAndScroll()
const table = document.querySelector('#table')
@ -1790,9 +1902,10 @@ page.getUsers = function ({ pageNum, scroll } = {}, element) {
if (!selected && allSelected) allSelected = false
let displayGroup = null
for (const group of Object.keys(user.groups)) {
if (!user.groups[group]) break
displayGroup = group
const groups = Object.keys(user.groups)
for (let i = 0; i < groups.length; i++) {
if (!user.groups[groups[i]]) break
displayGroup = groups[i]
}
// Server-side explicitly expects either of these two values to consider a user as disabled
@ -1842,8 +1955,6 @@ page.getUsers = function ({ pageNum, scroll } = {}, element) {
page.checkboxes.users = Array.from(table.querySelectorAll('.checkbox[data-action="select"]'))
}
if (scroll) page.dom.scrollIntoView(true)
if (allSelected && response.data.users.length) {
const selectAll = document.querySelector('#selectAll')
if (selectAll) selectAll.checked = true
@ -2055,7 +2166,7 @@ page.getServerStats = function (element) {
Please wait, this may take a while\u2026
<progress class="progress is-breeze" max="100" style="margin-top: 10px"></progress>
`
page.fadeIn()
page.fadeAndScroll()
const url = 'api/stats'
axios.get(url).then(function (response) {
@ -2067,39 +2178,42 @@ page.getServerStats = function (element) {
}
let content = ''
for (const key of Object.keys(response.data.stats)) {
const keys = Object.keys(response.data.stats)
for (let i = 0; i < keys.length; i++) {
let rows = ''
if (!response.data.stats[key])
if (!response.data.stats[keys[i]]) {
rows += `
<tr>
<td>Generating, please try again later\u2026</td>
<td></td>
</tr>
`
else
for (const valKey of Object.keys(response.data.stats[key])) {
const _value = response.data.stats[key][valKey]
} else {
const valKeys = Object.keys(response.data.stats[keys[i]])
for (let j = 0; j < valKeys.length; j++) {
const _value = response.data.stats[keys[i]][valKeys[j]]
let value = _value
if (['albums', 'users', 'uploads'].includes(key))
if (['albums', 'users', 'uploads'].includes(keys[i]))
value = _value.toLocaleString()
if (['memoryUsage', 'size'].includes(valKey))
if (['memoryUsage', 'size'].includes(valKeys[j]))
value = page.getPrettyBytes(_value)
if (valKey === 'systemMemory')
if (valKeys[j] === 'systemMemory')
value = `${page.getPrettyBytes(_value.used)} / ${page.getPrettyBytes(_value.total)} (${Math.round(_value.used / _value.total * 100)}%)`
rows += `
<tr>
<th>${valKey.replace(/([A-Z])/g, ' $1').toUpperCase()}</th>
<th>${valKeys[j].replace(/([A-Z])/g, ' $1').toUpperCase()}</th>
<td>${value}</td>
</tr>
`
}
}
content += `
<div class="table-container">
<table class="table is-fullwidth is-hoverable">
<thead>
<tr>
<th>${key.toUpperCase()}</th>
<th>${keys[i].toUpperCase()}</th>
<td style="width: 50%"></td>
</tr>
</thead>
@ -2116,49 +2230,19 @@ page.getServerStats = function (element) {
${content}
`
page.fadeIn()
page.fadeAndScroll()
})
}
page.getPrettyDate = function (date) {
return date.getFullYear() + '-' +
(date.getMonth() < 9 ? '0' : '') + // month's index starts from zero
(date.getMonth() + 1) + '-' +
(date.getDate() < 10 ? '0' : '') +
date.getDate() + ' ' +
(date.getHours() < 10 ? '0' : '') +
date.getHours() + ':' +
(date.getMinutes() < 10 ? '0' : '') +
date.getMinutes() + ':' +
(date.getSeconds() < 10 ? '0' : '') +
date.getSeconds()
}
page.getPrettyBytes = function (num, si) {
// MIT License
// Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
if (!Number.isFinite(num)) return num
const neg = num < 0 ? '-' : ''
const scale = si ? 1000 : 1024
if (neg) num = -num
if (num < scale) return `${neg}${num} B`
const exponent = Math.min(Math.floor(Math.log10(num) / 3), 8) // 8 is count of KMGTPEZY
const numStr = Number((num / Math.pow(scale, exponent)).toPrecision(3))
const pre = (si ? 'kMGTPEZY' : 'KMGTPEZY').charAt(exponent - 1) + (si ? '' : 'i')
return `${neg}${numStr} ${pre}B`
}
window.onload = function () {
// Add 'no-touch' class to non-touch devices
if (!('ontouchstart' in document.documentElement))
document.documentElement.classList.add('no-touch')
const selectedKeys = ['uploads', 'uploadsAll', 'users']
for (const selectedKey of selectedKeys) {
const ls = localStorage[lsKeys.selected[selectedKey]]
if (ls) page.selected[selectedKey] = JSON.parse(ls)
for (let i = 0; i < selectedKeys.length; i++) {
const ls = localStorage[lsKeys.selected[selectedKeys[i]]]
if (ls) page.selected[selectedKeys[i]] = JSON.parse(ls)
}
page.preparePage()

View File

@ -1,8 +1,15 @@
/* global swal, axios, Dropzone, ClipboardJS, LazyLoad */
const lsKeys = {
token: 'token',
chunkSize: 'chunkSize',
parallelUploads: 'parallelUploads',
ufBehavior: 'ufBehavior'
}
const page = {
// user token
token: localStorage.token,
token: localStorage[lsKeys.token],
// configs from api/check
private: null,
@ -13,6 +20,10 @@ const page = {
// store album id that will be used with upload requests
album: null,
maxSizeBytes: null,
urlMaxSize: null,
urlMaxSizeBytes: null,
albumSelect: null,
previewTemplate: null,
@ -27,7 +38,8 @@ page.checkIfPublic = function () {
axios.get('api/check').then(function (response) {
page.private = response.data.private
page.enableUserAccounts = response.data.enableUserAccounts
page.maxSize = response.data.maxSize
page.maxSize = parseInt(response.data.maxSize)
page.maxSizeBytes = page.maxSize * 1e6
page.chunkSize = response.data.chunkSize
page.preparePage()
}).catch(function (error) {
@ -96,9 +108,14 @@ page.prepareUpload = function () {
document.querySelector('#albumDiv').style.display = 'flex'
}
document.querySelector('#maxSize').innerHTML = `Maximum upload size per file is ${page.maxSize}`
document.querySelector('#maxSize').innerHTML = `Maximum upload size per file is ${page.getPrettyBytes(page.maxSizeBytes)}`
document.querySelector('#loginToUpload').style.display = 'none'
const urlMaxSize = document.querySelector('#urlMaxSize')
page.urlMaxSize = parseInt(urlMaxSize.innerHTML)
page.urlMaxSizeBytes = page.urlMaxSize * 1e6
urlMaxSize.innerHTML = page.getPrettyBytes(page.urlMaxSizeBytes)
if (!page.token && page.enableUserAccounts)
document.querySelector('#loginLinkText').innerHTML = 'Create an account and keep track of your uploads'
@ -190,16 +207,16 @@ page.prepareDropzone = function () {
`
tabDiv.querySelector('.dz-container').appendChild(div)
const maxSize = parseInt(page.maxSize)
const maxSizeBytes = maxSize * 1e6
const previewsContainer = tabDiv.querySelector('#tab-files .field.uploads')
const chunkSize = (localStorage[lsKeys.chunkSize] || parseInt(page.chunkSize)) * 1e6
const parallelUploads = localStorage[lsKeys.parallelUploads] || 2
page.dropzone = new Dropzone('#dropzone', {
url: 'api/upload',
paramName: 'files[]',
maxFilesize: maxSizeBytes / 1024 / 1024, // this option expects MiB
parallelUploads: 2,
maxFilesize: page.maxSizeBytes / 1024 / 1024, // this option expects MiB
parallelUploads,
uploadMultiple: false,
previewsContainer,
previewTemplate: page.previewTemplate,
@ -207,7 +224,7 @@ page.prepareDropzone = function () {
autoProcessQueue: true,
headers: { token: page.token },
chunking: Boolean(page.chunkSize),
chunkSize: (parseInt(page.chunkSize) * 1e6), // the option below expects Bytes
chunkSize, // the option below expects Bytes
parallelChunkUploads: false, // when set to true, it often hangs with hundreds of parallel uploads
chunksUploaded (file, done) {
file.previewElement.querySelector('.progress').setAttribute('value', 100)
@ -280,7 +297,7 @@ page.prepareDropzone = function () {
page.dropzone.on('error', function (file, error) {
if ((typeof error === 'string' && /^File is too big/.test(error)) ||
error.description === 'MulterError: File too large')
error = `File too large (${(file.size / 1e6).toFixed(2)}MB).`
error = `File too large (${page.getPrettyBytes(file.size)}).`
page.updateTemplateIcon(file.previewElement, 'icon-block')
file.previewElement.querySelector('.progress').style.display = 'none'
file.previewElement.querySelector('.name').innerHTML = file.name
@ -475,8 +492,9 @@ page.createAlbum = function () {
// Handle image paste event
window.addEventListener('paste', function (event) {
const items = (event.clipboardData || event.originalEvent.clipboardData).items
for (const index in items) {
const item = items[index]
const index = Object.keys(items)
for (let i = 0; i < index.length; i++) {
const item = items[index[i]]
if (item.kind === 'file') {
const blob = item.getAsFile()
const file = new File([blob], `pasted-image.${blob.type.match(/(?:[^/]*\/)([^;]*)/)[1]}`)

View File

@ -1,4 +1,7 @@
/* global page, swal */
/* global lsKeys, page, swal */
// keys for localStorage
lsKeys.render = 'render'
page.renderType = 'miku'
page.renderConfig = {
@ -66,7 +69,7 @@ page.doRenderSwal = function () {
<div class="field">
<div class="control">
<label class="checkbox">
<input id="swalRender" type="checkbox" ${localStorage.render === '0' ? '' : 'checked'}>
<input id="swalRender" type="checkbox" ${localStorage[lsKeys.render] === '0' ? '' : 'checked'}>
Enable random render of ${page.config.name}
</label>
</div>
@ -82,8 +85,11 @@ page.doRenderSwal = function () {
}).then(function (value) {
if (!value) return
const newValue = div.querySelector('#swalRender').checked ? undefined : '0'
if (newValue !== localStorage.render) {
newValue ? localStorage.render = newValue : localStorage.removeItem('render')
if (newValue !== localStorage[lsKeys.render]) {
if (newValue)
localStorage[lsKeys.render] = newValue
else
localStorage.removeItem(lsKeys.render)
swal('Success!', `Random render is now ${newValue ? 'disabled' : 'enabled'}.`, 'success')
const element = document.querySelector('body > .render')
element.remove()
@ -104,7 +110,7 @@ page.doRender = function () {
if (!page.config || !page.config.array.length) return
let element
if (localStorage.render === '0') {
if (localStorage[lsKeys.render] === '0') {
element = document.createElement('a')
element.className = 'button is-breeze is-hidden-mobile'
element.title = page.config.name

58
public/js/s/utils.js Normal file
View File

@ -0,0 +1,58 @@
/* global lsKeys, page */
// keys for localStorage
lsKeys.siBytes = 'siBytes'
page.prepareShareX = function () {
if (!page.token) return
const origin = (location.hostname + location.pathname).replace(/\/(dashboard)?$/, '')
const originClean = origin.replace(/\//g, '_')
const sharexElement = document.querySelector('#ShareX')
const sharexFile = `{
"Name": "${originClean}",
"DestinationType": "ImageUploader, FileUploader",
"RequestType": "POST",
"RequestURL": "${location.protocol}//${origin}/api/upload",
"FileFormName": "files[]",
"Headers": {
"token": "${page.token}"
},
"ResponseType": "Text",
"URL": "$json:files[0].url$",
"ThumbnailURL": "$json:files[0].url$"
}\n`
const sharexBlob = new Blob([sharexFile], { type: 'application/octet-binary' })
sharexElement.setAttribute('href', URL.createObjectURL(sharexBlob))
sharexElement.setAttribute('download', `${originClean}.sxcu`)
}
page.getPrettyDate = function (date) {
return date.getFullYear() + '-' +
(date.getMonth() < 9 ? '0' : '') + // month's index starts from zero
(date.getMonth() + 1) + '-' +
(date.getDate() < 10 ? '0' : '') +
date.getDate() + ' ' +
(date.getHours() < 10 ? '0' : '') +
date.getHours() + ':' +
(date.getMinutes() < 10 ? '0' : '') +
date.getMinutes() + ':' +
(date.getSeconds() < 10 ? '0' : '') +
date.getSeconds()
}
page.getPrettyBytes = function (num) {
// MIT License
// Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
if (!Number.isFinite(num)) return num
const si = localStorage[lsKeys.siBytes] !== '0'
const neg = num < 0 ? '-' : ''
const scale = si ? 1000 : 1024
if (neg) num = -num
if (num < scale) return `${neg}${num} B`
const exponent = Math.min(Math.floor(Math.log10(num) / 3), 8) // 8 is count of KMGTPEZY
const numStr = Number((num / Math.pow(scale, exponent)).toPrecision(3))
const pre = (si ? 'kMGTPEZY' : 'KMGTPEZY').charAt(exponent - 1) + (si ? '' : 'i')
return `${neg}${numStr} ${pre}B`
}

View File

@ -1,24 +0,0 @@
/* global page */
page.prepareShareX = function () {
if (!page.token) return
const origin = (location.hostname + location.pathname).replace(/\/(dashboard)?$/, '')
const originClean = origin.replace(/\//g, '_')
const sharexElement = document.querySelector('#ShareX')
const sharexFile = `{
"Name": "${originClean}",
"DestinationType": "ImageUploader, FileUploader",
"RequestType": "POST",
"RequestURL": "${location.protocol}//${origin}/api/upload",
"FileFormName": "files[]",
"Headers": {
"token": "${page.token}"
},
"ResponseType": "Text",
"URL": "$json:files[0].url$",
"ThumbnailURL": "$json:files[0].url$"
}\n`
const sharexBlob = new Blob([sharexFile], { type: 'application/octet-binary' })
sharexElement.setAttribute('href', URL.createObjectURL(sharexBlob))
sharexElement.setAttribute('download', `${originClean}.sxcu`)
}

View File

@ -64,7 +64,8 @@ routes.get('/a/:identifier', async (req, res, next) => {
downloadLink: album.download === 0 ? null : `../api/album/zip/${album.identifier}?v=${album.editedAt}`,
editedAt: album.editedAt,
url: `${homeDomain}/a/${album.identifier}`,
totalSize
totalSize,
nojs: req.query.nojs !== undefined
})
})

View File

@ -16,7 +16,7 @@
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 = "UOoSqCmggh" %}
{% set v1 = "Jlu03caLZN" %}
{% set v2 = "hiboQUzAzp" %}
{% set v3 = "f0nYw5J15T" %}
{% set v4 = "S3TAWpPeFS" %}
@ -58,6 +58,15 @@
},
icon: 'icon-sharex icon-2x'
},
{
attrs: {
title: 'Firefox extension',
href: 'https://addons.mozilla.org/en-US/firefox/addon/lolisafe/',
target: '_blank',
rel: 'noopener'
},
icon: 'icon-firefox icon-2x'
},
{
attrs: {
title: 'Chrome extension',
@ -67,15 +76,6 @@
},
icon: 'icon-chrome icon-2x'
},
{
attrs: {
title: 'Firefox extension',
href: 'https://github.com/BobbyWibowo/loli-safe-extension/releases',
target: '_blank',
rel: 'noopener'
},
icon: 'icon-firefox icon-2x'
},
{
attrs: {
title: 'Bash uploader',

View File

@ -4,13 +4,17 @@
<!-- Stylesheets -->
<link rel="stylesheet" href="../libs/bulma/bulma.min.css?v={{ globals.v3 }}">
<link rel="stylesheet" href="../css/style.css?v={{ globals.v1 }}">
<link rel="stylesheet" href="../css/thumbs.css?v={{ globals.v1 }}">
<link rel="stylesheet" href="../css/album.css?v={{ globals.v1 }}">
{% endblock %}
{% block scripts %}
{% if not nojs -%}
<!-- Scripts -->
<script src="../libs/lazyload/lazyload.min.js?v={{ globals.v3 }}"></script>
<script src="../js/album.js?v={{ globals.v1 }}"></script>
<script src="../js/s/utils.js?v={{ globals.v1 }}"></script>
{% endif %}
{% endblock %}
{% block opengraph %}
@ -59,25 +63,34 @@
</div>
{%- endif %}
</nav>
{% if description -%}
<h2 class="subtitle description">
{{ description | safe }}
</h2>
{%- endif %}
<hr>
{% if nojs -%}
<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 its regular version.</p>
</div>
</article>
{%- endif %}
{% 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">
<a class="image" href="{{ file.file }}" target="_blank" rel="noopener">
{% if file.thumb -%}
{% if nojs -%}
<img alt="{{ file.name }}" src="{{ file.thumb }}">
{%- else -%}
<img alt="{{ file.name }}" data-src="{{ file.thumb }}">
{#-
This will kinda increase the overall page size,
but this will still benefit users with JavaScript enabled by lazyloading images,
and not causing those who have JavaScript disabled be unable to view the images.
#}
<noscript><img alt="{{ file.name }}" src="{{ file.thumb }}" style="display: none"></noscript>
{%- endif %}
{%- else -%}
<h1 class="title">{{ file.extname | default('N/A') }}</h1>
{%- endif %}
@ -99,11 +112,17 @@
</div>
</section>
{# Hide lazyload img tags and show noscript img tags #}
{% if not nojs -%}
<noscript>
<style>
img[data-src] { display: none; }
img[src] { display: block !important; }
</style>
<style>body > section:not(#noscript) { display: none !important; }</style>
<section id="noscript" class="hero is-fullheight">
<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>
</div>
</div>
</section>
</noscript>
{%- endif %}
{% endblock %}

View File

@ -4,6 +4,7 @@
{{ super() }}
<link rel="stylesheet" href="libs/fontello/fontello.css?v={{ globals.v3 }}">
<link rel="stylesheet" href="css/sweetalert.css?v={{ globals.v1 }}">
<link rel="stylesheet" href="css/thumbs.css?v={{ globals.v1 }}">
<link rel="stylesheet" href="css/dashboard.css?v={{ globals.v1 }}">
{% endblock %}
@ -14,7 +15,7 @@
<script src="libs/clipboard.js/clipboard.min.js?v={{ globals.v3 }}"></script>
<script src="libs/lazyload/lazyload.min.js?v={{ globals.v3 }}"></script>
<script src="js/dashboard.js?v={{ globals.v1 }}"></script>
<script src="js/sharex.js?v={{ globals.v1 }}"></script>
<script src="js/s/utils.js?v={{ globals.v1 }}"></script>
{% endblock %}
{% block content %}
@ -53,16 +54,16 @@
<ul id="albumsContainer"></ul>
</li>
</ul>
<p class="menu-label">Administration</p>
<ul class="menu-list">
<p id="itemLabelAdmin" class="menu-label" style="display: none">Administration</p>
<ul id="itemListAdmin" class="menu-list" style="display: none">
<li>
<a id="itemServerStats" disabled>Statistics</a>
<a id="itemServerStats">Statistics</a>
</li>
<li>
<a id="itemManageUploads" disabled>Manage uploads</a>
<a id="itemManageUploads">Manage uploads</a>
</li>
<li>
<a id="itemManageUsers" disabled>Manage users</a>
<a id="itemManageUsers">Manage users</a>
</li>
</ul>
<p class="menu-label">Configuration</p>
@ -70,6 +71,9 @@
<li>
<a id="ShareX">ShareX user profile</a>
</li>
<li>
<a id="itemBrowserSettings">Browser settings</a>
</li>
<li>
<a id="itemFileLength">File name length</a>
</li>

View File

@ -1,21 +1,5 @@
{% extends "_layout.njk" %}
{% block stylesheets %}
{{ super() }}
<style>
.message {
background-color: #31363b;
}
.message-body {
color: #eff0f1;
border: 0;
-webkit-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);
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);
}
</style>
{% endblock %}
{% block content %}
{{ super() }}
<section class="section">
@ -23,7 +7,7 @@
<h2 class='subtitle'>What is safe.fiery.me?</h2>
<article class="message">
<div class="message-body">
safe.fiery.me is a fork of <a href="https://github.com/WeebDev/lolisafe" target="_blank" rel="noopener">lolisafe</a>. You can check out the fork <a href="https://github.com/BobbyWibowo/lolisafe" target="_blank" rel="noopener">here</a>.
This is a fork of <a href="https://github.com/WeebDev/lolisafe" target="_blank" rel="noopener">lolisafe</a>. GitHub repository of the fork is located <a href="https://github.com/BobbyWibowo/lolisafe" target="_blank" rel="noopener">here</a>.
</div>
</article>
@ -45,9 +29,9 @@
<article class="message">
<div class="message-body">
Albums are a simple way of sorting uploads together.<br>
Right now you can create albums through the dashboard (and the homepage if you are logged in),<br>
then afterwards you can use them with our <a href="https://chrome.google.com/webstore/detail/loli-safe-uploader/enkkmplljfjppcdaancckgilmgoiofnj" target="_blank" rel="noopener">Chrome extension</a> or <a href="https://github.com/BobbyWibowo/loli-safe-extension/releases" target="_blank" rel="noopener">Firefox extension</a>,<br>
which will enable you to <strong>right click -> send to safe</strong> or to a desired album if you have any.<br>
As long as you are logged in, you can create albums through the homepage or the dashboard,<br>
then afterwards you can use them with our <a href="https://addons.mozilla.org/en-US/firefox/addon/lolisafe/" target="_blank" rel="noopener">Firefox extension</a> or <a href="https://chrome.google.com/webstore/detail/loli-safe-uploader/enkkmplljfjppcdaancckgilmgoiofnj" target="_blank" rel="noopener">Chrome extension</a>,<br>
which will enable you to <strong>right click -> send file to safe</strong> or to a desired album if you have any.<br>
You will have to set the domain in the extension's settings to <strong>https://safe.fiery.me</strong> though.
</div>
</article>
@ -66,39 +50,53 @@
</div>
</article>
<h2 class='subtitle'>I found a bug! -or- I want to request a feature!</h2>
<article class="message">
<div class="message-body">
Feel free to create a GitHub issue <a href="https://github.com/BobbyWibowo/lolisafe/issues/new/choose" target="_blank" rel="noopener">here</a>.</br>
Or if you don't want to use GitHub, then you can also contact me through my email above.
</div>
</article>
<h2 class='subtitle'>Where is the server located at?</h2>
<article class="message">
<div class="message-body">
Paris, France.<br>
Expect high First Time Byte (FTB) everywhere else, especially anywhere on the other side of the planet Earth (generally can be over 1000ms).<br>
I believe it is mostly due to the fact that we are using Cloudflare, since your requests have to go through them first.<br>
However, since Cloudflare will cache your uploads too, the uploads should have much better FTB afterwards (and generally faster download speed too).
We are using Cloudflare though, so you can expect your uploads to be delivered quickly all over the world after they have been cached.
</div>
</article>
<h2 class='subtitle'>Since my uploads are being cached, what about after I delete them from the dashboard?</h2>
<h2 class='subtitle'>Since my uploads are cached, what about after I delete them from the dashboard?</h2>
<article class="message">
<div class="message-body">
No need to worry.<br>
We will send API requests to Cloudflare to purge their cache immediately after you delete your uploads from the dashboard.<br>
Cache of thumbnails will also be purged, so no need to worry about them either.
Cache of their thumbnails will also be purged, if applicable.
</div>
</article>
<h2 class='subtitle'>Do you have a No-JS uploader form?</h2>
<article class="message">
<div class="message-body">
Yes, check out <a href="nojs" target="_blank" rel="noopener">this page</a>.<br>
Unfortunately you will not be able to associate your uploads to your account, if you have any.
Yes, check out <a href="nojs" target="_blank" rel="noopener">this page</a>.
</div>
</article>
{% if noJsMaxSize and chunkSize -%}
<h2 class='subtitle'>Why is the maximum file size in the No-JS uploader form smaller?</h2>
<article class="message">
<div class="message-body">
This site is using Cloudflare, which limits the maximum upload size.<br>
Since the homepage uploader chunks your uploads through JS magic, it is possible to increase the maximum file size there.
</div>
</article>
{%- endif %}
{% if chunkSize -%}
<h2 class='subtitle'>Does your API support chunked uploads?</h2>
<article class="message">
<div class="message-body">
Yes, the homepage uploader will chunk your uploads into {{ chunkSize }} pieces by default.<br>
If you want to utilize chunked uploads with the API, then feel free to inspect the HTTP requests.
Yes, the homepage uploader is hard-coded to chunk uploads into {{ chunkSize }} pieces by default.<br>
If you want to chunk your API uploads, feel free to read the source code to see how it works.
</div>
</article>
{%- endif %}
@ -116,26 +114,16 @@
</div>
</article>
<h2 class='subtitle'>How are the file URLs going to be determined?</h2>
<h2 class='subtitle'>How are the file URLs be determined?</h2>
<article class="message">
<div class="message-body">
Random {{ fileLength.default }}-letter strings will automatically be generated for your uploads.
The safe will generate random {{ fileLength.default }}-letter identifiers.
{% if fileLength.userChangeable %}<br>
If you think that is too {{ "short" if tooShort else "long" }}, you can create an account, which will let you to set a preferred length.<br>
At the moment you can choose from {{ fileLength.min }} to {{ fileLength.max }} letters.
If you find that too {{ "short" if tooShort else "long" }}, you can create an account which will let you to set your preferred length.<br>
You can choose from {{ fileLength.min }} to {{ fileLength.max }} letters.
{%- endif %}
</div>
</article>
{% if noJsMaxSize and chunkSize -%}
<h2 class='subtitle'>Why is the maximum file size in the No-JS uploader form smaller?</h2>
<article class="message">
<div class="message-body">
This site is using Cloudflare, which limits the maximum upload size.<br>
The homepage uploader will automatically chunk your uploads into {{ chunkSize }} pieces, so it is possible to increase the maximum file size there, but not in the No-JS uploader form.
</div>
</article>
{%- endif %}
</div>
</section>
{% endblock %}

View File

@ -22,9 +22,9 @@
<script src="libs/clipboard.js/clipboard.min.js?v={{ globals.v3 }}"></script>
<script src="libs/lazyload/lazyload.min.js?v={{ globals.v3 }}"></script>
<script src="js/home.js?v={{ globals.v1 }}"></script>
<script src="js/sharex.js?v={{ globals.v1 }}"></script>
<script src="js/s/utils.js?v={{ globals.v1 }}"></script>
<!-- We assign an ID for this so that the script can find out version string for render images -->
<script id="renderScript" data-version="{{ globals.v4 }}" src="js/render.js?v={{ globals.v1 }}"></script>
<script id="renderScript" data-version="{{ globals.v4 }}" src="js/s/render.js?v={{ globals.v1 }}"></script>
{% endblock %}
{% block content %}
@ -66,7 +66,7 @@
{%- endif %}
<div id="tab-files" class="tab-content" style="display: none">
<div class="field dz-container"></div>
<div class="field uploads" style="display: none"></div>
<div class="field uploads"></div>
</div>
{% if urlMaxSize -%}
<div id="tab-urls" class="tab-content" style="display: none">
@ -76,7 +76,7 @@
</div>
<p class="help">
{% if urlMaxSize !== maxSize -%}
Maximum file size for URL upload is <span style="font-weight: bold">{{ urlMaxSize }}</span>.
Maximum file size for URL upload is <span id="urlMaxSize">{{ urlMaxSize }}</span>.
{%- endif %}
{% if urlExtensionsFilter.length and (urlExtensionsFilterMode === 'blacklist') -%}
@ -100,7 +100,7 @@
</a>
</div>
</div>
<div class="field uploads" style="display: none"></div>
<div class="field uploads"></div>
</div>
{%- endif %}
</div>
@ -112,7 +112,7 @@
<i class="icon" style="display: none"></i>
<img class="is-unselectable" style="display: none">
<p class="name is-unselectable"></p>
<progress class="progress is-small is-danger" value="0" max="100">0%</progress>
<progress class="progress is-small is-danger" max="100"></progress>
<p class="error"></p>
<p class="link">
<a target="_blank" rel="noopener"></a>

118
yarn.lock
View File

@ -288,6 +288,11 @@ body-parser@1.19.0, body-parser@^1.19.0:
raw-body "2.4.0"
type-is "~1.6.17"
bowser@2.5.3:
version "2.5.3"
resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.5.3.tgz#811b0a24219c566c9a6ab3402bc8a13f35a18a96"
integrity sha512-aWCA+CKfKNL/WGzNgjmK+Whp57JMzboZMwJ5gy2jDj2bEIjbMCb3ImGX+V++5wsJftyFiDIbOjRXl60ycniVqg==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@ -503,10 +508,10 @@ color@^3.1.2:
color-convert "^1.9.1"
color-string "^1.5.2"
colorette@1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.0.8.tgz#421ff11c80b7414027ebed922396bc1833d1903c"
integrity sha512-X6Ck90ReaF+EfKdVGB7vdIQ3dr651BbIrBwY5YBKg13fjH+940sTtp7/Pkx33C6ntYfQcRumOs/aUQhaRPpbTQ==
colorette@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.1.0.tgz#1f943e5a357fac10b4e0f5aaef3b14cdc1af6ec7"
integrity sha512-6S062WDQUXi6hOfkO/sBPVwE5ASXY4G2+b4atvhJfSsuUUhIaUKlkjLe9692Ipyt5/a+IPF5aVTu3V5gvXq5cg==
combined-stream@^1.0.6, combined-stream@~1.0.6:
version "1.0.8"
@ -823,10 +828,10 @@ escape-string-regexp@^1.0.5:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
eslint-config-standard@^14.0.1:
version "14.0.1"
resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-14.0.1.tgz#375c3636fb4bd453cb95321d873de12e4eef790b"
integrity sha512-1RWsAKTDTZgA8bIM6PSC9aTGDAUlKqNkYNJlTZ5xYD/HYkIM6GlcefFvgcJ8xi0SWG5203rttKYX28zW+rKNOg==
eslint-config-standard@^14.1.0:
version "14.1.0"
resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-14.1.0.tgz#b23da2b76fe5a2eba668374f246454e7058f15d4"
integrity sha512-EF6XkrrGVbvv8hL/kYa/m6vnvmUT+K82pJJc4JJVMM6+Qgqh0pnwprSxdduDLB9p/7bIxD+YV5O0wfb8lmcPbA==
eslint-import-resolver-node@^0.3.2:
version "0.3.2"
@ -844,12 +849,12 @@ eslint-module-utils@^2.4.0:
debug "^2.6.8"
pkg-dir "^2.0.0"
eslint-plugin-es@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-1.4.0.tgz#475f65bb20c993fc10e8c8fe77d1d60068072da6"
integrity sha512-XfFmgFdIUDgvaRAlaXUkxrRg5JSADoRC8IkKLc/cISeR3yHVMefFHQZpcyXXEUUPHfy5DwviBcrfqlyqEwlQVw==
eslint-plugin-es@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-1.4.1.tgz#12acae0f4953e76ba444bfd1b2271081ac620998"
integrity sha512-5fa/gR2yR3NxQf+UXkeLeP8FBBl6tSgdrAz1+cF84v1FMM4twGwQoqTnn+QxFLcPOrF4pdKEJKDB/q9GoyJrCA==
dependencies:
eslint-utils "^1.3.0"
eslint-utils "^1.4.2"
regexpp "^2.0.1"
eslint-plugin-import@^2.18.2:
@ -869,13 +874,13 @@ eslint-plugin-import@^2.18.2:
read-pkg-up "^2.0.0"
resolve "^1.11.0"
eslint-plugin-node@^9.1.0:
version "9.1.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-9.1.0.tgz#f2fd88509a31ec69db6e9606d76dabc5adc1b91a"
integrity sha512-ZwQYGm6EoV2cfLpE1wxJWsfnKUIXfM/KM09/TlorkukgCAwmkgajEJnPCmyzoFPQQkmvo5DrW/nyKutNIw36Mw==
eslint-plugin-node@^9.2.0:
version "9.2.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-9.2.0.tgz#b1911f111002d366c5954a6d96d3cd5bf2a3036a"
integrity sha512-2abNmzAH/JpxI4gEOwd6K8wZIodK3BmHbTxz4s79OIYwwIt2gkpEXlAouJXu4H1c9ySTnRso0tsuthSOZbUMlA==
dependencies:
eslint-plugin-es "^1.4.0"
eslint-utils "^1.3.1"
eslint-plugin-es "^1.4.1"
eslint-utils "^1.4.2"
ignore "^5.1.1"
minimatch "^3.0.4"
resolve "^1.10.1"
@ -899,7 +904,7 @@ eslint-scope@^5.0.0:
esrecurse "^4.1.0"
estraverse "^4.1.1"
eslint-utils@^1.3.0, eslint-utils@^1.3.1, eslint-utils@^1.4.2:
eslint-utils@^1.4.2:
version "1.4.2"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.2.tgz#166a5180ef6ab7eb462f162fd0e6f2463d7309ab"
integrity sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==
@ -911,10 +916,10 @@ eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2"
integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==
eslint@^6.2.2:
version "6.2.2"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.2.2.tgz#03298280e7750d81fcd31431f3d333e43d93f24f"
integrity sha512-mf0elOkxHbdyGX1IJEUsNBzCDdyoUgljF3rRlgfyYh0pwGnreLc0jjD6ZuleOibjmnUWZLY2eXwSooeOgGJ2jw==
eslint@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.3.0.tgz#1f1a902f67bfd4c354e7288b81e40654d927eb6a"
integrity sha512-ZvZTKaqDue+N8Y9g0kp6UPZtS4FSY3qARxBs7p4f0H0iof381XHduqVerFWtK8DPtKmemqbqCFENWSQgPR/Gow==
dependencies:
"@babel/code-frame" "^7.0.0"
ajv "^6.10.0"
@ -1335,10 +1340,10 @@ get-value@^2.0.3, get-value@^2.0.6:
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=
getopts@2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.2.4.tgz#3137fe8a5fddf304904059a851bdc1c22f0f54fb"
integrity sha512-Rz7DGyomZjrenu9Jx4qmzdlvJgvrEFHXHvjK0FcZtcTC1U5FmES7OdZHUwMuSnEE6QvBvwse1JODKj7TgbSEjQ==
getopts@2.2.5:
version "2.2.5"
resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.2.5.tgz#67a0fe471cacb9c687d817cab6450b96dde8313b"
integrity sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA==
getpass@^0.1.1:
version "0.1.7"
@ -1480,20 +1485,20 @@ helmet-crossdomain@0.4.0:
resolved "https://registry.yarnpkg.com/helmet-crossdomain/-/helmet-crossdomain-0.4.0.tgz#5f1fe5a836d0325f1da0a78eaa5fd8429078894e"
integrity sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA==
helmet-csp@2.8.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/helmet-csp/-/helmet-csp-2.8.0.tgz#746d329e24ef39c4ebc00278a48abd3c209e0378"
integrity sha512-MlCPeM0Sm3pS9RACRihx70VeTHmkQwa7sum9EK1tfw1VZyvFU0dBWym9nHh3CRkTRNlyNm/WFCMvuh9zXkOjNw==
helmet-csp@2.9.0:
version "2.9.0"
resolved "https://registry.yarnpkg.com/helmet-csp/-/helmet-csp-2.9.0.tgz#8524886b08c7f7d611cb5f36eae453dd604efd4c"
integrity sha512-DGGOQtOLM7ZQpjbf/uvUonq1yG/rFgsBuK10ZJt2AtxUJxqfkPvfmP9aLUmgH9IactiRiYoiFY72YYSPl1TLTQ==
dependencies:
bowser "2.5.3"
camelize "1.0.0"
content-security-policy-builder "2.1.0"
dasherize "2.0.0"
platform "1.3.5"
helmet@^3.20.0:
version "3.20.0"
resolved "https://registry.yarnpkg.com/helmet/-/helmet-3.20.0.tgz#8a9383bf8230a461cafe8bc763423fbde110d2fc"
integrity sha512-Ob+TqmQFZ5f7WgP8kBbAzNPsbf6p1lOj5r+327/ymw/IILWih3wcx9u/u/S8Mwv5wbBkO7Li6x5s23t3COhUKw==
helmet@^3.20.1:
version "3.20.1"
resolved "https://registry.yarnpkg.com/helmet/-/helmet-3.20.1.tgz#802fcb39ac6865208cbc6879d3502e582c6f777e"
integrity sha512-em+X5Wz/f0yqoRsBnpnVy3wJHSiIeskX3FQn30szBh1tILaOeSRRLkShuUVFlk/o4qTYjWxdHg4FrRe45iBWHg==
dependencies:
depd "2.0.0"
dns-prefetch-control "0.2.0"
@ -1502,7 +1507,7 @@ helmet@^3.20.0:
feature-policy "0.3.0"
frameguard "3.1.0"
helmet-crossdomain "0.4.0"
helmet-csp "2.8.0"
helmet-csp "2.9.0"
hide-powered-by "1.1.0"
hpkp "2.0.0"
hsts "2.2.0"
@ -1975,25 +1980,25 @@ kind-of@^6.0.0, kind-of@^6.0.2:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==
knex@^0.19.2:
version "0.19.2"
resolved "https://registry.yarnpkg.com/knex/-/knex-0.19.2.tgz#056efdb33fb8c77d3d76266b5d1d12dc483c21b5"
integrity sha512-TVYvlp2esS4LjjJSz8XuE48bPJq4N3lWnETQVgJ3hXPEqjiDjxcTa3bCn6F5ipQuBaMAAaFHNrqsZm7BttogdA==
knex@^0.19.3:
version "0.19.3"
resolved "https://registry.yarnpkg.com/knex/-/knex-0.19.3.tgz#b5d85b29a127f631a6924e8727c76e53e26cc713"
integrity sha512-HN32QB5PVkUYfvE4UoK/Tbf6UQ7CLEgS0PL8EP6xfonsP0IPZr2M84dy1dIy2KnB5dx+XO6NNEPgfzo8Y8BYzA==
dependencies:
bluebird "^3.5.5"
colorette "1.0.8"
colorette "1.1.0"
commander "^2.20.0"
debug "4.1.1"
getopts "2.2.4"
getopts "2.2.5"
inherits "~2.0.4"
interpret "^1.2.0"
liftoff "3.1.0"
lodash "^4.17.15"
mkdirp "^0.5.1"
pg-connection-string "2.0.0"
pg-connection-string "2.1.0"
tarn "^2.0.0"
tildify "2.0.0"
uuid "^3.3.2"
uuid "^3.3.3"
v8flags "^3.1.3"
lcid@^1.0.0:
@ -2153,9 +2158,9 @@ minimist@^1.2.0:
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
minipass@^2.2.1, minipass@^2.3.5:
version "2.4.0"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.4.0.tgz#38f0af94f42fb6f34d3d7d82a90e2c99cd3ff485"
integrity sha512-6PmOuSP4NnZXzs2z6rbwzLJu/c5gdzYg1mRI/WIYdx45iiX7T+a4esOzavD6V/KmBzAaopFSTZPZcUx73bqKWA==
version "2.5.0"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.5.0.tgz#dddb1d001976978158a05badfcbef4a771612857"
integrity sha512-9FwMVYhn6ERvMR8XFdOavRz4QK/VJV8elU1x50vYexf9lslDcWe/f4HBRxCPd185ekRSjU6CfYyJCECa/CQy7Q==
dependencies:
safe-buffer "^5.1.2"
yallist "^3.0.0"
@ -2640,10 +2645,10 @@ performance-now@^2.1.0:
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
pg-connection-string@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.0.0.tgz#3eefe5997e06d94821e4d502e42b6a1c73f8df82"
integrity sha1-Pu/lmX4G2Ugh5NUC5CtqHHP434I=
pg-connection-string@2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.1.0.tgz#e07258f280476540b24818ebb5dca29e101ca502"
integrity sha512-bhlV7Eq09JrRIvo1eKngpwuqKtJnNhZdpdOlvrPrA4dxqXPjxSrbNrfnIDmTpwMyRszrcV4kU5ZA4mMsQUrjdg==
pify@^2.0.0:
version "2.3.0"
@ -2657,11 +2662,6 @@ pkg-dir@^2.0.0:
dependencies:
find-up "^2.1.0"
platform@1.3.5:
version "1.3.5"
resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.5.tgz#fb6958c696e07e2918d2eeda0f0bc9448d733444"
integrity sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==
posix-character-classes@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
@ -3558,7 +3558,7 @@ utils-merge@1.0.1:
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
uuid@^3.3.2:
uuid@^3.3.2, uuid@^3.3.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866"
integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==