references #2 more options for reply vs thread post settings on manage pages

merge-requests/208/head
fatchan 5 years ago
parent 8cdd235e8f
commit 4787c0c1d8
  1. 49
      controllers/forms.js
  2. 2
      gulp/res/css/style.css
  3. 11
      gulpfile.js
  4. 12
      helpers/paramconverter.js
  5. 3
      models/forms/actionhandler.js
  6. 17
      models/forms/changeboardsettings.js
  7. 11
      models/forms/create.js
  8. 102
      views/pages/manage.pug

@ -264,28 +264,38 @@ router.post('/board/:board/post', Boards.exists, calcPerms, banCheck, postFiles,
const errors = [];
// even if force file and message are off, the psot must contain one of either.
// even if force file and message are off, the post must contain one of either.
if (!req.body.message && res.locals.numFiles === 0) {
errors.push('Must provide a message or file');
errors.push('Posts must include a message or file');
}
// ensure OP has file, subject and message acording to board settings
if (!req.body.thread && res.locals.board.settings.forceOPSubject && (!req.body.subject || req.body.subject.length === 0)) {
errors.push('Threads must include a subject');
// check file, subject and message enforcement according to board settings
if (!req.body.subject || req.body.subject.length === 0) {
if (!req.body.thread && res.locals.board.settings.forceThreadSubject) {
errors.push('Threads must include a subject');
} //no option to force op subject, seems useless
}
if (!req.body.thread && (res.locals.board.settings.forceOPFile && res.locals.board.settings.maxFiles !== 0) && res.locals.numFiles === 0) {
errors.push('Threads must include a file');
if (res.locals.board.settings.maxFiles !== 0 && res.locals.numFiles === 0) {
if (!req.body.thread && res.locals.board.settings.forceThreadFile) {
errors.push('Threads must include a file');
} else if (res.locals.board.settings.forceReplyFile) {
errors.push('Posts must include a file');
}
}
if (!req.body.thread && res.locals.board.settings.forceOPMessage && (!req.body.message || req.body.message.length === 0)) {
errors.push('Threads must include a message');
if (!req.body.message || req.body.message.length === 0) {
if (!req.body.thread && res.locals.board.settings.forceThreadMessage) {
errors.push('Threads must include a message');
} else if (res.locals.board.settings.forceReplyMessage) {
errors.push('Posts must include a message');
}
}
// make sure, min message length <= message length < max length (4k)
if (req.body.message) {
if (req.body.message.length > 4000) {
errors.push('Message must be 4000 characters or less');
} else if (req.body.message.length < res.locals.board.settings.minMessageLength) {
errors.push(`Message must be at least ${res.locals.board.settings.minMessageLength} characters long`);
} else if (!req.body.thread && req.body.message.length < res.locals.board.settings.minThreadMessageLength) {
errors.push(`Thread messages must be at least ${res.locals.board.settings.minMessageLength} characters long`);
} else if (req.body.thread && req.body.message.length < res.locals.board.settings.minReplyMessageLength) {
errors.push(`Reply messages must be at least ${res.locals.board.settings.minMessageLength} characters long`);
}
}
@ -357,8 +367,11 @@ router.post('/board/:board/settings', csrf, Boards.exists, calcPerms, banCheck,
if (typeof req.body.max_files === 'number' && (req.body.max_files < 0 || req.body.max_files > 3)) {
errors.push('Max files must be 0-3');
}
if (typeof req.body.min_message_length === 'number' && (req.body.min_message_length < 0 || req.body.min_message_length > 4000)) {
errors.push('Min message length must be 0-4000. 0 is disabled.');
if (typeof req.body.min_thread_message_length === 'number' && (req.body.min_thread_message_length < 0 || req.body.min_thread_message_length > 4000)) {
errors.push('Min thread message length must be 0-4000. 0 is disabled.');
}
if (typeof req.body.min_reply_message_length === 'number' && (req.body.min_reply_message_length < 0 || req.body.min_reply_message_length > 4000)) {
errors.push('Min reply message length must be 0-4000. 0 is disabled.');
}
if (typeof req.body.captcha_mode === 'number' && (req.body.captcha_mode < 0 || req.body.captcha_mode > 2)) {
errors.push('Invalid captcha mode.');
@ -487,10 +500,10 @@ async function boardActionController(req, res, next) {
}
//check if they have permission to perform the actions
if (res.locals.permLevel > res.locals.actions.authRequired) {
errors.push('No permission');
}
if (res.locals.permLevel >= 4) {
if (res.locals.permLevel > res.locals.actions.authRequired) {
errors.push('No permission');
}
if (req.body.delete && !res.locals.board.settings.userPostDelete) {
errors.push('Post deletion is disabled on this board');
}

@ -252,7 +252,7 @@ p {
}
.required {
margin-left: 5px;
margin: 0 5px;
}
.pinktext {

@ -64,10 +64,13 @@ async function wipe() {
'threadLimit': 200,
'replyLimit': 500,
'maxFiles': 0,
'forceOPSubject': false,
'forceOPMessage': true,
'forceOPFile': false,
'minMessageLength': 0,
'forceReplyMessage':false,
'forceReplyFile':false,
'forceThreadMessage'false,
'forceThreadFile':false,
'forceThreadSubject'false,
'minThreadMessageLength':0',
'minReplyMessageLength':0,
'defaultName': 'Anonymous',
'announcement': {
'raw':null,

@ -2,8 +2,10 @@
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 = ['uri', 'moderators', 'filters', 'announcement', 'description', 'message', 'name', 'subject', 'email', 'password', 'default_name', 'report_reason', 'ban_reason'] //trim if we dont want filed with whitespace
, numberFields = ['filter_mode', 'captcha_mode', 'tph_trigger', 'tph_trigger_action', 'reply_limit', 'max_files', 'thread_limit', 'thread', 'min_message_length'] //convert these to numbers before they hit our routes
, trimFields = ['uri', 'moderators', 'filters', 'announcement', 'description', 'message',
'name', 'subject', 'email', 'password', 'default_name', 'report_reason', 'ban_reason'] //trim if we dont want filed with whitespace
, numberFields = ['filter_mode', 'captcha_mode', 'tph_trigger', 'tph_trigger_action', 'reply_limit',
'max_files', 'thread_limit', 'thread', 'min_thread_message_length', 'min_reply_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')
@ -28,11 +30,7 @@ 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
*/
//trimEnd() because trailing whitespace doesnt affect how a post appear and if it is all whitespace, trimEnd will get it all anyway
req.body[field] = req.body[field].trimEnd();
}
}

@ -33,8 +33,7 @@ module.exports = async (req, res, next) => {
const inputPasswordHash = createHash('sha256').update(postPasswordSecret + req.body.password).digest('base64');
const inputPasswordBuffer = Buffer.from(inputPasswordHash);
passwordPosts = res.locals.posts.filter(post => {
//length comparison could reveal the length, but not contents, and is better than comparing and hashing for empty password (most posts)
if (post.password != null && post.password.length === req.body.password) {
if (post.password != null) {
const postBuffer = Buffer.from(post.password);
if (timingSafeEqual(inputBuffer, postBuffer) === true) {
passwordPostMongoIds.push(Mongo.ObjectId(post._id));

@ -49,19 +49,22 @@ module.exports = async (req, res, next) => {
locked: req.body.locked ? true : false,
ids: req.body.ids ? true : false,
forceAnon: req.body.force_anon ? true : false,
userPostDelete: req.body.user_post_delete ? true : false,
userPostSpoiler: req.body.user_post_spoiler ? true : false,
userPostUnlink: req.body.user_post_unlink ? true : false,
userPostDelete: req.body.user_reply_delete ? true : false,
userPostSpoiler: req.body.user_reply_spoiler ? true : false,
userPostUnlink: req.body.user_reply_unlink ? true : false,
captchaMode: typeof req.body.captcha_mode === 'number' && req.body.captcha_mode !== oldSettings.captchaMode ? req.body.captcha_mode : oldSettings.captchaMode,
tphTrigger: typeof req.body.tph_trigger === 'number' && req.body.tph_trigger !== oldSettings.tphTrigger ? req.body.tph_trigger : oldSettings.tphTrigger,
tphTriggerAction: typeof req.body.tph_trigger_action === 'number' && req.body.tph_trigger_action !== oldSettings.tphTriggerAction ? req.body.tph_trigger_action : oldSettings.tphTriggerAction,
threadLimit: typeof req.body.thread_limit === 'number' && req.body.thread_limit !== oldSettings.threadLimit ? req.body.thread_limit : oldSettings.threadLimit,
replyLimit: typeof req.body.reply_limit === 'number' && req.body.reply_limit !== oldSettings.replyLimit ? req.body.reply_limit : oldSettings.replyLimit,
maxFiles: typeof req.body.max_files === 'number' && req.body.max_files !== oldSettings.maxFiles ? req.body.max_files : oldSettings.maxFiles,
minMessageLength: typeof req.body.min_message_length === 'number' && req.body.min_message_length !== oldSettings.maxFiles ? req.body.min_message_length : oldSettings.minMessageLength,
forceOPSubject: req.body.force_op_subject ? true : false,
forceOPMessage: req.body.force_op_message ? true : false,
forceOPFile: req.body.force_op_file ? true : false,
minThreadMessageLength: typeof req.body.min_thread_message_length === 'number' && req.body.min_thread_message_length !== oldSettings.minThreadMessageLength ? req.body.min_thread_message_length : oldSettings.minThreadMessageLength,
minReplyMessageLength: typeof req.body.min_reply_message_length === 'number' && req.body.min_reply_message_length !== oldSettings.minReplyMessageLength ? req.body.min_reply_message_length : oldSettings.minReplyMessageLength,
forceThreadMessage: req.body.force_thread_message ? true : false,
forceThreadFile: req.body.force_thread_file ? true : false,
forceReplyMessage: req.body.force_reply_message ? true : false,
forceReplyFile: req.body.force_reply_file ? true : false,
forceThreadSubject: req.body.force_thread_subject ? true : false,
defaultName: req.body.default_name && req.body.default_name.trim().length > 0 ? req.body.default_name : oldSettings.defaultName,
announcement: {
raw: req.body.announcement !== null ? req.body.announcement : oldSettings.announcement.raw,

@ -45,10 +45,13 @@ module.exports = async (req, res, next) => {
'threadLimit': 200,
'replyLimit': 500,
'maxFiles': 3,
'forceOPSubject': false,
'forceOPMessage': true,
'forceOPFile': false,
'minMessageLength': 0,
'forceReplyMessage': false,
'forceReplyFile': false,
'forceThreadMessage': false,
'forceThreadFile': false,
'forceThreadSubject': false,
'minThreadMessageLength': 0,
'minReplyMessageLength': 0,
'defaultName': 'Anonymous',
'announcement': {
'raw':null,

@ -19,78 +19,98 @@ block content
.label Board Description
input(type='text' name='description' value=board.settings.description)
section.row
.label Moderators
textarea(name='moderators' placeholder='newline separated') #{board.settings.moderators.join('\n')}
.label Announcement
textarea(name='announcement' placeholder='supports post styling') #{board.settings.announcement.raw}
section.row
.label Board Locked
label.postform-style.ph-5
input(type='checkbox', name='locked', value='true' checked=board.settings.locked)
.label Anon Name
input(type='text' name='default_name' value=board.settings.defaultName)
section.row
.label Max Files
input(type='number' name='max_files' value=board.settings.maxFiles)
section.row
.label IDs
label.postform-style.ph-5
input(type='checkbox', name='ids', value='true' checked=board.settings.ids)
section.row
.label Force Anon
.label User Post Deletion
label.postform-style.ph-5
input(type='checkbox', name='force_anon', value='true' checked=board.settings.forceAnon)
section.row
.label Captcha Mode
select(name='captcha_mode' checked=board.settings.captchaMode)
option(value='0', selected=board.settings.captchaMode === 0) No Captcha
option(value='1', selected=board.settings.captchaMode === 1) Captcha for new thread
option(value='2', selected=board.settings.captchaMode === 2) Captcha for all posts
input(type='checkbox', name='user_post_delete', value='true' checked=board.settings.userPostDelete)
section.row
.label TPH Trigger Threshold
input(type='number', name='tph_trigger', value=board.settings.tphTrigger)
.label User File Spoilering
label.postform-style.ph-5
input(type='checkbox', name='user_post_spoiler', value='true' checked=board.settings.userPostSpoiler)
section.row
.label TPH Trigger Action
select(name='tph_trigger_action')
option(value='0', selected=board.settings.tphTriggerAction === 0) Do nothing
option(value='1', selected=board.settings.tphTriggerAction === 1) Enable captcha for new thread
option(value='2', selected=board.settings.tphTriggerAction === 2) Enable captcha for all posts
option(value='3', selected=board.settings.tphTriggerAction === 3) Lock Board
.label User File Unlinking
label.postform-style.ph-5
input(type='checkbox', name='user_post_unlink', value='true' checked=board.settings.userPostUnlink)
section.row
.label Post Deletion
.label Force Anon
label.postform-style.ph-5
input(type='checkbox', name='user_post_delete', value='true' checked=board.settings.userPostDelete)
input(type='checkbox', name='force_anon', value='true' checked=board.settings.forceAnon)
section.row
.label File Spoilers
.label Force Thread Subject
label.postform-style.ph-5
input(type='checkbox', name='user_post_spoiler', value='true' checked=board.settings.userPostSpoiler)
input(type='checkbox', name='force_thread_subject', value='true' checked=board.settings.forceThreadSubject)
section.row
.label File Unlinking
.label Force Thread Message
.required *
label.postform-style.ph-5
input(type='checkbox', name='user_post_unlink', value='true' checked=board.settings.userPostUnlink)
input(type='checkbox', name='force_thread_message', value='true' checked=board.settings.forceThreadMessage)
section.row
.label Force OP Message
.label Force Thread File
.required *
label.postform-style.ph-5
input(type='checkbox', name='force_op_message', value='true' checked=board.settings.forceOPMessage)
input(type='checkbox', name='force_thread_file', value='true' checked=board.settings.forceThreadFile)
section.row
.label Force OP Subject
.label Force Reply Message
.required *
label.postform-style.ph-5
input(type='checkbox', name='force_op_subject', value='true' checked=board.settings.forceOPSubject)
input(type='checkbox', name='force_reply_message', value='true' checked=board.settings.forceReplyMessage)
section.row
.label Force OP File
.label Force Reply File
.required *
label.postform-style.ph-5
input(type='checkbox', name='force_op_file', value='true' checked=board.settings.forceOPFile)
input(type='checkbox', name='force_reply_file', value='true' checked=board.settings.forceReplyFile)
.row
.label
.required *
| All posts still require either a message or file(s)
section.row
.label Anon Name
input(type='text' name='default_name' value=board.settings.defaultName)
.label Min Thread Message Length
input(type='number' name='min_thread_message_length' value=board.settings.minThreadMessageLength placeholder='0-4000')
section.row
.label Min Message Length
input(type='number' name='min_message_length' value=board.settings.minMessageLength)
.label Min Reply Message Length
input(type='number' name='min_reply_message_length' value=board.settings.minReplyMessageLength placeholder='0-4000')
section.row
.label Thread Limit
input(type='number' name='thread_limit' value=board.settings.threadLimit)
section.row
.label Reply Limit
input(type='number' name='reply_limit' value=board.settings.replyLimit)
br
section.row
.label Max Files
input(type='number' name='max_files' value=board.settings.maxFiles)
.label Moderators
textarea(name='moderators' placeholder='newline separated') #{board.settings.moderators.join('\n')}
section.row
.label Announcement
textarea(name='announcement' placeholder='supports post styling') #{board.settings.announcement.raw}
.label Board Locked
label.postform-style.ph-5
input(type='checkbox', name='locked', value='true' checked=board.settings.locked)
section.row
.label Captcha Mode
select(name='captcha_mode' checked=board.settings.captchaMode)
option(value='0', selected=board.settings.captchaMode === 0) No Captcha
option(value='1', selected=board.settings.captchaMode === 1) Captcha for new thread
option(value='2', selected=board.settings.captchaMode === 2) Captcha for all posts
section.row
.label TPH Trigger Threshold
input(type='number', name='tph_trigger', value=board.settings.tphTrigger)
section.row
.label TPH Trigger Action
select(name='tph_trigger_action')
option(value='0', selected=board.settings.tphTriggerAction === 0) Do nothing
option(value='1', selected=board.settings.tphTriggerAction === 1) Enable captcha for new thread
option(value='2', selected=board.settings.tphTriggerAction === 2) Enable captcha for all posts
option(value='3', selected=board.settings.tphTriggerAction === 3) Lock Board
section.row
.label Filters
textarea(name='filters' placeholder='newline separated') #{board.settings.filters.join('\n')}

Loading…
Cancel
Save