news composing and editing
This commit is contained in:
parent
2899f48d95
commit
c12ed739c7
@ -78,6 +78,31 @@ const News = {
|
|||||||
article = article[0]
|
article = article[0]
|
||||||
|
|
||||||
return cleanArticle(article)
|
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')
|
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
|
// Fetch article
|
||||||
router.get('/news/:id', wrap(async (req, res) => {
|
router.get('/news/:id', wrap(async (req, res) => {
|
||||||
if (!req.params.id || isNaN(parseInt(req.params.id))) {
|
if (!req.params.id || isNaN(parseInt(req.params.id))) {
|
||||||
@ -438,4 +453,9 @@ router.use((req, res) => {
|
|||||||
res.status(404).jsonp({error: 'Not found'})
|
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
|
module.exports = router
|
||||||
|
@ -31,6 +31,7 @@ function setSession (req, user) {
|
|||||||
display_name: user.display_name,
|
display_name: user.display_name,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
avatar_file: user.avatar_file,
|
avatar_file: user.avatar_file,
|
||||||
|
privilege: user.nw_privilege,
|
||||||
session_refresh: Date.now() + 1800000 // 30 minutes
|
session_refresh: Date.now() + 1800000 // 30 minutes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,10 +66,10 @@ router.use(wrap(async (req, res, next) => {
|
|||||||
|
|
||||||
// Update user session
|
// Update user session
|
||||||
let udata = await API.User.get(req.session.user.id)
|
let udata = await API.User.get(req.session.user.id)
|
||||||
|
setSession(req, udata)
|
||||||
|
|
||||||
// Update IP address
|
// Update IP address
|
||||||
await API.User.update(udata, {ip_address: req.realIP})
|
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)
|
// 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!')
|
if (!req.body['g-recaptcha-response']) return formError(req, res, 'Please complete the reCAPTCHA!')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -648,6 +649,39 @@ router.get('/docs/:name', (req, res, next) => {
|
|||||||
res.render('document', {doc: doc})
|
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
|
// Serve news
|
||||||
router.get('/news/:id?-*', wrap(async (req, res) => {
|
router.get('/news/:id?-*', wrap(async (req, res) => {
|
||||||
let id = parseInt(req.params.id)
|
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})
|
return res.status(404).render('article', {article: null})
|
||||||
}
|
}
|
||||||
|
|
||||||
res.header('Cache-Control', 'max-age=' + 24 * 60 * 60 * 1000) // 1 day
|
let editing = false
|
||||||
res.render('article', {article: article})
|
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) => {
|
router.get('/news/', wrap(async (req, res) => {
|
||||||
@ -672,7 +710,7 @@ router.get('/news/', wrap(async (req, res) => {
|
|||||||
|
|
||||||
let news = await News.listNews(page)
|
let news = await News.listNews(page)
|
||||||
|
|
||||||
res.render('news', {news: news})
|
res.render('news/news', {news: news})
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Render partials
|
// Render partials
|
||||||
|
@ -228,8 +228,8 @@ $(document).ready(function () {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
window.Dialog.openTemplate = function (title, template, data = {}) {
|
window.Dialog.openTemplate = function (title, template, data) {
|
||||||
window.Dialog.open(title, buildTemplateScript(template, data), true)
|
window.Dialog.open(title, buildTemplateScript(template, data || {}), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#dialog #close').click(function (e) {
|
$('#dialog #close').click(function (e) {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
html
|
html
|
||||||
head
|
head
|
||||||
meta(charset="utf8")
|
meta(charset="utf8")
|
||||||
|
block links
|
||||||
link(rel="stylesheet", type="text/css", href="https://fonts.googleapis.com/css?family=Open+Sans")
|
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="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/main.css")
|
||||||
link(rel="stylesheet", type="text/css", href="/style/admin.css")
|
link(rel="stylesheet", type="text/css", href="/style/admin.css")
|
||||||
block links
|
|
||||||
script.
|
script.
|
||||||
window.variables = {
|
window.variables = {
|
||||||
server_time: parseInt('#{server_time}')
|
server_time: parseInt('#{server_time}')
|
||||||
|
@ -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,9 +3,10 @@ html
|
|||||||
head
|
head
|
||||||
meta(charset="utf8")
|
meta(charset="utf8")
|
||||||
meta(name="viewport", content="width=device-width, initial-scale=1")
|
meta(name="viewport", content="width=device-width, initial-scale=1")
|
||||||
|
block links
|
||||||
link(rel="stylesheet", type="text/css", href="/style/main.css")
|
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="//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="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css")
|
||||||
script(src="/script/main.js")
|
script(src="/script/main.js")
|
||||||
title
|
title
|
||||||
block title
|
block title
|
||||||
|
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
|
block title
|
||||||
|Icy Network - News
|
|Icy Network - News
|
||||||
@ -6,7 +6,9 @@ block title
|
|||||||
block body
|
block body
|
||||||
.document
|
.document
|
||||||
.content
|
.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
|
if news.error
|
||||||
span.error There are no articles to show.
|
span.error There are no articles to show.
|
||||||
else
|
else
|
Reference in New Issue
Block a user