filadelfia_archive: Finished implementing 1.0
|
@ -3,6 +3,7 @@ import Client from '../../base/media/client.mjs'
|
||||||
import OriginalArticleRoutes from '../../base/article/routes.mjs'
|
import OriginalArticleRoutes from '../../base/article/routes.mjs'
|
||||||
import { deleteFile, uploadFile } from '../../base/media/upload.mjs'
|
import { deleteFile, uploadFile } from '../../base/media/upload.mjs'
|
||||||
import { parseVideos, parseVideo } from './util.mjs'
|
import { parseVideos, parseVideo } from './util.mjs'
|
||||||
|
import { RankLevels } from '../../base/authentication/security.mjs'
|
||||||
|
|
||||||
export default class ArticleRoutes extends OriginalArticleRoutes {
|
export default class ArticleRoutes extends OriginalArticleRoutes {
|
||||||
constructor(opts = {}) {
|
constructor(opts = {}) {
|
||||||
|
@ -16,16 +17,16 @@ export default class ArticleRoutes extends OriginalArticleRoutes {
|
||||||
}
|
}
|
||||||
|
|
||||||
register(server) {
|
register(server) {
|
||||||
server.flaska.get('/api/auth/articles', server.authenticate(), this.getVideos.bind(this))
|
server.flaska.get('/api/articles', this.getVideos.bind(this))
|
||||||
|
server.flaska.get('/api/articles/:path', this.getArticle.bind(this))
|
||||||
server.flaska.put('/api/auth/articles/:id', [server.authenticate(), server.jsonHandler()], this.updateCreateArticle.bind(this))
|
server.flaska.put('/api/auth/articles/:id', [server.authenticate(), server.jsonHandler()], this.updateCreateArticle.bind(this))
|
||||||
server.flaska.get('/api/auth/articles/:id', server.authenticate(), this.auth_getSingleArticle.bind(this))
|
server.flaska.get('/api/auth/uploadToken', server.authenticate(RankLevels.Admin), this.getUploadToken.bind(this))
|
||||||
server.flaska.get('/api/auth/uploadToken', server.authenticate(), this.getUploadToken.bind(this))
|
server.flaska.delete('/api/auth/articles/:id', server.authenticate(RankLevels.Admin), this.auth_removeSingleArticle.bind(this))
|
||||||
server.flaska.delete('/api/auth/articles/:id', server.authenticate(), this.auth_removeSingleArticle.bind(this))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** GET: /api/auth/articles */
|
/** GET: /api/auth/articles */
|
||||||
async getVideos(ctx) {
|
async getVideos(ctx) {
|
||||||
let res = await ctx.db.safeCallProc('filadelfia_archive.article_auth_get_all', [ctx.state.auth_token])
|
let res = await ctx.db.safeCallProc('filadelfia_archive.article_get_all', [])
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
videos: parseVideos(res.results[0]),
|
videos: parseVideos(res.results[0]),
|
||||||
|
@ -40,6 +41,7 @@ export default class ArticleRoutes extends OriginalArticleRoutes {
|
||||||
token: Client.createJwt({ iss: media.iss }, media.secret),
|
token: Client.createJwt({ iss: media.iss }, media.secret),
|
||||||
path: config.get('media:directFilePath'),
|
path: config.get('media:directFilePath'),
|
||||||
resize: config.get('media:path'),
|
resize: config.get('media:path'),
|
||||||
|
delete: config.get('media:removePath'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,64 @@ const api = {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
uploadBanner: function(bannerFile, token) {
|
||||||
|
var data = new FormData()
|
||||||
|
data.append('file', bannerFile)
|
||||||
|
data.append('preview', JSON.stringify({
|
||||||
|
"out": "base64",
|
||||||
|
"format": "avif",
|
||||||
|
"resize": {
|
||||||
|
"width": 360,
|
||||||
|
"height": 203,
|
||||||
|
"fit": "cover",
|
||||||
|
"withoutEnlargement": true,
|
||||||
|
"kernel": "mitchell"
|
||||||
|
},
|
||||||
|
"avif": {
|
||||||
|
"quality": 50,
|
||||||
|
"effort": 9
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
data.append('medium', JSON.stringify({
|
||||||
|
"format": "avif",
|
||||||
|
"resize": {
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"fit": "cover",
|
||||||
|
"withoutEnlargement": true,
|
||||||
|
"kernel": "mitchell"
|
||||||
|
},
|
||||||
|
"avif": {
|
||||||
|
"quality": 75,
|
||||||
|
"effort": 3
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return api.sendRequest({
|
||||||
|
method: 'POST',
|
||||||
|
url: token.resize + '?token=' + token.token,
|
||||||
|
body: data,
|
||||||
|
})
|
||||||
|
.then(banner => {
|
||||||
|
api.sendRequest({
|
||||||
|
method: 'DELETE',
|
||||||
|
url: token.delete + banner.filename + '?token=' + token.token,
|
||||||
|
}).catch(err => console.error(err))
|
||||||
|
|
||||||
|
return {
|
||||||
|
file: banner,
|
||||||
|
size: bannerFile.size,
|
||||||
|
medium: {
|
||||||
|
filename: banner.medium.filename,
|
||||||
|
path: banner.medium.path,
|
||||||
|
},
|
||||||
|
preview: {
|
||||||
|
base64: banner.preview.base64,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
prettyFormatBytes: function(bytes) {
|
prettyFormatBytes: function(bytes) {
|
||||||
if (bytes < 1024) {
|
if (bytes < 1024) {
|
||||||
return `${bytes} B`
|
return `${bytes} B`
|
||||||
|
|
|
@ -48,12 +48,12 @@ const Authentication = {
|
||||||
|
|
||||||
requiresLogin: function() {
|
requiresLogin: function() {
|
||||||
if (Authentication.currentUser) return
|
if (Authentication.currentUser) return
|
||||||
m.route.set('/')
|
m.route.set('/login')
|
||||||
},
|
},
|
||||||
|
|
||||||
requiresNotLogin: function() {
|
requiresNotLogin: function() {
|
||||||
if (!Authentication.currentUser) return
|
if (!Authentication.currentUser) return
|
||||||
m.route.set('/browse')
|
m.route.set('/')
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ const Menu = {
|
||||||
oninit: function(vnode) {
|
oninit: function(vnode) {
|
||||||
this.currentActive = 'home'
|
this.currentActive = 'home'
|
||||||
this.loading = false
|
this.loading = false
|
||||||
this.browsing = true
|
|
||||||
if (!videos.Tree.length) {
|
if (!videos.Tree.length) {
|
||||||
videos.refreshTree()
|
videos.refreshTree()
|
||||||
}
|
}
|
||||||
|
@ -17,7 +16,6 @@ const Menu = {
|
||||||
onbeforeupdate: function() {
|
onbeforeupdate: function() {
|
||||||
videos.calculateActiveBranches()
|
videos.calculateActiveBranches()
|
||||||
let currentPath = m.route.get()
|
let currentPath = m.route.get()
|
||||||
this.browsing = currentPath === '/' || currentPath === '/browse' || videos.year
|
|
||||||
},
|
},
|
||||||
|
|
||||||
logOut: function() {
|
logOut: function() {
|
||||||
|
@ -30,28 +28,30 @@ const Menu = {
|
||||||
let last = videos.Tree[videos.Tree.length - 1]
|
let last = videos.Tree[videos.Tree.length - 1]
|
||||||
let hasId = m.route.param('id')
|
let hasId = m.route.param('id')
|
||||||
|
|
||||||
return Authentication.currentUser
|
return [
|
||||||
? [
|
|
||||||
m('nav', [
|
m('nav', [
|
||||||
m('h4', m(m.route.Link, { href: '/browse' }, lang.header_title /* Filadelfia archival center */)),
|
m('h4', m(m.route.Link, { href: '/' }, lang.header_title /* Filadelfia archival center */)),
|
||||||
m('a.change', { onclick: lang.langtoggle }, lang.lang_current),
|
m('a.link.changelang', { onclick: lang.langtoggle }, lang.lang_current),
|
||||||
Authentication.currentUser.rank > 10
|
Authentication.currentUser?.rank >= 100
|
||||||
? m(m.route.Link, { class: 'upload', href: '/upload' }, lang.upload_goto) // Upload
|
? m(m.route.Link, { class: 'upload', href: '/upload' }, lang.upload_goto) // Upload
|
||||||
: null,
|
: null,
|
||||||
|
Authentication.currentUser
|
||||||
|
? m(m.route.Link, { class: 'link', href: '/logout' }, lang.logout)
|
||||||
|
: m(m.route.Link, { class: 'upload', href: '/login' }, lang.login_submit) // Upload,
|
||||||
// m('button.logout', { onclick: this.logOut }, lang.header_logout), // Log out
|
// m('button.logout', { onclick: this.logOut }, lang.header_logout), // Log out
|
||||||
]),
|
]),
|
||||||
!this.browsing && videos.error
|
videos.error
|
||||||
? m('div.error', { onclick: videos.refreshTree }, [
|
? m('div.error', { onclick: videos.refreshTree }, [
|
||||||
videos.error, m('br'), 'Click here to try again'
|
videos.error, m('br'), 'Click here to try again'
|
||||||
])
|
])
|
||||||
: null,
|
: null,
|
||||||
this.browsing && !videos.error
|
!videos.error
|
||||||
? [
|
? [
|
||||||
m('.nav', m('.inner', tree.map(year => {
|
m('.nav', m('.inner', tree.map(year => {
|
||||||
return m(m.route.Link, {
|
return m(m.route.Link, {
|
||||||
class: [videos.year === year ? 'active' : '',
|
class: [videos.year === year ? 'active' : '',
|
||||||
!year.videos.length ? 'empty' : ''].join(' '),
|
!year.videos.length ? 'empty' : ''].join(' '),
|
||||||
href: ['', (videos.year !== year && year !== last) || hasId ? year.title : 'browse' ].filter(x => x).join('/'),
|
href: ['', (videos.year !== year && year !== last) || hasId ? year.title : '' ].filter(x => x).join('/') || '/',
|
||||||
}, year.title)
|
}, year.title)
|
||||||
}))),
|
}))),
|
||||||
videos.year
|
videos.year
|
||||||
|
@ -66,7 +66,6 @@ const Menu = {
|
||||||
]
|
]
|
||||||
: null,
|
: null,
|
||||||
]
|
]
|
||||||
: null
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
68
filadelfia_web/app/holdbutton.js
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
|
||||||
|
const HoldButton = {
|
||||||
|
oninit: function(vnode) {
|
||||||
|
this.timer = null
|
||||||
|
this.holding = false
|
||||||
|
this.windowBlur = () => {
|
||||||
|
this.timerStop()
|
||||||
|
m.redraw()
|
||||||
|
}
|
||||||
|
window.addEventListener('blur', this.windowBlur)
|
||||||
|
},
|
||||||
|
|
||||||
|
onremove: function(vnode) {
|
||||||
|
this.timerStop()
|
||||||
|
window.removeEventListener('blur', this.windowBlur)
|
||||||
|
},
|
||||||
|
|
||||||
|
keydown(vnode, e) {
|
||||||
|
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
|
||||||
|
this.timerStart(vnode)
|
||||||
|
m.redraw()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
keyup(e) {
|
||||||
|
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
|
||||||
|
this.timerStop()
|
||||||
|
m.redraw()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
timerStart(vnode) {
|
||||||
|
if (this.timer) return
|
||||||
|
|
||||||
|
this.timer = setTimeout(this.timerConfirmed.bind(this), 2000, vnode)
|
||||||
|
this.holding = true
|
||||||
|
},
|
||||||
|
|
||||||
|
timerStop() {
|
||||||
|
clearTimeout(this.timer)
|
||||||
|
this.timer = null
|
||||||
|
this.holding = false
|
||||||
|
},
|
||||||
|
|
||||||
|
timerConfirmed(vnode) {
|
||||||
|
this.timerStop()
|
||||||
|
vnode.attrs.onclick()
|
||||||
|
m.redraw()
|
||||||
|
},
|
||||||
|
|
||||||
|
view: function(vnode) {
|
||||||
|
return m('button.holdbutton', {
|
||||||
|
style: '--hold-bg: var(--error-bg); --hold-color: var(--error); --hold-fill: var(--error);',
|
||||||
|
hidden: vnode.attrs.hidden,
|
||||||
|
class: (vnode.attrs.class || '')
|
||||||
|
+ (this.holding ? ' holdbutton-active' : ''),
|
||||||
|
onpointerdown: this.timerStart.bind(this, vnode),
|
||||||
|
onpointerup: this.timerStop.bind(this),
|
||||||
|
onpointerleave: this.timerStop.bind(this),
|
||||||
|
onkeydown: this.keydown.bind(this, vnode),
|
||||||
|
onkeyup: this.keyup.bind(this),
|
||||||
|
onclick: e => { return false },
|
||||||
|
}, m('div.inner', vnode.attrs.text))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = HoldButton
|
|
@ -2,6 +2,7 @@ const m = require('mithril')
|
||||||
const Authentication = require('./authentication')
|
const Authentication = require('./authentication')
|
||||||
const Header = require('./header')
|
const Header = require('./header')
|
||||||
const Login = require('./page_login')
|
const Login = require('./page_login')
|
||||||
|
const Logout = require('./page_logout')
|
||||||
const Browse = require('./page_browse')
|
const Browse = require('./page_browse')
|
||||||
const Upload = require('./page_upload')
|
const Upload = require('./page_upload')
|
||||||
const Article = require('./page_article')
|
const Article = require('./page_article')
|
||||||
|
@ -36,12 +37,13 @@ m.route.link = function(vnode){
|
||||||
m.route.prefix = ''
|
m.route.prefix = ''
|
||||||
|
|
||||||
const allRoutes = {
|
const allRoutes = {
|
||||||
'/': Login,
|
'/': Browse,
|
||||||
'/browse': Browse,
|
'/login': Login,
|
||||||
|
'/logout': Logout,
|
||||||
'/upload': Upload,
|
'/upload': Upload,
|
||||||
'/:year': Browse,
|
'/:year': Browse,
|
||||||
'/:year/:month': Browse,
|
'/:year/:month': Browse,
|
||||||
'/:year/:month/:id': Article,
|
'/:year/:month/:path': Article,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait until we finish checking avif support, some views render immediately and will ask for this immediately before the callback gets called.
|
// Wait until we finish checking avif support, some views render immediately and will ask for this immediately before the callback gets called.
|
||||||
|
|
|
@ -35,8 +35,17 @@ const Input = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onupdate: function(vnode) {
|
||||||
|
if (this.tempus && vnode.attrs.form[vnode.attrs.formKey]) {
|
||||||
|
if (vnode.attrs.form[vnode.attrs.formKey].getTime() !== this.tempus.viewDate?.getTime()) {
|
||||||
|
this.tempus.dates.setValue(new tempus.DateTime(vnode.attrs.form[vnode.attrs.formKey]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
imageChanged: function(vnode, e) {
|
imageChanged: function(vnode, e) {
|
||||||
let file = vnode.attrs.form[vnode.attrs.formKey] = e.currentTarget.files?.[0] || null
|
let file = e.currentTarget.files?.[0] || null
|
||||||
|
this.updateValue(vnode, file)
|
||||||
if (this.preview) {
|
if (this.preview) {
|
||||||
this.preview.clear()
|
this.preview.clear()
|
||||||
this.preview = null
|
this.preview = null
|
||||||
|
@ -54,6 +63,14 @@ const Input = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateValue: function(vnode, value) {
|
||||||
|
vnode.attrs.form[vnode.attrs.formKey] = value
|
||||||
|
if (typeof(vnode.attrs.oninput) === 'function') {
|
||||||
|
vnode.attrs.oninput(vnode.attrs.form[vnode.attrs.formKey])
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
|
||||||
getInput: function(vnode) {
|
getInput: function(vnode) {
|
||||||
switch (vnode.attrs.utility) {
|
switch (vnode.attrs.utility) {
|
||||||
case 'file':
|
case 'file':
|
||||||
|
@ -68,7 +85,7 @@ const Input = {
|
||||||
type: 'file',
|
type: 'file',
|
||||||
accept: vnode.attrs.accept,
|
accept: vnode.attrs.accept,
|
||||||
disabled: api.loading,
|
disabled: api.loading,
|
||||||
oninput: (e) => { vnode.attrs.form[vnode.attrs.formKey] = e.currentTarget.files?.[0] || null },
|
oninput: (e) => this.updateValue(vnode, e.currentTarget.files?.[0] || null),
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
case 'datetime':
|
case 'datetime':
|
||||||
|
@ -78,12 +95,11 @@ const Input = {
|
||||||
disabled: api.loading,
|
disabled: api.loading,
|
||||||
oncreate: (e) => {
|
oncreate: (e) => {
|
||||||
this.tempus = new tempus.TempusDominus(e.dom, {
|
this.tempus = new tempus.TempusDominus(e.dom, {
|
||||||
defaultDate: vnode.attrs.form[vnode.attrs.formKey],
|
|
||||||
viewDate: vnode.attrs.form[vnode.attrs.formKey],
|
|
||||||
localization: tempusLocalization,
|
localization: tempusLocalization,
|
||||||
})
|
})
|
||||||
|
this.tempus.dates.setValue(new tempus.DateTime(vnode.attrs.form[vnode.attrs.formKey]))
|
||||||
this.subscription = this.tempus.subscribe(tempus.Namespace.events.change, (e) => {
|
this.subscription = this.tempus.subscribe(tempus.Namespace.events.change, (e) => {
|
||||||
vnode.attrs.form[vnode.attrs.formKey] = e.date
|
this.updateValue(vnode, e.date)
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
@ -111,24 +127,14 @@ const Input = {
|
||||||
disabled: api.loading,
|
disabled: api.loading,
|
||||||
type: vnode.attrs.type || 'text',
|
type: vnode.attrs.type || 'text',
|
||||||
value: vnode.attrs.form[vnode.attrs.formKey],
|
value: vnode.attrs.form[vnode.attrs.formKey],
|
||||||
oninput: (e) => { vnode.attrs.form[vnode.attrs.formKey] = e.currentTarget.value },
|
oninput: (e) => this.updateValue(vnode, e.currentTarget.value),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
view: function(vnode) {
|
view: function(vnode) {
|
||||||
let input = m('input', {
|
|
||||||
type: vnode.attrs.type || 'text',
|
|
||||||
value: vnode.attrs.form[vnode.attrs.formKey],
|
|
||||||
oninput: (e) => { vnode.attrs.form[vnode.attrs.formKey] = e.currentTarget.value },
|
|
||||||
})
|
|
||||||
|
|
||||||
if (vnode.attrs.utility === 'datetime') {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
m('label', vnode.attrs.label),
|
vnode.attrs.label ? m('label', vnode.attrs.label) : null,
|
||||||
this.getInput(vnode),
|
this.getInput(vnode),
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -33,6 +33,8 @@ const i18n = {
|
||||||
'Lykilorð'],
|
'Lykilorð'],
|
||||||
login_submit: ['Log in',
|
login_submit: ['Log in',
|
||||||
'Skrá inn'],
|
'Skrá inn'],
|
||||||
|
logout: ['Log out',
|
||||||
|
'Skrá út'],
|
||||||
upload_missing_title: ['Title is missing',
|
upload_missing_title: ['Title is missing',
|
||||||
'Titill vantar'],
|
'Titill vantar'],
|
||||||
upload_missing_date: ['Date is missing',
|
upload_missing_date: ['Date is missing',
|
||||||
|
@ -47,6 +49,16 @@ const i18n = {
|
||||||
'Mynd eftir {0} frá {1}'],
|
'Mynd eftir {0} frá {1}'],
|
||||||
api_down: ['No internet or browser blocked the request.',
|
api_down: ['No internet or browser blocked the request.',
|
||||||
'Ekkert net eða vafri blockaði fyrirspurn.'],
|
'Ekkert net eða vafri blockaði fyrirspurn.'],
|
||||||
|
edit: ['Edit',
|
||||||
|
'Breyta'],
|
||||||
|
delete: ['Delete',
|
||||||
|
'Eyða'],
|
||||||
|
article_speaker: ['Speaker',
|
||||||
|
'Ræðumaður'],
|
||||||
|
delete_error: ['Error while deleting: {0}',
|
||||||
|
'Villa við að eyða efni: {0}'],
|
||||||
|
article_error: ['Error while saving: {0}',
|
||||||
|
'Villa við að vista: {0}'],
|
||||||
months: {
|
months: {
|
||||||
'1': ['January',
|
'1': ['January',
|
||||||
'Janúar'],
|
'Janúar'],
|
||||||
|
@ -81,6 +93,25 @@ const langs = {
|
||||||
|
|
||||||
const regexNumber = new RegExp('^\\d+$')
|
const regexNumber = new RegExp('^\\d+$')
|
||||||
|
|
||||||
|
out.printdate = function(date) {
|
||||||
|
let day = date.getDate().toString()
|
||||||
|
if (out.currentlang === 'en') {
|
||||||
|
let last = day[day.length - 1]
|
||||||
|
if (last === '1') {
|
||||||
|
day += 'st'
|
||||||
|
} else if (last === '2') {
|
||||||
|
day += 'nd'
|
||||||
|
} else if (last === '3') {
|
||||||
|
day += 'rd'
|
||||||
|
} else {
|
||||||
|
day += 'th'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
day += '.'
|
||||||
|
}
|
||||||
|
return `${day} ${out.months[date.getMonth() + 1]} ${date.getFullYear()}, ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
|
||||||
|
}
|
||||||
|
|
||||||
out.langset = function(lang) {
|
out.langset = function(lang) {
|
||||||
out.currentlang = lang
|
out.currentlang = lang
|
||||||
let index = langs[lang]
|
let index = langs[lang]
|
||||||
|
|
|
@ -3,14 +3,16 @@ const api = require('./api')
|
||||||
const Authentication = require('./authentication')
|
const Authentication = require('./authentication')
|
||||||
const Input = require('./input')
|
const Input = require('./input')
|
||||||
const lang = require('./lang')
|
const lang = require('./lang')
|
||||||
|
const videos = require('./videos')
|
||||||
|
const HoldButton = require('./holdbutton')
|
||||||
|
|
||||||
const Article = {
|
const Article = {
|
||||||
oninit: function(vnode) {
|
oninit: function(vnode) {
|
||||||
Authentication.requiresLogin()
|
|
||||||
this.error = ''
|
this.error = ''
|
||||||
this.path = ''
|
this.path = ''
|
||||||
this.data = null
|
this.data = null
|
||||||
this.editing = false
|
this.editing = false
|
||||||
|
this.cacheImage = null
|
||||||
this.form = {
|
this.form = {
|
||||||
title: 'Sunnudagssamkoma',
|
title: 'Sunnudagssamkoma',
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
|
@ -23,7 +25,9 @@ const Article = {
|
||||||
},
|
},
|
||||||
|
|
||||||
onbeforeupdate: function(vnode) {
|
onbeforeupdate: function(vnode) {
|
||||||
let path = m.route.param('id')
|
let path = m.route.param('year').padStart(4, '0')
|
||||||
|
+ '-' + m.route.param('month').padStart(2, '0')
|
||||||
|
+ '-' + m.route.param('path')
|
||||||
if (this.path === path) return
|
if (this.path === path) return
|
||||||
|
|
||||||
this.fetchArticle(vnode, path)
|
this.fetchArticle(vnode, path)
|
||||||
|
@ -33,10 +37,12 @@ const Article = {
|
||||||
this.error = ''
|
this.error = ''
|
||||||
this.data = null
|
this.data = null
|
||||||
this.path = path
|
this.path = path
|
||||||
|
this.cacheImage = null
|
||||||
|
this.editing = false
|
||||||
|
|
||||||
api.sendRequest({
|
api.sendRequest({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: '/api/auth/articles/' + this.path,
|
url: '/api/articles/' + this.path,
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
this.data = result.article
|
this.data = result.article
|
||||||
|
@ -58,19 +64,112 @@ const Article = {
|
||||||
|
|
||||||
updatevideo: function(vnode, e) {
|
updatevideo: function(vnode, e) {
|
||||||
this.error = ''
|
this.error = ''
|
||||||
|
|
||||||
|
if (!this.form.title) this.error = lang.upload_missing_title // Title is missing
|
||||||
|
if (!this.form.date) this.error = lang.upload_missing_date // Date is missing
|
||||||
if (this.error) return false
|
if (this.error) return false
|
||||||
|
|
||||||
|
let promise = Promise.resolve()
|
||||||
|
|
||||||
|
if (Authentication.currentUser && (typeof(this.form.banner) !== 'string' && this.cacheImage?.file !== this.form.banner)) {
|
||||||
|
promise = api.sendRequest({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/auth/uploadToken',
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
return api.uploadBanner(this.form.banner, res)
|
||||||
|
.then(imageData => {
|
||||||
|
this.cacheImage = imageData
|
||||||
|
|
||||||
|
if (this.data.banner_path) {
|
||||||
|
api.sendRequest({
|
||||||
|
method: 'DELETE',
|
||||||
|
url: res.delete + this.data.banner_path.slice(this.data.banner_path.lastIndexOf('/') + 1) + '?token=' + res.token,
|
||||||
|
}).catch(err => console.error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.then(() => {
|
||||||
|
return api.sendRequest({
|
||||||
|
method: 'PUT',
|
||||||
|
url: '/api/auth/articles/' + this.data.id,
|
||||||
|
body: {
|
||||||
|
name: this.form.title,
|
||||||
|
page_id: 'null',
|
||||||
|
path: this.form.date.toISOString().replace('T', '_').replace(/:/g, '').split('.')[0],
|
||||||
|
content: JSON.stringify(this.form.metadata),
|
||||||
|
publish_at: this.form.date,
|
||||||
|
admin_id: Authentication.getTokenDecoded().user_id,
|
||||||
|
is_featured: false,
|
||||||
|
media: null,
|
||||||
|
banner: this.cacheImage ? {
|
||||||
|
filename: this.cacheImage.medium.filename,
|
||||||
|
path: this.cacheImage.medium.path,
|
||||||
|
type: 'image/avif',
|
||||||
|
size: this.cacheImage.size,
|
||||||
|
preview: {
|
||||||
|
base64: this.cacheImage.preview.base64,
|
||||||
|
},
|
||||||
|
} : null,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
this.fetchArticle(vnode, this.path)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
this.error = lang.format(lang.article_error, error.message) // Error while saving:
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
|
||||||
|
deletevideo: function(vnode) {
|
||||||
|
api.sendRequest({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/auth/uploadToken',
|
||||||
|
body: this.form,
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
return Promise.all([
|
||||||
|
this.data.banner_path ? api.sendRequest({
|
||||||
|
method: 'DELETE',
|
||||||
|
url: res.delete + this.data.banner_path.slice(this.data.banner_path.lastIndexOf('/') + 1) + '?token=' + res.token,
|
||||||
|
}).catch(err => console.error(err)) : Promise.resolve(),
|
||||||
|
this.data.media_path ? api.sendRequest({
|
||||||
|
method: 'DELETE',
|
||||||
|
url: res.delete + this.data.media_path.slice(this.data.media_path.lastIndexOf('/') + 1) + '?token=' + res.token,
|
||||||
|
}).catch(err => console.error(err)) : Promise.resolve(),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return api.sendRequest({
|
||||||
|
method: 'DELETE',
|
||||||
|
url: '/api/auth/articles/' + this.data.id,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
videos.removeArticle(this.data.id)
|
||||||
|
m.route.set('/')
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (!error) return
|
||||||
|
|
||||||
|
this.error = lang.format(lang.delete_error, error.message) // Error while uploading:
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
view: function(vnode) {
|
view: function(vnode) {
|
||||||
console.log(this.data)
|
|
||||||
return [
|
return [
|
||||||
api.loading ? m('div.loading-spinner') : null,
|
api.loading && !this.data ? m('div.loading-spinner') : null,
|
||||||
this.error
|
this.data ? [
|
||||||
? m('div.full-error', { onclick: this.fetchArticle.bind(this, vnode, this.path) }, [
|
this.data.media_path
|
||||||
this.error, m('br'), 'Click here to try again'
|
|
||||||
])
|
|
||||||
: null,
|
|
||||||
this.data?.media_path
|
|
||||||
? [
|
? [
|
||||||
m('.player', [
|
m('.player', [
|
||||||
m('video', {
|
m('video', {
|
||||||
|
@ -93,7 +192,7 @@ const Article = {
|
||||||
m('div.form-row', [
|
m('div.form-row', [
|
||||||
m('div.form-columns', [
|
m('div.form-columns', [
|
||||||
m(Input, {
|
m(Input, {
|
||||||
label: 'Mynd',
|
label: '',
|
||||||
type: 'file',
|
type: 'file',
|
||||||
accept: 'image/*',
|
accept: 'image/*',
|
||||||
utility: 'image',
|
utility: 'image',
|
||||||
|
@ -101,7 +200,7 @@ const Article = {
|
||||||
formKey: 'banner',
|
formKey: 'banner',
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
m('div.form-columns', [
|
m('div.form-columns.article-name', [
|
||||||
m(Input, {
|
m(Input, {
|
||||||
label: 'Title',
|
label: 'Title',
|
||||||
form: this.form,
|
form: this.form,
|
||||||
|
@ -122,10 +221,43 @@ const Article = {
|
||||||
form: this.form.metadata,
|
form: this.form.metadata,
|
||||||
formKey: 'speaker',
|
formKey: 'speaker',
|
||||||
}),
|
}),
|
||||||
|
this.error ? m('div.full-error', this.error) : null,
|
||||||
|
m('div.row', [
|
||||||
|
m('input.spinner', {
|
||||||
|
hidden: api.loading,
|
||||||
|
type: 'submit',
|
||||||
|
value: lang.edit,
|
||||||
|
}),
|
||||||
|
m('div.filler', {
|
||||||
|
hidden: api.loading,
|
||||||
|
}),
|
||||||
|
api.loading ? m('div.loading-spinner') : null,
|
||||||
|
m(HoldButton, {
|
||||||
|
class: 'button spinner',
|
||||||
|
onclick: () => this.deletevideo(vnode),
|
||||||
|
hidden: api.loading,
|
||||||
|
text: lang.delete,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
])
|
])
|
||||||
: m('div.article', [
|
: m('div.article', [
|
||||||
m('h1', this.data.name)
|
m('h1', this.data.name),
|
||||||
|
m('p', [
|
||||||
|
lang.printdate(this.form.date),
|
||||||
]),
|
]),
|
||||||
|
m('div.table', [
|
||||||
|
m('div.table-row', [
|
||||||
|
m('div.table-item', lang.article_speaker),
|
||||||
|
m('div.table-item', this.data.content.speaker || '...'),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
Authentication.currentUser?.rank >= 10
|
||||||
|
? m('button.button', { onclick: () => this.editing = true },lang.edit)
|
||||||
|
: null,
|
||||||
|
]),
|
||||||
|
] : [
|
||||||
|
this.error ? m('div.full-error', this.error) : null,
|
||||||
|
],
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,16 +6,15 @@ const lang = require('./lang')
|
||||||
|
|
||||||
const Browse = {
|
const Browse = {
|
||||||
oninit: function(vnode) {
|
oninit: function(vnode) {
|
||||||
Authentication.requiresLogin()
|
|
||||||
},
|
},
|
||||||
|
|
||||||
mArticles: function(vnode, articles) {
|
mArticles: function(vnode, articles) {
|
||||||
return articles.map(article => {
|
return articles.map(article => {
|
||||||
return m(m.route.Link, {
|
return m(m.route.Link, {
|
||||||
href: ['', article.publish_at.getFullYear(), article.publish_at.getMonth() + 1, article.id].join('/'),
|
href: ['', article.publish_at.getFullYear(), article.publish_at.getMonth() + 1, article.path_short].join('/'),
|
||||||
style: article.avif_preview ? `background-image: url('${article.avif_preview}')` : null,
|
style: article.avif_preview ? `background-image: url('${article.avif_preview}')` : null,
|
||||||
}, [
|
}, [
|
||||||
m('span', article.publish_at.toUTCString()),
|
m('span', lang.printdate(article.publish_at)),
|
||||||
m('span', article.name),
|
m('span', article.name),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
@ -48,7 +47,7 @@ const Browse = {
|
||||||
? this.mMonth(vnode, videos.year)
|
? this.mMonth(vnode, videos.year)
|
||||||
: null,
|
: null,
|
||||||
!videos.year
|
!videos.year
|
||||||
? videos.Tree.slice(-2).map(year => {
|
? videos.Tree.slice(-1).map(year => {
|
||||||
return [
|
return [
|
||||||
m('.gallery-year', year.title),
|
m('.gallery-year', year.title),
|
||||||
this.mMonth(vnode, year),
|
this.mMonth(vnode, year),
|
||||||
|
|
|
@ -34,7 +34,7 @@ const Login = {
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (!result.token) return Promise.reject(new Error(lang.login_error_auth)) // Unknown error from server. Try again later
|
if (!result.token) return Promise.reject(new Error(lang.login_error_auth)) // Unknown error from server. Try again later
|
||||||
Authentication.updateToken(result.token)
|
Authentication.updateToken(result.token)
|
||||||
m.route.set(this.redirect || '/browse')
|
m.route.set(this.redirect || '/')
|
||||||
videos.refreshTree()
|
videos.refreshTree()
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
@ -71,7 +71,6 @@ const Login = {
|
||||||
value: lang.login_submit, // Log in
|
value: lang.login_submit, // Log in
|
||||||
}),
|
}),
|
||||||
api.loading ? m('div.loading-spinner') : null,
|
api.loading ? m('div.loading-spinner') : null,
|
||||||
m('a', { onclick: lang.langtoggle }, lang.lang_change_long /* Skipta yfir á íslensku */),
|
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
m('footer', lang.mformat(
|
m('footer', lang.mformat(
|
||||||
|
|
15
filadelfia_web/app/page_logout.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
const Authentication = require('./authentication')
|
||||||
|
|
||||||
|
const Logout = {
|
||||||
|
oninit: function(vnode) {
|
||||||
|
Authentication.clearToken()
|
||||||
|
m.route.set(vnode.attrs.redirect || '/')
|
||||||
|
},
|
||||||
|
|
||||||
|
view: function(vnode) {
|
||||||
|
return []
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Logout
|
|
@ -35,74 +35,23 @@ const Upload = {
|
||||||
|
|
||||||
if (!this.form.title) this.error = lang.upload_missing_title // Title is missing
|
if (!this.form.title) this.error = lang.upload_missing_title // Title is missing
|
||||||
if (!this.form.date) this.error = lang.upload_missing_date // Date is missing
|
if (!this.form.date) this.error = lang.upload_missing_date // Date is missing
|
||||||
// if (!this.form.file) this.error = lang.upload_missing_file // Video file missing
|
if (!this.form.file) this.error = lang.upload_missing_file // Video file missing
|
||||||
if (!this.form.banner) this.error = lang.upload_missing_banner // Video file missing
|
if (!this.form.banner) this.error = lang.upload_missing_banner // Poster image missing
|
||||||
|
|
||||||
if (this.error) return false
|
if (this.error) return false
|
||||||
|
|
||||||
api.sendRequest({
|
api.sendRequest({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: '/api/auth/uploadToken',
|
url: '/api/auth/uploadToken',
|
||||||
body: this.form,
|
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (this.cacheImage?.file === this.form.banner) {
|
if (this.cacheImage?.file === this.form.banner) {
|
||||||
return this.cacheImage
|
return this.cacheImage
|
||||||
}
|
}
|
||||||
|
|
||||||
var data = new FormData()
|
return api.uploadBanner(this.form.banner, res)
|
||||||
data.append('file', this.form.banner)
|
.then(imageData => {
|
||||||
data.append('preview', JSON.stringify({
|
this.cacheImage = imageData
|
||||||
"out": "base64",
|
|
||||||
"format": "avif",
|
|
||||||
"resize": {
|
|
||||||
"width": 360,
|
|
||||||
"height": 203,
|
|
||||||
"fit": "cover",
|
|
||||||
"withoutEnlargement": true,
|
|
||||||
"kernel": "mitchell"
|
|
||||||
},
|
|
||||||
"avif": {
|
|
||||||
"quality": 50,
|
|
||||||
"effort": 9
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
data.append('medium', JSON.stringify({
|
|
||||||
"format": "avif",
|
|
||||||
"resize": {
|
|
||||||
"width": 1280,
|
|
||||||
"height": 720,
|
|
||||||
"fit": "cover",
|
|
||||||
"withoutEnlargement": true,
|
|
||||||
"kernel": "mitchell"
|
|
||||||
},
|
|
||||||
"avif": {
|
|
||||||
"quality": 75,
|
|
||||||
"effort": 3
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
return api.sendRequest({
|
|
||||||
method: 'POST',
|
|
||||||
url: res.resize + '?token=' + res.token,
|
|
||||||
body: data,
|
|
||||||
})
|
|
||||||
.then(banner => {
|
|
||||||
this.cacheImage = {
|
|
||||||
file: this.form.banner,
|
|
||||||
medium: {
|
|
||||||
filename: banner.medium.filename,
|
|
||||||
path: banner.medium.path,
|
|
||||||
},
|
|
||||||
preview: {
|
|
||||||
base64: banner.preview.base64,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
api.sendRequest({
|
|
||||||
method: 'DELETE',
|
|
||||||
url: res.resize.replace('resize', banner.filename) + '?token=' + res.token,
|
|
||||||
}).catch(err => console.log(err))
|
|
||||||
|
|
||||||
return res
|
return res
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -151,7 +100,7 @@ const Upload = {
|
||||||
filename: this.cacheImage.medium.filename,
|
filename: this.cacheImage.medium.filename,
|
||||||
path: this.cacheImage.medium.path,
|
path: this.cacheImage.medium.path,
|
||||||
type: 'image/avif',
|
type: 'image/avif',
|
||||||
size: this.form.file.size,
|
size: this.cacheImage.size,
|
||||||
preview: {
|
preview: {
|
||||||
base64: this.cacheImage.preview.base64,
|
base64: this.cacheImage.preview.base64,
|
||||||
},
|
},
|
||||||
|
@ -160,9 +109,8 @@ const Upload = {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
console.log(res)
|
|
||||||
videos.refreshTree()
|
videos.refreshTree()
|
||||||
m.route.set('/browse')
|
m.route.set('/')
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.uploading = null
|
this.uploading = null
|
||||||
|
@ -181,6 +129,22 @@ const Upload = {
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
|
|
||||||
|
filechanged(file) {
|
||||||
|
if (!file || !file.name) return
|
||||||
|
|
||||||
|
let matches = /^(\d{4})-(\d\d)-(\d\d)_(\d\d)-(\d\d)/.exec(file.name)
|
||||||
|
if (!matches) return
|
||||||
|
|
||||||
|
var date = new Date(matches.slice(1, 4).join('-') + 'T' + matches.slice(4,6).join(':') + ':00')
|
||||||
|
if (isNaN(date.getTime())) return
|
||||||
|
|
||||||
|
if (date.getMinutes() >= 30 || date.getHours() === 10) {
|
||||||
|
date.setHours(date.getHours() + 1)
|
||||||
|
}
|
||||||
|
date.setMinutes(0)
|
||||||
|
this.form.date = date
|
||||||
|
},
|
||||||
|
|
||||||
view: function(vnode) {
|
view: function(vnode) {
|
||||||
return [
|
return [
|
||||||
m('div.page.page-upload', [
|
m('div.page.page-upload', [
|
||||||
|
@ -210,6 +174,7 @@ const Upload = {
|
||||||
button: 'fa-video',
|
button: 'fa-video',
|
||||||
form: this.form,
|
form: this.form,
|
||||||
formKey: 'file',
|
formKey: 'file',
|
||||||
|
oninput: (file) => this.filechanged(file),
|
||||||
}),
|
}),
|
||||||
m(Input, {
|
m(Input, {
|
||||||
label: 'Mynd',
|
label: 'Mynd',
|
||||||
|
@ -242,8 +207,8 @@ const Upload = {
|
||||||
]),
|
]),
|
||||||
m('footer', lang.mformat(
|
m('footer', lang.mformat(
|
||||||
lang.unsplash, // Photo by X on Y
|
lang.unsplash, // Photo by X on Y
|
||||||
m('a', { href: 'https://unsplash.com/@guzmanbarquin?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash', target: '_blank' }, 'Guzmán Barquín'),
|
m('a', { href: 'https://unsplash.com/@franhotchin?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash', target: '_blank' }, 'Francesca Hotchin'),
|
||||||
m('a', { href: 'https://unsplash.com/photos/sea-under-full-moon-Qd688l1yDOI?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash', target: '_blank' }, 'Unsplash'),
|
m('a', { href: 'https://unsplash.com/photos/landscape-photo-of-mountain-covered-with-snow-FN-cedy6NHA?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash', target: '_blank' }, 'Unsplash'),
|
||||||
)),
|
)),
|
||||||
]),
|
]),
|
||||||
]
|
]
|
||||||
|
|
|
@ -23,7 +23,7 @@ function calculateActiveBranches() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (!path && (m.route.get() === '/' || (m.route.get() || '').startsWith('/browse'))) {
|
} else if (!path && m.route.get() === '/') {
|
||||||
exports.year = Tree[Tree.length - 1]
|
exports.year = Tree[Tree.length - 1]
|
||||||
} else if (!path) {
|
} else if (!path) {
|
||||||
exports.year = null
|
exports.year = null
|
||||||
|
@ -74,6 +74,14 @@ function rebuildTree() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeArticle(id) {
|
||||||
|
let index = Articles.findIndex(article => article.id === id)
|
||||||
|
if (index >= 0) {
|
||||||
|
Articles.splice(index, 1)
|
||||||
|
rebuildTree()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function refreshTree() {
|
function refreshTree() {
|
||||||
exports.error = ''
|
exports.error = ''
|
||||||
|
|
||||||
|
@ -85,7 +93,7 @@ function refreshTree() {
|
||||||
|
|
||||||
return api.sendRequest({
|
return api.sendRequest({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: '/api/auth/articles',
|
url: '/api/articles',
|
||||||
})
|
})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
result.videos.forEach(video => {
|
result.videos.forEach(video => {
|
||||||
|
@ -106,6 +114,7 @@ function refreshTree() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.removeArticle = removeArticle
|
||||||
exports.rebuildTree = rebuildTree
|
exports.rebuildTree = rebuildTree
|
||||||
exports.refreshTree = refreshTree
|
exports.refreshTree = refreshTree
|
||||||
exports.calculateActiveBranches = calculateActiveBranches
|
exports.calculateActiveBranches = calculateActiveBranches
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "echo done;"
|
"build": "esbuild app/index.js --bundle --outfile=public/assets/app.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@eonasdan/tempus-dominus": "^6.7.19",
|
||||||
|
"@popperjs/core": "^2.11.8",
|
||||||
|
"eltro": "^1.4.4",
|
||||||
|
"esbuild": "^0.19.5",
|
||||||
|
"flaska": "^1.3.2",
|
||||||
|
"mithril": "^2.2.2",
|
||||||
"service-core": "^3.0.0-beta.17"
|
"service-core": "^3.0.0-beta.17"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "filadelfia_web",
|
"name": "filadelfia_web",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"port": 4130,
|
"port": 4130,
|
||||||
"description": "Filadelfia web portal",
|
"description": "Filadelfia web portal",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
|
BIN
filadelfia_web/public/assets/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 60 KiB |
BIN
filadelfia_web/public/assets/favicon.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
101
filadelfia_web/public/assets/logo.svg
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="26.656599mm"
|
||||||
|
height="26.65659mm"
|
||||||
|
viewBox="0 0 26.656599 26.65659"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
inkscape:version="1.3.1 (91b66b0783, 2023-11-16)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#111111"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="1"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
showguides="true"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="4.2083894"
|
||||||
|
inkscape:cx="-36.712382"
|
||||||
|
inkscape:cy="66.890197"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1377"
|
||||||
|
inkscape:window-x="1912"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<sodipodi:guide
|
||||||
|
position="21.496619,297"
|
||||||
|
orientation="0,-1"
|
||||||
|
id="guide1"
|
||||||
|
inkscape:locked="false" />
|
||||||
|
<inkscape:grid
|
||||||
|
id="grid2"
|
||||||
|
units="mm"
|
||||||
|
originx="-90.44421"
|
||||||
|
originy="-127.62701"
|
||||||
|
spacingx="0.99999998"
|
||||||
|
spacingy="1"
|
||||||
|
empcolor="#0099e5"
|
||||||
|
empopacity="0.30196078"
|
||||||
|
color="#0099e5"
|
||||||
|
opacity="0.14901961"
|
||||||
|
empspacing="5"
|
||||||
|
dotted="false"
|
||||||
|
gridanglex="30"
|
||||||
|
gridanglez="30"
|
||||||
|
visible="false" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="-2.3731349e-07,282.22545"
|
||||||
|
orientation="1,0"
|
||||||
|
id="guide2"
|
||||||
|
inkscape:locked="false" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="26.656599,279.39628"
|
||||||
|
orientation="1,0"
|
||||||
|
id="guide3"
|
||||||
|
inkscape:locked="false" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="8.8910958,270.34341"
|
||||||
|
orientation="0,-1"
|
||||||
|
id="guide4"
|
||||||
|
inkscape:locked="false" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-90.444211,-127.62701)">
|
||||||
|
<path
|
||||||
|
id="path1"
|
||||||
|
d="M 90.444211,154.2836 H 117.10081 V 127.62701 H 90.444211 Z"
|
||||||
|
style="fill:#18597d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.159661" />
|
||||||
|
<path
|
||||||
|
id="path224"
|
||||||
|
d="m 103.77216,127.62701 c 7.36141,0 13.32865,5.96724 13.32865,13.3283 0,7.36106 -5.96724,13.32829 -13.32865,13.32829 -7.360713,0 -13.327947,-5.96723 -13.327947,-13.32829 0,-7.36106 5.967234,-13.3283 13.327947,-13.3283"
|
||||||
|
style="fill:#18597d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.352778" />
|
||||||
|
<path
|
||||||
|
id="path225"
|
||||||
|
d="m 103.77216,128.3629 c -6.954663,0 -12.592405,5.63774 -12.592405,12.5924 0,6.95466 5.637742,12.5924 12.592405,12.5924 6.95501,0 12.59275,-5.63774 12.59275,-12.5924 0,-6.95466 -5.63774,-12.5924 -12.59275,-12.5924"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.352778" />
|
||||||
|
<path
|
||||||
|
id="path226"
|
||||||
|
d="m 103.74393,129.68123 c 6.19478,0 11.21586,5.02179 11.21586,11.21622 0,6.19477 -5.02108,11.21621 -11.21586,11.21621 -6.19442,0 -11.216219,-5.02144 -11.216219,-11.21621 0,-6.19443 5.021799,-11.21622 11.216219,-11.21622"
|
||||||
|
style="fill:#18597d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.352778" />
|
||||||
|
<path
|
||||||
|
id="path227"
|
||||||
|
d="m 102.98229,131.02708 v 5.04931 h 3.34469 c 0.56162,-1.17581 2.25918,-2.76684 3.26989,-3.64631 l -0.45332,1.06045 c 0.0794,-0.067 -0.70238,1.29046 0.009,2.81799 0.63217,1.35713 1.07032,2.09867 1.2894,3.73097 0.35278,-0.94015 0.56691,-1.62736 1.04034,-2.17628 0.27552,-0.31856 0.31538,-0.93698 0.27023,-1.63971 0.38488,0.42192 0.93556,2.09126 0.82726,3.4364 -0.0536,0.67134 0.10936,2.83175 -2.17523,3.46358 -0.36794,1.33879 -1.24283,2.57704 -2.68993,3.37961 -1.78646,0.9906 -2.58057,0.20249 -3.81987,2.15512 -0.0596,-0.33691 -0.0677,-0.71932 -0.0773,-0.99554 -0.12065,-0.1584 -0.20885,-0.26988 -0.26494,-0.57397 -0.0603,-0.16052 0.001,-0.51082 0.26423,-0.57256 -0.0127,-1.36984 1.36384,-1.15641 1.60585,-1.95474 0.52422,-1.24848 -1.57445,-1.39877 -2.44052,-2.12513 v 6.70877 c 0.30127,0.17286 0.58526,0.44062 0.84772,0.85478 1.29082,-2.03412 3.09175,-0.54151 4.95265,-1.57339 l 1.83021,0.63077 -0.1337,0.1076 c -2.52412,1.23119 -4.95406,-0.67381 -6.70772,2.0902 -1.78223,-2.80846 -4.264368,-0.79586 -6.832603,-2.15229 l 1.937463,-0.67628 c 0.670632,0.37183 1.33315,0.41593 1.96744,0.41522 v -10.6306 h -5.036613 c -0.20179,0.0473 -0.21449,-2.18899 0,-2.13713 l 5.036613,0.002 -0.001,-5.04931 c -0.0483,-0.21449 2.18969,-0.19614 2.1396,0 z m 3.31576,7.18397 h -1.01177 c 0.004,0.32773 10e-4,0.62194 10e-4,0.85901 0,0.78317 1.59526,1.75789 2.0387,1.95827 0.14958,-1.2125 -0.12029,-1.60549 -0.8128,-2.50049 -0.0832,-0.10689 -0.15451,-0.21167 -0.21519,-0.31679"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.352778" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 5 KiB |
1
filadelfia_web/public/assets/logo_nobg.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="100.749" height="100.749" viewBox="0 0 26.657 26.657"><path d="M103.772 127.627c7.362 0 13.329 5.967 13.329 13.328s-5.967 13.329-13.329 13.329c-7.36 0-13.328-5.968-13.328-13.329 0-7.36 5.967-13.328 13.328-13.328" style="fill:#18597d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.352778" transform="translate(-90.444 -127.627)"/><path d="M103.772 128.363c-6.955 0-12.592 5.638-12.592 12.592 0 6.955 5.637 12.593 12.592 12.593 6.955 0 12.593-5.638 12.593-12.593 0-6.954-5.638-12.592-12.593-12.592" style="fill:#fff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.352778" transform="translate(-90.444 -127.627)"/><path d="M103.744 129.681c6.195 0 11.216 5.022 11.216 11.216 0 6.195-5.021 11.217-11.216 11.217-6.194 0-11.216-5.022-11.216-11.217 0-6.194 5.022-11.216 11.216-11.216" style="fill:#18597d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.352778" transform="translate(-90.444 -127.627)"/><path d="M102.982 131.027v5.05h3.345c.562-1.176 2.26-2.767 3.27-3.647l-.453 1.06c.079-.066-.703 1.291.009 2.819.632 1.357 1.07 2.098 1.289 3.73.353-.94.567-1.627 1.04-2.176.276-.318.316-.937.27-1.64.385.422.936 2.092.828 3.437-.054.671.11 2.832-2.175 3.463-.368 1.34-1.243 2.578-2.69 3.38-1.787.99-2.581.203-3.82 2.155-.06-.337-.068-.72-.078-.995-.12-.159-.208-.27-.264-.574-.06-.16 0-.511.264-.573-.013-1.37 1.364-1.156 1.606-1.955.524-1.248-1.575-1.398-2.44-2.125v6.709c.3.173.584.44.847.855 1.29-2.034 3.092-.542 4.952-1.574l1.83.631-.133.108c-2.524 1.231-4.954-.674-6.708 2.09-1.782-2.808-4.264-.796-6.832-2.152l1.937-.677c.67.372 1.333.416 1.968.416v-10.63h-5.037c-.202.046-.215-2.19 0-2.138l5.037.002-.001-5.05c-.049-.214 2.19-.196 2.14 0zm3.316 7.184h-1.012c.004.328.001.622.001.86 0 .782 1.596 1.757 2.039 1.957.15-1.212-.12-1.605-.813-2.5a2.774 2.774 0 0 1-.215-.317" style="fill:#fff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.352778" transform="translate(-90.444 -127.627)"/></svg>
|
After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 252 KiB |
BIN
filadelfia_web/public/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
|
@ -5,500 +5,193 @@
|
||||||
<title>Filadelfia web portal</title>
|
<title>Filadelfia web portal</title>
|
||||||
<base href="/">
|
<base href="/">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="icon" type="image/png" href="/favicon.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon.png">
|
||||||
|
<link rel="icon" type="image/png" href="/assets/favicon.png">
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
[hidden] { display: none !important; }
|
[hidden] { display: none !important; }
|
||||||
|
|
||||||
:root {
|
:root { --bg: #fff; --bg-component: #f3f7ff; --bg-component-half: #f3f7ff77; --bg-component-alt: #ffd99c; --color: #031131; --color-alt: #7a9ad3; --main: #18597d; --main-fg: #fff; --error: red; --error-bg: hsl(0, 75%, 80%); } /* Box sizing rules */
|
||||||
--bg: #fff;
|
|
||||||
--bg-component: #f3f7ff;
|
|
||||||
--bg-component-half: #f3f7ff77;
|
|
||||||
--bg-component-alt: #ffd99c;
|
|
||||||
--color: #031131;
|
|
||||||
--color-alt: #7a9ad3;
|
|
||||||
--main: #1066ff;
|
|
||||||
--main-fg: #fff;
|
|
||||||
--error: red;
|
|
||||||
--error-bg: hsl(0, 75%, 80%);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Box sizing rules */
|
|
||||||
*, *::before, *::after { box-sizing: border-box;
|
*, *::before, *::after { box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove default margin */
|
/* Remove default margin */
|
||||||
body, h1, h2, h3, h4, p, figure, blockquote, dl, dd {
|
body, h1, h2, h3, h4, p, figure, blockquote, dl, dd { margin: 0; }
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body { min-height: 100vh; text-rendering: optimizeSpeed; line-height: 1.5; font-size: 16px; font-family: 'Inter var', Helvetica, Arial, sans-serif; font-variation-settings: "slnt" 0; font-feature-settings: "case", "frac", "tnum", "ss02", "calt", "ccmp", "kern"; background: var(--bg); color: var(--color); display: flex; flex-direction: column; }
|
||||||
min-height: 100vh;
|
|
||||||
text-rendering: optimizeSpeed;
|
|
||||||
line-height: 1.5;
|
|
||||||
font-size: 16px;
|
|
||||||
font-family: 'Inter var', Helvetica, Arial, sans-serif;
|
|
||||||
font-variation-settings: "slnt" 0;
|
|
||||||
font-feature-settings: "case", "frac", "tnum", "ss02", "calt", "ccmp", "kern";
|
|
||||||
background: var(--bg);
|
|
||||||
color: var(--color);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.italic { font-variation-settings: "slnt" 10deg; }
|
.italic { font-variation-settings: "slnt" 10deg; }
|
||||||
|
|
||||||
input, button, textarea, select {
|
input, button, textarea, select { font: inherit; }
|
||||||
font: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
h1 { font-size: 1.88rem; }
|
||||||
font-size: 1.88rem;
|
h2 { font-size: 1.66rem; }
|
||||||
}
|
h3 { font-size: 1.44rem; }
|
||||||
h2 {
|
h4 { font-size: 1.22rem; }
|
||||||
font-size: 1.66rem;
|
h5 { font-size: 1.0rem; }
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
font-size: 1.44rem;
|
|
||||||
}
|
|
||||||
h4 {
|
|
||||||
font-size: 1.22rem;
|
|
||||||
}
|
|
||||||
h5 {
|
|
||||||
font-size: 1.0rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
a, a:visited, button {
|
a, a:visited, button { text-decoration: underline; border: none; padding: 0; margin: 0; font-weight: bold; cursor: pointer; color: var(--main); background: transparent; }
|
||||||
text-decoration: underline;
|
h1 { margin-bottom: 1rem; }
|
||||||
border: none;
|
#main { flex: 2 1 auto; display: flex; flex-direction: column; }
|
||||||
padding: 0;
|
.page { flex: 2 1 auto; display: flex; flex-direction: column; }
|
||||||
margin: 0;
|
.modal { flex: 2 1 auto; display: flex; flex-direction: column; justify-content: center; align-items: center; }
|
||||||
font-weight: bold;
|
.modal h3 { text-align: center; margin-bottom: 1rem; }
|
||||||
cursor: pointer;
|
.error { color: var(--error); }
|
||||||
color: var(--main);
|
.modal form { background: var(--bg-component); border-radius: 20px; width: 100%; max-width: 500px; margin: 2rem; padding: 1rem; display: flex; flex-direction: column; }
|
||||||
background: transparent;
|
.loading-spinner { display: inline-block; width: 80px; height: 80px; margin-top: 0.5rem; align-self: center; }
|
||||||
}
|
.loading-spinner:after { content: " "; display: block; width: 64px; height: 64px; margin: 8px; border-radius: 50%; border: 6px solid var(--main); border-color: var(--main) transparent var(--main) transparent; animation: loading-spinner 1.2s linear infinite; }
|
||||||
|
@keyframes loading-spinner { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
||||||
h1 {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
#main {
|
|
||||||
flex: 2 1 auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page {
|
|
||||||
flex: 2 1 auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal {
|
|
||||||
flex: 2 1 auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal h3 {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
color: var(--error);
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal form {
|
|
||||||
background: var(--bg-component);
|
|
||||||
border-radius: 20px;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 500px;
|
|
||||||
margin: 2rem;
|
|
||||||
padding: 1rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-spinner {
|
|
||||||
display: inline-block;
|
|
||||||
width: 80px;
|
|
||||||
height: 80px;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
.loading-spinner:after {
|
|
||||||
content: " ";
|
|
||||||
display: block;
|
|
||||||
width: 64px;
|
|
||||||
height: 64px;
|
|
||||||
margin: 8px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 6px solid var(--main);
|
|
||||||
border-color: var(--main) transparent var(--main) transparent;
|
|
||||||
animation: loading-spinner 1.2s linear infinite;
|
|
||||||
}
|
|
||||||
@keyframes loading-spinner {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-upload {
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
background-size: cover;
|
|
||||||
background-image: url('./assets/bg_admin.avif');
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-upload footer {
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
background: var(--bg-component-half);
|
|
||||||
align-self: center;
|
|
||||||
padding: 0.25rem 2rem;
|
|
||||||
border-radius: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Common components */
|
/* Common components */
|
||||||
|
|
||||||
|
.row { display: flex; }
|
||||||
|
.column { display: flex; flex-direction: column; }
|
||||||
|
.filler { flex-grow: 2; }
|
||||||
|
|
||||||
input[type=text],
|
input[type=text],
|
||||||
input[type=password],
|
input[type=password],
|
||||||
input[type=datetime] {
|
input[type=datetime] { border: 1px solid var(--main); background: #fff; color: var(--color); border-radius: 0; padding: 0.25rem; line-height: 1rem; outline: none; width: 100%; }
|
||||||
border: 1px solid var(--main);
|
|
||||||
background: #fff;
|
|
||||||
color: var(--color);
|
|
||||||
border-radius: 0;
|
|
||||||
padding: 0.25rem;
|
|
||||||
line-height: 1rem;
|
|
||||||
outline: none;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=text]:disabled,
|
input[type=text]:disabled,
|
||||||
input[type=password]:disabled,
|
input[type=password]:disabled,
|
||||||
input[type=datetime]:disabled {
|
input[type=datetime]:disabled { background: var(--bg-component); border-color: var(--color-alt); color: var(--color-alt); }
|
||||||
background: var(--bg-component);
|
|
||||||
border-color: var(--color-alt);
|
|
||||||
color: var(--color-alt);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row input:disabled + button {
|
.form-row input:disabled + button { border-color: var(--color-alt); }
|
||||||
border-color: var(--color-alt);
|
.form-row { display: flex; position: relative; flex-wrap: wrap; }
|
||||||
}
|
.form-row input { flex: 2 1 auto; }
|
||||||
|
.form-row > input { width: auto; }
|
||||||
.form-row {
|
.form-row .form-column { display: flex; flex-direction: column; justify-content: space-around; }
|
||||||
display: flex;
|
.form-row button { min-width: 30px; text-align: center; border: 1px solid var(--main); border-left: none; background: var(--bg-component); text-decoration: none; }
|
||||||
position: relative;
|
.form-row.image-banner { background-size: cover; background-color: white; border: 2px dashed var(--main); aspect-ratio: 16 / 9; align-self: center; width: 100%; max-width: 360px; }
|
||||||
}
|
.form-row .cover { position: absolute; top: 0; left: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; }
|
||||||
|
|
||||||
.form-row input {
|
|
||||||
flex: 2 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row .form-column {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-around;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row button {
|
|
||||||
min-width: 30px;
|
|
||||||
text-align: center;
|
|
||||||
border: 1px solid var(--main);
|
|
||||||
border-left: none;
|
|
||||||
background: var(--bg-component);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row.image-banner {
|
|
||||||
background-size: cover;
|
|
||||||
background-color: white;
|
|
||||||
border: 2px dashed var(--main);
|
|
||||||
aspect-ratio: 16 / 9;
|
|
||||||
align-self: center;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 360px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row .cover {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
opacity: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=text]:focus,
|
input[type=text]:focus,
|
||||||
input[type=password]:focus,
|
input[type=password]:focus,
|
||||||
input[type=datetime]:focus {
|
input[type=datetime]:focus { outline: 1px solid var(--main); }
|
||||||
outline: 1px solid var(--main);
|
.button, input[type=submit] { background: var(--main); color:var(--main-fg); border-radius: 10px; padding: 0.25rem 1rem; border: none; margin: 1rem 0 2rem; align-self: center; cursor: pointer; text-decoration: none; }
|
||||||
}
|
|
||||||
|
|
||||||
.button, input[type=submit] {
|
.button.spinner, input[type=submit].spinner { height: 2rem; margin-top: 2rem; margin-bottom: 1.5rem; }
|
||||||
background: var(--main);
|
.button-alert { background: var(--error-bg); color: var(--color); }
|
||||||
color:var(--main-fg);
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 0.25rem 1rem;
|
|
||||||
border: none;
|
|
||||||
margin: 1rem 0 2rem;
|
|
||||||
align-self: center;
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.spinner, input[type=submit].spinner {
|
.loading-bar { border: 1px solid var(--main); background: var(--bg-component); }
|
||||||
height: 2rem;
|
.loading-bar::after { height: 1rem; background: var(--main); min-width: 1px; content: ''; display: block; width: var(--progress); transition: width 3s; }
|
||||||
margin-top: 2rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-alert {
|
form p, label { font-size: 0.75rem; font-weight: 500; margin: 0.75rem 0 0.5rem 0; display: block; }
|
||||||
background: var(--error-bg);
|
form p.separator { color: var(--color-alt); margin-top: 1.5rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--color-alt); }
|
||||||
color: var(--color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-bar {
|
|
||||||
border: 1px solid var(--main);
|
|
||||||
background: var(--bg-component);
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-bar::after {
|
|
||||||
height: 1rem;
|
|
||||||
background: var(--main);
|
|
||||||
min-width: 1px;
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
width: var(--progress);
|
|
||||||
transition: width 3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
form p, label {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 500;
|
|
||||||
margin: 0.75rem 0 0.5rem 0;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
form p.separator {
|
|
||||||
color: var(--color-alt);
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
border-bottom: 1px solid var(--color-alt);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Nav */
|
/* Nav */
|
||||||
|
|
||||||
#header {
|
#header { background: var(--bg-component); }
|
||||||
background: var(--bg-component);
|
|
||||||
}
|
|
||||||
|
|
||||||
#header nav,
|
#header nav,
|
||||||
#header .nav {
|
#header .nav { display: flex; padding: 0.5rem 1rem 0.5rem 0; }
|
||||||
display: flex;
|
|
||||||
padding: 0.5rem 1rem 0.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header nav a,
|
#header nav a,
|
||||||
#header nav button {
|
#header nav button { margin-left: 1rem; }
|
||||||
margin-left: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header h4 {
|
#header h4 { flex: 2 1 auto; margin: -0.5rem; }
|
||||||
flex: 2 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header h4 a,
|
#header h4 a,
|
||||||
#header h4 a:visited {
|
#header h4 a:visited { background: url('/assets/logo_nobg.svg') left center no-repeat; background-size: auto calc(3rem - 4px); height: 3rem; display: inline-block; padding-left: 3rem; line-height: 3rem; }
|
||||||
color: var(--color);
|
|
||||||
}
|
|
||||||
|
|
||||||
#header .change {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
line-height: 2rem;
|
|
||||||
font-weight: normal;
|
|
||||||
padding: 0 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
#header .link { font-size: 1.0rem; line-height: 2rem; font-weight: normal; padding: 0 0.5rem; }
|
||||||
|
#header .changelang { font-size: 1.2rem; }
|
||||||
#header .logout,
|
#header .logout,
|
||||||
#header .upload {
|
#header .upload { padding: 0.25rem 1.5rem; border-radius: 2rem; text-decoration: none; }
|
||||||
padding: 0.25rem 1.5rem;
|
|
||||||
border-radius: 2rem;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header .logout {
|
#header .logout { background: var(--bg-component-alt); color: var(--color); }
|
||||||
background: var(--bg-component-alt);
|
#header .upload { background: var(--main); color: var(--main-fg); }
|
||||||
color: var(--color);
|
#header .nav { overflow-x: hidden; padding: 0.75rem 1rem 0.25rem; min-height: 4rem; align-items: flex-start; border-bottom: 1px solid #0001; }
|
||||||
}
|
#header .nav:hover { overflow-x: auto; }
|
||||||
|
#header .nav .inner { flex: 2 1 auto; display: flex; justify-content: center; }
|
||||||
#header .upload {
|
#header .nav a { margin: 0 0.25rem; padding: 0.25rem 1.5rem; border-radius: 3rem; }
|
||||||
background: var(--main);
|
#header .nav a.empty { opacity: 0.5; }
|
||||||
color: var(--main-fg);
|
#header .nav a.active { background: var(--bg-component-alt); color: var(--color); text-decoration: none; }
|
||||||
}
|
#header .error { background: var(--error-bg); color: var(--color); font-size: 0.8rem; text-align: center; padding: 0.25rem; cursor: pointer; }
|
||||||
|
|
||||||
#header .nav {
|
|
||||||
overflow-x: hidden;
|
|
||||||
padding: 0.75rem 1rem 0.25rem;
|
|
||||||
min-height: 4rem;
|
|
||||||
align-items: flex-start;
|
|
||||||
border-bottom: 1px solid #0001;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header .nav:hover {
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header .nav .inner {
|
|
||||||
flex: 2 1 auto;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header .nav a {
|
|
||||||
margin: 0 0.25rem;
|
|
||||||
padding: 0.25rem 1.5rem;
|
|
||||||
border-radius: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header .nav a.empty {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header .nav a.active {
|
|
||||||
background: var(--bg-component-alt);
|
|
||||||
color: var(--color);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header .error {
|
|
||||||
background: var(--error-bg);
|
|
||||||
color: var(--color);
|
|
||||||
font-size: 0.8rem;
|
|
||||||
text-align: center;
|
|
||||||
padding: 0.25rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main */
|
/* Main */
|
||||||
|
|
||||||
.full-error {
|
.full-error { background: var(--error-bg); color: var(--color); font-size: 0.8rem; text-align: center; padding: 0.25rem; cursor: pointer; flex: 2 1 auto; display: flex; justify-content: center; align-items: center; }
|
||||||
background: var(--error-bg);
|
|
||||||
color: var(--color);
|
|
||||||
font-size: 0.8rem;
|
|
||||||
text-align: center;
|
|
||||||
padding: 0.25rem;
|
|
||||||
cursor: pointer;
|
|
||||||
flex: 2 1 auto;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
footer { text-align: center; padding: 1rem; }
|
||||||
text-align: center;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer a {
|
footer a { font-size: 0.8rem; }
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* login */
|
/* login */
|
||||||
|
/* upload */
|
||||||
|
|
||||||
.page-login form a {
|
.page-login,
|
||||||
text-align: center;
|
.page-upload { background-image: url('./assets/bg.avif'); background-repeat: no-repeat; background-position: center; background-size: cover; }
|
||||||
font-weight: normal;
|
.page-login .modal form,
|
||||||
}
|
.page-upload .modal form { backdrop-filter: blur(10px); background: var(--bg-component-half); }
|
||||||
|
|
||||||
.page-login {
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-login .modal form {
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
background: var(--bg-component-half);
|
|
||||||
}
|
|
||||||
|
|
||||||
.avifsupport .page-login {
|
|
||||||
background-image: url('./assets/bg.avif');
|
|
||||||
}
|
|
||||||
|
|
||||||
.jpegonly .page-login {
|
|
||||||
background-image: url('./assets/bg.jpg');
|
|
||||||
}
|
|
||||||
|
|
||||||
/* browse */
|
/* browse */
|
||||||
|
|
||||||
.gallery {
|
.gallery { margin: 1rem; display: flex; flex-direction: column; }
|
||||||
margin: 1rem;
|
.gallery-year { margin: 1rem; padding: 0 0 1rem; border-bottom: 1px solid var(--main); text-align: center; font-size: 2rem; }
|
||||||
display: flex;
|
.gallery-month { margin: 0rem 1rem 1rem; font-size: 1.2rem; border-bottom: 1px solid #0003; }
|
||||||
flex-direction: column;
|
.gallery .group { display: flex; flex-wrap: wrap; }
|
||||||
}
|
.gallery .group a { width: 40vw; max-width: 320px; aspect-ratio: 16 / 9; display: flex; flex-direction: column; justify-content: flex-end; background: url('./assets/placeholder.avif') center no-repeat; background-size: cover; margin: 0 1rem 1rem; text-align: center; border: 1px solid var(--main); }
|
||||||
|
|
||||||
.gallery-year {
|
.gallery .group a span { align-self: stretch; text-align: center; background: #fffb; }
|
||||||
margin: 1rem;
|
|
||||||
padding: 0 0 1rem;
|
|
||||||
border-bottom: 1px solid var(--main);
|
|
||||||
text-align: center;
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-month {
|
|
||||||
margin: 0rem 1rem 1rem;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
border-bottom: 1px solid #0003;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery .group {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery .group a {
|
|
||||||
width: 40vw;
|
|
||||||
max-width: 320px;
|
|
||||||
aspect-ratio: 16 / 9;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-end;
|
|
||||||
background: url('./assets/placeholder.avif') center no-repeat;
|
|
||||||
background-size: cover;
|
|
||||||
margin: 0 1rem 1rem;
|
|
||||||
text-align: center;
|
|
||||||
border: 1px solid var(--main);
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery .group a span {
|
|
||||||
align-self: stretch;
|
|
||||||
text-align: center;
|
|
||||||
background: #fffb;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Player */
|
/* Player */
|
||||||
|
|
||||||
.player {
|
.player { background: black; text-align: center; margin-bottom: 1rem; }
|
||||||
background: black;
|
.player video { margin: 0 auto; width: 1280px; max-width: 100%; aspect-ratio: 16 / 9; }
|
||||||
|
|
||||||
|
/* article */
|
||||||
|
|
||||||
|
.article { width: 100%; max-width: 1280px; padding: 0.5rem; align-self: center; margin-bottom: 5rem; }
|
||||||
|
.article .full-error { margin-top: 1rem; }
|
||||||
|
.article-name { flex: 2 1 auto; margin-left: 1rem; }
|
||||||
|
.image-banner { height: 160px; }
|
||||||
|
.article h1 { margin: 0; }
|
||||||
|
.article h1,
|
||||||
|
.article p { padding: 0 0.5rem 0.5rem; }
|
||||||
|
|
||||||
|
/* table */
|
||||||
|
|
||||||
|
.table { display: grid; column-gap: 0; row-gap: 0; grid-template-columns: minmax(150px, 1.33fr) minmax(150px, 2.33fr); }
|
||||||
|
.table-row { display: contents; }
|
||||||
|
.table-row:nth-child(odd) .table-item { background: #f8f6ff; }
|
||||||
|
.table-item { padding: 0.5rem; }
|
||||||
|
|
||||||
|
/* holdbutton */
|
||||||
|
.holdbutton {
|
||||||
|
--hold-bg: var(--bg-component);
|
||||||
|
--hold-color: var(--main);
|
||||||
|
--hold-fill: var(--main);
|
||||||
|
--hold-fill-fg: white;
|
||||||
|
display: inline-block;
|
||||||
|
background: var(--hold-bg);
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.holdbutton .inner {
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 1rem;
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
background: var(--hold-color);
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
.holdbutton.holdbutton-active {
|
||||||
|
background: linear-gradient( var(--hold-fill) , var(--hold-fill)) var(--hold-bg) no-repeat 0 0;
|
||||||
|
background-size: 0 100%;
|
||||||
|
animation: stripes 2s linear 1 forwards;
|
||||||
|
}
|
||||||
|
.holdbutton.holdbutton-active div.inner {
|
||||||
|
background: linear-gradient( var(--hold-fill-fg), var(--hold-fill-fg)) var(--hold-color) no-repeat 0 0;
|
||||||
|
background-size: 0 100%;
|
||||||
|
animation: stripes 2s linear 1 forwards;
|
||||||
|
background-clip: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.player video {
|
@keyframes stripes { to { background-size: 100% 100%; } }
|
||||||
margin: 0 auto;
|
|
||||||
width: 1280px;
|
|
||||||
max-width: 100vw;
|
|
||||||
aspect-ratio: 16 / 9;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|