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({ .fetchPage({
pageSize: 10, pageSize: 10,
page: page, 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) { async createArticle(ctx) {
await this.security.validUpdate(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) ctx.body = await this.Article.create(ctx.request.body)
} }

View File

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

View File

@ -28,6 +28,6 @@ export default class AuthHelper {
throw err 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') 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) return this.jwt.decode(token)
} }
createToken(email, level, opts) { createToken(id, email, level, opts) {
return this.sign({ return this.sign({
id: id,
email: email, email: email,
level: level, level: level,
}, email, opts) }, email, opts)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -41,6 +41,6 @@ exports.getAllPageArticlesPagination = function(pageId, options) {
exports.getArticle = function(id) { exports.getArticle = function(id) {
return common.sendRequest({ return common.sendRequest({
method: 'GET', 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 ? this.loading ?
m('div.loading-spinner') m('div.loading-spinner')
: m('article.article', [ : 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('header', m('h1', this.article.name)),
m('.fr-view', [ m('.fr-view', [
this.article.media this.article.media
@ -89,6 +90,13 @@ const Article = {
return m(Fileinfo, { file: file }) return m(Fileinfo, { file: file })
}) })
: null), : 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 Authentication.currentUser
? m('div.admin-actions', [ ? m('div.admin-actions', [

View File

@ -26,7 +26,7 @@ article.article {
.fr-view { .fr-view {
margin: 0 20px; margin: 0 20px;
padding: 20px 20px 60px; padding: 20px 20px 10px;
width: calc(100% - 40px); width: calc(100% - 40px);
max-width: 800px; max-width: 800px;
flex: 2 0 0; flex: 2 0 0;
@ -73,6 +73,34 @@ article.article {
min-height: 50px; min-height: 50px;
position: relative; 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 { .darkmodeon {
@ -88,5 +116,13 @@ article.article {
.fr-view { .fr-view {
background: $dark_newsitem-bg; 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({ return Pagination.fetchPage(Article.getAllArticlesPagination({
per_page: 10, per_page: 10,
page: this.lastpage, page: this.lastpage,
includes: ['parent', 'files', 'media', 'banner'], includes: ['parent', 'files', 'media', 'banner', 'staff'],
})) }))
.then(function(result) { .then(function(result) {
vnode.state.articles = result.data vnode.state.articles = result.data

View File

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

View File

@ -32,7 +32,6 @@ const Newsentry = {
: vnode.attrs.description : vnode.attrs.description
? m('span.entrydescription', Newsentry.strip(vnode.attrs.description)) ? m('span.entrydescription', Newsentry.strip(vnode.attrs.description))
: null), : 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 ? 'in' : ''),
(vnode.attrs.parent ? m(m.route.Link, { href: '/page/' + vnode.attrs.parent.path }, vnode.attrs.parent.name) : null), (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), 'at ' + (vnode.attrs.published_at.replace('T', ' ').split('.')[0]).substr(0, 16),
' by ' + (vnode.attrs.staff && vnode.attrs.staff.fullname || 'Admin'),
]), ]),
]), ]),
]), ]),