From a5d5668613d9a3d04c9a4f8b69122d02b7322137 Mon Sep 17 00:00:00 2001 From: Benjamin Auder <benjamin.auder@somewhere> Date: Sat, 22 Dec 2018 15:37:48 +0100 Subject: [PATCH] Almost finished: just translations TODO --- public/javascripts/components/game.js | 99 +++++++------ .../javascripts/components/problemSummary.js | 6 +- public/javascripts/components/problems.js | 31 +++-- public/javascripts/components/rules.js | 6 +- public/javascripts/variant.js | 9 +- public/javascripts/variants/Crazyhouse.js | 9 +- public/stylesheets/index.sass | 4 - public/stylesheets/layout.sass | 6 + public/stylesheets/variant.sass | 131 +++++++++++------- routes/all.js | 62 ++++++--- views/index.pug | 8 +- views/variant.pug | 24 ++-- views/welcome/en.pug | 2 +- views/welcome/fr.pug | 2 +- 14 files changed, 246 insertions(+), 153 deletions(-) diff --git a/public/javascripts/components/game.js b/public/javascripts/components/game.js index 773bc1f1..e2e908a0 100644 --- a/public/javascripts/components/game.js +++ b/public/javascripts/components/game.js @@ -40,7 +40,6 @@ Vue.component('my-game', { }, render(h) { const [sizeX,sizeY] = [V.size.x,V.size.y]; - const smallScreen = (window.innerWidth <= 420); // Precompute hints squares to facilitate rendering let hintSquares = doubleArray(sizeX, sizeY, false); this.possibleMoves.forEach(m => { hintSquares[m.end.x][m.end.y] = true; }); @@ -56,10 +55,11 @@ Vue.component('my-game', { attrs: { "aria-label": 'New online game' }, 'class': { "tooltip": true, + "play": true, "bottom": true, //display below "seek": this.seek, "playing": this.mode == "human", - "small": smallScreen, + "spaceright": true, }, }, [h('i', { 'class': { "material-icons": true } }, "accessibility")]) @@ -73,9 +73,10 @@ Vue.component('my-game', { attrs: { "aria-label": 'New game VS computer' }, 'class': { "tooltip":true, + "play": true, "bottom": true, "playing": this.mode == "computer", - "small": smallScreen, + "spaceright": true, }, }, [h('i', { 'class': { "material-icons": true } }, "computer")]) @@ -90,9 +91,10 @@ Vue.component('my-game', { attrs: { "aria-label": 'New IRL game' }, 'class': { "tooltip":true, + "play": true, "bottom": true, "playing": this.mode == "friend", - "small": smallScreen, + "spaceright": true, }, }, [h('i', { 'class': { "material-icons": true } }, "people")]) @@ -105,27 +107,33 @@ Vue.component('my-game', { ? parseFloat(window.getComputedStyle(square00).width.slice(0,-2)) : 0; const settingsBtnElt = document.getElementById("settingsBtn"); - const indicWidth = !!settingsBtnElt //-2 for border: - ? parseFloat(window.getComputedStyle(settingsBtnElt).height.slice(0,-2)) - 2 - : (smallScreen ? 31 : 37); + const settingsStyle = !!settingsBtnElt + ? window.getComputedStyle(settingsBtnElt) + : {width:"46px", height:"26px"}; + const [indicWidth,indicHeight] = //[44,24]; + [ + // NOTE: -2 for border + parseFloat(settingsStyle.width.slice(0,-2)) - 2, + parseFloat(settingsStyle.height.slice(0,-2)) - 2 + ]; + let aboveBoardElts = []; if (["chat","human"].includes(this.mode)) { const connectedIndic = h( 'div', { "class": { - "topindicator": true, "indic-left": true, "connected": this.oppConnected, "disconnected": !this.oppConnected, }, style: { "width": indicWidth + "px", - "height": indicWidth + "px", + "height": indicHeight + "px", }, } ); - elementArray.push(connectedIndic); + aboveBoardElts.push(connectedIndic); } if (this.mode == "chat") { @@ -139,15 +147,14 @@ Vue.component('my-game', { }, 'class': { "tooltip": true, - "topindicator": true, + "play": true, + "above-board": true, "indic-left": true, - "settings-btn": !smallScreen, - "settings-btn-small": smallScreen, }, }, [h('i', { 'class': { "material-icons": true } }, "chat")] ); - elementArray.push(chatButton); + aboveBoardElts.push(chatButton); } else if (this.mode == "computer") { @@ -161,32 +168,30 @@ Vue.component('my-game', { }, 'class': { "tooltip": true, - "topindicator": true, + "play": true, + "above-board": true, "indic-left": true, - "settings-btn": !smallScreen, - "settings-btn-small": smallScreen, }, }, [h('i', { 'class': { "material-icons": true } }, "clear")] ); - elementArray.push(clearButton); + aboveBoardElts.push(clearButton); } const turnIndic = h( 'div', { "class": { - "topindicator": true, "indic-right": true, "white-turn": this.vr.turn=="w", "black-turn": this.vr.turn=="b", }, style: { "width": indicWidth + "px", - "height": indicWidth + "px", + "height": indicHeight + "px", }, } ); - elementArray.push(turnIndic); + aboveBoardElts.push(turnIndic); const settingsBtn = h( 'button', { @@ -197,15 +202,20 @@ Vue.component('my-game', { }, 'class': { "tooltip": true, - "topindicator": true, + "play": true, + "above-board": true, "indic-right": true, - "settings-btn": !smallScreen, - "settings-btn-small": smallScreen, }, }, [h('i', { 'class': { "material-icons": true } }, "settings")] ); - elementArray.push(settingsBtn); + aboveBoardElts.push(settingsBtn); + elementArray.push( + h('div', + { "class": { "aboveboard-wrapper": true } }, + aboveBoardElts + ) + ); if (this.mode == "problem") { // Show problem instructions @@ -273,7 +283,10 @@ Vue.component('my-game', { (!["idle","chat"].includes(this.mode) || this.cursor==this.vr.moves.length); const gameDiv = h('div', { - 'class': { 'game': true }, + 'class': { + 'game': true, + 'clearer': true, + }, }, [_.range(sizeX).map(i => { let ci = (this.mycolor=='w' ? i : sizeX-i-1); @@ -354,8 +367,8 @@ Vue.component('my-game', { attrs: { "aria-label": 'Resign' }, 'class': { "tooltip":true, + "play": true, "bottom": true, - "small": smallScreen, }, }, [h('i', { 'class': { "material-icons": true } }, "flag")]) @@ -370,7 +383,7 @@ Vue.component('my-game', { on: { click: e => this.undo() }, attrs: { "aria-label": 'Undo' }, "class": { - "small": smallScreen, + "play": true, "spaceleft": true, }, }, @@ -379,7 +392,10 @@ Vue.component('my-game', { { on: { click: e => this.play() }, attrs: { "aria-label": 'Play' }, - "class": { "small": smallScreen }, + "class": { + "play": true, + "spaceleft": true, + }, }, [h('i', { 'class': { "material-icons": true } }, "fast_forward")]), ] @@ -394,7 +410,7 @@ Vue.component('my-game', { on: { click: this.undoInGame }, attrs: { "aria-label": 'Undo' }, "class": { - "small": smallScreen, + "play": true, "spaceleft": true, }, }, @@ -404,7 +420,10 @@ Vue.component('my-game', { { on: { click: () => { this.mycolor = this.vr.getOppCol(this.mycolor) } }, attrs: { "aria-label": 'Flip' }, - "class": { "small": smallScreen }, + "class": { + "play": true, + "spaceleft": true, + }, }, [h('i', { 'class': { "material-icons": true } }, "cached")] ), @@ -419,13 +438,13 @@ Vue.component('my-game', { { myReservePiecesArray.push(h('div', { - 'class': {'board':true, ['board'+sizeY]:true}, + 'class': {'board':true, ['board'+sizeY+'-reserve']:true}, attrs: { id: this.getSquareId({x:sizeX+shiftIdx,y:i}) } }, [ h('img', { - 'class': {"piece":true}, + 'class': {"piece":true, "reserve":true}, attrs: { "src": "/images/pieces/" + this.vr.getReservePpath(this.mycolor,i) + ".svg", @@ -443,13 +462,13 @@ Vue.component('my-game', { { oppReservePiecesArray.push(h('div', { - 'class': {'board':true, ['board'+sizeY]:true}, + 'class': {'board':true, ['board'+sizeY+'-reserve']:true}, attrs: { id: this.getSquareId({x:sizeX+(1-shiftIdx),y:i}) } }, [ h('img', { - 'class': {"piece":true}, + 'class': {"piece":true, "reserve":true}, attrs: { "src": "/images/pieces/" + this.vr.getReservePpath(oppCol,i) + ".svg", @@ -801,6 +820,7 @@ Vue.component('my-game', { ), h('button', { + attrs: { id: "sendChatBtn"}, on: { click: this.sendChat }, domProps: { innerHTML: "Send" }, } @@ -883,6 +903,7 @@ Vue.component('my-game', { [ h('h3', { + "class": { clickable: true }, domProps: { innerHTML: "Show solution" }, on: { click: this.toggleShowSolution }, } @@ -921,10 +942,10 @@ Vue.component('my-game', { { 'class': { "col-sm-12":true, - "col-md-8":true, - "col-md-offset-2":true, - "col-lg-6":true, - "col-lg-offset-3":true, + "col-md-10":true, + "col-md-offset-1":true, + "col-lg-8":true, + "col-lg-offset-2":true, }, // NOTE: click = mousedown + mouseup on: { diff --git a/public/javascripts/components/problemSummary.js b/public/javascripts/components/problemSummary.js index e7e1db7c..6b006cbb 100644 --- a/public/javascripts/components/problemSummary.js +++ b/public/javascripts/components/problemSummary.js @@ -2,11 +2,11 @@ Vue.component('my-problem-summary', { props: ['prob','preview'], template: ` - <div class="problem row" @click="showProblem()"> - <div class="col-sm-12 col-md-6 col-lg-3 diagram" + <div class="row problem clickable" @click="showProblem()"> + <div class="col-sm-6 diagram" v-html="getDiagram(prob.fen)"> </div> - <div class="col-sm-12 col-md-6 col-lg-9"> + <div class="col-sm-6"> <p v-html="prob.instructions"></p> <p v-if="preview" v-html="prob.solution"></p> <p v-else class="problem-time">{{ timestamp2date(prob.added) }}</p> diff --git a/public/javascripts/components/problems.js b/public/javascripts/components/problems.js index d9265a36..b67203a3 100644 --- a/public/javascripts/components/problems.js +++ b/public/javascripts/components/problems.js @@ -11,10 +11,21 @@ Vue.component('my-problems', { }; }, template: ` - <div> - <button @click="fetchProblems('backward')">Previous</button> - <button @click="fetchProblems('forward')">Next</button> - <button @click="showNewproblemModal">New</button> + <div class="col-sm-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2"> + <div id="problemControls" class="button-group"> + <button aria-label="Load previous problems" class="tooltip" + @click="fetchProblems('backward')"> + <i class="material-icons">skip_previous</i> + </button> + <button aria-label="Add a problem" class="tooltip" + @click="showNewproblemModal"> + New + </button> + <button aria-label="Load next problems" class="tooltip" + @click="fetchProblems('forward')"> + <i class="material-icons">skip_next</i> + </button> + </div> <my-problem-summary v-on:show-problem="bubbleUp(p)" v-for="(p,idx) in sortedProblems" v-bind:prob="p" v-bind:preview="false" v-bind:key="idx"> @@ -46,9 +57,9 @@ Vue.component('my-problems', { <label for="modal-newproblem" class="modal-close"></label> <my-problem-summary v-bind:prob="newProblem" v-bind:preview="true"> </my-problem-summary> - <div class="col-sm-12 col-md-6 col-lg-3 col-lg-offset-3 topspace"> - <button @click="sendNewProblem()">Send</button> + <div class="button-group"> <button @click="newProblem.stage='nothing'">Cancel</button> + <button @click="sendNewProblem()">Send</button> </div> </div> </div> @@ -59,9 +70,6 @@ Vue.component('my-problems', { // Newest problem first return this.problems.sort((p1,p2) => { return p2.added - p1.added; }); }, - mailErrProblem: function() { - return "mailto:contact@vchess.club?subject=[" + variant + " problems] error"; - }, }, methods: { // Propagate "show problem" event to parent component (my-variant) @@ -69,7 +77,6 @@ Vue.component('my-problems', { this.$emit('show-problem', JSON.stringify(problem)); }, fetchProblems: function(direction) { - return; //TODO: re-activate after server side is implemented (see routes/all.js) if (this.problems.length == 0) return; //what could we do?! // Search for newest date (or oldest) @@ -96,6 +103,10 @@ Vue.component('my-problems', { previewNewProblem: function() { if (!V.IsGoodFen(this.newProblem.fen)) return alert("Bad FEN string"); + if (this.newProblem.instructions.length == 0) + return alert("Empty instructions"); + if (this.newProblem.solution.length == 0) + return alert("Empty solution"); this.newProblem.stage = "preview"; }, sendNewProblem: function() { diff --git a/public/javascripts/components/rules.js b/public/javascripts/components/rules.js index 829bf3b1..d8aaa0fc 100644 --- a/public/javascripts/components/rules.js +++ b/public/javascripts/components/rules.js @@ -3,7 +3,11 @@ Vue.component('my-rules', { data: function() { return { content: "" }; }, - template: `<div v-html="content" class="section-content"></div>`, + template: ` + <div class="col-sm-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2"> + <div v-html="content" class="section-content"></div> + </div> + `, mounted: function() { // AJAX request to get rules content (plain text, HTML) ajax("/rules/" + variant, "GET", response => { diff --git a/public/javascripts/variant.js b/public/javascripts/variant.js index f77be329..d617c60a 100644 --- a/public/javascripts/variant.js +++ b/public/javascripts/variant.js @@ -1,23 +1,26 @@ new Vue({ el: "#variantPage", data: { - display: "game", //default: play! + display: "play", //default: play! problem: undefined, //current problem in view }, created: function() { const url = window.location.href; const hashPos = url.indexOf("#"); + console.log(hashPos + " " + url); if (hashPos >= 0) this.setDisplay(url.substr(hashPos+1)); }, methods: { showProblem: function(problemTxt) { this.problem = JSON.parse(problemTxt); - this.display = "game"; + this.display = "play"; }, setDisplay: function(elt) { this.display = elt; - document.getElementById("drawer-control").checked = false; + let menuToggle = document.getElementById("drawer-control"); + if (!!menuToggle) + menuToggle.checked = false; }, }, }); diff --git a/public/javascripts/variants/Crazyhouse.js b/public/javascripts/variants/Crazyhouse.js index 1fff1e91..f36bfcc4 100644 --- a/public/javascripts/variants/Crazyhouse.js +++ b/public/javascripts/variants/Crazyhouse.js @@ -11,10 +11,9 @@ class CrazyhouseRules extends ChessRules // 6) Check promoted array if (!fenParsed.promoted) return false; - fenpromoted = fenParsed.promoted; - if (fenpromoted == "-") + if (fenParsed.promoted == "-") return true; //no promoted piece on board - const squares = fenpromoted.split(","); + const squares = fenParsed.promoted.split(","); for (let square of squares) { const c = V.SquareToCoords(square); @@ -48,8 +47,8 @@ class CrazyhouseRules extends ChessRules getReserveFen() { - let counts = _.map(_.range(10), 0); - for (let i=0; i<V.PIECES.length; i++) + let counts = new Array(10); + for (let i=0; i<V.PIECES.length-1; i++) //-1: no king reserve { counts[i] = this.reserve["w"][V.PIECES[i]]; counts[5+i] = this.reserve["b"][V.PIECES[i]]; diff --git a/public/stylesheets/index.sass b/public/stylesheets/index.sass index 0c6d92a5..0d7803f4 100644 --- a/public/stylesheets/index.sass +++ b/public/stylesheets/index.sass @@ -42,7 +42,6 @@ #helpMenu float: right - cursor: pointer @media screen and (max-width: 767px) .info-container p @@ -50,7 +49,6 @@ #flagMenu float: right - cursor: pointer margin-right: 10px @media screen and (max-width: 767px) margin-right: 5px @@ -82,7 +80,6 @@ margin-top: 0 color: var(--a-link-color) text-decoration: underline - cursor: pointer #welcome max-width: 767px @@ -111,5 +108,4 @@ text-align: left border: 0 #disableMsg - cursor: pointer color: darkred diff --git a/public/stylesheets/layout.sass b/public/stylesheets/layout.sass index 38b78b1e..14892250 100644 --- a/public/stylesheets/layout.sass +++ b/public/stylesheets/layout.sass @@ -33,6 +33,12 @@ a .emphasis font-style: italic +.clickable + cursor: pointer + +.clearer + clear: both + .red color: #cc3300 diff --git a/public/stylesheets/variant.sass b/public/stylesheets/variant.sass index da55577f..f5599964 100644 --- a/public/stylesheets/variant.sass +++ b/public/stylesheets/variant.sass @@ -2,20 +2,23 @@ #menuBar background: linear-gradient(#e66465, #9198e5) - height: 77px + height: 29px margin-bottom: 10px @media screen and (max-width: 767px) height: 100% margin-bottom: 0 @media screen and (min-width: 768px) width: 100% + overflow: hidden a#homeLink - margin: 27px 0 0 10px + margin-left: 10px + margin-top: 2px + color: black display: inline-block @media screen and (max-width: 767px) - margin: 10px 0 0 10px display: block + margin: 5px 0 0 12px .info-container display: inline-block @@ -27,9 +30,9 @@ a#homeLink color: black a, p display: inline-block - padding: 3px + padding: 0 border: 1px solid black; - margin: 25px 0 0 15px + margin: 1px 0 0 15px @media screen and (max-width: 767px) margin-top: 10px display: block @@ -37,45 +40,70 @@ a#homeLink #helpMenu @media screen and (min-width: 768px) float: right - cursor: pointer - @media screen and (max-width: 767px) - .info-container - p - margin-right: 5px + .info-container + p + margin: 1px 0 0 15px #flagMenu @media screen and (min-width: 768px) + margin-top: 1px float: right - cursor: pointer margin: 0 15px @media screen and (max-width: 767px) - margin-right: 5px + margin: 25px 5px 0 15px img display: inline-block - height: 30px - margin-top: 25px + margin: 0 + height: 25px label.drawer-toggle padding: 0 &::before - font-size: 2.5em; - max-height: 43px; - top: -10px; - left: 10px + font-size: 2em; + max-height: 32px; + top: -7px; + left: 5px // Game section: -.topindicator - position: relative +button.play + height: 24px + margin: 0 + padding: 0 10px 24px 10px + box-sizing: border-box border: 1px solid brown +button.play.spaceleft + margin-left: 15px +button.play.spaceright + margin-right: 15px + +.aboveboard-wrapper + width: 80vh + margin: 0 auto + @media screen and (max-width: 767px) + width: 100% + margin: 0 + +button.above-board + margin-left: 15px + margin-right: 15px + +i.material-icons + font-size: 24px .indic-left + border: 1px solid brown float: left - margin: 0 0 var(--universal-margin) 20px + margin: 0 0 var(--universal-margin) 10vh + @media screen and (max-width: 767px) + margin-left: 20px .indic-right + border: 1px solid brown float: right - margin: 0 20px var(--universal-margin) 0 + margin: 0 10vh var(--universal-margin) 0 + @media screen and (max-width: 767px) + margin-right: 20px .my-chatmsg color: black @@ -83,18 +111,16 @@ label.drawer-toggle .opp-chatmsg color: blue +// TODO: this fix is not good (button height 0 if chat overflow window height) +#sendChatBtn + min-height: 42px + .connected background-color: green .disconnected background-color: red -.settings-btn - padding: 6px 7px 0 7px - -.settings-btn-small - padding: 0 3px - .white-turn background-color: white @@ -111,12 +137,12 @@ button.seek &:hover background-color: #cc99ff +.game.reserve-div + margin-bottom: 18px + .reserve-count padding-left: 40% -.reserve-div - margin-bottom: 20px - .reserve-row-1 margin-bottom: 15px @@ -130,6 +156,10 @@ div.board8 width: 12.5% padding-bottom: 12.5% +div.board8-reserve + width: 10% + padding-bottom: 10% + div.board10 width: 10% padding-bottom: 10% @@ -138,10 +168,16 @@ div.board11 width: 9.09% padding-bottom: 9.1% +// NOTE: no variants with reserve of size != 8 + .game - clear: both + width: 80vh + margin: 0 auto .board cursor: pointer + @media screen and (max-width: 767px) + width: 100% + margin: 0 #choices margin: 0 auto 0 auto @@ -226,6 +262,12 @@ img.ghost margin-left: 0 margin-right: 0 +#modal-eog+div .card + overflow: hidden + +#actions + margin: 10px 0 + // Rules section: .warn @@ -264,9 +306,6 @@ figure.diagram-container clear: both padding-top: 5px -.spaceleft - margin-left: 30px - p.boxed background-color: #FFCC66 padding: 5px @@ -323,21 +362,19 @@ ul:not(.browser-default) > li #problem-solution display: none -.topspace - margin-top: 15px - -.problem - cursor: pointer - margin-bottom: 15px - #solution-div h3 - cursor: pointer + background-color: lightgrey + padding: 3px 5px .newproblem-form, .newproblem-preview max-width: 90% -.clickable - cursor: pointer +#problemControls + width: 75% + margin: 0 auto + @media screen and (max-width: 767px) + width: 100% + margin: 0 -.clearer - clear: both +.problem + margin: 10px 0 diff --git a/routes/all.js b/routes/all.js index 691662f1..49ca802f 100644 --- a/routes/all.js +++ b/routes/all.js @@ -4,6 +4,7 @@ const createError = require('http-errors'); const sqlite3 = require('sqlite3');//.verbose(); const db = new sqlite3.Database('db/vchess.sqlite'); const sanitizeHtml = require('sanitize-html'); +const MaxNbProblems = 2; const supportedLang = ["fr","en"]; function selectLanguage(req, res) @@ -49,28 +50,30 @@ router.get('/', function(req, res, next) { }); // Variant -router.get("/:vname([a-zA-Z0-9]+)", (req,res,next) => { - const vname = req.params["vname"]; +router.get("/:variant([a-zA-Z0-9]+)", (req,res,next) => { + const vname = req.params["variant"]; db.serialize(function() { db.all("SELECT * FROM Variants WHERE name='" + vname + "'", (err,variant) => { if (!!err) return next(err); if (!variant || variant.length==0) return next(createError(404)); - // TODO (later...) get only n=100(?) most recent problems - db.all("SELECT * FROM Problems WHERE variant='" + vname + "'", - (err2,problems) => { - if (!!err2) - return next(err2); - res.render('variant', { - title: vname + ' Variant', - variant: vname, - problemArray: problems, - lang: selectLanguage(req, res), - languages: supportedLang, - }); - } - ); + // Get only N most recent problems + const query2 = "SELECT * FROM Problems " + + "WHERE variant='" + vname + "' " + + "ORDER BY added DESC " + + "LIMIT " + MaxNbProblems; + db.all(query2, (err2,problems) => { + if (!!err2) + return next(err2); + res.render('variant', { + title: vname + ' Variant', + variant: vname, + problemArray: problems, + lang: selectLanguage(req, res), + languages: supportedLang, + }); + }); }); }); }); @@ -83,13 +86,26 @@ router.get("/rules/:variant([a-zA-Z0-9]+)", (req,res) => { res.render("rules/" + req.params["variant"] + "/" + lang); }); -// Fetch 10 previous or next problems (AJAX) +// Fetch N previous or next problems (AJAX) router.get("/problems/:variant([a-zA-Z0-9]+)", (req,res) => { if (!req.xhr) return res.json({errmsg: "Unauthorized access"}); - // TODO: next or previous: in params + timedate (of current oldest or newest) + const vname = req.params["variant"]; + const directionStr = (req.query.direction == "forward" ? ">" : "<"); + const lastDt = req.query.last_dt; + if (!lastDt.match(/[0-9]+/)) + return res.json({errmsg: "Bad timestamp"}); db.serialize(function() { - //TODO + const query = "SELECT * FROM Problems " + + "WHERE variant='" + vname + "' " + + " AND added " + directionStr + " " + lastDt + " " + + "ORDER BY added " + (directionStr=="<" ? "DESC " : "") + + "LIMIT " + MaxNbProblems; + db.all(query, (err,problems) => { + if (!!err) + return res.json(err); + return res.json({problems: problems}); + }); }); }); @@ -103,8 +119,12 @@ router.post("/problems/:variant([a-zA-Z0-9]+)", (req,res) => { const fen = req.body["fen"]; if (!fen.match(/^[a-zA-Z0-9, /-]*$/)) return res.json({errmsg: "Bad characters in FEN string"}); - const instructions = sanitizeHtml(req.body["instructions"]); - const solution = sanitizeHtml(req.body["solution"]); + const instructions = sanitizeHtml(req.body["instructions"]).trim(); + const solution = sanitizeHtml(req.body["solution"]).trim(); + if (instructions.length == 0) + return res.json({errmsg: "Empty instructions"}); + if (solution.length == 0) + return res.json({errmsg: "Empty solution"}); db.serialize(function() { let stmt = db.prepare("INSERT INTO Problems " + "(added,variant,fen,instructions,solution) VALUES (?,?,?,?,?)"); diff --git a/views/index.pug b/views/index.pug index c8c90c52..81aafdb1 100644 --- a/views/index.pug +++ b/views/index.pug @@ -12,9 +12,11 @@ block content .info-container p vchess.club img(src="/images/index/wildebeest.svg") - #flagMenu(onClick="document.getElementById('modalLang').checked=true") + #flagMenu.clickable( + onClick="document.getElementById('modalLang').checked=true") img(src="/images/flags/" + lang + ".svg") - #helpMenu(onClick="document.getElementById('modalHelp').checked=true") + #helpMenu.clickable( + onClick="document.getElementById('modalHelp').checked=true") .info-container p Help .row @@ -27,7 +29,7 @@ block content div(role="dialog") #b4welcome.card.text-center.small-modal h3.blue First visit? - p#readThis(@click="showWelcomeMsg") >>> Please read this <<< + p#readThis.clickable(@click="showWelcomeMsg") >>> Please read this <<< case lang when "en" include welcome/en.pug diff --git a/views/variant.pug b/views/variant.pug index a3b846f7..9400ba58 100644 --- a/views/variant.pug +++ b/views/variant.pug @@ -12,32 +12,26 @@ block content input#drawer-control.drawer(type="checkbox") #menuBar label.drawer-close(for="drawer-control") - a#homeLink.conditional-jump(href="/") + a#homeLink(href="/") i.material-icons home .info-container - a.conditional-jump(href="#rules" @click="setDisplay('rules')") + a(href="#rules" @click="setDisplay('rules')") | Rules - a.conditional-jump(href="#play" @click="setDisplay('game')") + a(href="#play" @click="setDisplay('play')") | Play! - a.conditional-jump(href="#problems" @click="setDisplay('problems')") + a(href="#problems" @click="setDisplay('problems')") | Problems - #flagMenu.conditional-jump( + #flagMenu.clickable( onClick="document.getElementById('modalLang').checked=true") img(src="/images/flags/" + lang + ".svg") - #helpMenu.conditional-jump( + #helpMenu.clickable( onClick="document.getElementById('modalHelp').checked=true") .info-container p Help .row - .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2( - v-show="display=='rules'") - my-rules - .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2( - v-show="display=='game'") - my-game(v-bind:problem="problem") - .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2( - v-show="display=='problems'") - my-problems(v-on:show-problem="showProblem($event)") + my-rules(v-show="display=='rules'") + my-game(v-show="display=='play'" v-bind:problem="problem") + my-problems(v-show="display=='problems'" v-on:show-problem="showProblem($event)") // (Some) Modals: include modal-help.pug include modal-lang.pug diff --git a/views/welcome/en.pug b/views/welcome/en.pug index 28fb943e..dbaf234c 100644 --- a/views/welcome/en.pug +++ b/views/welcome/en.pug @@ -46,6 +46,6 @@ div(role="dialog") For informations about hundreds (if not thousands) of variants, you can visit the excellent #[a(href="https://www.chessvariants.com/") chessvariants] website. - p#disableMsg(@click="markAsVisited") + p#disableMsg.clickable(@click="markAsVisited") | Click here to not show this message next time p.smallfont Image credit: #[a(href=wikipediaUrl) Wikipedia] diff --git a/views/welcome/fr.pug b/views/welcome/fr.pug index d1bff4bf..33b5fb6f 100644 --- a/views/welcome/fr.pug +++ b/views/welcome/fr.pug @@ -46,6 +46,6 @@ div(role="dialog") Pour s'informer sur des centaines de variantes (au moins), je vous invite à visiter l'excellent site #[a(href="https://www.chessvariants.com/") chessvariants]. - p#disableMsg(@click="markAsVisited") + p#disableMsg.clickable(@click="markAsVisited") | Cliquer ici pour ne pas montrer ce message la prochaine fois p.smallfont Crédit image: #[a(href=wikipediaUrl) Wikipedia] -- 2.44.0