dev
All checks were successful
/ deploy (push) Successful in -34h50m18s

This commit is contained in:
TheThing 2023-11-29 19:19:41 +00:00
parent fad7acd5f7
commit bdeeff3794
7 changed files with 334 additions and 136 deletions

View file

@ -39,11 +39,12 @@ export default class ArticleRoutes extends OriginalArticleRoutes {
ctx.body = { ctx.body = {
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'),
} }
} }
/** PUT: /api/auth/articles/:id */ /** PUT: /api/auth/articles/:id */
async updateCreateArticle(ctx) { async updateCreateArticle(ctx) {
return this.private_getUpdateArticle(ctx, ctx.req.body, null, ctx.req.body.media) return this.private_getUpdateArticle(ctx, ctx.req.body, ctx.req.body.banner, ctx.req.body.media)
} }
} }

View file

@ -21,13 +21,37 @@ const Input = {
this.tempus = null this.tempus = null
this.subscription = null this.subscription = null
this.input = null this.input = null
this.preview = null
}, },
onremove: function(vnode) { onremove: function(vnode) {
if (!this.tempus) return
if (this.subscription) this.subscription.unsubscribe() if (this.subscription) this.subscription.unsubscribe()
this.tempus.dispose() if (this.tempus) {
this.tempus = null this.tempus.dispose()
this.tempus = null
}
if (this.preview) {
this.preview.clear()
}
},
imageChanged: function(vnode, e) {
let file = vnode.attrs.form[vnode.attrs.formKey] = e.currentTarget.files?.[0] || null
if (this.preview) {
this.preview.clear()
this.preview = null
}
if (!file) return
if (file.type.startsWith('image')) {
this.preview = {
file: file,
preview: URL.createObjectURL(file),
clear: function() {
URL.revokeObjectURL(this.preview)
},
}
}
}, },
getInput: function(vnode) { getInput: function(vnode) {
@ -67,6 +91,21 @@ const Input = {
onclick: () => { this.tempus.toggle(); return false }, onclick: () => { this.tempus.toggle(); return false },
}) })
]) ])
case 'image':
let imageLink = this.preview && this.preview.preview || vnode.attrs.form[vnode.attrs.formKey]
return m('div.form-row.image-banner', {
style: {
'background-image': typeof imageLink === 'string' ? 'url("' + (imageLink) + '")' : null,
},
}, [
m('input.cover', {
type: 'file',
accept: vnode.attrs.accept,
disabled: api.loading,
onchange: this.imageChanged.bind(this, vnode),
}),
])
default: default:
return m('input', { return m('input', {
disabled: api.loading, disabled: api.loading,

View file

@ -39,6 +39,8 @@ const i18n = {
'Dagsetning vantar'], 'Dagsetning vantar'],
upload_missing_file: ['Video file missing', upload_missing_file: ['Video file missing',
'Myndaskrá vantar'], 'Myndaskrá vantar'],
upload_missing_banner: ['Poster image missing',
'Mynd vantar'],
upload_error: ['Error while uploading: {0}', upload_error: ['Error while uploading: {0}',
'Villa við að hlaða upp myndefni: {0}'], 'Villa við að hlaða upp myndefni: {0}'],
unsplash: ['Photo by {0} on {1}', unsplash: ['Photo by {0} on {1}',

View file

@ -1,6 +1,7 @@
const m = require('mithril') const m = require('mithril')
const api = require('./api') const api = require('./api')
const Authentication = require('./authentication') const Authentication = require('./authentication')
const Input = require('./input')
const lang = require('./lang') const lang = require('./lang')
const Article = { const Article = {
@ -9,6 +10,15 @@ const Article = {
this.error = '' this.error = ''
this.path = '' this.path = ''
this.data = null this.data = null
this.editing = false
this.form = {
title: 'Sunnudagssamkoma',
date: new Date(),
banner: null,
metadata: {
speaker: '',
},
}
this.onbeforeupdate(vnode) this.onbeforeupdate(vnode)
}, },
@ -30,16 +40,25 @@ const Article = {
}) })
.then((result) => { .then((result) => {
this.data = result.article this.data = result.article
this.afterData() this.gotArticle(vnode)
}, (err) => { }, (err) => {
this.error = err.message this.error = err.message
}) })
}, },
afterData: function() { gotArticle: function(vnode) {
if (!this.data) { if (!this.data) {
this.error = 'Article not found' return this.error = 'Article not found'
} }
this.form.title = this.data.name
this.form.date = new Date(this.data.publish_at)
this.form.banner = this.data.banner_path
this.form.metadata.speaker = this.data.content.speaker
},
updatevideo: function(vnode, e) {
this.error = ''
if (this.error) return false
}, },
view: function(vnode) { view: function(vnode) {
@ -58,7 +77,7 @@ const Article = {
crossorigin: '', crossorigin: '',
controls: true, controls: true,
preload: 'none', preload: 'none',
poster: '/assets/placeholder.avif', poster: this.data.banner_path || '/assets/placeholder.avif',
}, [ }, [
m('source', { m('source', {
src: this.data.media_path src: this.data.media_path
@ -67,6 +86,46 @@ const Article = {
]), ]),
] ]
: null, : null,
this.editing
? m('form.article', {
onsubmit: this.updatevideo.bind(this, vnode),
}, [
m('div.form-row', [
m('div.form-columns', [
m(Input, {
label: 'Mynd',
type: 'file',
accept: 'image/*',
utility: 'image',
form: this.form,
formKey: 'banner',
}),
]),
m('div.form-columns', [
m(Input, {
label: 'Title',
form: this.form,
formKey: 'title',
}),
m(Input, {
label: 'Date (dd.mm.yyyy)',
type: 'text',
utility: 'datetime',
form: this.form,
formKey: 'date',
}),
]),
]),
m('p.separator', 'Optional'),
m(Input, {
label: 'Speaker',
form: this.form.metadata,
formKey: 'speaker',
}),
])
: m('div.article', [
m('h1', this.data.name)
]),
] ]
}, },
} }

