Added article by support

master
Jonatan Nilsson 2019-10-02 18:47:20 +00:00
parent 48e6d56ec1
commit 074ef0fbdb
18 changed files with 119 additions and 26 deletions

View File

@ -136,7 +136,7 @@ const Article = bookshelf.createModel({
.fetchPage({
pageSize: 10,
page: page,
withRelated: ['files', 'media', 'banner', 'parent'],
withRelated: ['files', 'media', 'banner', 'parent', 'staff'],
})
},
})

View File

@ -55,6 +55,10 @@ export default class ArticleRoutes {
async createArticle(ctx) {
await this.security.validUpdate(ctx)
if (!ctx.request.body.staff_id) {
ctx.request.body.staff_id = ctx.state.user.id
}
ctx.body = await this.Article.create(ctx.request.body)
}

View File

@ -8,6 +8,7 @@ const requiredFields = [
const validFields = [
'name',
'path',
'staff_id',
'description',
'parent_id',
'media_id',

View File

@ -28,6 +28,6 @@ export default class AuthHelper {
throw err
}
return this.jwt.createToken(staff.get('email'), staff.get('level'))
return this.jwt.createToken(staff.id, staff.get('email'), staff.get('level'))
}
}

View File

@ -38,7 +38,7 @@ export default class AuthRoutes {
level = staff.get('level')
}
ctx.body = { token: this.jwt.createToken(output.email, level) }
ctx.body = { token: this.jwt.createToken(staff.id, output.email, level) }
}
/*

View File

@ -43,8 +43,9 @@ export default class Jwt {
return this.jwt.decode(token)
}
createToken(email, level, opts) {
createToken(id, email, level, opts) {
return this.sign({
id: id,
email: email,
level: level,
}, email, opts)

View File

@ -17,6 +17,10 @@ function mapArticle(trim = false, x, includeBanner = false, includeFiles = true)
path: x.path,
description: x.description,
name: x.name,
staff: x.staff && ({
id: x.staff.id,
fullname: x.staff.fullname,
}) || null,
media: x.media && ({
link: !trim && x.media.link || null,
large_url: x.media.large_url,
@ -95,7 +99,7 @@ export async function serveIndex(ctx, path) {
{ id: x.id, name: x.name, path: x.path }
))
))
featured = await Article.getFeatured(['files', 'media', 'banner'])
featured = await Article.getFeatured(['media', 'banner'])
if (featured) {
featured = mapArticle(true, featured.toJSON(), true, false)
}
@ -120,7 +124,7 @@ export async function serveIndex(ctx, path) {
if (id) {
let found
if (path.startsWith('/article/')) {
found = await Article.getSingle(id, ['media', 'parent', 'banner', 'files'], false, null, true)
found = await Article.getSingle(id, ['media', 'parent', 'banner', 'files', 'staff'], false, null, true)
if (found) {
found = mapArticle(false, found.toJSON())
}

View File

@ -26,9 +26,7 @@ const Staff = bookshelf.createModel({
]),
}, {
// Hide password from any relations and include requests.
publicFields: bookshelf.safeColumns([
'fullname',
]),
publicFields: ['id', 'fullname'],
hash(password) {
return new Promise((resolve, reject) =>

View File

@ -29,7 +29,7 @@ const AdminArticles = {
return pagination.fetchPage(Article.getAllArticlesPagination({
per_page: 10,
page: this.lastpage,
includes: ['parent'],
includes: ['parent', 'staff'],
}))
.then(function(result) {
vnode.state.articles = result.data
@ -70,13 +70,10 @@ const AdminArticles = {
name: '-- Frontpage --',
}
}
let other = ''
let className = ''
if (new Date() < new Date(article.published_at)) {
other = '(hidden)'
className = 'rowhidden'
} else if (article.is_featured) {
other = '(featured)'
className = 'rowfeatured'
}
return [
@ -85,7 +82,7 @@ const AdminArticles = {
m('td', m(m.route.Link, { href: parent.path }, parent.name)),
m('td', m(m.route.Link, { href: '/article/' + article.path }, '/article/' + article.path)),
m('td.right', article.published_at.replace('T', ' ').split('.')[0]),
m('td.right', other),
m('td.right', article.staff && article.staff.fullname || 'Admin'),
m('td.right', m('button', { onclick: function() { vnode.state.removeArticle = article } }, 'Remove')),
]),
]
@ -113,7 +110,7 @@ const AdminArticles = {
m('th', 'Page'),
m('th', 'Path'),
m('th.right', 'Publish'),
m('th.right', 'Other'),
m('th.right', 'By'),
m('th.right', 'Actions'),
])
),

View File

@ -40,6 +40,14 @@ article.editarticle {
height: 300px;
}
label.slim {
font-size: 0.7em;
}
input.slim {
font-size: 0.8em;
padding: 2px 5px;
}
.loading-spinner {
height: 300px;
position: relative;

View File

@ -1,5 +1,6 @@
const Authentication = require('../authentication')
const FileUpload = require('../widgets/fileupload')
const Staff = require('../api/staff')
const Froala = require('./froala')
const Page = require('../api/page')
const File = require('../api/file')
@ -30,6 +31,18 @@ const EditArticle = {
oninit: function(vnode) {
this.froala = null
this.loadedFroala = Froala.loadedFroala
this.staffers = []
Staff.getAllStaff()
.then(function(result) {
vnode.state.staffers = result
})
.catch(function(err) {
vnode.state.error = err.message
})
.then(function() {
m.redraw()
})
if (!this.loadedFroala) {
Froala.createFroalaScript()
@ -121,6 +134,10 @@ const EditArticle = {
}
},
updateStaffer: function(e) {
this.article.staff_id = Number(e.currentTarget.value)
},
mediaUploaded: function(type, media) {
this.article[type] = media
},
@ -159,6 +176,7 @@ const EditArticle = {
media_id: this.article.media && this.article.media.id,
published_at: new Date(this.article.published_at),
is_featured: this.article.is_featured,
staff_id: this.article.staff_id,
})
} else {
promise = Article.createArticle({
@ -170,6 +188,7 @@ const EditArticle = {
media_id: this.article.media && this.article.media.id,
published_at: new Date(this.article.published_at),
is_featured: this.article.is_featured,
staff_id: this.article.staff_id,
})
}
@ -225,8 +244,20 @@ const EditArticle = {
return out
},
getStaffers: function() {
if (!this.article.staff_id) {
this.article.staff_id = 1
}
let out = []
this.staffers.forEach(function(item) {
out.push({ id: item.id, name: item.fullname })
})
return out
},
view: function(vnode) {
const parents = this.getFlatTree()
const staffers = this.getStaffers()
return (
this.loading ?
m('div.loading-spinner')
@ -265,18 +296,18 @@ const EditArticle = {
m('select', {
onchange: this.updateParent.bind(this),
}, parents.map(function(item) { return m('option', { value: item.id || -1, selected: item.id === vnode.state.article.parent_id }, item.name) })),
m('label', 'Path'),
m('input', {
type: 'text',
value: this.article.path,
oninput: this.updateValue.bind(this, 'path'),
}),
m('label', 'Name'),
m('input', {
type: 'text',
value: this.article.name,
oninput: this.updateValue.bind(this, 'name'),
}),
m('label.slim', 'Path'),
m('input.slim', {
type: 'text',
value: this.article.path,
oninput: this.updateValue.bind(this, 'path'),
}),
m('label', 'Description'),
(
this.loadedFroala ?
@ -289,12 +320,16 @@ const EditArticle = {
})
: null
),
m('label', 'Publish at'),
m('label', 'Published at'),
m('input', {
type: 'datetime-local',
value: this.article.published_at,
oninput: this.updateValue.bind(this, 'published_at'),
}),
m('label', 'Published by'),
m('select', {
onchange: this.updateStaffer.bind(this),
}, staffers.map(function(item) { return m('option', { value: item.id, selected: item.id === vnode.state.article.staff_id }, item.name) })),
m('label', 'Make featured'),
m('input', {
type: 'checkbox',

View File

@ -41,6 +41,6 @@ exports.getAllPageArticlesPagination = function(pageId, options) {
exports.getArticle = function(id) {
return common.sendRequest({
method: 'GET',
url: '/api/articles/public/' + id + '?includes=media,parent,banner,files',
url: '/api/articles/public/' + id + '?includes=media,parent,banner,files,staff',
})
}

View File

@ -75,6 +75,7 @@ const Article = {
this.loading ?
m('div.loading-spinner')
: m('article.article', [
this.article.parent ? m('div.goback', ['« ', m(m.route.Link, { href: '/page/' + this.article.parent.path }, this.article.parent.name)]) : null,
m('header', m('h1', this.article.name)),
m('.fr-view', [
this.article.media
@ -89,6 +90,13 @@ const Article = {
return m(Fileinfo, { file: file })
})
: null),
m('div.entrymeta', [
'Posted ',
(this.article.parent ? 'in' : ''),
(this.article.parent ? m(m.route.Link, { href: '/page/' + this.article.parent.path }, this.article.parent.name) : null),
'at ' + (this.article.published_at.replace('T', ' ').split('.')[0]).substr(0, 16),
' by ' + (this.article.staff && this.article.staff.fullname || 'Admin'),
]),
]),
Authentication.currentUser
? m('div.admin-actions', [

View File

@ -26,7 +26,7 @@ article.article {
.fr-view {
margin: 0 20px;
padding: 20px 20px 60px;
padding: 20px 20px 10px;
width: calc(100% - 40px);
max-width: 800px;
flex: 2 0 0;
@ -73,6 +73,34 @@ article.article {
min-height: 50px;
position: relative;
}
.goback {
width: calc(100% - 40px);
max-width: 800px;
align-self: center;
padding: 10px 5px 0;
margin-bottom: -10px;
a {
font-weight: bold;
text-decoration: none;
color: $secondary-dark-bg;
}
}
.entrymeta {
flex-grow: 2;
font-size: 11px;
color: $meta-fg;
font-weight: bold;
margin: 40px 0 0;
a {
color: $secondary-dark-bg;
margin: 0 4px;
text-decoration: none;
}
}
}
.darkmodeon {
@ -88,5 +116,13 @@ article.article {
.fr-view {
background: $dark_newsitem-bg;
}
.opencomments {
color: $dark_secondary-dark-bg;
}
.goback a {
color: $dark_secondary-dark-bg;
}
}
}

View File

@ -57,7 +57,7 @@ const Frontpage = {
return Pagination.fetchPage(Article.getAllArticlesPagination({
per_page: 10,
page: this.lastpage,
includes: ['parent', 'files', 'media', 'banner'],
includes: ['parent', 'files', 'media', 'banner', 'staff'],
}))
.then(function(result) {
vnode.state.articles = result.data

View File

@ -58,6 +58,7 @@ newsentry {
fileinfo {
padding: 0 5px;
margin-bottom: 5px;
display: block;
&.slim {
padding: 0;

View File

@ -32,7 +32,6 @@ const Newsentry = {
: vnode.attrs.description
? m('span.entrydescription', Newsentry.strip(vnode.attrs.description))
: null),
m('span.entrymeta', 'Posted ' + vnode.attrs.published_at.replace('T', ' ').split('.')[0]),
]),
])
},

View File

@ -30,6 +30,7 @@ const Newsitem = {
(vnode.attrs.parent ? 'in' : ''),
(vnode.attrs.parent ? m(m.route.Link, { href: '/page/' + vnode.attrs.parent.path }, vnode.attrs.parent.name) : null),
'at ' + (vnode.attrs.published_at.replace('T', ' ').split('.')[0]).substr(0, 16),
' by ' + (vnode.attrs.staff && vnode.attrs.staff.fullname || 'Admin'),
]),
]),
]),