Development
This commit is contained in:
parent
bd2e0d0fff
commit
ebd3cd4d26
8 changed files with 335 additions and 174 deletions
|
@ -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)
|
||||
return this.private_getUpdateArticle(ctx, ctx.req.body, newBanner, newMedia)
|
||||
}
|
||||
|
||||
ctx.body = {}
|
||||
/** 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,10 +8,12 @@ export function upload(file) {
|
|||
let token = client.createJwt({ iss: media.iss }, media.secret)
|
||||
|
||||
let out = {
|
||||
sizes: {
|
||||
small: {},
|
||||
medium: {},
|
||||
large: {},
|
||||
}
|
||||
}
|
||||
|
||||
return client.upload(media.path + '?token=' + token, { file: {
|
||||
file: file.path,
|
||||
|
@ -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(() => {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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({
|
||||
this.requestArticle(
|
||||
this.editor.save()
|
||||
.then(body => {
|
||||
formData.append('content', JSON.stringify(body))
|
||||
|
||||
return common.sendRequest({
|
||||
method: 'PUT',
|
||||
url: '/api/auth/articles/' + this.lastid,
|
||||
url: '/api/auth/articles/' + id,
|
||||
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.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,
|
||||
})
|
||||
} 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')
|
||||
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,22 +228,33 @@ const EditArticle = {
|
|||
selected: item.id === this.data.article.page_id
|
||||
}, item.name)
|
||||
})),
|
||||
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('label.slim', 'Path'),
|
||||
m('input.slim', {
|
||||
]),
|
||||
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('div.input-row', [
|
||||
m('div.input-group', [
|
||||
m('label', 'Published at'),
|
||||
m('input', {
|
||||
type: 'text',
|
||||
|
@ -300,10 +265,13 @@ const EditArticle = {
|
|||
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),
|
||||
|
@ -315,12 +283,16 @@ const EditArticle = {
|
|||
}, item.name)
|
||||
})
|
||||
),
|
||||
]),
|
||||
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),,
|
||||
]
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
const Editor = {
|
||||
oninit: function(vnode) {
|
||||
this.editor = null
|
||||
this.lastData = null
|
||||
},
|
||||
|
||||
oncreate: function(vnode) {
|
||||
|
@ -26,7 +27,16 @@ 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) {
|
||||
|
|
|
@ -52,6 +52,7 @@ img {
|
|||
border: 2px solid #ccc;
|
||||
border-top-color: #333;
|
||||
animation: spinner-loader .6s linear infinite;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.maincontainer {
|
||||
|
|
Loading…
Reference in a new issue