Added article by support
This commit is contained in:
parent
48e6d56ec1
commit
074ef0fbdb
18 changed files with 119 additions and 26 deletions
|
@ -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'],
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) =>
|
||||||
|
|
|
@ -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'),
|
||||||
])
|
])
|
||||||
),
|
),
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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', [
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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]),
|
|
||||||
]),
|
]),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
|
|
|
@ -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'),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
|
|
Loading…
Reference in a new issue