Updates [!! run database/migration.js !!]

Added description column into albums.
So yeah, now albums can have description.
It'll only be shown in the album's edit popup and public link.

HTML chars will now be escaped from album's name and description.

Removed message warning about CDN cache from album's public link.
A shortened version will be shown as the download button's tooltip.

Darkened color of textarea's placeholder.

Bumped v1 version string.
This commit is contained in:
Bobby Wibowo 2018-12-13 16:09:46 +07:00
parent 42b6c74711
commit da86f605c6
No known key found for this signature in database
GPG Key ID: 51C3A1E1E22D26CF
12 changed files with 115 additions and 31 deletions

View File

@ -32,7 +32,7 @@ albumsController.list = async (req, res, next) => {
let fields = ['id', 'name']
if (req.params.sidebar === undefined) {
fields = fields.concat(fields, ['timestamp', 'identifier', 'editedAt', 'download', 'public'])
fields = fields.concat(fields, ['timestamp', 'identifier', 'editedAt', 'download', 'public', 'description'])
}
const albums = await db.table('albums')
@ -70,7 +70,7 @@ albumsController.create = async (req, res, next) => {
const user = await utils.authorize(req, res)
if (!user) { return }
const name = req.body.name
const name = utils.escape(req.body.name)
if (name === undefined || name === '') {
return res.json({ success: false, description: 'No album name specified.' })
}
@ -102,7 +102,8 @@ albumsController.create = async (req, res, next) => {
editedAt: 0,
zipGeneratedAt: 0,
download: (req.body.download === false || req.body.download === 0) ? 0 : 1,
public: (req.body.public === false || req.body.public === 0) ? 0 : 1
public: (req.body.public === false || req.body.public === 0) ? 0 : 1,
description: utils.escape(req.body.description) || ''
})
return res.json({ success: true, id: ids[0] })
@ -191,14 +192,14 @@ albumsController.edit = async (req, res, next) => {
return res.json({ success: false, description: 'No album specified.' })
}
const name = req.body.name
const name = utils.escape(req.body.name)
if (name === undefined || name === '') {
return res.json({ success: false, description: 'No name specified.' })
}
const album = await db.table('albums')
.where({
name,
id,
userid: user.id,
enabled: 1
})
@ -221,7 +222,8 @@ albumsController.edit = async (req, res, next) => {
.update({
name,
download: Boolean(req.body.download),
public: Boolean(req.body.public)
public: Boolean(req.body.public),
description: utils.escape(req.body.description) || ''
})
if (req.body.requestLink) {

View File

@ -54,6 +54,58 @@ utilsController.extname = filename => {
return extname + multi
}
utilsController.escape = string => {
// MIT License
// Copyright(c) 2012-2013 TJ Holowaychuk
// Copyright(c) 2015 Andreas Lubbe
// Copyright(c) 2015 Tiancheng "Timothy" Gu
if (!string) { return string }
const str = '' + string
const match = /["'&<>]/.exec(str)
if (!match) { return str }
let escape
let html = ''
let index = 0
let lastIndex = 0
for (index = match.index; index < str.length; index++) {
switch (str.charCodeAt(index)) {
case 34: // "
escape = '&quot;'
break
case 38: // &
escape = '&amp;'
break
case 39: // '
escape = '&#39;'
break
case 60: // <
escape = '&lt;'
break
case 62: // >
escape = '&gt;'
break
default:
continue
}
if (lastIndex !== index) {
html += str.substring(lastIndex, index)
}
lastIndex = index + 1
html += escape
}
return lastIndex !== index
? html + str.substring(lastIndex, index)
: html
}
utilsController.authorize = async (req, res) => {
const token = req.headers.token
if (token === undefined) {

View File

@ -15,6 +15,7 @@ const init = function (db) {
table.integer('zipGeneratedAt')
table.integer('download')
table.integer('public')
table.string('description')
}).then(() => {})
}
})

View File

@ -7,7 +7,8 @@ const map = {
editedAt: 'integer',
zipGeneratedAt: 'integer',
download: 'integer',
public: 'integer'
public: 'integer',
description: 'string'
},
users: {
enabled: 'integer',
@ -23,11 +24,11 @@ migration.start = async () => {
const columns = Object.keys(map[table])
return Promise.all(columns.map(async column => {
if (await db.schema.hasColumn(table, column)) {
return console.log(`Column "${column}" already exists in table "${table}".`)
return // console.log(`SKIP: ${column} => ${table}.`)
}
const columnType = map[table][column]
return db.schema.table(table, t => { t[columnType](column) })
.then(() => console.log(`Added column "${column}" to table "${table}".`))
.then(() => console.log(`OK: ${column} (${columnType}) => ${table}.`))
.catch(console.error)
}))
}))
@ -45,7 +46,7 @@ migration.start = async () => {
console.log(`Updated root's permission to ${perms.permissions.superadmin} (superadmin).`)
})
console.log('Migration finished! Now start lolisafe normally')
console.log('Migration finished! Now you may start lolisafe normally.')
process.exit(0)
}

View File

@ -13,6 +13,12 @@
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,

View File

