news composing and editing
This commit is contained in:
parent
2899f48d95
commit
c12ed739c7
@ -78,6 +78,31 @@ const News = {
|
||||
article = article[0]
|
||||
|
||||
return cleanArticle(article)
|
||||
},
|
||||
compose: async (user, body) => {
|
||||
let article = {
|
||||
title: body.title,
|
||||
content: body.content,
|
||||
tags: body.tags || '',
|
||||
user_id: user.id,
|
||||
created_at: new Date()
|
||||
}
|
||||
|
||||
let result = await Models.News.query().insert(article)
|
||||
result.slug = slugify(result.title)
|
||||
|
||||
return result
|
||||
},
|
||||
edit: async (id, body) => {
|
||||
if (!body.content) return {error: 'Content required'}
|
||||
let patch = {
|
||||
content: body.content,
|
||||
updated_at: new Date()
|
||||
}
|
||||
|
||||
let result = await Models.News.query().patchAndFetchById(id, patch)
|
||||
if (!result) return {error: 'Something went wrong.'}
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -288,6 +288,21 @@ router.get('/news/all/', (req, res) => {
|
||||
res.redirect('/api/news/all/1')
|
||||
})
|
||||
|
||||
router.post('/news/edit/:id', wrap(async (req, res, next) => {
|
||||
if (!req.session.user || req.session.user.privilege < 1) return next()
|
||||
if (!req.params.id || isNaN(parseInt(req.params.id))) {
|
||||
return res.status(400).jsonp({error: 'Invalid ID number.'})
|
||||
}
|
||||
|
||||
let id = parseInt(req.params.id)
|
||||
let result = await News.edit(id, req.body)
|
||||
if (result.error) {
|
||||
return res.status(400).jsonp({error: result.error})
|
||||
}
|
||||
|
||||
res.status(204).end()
|
||||
}))
|
||||
|
||||
// Fetch article
|
||||
router.get('/news/:id', wrap(async (req, res) => {
|
||||
if (!req.params.id || isNaN(parseInt(req.params.id))) {
|
||||
@ -438,4 +453,9 @@ router.use((req, res) => {
|
||||
res.status(404).jsonp({error: 'Not found'})
|
||||
})
|
||||
|
||||
router.use((err, req, res, next) => {
|
||||
console.error(err)
|
||||
res.jsonp({error: 'Internal server error.'})
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
|
@ -31,6 +31,7 @@ function setSession (req, user) {
|
||||
display_name: user.display_name,
|
||||
email: user.email,
|
||||
avatar_file: user.avatar_file,
|
||||
privilege: user.nw_privilege,
|
||||
session_refresh: Date.now() + 1800000 // 30 minutes
|
||||
}
|
||||
}
|
||||
@ -65,10 +66,10 @@ router.use(wrap(async (req, res, next) => {
|
||||
|
||||
// Update user session
|
||||
let udata = await API.User.get(req.session.user.id)
|
||||
setSession(req, udata)
|
||||
|
||||
// Update IP address
|
||||
await API.User.update(udata, {ip_address: req.realIP})
|
||||
setSession(req, udata)
|
||||
}
|
||||
}
|
||||
|
||||
@ -431,7 +432,7 @@ router.post('/register', accountLimiter, wrap(async (req, res, next) => {
|
||||
}
|
||||
|
||||
// 6th Check: reCAPTCHA (if configuration contains key)
|
||||
if (config.security.recaptcha && config.security.recaptcha.site_key) {
|
||||
if (config.security && config.security.recaptcha && config.security.recaptcha.site_key) {
|
||||
if (!req.body['g-recaptcha-response']) return formError(req, res, 'Please complete the reCAPTCHA!')
|
||||
|
||||
try {
|
||||
@ -648,6 +649,39 @@ router.get('/docs/:name', (req, res, next) => {
|
||||
res.render('document', {doc: doc})
|
||||
})
|
||||
|
||||
/*
|
||||
========
|
||||
NEWS
|
||||
========
|
||||
*/
|
||||
|
||||
function privileged (req, res, next) {
|
||||
if (!req.session.user) return res.redirect('/news')
|
||||
if (req.session.user.privilege < 1) return res.redirect('/news')
|
||||
next()
|
||||
}
|
||||
|
||||
router.get('/news/writer', privileged, wrap(async (req, res) => {
|
||||
res.render('news/composer')
|
||||
}))
|
||||
|
||||
router.post('/news/writer', privileged, wrap(async (req, res) => {
|
||||
if (req.body.csrf !== req.session.csrf) {
|
||||
return formError(req, res, 'Invalid session! Try reloading the page.')
|
||||
}
|
||||
|
||||
if (!req.body.title || !req.body.content) {
|
||||
return formError(req, res, 'Required fields missing!')
|
||||
}
|
||||
|
||||
let result = await News.compose(req.session.user, req.body)
|
||||
if (result.error) {
|
||||
return formError(req, res, result.error)
|
||||
}
|
||||
|
||||
res.redirect('/news/' + result.id + '-' + result.slug)
|
||||
}))
|
||||
|
||||
// Serve news
|
||||
router.get('/news/:id?-*', wrap(async (req, res) => {
|
||||
let id = parseInt(req.params.id)
|
||||
@ -660,8 +694,12 @@ router.get('/news/:id?-*', wrap(async (req, res) => {
|
||||
return res.status(404).render('article', {article: null})
|
||||
}
|
||||
|
||||
res.header('Cache-Control', 'max-age=' + 24 * 60 * 60 * 1000) // 1 day
|
||||
res.render('article', {article: article})
|
||||
let editing = false
|
||||
if (req.query.edit === '1' && req.session.user && req.session.user.privilege > 1) {
|
||||
editing = true
|
||||
}
|
||||
|
||||
res.render('news/article', {article: article, editing: editing})
|
||||
}))
|
||||
|
||||
router.get('/news/', wrap(async (req, res) => {
|
||||
@ -672,7 +710,7 @@ router.get('/news/', wrap(async (req, res) => {
|
||||
|
||||
let news = await News.listNews(page)
|
||||
|
||||
res.render('news', {news: news})
|
||||
res.render('news/news', {news: news})
|
||||
}))
|
||||
|
||||
// Render partials
|
||||
|
@ -228,8 +228,8 @@ $(document).ready(function () {
|
||||
})
|
||||
}
|
||||
|
||||
window.Dialog.openTemplate = function (title, template, data = {}) {
|
||||
window.Dialog.open(title, buildTemplateScript(template, data), true)
|
||||
window.Dialog.openTemplate = function (title, template, data) {
|
||||
window.Dialog.open(title, buildTemplateScript(template, data || {}), true)
|
||||
}
|
||||
|
||||
$('#dialog #close').click(function (e) {
|
||||
|
@ -1,16 +1,16 @@
|
||||
html
|
||||
head
|
||||
meta(charset="utf8")
|
||||
link(rel="stylesheet", type="text/css", href="https://fonts.googleapis.com/css?family=Open+Sans")
|
||||
link(rel="stylesheet", type="text/css", href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css")
|
||||
link(rel="stylesheet", type="text/css", href="/style/main.css")
|
||||
link(rel="stylesheet", type="text/css", href="/style/admin.css")
|
||||
block links
|
||||
script.
|
||||
window.variables = {
|
||||
server_time: parseInt('#{server_time}')
|
||||
};
|
||||
script(src="/script/admin.js")
|
||||
link(rel="stylesheet", type="text/css", href="https://fonts.googleapis.com/css?family=Open+Sans")
|
||||
link(rel="stylesheet", type="text/css", href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css")
|
||||
link(rel="stylesheet", type="text/css", href="/style/main.css")
|
||||
link(rel="stylesheet", type="text/css", href="/style/admin.css")
|
||||
script.
|
||||
window.variables = {
|
||||
server_time: parseInt('#{server_time}')
|
||||
};
|
||||
script(src="/script/admin.js")
|
||||
title
|
||||
block title
|
||||
|Icy Network - Administration
|
||||
|
@ -1,24 +0,0 @@
|
||||
extends layout.pug
|
||||
|
||||
block title
|
||||
if article
|
||||
|Icy Network - News - #{article.title}
|
||||
else
|
||||
|Icy Network - News - 404
|
||||
|
||||
block body
|
||||
.document
|
||||
.content
|
||||
if !article
|
||||
span.error No such article
|
||||
else
|
||||
.article
|
||||
.title= article.title
|
||||
.author Published by
|
||||
span #{article.author.display_name}
|
||||
|at
|
||||
.timestamp #{new Date(article.created_at)}
|
||||
.content!= article.content
|
||||
.return
|
||||
a(href="/news") Back to news directory
|
||||
|
@ -3,10 +3,11 @@ html
|
||||
head
|
||||
meta(charset="utf8")
|
||||
meta(name="viewport", content="width=device-width, initial-scale=1")
|
||||
link(rel="stylesheet", type="text/css", href="/style/main.css")
|
||||
link(rel="stylesheet", type="text/css", href="https://fonts.googleapis.com/css?family=Open+Sans")
|
||||
link(rel="stylesheet", type="text/css", href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css")
|
||||
script(src="/script/main.js")
|
||||
block links
|
||||
link(rel="stylesheet", type="text/css", href="/style/main.css")
|
||||
link(rel="stylesheet", type="text/css", href="//fonts.googleapis.com/css?family=Open+Sans")
|
||||
link(rel="stylesheet", type="text/css", href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css")
|
||||
script(src="/script/main.js")
|
||||
title
|
||||
block title
|
||||
body
|
||||
|
55
views/news/article.pug
Normal file
55
views/news/article.pug
Normal file
@ -0,0 +1,55 @@
|
||||
extends ../layout.pug
|
||||
|
||||
block append links
|
||||
if editing
|
||||
script(src="//cdnjs.cloudflare.com/ajax/libs/ckeditor/4.7.2/ckeditor.js")
|
||||
|
||||
block title
|
||||
if article
|
||||
|Icy Network - News - #{article.title}
|
||||
else
|
||||
|Icy Network - News - 404
|
||||
|
||||
block body
|
||||
.document
|
||||
.content
|
||||
if !article
|
||||
span.error No such article
|
||||
else
|
||||
.article
|
||||
if user && user.privilege && user.privilege > 0 && !editing
|
||||
a.button(style="float: right;" href="?edit=1") Edit
|
||||
.title= article.title
|
||||
.author Published by
|
||||
span #{article.author.display_name}
|
||||
|at
|
||||
.timestamp #{new Date(article.created_at)}
|
||||
if editing
|
||||
.content(contenteditable="true" id="editor1")!= article.content
|
||||
else
|
||||
.content!= article.content
|
||||
if editing
|
||||
.button(id="done") Done editing
|
||||
br
|
||||
script.
|
||||
CKEDITOR.disableAutoInline = true;
|
||||
CKEDITOR.inline('editor1');
|
||||
$('#done').click(function (e) {
|
||||
let data = CKEDITOR.instances.editor1.getData();
|
||||
$.post({
|
||||
url: '/api/news/edit/#{article.id}',
|
||||
data: {content: data},
|
||||
success: function () {
|
||||
window.location.href = '/news/#{article.id}-#{article.slug}'
|
||||
},
|
||||
error: function (e) {
|
||||
if (e.responseJSON && e.responseJSON.error) {
|
||||
alert(e.responseJSON.error);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
.return
|
||||
a(href="/news") Back to the news archive
|
||||
|
31
views/news/composer.pug
Normal file
31
views/news/composer.pug
Normal file
@ -0,0 +1,31 @@
|
||||
extends ../layout.pug
|
||||
|
||||
block append links
|
||||
script(src="//cdnjs.cloudflare.com/ajax/libs/ckeditor/4.7.2/ckeditor.js")
|
||||
|
||||
block title
|
||||
|Icy Network - News - Compose
|
||||
|
||||
block body
|
||||
.document
|
||||
.content
|
||||
if message.text
|
||||
if message.error
|
||||
.message.error
|
||||
span #{message.text}
|
||||
else
|
||||
.message
|
||||
span #{message.text}
|
||||
form(action="", method="post")
|
||||
input(type="hidden", name="csrf", value=csrf)
|
||||
label(for="title") Title
|
||||
input(type="text", name="title", id="title")
|
||||
label(for="composer1") Content
|
||||
textarea(name="content" id="composer1")
|
||||
label(for="tags") Tags
|
||||
input(type="text", name="tags", id="tags")
|
||||
input(type="submit", value="Submit")
|
||||
script.
|
||||
CKEDITOR.replace('composer1')
|
||||
a(href="/news") Back to news directory
|
||||
|
@ -1,4 +1,4 @@
|
||||
extends layout.pug
|
||||
extends ../layout.pug
|
||||
|
||||
block title
|
||||
|Icy Network - News
|
||||
@ -6,7 +6,9 @@ block title
|
||||
block body
|
||||
.document
|
||||
.content
|
||||
h1 Icy Network News
|
||||
if user && user.privilege && user.privilege > 0
|
||||
a.button(style="float: right;" href="/news/writer") New Article
|
||||
h1 Icy Network News Archive
|
||||
if news.error
|
||||
span.error There are no articles to show.
|
||||
else
|
Reference in New Issue
Block a user