View file

@ -11,7 +11,10 @@ const Browse = {
mArticles: function(vnode, articles) { mArticles: function(vnode, articles) {
return articles.map(article => { return articles.map(article => {
return m(m.route.Link, { href: ['', article.publish_at.getFullYear(), article.publish_at.getMonth() + 1, article.id].join('/') }, [ return m(m.route.Link, {
href: ['', article.publish_at.getFullYear(), article.publish_at.getMonth() + 1, article.id].join('/'),
style: article.avif_preview ? `background-image: url('${article.avif_preview}')` : null,
}, [
m('span', article.publish_at.toUTCString()), m('span', article.publish_at.toUTCString()),
m('span', article.name), m('span', article.name),
]) ])

View file

@ -16,12 +16,14 @@ const Upload = {
d.setSeconds(0) d.setSeconds(0)
d.setMilliseconds(0) d.setMilliseconds(0)
this.cache = null this.cacheVideo = null
this.cacheImage = null
this.uploading = null this.uploading = null
this.form = { this.form = {
title: 'Sunnudagssamkoma', title: 'Sunnudagssamkoma',
date: d, date: d,
file: null, file: null,
banner: null,
metadata: { metadata: {
speaker: '', speaker: '',
}, },
@ -33,7 +35,8 @@ 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.error) return false if (this.error) return false
@ -43,8 +46,69 @@ const Upload = {
body: this.form, body: this.form,
}) })
.then(res => { .then(res => {
if (this.cache?.file === this.form.file) { if (this.cacheImage?.file === this.form.banner) {
return this.cache return this.cacheImage
}
var data = new FormData()
data.append('file', this.form.banner)
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: 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
})
})
.then(res => {
if (this.cacheVideo?.file === this.form.file) {
return this.cacheVideo
} }
return api.uploadFileProgress({ return api.uploadFileProgress({
@ -59,7 +123,7 @@ const Upload = {
}) })
}) })
.then(res => { .then(res => {
this.cache = { this.cacheVideo = {
file: this.form.file, file: this.form.file,
filename: res.filename, filename: res.filename,
path: res.path, path: res.path,
@ -79,10 +143,19 @@ const Upload = {
is_featured: false, is_featured: false,
media: { media: {
filename: res.filename, filename: res.filename,
type: this.form.file.type,
path: res.path, path: res.path,
type: this.form.file.type,
size: this.form.file.size, size: this.form.file.size,
} },
banner: {
filename: this.cacheImage.medium.filename,
path: this.cacheImage.medium.path,
type: 'image/avif',
size: this.form.file.size,
preview: {
base64: this.cacheImage.preview.base64,
},
},
}, },
}) })
}) })
@ -138,6 +211,14 @@ const Upload = {
form: this.form, form: this.form,
formKey: 'file', formKey: 'file',
}), }),
m(Input, {
label: 'Mynd',
type: 'file',
accept: 'image/*',
utility: 'image',
form: this.form,
formKey: 'banner',
}),
m('p.separator', 'Optional'), m('p.separator', 'Optional'),
m(Input, { m(Input, {
label: 'Speaker', label: 'Speaker',

View file

@ -79,104 +79,6 @@ a, a:visited, button {
background: transparent; background: transparent;
} }
input[type=text],
input[type=password],
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%;
}
input[type=text]:disabled,
input[type=password]:disabled,
input[type=datetime]:disabled {
background: var(--bg-component);
border-color: var(--color-alt);
color: var(--color-alt);
}
.form-row input:disabled + button {
border-color: var(--color-alt);
}
.form-row {
display: flex;
position: relative;
}
.form-row input {
flex: 2 1 auto;
}
.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 .cover {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
.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.spinner, input[type=submit].spinner {
height: 2rem;
margin-top: 2rem;
margin-bottom: 1.5rem;
}
.button-alert {
background: var(--error-bg);
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;
}
input[type=text]:focus,
input[type=password]:focus,
input[type=datetime]:focus {
outline: 1px solid var(--main);
}
h1 { h1 {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
@ -206,20 +108,6 @@ h1 {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
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);
}
.error { .error {
color: var(--error); color: var(--error);
} }
@ -266,6 +154,7 @@ form p.separator {
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
background-size: cover; background-size: cover;
background-image: url('./assets/bg_admin.avif');
} }
.page-upload footer { .page-upload footer {
@ -277,11 +166,133 @@ form p.separator {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.avifsupport .page-upload { /* Common components */
background-image: url('./assets/bg_admin.avif');
input[type=text],
input[type=password],
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%;
} }
.jpegonly .page-upload {
background-image: url('./assets/bg_admin.jpg'); input[type=text]:disabled,
input[type=password]:disabled,
input[type=datetime]:disabled {
background: var(--bg-component);
border-color: var(--color-alt);
color: var(--color-alt);
}
.form-row input:disabled + button {
border-color: var(--color-alt);
}
.form-row {
display: flex;
position: relative;
}
.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=password]:focus,
input[type=datetime]:focus {
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.spinner, input[type=submit].spinner {
height: 2rem;
margin-top: 2rem;
margin-bottom: 1.5rem;
}
.button-alert {
background: var(--error-bg);
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 */
@ -463,13 +474,15 @@ footer a {
justify-content: flex-end; justify-content: flex-end;
background: url('./assets/placeholder.avif') center no-repeat; background: url('./assets/placeholder.avif') center no-repeat;
background-size: cover; background-size: cover;
padding: 1rem;
margin: 0 1rem 1rem; margin: 0 1rem 1rem;
text-align: center; text-align: center;
border: 1px solid var(--main); border: 1px solid var(--main);
text-shadow: 2px 0 10px #fffc, -2px 0 10px #fffc, 0 2px 10px #fffc, 0 -2px 10px #fffc, }
1px 1px 10px #fffc, -1px -1px 10px #fffc, 1px -1px 10px #fffc, -1px 1px 10px #fffc;
.gallery .group a span {
align-self: stretch;
text-align: center;
background: #fffb;
} }
/* Player */ /* Player */