filters, improved manage page and store both raw and markdown announcements for showing in editor and on the pages themselved

merge-requests/208/head
fatchan 5 years ago
parent 8e7d7131af
commit f55f29fb9f
  1. 11
      helpers/paramconverter.js
  2. 17
      models/forms/changeboardsettings.js
  3. 43
      models/forms/makepost.js
  4. 4
      views/includes/announcements.pug
  5. 25
      views/pages/manage.pug

@ -2,7 +2,7 @@
const Mongo = require(__dirname+'/../db/db.js')
, allowedArrays = new Set(['checkedposts', 'globalcheckedposts', 'checkedbans', 'checkedbanners']) //only these can be arrays, since express bodyparser will output arrays
, trimFields = ['description', 'message', 'name', 'subject', 'email', 'password', 'default_name', 'report_reason', 'ban_reason'] //trim if we dont want filed with whitespace
, trimFields = ['filters', 'announcement', 'description', 'message', 'name', 'subject', 'email', 'password', 'default_name', 'report_reason', 'ban_reason'] //trim if we dont want filed with whitespace
, numberFields = ['captcha_mode', 'captcha_trigger', 'captcha_trigger_mode', 'reply_limit', 'max_files', 'thread_limit', 'thread', 'min_message_length'] //convert these to numbers before they hit our routes
, banDurationRegex = /^(?<year>[\d]+y)?(?<month>[\d]+m)?(?<week>[\d]+w)?(?<day>[\d]+d)?(?<hour>[\d]+h)?$/
, msTime = require(__dirname+'/mstime.js')
@ -13,6 +13,10 @@ module.exports = (req, res, next) => {
for (let i = 0; i < bodyfields.length; i++) {
const key = bodyfields[i];
const val = req.body[key];
/*
bodyparser can form arrays e.g. for multiple files, but we only want arrays in fields we
expect, to prevent issues when validating/using them later on.
*/
if (!allowedArrays.has(key) && Array.isArray(val)) {
return res.status(400).render('message', {
'title': 'Bad request',
@ -24,6 +28,11 @@ module.exports = (req, res, next) => {
for (let i = 0; i < trimFields.length; i++) {
const field = trimFields[i];
if (req.body[field]) {
/*
we only trimEnd() because:
- trailing whitespace doesnt matter, but leading can affect how a post appears
- if it is all whitespace, trimEnd will get it all anyway
*/
req.body[field] = req.body[field].trimEnd();
}
}

@ -21,11 +21,12 @@ module.exports = async (req, res, next) => {
const oldSettings = res.locals.board.settings;
let announcements;
if (req.body.announcements) {
const markdownAnnouncements = simpleMarkdown(req.body.announcements);
const quotedAnnouncements = (await linkQuotes(req.params.board, markdownAnnouncements, null)).quotedMessage;
announcements = sanitize(quotedAnnouncements, sanitizeOptions);
let markdownAnnouncement;
if (req.body.announcement !== oldSettings.announcement.raw) {
const styled = simpleMarkdown(req.body.announcement);
const quoted = (await linkQuotes(req.params.board, styled, null)).quotedMessage;
const sanitized = sanitize(quoted, sanitizeOptions);
markdownAnnouncement = sanitized;
}
const newSettings = {
@ -47,7 +48,11 @@ module.exports = async (req, res, next) => {
forceOPMessage: req.body.force_op_message ? true : false,
forceOPFile: req.body.force_op_file ? true : false,
defaultName: req.body.default_name && req.body.default_name.trim().length > 0 ? req.body.default_name : oldSettings.defaultName,
announcements: announcements ? announcements : oldSettings.announcements
announcement: {
raw: req.body.announcement !== null ? req.body.announcement : oldSettings.announcement.raw,
markdown: markdownAnnouncement || oldSettings.announcement.markdown
},
filters: req.body.filters !== null ? req.body.filters.split('\n').filter(n => n) /*prevents empty*/ : oldSettings.filters
};
//settings changed in the db

@ -40,7 +40,7 @@ module.exports = async (req, res, next) => {
let salt = null;
let thread = null;
const hasPerms = permsCheck(req, res);
const forceAnon = res.locals.board.settings.forceAnon;
const { filters, maxFiles, forceAnon, replyLimit, threadLimit, ids, userPostSpoiler, defaultName, captchaTrigger, captchaTriggerMode, captchaMode } = res.locals.board.settings;
if (req.body.thread) {
thread = await Posts.getPost(req.params.board, req.body.thread, true);
if (!thread || thread.thread != null) {
@ -61,7 +61,7 @@ module.exports = async (req, res, next) => {
'redirect': redirect
});
}
if (thread.replyposts >= res.locals.board.settings.replyLimit && !thread.cyclic) { //reply limit
if (thread.replyposts >= replyLimit && !thread.cyclic) { //reply limit
await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('message', {
'title': 'Bad request',
@ -70,14 +70,26 @@ module.exports = async (req, res, next) => {
});
}
}
if (res.locals.numFiles > res.locals.board.settings.maxFiles) {
if (res.locals.numFiles > maxFiles) {
await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('message', {
'title': 'Bad request',
'message': `Too many files. Max files per post is ${res.locals.board.settings.maxFiles}.`,
'message': `Too many files. Max files per post is ${maxFiles}.`,
'redirect': redirect
});
}
//filters
if (filters && filters.length > 0) {
const containsFilter = filters.some(filter => { return req.body.message.includes(filter) });
if (containsFilter) {
await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('message', { //is this a 400?
'title': 'Bad request',
'message': `Your message was blocked by a filter.`,
'redirect': redirect
});
}
}
let files = [];
// if we got a file
if (res.locals.numFiles > 0) {
@ -183,7 +195,7 @@ module.exports = async (req, res, next) => {
//thread salt for IDs
salt = (await randomBytes(128)).toString('base64');
}
if (res.locals.board.settings.ids) {
if (ids === true) {
const fullUserIdHash = createHash('sha256').update(salt + res.locals.ip).digest('hex');
userId = fullUserIdHash.substring(fullUserIdHash.length-6);
}
@ -194,9 +206,9 @@ module.exports = async (req, res, next) => {
let email = (hasPerms || !forceAnon || req.body.email === 'sage') ? req.body.email : null;
//spoiler files only if board settings allow
const spoiler = res.locals.board.settings.userPostSpoiler && req.body.spoiler ? true : false;
const spoiler = userPostSpoiler && req.body.spoiler ? true : false;
let name = res.locals.board.settings.defaultName;
let name = defaultName;
let tripcode = null;
let capcode = null;
if ((hasPerms || !forceAnon) && req.body.name && req.body.name.length > 0) {
@ -272,8 +284,8 @@ module.exports = async (req, res, next) => {
const postId = await Posts.insertOne(res.locals.board, data, thread);
if (!data.thread //if this is a new thread
&& res.locals.board.settings.captchaTriggerMode > 0 //and the triger mode is not nothing
&& res.locals.board.settings.captchaMode < res.locals.board.settings.captchaTriggerMode) { //and the current captcha mode is less than the trigger mode
&& captchaTriggerMode > 0 //and the triger mode is not nothing
&& captchaMode < captchaTriggerMode) { //and the current captcha mode is less than the trigger mode
const pastHourMongoId = Mongo.ObjectId.createFromTime(Math.floor((Date.now() - msTime.hour)/1000));
//count threads in past hour
const tph = await Posts.db.countDocuments({
@ -284,14 +296,15 @@ module.exports = async (req, res, next) => {
'board': res.locals.board._id
});
//if its above the trigger
if (tph > res.locals.board.settings.captchaTrigger) {
res.locals.board.settings.captchaMode = res.locals.board.settings.captchaTriggerMode; //update in memory too
if (tph > captchaTrigger) {
//update in memory for other stuff done e.g. rebuilds
res.locals.board.settings.captchaMode = captchaTriggerMode;
//set it in the db
await Boards.db.updateOne({
'_id': res.locals.board._id,
}, {
'$set': {
'settings.captchaMode': res.locals.board.settings.captchaTriggerMode
'settings.captchaMode': captchaTriggerMode
}
});
//remove the html (since pages will need captcha in postform now)
@ -300,13 +313,13 @@ module.exports = async (req, res, next) => {
}
//for cyclic threads, delete posts beyond bump limit
if (thread && thread.cyclic && thread.replyposts > res.locals.board.settings.replyLimit) {
if (thread && thread.cyclic && thread.replyposts > replyLimit) {
const cyclicOverflowPosts = await Posts.db.find({
'thread': data.thread,
'board': req.params.board
}).sort({
'postId': -1,
}).skip(res.locals.board.settings.replyLimit).toArray();
}).skip(replyLimit).toArray();
await deletePosts(cyclicOverflowPosts, req.params.board);
}
@ -335,7 +348,7 @@ module.exports = async (req, res, next) => {
if (prunedThreads.length > 0) {
await deletePosts(prunedThreads, req.params.board);
}
parallelPromises.push(buildBoardMultiple(res.locals.board, 1, Math.ceil(res.locals.board.settings.threadLimit/10)));
parallelPromises.push(buildBoardMultiple(res.locals.board, 1, Math.ceil(threadLimit/10)));
}
//always rebuild catalog for post counts and ordering

@ -1,4 +1,4 @@
if board.settings.announcements
if board.settings.announcement.markdown
hr(size=1)
pre.post-message.no-m-p !{board.settings.announcements}
pre.post-message.no-m-p !{board.settings.announcement.markdown}
hr(size=1)

@ -14,10 +14,10 @@ block content
input(type='hidden' name='_csrf' value=csrf)
section.row
.label Board name
input(type='text' name='name' placeholder=board.settings.name)
input(type='text' name='name' value=board.settings.name)
section.row
.label Board Description
input(type='text' name='description' placeholder=board.settings.description)
input(type='text' name='description' value=board.settings.description)
section.row
.label IDs
label.postform-style.ph-5
@ -34,10 +34,10 @@ block content
option(value='2', selected=board.settings.captchaMode === 2) Captcha for all posts
section.row
.label Captcha Trigger Threshold
input(type='number', name='captcha_trigger', placeholder=board.settings.captchaTrigger)
input(type='number', name='captcha_trigger', value=board.settings.captchaTrigger)
section.row
.label Captcha Trigger Mode
select(name='captcha_trigger_mode' placeholder=board.settings.captchaTriggerMode)
select(name='captcha_trigger_mode')
option(value='0', selected=board.settings.captchaTriggerMode === 0) Do nothing
option(value='1', selected=board.settings.captchaTriggerMode === 1) Enable for new thread
option(value='2', selected=board.settings.captchaTriggerMode === 2) Enable for all posts
@ -67,22 +67,25 @@ block content
input(type='checkbox', name='force_op_file', value='true' checked=board.settings.forceOPFile)
section.row
.label Anon Name
input(type='text' name='default_name' placeholder=board.settings.defaultName)
input(type='text' name='default_name' value=board.settings.defaultName)
section.row
.label Min Message Length
input(type='number' name='min_message_length' placeholder=board.settings.minMessageLength)
input(type='number' name='min_message_length' value=board.settings.minMessageLength)
section.row
.label Thread Limit
input(type='number' name='thread_limit' placeholder=board.settings.threadLimit)
input(type='number' name='thread_limit' value=board.settings.threadLimit)
section.row
.label Reply Limit
input(type='number' name='reply_limit' placeholder=board.settings.replyLimit)
input(type='number' name='reply_limit' value=board.settings.replyLimit)
section.row
.label Max Files
input(type='number' name='max_files' placeholder=board.settings.maxFiles)
input(type='number' name='max_files' value=board.settings.maxFiles)
section.row
.label Announcements
textarea(name='announcements')
.label Announcement
textarea(name='announcement' placeholder='supports post styling') #{board.settings.announcement.raw}
section.row
.label Filters
textarea(name='filters' placeholder='newline separated') #{board.settings.filters.join('\n')}
input(type='submit', value='save settings')
hr(size=1)
h4.no-m-p Add Banners:

Loading…
Cancel
Save