Development

master
Jonatan Nilsson 2022-07-22 11:18:33 +00:00
parent bd2e0d0fff
commit ebd3cd4d26
8 changed files with 335 additions and 174 deletions

View File

@ -1,4 +1,5 @@
import { parseFiles } from '../file/util.mjs'
import { parseArticles, parseArticle } from './util.mjs'
import { upload } from '../media/upload.mjs'
export default class ArticleRoutes {
@ -13,7 +14,7 @@ export default class ArticleRoutes {
let res = await ctx.db.safeCallProc('article_get_single', [ctx.params.path])
let out = {
article: res.results[0][0] || null,
article: parseArticle(res.results[0][0]),
files: parseFiles(res.results[1]),
}
@ -29,33 +30,124 @@ export default class ArticleRoutes {
])
let out = {
articles: res.results[0],
articles: parseArticles(res.results[0]),
total_articles: res.results[0][0].total_articles,
}
ctx.body = out
}
async private_getUpdateArticle(ctx, body = null, banner = null, media = null) {
let params = [
ctx.state.auth_token,
ctx.params.path
]
if (body) {
params = params.concat([
body.name,
body.page_id === 'null' ? null : Number(body.page_id),
body.path,
body.content,
new Date(body.publish_at),
Number(body.admin_id),
body.is_featured === 'true' ? 1 : 0,
0,
])
if (banner) {
params = params.concat([
banner.filename,
banner.type,
banner.path,
banner.size,
banner.preview.base64,
banner.sizes.small.avif.path.replace(/_small\.avif$/, ''),
JSON.stringify(banner.sizes),
0,
])
} else {
params = params.concat([
null,
null,
null,
null,
null,
null,
null,
null,
])
}
if (media) {
params = params.concat([
media.filename,
media.type,
media.path,
media.size,
media.preview.base64,
media.sizes.small.avif.path.replace(/_small\.avif$/, ''),
JSON.stringify(media.sizes),
0,
])
} else {
params = params.concat([
null,
null,
null,
null,
null,
null,
null,
null,
])
}
}
console.log(params)
let res = await ctx.db.safeCallProc('article_auth_get_update_create', params)
let out = {
article: parseArticle(res.results[0][0]),
files: parseFiles(res.results[1]),
staff: res.results[2],
}
if (out.article) {
if (out.article.content[0] === '{') {
try {
out.article.content = JSON.parse(out.article.content)
} catch (err) {
out.article.content = {
time: new Date().getTime(),
blocks: [
{id: '1', type: 'paragraph', data: { text: 'Error parsing article content: ' + err.message }},
],
version: '2.25.0'
}
}
} else if (out.article.content) {
out.article.content = {
time: new Date().getTime(),
blocks: [
{id: '1', type: 'htmlraw', data: { html: out.article.content }},
],
version: '2.25.0'
}
}
} else {
out.article = {
publish_at: new Date()
}
}
ctx.body = out
}
/** GET: /api/auth/articles/:path */
async auth_getSingleArticle(ctx) {
let res = await ctx.db.safeCallProc('article_auth_get_update_create', [
ctx.state.auth_token,
ctx.params.path
])
let out = {
article: res.results[0][0] || null,
files: parseFiles(res.results[1]),
staff: res.results[2],
}
ctx.body = out
auth_getSingleArticle(ctx) {
return this.private_getUpdateArticle(ctx)
}
/** PUT: /api/auth/articles/:path */
async auth_updateCreateSingleArticle(ctx) {
console.log(ctx.req.files)
console.log(ctx.req.body)
let newBanner = null
@ -78,9 +170,45 @@ export default class ArticleRoutes {
await Promise.all(promises)
console.log(newBanner)
console.log(newMedia)
ctx.body = {}
return this.private_getUpdateArticle(ctx, ctx.req.body, newBanner, newMedia)
}
/** DELETE: /api/auth/articles/:path */
async auth_removeSingleArticle(ctx) {
let params = [
ctx.state.auth_token,
ctx.params.path,
// Article data
null,
null,
null,
null,
null,
null,
null,
1,
// Banner data
null,
null,
null,
null,
null,
null,
null,
1,
// Media data
null,
null,
null,
null,
null,
null,
null,
1,
]
await ctx.db.safeCallProc('article_auth_get_update_create', params)
ctx.status = 204
}
}

View File

@ -8,9 +8,11 @@ export function upload(file) {
let token = client.createJwt({ iss: media.iss }, media.secret)
let out = {
small: {},
medium: {},
large: {},
sizes: {
small: {},
medium: {},
large: {},
}
}
return client.upload(media.path + '?token=' + token, { file: {
@ -30,9 +32,11 @@ export function upload(file) {
out.filename = res.filename
out.path = res.path
out.preview = res.preview
out.small.avif = res.small
out.medium.avif = res.medium
out.large.avif = res.large
out.sizes.small.avif = res.small
out.sizes.medium.avif = res.medium
out.sizes.large.avif = res.large
out.size = file.size
out.type = file.type
return client.post(media.path + '/' + out.filename + '?token=' + token, {
small: media.small.jpeg,
@ -40,9 +44,9 @@ export function upload(file) {
large: media.large.jpeg,
})
.then(res => {
out.small.jpeg = res.small
out.medium.jpeg = res.medium
out.large.jpeg = res.large
out.sizes.small.jpeg = res.small
out.sizes.medium.jpeg = res.medium
out.sizes.large.jpeg = res.large
})
})
.then(() => {

View File

@ -18,6 +18,7 @@ $headtext: $primary-light-fg;
border-collapse: collapse;
border-spacing: 0;
font-size: 0.8em;
position: relative;
thead th {
background-color: $headcolor;
@ -65,9 +66,49 @@ $headtext: $primary-light-fg;
align-items: center;
}
.input-group {}
.input-row {
display: flex;
& > * {
margin-right: 1rem;
flex: 2 1 auto;
}
& > .small {
flex: 0 0 auto;
}
& > *:last-child {
margin-right: 0;
}
}
.admin-wrapper .loading-spinner {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #00000066;
z-index: 1000;
}
@import 'admin/admin';
@import 'widgets/admin';
.codex-editor {
margin-bottom: 0.5rem;
}
input[type=checkbox] {
display: block;
height: 20px;
margin: 0.5rem 0;
width: 20px;
}
.darkmodeon {
.maincontainer .admin-wrapper {
color: $main-fg;

View File

@ -52,6 +52,7 @@
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
z-index: 10;
/* user-select: none; */
}
.cal-header, .cal-row {

View File

@ -34,7 +34,7 @@ article.editarticle {
}
form {
padding: 0 40px 20px;
padding: 0 2rem 1rem;
textarea {
height: 300px;
@ -83,15 +83,16 @@ article.editarticle {
}
.fileupload {
align-self: center;
padding: 0.5em;
margin: 0.5em 0;
min-width: 250px;
align-self: flex-start;
padding: 0.5rem;
margin: 0.5rem 0 0.5rem 2rem;
min-width: 150px;
border: none;
border: 1px solid $secondary-bg;
background: $secondary-light-bg;
color: $secondary-light-fg;
position: relative;
text-align: center;
input {
position: absolute;
@ -112,7 +113,7 @@ article.editarticle {
width: 100%;
display: flex;
flex-direction: column;
padding: 10px 40px 0;
padding: 1rem 2rem 0;
text-align: left;
h4 {

View File

@ -8,8 +8,6 @@ const Editor = require('./editor')
const EditArticle = {
oninit: function(vnode) {
this.editor = null
this.loading = false
this.showLoading = null
this.data = {
@ -44,6 +42,20 @@ const EditArticle = {
fetchArticle: function(vnode) {
this.lastid = m.route.param('id')
let id = this.lastid
if (id === 'add') {
id = '0'
}
this.error = ''
return this.requestArticle(
common.sendRequest({
method: 'GET',
url: '/api/auth/articles/' + id,
}))
},
requestArticle: function(data) {
this.error = ''
if (this.showLoading) {
@ -60,12 +72,8 @@ const EditArticle = {
this.loading = true
}
return common.sendRequest({
method: 'GET',
url: '/api/auth/articles/' + this.lastid,
})
data
.then((result) => {
console.log('result', result)
this.data = result
if (this.data.article) {
this.data.article.publish_at = new Date(this.data.article.publish_at)
@ -85,7 +93,6 @@ const EditArticle = {
},
updateValue: function(name, e) {
console.log(name, e.currentTarget.value)
if (name === 'is_featured') {
this.data.article[name] = e.currentTarget.checked
} else {
@ -120,7 +127,11 @@ const EditArticle = {
save: function(vnode, e) {
e.preventDefault()
console.log(this.data)
let id = this.lastid
if (id === 'add') {
id = '0'
}
let formData = new FormData()
if (this.newBanner) {
@ -133,93 +144,27 @@ const EditArticle = {
formData.append('id', this.data.article.id)
}
formData.append('admin_id', this.data.article.admin_id)
formData.append('admin_id', this.data.article.admin_id || this.data.staff[0].id)
formData.append('name', this.data.article.name)
formData.append('content', this.data.article.content)
formData.append('is_featured', this.data.article.is_featured)
formData.append('is_featured', this.data.article.is_featured || false)
formData.append('path', this.data.article.path)
formData.append('page_id', this.data.article.page_id)
formData.append('publish_at', this.data.article.publish_at)
formData.append('page_id', this.data.article.page_id || null)
formData.append('publish_at', this.dateInstance.inputElem.value.replace(', ', 'T') + 'Z')
this.loading = true
common.sendRequest({
method: 'PUT',
url: '/api/auth/articles/' + this.lastid,
body: formData,
})
.then((result) => {
console.log('result', result)
}, (err) => {
this.error = err.message
})
.then(() => {
this.loading = false
m.redraw()
})
/*e.preventDefault()
if (!this.data.article.name) {
this.error = 'Name is missing'
} else if (!this.data.article.path) {
this.error = 'Path is missing'
} else {
this.error = ''
}
if (this.error) return
this.requestArticle(
this.editor.save()
.then(body => {
formData.append('content', JSON.stringify(body))
this.data.article.description = vnode.state.froala && vnode.state.froala.html.get() || this.data.article.description
if (this.data.article.description) {
this.data.article.description = this.data.article.description.replace(/<p[^>]+data-f-id="pbf"[^>]+>[^>]+>[^>]+>[^>]+>/, '')
}
this.loading = true
let promise
if (this.data.article.id) {
promise = Article.updateArticle(this.data.article.id, {
name: this.data.article.name,
path: this.data.article.path,
page_id: this.data.article.page_id,
description: this.data.article.description,
banner_id: this.data.article.banner && this.data.article.banner.id,
media_id: this.data.article.media && this.data.article.media.id,
publish_at: new Date(this.data.article.publish_at),
is_featured: this.data.article.is_featured,
staff_id: this.data.article.staff_id,
return common.sendRequest({
method: 'PUT',
url: '/api/auth/articles/' + id,
body: formData,
})
})
} else {
promise = Article.createArticle({
name: this.data.article.name,
path: this.data.article.path,
page_id: this.data.article.page_id,
description: this.data.article.description,
banner_id: this.data.article.banner && this.data.article.banner.id,
media_id: this.data.article.media && this.data.article.media.id,
publish_at: new Date(this.data.article.publish_at),
is_featured: this.data.article.is_featured,
staff_id: this.data.article.staff_id,
})
}
promise.then(function(res) {
if (vnode.state.article.id) {
res.media = vnode.state.article.media
res.banner = vnode.state.article.banner
res.files = vnode.state.article.files
vnode.state.article = res
EditArticle.parsePublishedAt(vnode, null)
} else {
m.route.set('/admin/articles/' + res.id)
}
})
.catch(function(err) {
vnode.state.error = err.message
})
.then(function() {
vnode.state.loading = false
m.redraw()
})*/
)
},
uploadFile: function(vnode, e) {
@ -230,13 +175,22 @@ const EditArticle = {
const showPublish = this.data.article
? this.data.article.publish_at > new Date()
: false
const bannerImage = this.data.article && this.data.article.banner_prefix
? this.data.article.banner_prefix + '_large.avif'
: null
const mediaImage = this.data.article && this.data.article.media_prefix
? this.data.article.media_prefix + '_large.avif'
: null
return [
this.loading ?
m('div.loading-spinner')
: null,
this.loading && !this.data.article
? m('div.admin-spinner.loading-spinner')
: null,
this.data.article
? m('div.admin-wrapper', [
this.loading
? m('div.loading-spinner')
: null,
m('div.admin-actions', this.data.article.id
? [
m('span', 'Actions:'),
@ -253,14 +207,14 @@ const EditArticle = {
height: 300,
onfile: this.mediaUploaded.bind(this, 'banner'),
ondelete: this.mediaRemoved.bind(this, 'banner'),
media: this.data.article && this.data.article.banner,
media: bannerImage,
}),
m(FileUpload, {
class: 'cover',
useimg: true,
onfile: this.mediaUploaded.bind(this, 'media'),
ondelete: this.mediaRemoved.bind(this, 'media'),
media: this.data.article && this.data.article.media,
media: mediaImage,
}),
m('form.editarticle.content', {
onsubmit: this.save.bind(this, vnode),
@ -274,53 +228,71 @@ const EditArticle = {
selected: item.id === this.data.article.page_id
}, item.name)
})),
m('label', 'Name'),
m('input', {
type: 'text',
value: this.data.article.name,
oninput: this.updateValue.bind(this, 'name'),
}),
m('label.slim', 'Path'),
m('input.slim', {
type: 'text',
value: this.data.article.path,
oninput: this.updateValue.bind(this, 'path'),
}),
m('div.input-row', [
m('div.input-group', [
m('label', 'Name'),
m('input', {
type: 'text',
value: this.data.article.name,
oninput: this.updateValue.bind(this, 'name'),
}),
]),
m('div.input-group', [
m('label', 'Path'),
m('input', {
type: 'text',
value: this.data.article.path,
oninput: this.updateValue.bind(this, 'path'),
}),
]),
]),
m('label', 'Description'),
m(Editor, {
oncreate: (subnode) => {
this.editor = subnode.state.editor
},
contentdata: this.data.article.content,
}),
m('label', 'Published at'),
m('input', {
type: 'text',
oncreate: (div) => {
if (!this.dateInstance) {
this.dateInstance = new dtsel.DTS(div.dom, {
dateFormat: 'yyyy-mm-dd',
timeFormat: 'HH:MM:SS',
showTime: true,
m('div.input-row', [
m('div.input-group', [
m('label', 'Published at'),
m('input', {
type: 'text',
oncreate: (div) => {
if (!this.dateInstance) {
this.dateInstance = new dtsel.DTS(div.dom, {
dateFormat: 'yyyy-mm-dd',
timeFormat: 'HH:MM:SS',
showTime: true,
})
window.temp = this.dateInstance
}
},
value: this.data.article.publish_at.toISOString().replace('T', ', ').split('.')[0],
}),
]),
m('div.input-group', [
m('label', 'Published by'),
m('select', {
onchange: this.updateStaffer.bind(this),
},
this.data.staff.map((item) => {
return m('option', {
value: item.id,
selected: item.id === this.data.article.staff_id
}, item.name)
})
}
},
value: this.data.article.publish_at.toISOString().replace('T', ', ').split('.')[0],
}),
m('label', 'Published by'),
m('select', {
onchange: this.updateStaffer.bind(this),
},
this.data.staff.map((item) => {
return m('option', {
value: item.id,
selected: item.id === this.data.article.staff_id
}, item.name)
})
),
m('label', 'Make featured'),
m('input', {
type: 'checkbox',
checked: this.data.article.is_featured,
oninput: this.updateValue.bind(this, 'is_featured'),
}),
),
]),
m('div.input-group.small', [
m('label', 'Make featured'),
m('input', {
type: 'checkbox',
checked: this.data.article.is_featured,
oninput: this.updateValue.bind(this, 'is_featured'),
}),
]),
]),
m('div', [
m('input', {
type: 'submit',
@ -356,7 +328,10 @@ const EditArticle = {
: null,
]),
])
: null,
: m('div.error', {
hidden: !this.error,
onclick: () => { this.fetchArticle(vnode) },
}, this.error),,
]
},
}

View File

@ -1,6 +1,7 @@
const Editor = {
oninit: function(vnode) {
this.editor = null
this.lastData = null
},
oncreate: function(vnode) {
@ -25,8 +26,17 @@ const Editor = {
},
delimiter: window.Delimiter,
htmlraw: window.RawTool,
},
},
data: vnode.attrs.contentdata,
})
this.lastData = vnode.attrs.contentdata
},
onupdate: function(vnode) {
if (this.lastData !== vnode.attrs.contentdata) {
this.lastData = vnode.attrs.contentdata
this.editor.render(this.lastData)
}
},
view: function(vnode) {

View File

@ -52,6 +52,7 @@ img {
border: 2px solid #ccc;
border-top-color: #333;
animation: spinner-loader .6s linear infinite;
z-index: 1000;
}
.maincontainer {