improve how errors shown when making a post, use modals and fix ban seen marking

merge-requests/208/head
fatchan 5 years ago
parent 32a123c6a5
commit 485dc802aa
  1. 3
      controllers/forms/makepost.js
  2. 34
      gulp/res/css/style.css
  3. 52
      gulp/res/js/modal.js
  4. 28
      gulp/res/js/progress.js
  5. 3
      gulpfile.js
  6. 7
      helpers/captcha/captchaverify.js
  7. 2
      helpers/checks/bancheck.js
  8. 10
      helpers/dynamic.js
  9. 2
      helpers/tasks.js
  10. 19
      models/forms/makepost.js
  11. 4
      server.js
  12. 17
      views/includes/modal.pug

@ -2,6 +2,7 @@
const makePost = require(__dirname+'/../../models/forms/makepost.js')
, deleteTempFiles = require(__dirname+'/../../helpers/files/deletetempfiles.js')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, { globalLimits } = require(__dirname+'/../../configs/main.json')
, { Files } = require(__dirname+'/../../db/');
@ -70,7 +71,7 @@ module.exports = async (req, res, next) => {
if (errors.length > 0) {
await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('message', {
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
'errors': errors,
'redirect': `/${req.params.board}${req.body.thread ? '/thread/' + req.body.thread + '.html' : ''}`

@ -244,6 +244,7 @@ p {
font-weight: bolder;
margin-left: auto;
width: 25px;
cursor: pointer;
}
.reports {
@ -331,13 +332,44 @@ td, th {
z-index: 1;
}
.post-container, .stickynav, .pages, .toggle-summary, .catalog-tile {
.post-container, .stickynav, .pages, .toggle-summary, .catalog-tile, .modal {
background: var(--post-color);
border-width: 1px;/*0 1px 1px 0;*/
border-style: solid;
border-color: var(--post-outline-color);
}
.nomarks {
list-style: none;
margin: 5px;
padding: 0;
}
.modal-bg {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
background-color: #00000070;
z-index: 3;
}
.modal {
display: flex;
flex-direction: column;
max-width: calc(100% - 10px);
max-height: calc(100% - 50px);
position: fixed;
top: 38px;
background-color: var(--post-color);
z-index: 4;
box-sizing: border-box;
padding: 5px;
border: 1px solid var(--post-outline-color);
align-self: center;
}
.actions {
text-align: left;
max-width: 200px;

@ -0,0 +1,52 @@
function pug_escape(e){var a=""+e,t=pug_match_html.exec(a);if(!t)return e;var r,c,n,s="";for(r=t.index,c=0;r<a.length;r++){switch(a.charCodeAt(r)){case 34:n="&quot;";break;case 38:n="&amp;";break;case 60:n="&lt;";break;case 62:n="&gt;";break;default:continue}c!==r&&(s+=a.substring(c,r)),c=r+1,s+=n}return c!==r?s+a.substring(c,r):s}
var pug_match_html=/["&<>]/;function modal(locals) {var pug_html = "", pug_mixins = {}, pug_interp;;var locals_for_with = (locals || {});(function (error, errors, message, messages, title) {pug_html = pug_html + "\u003Cdiv class=\"modal-bg\"\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"modal\"\u003E\u003Cdiv class=\"row\"\u003E\u003Cp class=\"bold\"\u003E" + (pug_escape(null == (pug_interp = title) ? "" : pug_interp)) + "\u003C\u002Fp\u003E\u003Ca class=\"close postform-style\" id=\"modalclose\"\u003EX\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row\"\u003E\u003Cul class=\"nomarks\"\u003E";
if (message) {
pug_html = pug_html + "\u003Cli\u003E" + (pug_escape(null == (pug_interp = message) ? "" : pug_interp)) + "\u003C\u002Fli\u003E";
}
else
if (error) {
pug_html = pug_html + "\u003Cli\u003E" + (pug_escape(null == (pug_interp = error) ? "" : pug_interp)) + "\u003C\u002Fli\u003E";
}
else
if (messages) {
// iterate messages
;(function(){
var $$obj = messages;
if ('number' == typeof $$obj.length) {
for (var pug_index0 = 0, $$l = $$obj.length; pug_index0 < $$l; pug_index0++) {
var msg = $$obj[pug_index0];
pug_html = pug_html + "\u003Cli\u003E" + (pug_escape(null == (pug_interp = msg) ? "" : pug_interp)) + "\u003C\u002Fli\u003E";
}
} else {
var $$l = 0;
for (var pug_index0 in $$obj) {
$$l++;
var msg = $$obj[pug_index0];
pug_html = pug_html + "\u003Cli\u003E" + (pug_escape(null == (pug_interp = msg) ? "" : pug_interp)) + "\u003C\u002Fli\u003E";
}
}
}).call(this);
}
else
if (errors) {
// iterate errors
;(function(){
var $$obj = errors;
if ('number' == typeof $$obj.length) {
for (var pug_index1 = 0, $$l = $$obj.length; pug_index1 < $$l; pug_index1++) {
var err = $$obj[pug_index1];
pug_html = pug_html + "\u003Cli\u003E" + (pug_escape(null == (pug_interp = err) ? "" : pug_interp)) + "\u003C\u002Fli\u003E";
}
} else {
var $$l = 0;
for (var pug_index1 in $$obj) {
$$l++;
var err = $$obj[pug_index1];
pug_html = pug_html + "\u003Cli\u003E" + (pug_escape(null == (pug_interp = err) ? "" : pug_interp)) + "\u003C\u002Fli\u003E";
}
}
}).call(this);
}
pug_html = pug_html + "\u003C\u002Ful\u003E\u003C\u002Fdiv\u003E\u003C\u002Fdiv\u003E";}.call(this,"error" in locals_for_with?locals_for_with.error:typeof error!=="undefined"?error:undefined,"errors" in locals_for_with?locals_for_with.errors:typeof errors!=="undefined"?errors:undefined,"message" in locals_for_with?locals_for_with.message:typeof message!=="undefined"?message:undefined,"messages" in locals_for_with?locals_for_with.messages:typeof messages!=="undefined"?messages:undefined,"title" in locals_for_with?locals_for_with.title:typeof title!=="undefined"?title:undefined));;return pug_html;}

@ -22,25 +22,41 @@ window.addEventListener('DOMContentLoaded', () => {
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
submit.disabled = false;
let json;
if (xhr.responseText) {
try {
json = JSON.parse(xhr.responseText);
} catch (e) {
//wasnt json response
}
}
if (xhr.status == 200) {
//successful post
if (!isThread && xhr.responseURL) {
window.location = xhr.responseURL;
} else if (xhr.responseText) {
const json = JSON.parse(xhr.responseText);
} else if (json) {
window.myPostId = json.postId;
window.location.hash = json.postId;
}
form.reset(); //reset form on success
} else {
if (xhr.responseText) {
//some error/failed post, wrong captcha, etc
//not 200 status, so some error/failed post, wrong captcha, etc
if (json) {
//show modal when possible
const modalHtml = modal(json);
document.body.insertAdjacentHTML('afterbegin', modalHtml);
document.getElementById('modalclose').onclick = () => {
document.getElementsByClassName('modal')[0].remove();
document.getElementsByClassName('modal-bg')[0].remove();
}
} else {
//for bans, show
window.history.pushState(null, null, xhr.responseURL);
document.open('text/html', true);
document.write(xhr.responseText);
document.close();
window.history.pushState(null, null, xhr.responseURL);
}
}
form.reset();
submit.value = 'New Reply';
}
}

@ -141,13 +141,14 @@ function custompages() {
function scripts() {
try {
fs.writeFileSync('gulp/res/js/post.js', pug.compileFileClient(`${paths.pug.src}/includes/post.pug`, { compileDebug: false, debug: false, name: 'post' }));
fs.writeFileSync('gulp/res/js/modal.js', pug.compileFileClient(`${paths.pug.src}/includes/modal.pug`, { compileDebug: false, debug: false, name: 'modal' }));
fs.symlinkSync(__dirname+'/node_modules/socket.io-client/dist/socket.io.js', __dirname+'/gulp/res/js/socket.io.js', 'file');
} catch (e) {
//already exists, ignore error
}
gulp.src(`${paths.scripts.src}/*.js`)
.pipe(concat('all.js'))
.pipe(uglify())
// .pipe(uglify())
.pipe(gulp.dest(paths.scripts.dest));
return gulp.src(`${paths.scripts.src}/*.js`)
.pipe(uglify())

@ -3,6 +3,7 @@
const { Captchas, Ratelimits } = require(__dirname+'/../../db/')
, { ObjectId } = require(__dirname+'/../../db/db.js')
, remove = require('fs-extra').remove
, dynamicResponse = require(__dirname+'/../dynamic.js')
, uploadDirectory = require(__dirname+'/../files/uploadDirectory.js');
module.exports = async (req, res, next) => {
@ -19,7 +20,7 @@ module.exports = async (req, res, next) => {
//check if captcha field in form is valid
const input = req.body.captcha;
if (!input || input.length !== 6) {
return res.status(403).render('message', {
return dynamicResponse(req, res, 403, 'message', {
'title': 'Forbidden',
'message': 'Incorrect captcha'
});
@ -28,7 +29,7 @@ module.exports = async (req, res, next) => {
//make sure they have captcha cookie and its 24 chars
const captchaId = req.cookies.captchaid;
if (!captchaId || captchaId.length !== 24) {
return res.status(403).render('message', {
return dynamicResponse(req, res, 403, 'message', {
'title': 'Forbidden',
'message': 'Captcha expired'
});
@ -45,7 +46,7 @@ module.exports = async (req, res, next) => {
//check that it exists and matches captcha in DB
if (!captcha || !captcha.value || captcha.value.text !== input) {
return res.status(403).render('message', {
return dynamicResponse(req, res, 403, 'message', {
'title': 'Forbidden',
'message': 'Incorrect captcha'
});

@ -12,7 +12,7 @@ module.exports = async (req, res, next) => {
if (globalBans.length > 0 || (res.locals.permLevel >= 4 && globalBans.length !== bans.length)) {
//board staff bypass bans on their own board, but not global bans
const allowAppeal = bans.filter(ban => ban.allowAppeal === true && ban.appeal === null).length > 0;
const unseenBans = bans.filter(b => !b.seen).map(b._id);
const unseenBans = bans.filter(b => !b.seen).map(b => b._id);
await Bans.markSeen(unseenBans); //mark bans as seen
return res.status(403).render('ban', {
bans: bans,

@ -0,0 +1,10 @@
'use strict';
module.exports = (req, res, code, page, data) => {
res.status(code);
if (req.headers['x-using-xhr'] != null) {
return res.json(data);
} else {
return res.render(page, data);
}
}

@ -85,7 +85,7 @@ module.exports = {
//building multiple pages (for rebuilds)
buildBoardMultiple: async (options) => {
const start = process.hrtime();
const maxPage = Math.min(Math.ceil((await Posts.getPages(options.board._id)) / 10), Math.ceil(options.board.settings.threadLimit/10));
const maxPage = Math.min(Math.ceil((await Posts.getPages(options.board._id)) / 10), Math.ceil(options.board.settings.threadLimit/10)) || 1;
if (options.endpage === 0) {
//deleted only/all posts, so only 1 page will remain
options.endpage = 1;

@ -29,6 +29,7 @@ const path = require('path')
, spamCheck = require(__dirname+'/../../helpers/checks/spamcheck.js')
, { postPasswordSecret } = require(__dirname+'/../../configs/main.json')
, buildQueue = require(__dirname+'/../../queue.js')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, { buildThread } = require(__dirname+'/../../helpers/tasks.js');
module.exports = async (req, res, next) => {
@ -37,7 +38,7 @@ module.exports = async (req, res, next) => {
const flood = await spamCheck(req, res);
if (flood) {
deleteTempFiles(req).catch(e => console.error);
return res.status(429).render('message', {
return dynamicResponse(req, res, 429, 'message', {
'title': 'Flood detected',
'message': 'Please wait before making another post, or a post similar to another user',
'redirect': `/${req.params.board}${req.body.thread ? '/thread/' + req.body.thread + '.html' : ''}`
@ -55,7 +56,7 @@ module.exports = async (req, res, next) => {
captchaMode, locked, allowedFileTypes, flags } = res.locals.board.settings;
if (locked === true) {
await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('message', {
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
'message': 'Board is locked.',
'redirect': redirect
@ -65,7 +66,7 @@ module.exports = async (req, res, next) => {
thread = await Posts.getPost(req.params.board, req.body.thread, true);
if (!thread || thread.thread != null) {
await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('message', {
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
'message': 'Thread does not exist.',
'redirect': redirect
@ -75,7 +76,7 @@ module.exports = async (req, res, next) => {
redirect += `thread/${req.body.thread}.html`
if (thread.locked && res.locals.permLevel >= 4) {
await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('message', {
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
'message': 'Thread Locked',
'redirect': redirect
@ -83,7 +84,7 @@ module.exports = async (req, res, next) => {
}
if (thread.replyposts >= replyLimit && !thread.cyclic) { //reply limit
await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('message', {
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
'message': 'Thread reached reply limit',
'redirect': redirect
@ -92,7 +93,7 @@ module.exports = async (req, res, next) => {
}
if (res.locals.numFiles > maxFiles) {
await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('message', {
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
'message': `Too many files. Max files per post is ${maxFiles}.`,
'redirect': redirect
@ -105,7 +106,7 @@ module.exports = async (req, res, next) => {
if (containsFilter === true) {
await deleteTempFiles(req).catch(e => console.error);
if (filterMode === 1) {
return res.status(400).render('message', {
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
'message': 'Your post was blocked by a word filter',
'redirect': redirect
@ -139,7 +140,7 @@ module.exports = async (req, res, next) => {
for (let i = 0; i < res.locals.numFiles; i++) {
if (!fileCheckMimeType(req.files.file[i].mimetype, allowedFileTypes)) {
await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('message', {
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
'message': `Mime type ${req.files.file[i].mimetype} for "${req.files.file[i].name}" not allowed.`,
'redirect': redirect
@ -191,7 +192,7 @@ module.exports = async (req, res, next) => {
videoData.streams = videoData.streams.filter(stream => stream.width != null); //filter to only video streams or something with a resolution
if (videoData.streams.length <= 0) {
await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('message', {
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
'message': 'Audio only file not supported (yet)',
'redirect': redirect

@ -17,6 +17,7 @@ const express = require('express')
, themes = require(__dirname+'/helpers/themes.js')
, Mongo = require(__dirname+'/db/db.js')
, Socketio = require(__dirname+'/socketio.js')
, dynamicResponse = require(__dirname+'/helpers/dynamic.js')
, CachePugTemplates = require('cache-pug-templates');
(async () => {
@ -93,8 +94,9 @@ const express = require('express')
return res.status(403).send('Invalid CSRF token');
}
console.error(err.stack);
return res.status(500).render('message', {
return dynamicResponse(req, res, 500, 'message', {
'title': 'Internal Server Error',
'error': 'Internal Server Error', //what to put here?
'redirect': req.headers.referer || '/'
});
})

@ -0,0 +1,17 @@
.modal-bg
.modal
.row
p.bold #{title}
a.close.postform-style#modalclose X
.row
ul.nomarks
if message
li #{message}
else if error
li #{error}
else if messages
each msg in messages
li #{msg}
else if errors
each err in errors
li #{err}
Loading…
Cancel
Save