diff --git a/controllers/forms/makepost.js b/controllers/forms/makepost.js index 88feb3c9..43e80716 100644 --- a/controllers/forms/makepost.js +++ b/controllers/forms/makepost.js @@ -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' : ''}` diff --git a/gulp/res/css/style.css b/gulp/res/css/style.css index 490fb20f..a43fea02 100644 --- a/gulp/res/css/style.css +++ b/gulp/res/css/style.css @@ -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; diff --git a/gulp/res/js/modal.js b/gulp/res/js/modal.js new file mode 100644 index 00000000..96034cab --- /dev/null +++ b/gulp/res/js/modal.js @@ -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]/;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;} \ No newline at end of file diff --git a/gulp/res/js/progress.js b/gulp/res/js/progress.js index 4de91b48..9906b95b 100644 --- a/gulp/res/js/progress.js +++ b/gulp/res/js/progress.js @@ -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'; } } diff --git a/gulpfile.js b/gulpfile.js index 20921e45..f3723ef3 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -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()) diff --git a/helpers/captcha/captchaverify.js b/helpers/captcha/captchaverify.js index e34db63b..95c8e93f 100644 --- a/helpers/captcha/captchaverify.js +++ b/helpers/captcha/captchaverify.js @@ -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' }); diff --git a/helpers/checks/bancheck.js b/helpers/checks/bancheck.js index 2f9b52e8..767a9d63 100644 --- a/helpers/checks/bancheck.js +++ b/helpers/checks/bancheck.js @@ -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, diff --git a/helpers/dynamic.js b/helpers/dynamic.js new file mode 100644 index 00000000..f43d0d65 --- /dev/null +++ b/helpers/dynamic.js @@ -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); + } +} diff --git a/helpers/tasks.js b/helpers/tasks.js index 38ec47f4..5d0a01a3 100644 --- a/helpers/tasks.js +++ b/helpers/tasks.js @@ -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; diff --git a/models/forms/makepost.js b/models/forms/makepost.js index 67d1f34d..450a1d40 100644 --- a/models/forms/makepost.js +++ b/models/forms/makepost.js @@ -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 diff --git a/server.js b/server.js index 39d01fff..ad12e67b 100644 --- a/server.js +++ b/server.js @@ -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 || '/' }); }) diff --git a/views/includes/modal.pug b/views/includes/modal.pug new file mode 100644 index 00000000..94cc333a --- /dev/null +++ b/views/includes/modal.pug @@ -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}