min and max length to boards, and make it handled properly in posting, frontend js message coutner, etc

merge-requests/208/head
fatchan 5 years ago
parent a7088c0092
commit 68bd674fe5
  1. 9
      configs/main.js.example
  2. 54
      controllers/forms/boardsettings.js
  3. 12
      controllers/forms/makepost.js
  4. 3
      gulp/res/js/counter.js
  5. 2
      gulp/res/js/themelist.js
  6. 17
      gulp/res/js/time.js
  7. 2
      helpers/paramconverter.js
  8. 2
      models/forms/changeboardsettings.js
  9. 4
      views/includes/postform.pug
  10. 6
      views/pages/managesettings.pug

@ -141,18 +141,21 @@ module.exports = {
postFiles: { //number of files in a post
max: 3
},
postFilesSize: { //max combined size of all files in a post (10MB default)
postFilesSize: { //in bytes, 10MB default
max: 10485760
},
bannerFiles: { //max number of banners uploadable at once
max: 10
},
bannerFilesSize: { //max combined size of banner files (10MB default)
bannerFilesSize: { //in bytes, 10MB default
max: 10485760
},
messageLength: { //max length of a post mesage field
max: 4000
}
/* NOTE: postFilesSize and bannerFilesSize counts in bytes the amount of total data in form submission including
other fields like message, name, etc. Therefore a very long message would reduce the space left for files very slightly.
To counteract this, consider increasing postFilesSize and bannerFilesSize beyond your desired max filesize by a small margin */
},
//default board settings when a board is created
@ -185,6 +188,8 @@ module.exports = {
disableReplySubject: false,
minThreadMessageLength: 0,
minReplyMessageLength: 0,
maxThreadMessageLength: 4000,
maxReplyMessageLength: 4000,
defaultName: 'Anon',
filters: [], //words/phrases to block
filterMode: 0, //0=nothing, 1=prevent post, 2=auto ban

@ -9,6 +9,7 @@ module.exports = async (req, res, next) => {
const errors = [];
//TODO: add helpers for different checks, passing name, min/max and return true with error if hit
if (req.body.description && (req.body.description.length < 1 || req.body.description.length > 50)) {
errors.push('Board description must be 1-50 characters');
}
@ -30,21 +31,62 @@ module.exports = async (req, res, next) => {
if (req.body.default_name && (req.body.default_name.length < 1 || req.body.default_name.length > 50)) {
errors.push('Anon name must be 1-50 characters');
}
if (typeof req.body.reply_limit === 'number' && (req.body.reply_limit < globalLimits.replyLimit.min || req.body.reply_limit > globalLimits.replyLimit.max)) {
if (typeof req.body.reply_limit === 'number'
&& (req.body.reply_limit < globalLimits.replyLimit.min
|| req.body.reply_limit > globalLimits.replyLimit.max)) {
errors.push(`Reply Limit must be ${globalLimits.replyLimit.min}-${globalLimits.replyLimit.max}`);
}
if (typeof req.body.thread_limit === 'number' && (req.body.thread_limit < globalLimits.threadLimit.min || req.body.thread_limit > globalLimits.threadLimit.max)) {
if (typeof req.body.thread_limit === 'number'
&& (req.body.thread_limit < globalLimits.threadLimit.min
|| req.body.thread_limit > globalLimits.threadLimit.max)) {
errors.push(`Threads Limit must be ${globalLimits.threadLimit.min}-${globalLimits.threadLimit.max}`);
}
if (typeof req.body.max_files === 'number' && (req.body.max_files < 0 || req.body.max_files > globalLimits.postFiles.max)) {
errors.push(`Max files must be 0-${globalLimits.postFiles.max}`);
}
if (typeof req.body.min_thread_message_length === 'number' && (req.body.min_thread_message_length < 0 || req.body.min_thread_message_length > globalLimits.messageLength.max)) {
errors.push(`Min thread message length must be 0-${globalLimits.messageLength.max}`);
//make sure new min/max message dont conflict
if (typeof req.body.min_thread_message_length === 'number'
&& typeof req.body.max_thread_message_length === 'number'
&& req.body.min_thread_message_length
&& req.body.max_thread_message_length
&& req.body.min_thread_message_length > req.body.max_thread_message_length) {
errors.push('Min and max thread message lengths must not violate eachother');
}
if (typeof req.body.min_reply_message_length === 'number'
&& typeof req.body.max_reply_message_length === 'number'
&& req.body.min_reply_message_length > req.body.max_reply_message_length) {
errors.push('Min and max reply message lengths must not violate eachother');
}
if (typeof req.body.min_reply_message_length === 'number' && (req.body.min_reply_message_length < 0 || req.body.min_reply_message_length > globalLimits.messageLength.max)) {
errors.push(`Min reply message length must be 0-${globalLimits.messageLength.max}`);
//make sure existing min/max message dont conflict
const minThread = Math.min(globalLimits.messageLength.max, res.locals.board.settings.maxThreadMessageLength) || globalLimits.messageLength.max;
if (typeof req.body.min_thread_message_length === 'number'
&& (req.body.min_thread_message_length < 0
|| req.body.min_thread_message_length > minThread)) {
errors.push(`Min thread message length must be 0-${globalLimits.messageLength.max} and not more than "Max Thread Message Length" (currently ${res.locals.board.settings.maxThreadMessageLength})`);
}
const minReply = Math.min(globalLimits.messageLength.max, res.locals.board.settings.maxReplyMessageLength) || globalLimits.messageLength.max;
if (typeof req.body.min_reply_message_length === 'number'
&& (req.body.min_reply_message_length < 0
|| req.body.min_reply_message_length > minReply)) {
errors.push(`Min reply message length must be 0-${globalLimits.messageLength.max} and not more than "Max Reply Message Length" (currently ${res.locals.board.settings.maxReplyMessageLength})`);
}
if (typeof req.body.max_thread_message_length === 'number'
&& (req.body.max_thread_message_length < 0
|| req.body.max_thread_message_length > globalLimits.messageLength.max
|| (req.body.max_thread_message_length
&& req.body.max_thread_message_length < res.locals.board.settings.minThreadMessageLength))) {
errors.push(`Max thread message length must be 0-${globalLimits.messageLength.max} and not less than "Min Thread Message Length" (currently ${res.locals.board.settings.minThreadMessageLength})`);
}
if (typeof req.body.max_reply_message_length === 'number'
&& (req.body.max_reply_message_length < 0
|| req.body.max_reply_message_length > globalLimits.messageLength.max
|| (req.body.max_reply_message_length
&& req.body.max_reply_message_length < res.locals.board.settings.minReplyMessageLength))) {
errors.push(`Max reply message length must be 0-${globalLimits.messageLength.max} and not less than "Min Reply Message Length" (currently ${res.locals.board.settings.minReplyMessageLength})`);
}
if (typeof req.body.captcha_mode === 'number' && (req.body.captcha_mode < 0 || req.body.captcha_mode > 2)) {
errors.push('Invalid captcha mode');
}

@ -40,10 +40,18 @@ module.exports = async (req, res, next) => {
if (req.body.message) {
if (req.body.message.length > globalLimits.messageLength.max) {
errors.push(`Message must be ${globalLimits.messageLength.max} characters or less`);
} else if (!req.body.thread
&& res.locals.board.settings.maxThreadMessageLength
&& req.body.message.length > res.locals.board.settings.maxThreadMessageLength) {
errors.push(`Thread messages must be ${res.locals.board.settings.maxThreadLength} characters or less`);
} else if (req.body.thread
&& res.locals.board.settings.maxReplyMessageLength
&& req.body.message.length > res.locals.board.settings.maxReplyMessageLength) {
errors.push(`Reply messages must be ${res.locals.board.settings.maxReplyMessageLength} characters or less`);
} 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`);
errors.push(`Thread messages must be at least ${res.locals.board.settings.minThreadMessageLength} 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`);
errors.push(`Reply messages must be at least ${res.locals.board.settings.minReplyMessageLength} characters long`);
}
}

@ -5,13 +5,14 @@ window.addEventListener('DOMContentLoaded', (event) => {
if (messageBox) {
const messageBoxLabel = messageBox.previousSibling;
const maxLength = messageBox.getAttribute('maxlength');
const minLength = messageBox.getAttribute('minlength');
let currentLength = messageBox.value.length;
const counter = document.createElement('small');
messageBoxLabel.appendChild(counter);
const updateCounter = () => {
counter.innerText = `(${currentLength}/${maxLength})`;
if (currentLength >= maxLength) {
if (currentLength >= maxLength || currentLength < minLength) {
counter.style.color = 'red';
} else {
counter.removeAttribute('style');

@ -1 +1 @@
const themes = ['chaos', 'choc', 'gurochan', 'lain', 'makaba', 'navy', 'pink', 'rei-zero', 'sushi', 'tomorrow', 'tomorrow2', 'yotsuba b', 'yotsuba'];const codeThemes = ['a11y-dark', 'a11y-light', 'agate', 'an-old-hope', 'androidstudio', 'arduino-light', 'arta', 'ascetic', 'atelier-cave-dark', 'atelier-cave-light', 'atelier-dune-dark', 'atelier-dune-light', 'atelier-estuary-dark', 'atelier-estuary-light', 'atelier-forest-dark', 'atelier-forest-light', 'atelier-heath-dark', 'atelier-heath-light', 'atelier-lakeside-dark', 'atelier-lakeside-light', 'atelier-plateau-dark', 'atelier-plateau-light', 'atelier-savanna-dark', 'atelier-savanna-light', 'atelier-seaside-dark', 'atelier-seaside-light', 'atelier-sulphurpool-dark', 'atelier-sulphurpool-light', 'atom-one-dark-reasonable', 'atom-one-dark', 'atom-one-light', 'brown-paper', 'brown-papersq', 'codepen-embed', 'color-brewer', 'darcula', 'dark', 'darkula', 'default', 'docco', 'dracula', 'far', 'foundation', 'github-gist', 'github', 'gml', 'googlecode', 'gradient-dark', 'grayscale', 'gruvbox-dark', 'gruvbox-light', 'hopscotch', 'hybrid', 'idea', 'ir-black', 'isbl-editor-dark', 'isbl-editor-light', 'kimbie.dark', 'kimbie.light', 'lightfair', 'magula', 'mono-blue', 'monokai-sublime', 'monokai', 'night-owl', 'nord', 'obsidian', 'ocean', 'paraiso-dark', 'paraiso-light', 'pojoaque', 'pojoaque', 'purebasic', 'qtcreator_dark', 'qtcreator_light', 'railscasts', 'rainbow', 'routeros', 'school-book', 'school-book', 'shades-of-purple', 'solarized-dark', 'solarized-light', 'sunburst', 'tomorrow-night-blue', 'tomorrow-night-bright', 'tomorrow-night-eighties', 'tomorrow-night', 'tomorrow', 'vs', 'vs2015', 'xcode', 'xt256', 'zenburn']
const themes = ['chaos', 'choc', 'gurochan', 'lain', 'makaba', 'navy', 'pink', 'rei-zero', 'sushi', 'tomorrow', 'tomorrow2', 'yotsuba b', 'yotsuba'];const codeThemes = ['a11y-dark', 'a11y-light', 'agate', 'an-old-hope', 'androidstudio', 'arduino-light', 'arta', 'ascetic', 'atelier-cave-dark', 'atelier-cave-light', 'atelier-dune-dark', 'atelier-dune-light', 'atelier-estuary-dark', 'atelier-estuary-light', 'atelier-forest-dark', 'atelier-forest-light', 'atelier-heath-dark', 'atelier-heath-light', 'atelier-lakeside-dark', 'atelier-lakeside-light', 'atelier-plateau-dark', 'atelier-plateau-light', 'atelier-savanna-dark', 'atelier-savanna-light', 'atelier-seaside-dark', 'atelier-seaside-light', 'atelier-sulphurpool-dark', 'atelier-sulphurpool-light', 'atom-one-dark-reasonable', 'atom-one-dark', 'atom-one-light', 'brown-paper', 'brown-papersq', 'codepen-embed', 'color-brewer', 'darcula', 'dark', 'darkula', 'default', 'docco', 'dracula', 'far', 'foundation', 'github-gist', 'github', 'gml', 'googlecode', 'gradient-dark', 'grayscale', 'gruvbox-dark', 'gruvbox-light', 'hopscotch', 'hybrid', 'idea', 'ir-black', 'isbl-editor-dark', 'isbl-editor-light', 'kimbie.dark', 'kimbie.light', 'lightfair', 'magula', 'mono-blue', 'monokai-sublime', 'monokai', 'night-owl', 'nord', 'obsidian', 'ocean', 'paraiso-dark', 'paraiso-light', 'pojoaque', 'pojoaque', 'purebasic', 'qtcreator_dark', 'qtcreator_light', 'railscasts', 'rainbow', 'routeros', 'school-book', 'school-book', 'shades-of-purple', 'solarized-dark', 'solarized-light', 'sunburst', 'tomorrow-night-blue', 'tomorrow-night-bright', 'tomorrow-night-eighties', 'tomorrow-night', 'tomorrow', 'vs', 'vs2015', 'xcode', 'xt256', 'zenburn'];

@ -1,8 +1,9 @@
setDefaultLocalStorage('relative', false);
let relativeTime = localStorage.getItem('relative') == 'true';
!localStorage.getItem('24hour') ? setLocalStorage('24hour', false) : void 0;
setDefaultLocalStorage('24hour', false);
let hour24 = localStorage.getItem('24hour') == 'true';
setDefaultLocalStorage('localtime', true);
let localTime = localStorage.getItem('localtime') == 'true';
let dates = [];
const dateElems = document.getElementsByClassName('reltime');
@ -51,7 +52,7 @@ const relativeTimeString = (date) => {
}
const changeDateFormat = (date) => {
const dateString = new Date(date.dateTime).toLocaleString(0, {hour12: !hour24});
const dateString = new Date(date.dateTime).toLocaleString(localTime ? 0 : SERVER_TIMEZONE, {hour12: !hour24});
if (relativeTime) {
date.innerText = relativeTimeString(date.dateTime);
date.title = dateString;
@ -83,6 +84,16 @@ window.addEventListener('settingsReady', function(event) {
hour24Setting.checked = hour24;
hour24Setting.addEventListener('change', togglehour24, false);
const localTimeSetting = document.getElementById('localtime-setting');
const toggleLocalTime = () => {
localTime = !localTime;
setLocalStorage('localtime', localTime);
updateDates();
console.log('toggling local time', localTime);
}
localTimeSetting.checked = localTime;
localTimeSetting.addEventListener('change', toggleLocalTime, false);
const relativeTimeSetting = document.getElementById('relative-setting');
const togglerelativeTime = () => {
relativeTime = !relativeTime;

@ -6,7 +6,7 @@ const { ObjectId } = require(__dirname+'/../db/db.js')
, trimFields = ['tags', 'uri', 'moderators', 'filters', 'announcement', 'description', 'message',
'name', 'subject', 'email', 'password', 'default_name', 'report_reason', 'ban_reason', 'log_message'] //trim if we dont want filed with whitespace
, numberFields = ['filter_mode', 'captcha_mode', 'tph_trigger', 'pph_trigger', 'trigger_action', 'reply_limit', 'move_to_thread',
'max_files', 'thread_limit', 'thread', 'min_thread_message_length', 'min_reply_message_length', 'auth_level'] //convert these to numbers before they hit our routes
'max_files', 'thread_limit', 'thread', 'max_thread_message_length', 'max_reply_message_length', 'min_thread_message_length', 'min_reply_message_length', 'auth_level'] //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)?$/
, timeUtils = require(__dirname+'/timeutils.js')
, makeArrayIfSingle = (obj) => !Array.isArray(obj) ? [obj] : obj;

@ -98,6 +98,8 @@ module.exports = async (req, res, next) => {
'maxFiles': numberSetting(req.body.max_files, oldSettings.maxFiles),
'minThreadMessageLength': numberSetting(req.body.min_thread_message_length, oldSettings.minThreadMessageLength),
'minReplyMessageLength': numberSetting(req.body.min_reply_message_length, oldSettings.minReplyMessageLength),
'maxThreadMessageLength': numberSetting(req.body.max_thread_message_length, oldSettings.maxThreadMessageLength),
'maxReplyMessageLength': numberSetting(req.body.max_reply_message_length, oldSettings.maxReplyMessageLength),
'filterMode': numberSetting(req.body.filter_mode, oldSettings.filterMode),
'filterBanDuration': numberSetting(req.body.ban_duration, oldSettings.filterBanDuration),
'tags': arraySetting(req.body.tags, oldSettings.tags, 10),

@ -28,7 +28,9 @@ section.form-wrapper.flex-center
span Message
if messageRequired
span.required *
textarea#message(name='message', rows='5', autocomplete='off' maxlength=globalLimits.messageLength.max required=messageRequired)
- const minLength = (isThread ? board.settings.minReplyMessageLength : board.settings.minThreadMessageLength) || 0;
- const maxLength = Math.min((isThread ? board.settings.maxReplyMessageLength : board.settings.maxThreadMessageLength), globalLimits.messageLength.max) || globalLimits.messageLength.max;
textarea#message(name='message', rows='5', autocomplete='off' minlength=minLength maxlength=maxLength required=messageRequired)
if board.settings.maxFiles > 0
- const maxFiles = board.settings.maxFiles;
section.row

@ -135,6 +135,12 @@ block content
.row
.label Min Reply Message Length
input(type='number' name='min_reply_message_length' value=board.settings.minReplyMessageLength max=globalLimits.messageLength.max)
.row
.label Max Thread Message Length
input(type='number' name='max_thread_message_length' value=board.settings.maxThreadMessageLength max=globalLimits.messageLength.max)
.row
.label Max Reply Message Length
input(type='number' name='max_reply_message_length' value=board.settings.maxReplyMessageLength max=globalLimits.messageLength.max)
.row
.label Thread Limit
input(type='number' name='thread_limit' value=board.settings.threadLimit min=globalLimits.threadLimit.min max=globalLimits.threadLimit.max)

Loading…
Cancel
Save