Almost finished: just translations TODO
authorBenjamin Auder <benjamin.auder@somewhere>
Sat, 22 Dec 2018 14:37:48 +0000 (15:37 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Sat, 22 Dec 2018 14:37:48 +0000 (15:37 +0100)
14 files changed:
public/javascripts/components/game.js
public/javascripts/components/problemSummary.js
public/javascripts/components/problems.js
public/javascripts/components/rules.js
public/javascripts/variant.js
public/javascripts/variants/Crazyhouse.js
public/stylesheets/index.sass
public/stylesheets/layout.sass
public/stylesheets/variant.sass
routes/all.js
views/index.pug
views/variant.pug
views/welcome/en.pug
views/welcome/fr.pug

index 773bc1f..e2e908a 100644 (file)
@@ -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: {
index e7e1db7..6b006cb 100644 (file)
@@ -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>
index d9265a3..b67203a 100644 (file)
@@ -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() {
index 829bf3b..d8aaa0f 100644 (file)
@@ -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 => {
index f77be32..d617c60 100644 (file)
@@ -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;
                },
        },
 });
index 1fff1e9..f36bfcc 100644 (file)
@@ -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]];
index 0c6d92a..0d7803f 100644 (file)
@@ -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
           text-align: left
           border: 0
   #disableMsg
-    cursor: pointer
     color: darkred
index 38b78b1..1489225 100644 (file)
@@ -33,6 +33,12 @@ a
 .emphasis
   font-style: italic
 
+.clickable
+  cursor: pointer
+
+.clearer
+  clear: both
+
 .red
   color: #cc3300
 
index da55577..f559996 100644 (file)
@@ -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
index 691662f..49ca802 100644 (file)
@@ -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 (?,?,?,?,?)");
index c8c90c5..81aafdb 100644 (file)
@@ -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
index a3b846f..9400ba5 100644 (file)
@@ -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
index 28fb943..dbaf234 100644 (file)
@@ -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]
index d1bff4b..33b5fb6 100644 (file)
@@ -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]