@ -57,19 +57,23 @@ hr {
color: #7f8c8d;
}
.input::-moz-placeholder {
.input::-moz-placeholder,
.textarea::-moz-placeholder {
color: #7f8c8d;
}
.input::-webkit-input-placeholder {
.input::-webkit-input-placeholder,
.textarea::-webkit-input-placeholder {
color: #7f8c8d;
}
.input:-moz-placeholder {
.input:-moz-placeholder,
.textarea:-moz-placeholder {
color: #7f8c8d;
}
.input:-ms-input-placeholder {
.input:-ms-input-placeholder,
.textarea:-ms-input-placeholder {
color: #7f8c8d;
}

View File

@ -10,6 +10,7 @@ const page = {
page.getPrettyBytes = num => {
// MIT License
// Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
if (!Number.isFinite(num)) { return num }
const neg = num < 0

View File

@ -1046,6 +1046,11 @@ page.getAlbums = function () {
<input id="albumName" class="input" type="text" placeholder="Name">
</div>
</div>
<div class="field">
<div class="control">
<textarea id="albumDescription" class="textarea" placeholder="Description" rows="1"></textarea>
</div>
</div>
<div class="field">
<div class="control">
<a id="submitAlbum" class="button is-breeze is-fullwidth" data-action="submit-album">
@ -1090,7 +1095,8 @@ page.getAlbums = function () {
page.cache.albums[album.id] = {
name: album.name,
download: album.download,
public: album.public
public: album.public,
description: album.description
}
const tr = document.createElement('tr')
@ -1139,9 +1145,13 @@ page.editAlbum = function (id) {
const div = document.createElement('div')
div.innerHTML = `
<div class="field">
<label class="label">Album name</label>
<div class="controls">
<input id="swalName" class="input" type="text" value="${album.name || ''}">
<input id="swalName" class="input" type="text" placeholder="Name" value="${album.name || ''}">
</div>
</div>
<div class="field">
<div class="control">
<textarea id="swalDescription" class="textarea" placeholder="Description" rows="2">${album.description || ''}</textarea>
</div>
</div>
<div class="field">
@ -1186,6 +1196,7 @@ page.editAlbum = function (id) {
axios.post('api/albums/edit', {
id,
name: document.getElementById('swalName').value,
description: document.getElementById('swalDescription').value,
download: document.getElementById('swalDownload').checked,
public: document.getElementById('swalPublic').checked,
requestLink: document.getElementById('swalRequestLink').checked
@ -1265,7 +1276,8 @@ page.submitAlbum = function (element) {
page.isLoading(element, true)
axios.post('api/albums', {
name: document.getElementById('albumName').value
name: document.getElementById('albumName').value,
description: document.getElementById('albumDescription').value
}).then(function (response) {
if (!response) { return }
@ -1572,6 +1584,7 @@ page.getPrettyDate = date => {
page.getPrettyBytes = num => {
// MIT License
// Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
if (!Number.isFinite(num)) { return num }
const neg = num < 0

View File

@ -384,9 +384,13 @@ page.createAlbum = function () {
const div = document.createElement('div')
div.innerHTML = `
<div class="field">
<label class="label">Album name</label>
<div class="controls">
<input id="swalName" class="input" type="text" placeholder="My super album">
<input id="swalName" class="input" type="text" placeholder="Name">
</div>
</div>
<div class="field">
<div class="control">
<textarea id="swalDescription" class="textarea" placeholder="Description" rows="2"></textarea>
</div>
</div>
<div class="field">
@ -423,6 +427,7 @@ page.createAlbum = function () {
const name = document.getElementById('swalName').value
axios.post('api/albums', {
name,
description: document.getElementById('swalDescription').value,
download: document.getElementById('swalDownload').checked,
public: document.getElementById('swalPublic').checked
}, {

View File

@ -57,6 +57,7 @@ routes.get('/a/:identifier', async (req, res, next) => {
return res.render('album', {
title: album.name,
description: album.description.replace(/\n/g, '<br>'),
count: files.length,
thumb,
files,

View File

@ -15,7 +15,7 @@
v2: Images and config files (manifest.json, browserconfig.xml, etc).
v3: CSS and JS files (libs such as bulma, lazyload, etc).
#}
{% set v1 = "0mdbHpy0x2" %}
{% set v1 = "9cK64TLE3Z" %}
{% set v2 = "Ii3JYKIhb0" %}
{% set v3 = "ll7yHY3b2b" %}

View File

@ -16,7 +16,7 @@
{% block opengraph %}
<!-- Open Graph tags -->
<meta property="og:type" content="website" />
<meta property="og:title" content="{{ title }} &#8211; {{ count }} files" />
<meta property="og:title" content="{{ title | safe }} &#8211; {{ count }} files" />
<meta property="og:url" content="{{ url }}" />
<meta property="og:description" content="A pomf-like file uploading service that doesn't suck." />
<meta property="og:image" content="{{ thumb }}" />
@ -24,7 +24,7 @@
<!-- Twitter Card tags -->
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="{{ title }} &#8211; {{ count }} files">
<meta name="twitter:title" content="{{ title | safe }} &#8211; {{ count }} files">
<meta name="twitter:description" content="A pomf-like file uploading service that doesn't suck.">
<meta name="twitter:image" content="{{ thumb }}">
{% endblock %}
@ -37,7 +37,7 @@
<div class="level-left">
<div class="level-item">
<h1 id="title" class="title">
{{ title }}
{{ title | safe }}
</h1>
</div>
<div class="level-item">
@ -51,7 +51,7 @@
<div class="level-right">
<p class="level-item">
{% if downloadLink -%}
<a class="button is-primary is-outlined" title="Download album" href="{{ downloadLink }}">Download album</a>
<a class="button is-primary is-outlined" title="Be aware that album archive may be cached by CDN" href="{{ downloadLink }}">Download album</a>
{%- else -%}
<a class="button is-primary is-outlined" title="The album's owner has chosen to disable download" disabled>Download disabled</a>
{%- endif %}
@ -59,12 +59,10 @@
</div>
{%- endif %}
</nav>
{% if generateZips and downloadLink and files.length -%}
<article class="message">
<div class="message-body">
Album archives may be cached by CDN, if the one you have downloaded seems outdated, refresh the page to get the latest download link.
</div>
</article>
{% if description -%}
<h2 class="subtitle description">
{{ description | safe }}
</h2>
{%- endif %}
<hr>
{% if files.length -%}